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

Heroku

This site is built on Heroku

Join the ranks of developers at Salesforce, Airbase, DEV, and more who deploy their mission critical applications on Heroku. Sign up today and launch your first app!

Get Started

Top comments (0)

Billboard image

πŸ“Š A side-by-side product comparison between Sentry and Crashlytics

A free guide pointing out the differences between Sentry and Crashlytics, that’s it. See which is best for your mobile crash reporting needs.

See Comparison