Usage of .safeToolbar
import SwiftUI
/// SwiftUI Toolbar crasher
/// =======================
///
/// Steps to reproduce
/// ------------------
/// * launch this app on a physical device running iOS 17 (any stable or beta release).
/// * Tap the center button ("Go to view 2")
/// * Tap the center button ("Go to view 3")
/// * Tap the center button ("Go to view 4")
/// * Tap the center button ("Go to view 5")
/// * Tap the center button ("Pop to root 💣")
///
/// Expected result
/// ---------------
/// * The app navigates back to the root screen.
///
/// Actual result
/// -------------
/// * The app crashes in 30-50% of the attempts.
///
///
class ToolbarCrasher: ObservableObject {
@Published var navTree: NavigationTree? = nil
}
enum NavigationTree {
case one(One?)
var one: One? {
switch self {
case let .one(one): one
}
}
var isOne: Bool {
switch self {
case .one: true
}
}
enum One {
case two(Two?)
var two: Two? {
switch self {
case let .two(two): two
}
}
var isTwo: Bool {
switch self {
case .two: true
}
}
enum Two {
case three(Three?)
var three: Three? {
switch self {
case let .three(three): three
}
}
var isThree: Bool {
switch self {
case .three: true
}
}
enum Three {
case four
var isFour: Bool {
switch self {
case .four: true
}
}
}
}
}
}
struct ContentView: View {
@StateObject var toolbarCrasher: ToolbarCrasher = .init()
var body: some View {
NavigationStack {
VStack {
Text("Root View")
Button(
action: { toolbarCrasher.navTree = .one(nil) },
label: { Text("Go to view 2") }
)
}
.navigationDestination(
isPresented: .init(
get: { toolbarCrasher.navTree?.isOne ?? false },
set: { isForward in if isForward { toolbarCrasher.navTree = nil } }
),
destination: {
ContentView2(toolbarCrasher: toolbarCrasher)
}
)
.padding()
.toolbar {
ToolbarItem(placement: .topBarTrailing, content: {
Button(
action: { toolbarCrasher.navTree = nil },
label: { Text("Pop to root 💣") }
)
})
}
}
}
}
struct ContentView2: View {
@ObservedObject var toolbarCrasher: ToolbarCrasher
var body: some View {
VStack {
Text("View 2")
Button(
action: { toolbarCrasher.navTree = .one(.two(nil)) },
label: { Text("Go to view 3") }
)
}
.safeToolbar {
ToolbarItem(placement: .topBarTrailing, content: {
Button(
action: { toolbarCrasher.navTree = nil },
label: { Text("Pop to root 💣") }
)
})
}
.navigationDestination(
isPresented: .init(
get: { toolbarCrasher.navTree?.one?.isTwo ?? false },
set: { isForward in if isForward { toolbarCrasher.navTree = .one(nil) } }
),
destination: {
ContentView3(toolbarCrasher: toolbarCrasher)
}
)
.padding()
}
}
struct ContentView3: View {
@ObservedObject var toolbarCrasher: ToolbarCrasher
var body: some View {
VStack {
Text("View 3")
Button(
action: { toolbarCrasher.navTree = .one(.two(.three(nil))) },
label: { Text("Go to view 4") }
)
}
.safeToolbar {
ToolbarItem(placement: .topBarTrailing, content: {
Button(
action: { toolbarCrasher.navTree = nil },
label: { Text("Pop to root 💣") }
)
})
}
.navigationDestination(
isPresented: .init(
get: { toolbarCrasher.navTree?.one?.two?.isThree ?? false },
set: { isForward in if isForward { toolbarCrasher.navTree = .one(.two(nil)) } }
),
destination: {
ContentView4(state: toolbarCrasher)
}
)
.padding()
}
}
struct ContentView4: View {
@ObservedObject var state: ToolbarCrasher
var body: some View {
VStack {
Text("View 4")
Button(
action: { state.navTree = .one(.two(.three(.four))) },
label: { Text("Go to view 5") }
)
}
.safeToolbar {
ToolbarItem(placement: .topBarTrailing, content: {
Button(
action: { state.navTree = nil },
label: { Text("Pop to root 💣") }
)
})
}
.navigationDestination(
isPresented: .init(
get: { state.navTree?.one?.two?.three?.isFour ?? false },
set: { isForward in if isForward { state.navTree = .one(.two(.three(nil))) } }
),
destination: {
ContentView5(toolbarCrasher: state)
}
)
.padding()
}
}
struct ContentView5: View {
@ObservedObject var toolbarCrasher: ToolbarCrasher
var body: some View {
Text("View 5")
Button(
action: { toolbarCrasher.navTree = nil },
label: { Text("Pop to root 💣") }
)
}
}
#Preview {
ContentView()
}
Topic:
UI Frameworks
SubTopic:
SwiftUI
Tags: