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)