SwiftUI Combine
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.
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()
}
}