SwiftUI Combine

Sarim Khan
10 min readJan 3, 2023

--

In SwiftUI, Combine is a framework that provides a declarative Swift API for processing values over time. It allows developers to react to changes in data and perform side-effects in a simple and predictable way.

dense forest girl in red coat working on MacBook iOS programming, digital art

Combine is particularly useful in SwiftUI because it allows developers to bind asynchronous data to their user interface in a declarative way, using the @ObservedObject, @StateObject, and @Binding property wrappers.

Overall, Combine is a powerful tool for handling asynchronous logic and data in a concise, expressive, and maintainable way, and is an essential part of the SwiftUI ecosystem.

PropertyWrapper:

https://www.youtube.com/watch?v=q_fFZ5FryPI&t=23s

The PropertyWrapperView consists of a toggle and a MyTextField view. The toggle has a @State property wrapper applied to it, which adds mutability and observation behavior to the isOn variable. The MyTextField view has a @Binding property wrapper applied to its textField and isOn variables, which adds a reference-like behavior to them.

The MyTextField view also contains a conditional statement that checks the value of isOn. If isOn is true, it displays a TextField for the user to enter their name. If isOn is false, it displays an empty view. The MyTextField view also displays the value of textField below the TextField, and applies an animation to the view using the animation modifier.

//
// PropertyWrapperView.swift
// SwiftUIPlaygroung
//
// Created by sarim khan on 22/07/2022.
//

import SwiftUI

struct PropertyWrapperView: View {

@State private var isOn:Bool=false
@State private var textField:String=""

var body: some View {
VStack(alignment:.leading,spacing:20){
Toggle(isOn: $isOn) {
Text("Edit Name")
}

MyTextField(textField: $textField,isOn: $isOn)
}
.padding()
}
}

struct MyTextField: View {

@Binding public var textField:String
@Binding public var isOn:Bool

var body: some View {
VStack(alignment:.leading){
if(isOn){

TextField("Enter Name", text: $textField)

}else{
EmptyView()
}

Text(textField)
}
.animation(.easeInOut(duration: 0.4), value: isOn)
}
}


struct PropertyWrapperView_Previews: PreviewProvider {
static var previews: some View {
PropertyWrapperView()
}
}

StateObject:

https://www.youtube.com/watch?v=wOa6jYtur0w

In SwiftUI, @StateObject is a property wrapper that you can use to bind an object to a view. It's similar to @ObservedObject, but has some additional behavior that makes it more suitable for use as the source of truth for a view.

When you use @StateObject in a view, it adds observation behavior to the object, which means that the view will automatically update whenever the object changes. This can be useful for keeping the view in sync with the data it displays, and is a key part of how SwiftUI works.

Example:

This code defines a class called StateObjectViewModel, which conforms to the ObservableObject protocol. The class has two published properties, operatingSystem and age, which are both marked with the @Published attribute. This attribute adds publisher behavior to the properties, which allows them to be observed by a SwiftUI view.

The StateObjectViewModel class also has two methods, changeAge and changeOperatinSystem, which use the Combine framework to perform asynchronous operations and update the published properties.

The changeAge method uses the map operator to multiply the value of age by 13, and the sink operator to assign the result back to the age property. The changeOperatinSystem method uses the map operator to uppercase the value of operatingSystem, and the sink operator to assign the result back to the operatingSystem property.

//
// StateObjectViewModel.swift
// SwiftUIPlaygroung
//
// Created by sarim khan on 26/07/2022.
//

import Foundation
import Combine

class StateObjectViewModel:ObservableObject{

@Published var operatingSystem:String = "iOS"
@Published var age:Int = 30

var cancellable=Set<AnyCancellable>()

func changeAge() {
Just(30)
.map { input in
return input * 13
}
.sink { value in
self.age=value
}
.store(in: &cancellable)
}

func changeOperatinSystem() {
Just("Android")
.map { input in
return input.uppercased()
}
.sink { value in
self.operatingSystem=value
}
.store(in: &cancellable)
}
}

Example (cont.):

This code defines a StateObjectView in a SwiftUI app, which displays the operatingSystem and age properties of a StateObjectViewModel object.

The StateObjectView has a @StateObject property wrapper applied to its stateObjectViewModel variable, which adds observation behavior to the object. This allows the view to automatically update whenever the stateObjectViewModel object changes.

