You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NavigationSplitView详情视图嵌套ZStack导致.sheet(item卡顿问题求助

问题描述

使用XCode 16.4开发MacCatalyst应用时,在macOS 14.6环境下遇到性能异常:多次(3-8次)点击按钮唤起.sheet(item)后,应用CPU占用率飙升至100%,同时出现旋转等待光标。该问题在macOS 13.6或iOS 16/17系统中不会触发。

问题根源:为维持NavigationSplitView详情视图中所有面板的状态,曾参考方案使用ZStack包裹详情视图,但这正是引发macOS 14.6上性能问题的原因。

最小复现代码
import SwiftUI

enum Panel: Hashable {
    case panel
}    

@main
struct MyApp: App {
    @State private var selection: Panel? = Panel.panel

    var body: some Scene {
        WindowGroup {
            NavigationSplitView {
                List(selection: $selection) {
                    NavigationLink(value: Panel.panel) {
                        Label("Panel", systemImage: "circle")
                    }
                }
            } detail: {
                ZStack { // 该ZStack在macOS 14.6上引发性能问题,但原用于维持视图状态
                    ContentView()
                }
            }
        }
    }
}

struct PopupItem: Identifiable {
    let id = UUID()
}

struct ContentView: View {
    @State var popupItem : PopupItem?
    
    var body: some View {
        Button {
            popupItem = PopupItem()
        } label: {
            Text("Show popup")
                .frame(maxWidth: .infinity, maxHeight: .infinity)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .navigationBarTitleDisplayMode(.inline)
        .sheet(item: $popupItem) { popupItem in
            DismissView()
        }
    }
}

struct DismissView: View {
    @Environment(\.dismiss) var dismiss
    var body: some View {
        NavigationStack {
            Button {
                dismiss()
            } label: {
                Text("Dismiss")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .principal) {
                    Text("Popup")
                }
            }
        }
    }
}
解决建议

以下方案均兼容macOS 13.6,同时可解决macOS 14.6上的性能问题,替代ZStack实现状态维持:

方案1:将视图状态提升至父级托管

把ContentView的状态转移到App层级的@State变量中,通过绑定传递给ContentView,这样即使视图重建,状态也能保留,同时移除引发问题的ZStack。

修改后的核心代码:

@main
struct MyApp: App {
    @State private var selection: Panel? = Panel.panel
    @State private var popupItem: PopupItem? // 托管ContentView的弹窗状态

    var body: some Scene {
        WindowGroup {
            NavigationSplitView {
                List(selection: $selection) {
                    NavigationLink(value: Panel.panel) {
                        Label("Panel", systemImage: "circle")
                    }
                }
            } detail: {
                // 直接渲染ContentView,移除ZStack
                ContentView(popupItem: $popupItem)
            }
        }
    }
}

// 修改ContentView接收绑定状态
struct ContentView: View {
    @Binding var popupItem: PopupItem?
    
    var body: some View {
        Button {
            popupItem = PopupItem()
        } label: {
            Text("Show popup")
                .frame(maxWidth: .infinity, maxHeight: .infinity)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .navigationBarTitleDisplayMode(.inline)
        .sheet(item: $popupItem) { popupItem in
            DismissView()
        }
    }
}

方案2:使用状态对象管理视图状态

通过@StateObject创建独立的状态管理器,将ContentView的状态与视图生命周期解绑,确保状态不会因视图重建而丢失。此方案适合状态逻辑较复杂的场景。

修改后的核心代码:

// 定义状态管理器
class ContentStateManager: ObservableObject {
    @Published var popupItem: PopupItem?
}

struct ContentView: View {
    @StateObject private var stateManager = ContentStateManager()
    
    var body: some View {
        Button {
            stateManager.popupItem = PopupItem()
        } label: {
            Text("Show popup")
                .frame(maxWidth: .infinity, maxHeight: .infinity)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .navigationBarTitleDisplayMode(.inline)
        .sheet(item: $stateManager.popupItem) { popupItem in
            DismissView()
        }
    }
}

// 修改App的detail部分,移除ZStack
@main
struct MyApp: App {
    @State private var selection: Panel? = Panel.panel

    var body: some Scene {
        WindowGroup {
            NavigationSplitView {
                List(selection: $selection) {
                    NavigationLink(value: Panel.panel) {
                        Label("Panel", systemImage: "circle")
                    }
                }
            } detail: {
                ContentView()
            }
        }
    }
}

方案3:多面板状态的统一管理(适合多Panel场景)

如果后续需要支持多个Panel且各自维持状态,可创建对应的数据结构存储每个Panel的状态,通过父级状态传递实现持久化:

// 定义每个Panel对应的状态结构
struct PanelState {
    var popupItem: PopupItem?
}

@main
struct MyApp: App {
    @State private var selection: Panel? = Panel.panel
    @State private var panelStates: [Panel: PanelState] = [.panel: PanelState()]

    var body: some Scene {
        WindowGroup {
            NavigationSplitView {
                List(selection: $selection) {
                    NavigationLink(value: Panel.panel) {
                        Label("Panel", systemImage: "circle")
                    }
                }
            } detail: {
                if let selection = selection, let state = $panelStates[selection] {
                    ContentView(popupItem: state.popupItem)
                } else {
                    Text("Select an item")
                }
            }
        }
    }
}

内容的提问来源于stack exchange,提问作者John Cashew

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 17:53:19