public var body: some View {
ZStack {
if viewModel.callingState == .outgoing {
viewFactory.makeOutgoingCallView(viewModel: viewModel)
} else if viewModel.callingState == .inCall {
if !viewModel.participants.isEmpty {
if viewModel.isMinimized {
MinimizedCallView(viewModel: viewModel)
} else {
viewFactory.makeCallView(viewModel: viewModel)
}
} else {
WaitingLocalUserView(viewModel: viewModel, viewFactory: viewFactory)
}
} else if case let .incoming(callInfo) = viewModel.callingState {
viewFactory.makeIncomingCallView(viewModel: viewModel, callInfo: callInfo)
}
}
.onReceive(viewModel.$callingState) { _ in
if viewModel.callingState == .idle || viewModel.callingState == .inCall {
utils.callSoundsPlayer.stopOngoingSound()
}
}
}
Calling state
If you are using our CallViewModel
, the state of the call is managed for you and available as a @Published
property called callingState
. It can be used to show custom UI, such as incoming / outgoing call screens, depending on your use-case. If you are using our default UI components, you don’t have to do any special handling about the callingState
.
The CallingState
enumeration has the following possible values:
idle
- There’s no active call at the moment. In this case, your hosting view should be displayed.lobby(LobbyInfo)
- The user is in the lobby before joining the call.incoming(IncomingCall)
- There’s an incoming call, therefore an incoming call screen needs to be displayed.joining
- The user is joining a call.outgoing
- The user rings someone, therefore an outgoing call needs to be displayed.inCall
- The user is in a call.reconnecting
- The user dropped the connection and now they are trying to reconnect.
Example handling
If you want to build your own UI layer, here’s an example how to react to the changes of the calling state in SwiftUI:
Similarly, you can listen to the UI changes via the @Published
property in UIKit:
@MainActor
private func listenToIncomingCalls() {
callViewModel.$callingState.sink { [weak self] newState in
guard let self = self else { return }
if case .incoming(_) = newState, self == self.navigationController?.topViewController {
let next = CallViewController.make(with: self.callViewModel)
CallViewHelper.shared.add(callView: next.view)
} else if newState == .idle {
CallViewHelper.shared.removeCallView()
}
}
.store(in: &cancellables)
}
An example implementation of CallViewHelper
can be seen below:
class CallViewHelper {
static let shared = CallViewHelper()
private var callView: UIView?
private init() {}
func add(callView: UIView) {
guard self.callView == nil else { return }
guard let window = UIApplication.shared.windows.first else {
return
}
callView.isOpaque = false
callView.backgroundColor = UIColor.clear
self.callView = callView
window.addSubview(callView)
}
func removeCallView() {
callView?.removeFromSuperview()
callView = nil
}
}
Call Settings
The CallViewModel
provides information about the current call settings, such as the camera position and whether there’s an audio and video turned on. This is available as a @Published
property called callSettings
.
If you are building a custom UI, you should use the values from this struct to show the corresponding call controls and camera (front or back).
If you want to learn more about the call settings and how to use them, please check the following page.