问题复现
在Xcode中,存在“Cannot use instance member ‘defaultWakeTime’ within property initializer; property initializers run before ‘self’ is available”报错提示。
在于Swift的struct中,属性初始化的顺序需要严格遵守。这个错误是由于Swift视图在属性中使用另一个属性,违反了Swift的规则。
具体来讲就是,Swift不允许直接从另一个属性中获取默认值,因为Swift不知道这些属性初始化的顺序。在本次报错中就是声明@State private var wakeUp的时候,试图直接调用defaultWakeTime,而defaultWakeTime本身是一个计算属性,Swift无法保证这两个属性的创建顺序。
解决方案
方案一(使用静态属性)
将 defaultWakeTime 改为
var defaultWakeTime: Date { // 修改前
static var defaultWakeTime: Date { // 修改后
将 defaultWakeTime声明改为静态属性,可以避免Swift对于属性顺序化的限制,原因在于,Swift中静态属性属于类型本身,而不属于类型的某个实例,所以不依赖某个实例的存在状态,可以随时读取,因此可以绕过属性初始化顺序的问题。
方法二(使用onAppear)
在视图的onAppear方法中设置wakeUp的初始值,而不是在声明时直接初始化。
struct ContentView: View {
@State private var wakeUp = Date.now
var defaultWakeTime: Date {
... // 其他代码
}
var body: some View {
VStack {
DatePicker("Please select a time", selection: $wakeUp, displayedComponents: .hourAndMinute)
.labelsHidden()
}
.onAppear {
wakeUp = defaultWakeTime // 在 onAppear中设置初始值
}
}
}
方法三(在init方法中设置)
在 View 的 init 方法中设置 wakeUp 的初始值
struct ContentView: View {
@State private var wakeUp = Date.now
var defaultWakeTime: Date {
... // 其他代码
}
init() {
_wakeUp = State(initialValue: defaultWakeTime) // 在init方法中设置
}
var body: some View {
...
}
}
_wakeUp 是 @State 包装器的实际存储属性。使用 _ 前缀可以访问包装器实例本身,而不是包装器管理的值(即 wakeUp)。
State(initialValue:) 是 State 属性包装器的初始化方法,用于设置 @State 属性的初始值。这里通过 State(initialValue: ContentView.defaultWakeTime) 来将 defaultWakeTime 作为初始值赋给 wakeUp 状态。
参考资料
清理用户界面:https://www.hackingwithswift.com/books/ios-swiftui/cleaning-up-the-user-interface