Introducing MVVM in SwiftUI: How to Easily Implement MVVM in SwiftUI🧐

Nindya Alita Rosalia
14 min readAug 27, 2023

--

Photo by Christina @ wocintechchat.com on Unsplash

👋 Introduction/Overview

Model-View-ViewModel (MVVM) is an design pattern that has gained significant traction in modern app development, particularly in the context of frameworks like SwiftUI. MVVM promotes a clear separation of concerns, making codebase maintenance and readability more manageable. In SwiftUI, MVVM offers a structured approach to building user interfaces while keeping the underlying logic organized and testable.

Let’s explore what MVVM is all about. We’ll cover the basic idea of the Model-View-ViewModel, understand how MVVM works in SwiftUI, and, of course, there will be practical coding examples on how to implement MVVM.

Starter Project : https://www.dropbox.com/scl/fo/kcoqq5kxryqtxoao7pm26/h?rlkey=odd3o0udpo2to312xxg7ux0m8&dl=0
Completed Project: https://www.dropbox.com/scl/fo/nieep2cqglwoyu6trjeia/h?rlkey=abokhxcwy3drai1e2mfijvo3h&dl=0

🤔 What is MVVM ?

Model–View–ViewModel (MVVM) is a software design patern that facilitates the separation of the development of the GUI (the view) from the development of the businnes logic or back-end logic (the model) so that the view is not dependent on any specific model platform. The ViewModel of MVVM is responsible for exposing the data objects from the model in such a way that objects are asily managed and presented. In this respect, the ViewModel is more model than view and handles most if not all of the view’s display logic. The ViewModel may implement a mediator pattern, organizing access to the back-end logic around the set of use case supported by the view.

Model–View–ViewModel (MVVM)

🤓 🧠
make your view as dumb as possible

🤩 What are the benefits of using MVVM?

Using the Model-View-ViewModel (MVVM) design pattern offers several benefits for designing and developing applications, particularly in user interface (UI)-intensive contexts like mobile and desktop applications. Here are some of the key benefits of using MVVM:

Benefits Of Using MVVM

While MVVM offers numerous benefits, it’s important to note that the choice of architectural pattern depends on the specific needs of the project and the development team’s familiarity with the pattern. MVVM might not be the best fit for every project, but in scenarios where a clean separation of UI and logic is desired, MVVM can be a powerful approach to building robust and maintainable applications.

🔑 Basic Idea of Model-View-ViewModel

Basic Idea of Model-View-ViewModel

The view serves as the space where we define the visual layout of the user interface. To accomplish this, it requires access to data, which, in the context of MVVM, it acquires from the view model. However, the view doesn’t directly possess ownership of the data. Its role also entails responding to alterations in the data to consistently display updated information. We aim for an automated refresh of views to consistently reflect the accurate data, avoiding any disparities between the underlying data and the presented view. This pursuit revolves around maintaining a single “truth.”

During user interactions, various views might be presented, or data could be altered. Instances of “user intents” necessitate modifications to the underlying data. It’s important to note that the view itself doesn’t directly manipulate the data. This task is within the purview of the view model. Requests for data changes from the view are channeled through the view model.

The view model retains the data and is responsible for managing changes to the data. It functions as an intermediary between the model and the view.

Importantly, the view model doesn’t possess knowledge of how or where this data is utilized within our views. Its awareness is limited to the views (as depicted by the expressions in the illustration). This concept is termed “blind structured communication” and serves to detach logic from UI code, contributing to a more modular architecture. The view itself interfaces with the view model since it requires data retrieval and subscription to change notifications, enabling it to refresh.

However, there’s a necessity to inform the view model when a change occurs within the data model. This is imperative because the view model must internally communicate these changes to the views, triggering the need for view re-rendering.

Moving to the “model layer,” this encompasses Swift types that embody your app’s data and domain-related business logic. Model types exclusively encompass data and the corresponding manipulation code. They remain oblivious to data storage, networking, and data presentation mechanisms. Put simply, they remain detached from the other layers in the MVVM pattern. On the other hand, the “view layer” encompasses the visual elements seen on the screen. In iOS, this layer encompasses SwiftUI views responsible for displaying information and facilitating user interaction. Lastly, the “view model” serves as the link between a view and the app’s model. It houses the current state of a view, connects with other components of the app’s architecture (like data storage or network operations), and manages user interactions.

🧐 How MVVM works in SwifUI?

