To integrate Combine with SwiftUI, make your model conform to the ObservableObject protocol, mark its fields with @Published so they become publishers, and connect the model to the view with @ObservedObject; a $ binding then keeps the UI and the data in sync in both directions. SwiftUI describes the interface declaratively, and Combine handles the asynchronous events behind it.
SwiftUI brought declarative UI to iOS development, replacing the imperative approach that developers, including us, used before iOS 13. Combine is Apple’s step toward Functional Reactive Programming (FRP). Together they remove a lot of boilerplate from UI building, so developers can meet the requirements of end-users sooner. We created this Combine/SwiftUI tutorial to show how the two frameworks work to each other’s advantage.
Below, we cover what SwiftUI is and how it simplifies UI building, what Combine is, including publishers, subscribers, operators, and the tasks it solves, and a SwiftUI + Combine example of a simple input form.
SwiftUI
Apple defines SwiftUI as a new way to build UIs for Apple platforms using the advantages of Swift.
Another possible definition for SwiftUI is that it’s a framework, a sort of a toolkit, for building UIs in a declarative programming style using Swift in the form of Domain-specific language (DSL).
How SwiftUI simplifies UI building
Today in the vast majority of cases UI building involves using an Interface Builder where you set UI components and their locations relative to each other. Then these components get attached to the code using Outlets, Actions, etc. Thus, the user interface and its behavior are essentially created in two separate places, which in itself is not very convenient.
An alternative to this is making UI in code. Though at the moment this is an even more difficult task. For example, here’s what button building looks like:

As you can see from a piece of code above, it takes to be aware of a lot of nuances:
- You have to be aware of setting the corner radius for a button on its CALayer, instead of a button itself.
- To set an on click event, you need to create a selector, and in order to specify the hello() function there, you set the @objc attribute for a button.
- One more thing you have to be acquainted with is constraints creation.
Along with all the difficulties, you can see the results only after launching an application.
To compare, this is how you create a button in SwiftUI:

