资讯详情

资讯详情

swiftui-expert-skill - sheet-navigation-patterns

SwiftUI Sheet、导航与 Inspector 模式参考目录Sheet 模式导航模式使用 NavigationSplitView 的多列导航Inspector呈现修饰符汇总清单Sheet 模式项目驱动的 Sheet首选呈现基于模型的内容时使用.sheet(item:)而不是.sheet(isPresented:)。// 好 - 项目驱动StateprivatevarselectedItem:Item?varbody:someView{List(items){iteminButton(item.name){selectedItemitem}}.sheet(item:$selectedItem){iteminItemDetailSheet(item:item)}}// 避免 - 布尔标志需要单独的状态StateprivatevarshowSheetfalseStateprivatevarselectedItem:Item?varbody:someView{List(items){iteminButton(item.name){selectedItemitem showSheettrue}}.sheet(isPresented:$showSheet){ifletselectedItem{ItemDetailSheet(item:selectedItem)}}}原因.sheet(item:)自动处理呈现状态并避免在 sheet body 中进行可选解包。Sheet 自己拥有其动作Sheet 应该使用Environment(\.dismiss)在内部处理自己的关闭和动作。避免从父视图传递onSave/onCancel闭包——这会创建回调逐层传递并降低可复用性。structEditItemSheet:View{Environment(\.dismiss)privatevardismissletitem:ItemStateprivatevarname:Stringinit(item:Item){self.itemitem _nameState(initialValue:item.name)}varbody:someView{NavigationStack{Form{TextField(Name,text:$name)}.navigationTitle(Edit Item).toolbar{ToolbarItem(placement:.cancellationAction){Button(Cancel){dismiss()}}ToolbarItem(placement:.confirmationAction){Button(Save){/* 保存并关闭 */}}}}}}基于枚举的 Sheet 管理当呈现多个不同的 sheet 时使用遵循Identifiable的枚举搭配.sheet(item:)而不是多个布尔状态属性structArticlesView:View{enumSheet:Identifiable{caseadd,edit(Article),categoriesvarid:String{switchself{case.add:addcase.edit(leta):edit-\(a.id)case.categories:categories}}}StateprivatevarpresentedSheet:Sheet?varbody:someView{List{/* ... */}.toolbar{Button(Add){presentedSheet.add}}.sheet(item:$presentedSheet){sheetinswitchsheet{case.add:AddArticleView()case.edit(letarticle):EditArticleView(article:article)case.categories:CategoriesView()}}}}原因单个State属性和一个.sheet(item:)修饰符取代 N 个布尔属性和 N 个 sheet 修饰符提高了可读性并防止同一时间只能有一个 sheet 的冲突。导航模式使用 NavigationStack 的类型安全导航structContentView:View{varbody:someView{NavigationStack{List{NavigationLink(Profile,value:Route.profile)NavigationLink(Settings,value:Route.settings)}.navigationDestination(for:Route.self){routeinswitchroute{case.profile:ProfileView()case.settings:SettingsView()}}}}}enumRoute:Hashable{caseprofilecasesettings}编程式导航structContentView:View{StateprivatevarnavigationPathNavigationPath()varbody:someView{NavigationStack(path:$navigationPath){List{Button(Go to Detail){navigationPath.append(DetailRoute.item(id:1))}}.navigationDestination(for:DetailRoute.self){routeinswitchroute{case.item(letid):ItemDetailView(id:id)}}}}}enumDetailRoute:Hashable{caseitem(id:Int)}使用 NavigationSplitView 的多列导航两列布局对侧边栏驱动的导航使用NavigationSplitView。在 iOS 16、macOS 13、tvOS 16、watchOS 9 上可用。structContentView:View{StateprivatevarselectedItem:Item.ID?varbody:someView{NavigationSplitView{List(items,selection:$selectedItem){iteminText(item.name)}.navigationTitle(Items)}detail:{ifletselectedItem,letitemitems.first(where:{$0.idselectedItem}){ItemDetailView(item:item)}else{ContentUnavailableView(Select an Item,systemImage:doc)}}}}三列布局structContentView:View{StateprivatevardepartmentId:Department.ID?StateprivatevaremployeeIdsSetEmployee.ID()varbody:someView{NavigationSplitView{List(model.departments,selection:$departmentId){deptinText(dept.name)}}content:{ifletdepartmentmodel.department(id:departmentId){List(department.employees,selection:$employeeIds){empinText(emp.name)}}else{Text(Select a department)}}detail:{EmployeeDetails(for:employeeIds)}}}配置列可见性NavigationSplitView(columnVisibility: $visibility)搭配NavigationSplitViewVisibility.detailOnly、.doubleColumn、.all列宽每列上使用.navigationSplitViewColumnWidth(min:ideal:max:)紧凑列NavigationSplitView(preferredCompactColumn: $column)控制窄设备上显示哪一列样式.navigationSplitViewStyle(.balanced)或.prominentDetail默认平台行为平台行为macOS列始终并排可见侧边栏有半透明材质可通过拖动调整列宽iPadOS常规侧边栏可以覆盖或推开详情支持通过工具栏按钮切换列可见性iOS / iPadOS紧凑折叠成单个NavigationStack侧边栏项显示展开箭头返回按钮在列之间导航iPhone所有尺寸始终折叠成堆栈侧边栏作为根列表出现选择将详情推入堆栈watchOS / tvOS折叠成单个堆栈Inspector可用性iOS 17.0、macOS 14.0用于补充信息的尾缘面板。在较宽的尺寸类别macOS、iPad 横屏下它显示为尾缘列。在紧凑尺寸类别iPhone下它自动适配为 sheet。基本 InspectorstructShapeEditor:View{StateprivatevarshowInspectorfalsevarbody:someView{MyEditorView().inspector(isPresented:$showInspector){InspectorContent()}.toolbar{ToolbarItem{Button{showInspector.toggle()}label:{Label(Inspector,systemImage:info.circle)}}}}}带列宽的 InspectorMyEditorView().inspector(isPresented:$showInspector){InspectorContent().inspectorColumnWidth(min:200,ideal:250,max:400)}带固定宽度的 InspectorMyEditorView().inspector(isPresented:$showInspector){InspectorContent().inspectorColumnWidth(300)}平台行为平台行为macOS尾缘侧边栏面板可通过拖动边缘调整大小与窗口工具栏集成iPadOS常规与内容并排的尾缘列可通过工具栏按钮切换iOS / iPadOS紧凑适配为 sheet 呈现支持滑动关闭iPhone所有尺寸总是以 sheet 形式呈现无尾缘列通过滑动或按钮关闭提示在应用的.commands中使用InspectorCommands以包含默认的 inspector 切换键盘快捷键。呈现修饰符全屏覆盖structContentView:View{StateprivatevarshowFullScreenfalsevarbody:someView{Button(Show Full Screen){showFullScreentrue}.fullScreenCover(isPresented:$showFullScreen){FullScreenView()}}}PopoverstructContentView:View{StateprivatevarshowPopoverfalsevarbody:someView{Button(Show Popover){showPopovertrue}.popover(isPresented:$showPopover){PopoverContentView().presentationCompactAdaptation(.popover)// 在 iPhone 上不适配为 sheet}}}关于alert和confirmationDialogAPI 模式请参阅latest-apis.md。汇总清单基于模型的 sheet 使用.sheet(item:)Sheet 在内部拥有自己的动作和关闭类型安全导航使用带navigationDestination(for:)的NavigationStack编程式导航使用NavigationPath侧边栏驱动的多列布局使用NavigationSplitView尾缘补充面板使用Inspector使用navigationSplitViewColumnWidth(min:ideal:max:)或inspectorColumnWidth(min:ideal:max:)设置列宽使用合适的呈现修饰符sheet、fullScreenCover、popoverAlert 和确认对话框使用带 actions 的现代 API避免向 sheet 传递 dismiss/save 回调呈现多个 sheet 时使用基于枚举的Identifiable类型搭配.sheet(item:)需要时可保存/恢复导航状态
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →