实战问题
在开发涉及Store Kit 2的过程当中,当我尝试点击“赞助应用”时,因为product.purchase()方法是异步调取的,所以需要等待异步获取成功后,才会弹出内购窗口。
当异步方法在调取的过程中,加载比较缓慢的话,那么用户以为按钮没有反应,就会尝试关闭当前窗口或者点击其他按钮,当异步方法返回后,就会在用户新的操作界面下弹出内购窗口,因此是不合时宜的。
为了解决这一问题,我需要设置一个加载动画,让用户点击之后显示一个overlay覆盖层/遮罩层:
.overlay(
// 加载内购商品动画层
Group {
if IAPManager.shared.loadPurchased {
ZStack {
Color.black.opacity(0.3).edgesIgnoringSafeArea(.all)
...
}
}
}
)
这个覆盖层会判断IAPManager.shared.loadPurchased是否为true,当为true时,显示遮罩层,当为false时,隐藏遮罩层。
注意:IAPManager.shared是单例模式,所以可以直接这样设置。
然后在按钮部分添加对IAPManager.shared.loadPurchased的修改:
HStack{
// 赞助应用的内容
}
.contentShape(Rectangle()) // 扩展点击区域
.onTapGesture {
print("点击了内购按钮")
IAPManager.shared.loadPurchased = true
}
当点击按钮时,IAPManager.shared.loadPurchased被设置为true,这时遮罩层显示出来加载动画。
在IAPManager类中,我使用的是单例模式,因此IAPManager.shared.loadPurchased变成了一个全局变量,所有上面的操作都是合理的。
class IAPManager {
static let shared = IAPManager()
var loadPurchased = false // 如果开始内购流程,loadPurchased为true,View视图显示加载画布
...
}
但是在实际的操作中,当我点击“赞助应用”按钮时,并不会显示我所设置的遮罩层,而是等待异步方法调取成功后,返回内购界面。
这个问题经过分析发现,问题在于IAPManager.shared.loadPurchased 的值更新后,Settings 视图没有重新刷新导致的。因为更新静态对象的属性(如 IAPManager.shared)不会触发视图的更新。
可以通过@Publish和@observedObject来解决这一问题,接着引出本文的主题内容。
@Published 和 @ObservedObject是什么?
在 Swift 中,@Published 和 @ObservedObject 是两种用于管理和观察数据状态变化的属性包装器。它们在 SwiftUI 中非常重要,因为它们帮助构建响应式 UI,使视图能够自动更新和重新渲染。
@Published
定义: @Published 是用于 ObservableObject 类中的属性包装器。
作用: 当一个 @Published 属性的值发生变化时,任何观察该对象的 SwiftUI 视图都会自动重新渲染。
场景: 通常用于需要在应用程序中全局共享和更新的状态变量。例如,我的应用需要知道一个购买状态是否已经完成。
class IAPManager: ObservableObject {
static let shared = IAPManager()
@Published var loadPurchased = false
}
所以,当我的SwiftUI视图需要根据loadPurchased属性判断,是否显示加载动画时。我就需要给loadPurchased添加@Published属性包装器,以便让SwiftUI观察IAPManager的loadPurchased属性并自动重新渲染。
@ObservedObject
定义: @ObservedObject 是一种用于 SwiftUI 视图中的属性包装器,用于引用外部的 ObservableObject。
作用: 它使视图能够观察到某个 ObservableObject 中 @Published 属性的变化。一旦变化发生,视图会自动重新渲染。
场景: 当一个视图需要依赖外部对象(如视图模型)的状态时,可以使用 @ObservedObject 观察这些状态变化。
struct Settings: View {
@ObservedObject var iapManager = IAPManager.shared
var body: some View {
VStack {
if iapManager.loadPurchased {
Text("Purchase Complete!")
} else {
Text("No Purchase Yet")
}
}
}
}
Settings 视图会观察 iapManager 中的 loadPurchased 属性。每当 loadPurchased 发生变化,Settings 视图将自动重新渲染。
为什么 @Published 和 @ObservedObject 结合使用?
使用 @Published 可以确保在 IAPManager 中,loadPurchased 的变化会被监听到。
@ObservedObject 使视图能够观察到这些变化,并触发自动更新。
没有这两个机制,SwiftUI 视图将无法知道状态变化,因此也无法自动更新 UI。
总结
@Published: 通知状态发生变化的属性包装器,必须在 ObservableObject 类中使用。
@ObservedObject: 用于 SwiftUI 视图中的属性包装器,以观察 ObservableObject 中的 @Published 属性变化,从而更新视图。
总的来讲,就是在Swift当中需要设置一个@ObservedObject的属性包装器:
@ObservedObject var iapManager = IAPManager.shared
然后对应的类需要符合ObservableObject协议:
class IAPManager:ObservableObject {}
最后给需要观察的属性添加一个@Published:
@Published var loadPurchased = false
到这里,@Publish和@ObservedObject需要添加的部分就完成了。
主要涉及SwiftUI中需要添加@ObservedObject、观察的类需要添加ObservableObject协议,观察的属性添加@Published。
返回到最开始的实战问题当中,完成上述的全部配置后:
.overlay(
// 加载内购商品动画层
Group {
if iapManager.loadPurchased {
...
}
}
)
我的遮罩层只需要根据在SwiftUI中定义的iapManager.loadPurchased的变化来显示对应的遮罩层。
最后也是成功的观察到了loadPurchased的变化。
相关资料
Swift通过 @EnvironmentObjec共享和传递数据:https://fangjunyu.com/2024/10/23/swift%e9%80%9a%e8%bf%87-environmentobjec%e5%85%b1%e4%ba%ab%e5%92%8c%e4%bc%a0%e9%80%92%e6%95%b0%e6%8d%ae/