如何在SwiftUI顶部显示来电视图?

问题描述

我试图模拟如何在所有顶部显示呼叫视图。但是我有一些不清楚的地方:

  • 有什么方法可以使其更简单?
  • 在这种情况下视图层次结构是否受到保护?

AppView.swift

import SwiftUI

struct AppView: View {
    
    @Observedobject var callVM: Callviewmodel
    
    init() {
        self.callVM = Callviewmodel()
    }
    
    var body: some View {
        vstack {
            IncomingCallView(rootView: appView,isActive: self.$callVM.isIncomingCallActive)
            TabView {
                TabOne()
                    .tabItem {
                        Image(systemName: "list.dash")
                        Text("Menu")
                    }

                TabTwo()
                    .tabItem {
                        Image(systemName: "square.and.pencil")
                        Text("Order")
                    }
            }
        }
        .onAppear(perform: load)
    }
    
    var appView: some View {
        Text("")
    }
    
    func load() {
        self.callVM.getCall()
    }
}

struct AppView_Previews: PreviewProvider {
    static var previews: some View {
        AppView()
    }
}

Callviewmodel.swift

import Foundation

class Callviewmodel: ObservableObject {
    
    @Published public var isIncomingCallActive = Bool()
    
    init() {
        self.isIncomingCallActive = false
    }
    
    func getCall() {
        dispatchQueue.main.asyncAfter(deadline: .Now() + 5) {
            self.isIncomingCallActive = true
        }
    }
}

IncomingCallView.swift

import SwiftUI

struct IncomingCallView<RootView: View>: View {
    private let rootView: RootView
    @Binding var isActive: Bool

    init(rootView: RootView,isActive: Binding<Bool>) {
        self.rootView = rootView
        self._isActive = isActive
    }

    var body: some View {
        rootView
            .background(Activator(isActive: $isActive))
    }

    struct Activator: UIViewRepresentable {
        @Binding var isActive: Bool
        @State private var myWindow: UIWindow? = nil

        func makeUIView(context: Context) -> UIView {
            let view = UIView()
            dispatchQueue.main.async {
                self.myWindow = view.window
            }
            return view
        }

        func updateUIView(_ uiView: UIView,context: Context) {
            guard let holder = myWindow?.rootViewController?.view else { return }

            if isActive && context.coordinator.controller == nil {
                context.coordinator.controller = UIHostingController(rootView: loadingView)

                let view = context.coordinator.controller!.view
                view?.backgroundColor = UIColor.black.withAlphaComponent(1)
                view?.isUserInteractionEnabled = true
                view?.translatesAutoresizingMaskIntoConstraints = false
                holder.addSubview(view!)
                holder.isUserInteractionEnabled = true

                view?.leadingAnchor.constraint(equalTo: holder.leadingAnchor).isActive = true
                view?.trailingAnchor.constraint(equalTo: holder.trailingAnchor).isActive = true
                view?.topAnchor.constraint(equalTo: holder.topAnchor).isActive = true
                view?.bottomAnchor.constraint(equalTo: holder.bottomAnchor).isActive = true
            } else if !isActive {
                context.coordinator.controller?.view.removeFromSuperview()
                context.coordinator.controller = nil
                holder.isUserInteractionEnabled = true
            }
        }

        func makeCoordinator() -> Coordinator {
            Coordinator()
        }

        class Coordinator {
            var controller: UIViewController? = nil
        }
        
        private var loadingView: some View {
            HStack(spacing: 20) {
                
                Button(action: {
                    print("acceptCall pressed")
                    // change UI for accepted call
                }) {
                    Image("acceptCall")
                        .resizable()
                        .frame(width: 50,height: 50,alignment: .center)
                        .aspectRatio(contentMode: .fit)
                }

                Button(action: {
                    // close the view
                    print("rejectCall pressed")
                    self.isActive = false
                }) {
                    Image("rejectCall")
                        .resizable()
                        .frame(width: 50,alignment: .center)
                        .aspectRatio(contentMode: .fit)
                }
                
            }
            .frame(width: 300,height: 300)
            .background(Color.primary.opacity(0.7))
            .cornerRadius(10)
        }
    }
}

解决方法

swiftUI中的内容看起来像这样

struct AppView: View {
    
    @StateObject var callVM = CallViewModel()
    
    var body: some View {
        ZStack {
            TabView {
                Text("TabOne")
                    .tabItem {
                        Image(systemName: "list.dash")
                        Text("Menu")
                    }
                
                Text("TabTwo")
                    .tabItem {
                        Image(systemName: "square.and.pencil")
                        Text("Order")
                    }
            }
            if callVM.isIncomingCallActive {
                ZStack {
                    Color.green
                    VStack {
                        Text("Incoming call...")
                        Button(action: { callVM.isIncomingCallActive = false }) {
                            Text("Cancel")
                        }
                    }
                }.edgesIgnoringSafeArea(.all) // <= here
            }
        }
        .onAppear {
            self.callVM.getCall()
        }
    }
}

class CallViewModel: ObservableObject {
    
    @Published public var isIncomingCallActive = false
    
    func getCall() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
            self.isIncomingCallActive = true
        }
    }
}