Sheet
- 使用Sheet-Present一个新视图
- 使用Sheets多重present
- Sheet的视图如何dismiss
- 显示Popover视图
- 防止Sheet被dismiss
- 显示一个bottom-sheet
- 如何展示白画面的占位图
概述
文章主要分享SwiftUI Modifier的学习过程,将使用案例的方式进行说明。内容浅显易懂,Sheet展示部分调试结果,不过测试代码是齐全的。如果想要运行结果,可以移步Github下载code -> github案例链接
1、使用Sheet-Present一个新视图
SwiftUI的Sheets
用于视图上present
新视图,同时仍然允许用户在准备好时向下拖动以关闭新视图。要使用sheet,给他一些东西来显示(文本、图片、自定义View等),添加一个Bool值来定义DetailView是否应该被显示,然后将其作为模态sheet
附加到主视图上。
//例如,创建一个简单的DetailView,然后在点击Button时从ContentView-present它
struct SheetView: View {
@Environment(\.dismiss) var dismiss
var body: some View {
Button("Press to dismiss") {
dismiss()
}
.font(.title)
.padding()
.background(.black)
}
}
struct FFPresentSheets: View {
@State private var showingSheet = false
var body: some View {
Button("Show Sheet") {
showingSheet.toggle()
}
.sheet(isPresented: $showingSheet) {
SheetView()
}
}
}
如果在iOS14以下,使用@environment(.presentationMode) var presentationMode 和presentationMode.wrappedValue.dismiss()来代替。与导航push不同,Sheet不需要NavigationStack。 在iOS上,如果想关闭向下拖动关闭的操作,使用fullScreenCover()
修饰符。
2、使用Sheets多重present
如果想在SwiftUI中显示多个View页面,通过从第一个View-present第二个视图来实现,不可以将两个sheet()修饰符同时附加到一个父视图上。
struct FFPresentSheetsMultiple: View {
@State private var showingFirst = false
@State private var showingSecond = false
var body: some View {
VStack {
Button("Show First Sheet") {
showingFirst = true
}
}
.sheet(isPresented: $showingFirst) {
Button("Show Second Sheet") {
showingSecond = true
}
.sheet(isPresented: $showingSecond) {
Text("Second Sheet")
}
}
}
}
使用这种方法,两个Sheet都将正确显示。如果把两个Sheet()修饰符放在同一个父元素中,SwiftUI会失效,只显示第一个sheet。
调试结果
3、Sheet的视图如何dismiss
当使用Sheet显示了一个SwiftUI视图时,通常想要在某些事情完成后关闭那个视图。例如当用户点击一个按钮时。在SwiftUI中有两种解决这个问题的方法。
3.1、@Environment(.dismiss)
第一个方法是告诉视图使用它自己的Presentation mode的环境变量来关闭。任何视图都可以关闭自己,不管它是如何present的。使用@Environment(\.dismiss)
。
struct DismissingView_015_01: View {
@Environment(\.dismiss) var dismiss
var body: some View {
Button("Dismiss me") {
dismiss()
}
}
}
struct FFSheetViewDismiss: View {
@State private var showingDetail = false
@State private var showingDetail2 = false
var body: some View {
Button("Show Detail") {
showingDetail = true
}
.sheet(isPresented: $showingDetail) {
DismissingView_015_01()
}
}
}
3.2、@Binding state
另外一种选择是将绑定
传递到所显示的Detail视图中,这样就可以将绑定值更改回false。仍然需要在sheet视图中拥有某种state属性,但现在它作为绑定传递给Detail视图。使用这种方法,视图将其绑定设置为false也会更新原始视图中的状态,导致Detail视图Dismiss
struct DismissingView_015_02: View {
@Binding var isPresented: Bool
var body: some View {
Button("Dismiss me") {
isPresented = false
}
}
}
struct FFSheetViewDismiss: View {
@State private var showingDetail = false
@State private var showingDetail2 = false
var body: some View {
Button("Show Detail2") {
showingDetail2 = true
}
.sheet(isPresented: $showingDetail2) {
DismissingView_015_02(isPresented: $showingDetail2)
}
}
}
3、fullScreenCover-present全屏视图
当想要全屏的时候,SwiftUI的fullScreenCover()
修饰符提供了一样显示方式,在代码中,他的工作原理几乎与Sheet一样。常规的sheet可以通过向下拖动来关闭,但是使用fullScreenCover-present的视图是不能的,因此,提供一种方法来dismiss新视图是要解决的问题。fullScreenCover()在macOS上不可用。
struct FullScreenModalView_015: View {
@Environment(\.dismiss) var dismiss
var body: some View {
ZStack {
Color.primary.ignoresSafeArea(edges: .all)
Button("Dismiss modal") {
dismiss()
}
}
}
}
struct FFSheetFullScreenCover: View {
@State private var isPresented = false
var body: some View {
Button("Present!") {
isPresented.toggle()
}
.fullScreenCover(isPresented: $isPresented, content: FullScreenModalView_015.init)
}
}
4、显示Popover视图
SwiftUI有一个专门的修饰器来显示弹出窗口,在iPad上显示为漂浮的气泡,在iOS上是滑动。要显示弹出窗口,需要一些状态来确定弹出窗口是否可见。与alert不同,弹出窗口可以包含任何类型的视图,所以,只要把需要的东西放在弹出窗口中就可以。
struct FFSheetPopover: View {
@State private var showingPopover = false
var body: some View {
Button("Show Menu") {
showingPopover = true
}
.popover(isPresented: $showingPopover) {
Text("Your content here")
.font(.headline)
.padding()
}
}
}
5、防止Sheet被dismiss
SwiftUI提供了interactiveDismissDisabled()
修饰符来控制用户是否可以向下滑动来关闭一个Sheet View。例如,如果用户必须接受的协议,只用同意才能关闭。
5.1、interactiveDismissDisabled()
最简单的方法是,interactiveDismissDisabled()修饰符附加到你的Sheet Content上。
struct ExampleSheet_015_01: View {
@Environment(\.dismiss) var dismiss
var body: some View {
VStack {
Text("Sheet View")
Button("Dismiss", action: close)
}
.interactiveDismissDisabled()
}
func close() {
dismiss()
}
}
struct FFSheetSwipe: View {
@State private var showingSheet = false
@State private var showingSheet1 = false
var body: some View {
Button("Show Sheet") {
showingSheet.toggle()
}
.sheet(isPresented: $showingSheet, content: ExampleSheet_015_01.init)
}
}
5.2、设定dismiss的条件
可以将一个bool值绑定到修饰符,以允许swipe
仅在成功满足某些条件时才取消。
struct ExampleSheet_015_02: View {
@State private var termsAccepted = false
var body: some View {
VStack {
Text("Terms and conditions")
.font(.title)
Text("Lots of legalese here")
Toggle("Accept", isOn: $termsAccepted)
}
.padding()
.interactiveDismissDisabled(!termsAccepted)
}
}
struct FFSheetSwipe: View {
@State private var showingSheet1 = false
var body: some View {
Button("Show Sheet 1") {
showingSheet1.toggle()
}
.sheet(isPresented: $showingSheet1, content: ExampleSheet_015_02.init)
}
}
6、显示一个bottom-sheet
SwiftUI的presentationsDetents()
修饰符可以创建从视图底部向上滑动的Sheet,可以不占满全屏,至于多少,根据需求来控制。
6.1、presentationDetents([.medium, .large])
同时支持.medium和.large,SwiftUI将创建一个调整大小的句柄,让用户在这两个size之间调整表单
struct FFSheetShowBottom: View {
@State private var showingCredits = false
var body: some View {
Button("Show Credits") {
showingCredits.toggle()
}
.sheet(isPresented: $showingCredits) {
Text("同时支持.medium和.large")
.presentationDetents([.medium, .large])
}
}
}
6.2、presentationDragIndicator(.hidden)
隐藏提示条
struct FFSheetShowBottom: View {
@State private var showingCredits2 = false
var body: some View {
Button("Show Credits 2") {
showingCredits2.toggle()
}
.sheet(isPresented: $showingCredits2) {
Text("隐藏提示条")
.presentationDetents([.medium, .large])
.presentationDragIndicator(.hidden)
}
}
}
6.3、presentationDragIndicator(.hidden)
即使有自定义的显示控件,当有一个高度比较小的class时,sheet也会自动占据整个屏幕。比如,横向的iPhone。如果想支持此场景,请确保提供一种方法来关闭sheet view。即使指定一个内置大小外,还可以提供一个范围为0-1的自定义分数。例如,创建一个占用屏幕底部15%的sheet View
struct FFSheetShowBottom: View {
@State private var showingCredits3 = false
var body: some View {
Button("Show Credits 3") {
showingCredits3.toggle()
}
.sheet(isPresented: $showingCredits3) {
Text("创建百分比的占用高度")
.presentationDetents([.fraction(0.15)])
}
}
}
6.4、presentationDetents([.height(300)])
创建一个精确的高度
struct FFSheetShowBottom: View {
@State private var showingCredits4 = false
var body: some View {
Button("Show Credits 4") {
showingCredits4.toggle()
}
.sheet(isPresented: $showingCredits4) {
Text("固定高度")
.presentationDetents([.height(300)])
}
}
}
6.5、动态切换高度
可以根据需要给视图附加任意多的detens,只要把他们全部添加到detens的集合中,SwiftUI会自动处理。例如,可以让用户在从10%切换到100%
struct FFSheetShowBottom: View {
@State private var showingCredits5 = false
let heights = stride(from: 0.1, to: 1.0, by: 0.1).map { PresentationDetent.fraction($0) }
var body: some View {
Button("Show Credits 5") {
showingCredits5.toggle()
}
.sheet(isPresented: $showingCredits5) {
Text("动态切换高度")
.presentationDetents(Set(heights))
}
}
}
调试结果
7、如何展示白画面的占位图
SwiftUI有一个专用的ContentUnavailableView
视图,在没有数据的时候向用户展示非空画面。例如,在用户执行了搜索操作之后,并未搜索到内容,使用此View。
7.1、基础样式
默认提供一个放大镜图标,由标题和副标题构成,用来展示用户并未搜索到具体内容
struct FFContentUnavailable: View {
var body: some View {
ContentUnavailableView.search
}
}
7.2、自定义文本
如果有需求,可以对其进行自定义文本,以添加用户搜索的内容
struct FFContentUnavailableCustom: View {
var body: some View {
ContentUnavailableView.search(text: "Life, the Universe, and Everything")
}
}
7.3、完全自定义
完全自定义所有的内容
struct FFContentUnavailableCustomAll: View {
var body: some View {
ContentUnavailableView("No favorites", systemImage: "star", description: Text("You don't have any favorites yet."))
.symbolVariant(.slash)
}
}