View Customizations

Injecting Your Views

The SwiftUI SDK allows complete view swapping of some of its components. This means you can, for example, create your own (different) outgoing call view and inject it in the slot of the default one. For most of the views, the SDK doesn’t require anything else than the view to conform to the standard SwiftUI View protocol and return a view from the body variable. You don’t need to implement any other lifecycle related methods or additional protocol conformance.

How the View Swapping Works

All the views that allow slots that your implementations can replace are generic over those views. This means that view type erasure (AnyView) is not used. The views contain default implementations, and in general, you don’t have to deal with the generics part of the code. Using generics over type erasure allows SwiftUI to compute the diffing of the views faster and more accurately while boosting performance and correctness.

View Factory

To abstract away the creation of the views, a protocol called ViewFactory is used in the SDK. This protocol defines the swappable views of the video experience. There are default implementations for all the views used in the SDK. If you want to customize a view, you will need to provide your own implementation of the ViewFactory, but you will need to implement only the view you want to swap.

For example, if we want to change the outgoing call view, we will need to implement the makeOutgoingCallView(viewModel: CallViewModel) -> OutgoingCallViewType in the ViewFactory:

class CustomViewFactory: ViewFactory {

	func makeOutgoingCallView(viewModel: CallViewModel) -> some View {
        CustomOutgoingCallView(viewModel: viewModel)
    }

}

Next, when you attach the CallModifier to your hosting view, you need to inject the newly created CustomViewFactory. The SDK will use the views you have provided in your custom implementation, while it will default back to the ones from the SDK in the slots where you haven’t provided any implementation.

var body: some View {
    YourHostingView()
        .modifier(CallModifier(viewFactory: CustomViewFactory(), viewModel: viewModel))
}

Here are all the slots available for customization in the SwiftUI SDK.

Outgoing Call View

In order to swap the outgoing call view, we will need to implement the makeOutgoingCallView(viewModel: CallViewModel) -> some View in the ViewFactory:


class CustomViewFactory: ViewFactory {

	func makeOutgoingCallView(viewModel: CallViewModel) -> some View {
        CustomOutgoingCallView(viewModel: viewModel)
    }

}

Incoming Call View

Similarly, the incoming call view can be replaced by implementing the makeIncomingCallView(viewModel: CallViewModel, callInfo: IncomingCall) -> some View in the ViewFactory:

public func makeIncomingCallView(viewModel: CallViewModel, callInfo: IncomingCall) -> some View {
    CustomIncomingCallView(callInfo: callInfo, viewModel: viewModel)
}

Call View

When the call state change to .inCall, the call view slot is shown. The default implementation provides several customizable parts, such as the video participants, the call controls (mute/unmute, hang up) and the top trailing view (which by default displays participants’ info).

In order to swap the default call view, you will need to implement the makeCallView(viewModel: CallViewModel) -> some View:

public func makeCallView(viewModel: CallViewModel) -> some View {
    CustomCallView(viewModel: viewModel)
}

Apart from the main call view, you can also swap its building blocks.

Call Controls View

The call controls view by default displays controls for hiding/showing the camera, muting/unmuting the microphone, changing the camera source (front/back) and hanging up. If you want to change these controls, you will need to implement the makeCallControlsView(viewModel: CallViewModel) -> some View method:

func makeCallControlsView(viewModel: CallViewModel) -> some View {
    CustomCallControlsView(viewModel: viewModel)
}

Video Participants View

The video participants view slot presents the grid of users that are in the call. If you want to provide a different variation of the participants display, you will need to implement the makeVideoParticipantsView in the ViewFactory:

public func makeVideoParticipantsView(
    viewModel: CallViewModel,
    availableFrame: CGRect,
    onChangeTrackVisibility: @escaping @MainActor(CallParticipant, Bool) -> Void
) -> some View {
    VideoParticipantsView(
        viewFactory: self,
        viewModel: viewModel,
        availableFrame: availableFrame,
        onChangeTrackVisibility: onChangeTrackVisibility
    )
}

In the method, the following parameters are provided:

  • viewModel - the viewModel that manages the call.
  • availableFrame - the available frame for the participants view.
  • onChangeTrackVisibility - callback when the track changes its visibility.

Video Participant View

If you want to customize one particular participant view, you can change it via the method makeVideoParticipantView:

func makeVideoParticipantView(
    participant: CallParticipant,
    id: String,
    availableFrame: CGRect,
    contentMode: UIView.ContentMode,
    customData: [String: RawJSON],
    call: Call?
) -> some View {
    VideoCallParticipantView(
        participant: participant,
        id: id,
        availableFrame: availableFrame,
        contentMode: contentMode,
        customData: customData, 
        call: call
    )
}

Additionally, you can change the modifier applied to the view, by implementing the makeVideoCallParticipantModifier:

public func makeVideoCallParticipantModifier(
        participant: CallParticipant,
        call: Call?,
        availableFrame: CGRect,
        ratio: CGFloat,
        showAllInfo: Bool
) -> some ViewModifier {
    VideoCallParticipantModifier(
        participant: participant,
        call: call,
        availableFrame: availableFrame,
        ratio: ratio,
        showAllInfo: showAllInfo
    )
}

Top View

This is the view presented in the top area of the call view. By default, it displays a back button (to go in minimized mode) and a button that shows the list of participants. You can swap this view with your own implementation, by implementing the makeCallTopView in the ViewFactory:

public func makeCallTopView(viewModel: CallViewModel) -> some View {
    CallTopView(viewModel: viewModel)
}
© Getstream.io, Inc. All Rights Reserved.