The concept of “blind communication” for signaling changes is a fundamental aspect of how MVVM manages the flow of data. In SwiftUI, this mechanism is built upon Combine data streams, which are executed through the utilization of property wrappers. Any element that is subject to modification during runtime necessitates the inclusion of a property wrapper to guarantee that the views consistently exhibit the accurate and up-to-date values.

MVVM works in SwifUI

In the Swift programming language, models are frequently defined as structs due to the fact that structs are considered value types. This characteristic proves beneficial to the view model in easily determining when changes occur.

But the question remains, how does the view model convey the occurrence of a change? How are these updates propagated? In the SwiftUI framework, each view model should adhere to the ObservableObject protocol. Objects conforming to this protocol are required to expose any property that impacts the user interface by utilizing the @Published property wrapper.

In SwiftUI’s context, an ObservableObject can be likened to a special element that imparts the ability for your data to be observed by views. Imagine it as a sort of enchanting signal that communicates to your views, saying, "Hey, there's been a change here!" So, when you infuse this magical quality into a data type by embracing the ObservableObject "enchantment," any modifications made to its properties automatically send ripples of updates throughout your views. This mystical concept serves as the foundation of reactive programming within SwiftUI. It ensures that your views remain synchronized with the latest alterations in the data you're observing. Thanks to ObservableObject, you are relieved from the burden of manually refreshing your views each time you modify your data. It's akin to having a helpful assistant that handles synchronization on your behalf. By embracing ObservableObject, you effortlessly manage and stay attuned to changes in your data. Your code becomes more responsive to these changes, and your views gracefully refresh themselves whenever the observed data decides to introduce modifications.

@Published is a property attribute used in SwiftUI within a class that adopts the ObservableObject protocol. This attribute informs SwiftUI that the property should be observed, and when its value changes, the associated view will be automatically updated. By using @Published, you can make views more responsive to data changes and avoid repetitive manual updates.

Views connect to observed objects through the @StateObject, @ObservedObject, and @EnvironmentObject property wrappers. @StateObject, @ObservedObject, and @EnvironementObject is a property attribute in SwiftUI used to initialize and store an object that implements the ObservableObject protocol. This attribute automatically manages the lifecycle of the ObservableObject and ensures that the object remains alive as long as it's needed by the view using it.

🙄 But, what are the differences between @StateObject, @ObservedObject, and @EnvironmentObject?

So, let discuss it now!

  • @StateObject is used to initialize and retain objects that implement the ObservableObject protocol specifically for a single view.
  • @ObservedObject is used to observe changes in the given ObservableObject within a view and share the object across multiple views.
  • @EnvironmentObjectis used to automatically inject an ObservableObject into many views within a hierarchy and provide a way to share data globally. The object is created at a higher level and automatically shared with child views.

✍️ Practical code example: How to implement MVVM?

  1. Place the model in an extra group/file
files and folders organization

Once you’re ready, we will proceed to create the model. Do you still remember what a model is? A model represents your app’s data and its domain business logic. From the provided files, you can find the model in the PlayView file.

struct Number: Hashable {
var id: String = UUID().uuidString
var operand1: Int
var operand2: Int
var result: Int
var isCorrect: Bool = false
}

Well, you move that model into a new Swift file named NumberModel or you can come up with your own name, but don’t forget to make sure the name can describe the content of the Swift file. And please remember to place the Swift file containing the model in the Modelsfolder.

2. Create view model
The next step is to create a view model for the project. Before we proceed to create the view model, do you still remember what a view model is? A view model holds the presentation logic and state that is specific to the UI. The ViewModel transforms the data from the Model into a format that the View can understand and display.
The best practice is that each view should have its own view model. Therefore, since we have 3 views in our starter project, each of these views will have 1 view model. Let’s get started.

WelcomeView.SwiftUI

import SwiftUI

struct WelcomeScreen: View {

@State var isAnimating: Bool = false //move to view model

var body: some View {
NavigationView{
ZStack {
Image("light_astro")
.resizable()
.opacity(isAnimating ? 0.5 : 1)
.animation(.easeInOut(duration: 1).repeatForever(), value: isAnimating)

Image("astro")
.resizable()

Image("light_arith")
.resizable()
.opacity(isAnimating ? 0.5 : 1)
.animation(.easeInOut(duration: 1).repeatForever(), value: isAnimating)

Image("arithmetic")
.resizable()

Image("light_button")
.resizable()
.opacity(isAnimating ? 0.5 : 1)
.animation(.easeInOut(duration: 1).repeatForever(), value: isAnimating)
.onAppear{
withAnimation{isAnimating = true}
}


NavigationLink(destination: PlayView()) {
Image("play_button3")
.resizable()
.frame(width: 100, height: 100)
.rotationEffect(Angle.degrees(isAnimating ? 10 : -10))
.onAppear{
isAnimating = true
}
.animation(.easeOut(duration: 1).repeatForever(), value: isAnimating)
}
.offset(y: 75)
}
.background(Image("BgWelcomeScreen")
.resizable()
.scaledToFill()
.edgesIgnoringSafeArea(.all ))
}
.navigationBarHidden(true)
.navigationViewStyle(StackNavigationViewStyle())
}

}


