DEV Community

Swee Sen
Swee Sen

Posted on

[2023] SwiftUI @Observable macro: Basic MVVM setup

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

1. Setup ViewModel

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] } } 
Enter fullscreen mode Exit fullscreen mode

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

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()) } 
Enter fullscreen mode Exit fullscreen mode
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) } } 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)