如何在 SwiftUI 中使用 onReceive 从 ObservedObject 获取数据?
How can I get data from ObservedObject with onReceive in SwiftUI?
在我的 SwiftUI 应用程序中,每次值更改时我都需要从 ObservedObject 获取数据。我知道我们可以用 .onReceive 做到这一点?我不太了解苹果公司关于它的文档。我不知道我该怎么做。
我的代码:
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 class 中,我有一个标题 Published 变量,这是我要检查的变量。
每次航向值发生变化时,我都需要获取数据,以便在我的箭头移动时创建动画。对于某些理由,我需要使用 CGAffineTransform。
首先在您的视图中,您需要请求 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()。
您可以按照@LuLuGaGa 的建议进行操作,但这有点麻烦。 objectWillChange 被定义为 ObservableObjectPublisher
,虽然将其定义为 PassthroughSubject<CGFloat,Never>
今天会起作用,但不能保证它将来会起作用。
对象不限于具有单个发布者,因此您可以定义第二个或第三个用于 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,因此您不必自己做。
您可以考虑在您的 ObservableObject 中使用@Published。然后你的 onreceive 可以通过使用 location.$heading.
来接电话
对于observable对象可以是
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)
SwiftUI 2 / iOS14
从 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
}
}
}
}
在我的 SwiftUI 应用程序中,每次值更改时我都需要从 ObservedObject 获取数据。我知道我们可以用 .onReceive 做到这一点?我不太了解苹果公司关于它的文档。我不知道我该怎么做。
我的代码:
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 class 中,我有一个标题 Published 变量,这是我要检查的变量。
每次航向值发生变化时,我都需要获取数据,以便在我的箭头移动时创建动画。对于某些理由,我需要使用 CGAffineTransform。
首先在您的视图中,您需要请求 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()。
您可以按照@LuLuGaGa 的建议进行操作,但这有点麻烦。 objectWillChange 被定义为 ObservableObjectPublisher
,虽然将其定义为 PassthroughSubject<CGFloat,Never>
今天会起作用,但不能保证它将来会起作用。
对象不限于具有单个发布者,因此您可以定义第二个或第三个用于 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,因此您不必自己做。
您可以考虑在您的 ObservableObject 中使用@Published。然后你的 onreceive 可以通过使用 location.$heading.
来接电话对于observable对象可以是
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)
SwiftUI 2 / iOS14
从 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
}
}
}
}