我正在尝试在Swiftui中实现一个视图堆栈,我的@State对象正在被重置,原因我不清楚 [英] I'm trying to implement a view stack in swiftui and my @State objects are being reset for reasons that are unclear to me

查看:0
本文介绍了我正在尝试在Swiftui中实现一个视图堆栈,我的@State对象正在被重置,原因我不清楚的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我还是个初学者,正在做一个用堆栈推送和弹出视图的实验。当我从堆栈中弹出一个视图时,前一个视图的@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()
            }
        }
    }
}

如果我在编辑器中键入一些文本,然后点击Push,然后弹出该视图,我希望文本编辑器保留我的更改,但它会恢复为默认文本。

我错过了什么?

编辑:

我想这实际上是一个如何实现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上运行它,这样您就可以获得NAV堆栈。编辑文本,然后按下。如果需要,请再次编辑,然后返回以查看状态已保留。

原则上,我的代码正在尝试做同样的事情。

推荐答案

我将分享此尝试,或许它将帮助您创建您的版本。

这一切都始于尝试创建类似NavigationViewNavigationLink的内容,但能够回溯到堆栈中的随机View

我有一个协议,其中对象返回View。通常是enumview()引用具有提供正确子项ViewViewViewContentView/MainView的工作方式类似于情节提要,它只表示currentpath变量中指定的内容。

//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()
            }
        }
    }
}

这篇关于我正在尝试在Swiftui中实现一个视图堆栈,我的@State对象正在被重置,原因我不清楚的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