我正在尝试在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
本文介绍了我正在尝试在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堆栈。编辑文本,然后按下。如果需要,请再次编辑,然后返回以查看状态已保留。
原则上,我的代码正在尝试做同样的事情。
推荐答案
我将分享此尝试,或许它将帮助您创建您的版本。
这一切都始于尝试创建类似NavigationView
和NavigationLink
的内容,但能够回溯到堆栈中的随机View
我有一个协议,其中对象返回View
。通常是enum
。view()
引用具有提供正确子项View
的View
的View
。ContentView
/MainView
的工作方式类似于情节提要,它只表示current
或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()
}
}
}
}
这篇关于我正在尝试在Swiftui中实现一个视图堆栈,我的@State对象正在被重置,原因我不清楚的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
查看全文