I'm trying to write a simple replacement for NavigationView/NavigationLink as an experiment. I've seen a couple of projects on Github that do this, but they both fail in the same way that my code fails, but where NavigationView works.
In a NavigationView, the pushed views maintain the value of their @State variables. Say you have a TextEditor and you edit the text, then push a new view on the stack. After going back, the editor will maintain whatever changes you had made. Quick iOS example:
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()
}
}
}
I'm new to SwiftUI so my code is not great but this is what I'm doing for my simple experiment for a macos build
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()
}
}
}
}
With that code, if you edit the text, click Push, then Pop back, you will see the editor state has been reset to its default value.
I've read in numerous places that @State is only valid while the view is in the hierarchy, so how does NavigationView accomplish this? Obviously none of us have seen the source code for it but does anyone have an idea of how Apple might be storing the Views in its stack such that @State is retained?