Post

Replies

Boosts

Views

Activity

toolbar` bottomBar disappears after rotating iPhone from portrait to landscape
I have a SwiftUI detail view with a native toolbar. On iPhone, the bottom toolbar appears correctly in portrait. After rotating the device to landscape, the bottom toolbar disappears. It does not come back unless the detail view is rebuilt. I would like to keep the native toolbar appearance and behavior, especially the iOS toolbar/glass effect. I do not want to replace it with a custom safeAreaInset bar. Environment Platform: iOS Current target/system: iOS 27 UI framework: SwiftUI Device idiom: iPhone The issue happens when rotating from portrait to landscape. Expected behavior The native bottom toolbar remains visible after device rotation. Actual behavior The native bottom toolbar is visible in portrait, but disappears after rotating to landscape. Core code The main view attaches toolbar content like this: private var contentWithToolbarAndSheets: some View { coreLayout .slateNavigationBarTitleDisplayModeInline() .toolbar { #if os(iOS) ToolbarItem(placement: .principal) { VStack(spacing: 0) { Text(String(localized: "第 \(scene.safeNumber) 场")) .font(.headline) .fontWeight(.semibold) .lineLimit(1) Text(scene.safeSetName) .font(.subheadline) .foregroundStyle(.secondary) .lineLimit(1) } } #endif bottomBarContent } #if os(macOS) .navigationTitle(String(localized: "第 \(scene.safeNumber) 场")) .navigationSubtitle(scene.safeSetName) #endif .slateBottomBarBackgroundHidden() } The bottom toolbar content: @ToolbarContentBuilder private var bottomBarContent: some ToolbarContent { #if os(iOS) let bar = scriptBottomBar ToolbarItem(placement: .slateBottomBar) { bar.monitorButton } if #available(iOS 26.0, macOS 26.0, *) { ToolbarSpacer(.fixed, placement: .slateBottomBar) } ToolbarItem(placement: .slateBottomBar) { bar.soundRollButton } if #available(iOS 26.0, macOS 26.0, *) { ToolbarSpacer(.flexible, placement: .slateBottomBar) } ToolbarItem(placement: .status) { bar.principalContent } ToolbarItem(placement: .slateBottomBar) { bar.historyButton } if #available(iOS 26.0, macOS 26.0, *) { ToolbarSpacer(.fixed, placement: .slateBottomBar) } if isRecording { if #available(iOS 26.0, macOS 26.0, *) { ToolbarSpacer(.fixed, placement: .slateBottomBar) } ToolbarItem(placement: .slateBottomBar) { bar.trailingContent } } #endif } The compatibility wrappers are: func slateBottomBarBackgroundHidden() -> some View { #if os(iOS) self.toolbarBackground(.hidden, for: .bottomBar) #else self #endif } extension ToolbarItemPlacement { static var slateBottomBar: ToolbarItemPlacement { #if os(iOS) .bottomBar #else .automatic #endif } } Workaround that made it reappear Previously, I had a workaround that listened to size class and orientation changes, then forced the detail view to rebuild by clearing and restoring the selected scene: #if os(iOS) .onChange(of: horizontalSizeClass) { _, _ in forceRefreshByClearingSidebarSelection() } .onChange(of: verticalSizeClass) { _, _ in forceRefreshByClearingSidebarSelection() } .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in let orientation = UIDevice.current.orientation guard orientation.isPortrait || orientation.isLandscape else { return } forceRefreshByClearingSidebarSelection() } #endif #if os(iOS) private func forceRefreshByClearingSidebarSelection() { guard UIDevice.current.userInterfaceIdiom == .phone else { return } let currentSceneID = session.selectedSceneID session.selectedSceneID = nil DispatchQueue.main.async { if session.selectedSceneID == nil { session.selectedSceneID = currentSceneID } } } #endif This made the toolbar reappear after rotation, but it is too heavy because it rebuilds the selected scene/detail view. Things I tried Moved the center toolbar item from .status to .bottomBar. Result: did not fix the disappearing toolbar. Kept native toolbar, added a local toolbarRefreshToken, updated it on horizontalSizeClass / verticalSizeClass changes, and attached .id(toolbarRefreshToken) to toolbar item contents. Result: did not fix it. Removed .toolbarBackground(.hidden, for: .bottomBar). Result: did not fix it. Replacing the toolbar with safeAreaInset(edge: .bottom) works visually in terms of persistence, but loses the native toolbar/glass behavior, so this is not acceptable for this app. Question Is this expected behavior for SwiftUI bottom toolbars in compact-height landscape on iPhone, or is this a SwiftUI toolbar invalidation bug? Is there a recommended way to keep native .toolbar / .bottomBar behavior stable across portrait-to-landscape rotation without forcing the entire detail view to rebuild?
Topic: UI Frameworks SubTopic: SwiftUI
0
0
17
4h
toolbar` bottomBar disappears after rotating iPhone from portrait to landscape
I have a SwiftUI detail view with a native toolbar. On iPhone, the bottom toolbar appears correctly in portrait. After rotating the device to landscape, the bottom toolbar disappears. It does not come back unless the detail view is rebuilt. I would like to keep the native toolbar appearance and behavior, especially the iOS toolbar/glass effect. I do not want to replace it with a custom safeAreaInset bar. Environment Platform: iOS Current target/system: iOS 27 UI framework: SwiftUI Device idiom: iPhone The issue happens when rotating from portrait to landscape. Expected behavior The native bottom toolbar remains visible after device rotation. Actual behavior The native bottom toolbar is visible in portrait, but disappears after rotating to landscape. Core code The main view attaches toolbar content like this: private var contentWithToolbarAndSheets: some View { coreLayout .slateNavigationBarTitleDisplayModeInline() .toolbar { #if os(iOS) ToolbarItem(placement: .principal) { VStack(spacing: 0) { Text(String(localized: "第 \(scene.safeNumber) 场")) .font(.headline) .fontWeight(.semibold) .lineLimit(1) Text(scene.safeSetName) .font(.subheadline) .foregroundStyle(.secondary) .lineLimit(1) } } #endif bottomBarContent } #if os(macOS) .navigationTitle(String(localized: "第 \(scene.safeNumber) 场")) .navigationSubtitle(scene.safeSetName) #endif .slateBottomBarBackgroundHidden() } The bottom toolbar content: @ToolbarContentBuilder private var bottomBarContent: some ToolbarContent { #if os(iOS) let bar = scriptBottomBar ToolbarItem(placement: .slateBottomBar) { bar.monitorButton } if #available(iOS 26.0, macOS 26.0, *) { ToolbarSpacer(.fixed, placement: .slateBottomBar) } ToolbarItem(placement: .slateBottomBar) { bar.soundRollButton } if #available(iOS 26.0, macOS 26.0, *) { ToolbarSpacer(.flexible, placement: .slateBottomBar) } ToolbarItem(placement: .status) { bar.principalContent } ToolbarItem(placement: .slateBottomBar) { bar.historyButton } if #available(iOS 26.0, macOS 26.0, *) { ToolbarSpacer(.fixed, placement: .slateBottomBar) } if isRecording { if #available(iOS 26.0, macOS 26.0, *) { ToolbarSpacer(.fixed, placement: .slateBottomBar) } ToolbarItem(placement: .slateBottomBar) { bar.trailingContent } } #endif } The compatibility wrappers are: func slateBottomBarBackgroundHidden() -> some View { #if os(iOS) self.toolbarBackground(.hidden, for: .bottomBar) #else self #endif } extension ToolbarItemPlacement { static var slateBottomBar: ToolbarItemPlacement { #if os(iOS) .bottomBar #else .automatic #endif } } Workaround that made it reappear Previously, I had a workaround that listened to size class and orientation changes, then forced the detail view to rebuild by clearing and restoring the selected scene: #if os(iOS) .onChange(of: horizontalSizeClass) { _, _ in forceRefreshByClearingSidebarSelection() } .onChange(of: verticalSizeClass) { _, _ in forceRefreshByClearingSidebarSelection() } .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in let orientation = UIDevice.current.orientation guard orientation.isPortrait || orientation.isLandscape else { return } forceRefreshByClearingSidebarSelection() } #endif #if os(iOS) private func forceRefreshByClearingSidebarSelection() { guard UIDevice.current.userInterfaceIdiom == .phone else { return } let currentSceneID = session.selectedSceneID session.selectedSceneID = nil DispatchQueue.main.async { if session.selectedSceneID == nil { session.selectedSceneID = currentSceneID } } } #endif This made the toolbar reappear after rotation, but it is too heavy because it rebuilds the selected scene/detail view. Things I tried Moved the center toolbar item from .status to .bottomBar. Result: did not fix the disappearing toolbar. Kept native toolbar, added a local toolbarRefreshToken, updated it on horizontalSizeClass / verticalSizeClass changes, and attached .id(toolbarRefreshToken) to toolbar item contents. Result: did not fix it. Removed .toolbarBackground(.hidden, for: .bottomBar). Result: did not fix it. Replacing the toolbar with safeAreaInset(edge: .bottom) works visually in terms of persistence, but loses the native toolbar/glass behavior, so this is not acceptable for this app. Question Is this expected behavior for SwiftUI bottom toolbars in compact-height landscape on iPhone, or is this a SwiftUI toolbar invalidation bug? Is there a recommended way to keep native .toolbar / .bottomBar behavior stable across portrait-to-landscape rotation without forcing the entire detail view to rebuild?
Topic: UI Frameworks SubTopic: SwiftUI
Replies
0
Boosts
0
Views
17
Activity
4h