我正在尝试在 swiftui 中实现视图堆栈,并且我的 @State 对象正在重置,原因我不清楚

2023-12-22

我是 swiftui 的新手,正在做一个用堆栈推送和弹出视图的实验。当我从堆栈中弹出视图时,前一个视图的 @State 变量已被重置,我不明白为什么。

该演示代码已在 macOS 上测试。

import SwiftUI

typealias Push = (AnyView) -> ()
typealias Pop = () -> ()

struct PushKey: EnvironmentKey {
    
    static let defaultValue: Push = { _ in }
    
}

struct PopKey: EnvironmentKey {
    
    static let defaultValue: Pop = {() in }
    
}

extension EnvironmentValues {
    
    var push: Push {
        get { self[PushKey.self] }
        set { self[PushKey.self] = newValue }
    }
    
    var pop: Pop {
        get { self[PopKey.self] }
        set { self[PopKey.self] = newValue }
    }
}

struct ContentView: View {
    @State private var stack: [AnyView]

    var body: some View {
        currentView()
            .environment(\.push, push)
            .environment(\.pop, pop)
            .frame(width: 600.0, height: 400.0)
    }
    
    public init() {
        _stack = State(initialValue: [AnyView(AAA())])
    }
    
    private func currentView() -> AnyView {
        if stack.count == 0 {
            return AnyView(Text("stack empty"))
        }
        return stack.last!
    }
    
    public func push(_ content: AnyView) {
        stack.append(content)
    }
    
    public func pop() {
        stack.removeLast()
    }
}

struct AAA : View {
    @State private var data = "default text"
    @Environment(\.push) var push

    var body: some View {
        VStack {
            TextEditor(text: $data)
            Button("Push") {
                self.push(AnyView(BBB()))
            }
        }
    }
}

struct BBB : View {
    @Environment(\.pop) var pop

    var body: some View {
        VStack {
            Button("Pop") {
                self.pop()
            }
        }
    }
}

如果我在编辑器中输入一些文本,然后点击“推送”,然后从该视图中弹出,我期望文本编辑器保留我的更改,但它会恢复为默认文本。

我缺少什么?

Edit:

我想这实际上是一个NavigationView和NavigationLink如何实现的问题。这个简单的代码完成了我想做的事情:

import SwiftUI

struct MyView: View {
    @State var text = "default text"
    var body: some View {
        VStack {
            TextEditor(text: $text)
            NavigationLink(destination: MyView()) {
                Text("Push")
            }
        }
    }
}

struct ContentView: View {
    var body: some View {
        NavigationView {
            MyView()
        }
    }
}

在 iOS 上运行它,这样你就可以得到一个导航堆栈。编辑文本,然后推送。如果需要,请再次编辑,然后返回并查看状态是否保留。

我的代码原则上试图做同样的事情。


我将分享这个尝试,也许它会帮助您创建您的版本。

这一切都始于尝试创建类似的东西NavigationView and NavigationLink但能够回溯到随机View在堆栈中

我有一个协议,其中一个对象返回一个View。通常它是一个enum. The view()参考文献一View with a switch提供正确的孩子View. The ContentView/MainView工作原理几乎像故事板一样,只呈现故事板中指定的内容current or path变量。

//To make the View options generic
protocol ViewOptionsProtocol: Equatable {
    associatedtype V = View
    @ViewBuilder func view() -> V
}

这是基本的导航路由器,用于跟踪主视图和NavigationLink/path。这看起来与您想要做的类似。

//A generic Navigation Router
class ViewNavigationRouter<T: ViewOptionsProtocol>: ObservableObject{
    
    //MARK: Variables
    var home: T
    //Keep track of your current screen
    @Published private (set) var current: T
    //Keep track of the path
    @Published private (set) var path: [T] = []
    //MARK: init
    init(home: T, current: T){
        self.home = home
        self.current = current
    }
    //MARK: Functions
    //Control how you get to the screen
    ///Navigates to the nextScreen adding to the path/cookie crumb
    func push(nextScreen: T){
        //This is a basic setup just going forward
        path.append(nextScreen)
    }
    ///Goes back one step in the path/cookie crumb
    func pop(){
        //Use the stored path to go back
        _ = path.popLast()
    }
    ///clears the path/cookie crumb and goes to the home screen
    func goHome(){
        path.removeAll()
        current = home
    }
    ///Clears the path/cookie crumb array
    ///sets the current View to the desired screen
    func show(nextScreen: T){
        goHome()
        current = nextScreen
    }
    ///Searches in the path/cookie crumb for the desired View in the latest position
    ///Removes the later Views
    ///sets the nextScreen
    func dismissTo(nextScreen: T){
        while !path.isEmpty && path.last != nextScreen{
            pop()
        }
        if path.isEmpty{
            show(nextScreen: nextScreen)
        }
    }
    
}

这不是一个@Environment但它很容易成为@EnvrionmentObject所有的观点都必须在enum所以这些观点并不是完全未知,但这是我能够规避的唯一方法AnyView并将视图保存在@ViewBuilder.

我使用这样的东西作为主视图中的主要部分body

router.path.last?.view() ?? router.current.view()

这是示例的简单实现

import SwiftUI

class MyViewModel:  ViewNavigationRouter<MyViewModel.ViewOptions> {
    //In some view router concepts the data that is /preserved/shared among the views is preserved in the router itself.
    @Published var preservedData: String = "preserved"
    init(){
        super.init(home: .aaa ,current: .aaa)
    }
    
    enum ViewOptions: String, ViewOptionsProtocol, CaseIterable{
        case aaa
        case bbb
        
        @ViewBuilder func view() -> some View{
            ViewOptionsView(option: self)
        }
    }
    
    struct ViewOptionsView: View{
        let option: ViewOptions
        var body: some View{
            switch option {
            case .aaa:
                AAA()
            case .bbb:
                BBB()
            }
            
        }
    }
}
struct MyView: View {
    @StateObject var router: MyViewModel = .init()
    var body: some View {
        NavigationView{
            ScrollView {
                router.path.last?.view() ?? router.current.view()
            }
            .toolbar(content: {
                //Custom back button
                ToolbarItem(placement: .navigationBarLeading, content: {
                    if !router.path.isEmpty {
                        Button(action: {
                            router.pop()
                        }, label: {
                            HStack(alignment: .center, spacing: 2, content: {
                                Image(systemName: "chevron.backward")
                                if router.path.count >= 2{
                                    Text(router.path[router.path.count - 2].rawValue)
                                }else{
                                    Text(router.current.rawValue)
                                }
                            })
                            
                        })
                    }
                })
            })
            .navigationTitle(router.path.last?.rawValue ?? router.current.rawValue)
        }.environmentObject(router)
    }
}


struct MyView_Previews: PreviewProvider {
    static var previews: some View {
        MyView()
    }
}

struct AAA : View {
    //This will reset because the view is cosmetic. the data needs to be preserved somehow via either persistence or in the router for sharing with other views.
    @State private var data = "default text"
    @EnvironmentObject var vm: MyViewModel
    var body: some View {
        VStack {
            TextEditor(text: $data)
            TextEditor(text: $vm.preservedData)
            Button("Push") {
                vm.push(nextScreen: .bbb)
            }
        }
    }
}

struct BBB : View {
    @EnvironmentObject var vm: MyViewModel
    
    var body: some View {
        VStack {
            Button("Pop") {
                vm.pop()
            }
        }
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

我正在尝试在 swiftui 中实现视图堆栈,并且我的 @State 对象正在重置,原因我不清楚 的相关文章

随机推荐