You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中实现VoIP来电Sheet的最小化与恢复功能

看起来你已经搞定了基础的来电Sheet展示,现在要实现类似WhatsApp的通话状态提示和Sheet最小化功能对吧?我来给你梳理下实现思路,直接上修改后的代码和解释:

核心思路拆解

原来的单个showModal状态不够用,我们需要拆分两个状态:

  • isIncomingActive:标记当前是否有活跃的来电(即使Sheet被最小化,这个状态也要保持为true)
  • isCallSheetVisible:控制来电Sheet的显示/隐藏

通过这两个状态的组合,就能实现:Sheet显示时隐藏提示栏,Sheet最小化时显示顶部绿色提示栏,点击提示栏恢复Sheet的效果。

修改后的完整代码

MainView 部分

struct MainView: View {
    // 拆分状态变量,分别控制来电活跃状态和Sheet显示状态
    @State private var isIncomingActive = false
    @State private var isCallSheetVisible = false
    
    let acceptPublisher = NotificationCenter.default
        .publisher(for: Notification.Name.DidCallAccepted)
    let endPublisher = NotificationCenter.default
        .publisher(for: Notification.Name.DidCallEnd)
    
    var body: some View {
        NavigationView {
            TabView {
                TabListView()
                    .tabItem {
                        Label("Home", systemImage: "house.fill")
                    }
                
                ContactListView()
                    .tabItem {
                        Label("Contacts", systemImage: "person.crop.circle")
                    }
                
                ProfileView()
                    .tabItem {
                        Label("Profile", systemImage: "person.crop.circle")
                    }
            }
            .padding(0)
            // 添加顶部通话状态提示栏
            .overlay(alignment: .top) {
                if isIncomingActive && !isCallSheetVisible {
                    HStack(spacing: 8) {
                        Image(systemName: "phone.fill")
                            .foregroundColor(.white)
                        Text("正在来电中")
                            .foregroundColor(.white)
                            .font(.subheadline)
                        Spacer()
                        Button("返回通话") {
                            isCallSheetVisible = true
                        }
                        .foregroundColor(.white)
                        .font(.subheadline)
                    }
                    .padding(.horizontal, 16)
                    .padding(.vertical, 8)
                    .background(Color.green)
                    .transition(.move(edge: .top))
                    .animation(.easeInOut, value: isIncomingActive)
                }
            }
            .accentColor(Color(.green))
        }
        .sheet(isPresented: $isCallSheetVisible) {
            // 给IncomingCallView传递Sheet状态的Binding,用于最小化操作
            IncomingCallView(isSheetVisible: $isCallSheetVisible)
        }
        .onAppear {
            // 初始化状态,同步CallDelegate的来电状态
            isIncomingActive = MyCallDelegate.shared.isIncomingCall
            isCallSheetVisible = MyCallDelegate.shared.isIncomingCall
        }
        .onReceive(acceptPublisher) { _ in
            // 接到来电时,同时激活两个状态
            isIncomingActive = true
            isCallSheetVisible = true
        }
        .onReceive(endPublisher) { _ in
            // 结束通话时,同时关闭两个状态
            isIncomingActive = false
            isCallSheetVisible = false
        }
    }
}

IncomingCallView 部分

struct IncomingCallView: View {
    // 通过Binding控制Sheet的显示状态,实现最小化功能
    @Binding var isSheetVisible: Bool
    
    var body: some View {
        VStack {
            // 顶部添加最小化按钮
            HStack {
                Spacer()
                Button(action: {
                    // 仅隐藏Sheet,不结束来电状态
                    isSheetVisible = false
                }) {
                    Image(systemName: "minus.circle.fill")
                        .foregroundColor(.white)
                        .font(.title)
                }
                .padding(.trailing, 16)
            }
            
            Spacer()
            
            Text("callerId")
                .foregroundColor(.white)
                .font(.title)
            Text("Timer")
                .foregroundColor(.white)
                .font(.subheadline)
            
            Spacer()
            
            // 示例接听/挂断按钮(你可以替换成自己的逻辑)
            HStack(spacing: 40) {
                Button(action: {
                    // 挂断逻辑:发送结束通话通知
                    NotificationCenter.default.post(name: Notification.Name.DidCallEnd, object: nil)
                }) {
                    Image(systemName: "phone.down.fill")
                        .foregroundColor(.white)
                        .font(.largeTitle)
                        .padding(20)
                        .background(Color.red)
                        .clipShape(Circle())
                }
                
                Button(action: {
                    // 接听逻辑:这里可以保留Sheet,或者根据需求隐藏
                    // isSheetVisible = false
                    // ... 你的通话初始化代码 ...
                }) {
                    Image(systemName: "phone.fill")
                        .foregroundColor(.white)
                        .font(.largeTitle)
                        .padding(20)
                        .background(Color.green)
                        .clipShape(Circle())
                }
            }
            .padding(.bottom, 40)
        }
        .background(Color.black.edgesIgnoringSafeArea(.all))
    }
}

关键细节说明

  1. 状态拆分:拆分后的状态让我们能独立控制“来电是否在进行”和“Sheet是否显示”,这是实现最小化功能的核心。
  2. 顶部提示栏:用overlay添加在TabView上方,只有当来电活跃但Sheet隐藏时才显示,点击按钮可以直接恢复Sheet。添加了过渡动画让显示/隐藏更流畅。
  3. 最小化逻辑:IncomingCallView通过@Binding获取Sheet的状态,点击最小化按钮时只是隐藏Sheet,不会改变来电的活跃状态。
  4. 状态同步:通过Notification监听通话的开始和结束,确保两个状态始终保持同步。

额外建议

如果你的MyCallDelegate支持实时状态更新,建议把它改成ObservableObject,用@ObservedObject注入到View中,这样状态变化会自动刷新UI,比使用Notification更简洁。

内容的提问来源于stack exchange,提问作者SoftSan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 18:15:35