struct WelcomeScreen_Previews: PreviewProvider {
static var previews: some View {
WelcomeScreen()
}
}

Next, move the presentation logic and state that exist in that file. In the WelcomeView file, there’s only state that is specific to the UI and no presentation logic. So, create a new Swift file named WelcomeViewModelwith a class that conforms to ObservableObject, and remember to change @Stateto @Published.

import Foundation

class WelcomeViewModel: ObservableObject{

@Published var isAnimating: Bool = false

}

PlayView.SwiftUI

We will do the same thing to the PlayView file.

import SwiftUI

struct PlayView: View {

//move from here
@State var question: [[Number]] = []
@State var isShowFalseAlert: Bool = false
@State var isShowCorrectAlert: Bool = false
@State var sumCorrectAns: [String] = []
@State var isGameFinished: Bool = false
@State var confirmReplayGame: Bool = false
@State var imageName: String = "helmet3"
@State var isOpenGuidance: Bool = true

init(){
imageName = randomImageName()
}

func randomImageName() -> String {
let imageNames = ["helmet3", "Astronout","Glove", "Shoes", "Suit"]
let imageName = imageNames.randomElement() ?? "helmet3"
return imageName
}

func generateRandomNum(){
var randomNumber : [[Number]] = []

for _ in 0..<4 {
var arrNumTemp: [Number] = []
for _ in 0..<4{
let tempNum1 = Int.random(in: 1..<10)
let tempNum2 = Int.random(in: 1..<10)
let resTemp = tempNum1 + tempNum2
let tempData = Number(operand1: tempNum1, operand2: tempNum2, result: resTemp)
arrNumTemp.append(tempData)
}
randomNumber.append(arrNumTemp)
arrNumTemp.removeAll()
}
question = randomNumber
print(question)
}

func hideAlert (timer: Int) async{
try? await Task.sleep(for: .seconds(timer))
DispatchQueue.main.async {
self.isShowFalseAlert=false
self.isShowCorrectAlert=false
}
}
//to this

var body: some View {
NavigationView{
ZStack{
NavigationLink(destination: WinningScreen(), isActive: $isGameFinished){}

HStack{

//MARK: QUESTION
VStack{

Spacer()

VStack{

Text("Penjumlahan")
.font(.system(size: 24))
.fontWeight(.heavy)
.foregroundColor(Color(.white))

ZStack{
Image(imageName)
.resizable()
.frame(width: 350, height: 190)

QuestionView(question: $question, isShowAlert: $isShowFalseAlert, sumCorrectAns: $sumCorrectAns, isGameFinished: $isGameFinished, isShowCorrectAlert: $isShowCorrectAlert)
}
.frame(maxWidth: 347, maxHeight: 202)
}
.padding(.horizontal, 10)
.frame(maxWidth: 387, maxHeight: 300)
.background(Color("PurpleDark"))
.cornerRadius(10)
}// : VSTACK

Spacer()

VStack{

//MARK: BUTTON
HStack(alignment: .bottom){

Spacer()

//guide button
CustomButtonView(imageButtonName: "helpButton", action: {
isOpenGuidance.toggle()
})

//replay button
CustomButtonView(imageButtonName: "restartButton", action: {
confirmReplayGame.toggle()
})
} //: HSTACK BUTTON

Spacer()

//MARK: ANSWER
AnswerView(question: question)
}
}//: HSTACK
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Image("BgMainScreen")
.resizable()
.edgesIgnoringSafeArea(.all)
)
.onAppear(){
generateRandomNum()
}

//MARK: CORRECT ALERT
if isShowCorrectAlert{
AlertView(text: "Benar!", backgroundColor: .green, foregroundColor: .white)
.task {
await hideAlert(timer: 1)
}
}

//MARK: FALSE ALERT
if isShowFalseAlert{
AlertView(text: "Yah jawaban kamu belum tepat. Ayo coba lagi!", backgroundColor: Color("BlueLight"), foregroundColor: .white)
.task {
await hideAlert(timer: 3)
}
}

//MARK: CONFIRM REPLAY GAME
if(confirmReplayGame){
ZStack{

ConfirmationButtonView()

VStack{

HStack(){

CustomConfirmationPropertiesView(imageButtonName: "correct_button", action: {
generateRandomNum()
sumCorrectAns.removeAll()
confirmReplayGame.toggle()
imageName = randomImageName()
})

CustomConfirmationPropertiesView(imageButtonName: "wrong_button", action: {
confirmReplayGame.toggle()
})

}
.position(x:380, y:260)
}.background(Image("ImgAlert")
.resizable()
.scaledToFill()
.frame(width: 400).ignoresSafeArea(.all)
)
}

}

//MARK: CONFIRM OPEN GUIDE
if(isOpenGuidance){

ZStack{

ConfirmationButtonView()

VStack{

CustomConfirmationPropertiesView(imageButtonName: "tombolMengerti", action: {
isOpenGuidance.toggle()
})
.position(x:380, y:290)

}.background(Image("guidanceAlert")
.resizable()
.scaledToFill()
.frame(width: 400).ignoresSafeArea(.all)
)
}

}
}//: ZSTACK
}
.navigationBarHidden(true)
.navigationViewStyle(StackNavigationViewStyle())
}
}



