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)) } }
关键细节说明
- 状态拆分:拆分后的状态让我们能独立控制“来电是否在进行”和“Sheet是否显示”,这是实现最小化功能的核心。
- 顶部提示栏:用
overlay添加在TabView上方,只有当来电活跃但Sheet隐藏时才显示,点击按钮可以直接恢复Sheet。添加了过渡动画让显示/隐藏更流畅。 - 最小化逻辑:IncomingCallView通过
@Binding获取Sheet的状态,点击最小化按钮时只是隐藏Sheet,不会改变来电的活跃状态。 - 状态同步:通过Notification监听通话的开始和结束,确保两个状态始终保持同步。
额外建议
如果你的MyCallDelegate支持实时状态更新,建议把它改成ObservableObject,用@ObservedObject注入到View中,这样状态变化会自动刷新UI,比使用Notification更简洁。
内容的提问来源于stack exchange,提问作者SoftSan
相关产品推荐
相关产品推荐

