如何在 SwiftUI 中使用 onReceive 从 ObservedObject 获取数据?

Gui*_*ume 21 swift swiftui combine

在我的 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))
    )
  }
}
Run Code Online (Sandbox Code Playgroud)

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

每次当我的箭头移动时,我需要获取数据以创建动画。对于某些原因,我需要使用 CGAffineTransform。

LuL*_*aGa 21

首先在您的视图中,您需要请求 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()
        })
  }
}
Run Code Online (Sandbox Code Playgroud)

我写了一个例子 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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Pbk*_*Pbk 16

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

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

class LocationManager: ObservableObject {
@Published var heading:Angle = Angle(degrees: 20)
}
Run Code Online (Sandbox Code Playgroud)

对于接收,您可以使用

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
          }
        })
  }
}
}
Run Code Online (Sandbox Code Playgroud)

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

            .modifier(RotationEffect(angle: location.heading))
            .animation(.easeInOut)
Run Code Online (Sandbox Code Playgroud)


paw*_*222 9

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
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)