The view also has two buttons that call the changeAge and changeOperatinSystem methods on the stateObjectViewModel object, which use the Combine framework to perform asynchronous operations and update the age and operatingSystem properties.

//
// StateObjectView.swift
// SwiftUIPlaygroung
//
// Created by sarim khan on 26/07/2022.
//

import SwiftUI

struct StateObjectView: View {

@StateObject private var stateObjectViewModel=StateObjectViewModel()

var body: some View {

VStack(spacing:25) {
Text(stateObjectViewModel.operatingSystem)
.font(.title)
.fontWeight(.medium)

Text("\(stateObjectViewModel.age)")
.font(.title3)
.fontWeight(.light)

Button {
stateObjectViewModel.changeAge()
} label: {
Text("Change Age")
}

Button {
stateObjectViewModel.changeOperatinSystem()
} label: {
Text("Change Operatin System")
}

}

}
}

struct StateObjectView_Previews: PreviewProvider {
static var previews: some View {
StateObjectView()
}
}

ObservedObject:

https://www.youtube.com/watch?v=wGEzjWwfP5Y

In SwiftUI, @ObservedObject is a property wrapper that adds observation behavior to an object that conforms to the ObservableObject protocol. It allows the object to be used as the source of truth for a view, and triggers updates to the view whenever the object changes.

@ObservedObject is similar to @StateObject, but has some differences in behavior. For example, @ObservedObject does not create a new instance of the wrapped object whenever the view that uses it is recreated, which can be useful for preserving state across multiple view updates.

Example:

This code defines a class called ObservedObjectViewModel, which conforms to the ObservableObject protocol. The class has a published property called items, which is marked with the @Published attribute. This attribute adds publisher behavior to the items property, which allows it to be observed by a SwiftUI view.

The ObservedObjectViewModel class also has a method called appendItems, which uses the Combine framework to perform an asynchronous operation and append a new item to the items array. The method uses the map operator to lowercase the input item, and the sink operator to append the result to the items array.

//
// ObservedObjectViewModel.swift
// SwiftUIPlaygroung
//
// Created by sarim khan on 27/07/2022.
//

import Foundation
import Combine

class ObservedObjectViewModel:ObservableObject{

@Published var items:[String]=[]

var cancellable=Set<AnyCancellable>()

func appendItems(item:String) {
Just(item)
.map { input in
return input.lowercased()
}
.sink { value in
self.items.append(value)
}
.store(in: &cancellable)
}
}

Example(Cont.):

This code defines two views in a SwiftUI app: ObservedObjectView and MyView.

The ObservedObjectView has a @StateObject property wrapper applied to its observedObjectViewModel variable, which adds observation behavior to the object. It also displays a list of items from the items array in the observedObjectViewModel object, and includes a MyView view in its hierarchy.

The MyView view has a @ObservedObject property wrapper applied to its observedObjectViewModel variable, which also adds observation behavior to the object. It also has a @State property wrapper applied to its textField variable, which adds mutability and observation behavior to the variable. The MyView view displays a text field that allows the user to enter items, and calls the appendItems method on the observedObjectViewModel object whenever the user submits the form.

Overall, this code demonstrates how @ObservedObject and @StateObject property wrappers can be used to bind objects to views and observe changes to their properties, and how the Combine framework can be used to perform asynchronous operations and update observed properties.

//
// ObservedObjectView.swift
// SwiftUIPlaygroung
//
// Created by sarim khan on 27/07/2022.
//

import SwiftUI

struct ObservedObjectView: View {

@StateObject private var observedObjectViewModel=ObservedObjectViewModel()


var body: some View {
VStack {
List(observedObjectViewModel.items,id: \.self){items in
Text(items)
}

MyView(observedObjectViewModel: observedObjectViewModel)

}
}
}

struct MyView: View {

@ObservedObject var observedObjectViewModel:ObservedObjectViewModel
@State private var textField:String=""

var body: some View {
VStack{
TextField("Enter Items", text: $textField)
.onSubmit {
observedObjectViewModel.appendItems(item: textField)
textField.removeAll()
}
}
.padding()
}
}



struct ObservedObjectView_Previews: PreviewProvider {
static var previews: some View {
ObservedObjectView()
}
}

Future:

https://www.youtube.com/watch?v=r2cnE7AQQiI

