Prototyping Stream’s iOS Chat SDK Using SwiftUI – Part 1

In part one of this 3-part series, you’ll learn how to prototype different elements of the user interface for Stream’s iOS Chat SDK using SwiftUI.

Amos G.
Amos G.
Published December 14, 2021 Updated February 17, 2022
Prototyping Stream's iOS Chat SDK - Part 1

This tutorial will focus on designing the elements that make up the ChannelListView and ChatsView components. In parts two and three, you’ll build on these components by adding interactions and animations, creating a more seamless chat experience.

Resources

You can download the Xcode project containing the SwiftUI source code from GitHub to follow along.

The project contains a ZIP file with several Swift files and folders for the layouts, interactions, and animations for this project. Additionally, you will use SF Symbols 3 as icons for the project. You can find all other assets, such as images and custom icons, in the Xcode assets folder.

Getting Started

Before getting started, download the latest version of Xcode (this tutorial uses v13.0). If you haven’t installed Xcode, download it at Xcode 13 (recommended) or download it from the App Store:

  1. Launch the Mac App Store.
  2. Search for Xcode. If you haven’t installed it, you will see a DOWNLOAD button instead of an UPDATE button.
  3. After downloading Xcode, move it to the Applications folder and follow the instructions to install.

Once you've installed Xcode, you're ready to create a project!

Want to integrate SwiftUI into your project? Check out our new SwiftUI SDK to get started.

Create a New Xcode (SwiftUI) Project

To create a project, launch Xcode:

  1. From the welcome screen, select Create a new Xcode project.
Welcome to Xcode
  1. In the Application window, select App and then Next.
Create new app in Xcode
  1. Enter the Project Name, then select Next.
Project Name
  1. Select a location on your computer where you want to store the project files, then click Create.
Create project in Xcode

After creating the Xcode project and adding your new folders and files, the folder and files structure should look similar to the image below.
In Xcode, you can use new files and folders to organize sections of your designs or compositions in the same way you use groups and layers in design tools.

To add a new file or folder to your project:

  1. Press Control on the keyboard and click the project folder.
  2. Then, select New File or New Group.

In the image below, you can see several folders and files have been added to the project to organize various sections of the app screens.

Folders and files structure

Creating the Navigation Bars and Compose Area

The following sections will show you how to design the header at the top of the screen (HeaderView), a compose area where users can input messages before sending (ComposeAreaView), and the tab bar at the bottom of the screen (TabBarView).

Create the Header View

SwiftUI is a declarative language, which means you describe what and how the interface should be. Then, the SwiftUI system presents the visual representation of what you’ve described.

To create your HeaderView, create a new Swift file HeaderView by right-clicking the folder ChannelList and selecting New File....

Then, enter the code below:

swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import SwiftUI struct HeaderView: View { var body: some View { // Channel List Header HStack { // Horizontal parent container Image("hamburger") Spacer() Text("Stream Chat") .font(.headline) .fontWeight(.bold) Spacer() Image("start-conversation-icon") } } }

In this snippet, you’re telling SwiftUI to present a horizontal parent container (HStack) consisting of an Image on the left, Text in the middle, a space between the content (Spacer), and another Image on the right.

By resuming the Xcode preview, you will see the following header image:

Header image

Note: You can find the hamburger and start conversation icons in the assets folder. You may also upload your own icons to the Xcode assets folder and use them.

Create the Compose Area

Compose area view

The compose area is made up of a TextField, which allows users to input a message. It also has attachment and send buttons.

To create the compose area, create a file called ComposeAreaView and add the following code:

swift
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import SwiftUI struct ComposeAreaView: View { let composeAreaBackground = Color( colorLiteral(red: 0.07058823529, green: 0.07843137255, blue: 0.0862745098, alpha: 1)) let darkDisabled = Color( colorLiteral(red: 0.2980392157, green: 0.3215686275, blue: 0.3607843137, alpha: 1)) @State private var composeText = "Send a message" var body: some View { ZStack(alignment: .top) { HStack { Button { print("Attachment/Paperclip") } label: { Image(systemName: "paperclip") .renderingMode(.original) .foregroundColor(darkDisabled) } Button { print("Bolt") } label: { Image(systemName: "bolt.fill") .foregroundColor(darkDisabled) } TextField("Send a message", text: $composeText) .font(.footnote) .foregroundColor(.secondary) .padding( EdgeInsets(top: 4, leading: 12, bottom: 4, trailing: 0)) .background(composeAreaBackground) .cornerRadius(20) .padding() Button { print("Send") } label: { Image(systemName:"arrow.forward.circle.fill") .renderingMode(.original) .foregroundColor(darkDisabled) } } // Compose Area .padding() .frame(width: .infinity, height: 75) Rectangle() .frame(width: .infinity, height: 1) .foregroundColor(Color(.systemGray3)) } } }

In this snippet, you:

  • Define the color constant composeAreaBackgroud and, as the variable implies, use it for the background of the compose area.
  • Define the constant darkDisabled for the background of the send button when the text field is empty.
  • Define the state variable composeText and use it as a value for the text parameter of the textField, which will display a placeholder text in the text field.
  • Create the paper clip, lightning, and send (arrow) icons as buttons using San Francisco Symbols (SF), and use a horizontal container to arrange them.

Create the Tab Bar

Tab bar

The tab bar appears at the bottom of the screen and allows users to switch between pages within the application.

To create the tab bar, add a new Swift file called TabBarView and enter the following code:

swift
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
import SwiftUI struct TabBarView: View { var body: some View { ZStack { TabView { if #available(iOS 15.0, *) { MentionsView() .tabItem { Label("Chats", image: "message_icon") } .badge(82) MentionsView() .tabItem { Label("Mentions", systemImage: "at") }.badge(1) } else { // Fallback on earlier versions } }.frame(width: .infinity, height: 73) } } }

In this snippet, you create the tab bar by embedding two tab items (”Chats” and ”Mentions”) in a TabView. Because the tab bar allows you to switch between pages, you need to define the page where each tab item should point to.

From the code, you can see the tab items point to the same page, MentionsView. This is intentional, but in your application, you could point the Chats button to a chats page and the Mentions (or code>@) button to the

To attach a tab item to a page, use the .tabItem modifier. In each tab item, add a Label consisting of the text (for example, “Chats”) and an image (SF Symbol).

The tab bar also displays two badges on the top right of each tab item with a notification count. To create the badges, add the .badge modifier and indicate the badge value.

You can learn more about the tab bar by visiting the Apple documentation or the Human Interface Guidelines

Note: The badge modifier is only available in iOS 15. After adding the badge modifier to the code, you’ll get an error. To resolve the error, click the error button when it appears. This will add the 'if #available' version check to the code as shown below.

Creating the Channel List

The channel list view has four main sections:

  • HeaderView
  • CustomSearchBarView,
  • ChanelListView
  • TabBarView

The ChannelListView shows a list of messages containing the user’s avatar, name, message summary, status, timestamp, message count, and delivery receipt.

To create the ChannelListView, you need data. You can find the data for this project in the file ChannelListData.swift (located in this interaction prototyping GitHub.

Channel List view

Create Your Data Source

To get the user data, create a new file and copy the content of ChannelListData.swift to it. Or, you can define the structure of the data and the fields you want to display in the file as seen in the code below:

swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Data Structure struct ChannelListStructure: Identifiable{ var id = UUID() var userAvatar: String var userName: String var userMessageSummary: String var userStatus: String var timestamp: String var unreadMessageCount: String var receipt: String } // Data Storage let ChannelData = [ ChannelListStructure ( userAvatar: "user_luke", userName: "Blushing Drawer", userMessageSummary: "Hi", userStatus: "circle.fill", timestamp: "Today", unreadMessageCount: "12.circle.fill", receipt: "readReceipt") ]

In the code above, there is only one user under ChannelData . You can find the complete list of users in the file ChannelListData after downloading the project file.

Add a Scrollable List (ChannelListView)

To build your scrollable list, create a new Swift file called ChannelListView.swift and populate it with the data source you created in the file ChannelListData.swift.

Then, add the code in the snippet below:

swift
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import SwiftUI struct ChannelListView: View { let StreamBlue = Color( colorLiteral(red: 0, green: 0.368627451, blue: 1, alpha: 1)) let notificationColor = Color( colorLiteral(red: 1, green: 0.2156862745, blue: 0.2588235294, alpha: 1)) let onlineColor = Color( colorLiteral(red: 0.1254901961, green: 0.8784313725, blue: 0.4392156863, alpha: 1)) let appBarColor = Color( colorLiteral(red: 0.07058823529, green: 0.07843137255, blue: 0.0862745098, alpha: 1)) var messages: [ChannelListStructure] = [] var body: some View { VStack { // Header view HeaderView() CustomSearchBarView() // Populating Message List if #available(iOS 15.0, *) { List(messages) { item in HStack { ZStack(alignment: .topTrailing) { Image(item.userAvatar) //User status: Online or offline Image(systemName: item.userStatus) .font(.system(size: 12)) .foregroundColor(onlineColor) } VStack(alignment: .leading){ Text("\(item.userName)") .font(.body) Text("\(item.userMessageSummary)") .font(.footnote) .lineLimit(1) .foregroundColor(.secondary) } Spacer() VStack(alignment: .trailing) { // Number of unread messages Image(systemName: item.unreadMessageCount) .foregroundColor(notificationColor) .font(.footnote) HStack(spacing: 4) { Image(item.receipt) Text("\(item.timestamp)") .font(.footnote) .foregroundColor(.secondary) } } } }.listStyle(.plain) .refreshable{ print("Pull to refresh") } } else { // Fallback on earlier versions } // Tab bar view TabBarView() } // All Views .padding() } }

Here’s what’s going on this snippet:

  1. In ChannelListView.swift, you define the color styles you want to use for UI elements (like StreamBlue, notificationColor, and onlineColor) .
  2. Since the messages will be pulled from a data source, you create a variable called messages and set it as an empty array.. Then, create an empty List.
  3. Inside the List, you use an HStack to arrange the list items horizontally.
  4. Then, you retrieve the userAvatar and userStatus defined in the ChannelListData.swift file and place them in a depth stack (ZStack) so that the status shows on the top-right of the avatar.
  5. Next, you add a vertical container (VStack) in the list and place the userName and userMessageSummary in it.
  6. You add another HStack to arrange the timestamp and readReceipt horizontally.
  7. Then, you embed this horizontal container in a vertical container and place the unreadMessageCount on the content.
  8. For the content in the top-level HStack, you use a Spacer() to push it to the left and right edges of the screen.

To learn more about how to retrieve data from another Swift file to populate a list in SwiftUI, watch Apple’s Introduction to SwiftUI.

Combining Header, Search Bar, Channel List, and Tab Bar

To put it all together, embed the scrollable list created in ChannelListView.swift in a vertical container VStack. This will serve as the main layout container for the four sections of the screen: your header, search bar, list, and tab bar.

In the main vertical container, add the header HeaderView() and search bar CustomSearchBarView(), which are contained in another Swift file above the list.

Finally, add the TabBarView(), which is also contained in another Swift file, so that it appears at the bottom of the screen.

Designing Inbound and Outbound Messages

Inbound and outbound messages

This section shows you how to build the composition of incoming and outgoing messages with the user's avatar, message bubbles, text, timestamp, emojis, and delivery status.

Creating an Inbound Message

Inbound message

Create a new file called InboundSingleLineView.swift and add the following code:

swift
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
35
import SwiftUI struct InboundSingleLineView: View { let inboundBubbleColor = Color( colorLiteral(red: 0.07058823529, green: 0.07843137255, blue: 0.0862745098, alpha: 1)) var body: some View { HStack(alignment: .bottom) { Image("user_han") .resizable() .frame(width: 36, height: 36) VStack(alignment: .leading) { ZStack(alignment: .bottomLeading) { RoundedRectangle(cornerRadius: 18) .frame(width: 200, height: 36) .overlay( Text("They ain't in space yet") .foregroundColor(.white) ) Rectangle() .frame(width: 20, height: 21) } // Inbound Message Bubble .foregroundColor(inboundBubbleColor) Text("Space Cadet 9.01 AM") .font(.footnote) .foregroundColor(.secondary) } } } }

In this snippet, you pull the user’s avatar from the assets folder, adding the user’s name, text on the message bubble, and the timestamp:

  1. Begin by defining the chat bubble color as inboundBubbleColor using color literal and stating the red, green, and blue color values.
  2. Next, draw a rectangle for the chat bubble and place a text on the rectangle using the .overlay modifier.
  3. Embed the chat bubble with the text in a VStack layout container and add the text Space Cadet 9.01 AM below the bubble.
  4. Align the Space Cadet 9.01 AM and the bubble to the left using the alignment parameter of the VStack and set its value to .leading.
  5. Finally, put the VStack inside an HStack` and add the user avatar to the left.
  6. In the horizontal layout container, set the alignment parameter to .bottom so that the avatar appears at the bottom left of the message bubble and the text below it.

Creating an Outbound Message

Next, you will create an outbound message by embedding text, a message bubble, and emojis using stack views, vertical stack (VStack), horizontal stack (HStack), and depth stack (ZStack) in a new Swift file called OutboundSingleLineView.swift.

To create an outbound message, add the following code:

swift
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
35
36
37
import SwiftUI struct OutboundSingleLineView: View { let inboundBubbleColor = Color( colorLiteral(red: 0.07058823529, green: 0.07843137255, blue: 0.0862745098, alpha: 1)) let StreamBlue = Color( colorLiteral(red: 0, green: 0.368627451, blue: 1, alpha: 1)) var body: some View { VStack(alignment: .trailing) { ZStack(alignment: .bottomTrailing) { RoundedRectangle(cornerRadius: 18) .frame(width: 144, height: 36) .overlay( Text("😅 😅 😅 😅") .frame(width: 240, height: 56) .foregroundColor(.white) ) Rectangle() .frame(width: 20, height: 21) } // Inbound Message Bubble .foregroundColor(inboundBubbleColor) HStack(spacing: 4) { // Timestamp and read receipt Text("82") .foregroundColor(StreamBlue) Image("readReceipt") Text("18.37 PM") .foregroundColor(.secondary) } .font(.footnote) } } }

When you preview your code, Xcode will present the composition as seen in the image below:

Outbound message

You can see the rectangle has three rounded corners and the bottom right corner is straight.

To give your outbound messages this visual effect:

  1. Place the rounded rectangle (bubble) in a ZStack.
  2. Then, draw another small rectangle with a width of 20 and a height of 21 above the bubble.
  3. Set the alignment parameter of the ZStack to .bottomTrailing so that the small rectangle appears at the bottom right of the bubble to form a straight edge.
  4. Next, add the emojis using text and place them in front of the bubble with the .overlay modifier. You can use the .overlay modifier or ZStack in SwiftUI to put an element in front of another.
  5. Create a horizontal container HStack and place the readRecept image and the timestamp as text inside the HStack.
  6. Finally, put all the content in a VStack so that it becomes the root container for all the message views and use the alignment parameter to align the content to the right by setting the value as .trailing.

Building the Message List in ChatsView

Chats View

The ChatsView screen has three main sections: MessageListHeader(), ScrollView(), and ComposeAreaView(). It is laid out by composing inbound and outbound messages created in the previous sections as well as other message components found in the folders below.

To get started, download the project file and create a new Swift file called ChatsView.swift.

With the ChatsView file created, add the following code:

swift
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import SwiftUI struct ChatsView: View { var body: some View { VStack { Spacer() MessageListHeaderView() ScrollView { // Chat area VStack { HStack { // Left Message //InboundDoubleLineView() InboundMessageView() Spacer() } HStack { // Right Message Spacer() OutboundSingleLineView() } HStack { // Left Message InboundSingleLineView() Spacer() } HStack { // Left Message InboundDoubleLineView() Spacer() } HStack { // Right Message Spacer() ClappingHandsEmojiView() } HStack { // Left Message InboundTrippleLineView() Spacer() } HStack { // Right Message Spacer() OutboundTrippleLineView() } HStack { // Right Message Spacer() OutboundDoubleLineView() } HStack { // Right Message Spacer() OutboundSingleLineView() } HStack { // Right Message Spacer() RevolvingHeartsView() .padding(EdgeInsets(top: 40, leading: 0, bottom: 0, trailing: 40)) } } .padding() } // Chat area ComposeAreaView() } // All Views } }

In this snippet, you bring the inbound and outbound messages together onto one screen.

Here’s a walkthrough of the steps:

  1. Begin by creating the root VStack, which you will use to arrange the MessageListHeaderView, the ScrollView, and the ComposeAreaView vertically.
  2. In the root VStack, add the MessageListHeaderView and ComposeAreaView as components.
  3. Between the header and the compose area, add a ScrollView, which enables scroll functionality.
  4. Inside the ScrollView, add a VStack as a root layout container.
  5. Add horizontal containers HStack in the root vertical container to arrange the inbound and outbound messages.
  6. In the top HStack, add InboundMessageView() and place a spacer below it so that it pushes the content to the left of the screen.
  7. Next, add another HStack and place OutboundSingleLineView() in it. By adding a spacer below OutboundSingleLineView(), the content will be pushed to the right edge of the screen.

Repeat the above steps to bring as many inbound and outbound messages to the scrollable section as possible.

Congratulations!

Well done. This tutorial covered how to design the main screens and chats view of the Stream iOS Chat SDK using SwiftUI.

You also learned how to create child or sub-views (like inbound and outbound messages) as components by creating them in new files in Xcode.

You can download the SwiftUI source code for this project as a zip file from GitHub. In part two of this tutorial, you will use the designs you created in part one to design interactions and micro-interactions for the iOS Chat SDK.

Integrating Video With Your App?
We've built a Video and Audio solution just for you. Check out our APIs and SDKs.
Learn more ->