Customizing Components

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) channel avatar view and inject it in the slot of the default avatar. 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. However, you have to conform to SwiftUI’s ToolbarContent protocol for the navigation bar customizations.

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. With this, your SwiftUI views will not slow down the chat experience.

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 chat experience. There are default implementations for all the views used in the SDK. If you want to customize a view, you will need to implement the ViewFactory, but you will need to implement only the view you want to swap.

For example, we want to change the view displayed when there are no channels available. First, we need to create our custom view factory. For simplicity, a singleton is used in the code sample, but that’s not required if you have a different setup in your project. Then, we override the corresponding view that we want to be replaced.

class CustomFactory: ViewFactory {

    @Injected(\.chatClient) public var chatClient

    private init() {}

    public static let shared = CustomFactory()

    func makeNoChannelsView() -> some View {
        VStack {
            Text("This is our own custom no channels view.")
        }
    }

}

Afterwards, we need to inject the CustomFactory in our view. To do this, we pass the newly created factory to the ChatChannelListView (or ChatChannelView if only that one is used).

var body: some Scene {
    WindowGroup {
        ChatChannelListView(viewFactory: CustomFactory.shared)
    }
}

And that’s everything we need to do to provide our own version of some of the views used in the SDK.

Build Time Improvements

If you are customizing many view slots (over 15) from the SDK, and you have many generics in your codebase, it’s good idea to explicitly specify the types of your custom views with typealias. This will improve the build time of your project.

For example, in our ViewFactory, the associatedType for creating the no channels view is NoChannels:

associatedtype NoChannels: View
func makeNoChannelsView() -> NoChannels

In order to improve the build time, you would need to specify the associated type with a typealias, like this:

typealias NoChannels = CustomNoChannelsView

Then, your custom implementation of the factory method makeNoChannelsView will look like the following:

public func makeNoChannelsView() -> CustomNoChannelsView {
    CustomNoChannelsView()
}

You can find all the associated types we use in the ViewFactory here.

© Getstream.io, Inc. All Rights Reserved.