In SwiftUI, Future is a type that represents an asynchronous operation that produces a single result. It is used to perform tasks that take time to complete, such as fetching data from a server or accessing a device's camera.

A Future is created by providing a closure that performs the asynchronous operation, and can be used to chain together a sequence of operations using the map and flatMap operators. When the Future completes, it calls a completion closure with the result of the operation.

Example:

This code defines a class called FutureViewModel, which conforms to the ObservableObject protocol. The class has two published properties, name and num, which are both marked with the @Published attribute. This attribute adds publisher behavior to the properties, which allows them to be observed by a SwiftUI view.

The FutureViewModel class has two methods, getFuture and addFuture, which return Future objects that perform asynchronous operations. The getFuture method returns a Future that completes with a string value, and the addFuture method returns a Future that completes with a double value.

The FutureViewModel class also has two methods, returnFuture and returnAddFuture, which use the Combine framework to subscribe to the Future objects and assign their results to the name and num properties, respectively.

//
// FutureViewModel.swift
// SwiftUIPlaygroung
//
// Created by sarim khan on 01/08/2022.
//

import Foundation
import Combine

class FutureViewModel:ObservableObject{

@Published var name:String=""
@Published var num:Double=0.0

var cancellable=Set<AnyCancellable>()

func getFuture() -> Future<String,Never> {
return Future {promise in

promise(.success("iOS"))
}
}

func addFuture(num1:Double,num2:Double) -> Future<Double,Never> {

let sum=num1+num2

return Future {promise in

promise(.success(sum))
}
}

func returnFuture() {

getFuture().sink { value in
self.name=value
}
.store(in: &cancellable)
}

func returnAddFuture(num1:Double,num2:Double) {

addFuture(num1: num1, num2: num2)


.sink { value in
self.num=value
}
.store(in: &cancellable)
}



}

Example(Cont.):

This code defines a SwiftUI view called FutureView, which has a @StateObject property wrapper applied to its futureViewModel variable. The futureViewModel object is an instance of the FutureViewModel class, which conforms to the ObservableObject protocol and has two published properties: name and num.

The FutureView view displays the values of the name and num properties in text labels, and includes two buttons that trigger the returnFuture and returnAddFuture methods on the futureViewModel object. These methods use the Combine framework to perform asynchronous operations and update the name and num properties, respectively.

//
// FutureView.swift
// SwiftUIPlaygroung
//
// Created by sarim khan on 01/08/2022.
//

import SwiftUI

struct FutureView: View {

@StateObject private var futureViewModel=FutureViewModel()

var body: some View {
VStack(spacing:25) {
Text(futureViewModel.name)
Text("\(futureViewModel.num,specifier: "%.2f")")
.font(.title3)
.fontWeight(.bold)
Button {
futureViewModel.returnFuture()
} label: {
Text("Future")
}
Button {
futureViewModel.returnAddFuture(num1: 2.5, num2: 45.8)
} label: {
Text("Add")
}

}
}
}

struct FutureView_Previews: PreviewProvider {
static var previews: some View {
FutureView()
}
}

Record:

https://www.youtube.com/watch?v=SuF7C0VyZRc&t=14s

In the Combine framework, a Record is a type that represents a sequence of values that are emitted over time. It is similar to a stream of data, where each value in the stream is called a "record." A Record can be used to perform operations on a sequence of values, such as filtering, mapping, and reducing.

Example:

ObservableObject protocol and has a published property called sumOfNumbers. The RecordViewModel class also has a recordPublisher property, which is an instance of the Record type.

The recordPublisher property is initialized with a closure that uses the record object to emit a series of integer values and a completion event.

The RecordViewModel class has a method called getSumOfRecords, which uses the Combine framework to perform a series of operations on the recordPublisher record. These operations include replacing nil values with a default value, removing duplicates, filtering out values that are greater than 5, doubling the remaining values, and summing them up using the scan operator. The final result is assigned to the sumOfNumbers property.

//
// RecordViewModel.swift
// SwiftUIPlaygroung
//
// Created by sarim khan on 01/08/2022.
//

import Foundation
import Combine

