【问题标题】:How can I get data from ObservedObject with onReceive in SwiftUI?如何在 SwiftUI 中使用 onReceive 从 ObservedObject 获取数据?
【发布时间】:2020-01-25 22:17:36
【问题描述】:

在我的 SwiftUI 应用程序中,每次值更改时,我都需要从 ObservedObject 获取数据。我知道我们可以用 .onReceive 做到这一点?我不太了解 Apple 关于它的文档。我不知道我该怎么做。

我的代码:

import SwiftUI
import CoreLocation

struct Compass: View {
  
  @StateObject var location = LocationManager()
  @State private var angle: CGFloat = 0
  
  var body: some View {
    VStack {
      Image("arrow")
        .resizable()
        .aspectRatio(contentMode: .fit)
        .frame(width: 300, height: 300)
        .modifier(RotationEffect(angle: -CGFloat(self.angle.degreesToRadians)))
        .onReceive(location, perform: {
          withAnimation(.easeInOut(duration: 1.0)) {
            self.angle = self.location.heading
          }
        })
      
      Text(String(self.location.heading.degreesToRadians))
        .font(.system(size: 20))
        .fontWeight(.light)
        .padding(.top, 15)
    }
  }
}

struct RotationEffect: GeometryEffect {
  var angle: CGFloat

  var animatableData: CGFloat {
    get { angle }
    set { angle = newValue }
  }

  func effectValue(size: CGSize) -> ProjectionTransform {
    return ProjectionTransform(
      CGAffineTransform(translationX: -150, y: -150)
        .concatenating(CGAffineTransform(rotationAngle: angle))
        .concatenating(CGAffineTransform(translationX: 150, y: 150))
    )
  }
}

在我的 LocationManager 类中,我有一个标题 Published 变量,这是我要检查的变量。

我需要在每次航向值改变时获取数据,以便在我的箭头移动时创建动画。对于某些原因,我需要使用 CGAffineTransform。

【问题讨论】:

    标签: swift swiftui combine


    【解决方案1】:

    首先在您看来,您需要请求 HeadingProvider 开始更新标题。您需要监听 objectWillChange 通知,闭包有一个参数,即 ObservableObject 上设置的新值。

    我稍微改变了你的指南针:

    struct Compass: View {
    
      @StateObject var headingProvider = HeadingProvider()
      @State private var angle: CGFloat = 0
    
      var body: some View {
        VStack {
          Image("arrow")
            .resizable()
            .aspectRatio(contentMode: .fit)
            .frame(width: 300, height: 300)
            .modifier(RotationEffect(angle: angle))
            .onReceive(self.headingProvider.objectWillChange) { newHeading in
                withAnimation(.easeInOut(duration: 1.0)) {
                    self.angle = newHeading
                }
            }
    
          Text(String("\(angle)"))
            .font(.system(size: 20))
            .fontWeight(.light)
            .padding(.top, 15)
        }   .onAppear(perform: {
                self.headingProvider.updateHeading()
            })
      }
    }
    

    我已经写了一个示例 HeadingProvider:

    public class HeadingProvider: NSObject, ObservableObject {
        
        public let objectWillChange = PassthroughSubject<CGFloat,Never>()
        
        public private(set) var heading: CGFloat = 0 {
            willSet {
                objectWillChange.send(newValue)
            }
        }
        
        private let locationManager: CLLocationManager
        
        public override init(){
            self.locationManager = CLLocationManager()
            super.init()
            self.locationManager.delegate = self
        }
        
        public func updateHeading() {
            locationManager.startUpdatingHeading()
        }
    }
    
    extension HeadingProvider: CLLocationManagerDelegate {
        
        public func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
            DispatchQueue.main.async {
                self.heading = CGFloat(newHeading.trueHeading)
            }
        }
    }
    

    请记住,您需要处理请求读取用户位置的权限,并且您需要在某个时候调用 stopUpdatingHeading()。

    【讨论】:

    • 嗨,谢谢,我现在明白了。但是我有这个消息:无法将类型“(Double)->()”的值转换为预期的参数类型“(_)-> Void”我需要添加其他内容吗?
    【解决方案2】:

    您可以考虑在 ObservableObject 中使用 @Published。然后你的 onreceive 可以通过使用 location.$heading 来接听电话。

    对于可观察的对象,它可以是

    class LocationManager: ObservableObject {
    @Published var heading:Angle = Angle(degrees: 20)
    }
    

    对于接收,您可以使用

    struct Compass: View {
    
      @ObservedObject var location: LocationManager = LocationManager()
      @State private var angle: Angle = Angle(degrees:0)
    
      var body: some View {
        VStack {
          Image("arrow")
            .resizable()
            .aspectRatio(contentMode: .fit)
            .frame(width: 300, height: 300)
            .modifier(RotationEffect(angle: angle))
            .onReceive(location.$heading, perform: { heading in
              withAnimation(.easeInOut(duration: 1.0)) {
                self.angle = heading
              }
            })
      }
    }
    }
    

    如果您想对对象更改执行附加功能,上述内容很有用。在许多情况下,您可以直接使用 location.heading 作为您的状态更改器。然后在它下面给它一个动画。所以

                .modifier(RotationEffect(angle: location.heading))
                .animation(.easeInOut)
    

    【讨论】:

    • 我发现这个答案更容易理解。谢谢! (我认为这应该是公认的答案)
    • 谢谢!我相信这是我的问题stackoverflow.com/questions/65874566/… 的解决方案。我使用 onReceive 和 @Published 让一个正在运行的计时器继续将状态更改发布到我的视图。
    【解决方案3】:

    SwiftUI 2 / iOS 14

    从 iOS 14 开始,我们现在可以使用 onChange 修饰符,该修饰符在每次值更改时执行一个操作:

    这是一个例子:

    struct Compass: View {
        @StateObject var location = LocationManager()
        @State private var angle: CGFloat = 0
    
        var body: some View {
            VStack {
                // ...
            }
            .onChange(of: location.heading) { heading in
                withAnimation(.easeInOut(duration: 1.0)) {
                    self.angle = heading
                }
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

      您可以按照@LuLuGaGa 的建议进行操作,但这有点麻烦。 objectWillChange 被定义为 ObservableObjectPublisher,虽然将其定义为 PassthroughSubject&lt;CGFloat,Never&gt;,但它今天可以工作,但不能保证它将来会工作。

      一个对象不限于只有一个发布者,因此您可以为 SwiftUI 之外的其他目的定义第二个或第三个。例如:

      class Observable<T>: ObservableObject, Identifiable {
          let id = UUID()
          let publisher = PassthroughSubject<T, Never>()
          var value: T {
              willSet { objectWillChange.send() }
              didSet { publisher.send(value) }
          }
      
          init(_ initValue: T) { self.value = initValue }
      }
      

      子类化 ObservableObject 将为您正确定义 objectWillChange,因此您不必自己做。

      【讨论】:

      • 否 - objectWillChange 的类型由关联类型定义,所以@LuLuGaGa 的建议很好:var objectWillChange: Self.ObjectWillChangePublisher
      • Apple 说您可以将 objectWillChange 重新定义为您喜欢的任何类型的 Publisher。 WWDC2020 SwiftUI 中的数据要点
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-02-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多