如何在SwiftUI的视图外创建一个状态包装器?

4

我知道State包装器是为了View而设计的,但我想尝试构建和测试一些代码是否可行,我的目标只是为了学习目的。

我的代码有两个大问题!

  1. Xcode无法找到T

  2. 如何初始化我的状态?

import SwiftUI

var state: State<T> where T: StringProtocol = State(get: { state }, set: { newValue in state = newValue })

struct ContentView: View {
    var body: some View {
        Text(state)
    }
}

更新:我可以在这里为绑定执行相同的操作,现在我想使用上面的代码来处理状态。
import SwiftUI

var state2: String = String() { didSet { print(state2) } }
var binding: Binding = Binding.init(get: { state2 }, set: { newValue in state2 = newValue })


struct ContentView: View {

    var body: some View {
        
        TextField("Enter your text", text: binding)

    }
 
}

如果我能找到解决我的问题的答案,那么我就可以在视图之外定义我的状态和绑定,这项工作已经完成了50%,还需要50%的状态包装器。

新更新:

import SwiftUI

var state: State<String> = State.init(initialValue: "Hello") { didSet { print(state.wrappedValue) } }
var binding: Binding = Binding.init(get: { state.wrappedValue }, set: { newValue in state = State(wrappedValue: newValue) })


struct ContentView: View {

    var body: some View {
        
        Text(state)                                     // <<: Here is the issue!
        
        TextField("Enter your text", text: binding)

    }
 
}

enter image description here


1
@aheze:我想以State包装器的方式完成它的原因是因为我对Binding做了同样的事情,而且它很顺利!因为我可以在View之外创建和定义一个Binding,所以我想为什么不用State呢?请看我的更新代码。 - ios coder
1
一个泛型的外部视图?你如何满足泛型类型 T?目前你的 T 受到了 stringProtocol 的限制,但是你如何满足 T 的具体类型呢?T 不知道要与谁通信。另外,你正在将 State<String> 传递给 Text,这是不正确的。还有一件事,与绑定不同,State 的初始化器没有 get 和 set @escaping 块。 - Tushar Sharma
1
@TusharSharma:是的,你说得对,这就是为什么我寻求帮助的原因 :) - ios coder
1
你可以在视图外部拥有普通的 State 对象,例如 var state = State(wrappedValue: “foo”),并且你可以在视图内使用 projectedValue 作为 Binding,使用 wrapped 值来访问实际值。这是一种不需要泛型的方法。是否必须使用泛型? - Tushar Sharma
1
通过我之前的建议,在运行时得到了以下结果:访问未安装在视图上的“State”值。这将导致初始值的常量绑定,并且不会更新。所以,我的建议是不要使用它。 - Tushar Sharma
显示剩余5条评论
1个回答

4
即使在视图之外创建了一个State包装器,视图如何知道何时刷新其内容?
没有通知视图的方法,您的代码将执行与以下代码相同的操作:
struct ContentView: View {
    var body: some View {
        Text("Hello")
    }
}

接下来你可以根据自己的目标采取不同的行动。

如果你只是需要一种在视图之外复制状态行为的方法,我建议你仔细研究Combine框架。

一个有趣的例子是CurrentValueSubject

var state = CurrentValueSubject<String, Never>("state1")

它存储当前值并充当发布者。

如果我们在不观察任何内容的视图中使用它,会发生什么?

struct ContentView: View {
    var body: some View {
        Text(state.value)
            .onAppear {
                DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                    state.value = "state2"
                }
            }
    }
}

答案是:没有。视图只会被绘制一次,即使 state 发生变化,视图也不会重新绘制。
您需要一种通知视图更改的方法。理论上,您可以执行以下操作:
var state = CurrentValueSubject<String, Never>("state1")

struct ContentView: View {
    @State var internalState = ""

    var body: some View {
        Text(internalState)
            .onAppear {
                DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                    state.value = "state2"
                }
            }
            .onReceive(state) {
                internalState = $0
            }
    }
}

但这样既不优雅也不干净。在这些情况下,我们应该使用@State

struct ContentView: View {
    @State var state = "state1"

    var body: some View {
        Text(state)
            .onAppear {
                DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                    state = "state2"
                }
            }
    }
}

总之,如果您需要刷新视图,请使用原生的SwiftUI属性包装器(如@State)。如果您需要在视图外声明状态值,则使用ObservableObject+@Published
否则,有一个巨大的Combine框架可以完全满足您的需求。我建议您查看以下链接:

说实话,我认为我的做法是苹果用于State的方式,但是苹果向我们隐藏了一些代码,这些代码调用View来使用已经更新过的State值,你觉得呢?我会说,State更新后,它本身会向View发送一些代码并说道:“嘿!我被更新了!所以请你也进行更新!”苹果隐藏的代码与State的位置有关。我们无法获取此位置参数!您提供的方法很容易理解,我感谢您的帮助,但我的想法是使用State的方式而不是Class或Combine。已投票。 - ios coder
如果你对 SwiftUI 视图如何知道当 @State 更改时应该刷新感兴趣,可以查看 DynamicProperty。此外,在 SwiftUI 中,你永远不会在全局级别(结构体等之外)使用 var state: State。如果想要创建自己的属性包装器,请将其创建在属于它的位置上而不是根级别。这篇文章可能会有所帮助:https://www.hackingwithswift.com/plus/intermediate-swiftui/creating-a-custom-property-wrapper-using-dynamicproperty - pawello2222
@swiftPunk 此外,要利用 DynamicProperty,您需要使用属性包装器(带有 @ 符号)。 您只能在结构体/类等内部执行此操作。 Xcode 明确指出 属性包装器尚不支持顶级代码 - pawello2222
感谢您提供的所有信息,我会阅读并学习它们。您说我应该在其位置创建状态,而不是在外部!但我已经在问题开头解释了我知道所有这些事实,并且正在尝试测试和实验事物。 - ios coder
@swiftPunk 我想说的是,Swift语言目前不允许在全局级别使用属性包装器。所以这是不可能的。请记住,var state: State 不是一个属性包装器,而只是一个简单的结构体。只有 @State var state 是一个属性包装器(带有 @ 符号)。 - pawello2222
是的,那是一个很好的观点,我只是因为我可以在外部定义绑定而感到兴奋!然后我想也许我可以对状态做同样的事情。 - ios coder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接