SwiftUI uses declarative UI programming, where Views have immutable state, changing automatically as the data state they depend on changes. This is a mindset change for most iOS/macOS developers: instead of changing the UI with a “command and control” MVC mindset, data state changes indirectly change the UI.
But how can we signal to SwiftUI’s unseen magical hand that it needs to make changes to our UI?
Three Potential Approaches
This post presents three approaches to signaling changes to the SwiftUI that I’ve found useful:
- Binding View Elements to
@Published
variables in a ViewModel (or an observable Environment object) - Using the
.onChanged()
event in a View to monitor when a@Published
variable changes—and then fire a code block in the view layer. - Using a
NotificationCenter
message to send an event that a ViewModel or View can subscribe to.
Binding UI Elements to @Published Variables
This first approach should be used whenever possible. It’s a classic MVMM approach where the ViewModel holds application state, and the UI simply presents that state. It’s simple and effective.
To use this approach, create a ViewModel that conforms to the @ObservableObject
protocol, and create @Published
variables for any ViewModel properties that should be used to configure UI.
In this example, I’ve created a ViewModel that computes a random color every 1 second via a Timer
:
1class ContentViewModel: ObservableObject { 2 @Published var changingColor = Color.blue 3 4 init() { 5 Timer.scheduledTimer( 6 withTimeInterval: 1.0, 7 repeats: true, 8 block: { _ in 9 self.changingColor = self.randomColor()10 }11 )12 }13 14 private func randomColor() -> Color {15 return Color(red: Double.random(in: 0.0...1.0),16 green: Double.random(in: 0.0...1.0),17 blue: Double.random(in: 0.0...1.0))18 }19}
The companion view creates the ViewModel instance as a @StateObject, and uses the changingColor
in the body.
1 struct ContentView: View {2 @StateObject var viewModel = ContentViewModel()3 4 var body: some View {5 RoundedRectangle(cornerRadius: 20.0)6 .foregroundColor(viewModel.changingColor)7 }8 }
Be careful to use @StateObject with ViewModels when needed instead of @ObservedObjects. The latter may be destroyed during view redraws, and you’ll have weird bugs where your state is lost unexpectedly. Donny Wals discusses this in detail in this post
When the application runs, the color of the rectangle changes to a random color every second as the Timer fires.
Implementing .onChange() in the View
If the UI changes require more knowledge of the View than the ViewModel has, we can use an onChange()
event in the View to run a code block when a @Published
variable in the ViewModel (or in an Environment object) changes.
In the below example, we’ll rotate the view to a random angle, but for some reason the rotation is adjusted depending on the the values returned by a GeometryReader
. Since the ViewModel won’t have knowledge of the GeometryReader
, instead we’ll observe the change in the View, and run the final rotation calculation there. This is contrived, but hopefully illustrates that a View is capable of running some code when the ViewModel state changes rather than leaving all control in the ViewModel.
Here’s the View with the .onChange()
event added:
1 struct ContentView: View { 2 @StateObject var viewModel = ContentViewModel() 3 @State private var degrees = 0.0 4 5 var body: some View { 6 GeometryReader { geom in 7 RoundedRectangle(cornerRadius: 20.0) 8 .foregroundColor(viewModel.changingColor) 9 .frame(width: viewModel.largeSize ? 200 : 100,10 height: viewModel.largeSize ? 100 : 50)11 .rotationEffect(Angle.degrees(degrees))12 .onChange(of: viewModel.rotationAngle) { newValue in13 withAnimation {14 if geom.size.width > 300 {15 degrees = newValue16 } else {17 degrees = newValue - 20.018 }19 }20 }21 }22 }23 }
The effect of this code is that whenever the ViewModel’s changingColor
property changes, the RoundedRectangle
’s color is changed directly by the ViewModel as before, but the onChange()
event handler is the code block that animates a change to its rotation angle.
Observing Notification Center messages
The above two techniques should be sufficient to cover most state changes between ViewModels and EnvironmentObjects. But there is another tool in the toolbox that I’ve occasionally used: our old friend NotificationCenter
.
If the UI needs to respond to events that are completely outside the MVVM related objects (and aren’t even part of the SwiftUI stack), such as events that occur within a traditional Singleton object, a NotificationCenter
message can provide the needed glue to trigger UI events in the SwiftUI layers.
For example, let’s say we have some DataService running as a Singleton, and that service receives some state change from the outside world…for example a Push Notification or an IoT message, and this outside world event should cause a UI update.
I’ll simulate this by implementing a DataService singleton that sends a Notification event every 2 seconds. This is a simulation to keep the demo code small, but imagine this could be a silent push notification or other network event outside the context of the app itself.
1 class DataService { 2 static let notificationName = "CHANGE_SIZE" 3 4 class var sharedInstance:DataService { 5 struct SingletonWrapper { 6 static let singleton = DataService() 7 } 8 return SingletonWrapper.singleton 9 }10 11 func startService() {12 Timer.scheduledTimer(13 withTimeInterval: 2.0, 14 repeats: true, block: { _ in15 NotificationCenter.default.post(16 name: NSNotification.Name(17 rawValue: DataService.notificationName), 18 object: nil)19 })20 }21 }
For the purposes of demo, we’ll start the notification events from this service as the application is launched:
1 struct SwiftUINotifyExampleApp: App { 2 var body: some Scene { 3 WindowGroup { 4 ContentView() 5 } 6 } 7 8 init() { 9 DataService.sharedInstance.startService()10 }11 }
Now the DataService is sending a notification message to any subscribers every 2 seconds for the life of the app--but at this point, nobody is listening for this event.
We always prefer to put logic into the ViewModel (not the View), so the ContentViewModel
will listen for the NotificationCenter
event.
Whenever the NotificationCenter
event arrives, the ViewModel will toggle the RoundedRectangle
between a large and small rendering mode. Note that the animation this time is done right in the ViewModel.
1 class ContentViewModel: ObservableObject { 2 @Published var changingColor = Color.blue 3 @Published var rotationAngle = 0.0 4 @Published var largeSize = true 5 private var changeSizeMessageObserver:NSObjectProtocol? 6 7 init() { 8 Timer.scheduledTimer(withTimeInterval: 1.0, 9 repeats: true, block: { _ in10 self.changingColor = self.randomColor()11 self.rotationAngle = self.changingColor.cgColor!.components![0] * 360.012 })13 14 changeSizeMessageObserver =15 NotificationCenter.default.addObserver(16 forName: NSNotification.Name(17 rawValue: DataService.notificationName),18 object: nil, 19 queue: nil) {_ in20 withAnimation {21 self.largeSize.toggle()22 }23 }24 }25 26 deinit {27 if let obs = changeSizeMessageObserver {28 print("Removing observer")29 NotificationCenter.default.removeObserver(obs)30 }31 }32 33 private func randomColor() -> Color {34 return Color(red: Double.random(in: 0.0...1.0),35 green: Double.random(in: 0.0...1.0),36 blue: Double.random(in: 0.0...1.0))37 }38 }
And the view is updated to connect to the new largeSize
ViewModel @Published
property:
1 RoundedRectangle(cornerRadius: 20.0)2 .foregroundColor(viewModel.changingColor)3 .frame(width: viewModel.largeSize ? 200 : 100,4 height: viewModel.largeSize ? 100 : 50)5 .rotationEffect(Angle.degrees(degrees))
Final App
Here's what the final app looks like when we run it in a simulator. Note the three changes to the UI triggered by each of the techniques:
- Color change - View color bound to ViewModel @Published variable, which changes the color and triggers a view redraw.
- Rotation - View subscribes to .onChange() of the rotation angle, and calcuales the final rotation value based on the ViewModel's initial recommendation.
- Size - ViewModel subscribes to a NotificationCenter message, and animates the size change for the View.
Summary
Changing our mindset from imperative programming where we directly change UI (e.g. UIKit) to declarative programming where we use events and data changes to signal UI changes can be a mind-bender—but once we learn some new patterns and tools, it’s easy to accomplish the same ends with the added safety and predictability of decorative UI programming.
The three techniques covered in this post:
- Binding @Published variables in ViewModels and Environment objects — use whenever possible.
- Using onChanged() in the View to observe change events to @Published variables — use when the publisher of the variable doesn’t have all the information needed to effect the correct UI change.
- Using NotificationCenter. Use when the event that should signal the change is external to the MVVM architecture (for example a legacy service or a singleton that isn’t installed as an Environment object).
Code
The source code for this sample project is available on Github:
FAQs
How to pass data from ViewModel to view in SwiftUI? ›
- Use @State and @Binding property wrappers.
- Use @StateObject property wrapper.
- Use @EnvironmentObject property wrapper.
The simplest approach to share data between views in SwiftUI is to pass it as a property. SwiftUI views are structs. If you add a property to a view and don't provide an initial value, you can use the memberwise initializer to pass data into the view.
What is the difference between MVC and MVVM in SwiftUI? ›The difference between the MVC and MVVM is View and controller are responsible for calculating the value and assigning the value so the load is more on View and Controller where in MVVM View and Controller are only responsible for assigning the value not calculating the value.
What is the difference between view and ViewModel in SwiftUI? ›A ViewModel provides a binding to the properties of its model data, and a SwiftUI view automatically updates when that data changes.
How to pass data from ViewModel to activity? ›Passing Data between fragments in Android using ViewModel:
This is because ViewModel is tied to the activity lifecycle. To actually pass the data between fragments, we need to create a ViewModel object with an activity scope of both the fragments, initialize the ViewModel , and set the value of the LiveData object.
The View Models can be passed to the View by creating a dynamic property in ViewBag. It can be passed using the Model Property of the ViewData. The Model property allows us to create the strongly typed Views using the @model directive.
Can a ViewModel share between activities? ›You can't share a ViewModel across Activities. That's specifically one of the downsides of using multiple activities as per the Single Activity talk.
How do I share a ViewModel between two fragments? ›- viewModels() gives you the ViewModel instance scoped to the current fragment. ...
- activityViewModels() gives you the ViewModel instance scoped to the current activity.
Getting Started with Navigation Links:
Text("Navigate here.") Text("Now on the second view.") As you can see from the example above, the navigation link will act as a standard link. Once clicked, the application will then navigate to and display the view that was set as the destination.
- Communication between various MVVM components and data binding can be painful.
- Code reusability of views and view model is difficult.
- Managing view models and their state in nested views and complex UI's is difficult.
- MVVM for beginners is hard to put to use.
Why prefer MVVM over MVC? ›
MVVM is better than MVC/MVP because of its unidirectional data and dependency flow. Dependency is one way, thus it is a lot easier to decouple it when we need to. It is also easier for testing. All my projects(written in Kotlin for Android app) are based on MVVM.
Which design pattern is best for SwiftUI? ›Although you can use any design pattern to build your SwiftUI apps, MVVM pattern is preferred due to the declarative nature of the SwiftUI framework.
What is the advantage of using ViewModel? ›The ViewModel class allows data to survive configuration changes such as screen rotations. The ViewModel class also helps in implementing MVVM(Model-View-ViewModel) design pattern which is also the recommended Android app architecture for Android applications by Google.
Should every view have a ViewModel? ›It is not necessary for any view to have a view model to function. However, Google recommends the pattern because it leads to good design. If you want to say that your app is MVVM, then you need to keep the view separated from the data that drives it.
When should I use ViewModel? ›The purpose of the ViewModel is to acquire and keep the information that is necessary for an Activity or a Fragment. The Activity or the Fragment should be able to observe changes in the ViewModel. ViewModels usually expose this information via LiveData or Android Data Binding.
Can we observe live data in a ViewModel? ›ViewModel allows the app's data to survive configuration changes. In this codelab, you'll learn how to integrate LiveData with the data in the ViewModel . The LiveData class is also part of the Android Architecture Components and is a data holder class that can be observed.
How do you collect flow in ViewModel? ›- Collect Flow Using ViewModel.viewModelScope.launch{} Copy. class FlowViewModel: ViewModel() { private val _state: MutableState<Int?> = ...
- Collect Flow Using LifeCycleCoroutineScope.LaunchWhenStarted() Copy. ...
- Collect Flow Using LifeCycle. RepeatOnLifecycle()
lifecycle . When you want to make a component lifecycle-aware, simply implement its LifecycleObserver interface and declare it as an observer from the lifecycle owner you'd like to observe. If you don't need to react to all lifecycle events, you can use the DefaultLifecycleObserver interface.
Is it OK to pass context to ViewModel? ›It is a good practice (but not mandatory) to keep ViewModel free from Android framework references like Activity, Context, Drawables etc. So passing in a context to ViewModel is not recommend.
How does ViewModel retain data? ›ViewModel objects are automatically retained (they are not destroyed like the activity or a fragment instance) during configuration changes so that data they hold is immediately available to the next activity or fragment instance.
How to get intent data in ViewModel? ›
So to get the argument intent in ViewModel is…
Then in the ViewModel, the way to access the intent is just simply using the key to extract the intent. extra data through the savedStateHandle . ) : ViewModel(), LifecycleObserver { // Assume we're making the intent data a lifedata. That's simple, isn't it!
So the view model doesn't know anything about the view except its role (default view, details view, ...), and the view contains no code to create another view. Also, the view models are in a separate assembly which doesn't reference any Wpf assembly. Save this answer.
Does ViewModel survive configuration changes? ›So, we finally found that our ViewModel is stored in the ActivityThread that is a singleton so the ViewModel is not destroyed during a configuration changed.
Does ViewModel have a lifecycle? ›The lifecycle of a ViewModel extends from when the associated UI controller is first created, till it is completely destroyed. Never store a UI controller or Context directly or indirectly in a ViewModel. This includes storing a View in a ViewModel.
Should every fragment have its own ViewModel? ›If your fragments are isolated from each other, then feel free to use a ViewModel per Fragment. If you need to share the state (and by state I mean LiveData), then it's reasonable to have a ViewModel per activity.
How does ViewModel internally work? ›ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way.It is the main component in the MVVM architecture. ViewModel can be created with activity context or fragment context. When a ViewModel object is created, it is stored inside Activity OR FragmentManager.
Can one fragment have multiple Viewmodels? ›In fact you can have multiple view models for a single fragments doing different things for you. Keeping everything in one view model would make testing harder and also you have to keep viewmodel for all 20 fragments may be scoping to activity. Save this answer.
What is the difference between navigation view and navigation link? ›The NavigationView is used to wrap the content of your views, setting them up for subsequent navigation. The NavigationLink does the actual work of assigning what content to navigate to and providing a UI component for the user to initiate that navigation.
How to pass value from one view to another in Swift? ›- By using an instance property (A → B)
- By using segues with Storyboards.
- By using instance properties and functions (A ← B)
- By using the delegation pattern.
- By using a closure or completion handler.
- By using NotificationCenter and the Observer pattern.
First, there is an element of performance: structs are simpler and faster than classes. I say an element of performance because lots of people think this is the primary reason SwiftUI uses structs, when really it's just one part of the bigger picture.
Should you use singletons in Swift? ›
The main advantage of the singleton pattern is its ease of access. Wherever you need the shared instance, you can access it without modifying your code further. It's also possible to enforce a single instance if you make the Facade class's initializer private.
Is MVVM deprecated? ›The MVVM Toolkit is inspired by MvvmLight, and is also the official replacement for it now that the library has been deprecated.
Does SwiftUI need MVVM? ›SwiftUI comes with MVVM built-in. In the simplest case, where the View does not rely on any external state, its local @State variables take the role of the ViewModel , providing the subscription mechanism ( Binding ) for refreshing the UI whenever the state changes.
What are some pros and cons with using MVVM? ›- MVVM facilitates easier parallel development of a UI and the building blocks that power it.
- MVVM abstracts the View and thus reduces the quantity of business logic (or glue) required in the code behind it.
- The ViewModel can be easier to unit test than in the case of event-driven code.
The MVVM pattern presents a better separation of concerns by adding view models to the mix. The view model translates the data of the model layer into something the view layer can use.
What is the difference between ViewModel and model in MVVM? ›Using the MVVM pattern, the UI of the app and the underlying presentation and business logic are separated into three separate classes: the view, which encapsulates the UI and UI logic; the view model, which encapsulates presentation logic and state; and the model, which encapsulates the app's business logic and data.
Which is the hardest design pattern? ›“What is the most complicated design pattern that you have ever worked with?” And, we mostly agreed that the most complicated one is “Visitor Pattern”.
Which database is best for SwiftUI? ›Products | About | Pros |
---|---|---|
SQLite | This is the most well known relational database across both iOS and Android. | It's an open-source, relational database compatible with Swift. It can be used across multiple platforms, such as iOS and Android, because it uses a file-based system. |
More than 98 percent of respondents to the 2019 survey shared that the iOS apps they had shipped within the past year used Apple's UIKit, and only nine percent of respondents had used SwiftUI in projects they had shipped.
What are the disadvantages of MVVM? ›- Complexity: MVVM is overkill when it comes to creating simple user interfaces. ...
- Difficult to debug: Because data binding is declarative, it can be harder to debug than traditional, imperative code.
What should go in a ViewModel? ›
The simplest kind of viewmodel to understand is one that directly represents a control or a screen in a 1:1 relationship, as in "screen XYZ has a textbox, a listbox, and three buttons, so the viewmodel needs a string, a collection, and three commands." Another kind of object that fits in the viewmodel layer is a ...
Should ViewModel be a struct or class? ›You might want to subclass a base ViewModel, and this is only possible using the features of a class . In any case, you should really use a class for a ViewModel.
What should a ViewModel never contain? ›A ViewModel should never contain any references to fragments, activities, or views.
What is the difference between ViewModel and activity? ›ViewModel has its own lifecycle, which is “longer” than the lifecycles of individual Activities and Fragments. Due to its longer lifecycle, ViewModel will stick around for as long as any instance of the same “logical” Activity or Fragment is “in scope”.
Can a ViewModel have multiple models? ›ViewModel is nothing but a single class that may have multiple models. It contains multiple models as a property.
Which of the following are reasons to use a ViewModel? ›A ViewModel allows you to separate code that updates the UI from code that doesn't need to rely on the UI or its lifecycle. A ViewModel prevents your data from updating the UI automatically.
What is the difference between model view and ViewModel? ›The major difference between "Model" and "ViewModel" is that we use a ViewModel only in rendering views. We put all our ViewModel classes in a "ViewModels" named folder, we create this folder.
Is the ViewModel backend or frontend? ›Unlike the controller method, the ViewModel method relies heavily on the frontend of the application.
How to pass data to a ViewModel in Swift? ›- By using an instance property (A → B)
- By using segues with Storyboards.
- By using instance properties and functions (A ← B)
- By using the delegation pattern.
- By using a closure or completion handler.
- By using NotificationCenter and the Observer pattern.
LiveData can be used to get value from ViewModel to Fragment . Make the function findbyID return LiveData and observe it in the fragment. fun findbyID(id: Int): LiveData</*your data type*/> { val result = MutableLiveData</*your data type*/>() viewModelScope.
How to pass data from ViewController to view? ›
If you have a value in one view controller and want to pass it to another, there are two approaches: for passing data forward you should communicate using properties, and for passing data backwards you can either use a delegate or a block.
How to pass data between view controllers in Swift medium? ›...
Passing data using segues:
- You need to create a segue between viewcontrollers in storyboard.
- Assign an identifier name to segue.
- use prepare(for:sender:) method to pass the function.
Yes, the activity can pass the intent. extras into the ViewModel, through the AbstractSavedStateViewModelFactory constructor.
How to pass data from ViewModel to view in MVVM Swift? ›Make your main view inject the instance of your view model in the environment, so all other views will read from that. The main view uses . environmentObject(viewModel) to do that. The other views read the view model from the environment by calling @EnvironmentObject .
How to pass optional parameters in Swift? ›Swift is not like languages like JavaScript, where you can call a function without passing the parameters and it will still be called. So to call a function in Swift, you need to assign a value to its parameters. Default values for parameters allow you to assign a value without specifying it when calling the function.
How data is sent from view to controller? ›- An anchor (<a>) where the data is passed as query string name/value parameters. The mvc data binding will bind the parameters to the controllers action parameters by name.
- A form post. The form data is again passed as names / value pairs.
ViewData, ViewBag, and TempData are used to pass data between controller, action, and views. To pass data from the controller to view, either ViewData or ViewBag can be used. To pass data from one controller to another controller, TempData can be used.
How to pass data from one vc to another in Swift using delegate? ›- Let's start by creating a SingleView iOS app. ...
- Add another View Controller to your main storyboard. ...
- We will create the class for the second View Controller by adding a Cocoa Touch class file to our project. ...
- Let's name this file DestinationViewController.
Click on delegate and dataSource. If you see the white dots besides dataSource and delegate, then you have connected the tableview to them. Connect your tableview to your ViewController by opening Assistant.
How to pass array from one ViewController to another in ios Swift? ›- var imageArray: [UIImage] = [] public var emptyView: EmptyHomeView! override func loadView() { ...
- self. imageArray. ...
- var imageArray2 = [UIImage]() // MARK: - View Lifecycle. ...
- let otherVC = HomeViewController()
- print(imageArray2) }