class RecordViewModel:ObservableObject{


@Published var sumOfNumbers:Int?
var cancellable=Set<AnyCancellable>()

let recordPublisher = Record<Int?,Never>{ record in

record.receive(nil)
record.receive(1)
record.receive(2)
record.receive(3)
record.receive(4)
record.receive(5)
record.receive(5)
record.receive(6)
record.receive(7)
record.receive(8)
record.receive(9)
record.receive(9)
record.receive(completion: .finished)

}

func getSumOfRecords() {
recordPublisher
.replaceNil(with: 5)
.removeDuplicates()
.filter { value in
value <= 5
}
.map{value in
value * 2
}
.scan(0) { current, next in
current + next
}
.sink { value in
self.sumOfNumbers=value
}
.store(in: &cancellable)
}
}

Example(Cont.):

This code defines a SwiftUI view called RecordView, which has a @StateObject property wrapper applied to its recordViewModel variable. The recordViewModel object is an instance of the RecordViewModel class, which conforms to the ObservableObject protocol and has a published property called sumOfNumbers.

The RecordView view displays the value of the sumOfNumbers property in a text label, and includes a button that triggers the getSumOfRecords method on the recordViewModel object when clicked. This method uses the Combine framework to perform a series of operations on a Record object and update the sumOfNumbers property with the final result.

//
// RecordView.swift
// SwiftUIPlaygroung
//
// Created by sarim khan on 01/08/2022.
//

import SwiftUI

struct RecordView: View {

@StateObject private var recordViewModel=RecordViewModel()

var body: some View {
VStack(spacing:16) {
Text("\(recordViewModel.sumOfNumbers ?? 0)")
.font(.title)
.fontWeight(.bold)

Button {
recordViewModel.getSumOfRecords()
} label: {
Text("Sum")
}


}
}
}

struct RecordView_Previews: PreviewProvider {
static var previews: some View {
RecordView()
}
}

Sequence:

https://www.youtube.com/watch?v=xgNpo0jyTy0

In the Combine framework, a Sequence is a type that represents a finite sequence of values. It is similar to an array, but it is designed to be used with the Combine framework's operators and publishers.

Example:

This code defines a SequenceViewModel class that conforms to the ObservableObject protocol and has a published property called num. The class also defines three Sequence objects: firstSequence, secondSequence, and timerPublisher.

The sumPublishers method combines the values from the firstSequence and secondSequence sequences using the Zip3 operator, and updates the num property with the sum of the combined values. The timerPublisher sequence is also included in the zip, but it is not used in the mapping operation.

When this method is called, the SequenceViewModel object will subscribe to the combined sequence and update the num property with the combined values emitted by the sequence. This can be useful for performing operations on multiple sequences and updating the state of a SwiftUI view based on the results.

//
// SequenceViewModel.swift
// SwiftUIPlaygroung
//
// Created by sarim khan on 06/08/2022.
//

import Foundation
import Combine


class SequenceViewModel:ObservableObject{

@Published var num:Int=0

var cancellable=Set<AnyCancellable>()

let firstSequence = [1,4,5,3,1,6,8,7].publisher

let secondSequence = [5,7,2,3,5,1,2,5].publisher

let timerPublisher = Timer.TimerPublisher(interval: 0.9, runLoop: .main, mode: .default).autoconnect()


func sumPublishers() {
Publishers.Zip3(firstSequence, secondSequence, timerPublisher)
.map{ firstSequence, secondSequence, _ in

firstSequence+secondSequence

}
.sink { value in
self.num=value
}
.store(in: &cancellable)
}


}

Example(Cont.):

This code defines a SequenceView that displays the value of the num property of a SequenceViewModel object. When the view appears on the screen, it calls the sumPublishers method of the sequenceViewModel object, which combines the values from the firstSequence, secondSequence, and timerPublisher sequences using the Zip3 operator and updates the num property with the sum of the combined values.

The SequenceView then displays the updated value of the num property using a large title font. This allows the view to display the combined values of the sequences in real-time as they are emitted.

//
// SequenceView.swift
// SwiftUIPlaygroung
//
// Created by sarim khan on 06/08/2022.
//

import SwiftUI

struct SequenceView: View {

@StateObject private var sequenceViewModel=SequenceViewModel()

var body: some View {
VStack {
Text("\(sequenceViewModel.num)")
.font(.largeTitle)
}
.onAppear{
sequenceViewModel.sumPublishers()
}
}



}

struct SequenceView_Previews: PreviewProvider {
static var previews: some View {
SequenceView()
}
}

--

--