class CustomFactory: ViewFactory {
@Injected(\.chatClient) public var chatClient
private init() {}
public static let shared = CustomFactory()
func makeMessageAvatarView(for userDisplayInfo: UserDisplayInfo) -> some View {
CustomMessageAvatarView(
avatarURL: userDisplayInfo.imageURL,
size: userDisplayInfo.size ?? .messageAvatarSize
)
}
}Customizing Components
The SwiftUI SDK components are fully customizable and interchangeable through the ViewFactory protocol that holds all the reusable views of the SDK. You can find the full reference of the ViewFactory protocol here.
View Factory
The ViewFactory 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 protocol, but only implement the view you want to swap.
In most cases, your custom view only needs to conform to the standard SwiftUI View protocol and provide a body property, there’s no need to implement additional lifecycle methods or protocols. The only exception is when customizing the navigation bar, where your custom view must conform to SwiftUI’s ToolbarContent protocol.
For example, if you want to change the default usar avatar view that is used in multiple views, you can implement the makeMessageAvatarView() slot in the ViewFactory protocol.
Afterwards, you need to inject the CustomFactory in your view. To do this, you pass the newly created factory to the ChatChannelListView. If you are only using the ChatChannelView, you can pass the factory to it instead.
var body: some Scene {
WindowGroup {
ChatChannelListView(viewFactory: CustomFactory.shared)
}
}And that’s everything needed to provide custom views for the SwiftUI SDK.
The ViewFactory protocol is generic over the views that it can replace, and there is no usage of AnyView. This allows SwiftUI to compute the diffing of the views faster and more accurately without any performance overhead.
Accessing Dependencies
When building your own view components and you want to use the dependencies of the SwiftUI SDK, you can access them by using the @Injected(\.keyPath) property wrapper:
@Injected(\.chatClient) var chatClient
@Injected(\.colors) var colors
@Injected(\.fonts) var fonts
@Injected(\.images) var images
@Injected(\.utils) var utilsThe @Injected property wrapper works similarly to the @Environment in SwiftUI, but it also allows access to the dependencies in non-view related code. This approach is inspired by the following Dependency Injection article by Antoine van der Lee.
Extending with Custom Types
In some cases, you might also need to extend the @Injected property wrapper with your own types. For example, you may want to be able to access your custom types like this:
@Injected(\.customType) var customTypeIn order to achieve this, you first need to define your own InjectionKey, and define it’s currentValue, which basically creates the new instance of your type.
class CustomType {
// your custom logic here
}
struct CustomInjectionKey: InjectionKey {
static var currentValue: CustomType = CustomType()
}Next, you need to extend our InjectedValues with your own custom type, by defining its getter and setter.
extension InjectedValues {
/// Provides access to the `CustomType` instance in the views and view models.
var customType: CustomType {
get {
Self[CustomInjectionKey.self]
}
set {
Self[CustomInjectionKey.self] = newValue
}
}
}With these few simple steps, you can now access your custom type in both your app code and in your custom implementations of the views used throughout 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 a 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() -> NoChannelsIn order to improve the build time, you would need to specify the associated type with a typealias, like this:
typealias NoChannels = CustomNoChannelsViewThen, 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.