[2023] SwiftUI @Observable macro: Basic MVVM setup
Tags: swift, swiftui, macro, swiftmacro
Published: 2023-07-01 06:59:40

With the new `@Observable` macro, setting up MVVM in SwiftUI has become much simpler. We shall explain the setup by trying to build the following page:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xs22387wk8w0ziydn1e5.png)


## 1. Setup ViewModel
```swift
struct User {
    let name: String
    let profileImgUrl: String
}

struct ChatMessage {
    var message: String
}

struct HomePageChat: Identifiable {
    let id = UUID()
    let sender: User
    var latestMessage: ChatMessage
}

@Observable class HomePageViewModel {

    var chats: [HomePageChat] = []
    
    init() {
        setupDummyData()
    }
    
    private func setupDummyData() {
        let userA = User(name: "Tim Cook", profileImgUrl: "https://www.apple.com/leadership/images/bio/tim-cook_image.png.og.png?1685138662136")
        let messageA = ChatMessage(message: "Hello! Tim Cook here, how are you doing?")
        let chatA = HomePageChat(sender: userA, latestMessage: messageA)
        
        let userB = User(name: "Craig Federighi", profileImgUrl: "https://www.apple.com/leadership/images/bio/craig_federighi_image.png.og.png?1685171686562")
        let messageB = ChatMessage(message: "I am Craig Federighi, who are you?")
        let chatB = HomePageChat(sender: userB, latestMessage: messageB)
        
        
        self.chats = [chatA, chatB]
    }
}
```

Note that all we need to do is to mark our `HomePageViewModel` with `@Observable` macro to make our viewModel observable by SwiftUI views. 

## 2. Setup Views
```swift
struct HomePageView: View {
    
    @Environment(HomePageViewModel.self) private var viewModel
    
    var body: some View {
        NavigationView {
            VStack {
                List(viewModel.chats) { chat in
                    HomePageCellView(chat: chat)
                }
                .listStyle(.sidebar)
            }
            .navigationTitle("Chats")
            .navigationBarTitleDisplayMode(.large)
        }
    }
}

#Preview {
    HomePageView()
        .environment(HomePageViewModel())
}
```
```swift
struct HomePageCellView: View {
    
    let chat: HomePageChat
    
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                AsyncImage(url: URL(string: chat.sender.profileImgUrl)) { image in
                    image
                        .resizable()
                        .scaledToFill()
                } placeholder: {
                    ProgressView()
                }
                .frame(width: 50, height: 50)
                .clipShape(Circle())
                .clipShape(Circle())
            }
            VStack(alignment: .leading) {
                Text(chat.sender.name)
                    .bold()
                    .padding(.bottom, 1)
                Text(chat.latestMessage.message)
                    .foregroundStyle(.gray)
                    .font(.system(size: 12))
                    .fixedSize(horizontal: false, vertical: true)
            }
            .padding(.leading, 6)
            
            Spacer()
            VStack {
                Text("Yesterday")
                    .foregroundStyle(.gray)
                    .font(.system(size: 12))
                Spacer()
                
            }
        }
        .padding(.leading, 0)
        .padding(.vertical, 6)
        .frame(height: 60)
    }
}
```