[SwiftUI]系统弹窗和自定义弹窗

本文详细介绍了SwiftUI中如何使用Alert、ActionSheet展示警告对话框和提供多选,以及如何通过Overlay、ZStack和fullScreenCover实现自定义弹窗,包括半屏和全屏视图的创建与管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、系统弹窗

在 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 的 titlemessage 和 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)
    }
}

示意图:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值