struct PlayView_Previews: PreviewProvider {
static var previews: some View {
PlayView()
}
}

Then, move the presentation logic and state from that file. After that, create a new Swift file named PlayViewModel with a class that conforms to ObservableObject, and don’t forget to change @State to @Published .

import Foundation

class PlayViewModel: ObservableObject{

@Published var question: [[Number]] = []
@Published var isShowFalseAlert: Bool = false
@Published var isShowCorrectAlert: Bool = false
@Published var sumCorrectAns: [String] = []
@Published var isGameFinished: Bool = false
@Published var confirmReplayGame: Bool = false
@Published var imageName: String = "helmet3"
@Published var isOpenGuidance: Bool = true

init(){
imageName = randomImageName()
}

func randomImageName() -> String {
let imageNames = ["helmet3", "Astronout","Glove", "Shoes", "Suit"]
let imageName = imageNames.randomElement() ?? "helmet3"
return imageName
}

func generateRandomNum(){
var randomNumber : [[Number]] = []

for _ in 0..<4 {
var arrNumTemp: [Number] = []
for _ in 0..<4{
let tempNum1 = Int.random(in: 1..<10)
let tempNum2 = Int.random(in: 1..<10)
let resTemp = tempNum1 + tempNum2
let tempData = Number(operand1: tempNum1, operand2: tempNum2, result: resTemp)
arrNumTemp.append(tempData)
}
randomNumber.append(arrNumTemp)
arrNumTemp.removeAll()
}
question = randomNumber
print(question)
}

func hideAlert (timer: Int) async{
try? await Task.sleep(for: .seconds(timer))
DispatchQueue.main.async {
self.isShowFalseAlert=false
self.isShowCorrectAlert=false
}
}
}

WinningView.SwiftUI
We will do the same thing to the WinningView file.

import SwiftUI

struct WinningScreen: View {

@State var replayGame: Bool = false //move to WinningViewModel

var body: some View {
VStack{
Image("helmet3")
.resizable()
.scaledToFit()
.frame(width: 300)
.padding(.top, 40)

NavigationLink(destination: WelcomeScreen(), isActive: $replayGame){
Button(){
print("correct")
replayGame.toggle()
} label: {
Image("replayGameButton")
.resizable()
.scaledToFit()
.frame(width: 150)
}
.padding(.vertical, 10)
}
}
.position(x:380, y: 210)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Image("BgWinningScreen")
.resizable()
.edgesIgnoringSafeArea(.all)
)
.navigationBarHidden(true)
.navigationViewStyle(StackNavigationViewStyle())
}
}

struct WinningScreen_Previews: PreviewProvider {
static var previews: some View {
WinningScreen()
}
}

Next, move the presentation logic and state that exist in that file. In the WinningViewfile, there’s only state that is specific to the UI and no presentation logic. So, create a new Swift file named OWinningViewModelwith a class that conforms to ObservableObject, and remember to change @State to @Published .

import Foundation

class WinningViewModel: ObservableObject{

@Published var replayGame: Bool = false

}

3. Use the view model in the view
Now that all the views have their respective view models, let’s discuss how to use these view models within the views. As previously discussed in the “How MVVM Works in SwiftUI” section, views connect to observed objects through the @StateObject, @ObervedObject, and @EnvironementObject property wrappers.

Therefore, in this project, we will use @StateObject use the view model within a view, since the created view model is intended for a single view only. Let’s directly put this into practice.

WelcomeView.SwiftUI

import SwiftUI

struct WelcomeScreen: View {

@StateObject private var welcomeViewModel = WelcomeViewModel()

var body: some View {
NavigationView{
ZStack {
Image("light_astro")
.resizable()
.opacity(welcomeViewModel.isAnimating ? 0.5 : 1)
.animation(.easeInOut(duration: 1).repeatForever(), value: welcomeViewModel.isAnimating)

Image("astro")
.resizable()

Image("light_arith")
.resizable()
.opacity(welcomeViewModel.isAnimating ? 0.5 : 1)
.animation(.easeInOut(duration: 1).repeatForever(), value: welcomeViewModel.isAnimating)

Image("arithmetic")
.resizable()

Image("light_button")
.resizable()
.opacity(welcomeViewModel.isAnimating ? 0.5 : 1)
.animation(.easeInOut(duration: 1).repeatForever(), value: welcomeViewModel.isAnimating)
.onAppear{
withAnimation{welcomeViewModel.isAnimating = true}
}


NavigationLink(destination: PlayView()) {
Image("play_button3")
.resizable()
.frame(width: 100, height: 100)
.rotationEffect(Angle.degrees(welcomeViewModel.isAnimating ? 10 : -10))
.onAppear{
welcomeViewModel.isAnimating = true
}
.animation(.easeOut(duration: 1).repeatForever(), value: welcomeViewModel.isAnimating)
}
.offset(y: 75)
}
.background(Image("BgWelcomeScreen")
.resizable()
.scaledToFill()
.edgesIgnoringSafeArea(.all ))
}
.navigationBarHidden(true)
.navigationViewStyle(StackNavigationViewStyle())
}

}


struct WelcomeScreen_Previews: PreviewProvider {
static var previews: some View {
WelcomeScreen()
}
}

Creates a property welcomeviewModel using the @StateObjectproperty wrapper. This property holds an instance of the WelcomeviewModel class, which is likely used to manage some data and business logic for the SwiftUI view. The @StateObject ensures that the same instance of the view model is used across view updates, maintaining its state and lifecycle properly.

PlayView.SwiftUI

import SwiftUI

struct PlayView: View {

@StateObject private var playViewModel = PlayViewModel()

var body: some View {
NavigationView{
ZStack{
NavigationLink(destination: WinningScreen(), isActive: $playViewModel.isGameFinished){}

HStack{

//MARK: QUESTION
VStack{

Spacer()

VStack{

Text("Penjumlahan")
.font(.system(size: 24))
.fontWeight(.heavy)
.foregroundColor(Color(.white))

ZStack{
Image(playViewModel.imageName)
.resizable()
.frame(width: 350, height: 190)

QuestionView(question: $playViewModel.question, isShowAlert: $playViewModel.isShowFalseAlert, sumCorrectAns: $playViewModel.sumCorrectAns, isGameFinished: $playViewModel.isGameFinished, isShowCorrectAlert: $playViewModel.isShowCorrectAlert)
}
.frame(maxWidth: 347, maxHeight: 202)
}
.padding(.horizontal, 10)
.frame(maxWidth: 387, maxHeight: 300)
.background(Color("PurpleDark"))
.cornerRadius(10)
}// : VSTACK

Spacer()

VStack{

//MARK: BUTTON
HStack(alignment: .bottom){

Spacer()

//guide button
CustomButtonView(imageButtonName: "helpButton", action: {
playViewModel.isOpenGuidance.toggle()
})

//replay button
CustomButtonView(imageButtonName: "restartButton", action: {
playViewModel.confirmReplayGame.toggle()
})
} //: HSTACK BUTTON

Spacer()

//MARK: ANSWER
AnswerView(question: playViewModel.question)
}
}//: HSTACK
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Image("BgMainScreen")
.resizable()
.edgesIgnoringSafeArea(.all)
)
.onAppear(){
playViewModel.generateRandomNum()
}

//MARK: CORRECT ALERT
if playViewModel.isShowCorrectAlert{
AlertView(text: "Benar!", backgroundColor: .green, foregroundColor: .white)
.task {
await playViewModel.hideAlert(timer: 1)
}
}

//MARK: FALSE ALERT
if playViewModel.isShowFalseAlert{
AlertView(text: "Yah jawaban kamu belum tepat. Ayo coba lagi!", backgroundColor: Color("BlueLight"), foregroundColor: .white)
.task {
await playViewModel.hideAlert(timer: 3)
}
}

//MARK: CONFIRM REPLAY GAME
if(playViewModel.confirmReplayGame){
ZStack{

ConfirmationButtonView()

VStack{

HStack(){

CustomConfirmationPropertiesView(imageButtonName: "correct_button", action: {
playViewModel.generateRandomNum()
playViewModel.sumCorrectAns.removeAll()
playViewModel.confirmReplayGame.toggle()
playViewModel.imageName = playViewModel.randomImageName()
})

CustomConfirmationPropertiesView(imageButtonName: "wrong_button", action: {
playViewModel.confirmReplayGame.toggle()
})

}
.position(x:380, y:260)
}.background(Image("ImgAlert")
.resizable()
.scaledToFill()
.frame(width: 400).ignoresSafeArea(.all)
)
}

}

//MARK: CONFIRM OPEN GUIDE
if(playViewModel.isOpenGuidance){

ZStack{

ConfirmationButtonView()

VStack{

CustomConfirmationPropertiesView(imageButtonName: "tombolMengerti", action: {
playViewModel.isOpenGuidance.toggle()
})
.position(x:380, y:290)

}.background(Image("guidanceAlert")
.resizable()
.scaledToFill()
.frame(width: 400).ignoresSafeArea(.all)
)
}

}
}//: ZSTACK
}
.navigationBarHidden(true)
.navigationViewStyle(StackNavigationViewStyle())
}
}



struct PlayView_Previews: PreviewProvider {
static var previews: some View {
PlayView()
}
}

Creates a property playViewModelusing the @StateObject property wrapper. This property holds an instance of the PlayViewModel class, which is likely used to manage some data and business logic for the SwiftUI view. The @StateObject ensures that the same instance of the view model is used across view updates, maintaining its state and lifecycle properly.

WinningView.SwiftUI

import SwiftUI

struct WinningScreen: View {

@StateObject private var winningViewModel = WinningViewModel()

var body: some View {
VStack{
Image("helmet3")
.resizable()
.scaledToFit()
.frame(width: 300)
.padding(.top, 40)

NavigationLink(destination: WelcomeScreen(), isActive: $winningViewModel.replayGame){
Button(){
print("correct")
winningViewModel.replayGame.toggle()
} label: {
Image("replayGameButton")
.resizable()
.scaledToFit()
.frame(width: 150)
}
.padding(.vertical, 10)
}
}
.position(x:380, y: 210)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Image("BgWinningScreen")
.resizable()
.edgesIgnoringSafeArea(.all)
)
.navigationBarHidden(true)
.navigationViewStyle(StackNavigationViewStyle())
}
}

struct WinningScreen_Previews: PreviewProvider {
static var previews: some View {
WinningScreen()
}
}

Creates a property winningViewModel using the @StateObject property wrapper. This property holds an instance of the WinningViewModel class, which is likely used to manage some data and business logic for the SwiftUI view. The @StateObjectensures that the same instance of the view model is used across view updates, maintaining its state and lifecycle properly.

😎 Conclusion

Implementing MVVM (Model-View-ViewModel) in SwiftUI brings about several advantages for building well-structured and maintainable applications. MVVM emphasizes the separation of concerns, dividing the application into distinct components:

  • Model: Represents the data and business logic of the app.
  • View: Focuses on UI presentation and rendering.
  • ViewModel: Handles presentation logic and serves as an intermediary between the Model and View.

In SwiftUI, MVVM is implemented by connecting Views to ViewModel instances using the @StateObject, @ObervedObject, and @EnvironementObject property wrappers.This maintains consistent state across view updates. By applaying this design pattern developers can create apps with cleaner, more maintainable code, enabling them to craft exceptional user interfaces while maintaining a clear separation between data, logic, and presentation.

🤸 Call-to-action

Having explored the remarkable capabilities of applying MVVM within SwiftUI, are you interested in delving even deeper into the world of SwiftUI? Expand your understanding by subscribing to our updates. Discover advanced techniques, expert insights, and hands-on tutorials designed to elevate your skills in app development. Feel free to share your questions or thoughts in the comment section below. We’re grateful to have you with us on this exciting journey!

--

--

Nindya Alita Rosalia

Informatic Engineer Student 👩‍💻|| Enjoy learning new things and sharing with everyone🙌