Let’s build a conversation view which shows a list of messages and has input text field with send button. Sent and received messages are managed by Conversation object. Conversation object manages a Session object which is simulating networking stack. This kind of setup allows us to look into how to propagate received messages from Session object to Conversation and then to the list view. We’ll jump into using types Combine and SwiftUI provide therefore if you need more information, definitely watch WWDC videos about Combine and SwiftUI.
In the UI we are going to show a list of messages, therefore let’s define a struct for a Message. We’ll make the Message to conform to protocol defined in SwiftUI – Identifiable. We can add conformance by adding id property with type UUID what provides us unique identifier whenever we create a message. Identification is used by SwiftUI to identify messages and finding changes in the messages list.
Session is owned by Conversation and simulates a networking stack dealing with sending and receiving messages. This like a place were we could use delegate pattern for forwarding received messages back to the Conversation. Instead of delegation pattern, we can use Combine’s PassthroughSubject. It enables us to publish new messages which we can then collect on the Conversation side. Great, but let’s see how to receive messages which are published by PassthroughSubject.
Conversation is responsible of receiving messages from the Session and keeping the current history: list of messages. For receiving messages published by Session, we can use a subscriber called sink, which just gives access to values flowing through the channel. Subscribers are added directly to publishers, then publisher sends a subscription object back to the subscriber what subscriber can use for communicating with publisher. Here communicating means requesting values from publisher. To recap: Session owns PassthroughSubject what Conversation starts to listen by attaching subscriber to it.
Conversation also conforms to SwiftUI’s BindableObject. This protocol requires us to implement didChange publisher and it is our responsibility to trigger change in didChange publisher when the conversation object changes. Changes in this context are changes in the message list. When conversation is used in SwiftUI view, then SwiftUI subscribes to this publisher and updates itself whenever didChange.send() is called.
Creating simple list view
In SwiftUI, views are described by value types conforming to View protocol. Every view return their content in the body property. Our UI is simple enough and requires to add navigation view, list and then input view. List is the table view construct which creates new rows whenever it needs to. As we made Message to conform to Identifiable, then we can pass the messages directly to the List.
Input view contains text field and button for sending the entered message. Input text is local state owned by the view itself. @State is a property wrapper and internally it creates a separate storage where the input text is stored and read during view updates.
Now we have a the whole picture put together. Conversation object manages messages and lets SwiftUI know when it changes through didChange publisher. Then SwiftUI compares the changes in the view hierarchy and updates only what is needed.
We created a basic list view what displays messages in the conversation object. We used simple constructs for passing on the data down from the Session to the SwiftUI layer. The aim of the sample project was to try out some of the ways Combine and SwiftUI allow us to build views.
ConversationInSwiftUI (Xcode 11b2, Swift 5.1)