一、系统弹窗
在 SwiftUI 中,.alert
是一个修饰符,用于在某些条件下显示一个警告对话框。Alert
可以配置标题、消息和一系列的按钮。每个按钮可以是默认样式、取消样式,或者是破坏性的样式,它们分别对应不同的用户操作。
1.Alert
基本用法
只显示一个按钮,通常用于确认消息。
struct ContentView: View {@State private var showAlert = falsevar body: some View {Button("Show Basic Alert") {showAlert = true}.alert(isPresented: $showAlert) {Alert(title: Text("Basic Alert"),message: Text("This is a basic alert with a single button."),dismissButton: .default(Text("OK")))}}
}
示意图:
注意:
Alert
的 title
、message
和 dismissButton
的样式(如颜色、字体)是由系统控制的,不可以直接通过修改 Text
视图的属性来改变。这是为了确保 Alert
对话框保持一致的系统外观和行为,也为了确保它符合当前的操作系统主题,无论是暗模式还是亮模式。
因此,即使你尝试在 Text
视图中使用 .foregroundColor
或 .font
等修饰符,这些样式也不会应用到 Alert
中的 Text
上。
如果你需要定制的弹窗样式,需要创建一个自定义的弹窗视图,并使用 .sheet
或者其他视图容器来显示它。这样就可以完全控制弹窗视图的外观和布局,见下面自定义弹窗部分。
多个按钮
展示多个按钮,包括取消按钮和其他操作。
struct ContentView: View {@State private var showAlert = falsevar body: some View {Button("Show Alert with Multiple Buttons") {showAlert = true}.alert(isPresented: $showAlert) {Alert(title: Text("Multiple Buttons"),message: Text("This alert has multiple buttons."),primaryButton: .destructive(Text("Delete")) {// Handle delete action},secondaryButton: .cancel())}}
}
示意图:
.alert(isPresented: $showAlert) {Alert(title: Text("Multiple Buttons"),message: Text("This alert has multiple buttons."),primaryButton: .cancel(Text("NO")) {},secondaryButton: .default(Text("Logout")) {})
}
示意图:
注意:
Alert中最多只能添加两个按钮,被.cancel修饰的按钮一定会加粗并展示在左边,
.cancel最多只能修饰一个按钮,同时修饰两个按钮时会报错
"UIAlertController can only have one action with a style of UIAlertActionStyleCancel"
使用枚举来显示不同的Alerts
使用 Identifiable
协议来区分不同的警告类型,根据不同的情况显示不同的警告。
struct ContentView: View {@State private var alertType: AlertType? = nilenum AlertType: Identifiable {case first, secondvar id: Int {hashValue}}var body: some View {VStack {Button("Show First Alert") {alertType = .first}Button("Show Second Alert") {alertType = .second}}.alert(item: $alertType) { type -> Alert inswitch type {case .first:return Alert(title: Text("First Alert"),message: Text("This is the first alert."),dismissButton: .default(Text("OK")))case .second:return Alert(title: Text("Second Alert"),message: Text("This is the second alert."),primaryButton: .default(Text("NO")) {},secondaryButton: .default(Text("YES")) {})}}}
}
这种方式也比较常用,比如同一个页面有多个弹窗时,总不至于傻兮兮去定义多个@State吧。
2.ActionSheet
ActionSheet
是一种展示给用户一组操作或选择列表的方式。它与 Alert
类似,但通常用于提供两个或更多选项。ActionSheet
在 iPad 上以弹出的形式呈现,在 iPhone 上则从屏幕底部滑出。
import SwiftUIstruct ContentView: View {@State private var showActionSheet = falsevar body: some View {Button("Show Action Sheet") {showActionSheet = true}.actionSheet(isPresented: $showActionSheet) {ActionSheet(title: Text("What do you want to do?"),message: Text("There's only one option..."),buttons: [.default(Text("Option 1")) {// Handle Option 1 action},.destructive(Text("Delete")) {},.cancel()])}}
}
示意图:
注意:
与Alert一样,被.cancel修饰的按钮会被加粗并固定在底部,且最多只能有一个按钮被.cancel修饰。
二、自定义弹窗
1.使用 Overlay
创建自定义弹窗
Overlay
是一个视图修饰符,它可以用来在现有视图上层添加一个新的视图层。
import SwiftUIstruct ContentView: View {// 弹窗的显示状态@State private var showingPopup = falsevar body: some View {VStack {// 主视图内容Button("Show Popup") {withAnimation {showingPopup.toggle()}}.frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.white)}// 在这里使用 .overlay 添加弹窗.overlay(// 判断是否显示弹窗showingPopup ? popupOverlayView : nil)}// 弹窗的视图var popupOverlayView: some View {VStack {Spacer()// 弹窗内容VStack {Text("Basic Alert").font(.headline).padding()Text("This is a basic alert with a single button.").multilineTextAlignment(.center).padding(.horizontal, 10)Button("Dismiss") {withAnimation {showingPopup = false}}.padding()}.frame(maxWidth: .infinity, minHeight: 200).background(Color.white).cornerRadius(12).shadow(radius: 8).padding(.horizontal, 30)Spacer()}.background(// 背景遮罩Color.black.opacity(0.5).edgesIgnoringSafeArea(.all).onTapGesture {withAnimation {showingPopup = false}})}
}
示意图:
2.使用 ZStack
创建自定义弹窗
ZStack
是一个用来叠加视图的容器,它可以让你在同一个屏幕坐标空间中放置多个视图。当你使用 ZStack
创建弹窗时,你通常会在同一视图层次中添加弹窗视图和背景遮罩。这种方式直观且容易理解,尤其是当你的弹窗视图需要位于内容的正中央时。
基础用法
import SwiftUIstruct ContentView: View {@State private var showingPopup = falsevar body: some View {ZStack {// 主视图内容Button("Show Popup") {showingPopup.toggle()}if showingPopup {// 弹窗背景Color.black.opacity(0.4).edgesIgnoringSafeArea(.all).onTapGesture {showingPopup = false}// 弹窗内容CustomPopupView(showingPopup: $showingPopup).frame(width: 300, height: 200).background(Color.white).cornerRadius(10).shadow(radius: 10).position(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height / 2).transition(.scale)}}.animation(.easeInOut, value: showingPopup)}
}struct CustomPopupView: View {@Binding var showingPopup: Boolvar body: some View {VStack {Text("Basic Alert").font(.headline).padding()Text("This is a basic alert with a single button.").frame(alignment: .center).multilineTextAlignment(.center).font(.body).padding()Spacer()Button("Dismiss") {// 传递动作以关闭弹窗showingPopup = false// 可能需要使用一个绑定变量或闭包}.padding(.bottom)}.frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.white).cornerRadius(20).shadow(radius: 20)}
}
示意图:
封装后使用
import SwiftUIstruct ContentView: View {@State private var showAlertType: CustomAlertType? = nilvar body: some View {VStack {Button("Show Alert") {showAlertType = .Alert}}.customAlert($showAlertType, message: "toastText")}}
enum CustomAlertType: Int {case None = -1case Loading = 100case Toast = 101case Alert = 102
}extension View {/// 自定义弹窗func customAlert(_ alertType: Binding<CustomAlertType?>, message: String = "" , finish: ((String?) -> ())? = nil) -> some View {ZStack {selflet type = alertType.wrappedValueif type != nil && type != .None {if type == .Alert {Color.black.opacity(0.3).edgesIgnoringSafeArea(.all).onTapGesture {alertType.wrappedValue = .None}CustomPopupView(showAlertType: alertType, message: message, finish: finish).popupAnimation()} else if type == .Toast {}}}}}struct PopupAnimationModifier: ViewModifier {@State private var isVisible: Bool = falsefunc body(content: Content) -> some View {content.scaleEffect(isVisible ? 1 : 0.9).onAppear {withAnimation(.linear(duration: 0.15)) { // easeIn easeInOut easeOut linearisVisible = true}}}
}extension View {func popupAnimation() -> some View {self.modifier(PopupAnimationModifier())}}
import SwiftUIstruct CustomPopupView: View {@Binding var showAlertType: CustomAlertType?@State var message: Stringvar finish: ((String?) -> ())? = nilvar body: some View {VStack {Text("Alert").font(.headline).padding()Text(message).frame(alignment: .center).multilineTextAlignment(.center).font(.body).padding()Spacer()Button("OK") {showAlertType = nilfinish?(nil)}.padding(.bottom)}.frame(maxWidth: 300, maxHeight: 200).background(Color.white).cornerRadius(20).shadow(radius: 20)}
}
示意图:
3. 使用 sheet
创建半屏自定义弹窗
sheet
是一个用来展示一个新视图的修饰符,这个新视图会覆盖在当前视图上。通常 sheet
用于导航到另一个视图,比如详情页、编辑表单或者是分享菜单等。
sheet
修饰符可以通过多种方式使用,其中包括基于布尔值的呈现、使用可选的绑定对象来管理呈现以及使用标识符进行呈现。
.sheet(isPresented:)
基于布尔值的呈现
import SwiftUIstruct ContentView: View {@State private var showingSheet = falsevar body: some View {Button("Show Sheet") {showingSheet.toggle()}.sheet(isPresented: $showingSheet) {// Sheet 的内容SheetView()}}
}struct SheetView: View {var body: some View {Text("Here's the sheet!")}
}
.sheet(item:)
使用可选的绑定对象
import SwiftUIstruct ContentView: View {@State private var selectedUser: User? = nilvar body: some View {Button("Show Sheet") {selectedUser = User(name: "John Doe") // 假设 User 是一个简单的数据模型}.sheet(item: $selectedUser) { user in// Sheet 的内容UserDetailsView(user: user)}}
}struct User: Identifiable {let id = UUID()let name: String
}struct UserDetailsView: View {var user: Uservar body: some View {Text("User Details for \(user.name)")}
}
使用标识符进行呈现
import SwiftUIstruct ContentView: View {@State private var activeSheet: SheetType? = nilvar body: some View {VStack {Button("Show First Sheet") {activeSheet = .first}Button("Show Second Sheet") {activeSheet = .second}}.sheet(item: $activeSheet) { item in// 根据不同的标识符显示不同的视图switch item {case .first:FirstSheetView()case .second:SecondSheetView()}}}
}enum SheetType: Identifiable {case first, secondvar id: Self { self }
}struct FirstSheetView: View {var body: some View {Text("This is the first sheet")}
}struct SecondSheetView: View {var body: some View {Text("This is the second sheet")}
}
示意图:
4.使用 fullScreenCover
创建全屏自定义弹窗
fullScreenCover
是一个视图修饰符,它用于展示一个全屏的覆盖视图。这个修饰符通常用于呈现一个全屏弹窗,比如登录页面、介绍页面或者任何需要从当前视图完全转移焦点的场景。
.fullScreenCover(isPresented:)
基于布尔值的呈现
import SwiftUIstruct ContentView: View {// 管理全屏弹窗的显示状态@State private var showingFullScreenPopup = falsevar body: some View {// 主视图的内容Button("Show Full Screen Popup") {// 显示全屏弹窗showingFullScreenPopup = true}// 使用 fullScreenCover 修饰符来显示全屏弹窗.fullScreenCover(isPresented: $showingFullScreenPopup) {// 传递 isPresented 绑定到弹窗视图,以便可以关闭弹窗FullScreenPopupView(isPresented: $showingFullScreenPopup)}}
}// 自定义全屏弹窗视图
struct FullScreenPopupView: View {// 绑定变量,用于控制弹窗的显示与隐藏@Binding var isPresented: Boolvar body: some View {ZStack {// 弹窗的背景Color.blue.edgesIgnoringSafeArea(.all)// 弹窗的内容VStack {Text("This is a full screen popup!").font(.largeTitle).foregroundColor(.white).padding()Button("Dismiss") {// 关闭弹窗isPresented = false}.font(.title).padding().background(Color.white).foregroundColor(.blue).cornerRadius(10)}}}
}
.fullScreenCover(item:)
import SwiftUIstruct ContentView: View {// 用于控制全屏弹窗的状态@State private var selectedFullScreenItem: FullScreenItem?var body: some View {VStack(spacing: 20) {// 触发全屏弹窗的按钮Button("Show FullScreen Cover") {selectedFullScreenItem = FullScreenItem(id: 2)}}// 全屏弹窗修饰符.fullScreenCover(item: $selectedFullScreenItem) { item inFullScreenCoverView(fullScreenItem: item)}}
}// 用于全屏弹窗的数据模型
struct FullScreenItem: Identifiable {let id: Int
}// 用于全屏弹窗的视图
struct FullScreenCoverView: View {var fullScreenItem: FullScreenItemvar body: some View {VStack {Text("FullScreen Cover View with item id: \(fullScreenItem.id)")Spacer()}.frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.blue)}
}
示意图: