Customizing Views

LAST EDIT Apr 08 2021

View Lifecycle Methods

Copied!

To make subclassing and customization simple, almost all view components in StreamChatUI has the following set of lifecycle methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import StreamChatUI 
 
class MyView: View { 
    /// Main point of customization for the view functionality. 
    /// It's called zero or one time(s) during the view's 
    /// lifetime. Calling super implementation is required. 
    override func setUp() { } 
 
    /// Main point of customization for the view appearance. 
    /// It's called zero or one time(s) during the view's lifetime. 
    /// The default implementation of this method is empty so calling `super` is usually not needed. 
    override func setUpAppearance() { } 
 
    /// Main point of customization for the view layout. 
    /// It's called zero or one time(s) during the view's lifetime. 
    /// Calling super is recommended but not required if you provide a complete layout for all subviews. 
    override func setUpLayout() { } 
 
    /// Main point of customization for the view appearance. 
    /// It's called every time view's content changes. 
    /// Calling super is recommended but not required 
    /// if you update the content of all subviews of the view. 
    override func updateContent() { } 
}

Changing Main Color

Copied!

If suitable, UI elements respect UIView.tintColor as the main (brand) color. The current tintColor depends on the tint color of the view hierarchy the UI element is presented on.

For example, by changing the tint color of the UIWindow of the app, you can easily modify the brand color of the whole chat UI:

1
2
3
4
5
6
7
8
9
import UIKit 
import StreamChatUI 
 
class SceneDelegate: UIResponder, UIWindowSceneDelegate { 
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 
        guard let scene = scene as? UIWindowScene else { return } 
        scene.windows.forEach { $0.tintColor = .systemPink } 
    } 
}

default tintcolor

tintcolor = .systempink

Image shows chat app with blue tint color

Image shows chat app with pink tint color

Changing Appearance

Copied!

StreamChatUI offers a simple way of customizing the default appearance of almost every UI element using the DefaultAppearance protocol. Elements conforming to this protocol expose the defaultAppearance property which allows adding custom appearance rules.

For example, this is the default appearance of the list of channels:

Image shows a  channels view

We can easily change the appearance of the channel list items using the defaultAppearance API:

1
2
3
4
5
6
7
import UIKit 
import StreamChatUI 
 
UIConfig.default.channelList.channelListItemView.defaultAppearance.addRule { 
    $0.backgroundColor = UIColor.yellow.withAlphaComponent(0.2) 
    $0.titleLabel.textColor = .darkGray 
}

Image shows a channel view with a yellow background

The unread count indicator now seems quite off. We can change its color, too:

1
2
3
4
5
6
import UIKit 
import StreamChatUI 
 
UIConfig.default.channelList.channelListItemSubviews.unreadCountView.defaultAppearance.addRule { 
    $0.backgroundColor = .darkGray 
}

Image shows a channel view with a yellow background and dark gray unread indicator

Changing Layout

Copied!

It is possible to adjust/customize the layout of any UI component in the following ways:

  • add a new subview

  • remove the existed subview

  • change spacing/margins/relative subviews arrangement

All the customizations can be done in 3 steps:

  1. Create a subclass of the UI component

  2. Override setUpLayout and apply custom layout

  3. Inject created subclass via UIConfig

Subclassing Components

Copied!

Adding a new subview to the UI component

Copied!

This type of customization is especially useful when working with custom extra-data. Whenever custom extra data contains the information that should be shown inside the UI component, it can be subclassed and extended with the new subview.

Read more about ExtraData here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import UIKit 
import StreamChat 
import StreamChatUI 
 
struct MyExtraData: ExtraDataTypes { 
    struct Message: MessageExtraData { 
        static let defaultValue = Self(authorWasInGoodMood: true) 
 
        let authorWasInGoodMood: Bool 
    } 
} 
 
// 1. Create custom UI component subclass. 
class MyChatMessageMetadataView: _ChatMessageMetadataView<MyExtraData> { 
 
    // 2. Declare new subview. 
    let moodLabel = UILabel() 
 
    // 3. Override `setUpLayout` and add the new subview to the hierarchy. 
    override func setUpLayout() { 
        // Use base implementation. 
        super.setUpLayout() 
        // But also extend it with the new subview. 
        stack.addArrangedSubview(moodLabel) 
    } 
 
    // 4. Override `updateContent` and provide data for the new subview. 
    override func updateContent() { 
        // Use base implementation. 
        super.updateContent() 
        // But also provide data for the new subview. 
        moodLabel.text = message?.authorWasInGoodMood == true ? "😃" : "😞" 
    } 
}

Before

after

Image shows chat messages

Image shows chat message with smiley face beside timestamp

Once the custom subclass is implemented, it should be injected via UIConfig so it is used instead of base UI component.

Refer to Injecting Custom Subclass chapter below for more information.

Changing component subview's relative arrangement, or removing subview

Copied!

Imagine you are not satisfied with some UI component default layout and you're looking for how to exclude a particular subview or change relative subviews arrangement.

Let's see how it can be done by the ChatMessageInteractiveAttachmentView example:

Default layout

updated layout

image shows message with action buttons arranged horizontally

image shows message with action buttons arranged vertically

As you see, in the updated layout:

  • action buttons are arranged vertically;

  • there is no GIF title label.

The code-snippet that does the job:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import UIKit 
import StreamChatUI 
 
// 1. Create custom UI component subclass. 
class InteractiveAttachmentView: ChatMessageInteractiveAttachmentView { 
    // 2. Override `setUpLayout` and provide custom subviews arrangement. 
    override open func setUpLayout() { 
        // 3. Add only `preview` and `actionsStackView`. 
        addSubview(preview) 
        addSubview(actionsStackView) 
 
        // 4. Make the action buttons stack vertical. 
        actionsStackView.axis = .vertical 
 
        // 5. Set up the necessary constraints. 
        NSLayoutConstraint.activate([ 
            preview.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), 
            preview.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), 
            preview.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor), 
            preview.heightAnchor.constraint(equalTo: preview.widthAnchor), 
 
            actionsStackView.topAnchor.constraint(equalTo: preview.bottomAnchor), 
            actionsStackView.leadingAnchor.constraint(equalTo: leadingAnchor), 
            actionsStackView.trailingAnchor.constraint(equalTo: trailingAnchor), 
            actionsStackView.bottomAnchor.constraint(equalTo: bottomAnchor) 
        ]) 
    } 
}

Once the custom subclass is implemented it should be injected via UIConfig so it is used instead of the base UI component.

Refer to Injecting Custom Subclass chapter below for more information.

Injecting Custom Subclass

Copied!

You can change UI behavior or add new elements by subclassing StreamChatUI views and injecting them in UIConfig.

UIConfig.default is used to inject custom UI component types.

Once the new type is injected it will is used instead of the base UI component.

In most cases the type injection should be done once and the application(_, didFinishLaunchingWithOptions) is the right place for it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import UIKit 
import StreamChatUI 
 
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 
    // Override point for customization after application launch. 
 
    UIConfig.default 
      .messageList 
      .messageContentSubviews 
      .attachmentSubviews 
      .interactiveAttachmentView = InteractiveAttachmentView.self 
 
    return true 
}

For example, this user really loves ducks and has a bad time without them.

Image shows chat message

Let's help him, by showing 🦆 near message view if it contains "duck" word. To achive this we would subclass ChatMessageContentView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class DuckBubbleView: ChatMessageContentView { 
    lazy var duckView: UILabel = { 
        let label = UILabel() 
        label.translatesAutoresizingMaskIntoConstraints = false 
        label.text = "🦆" 
        label.font = .systemFont(ofSize: 60) 
        return label 
    }() 
 
    var incomingMessageConstraint: NSLayoutConstraint? 
    var outgoingMessageConstraint: NSLayoutConstraint? 
 
    override func setUpLayout() { 
        super.setUpLayout() 
        addSubview(duckView) 
        duckView.centerYAnchor.constraint(equalTo: messageBubbleView.bottomAnchor).isActive = true 
 
        incomingMessageConstraint = duckView.centerXAnchor.constraint(equalTo: trailingAnchor) 
        outgoingMessageConstraint = duckView.centerXAnchor.constraint(equalTo: leadingAnchor) 
    } 
 
    override func updateContent() { 
        super.updateContent() 
        let isOutgoing = message?.isSentByCurrentUser ?? false 
        incomingMessageConstraint?.isActive = !isOutgoing 
        outgoingMessageConstraint?.isActive = isOutgoing 
 
        let isDuckInIt = message?.text.contains("duck") ?? false 
        duckView.isHidden = !isDuckInIt 
    } 
}

Now to teach StreamChatUI sdk to use our 🦆 view, pass it inside UIConfig

1
2
3
4
import UIKit 
import StreamChatUI 
 
UIConfig.default.messageList.messageContentView = DuckBubbleView.self

Image shows chat message with a duck emoji beside it