一、系统弹窗
在 SwiftUI 中,.alert
是一个修饰符,用于在某些条件下显示一个警告对话框。Alert
可以配置标题、消息和一系列的按钮。每个按钮可以是默认样式、取消样式,或者是破坏性的样式,它们分别对应不同的用户操作。
1.Alert
基本用法
只显示一个按钮,通常用于确认消息。
struct ContentView: View {
@State private var showAlert = false
var 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 = false
var 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? = nil
enum AlertType: Identifiable {
case first, second
var 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 in
switch 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 SwiftUI
struct ContentView: View {
@State private var showActionSheet = false
var 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 SwiftUI
struct ContentView: View {
// 弹窗的显示状态
@State private var showingPopup = false
var 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 SwiftUI
struct ContentView: View {
@State private var showingPopup = false
var 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: Bool
var 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 SwiftUI
struct ContentView: View {
@State private var showAlertType: CustomAlertType? = nil
var body: some View {
VStack {
Button("Show Alert") {
showAlertType = .Alert
}
}
.customAlert($showAlertType, message: "toastText")
}
}
enum CustomAlertType: Int {
case None = -1
case Loading = 100
case Toast = 101
case Alert = 102
}
extension View {
/// 自定义弹窗
func customAlert(_ alertType: Binding<CustomAlertType?>, message: String = "" , finish: ((String?) -> ())? = nil) -> some View {
ZStack {
self
let type = alertType.wrappedValue
if 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 = false
func body(content: Content) -> some View {
content
.scaleEffect(isVisible ? 1 : 0.9)
.onAppear {
withAnimation(.linear(duration: 0.15)) { // easeIn easeInOut easeOut linear
isVisible = true
}
}
}
}
extension View {
func popupAnimation() -> some View {
self.modifier(PopupAnimationModifier())
}
}
import SwiftUI
struct CustomPopupView: View {
@Binding var showAlertType: CustomAlertType?
@State var message: String
var finish: ((String?) -> ())? = nil
var body: some View {
VStack {
Text("Alert")
.font(.headline)
.padding()
Text(message)
.frame(alignment: .center)
.multilineTextAlignment(.center)
.font(.body)
.padding()
Spacer()
Button("OK") {
showAlertType = nil
finish?(nil)
}
.padding(.bottom)
}
.frame(maxWidth: 300, maxHeight: 200)
.background(Color.white)
.cornerRadius(20)
.shadow(radius: 20)
}
}
示意图:
3. 使用 sheet
创建半屏自定义弹窗
sheet
是一个用来展示一个新视图的修饰符,这个新视图会覆盖在当前视图上。通常 sheet
用于导航到另一个视图,比如详情页、编辑表单或者是分享菜单等。
sheet
修饰符可以通过多种方式使用,其中包括基于布尔值的呈现、使用可选的绑定对象来管理呈现以及使用标识符进行呈现。
.sheet(isPresented:)
基于布尔值的呈现
import SwiftUI
struct ContentView: View {
@State private var showingSheet = false
var 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 SwiftUI
struct ContentView: View {
@State private var selectedUser: User? = nil
var 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: User
var body: some View {
Text("User Details for \(user.name)")
}
}
使用标识符进行呈现
import SwiftUI
struct ContentView: View {
@State private var activeSheet: SheetType? = nil
var 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, second
var 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 SwiftUI
struct ContentView: View {
// 管理全屏弹窗的显示状态
@State private var showingFullScreenPopup = false
var body: some View {
// 主视图的内容
Button("Show Full Screen Popup") {
// 显示全屏弹窗
showingFullScreenPopup = true
}
// 使用 fullScreenCover 修饰符来显示全屏弹窗
.fullScreenCover(isPresented: $showingFullScreenPopup) {
// 传递 isPresented 绑定到弹窗视图,以便可以关闭弹窗
FullScreenPopupView(isPresented: $showingFullScreenPopup)
}
}
}
// 自定义全屏弹窗视图
struct FullScreenPopupView: View {
// 绑定变量,用于控制弹窗的显示与隐藏
@Binding var isPresented: Bool
var 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 SwiftUI
struct 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 in
FullScreenCoverView(fullScreenItem: item)
}
}
}
// 用于全屏弹窗的数据模型
struct FullScreenItem: Identifiable {
let id: Int
}
// 用于全屏弹窗的视图
struct FullScreenCoverView: View {
var fullScreenItem: FullScreenItem
var body: some View {
VStack {
Text("FullScreen Cover View with item id: \(fullScreenItem.id)")
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.blue)
}
}
示意图: