MVVM List with SwiftUI

Lee young-jun
4 min readMay 17, 2022

I will create scheduler with SwiftUI.
However I didn’t know how to bind a model from ListView to ListItemView.
So I studied how to do and it is I will share to you Today.

Model

First, I created a data model for a row in list
and named it ‘AlarmModel’.

List

To preview ListView, I created a module to load AlarmModel list mock.

ListView

Now I created a basic list view like this.

I tried to create the Alarm rows with HStack, Text, Toggle.

But I met Binding error.

I used Constant instead of a bindable variable to Toggle this time.

isOn: .constant(alarm.enabled)

ListViewModel

I noticed something was wrong, when I tried to toggle the switches.
I couldn’t toggle them.

Because isOn is constant and it doesn’t have setter.

How can I solve this?
I tried to use $alarm.enabled instead of alarm.enabled.

However I got this error.

I guess this code tried to find ‘@State alarm’.

So I had to use ForEach instead of List like this.

When I updated preview, I got this problem.

Because the above source is exactly same to this source.

To solve it, we should put HStacks into List.

Ah… I forgot talking about ViewModel.

Now List is working what I expected, however something was wrong.
List did access to the raw model’s property directly.
It is not a valid implementation for MVVM.

To apply MVVM pattern, I created ListViewModel and replaced the list state variable with it.

@StateObject can make State Objects with bindable variables.

@StateObject var viewModel = 

And you can create bindable property with @Published in the Object.

@Published var alarms: [AlarmItemViewModel] =

ListItemViewModel

To use VM to each rows, I created ListItemViewModel.

I created a constructor to create ItemViewModel with model got from ListViewModel and encapsulated the model.

init(alarm: AlarmModel){ 
self.alarm = alarm
self.enabled = alarm.enabled
}

To bind Toggle’s value, I attached ‘Published’ attribute to ‘enabled’ property.
It will make the property bindable.

@Published var enabled: Bool

ListItemView

I created new View to replace HStack in ForEach.

And created previews for various layouts.

With this new View, I replaced HStack in ListView like this.

The app works exactly same to before applying ItemView.

However now my app is totally conforming MVVM.

Advanced

Indices

Instead of 0..<$list.count, you can use $list.indices.
indices will make array, and it will can be subscribable.

Foreach with id

To reduce rendering when collection’s children count has been changed,
we can specify KeyPath for id.
With id, List will know which children were inserted or deleted.

Reload list when View did appear

To reload data when View did appear, use onAppear.
This is like viewDidAppear, and we can use like this.

Binding Element without Index

I wanted more simple code like this.

However I couldn’t use it because the error.

But now I can make simple my code like this.

The trick is $

$alarm in

And Element should be Identifiable.

Thanks answer Swift Dev.

References

--

--