The advantage of the declarative programming of SwiftUI is an easier perception, so to speak. You describe the desired result, as opposed to the imperative approach when you need to set the sequence of specific actions to obtain the wanted result. For a broader comparison of the two approaches, see SwiftUI vs UIKit.
How SwiftUI works
As mentioned above, SwiftUI uses DSL. What exactly is Domain-specific language? It’s a so-called domain language that allows describing primitives of a certain particular domain on the basis of a high-level language (in our case it’s Swift, the main iOS development language). Then, with the help of these primitives, the tasks specific to the subject area are solved. In essence, a new language is built on the basis of the old one.
In the example above, such primitives are Button and Text.
In order for SwiftUI syntax to be as simple and straightforward as possible, some changes were introduced in Swift 5.1.
Here are some of those changes:
- Function builders [SE-XXXX]
Thanks to this addition, syntax like this became possible:
HStack {
Text("SwiftUI")
Text("rocks")
}
- Opaque return types [SE-0244]
It allows you to return a protocol with an associated type from a function and eliminates the need to explicitly specify generic parameters of the return type:
var body: some View {...}
- Property wrappers [SE-0258]
SwiftUI uses this to bind the object’s properties to View.
Combine
Apple describes Combine rather simply as a framework to customize the handling of asynchronous events by combining event-processing operators.
What is behind this description
When you begin to dig into Combine’s documentation, you see such technical terms as Publisher, Subscriber, Operators, Cancellable, Scheduler. It immediately becomes clear that the Combine framework is basically an implementation of Apple’s functional reactive programming (FRP) paradigm. iOS developers familiar with such libraries as RxSwift and ReactiveCocoa, immediately get why Combine was created in the first place and what tasks it is supposed to solve. Besides, with Combine, there’s no more need to use any third-party solutions.
Others though may find it difficult to deal with such abstract concepts which nevertheless help to keep the Combine code concise and easily readable. Let’s look a bit closer at what is behind the key ones.
Publishers
Publishers send values that can change over time to Subscribers (one or many instances) or fail with an error. They support the Publisher protocol:
public protocol Publisher {
associatedtype Output
associatedtype Failure : Error
func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}
Where Output is a type of sent values and Failure is a type of produced error. If a Publisher never fails, then the type of error is Never.
Subscribers
Subscribers receive values from a Publisher (one instance) until the subscription is canceled. They support the Subscriber protocol:
public protocol Subscriber : CustomCombineIdentifierConvertible {
associatedtype Input
associatedtype Failure : Error
func receive(subscription: Subscription)
func receive(_ input: Self.Input) -> Subscribers.Demand
func receive(completion: Subscribers.Completion<Self.Failure>)
}
Where Input is a type of received values and Failure is a type of received error. The associated types of Input and Failure are equal to the corresponding Publisher types Output and Failure.
Operators
Publishers and Subscribers hold SwiftUI together, they provide the synchronization between the UI and the underlying model. Besides Publishers and Subscribers, Combine also contains a third feature called Operators. Operators operate on a Publisher, perform some computation, and return another Publisher. For example, a filter operator that opts out some of the values that don’t satisfy the conditions or a map operator that transforms the input values of a certain type into output values of a different type.
What tasks Combine solves
FRP paradigm in general, and Combine in particular increases the level of code abstraction. It allows you to concentrate on events that determine the business logic of the application, instead of spending time dealing with a large number of implementation details. All this allows reducing the amount of boilerplate code (DispatchQueues, Delegates, KVOs, Target-Actions, etc.).
Benefits of using Combine framework:
- Simple asynchronous code.
- Multithreading is simplified.
- Composable components of business logic that can be easily combined into chains.
Let’s say it is necessary for our application to execute 3 asynchronous requests at the same time, and get the execution result.
We could use DispatchGroup and DispatchQueue to solve this complex task. It will require some boilerplate code to create a queue, group, add tasks to the queue, and subscribe to the callback. Things get a little more complicated if asynchronous tasks should return some kind of result. In this case, you’ll need to create instance variables and write and read values from there.
This is how the solution looks like in Combine:
Publishers.Zip3(intAsyncTask, stringAsyncTask, voidAsyncTask)
.sink { (intValue, stringValue, _) in
// tasks executions are finished
}
The alternative is more concise. This is the declarative approach in action. You can focus on what you need to get, rather than burrowing into an abundance of implementation details.
SwiftUI + Combine
And now let’s see what happens if we use SwiftUI and Combine side by side. Consider a simple example of a username input form.
Let’s define the ResetPasswordModel that has only one field taking the user’s input.
class ResetPasswordModel: ObservableObject {
@Published var email = ""
}
- ResetPasswordModel conforms ObservableObject protocol, which means that the ResetPasswordModel’s fields can be used for SwiftUI’s bindings.
- The @Published modifier creates a publisher for the email field, so now it is possible to observe the email property.
Second, let’s define the UI with SwiftUI.
struct ResetPasswordView: View {
@ObservedObject private var model = ResetPasswordModel()
var body: some View {
Form {
Section { TextField("Email", text: $model.email) }
}
}
- The @ObservedObject is a property delegate that creates a connection between the View and the Model. View is notified when the data source is about to change and consequently re-render itself.
- $model.email – $ sign here is used to create a property wrapper that provides a two-way binding to data, so any changes to the value of email will update the TextField, and any changes to the TextField will update email.
FAQ
What is SwiftUI?
SwiftUI is Apple’s framework for building user interfaces on Apple platforms in a declarative style using Swift. It works as a Domain-specific language: primitives such as Button and Text describe the desired result instead of a sequence of steps. This removes the split between Interface Builder and code, and some changes were introduced in Swift 5.1 to keep its syntax simple.
What is the Combine framework?
Combine is Apple’s framework for handling asynchronous events by combining event-processing operators. It is Apple’s implementation of the functional reactive programming paradigm, similar to libraries such as RxSwift and ReactiveCocoa, so there is no longer a need for third-party solutions. Its key concepts are Publisher, Subscriber, Operators, Cancellable, and Scheduler.
What are publishers, subscribers, and operators in Combine?
Publishers send values that can change over time to one or many subscribers, or fail with an error. Subscribers receive values from a publisher until the subscription is canceled, and their input and failure types match the publisher’s output and failure types. Operators work on a publisher, perform a computation such as filter or map, and return another publisher.
How do you use Combine with SwiftUI?
Create a model class that conforms to ObservableObject and mark the fields the view needs with @Published, which turns each field into a publisher. In the SwiftUI view, connect the model with @ObservedObject, so the view is notified and re-renders when data changes. Then pass a binding with the $ sign, for example $model.email, to keep a TextField and the model in sync both ways.
What are the benefits of using Combine?
Combine makes asynchronous code simpler, simplifies multithreading, and turns business logic into composable components that can be combined into chains. It reduces boilerplate such as DispatchQueues, delegates, KVO, and target-actions. For example, running 3 asynchronous requests at once and collecting the results takes a single Publishers.Zip3 call instead of setting up a DispatchGroup, queues, and callbacks.
Conclusion
It seems that Apple has done a great job by providing us with instruments that make the UI-building process more efficient. New SwiftUI’s declarative approach delivers a set of advantages including the increased speed of development, better integration between designers and coders, and code quality enhancement.
Use the combining effect of SwiftUI and Combine to produce a well-structured, efficient, and maintainable code.
Do you want to build an iOS app? Reach out to our team to create a versatile project and win a place under the sun.
Written by Ruslan Krohalev and Kate Shokurova
Related reading
- Supercharge Your App: Top Mobile Frameworks Unleashed
- Swift and Objective-C: An In-Depth Comparison of iOS Programming Languages
- Task Manager — A Tool for Asynchronous iOS Development
* * *
This SwiftUI + Combine tutorial was originally published in December 2019 and was updated in December 2020 to make it more relevant and comprehensive.
