In this tutorial I will show you about how to create a project by following MVVM design pattern in SwiftUI.

MVVM stands for “Model View ViewModel”.

SwiftUI. MVVM Architecture

Model : All the Models and Data pass through Model

View : View Handles User Interfaces, User Interaction

ViewModel : It works like a glue between Model & View

Now open Xcode and create a new project named “MVVM Example”. Keep Interface as SwiftUi

Create a new project
MVVM Example

You can ignore Git option if you don’t want to publish this project into Github or something else.

You can ignore Git option

Delete ContentView.swift file. Don’t worry, we will create our own custom view.

Default Hello World Project

Create 3 Groups / Folders inside MVVM Example like below (Model, View, ViewModel)

Create Model, View, ViewModel folders

Now, select Model folder and create a new file named ExampleModel.swift under Model folder.

ExampleModel.swift

Paste the code below in ExampleModel.swift file.


import SwiftUI


struct MainModelFetcher: Decodable {
    
    let results : [PopularMovieList]
}

struct PopularMovieList: Identifiable, Decodable {
    
    // We want to have a unique id for our data
    let id = UUID()
    let original_title : String
    let poster_path : String
    
}



Explanation: We will call a movie list API which will return us a JsonObject with movie name, movie poster, release date etc. We have created 2 models to extract those data or information perfectly.

Under View folder, create a new file named ExampleView.swift.

ExampleView.swift

In ExampleView.swift file, paste the code below :


import SwiftUI
import SDWebImageSwiftUI

struct ExampleView: View {
    @ObservedObject var popularMoviewFetcherVM = MovieViewModel()
    
    private var gridItemLayout = [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]
    
    var body: some View {
        
        
        
        ScrollView(.vertical, showsIndicators: false) {
          LazyVGrid(columns: gridItemLayout, spacing: 20) {
            ForEach(popularMoviewFetcherVM.results, id: \.id) { movie in
                
                
                VStack(alignment: .center, spacing: 5){
                    
                    VStack{
                        
                        
                        WebImage(url: URL(string: popularMoviewFetcherVM.imageServerURL+movie.poster_path))
                            // Supports options and context, like `.delayPlaceholder` to show placeholder only when error
                            .onSuccess { image, data, cacheType in
                                // Success
                                // Note: Data exist only when queried from disk cache or network. Use `.queryMemoryData` if you really need data
                            }
                            .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
                            .placeholder(Image("placeholder")) // Placeholder Image
                            // Supports ViewBuilder as well
                            
                            .indicator(.activity) // Activity Indicator
                            //.transition(.fade(duration: 0.5)) // Fade Transition with duration
                            .scaledToFit()

                        
                    }
                   
                    
                    
                    VStack{
                        
                        Text(movie.original_title)
                            .font(.custom("Helvetica Neue", size: 12))
                    }
                    
                }
                
                
       
              }
             
              
            }
          .padding([.top,.bottom], 50)
          .padding([.leading, .trailing],20)
        }.onAppear(perform: {
            popularMoviewFetcherVM.FetchApiData()
        })
        
       

        
    }
}

struct ExampleView_Previews: PreviewProvider {
    static var previews: some View {
        ExampleView()
    }
}

Explanation: We will show the Movie Posters with Movie names in a Gridview. The API call will initialize when app opens in the main tread.

Under ViewModel Folder, create a new file named ExampleViewModel.swift.

ExampleViewModel.swift

In ExampleViewModel.swift file, paste the code below:


import SwiftUI
import Alamofire

class MovieViewModel: ObservableObject {
    
    
    let mainServerURL = "https://api.themoviedb.org/3/movie/popular?api_key=YourApiKey"
    let imageServerURL = "https://image.tmdb.org/t/p/w500/"
    
    @Published var results = [PopularMovieList]()
    
    
    func FetchApiData(){
        
        AF.request(mainServerURL).response { response in
            
            do {
                
                let decodedResponse  = try JSONDecoder().decode(MainModelFetcher.self, from: response.data!)
                
                print(decodedResponse)
                
                for x in decodedResponse.results {
                    
                
                    print("Value \(x.original_title) Not Found")
                }
                
                self.results = decodedResponse.results
                
                //print(decodedResponse)
                
            } catch DecodingError.keyNotFound(let key, let context) {
                Swift.print("could not find key \(key) in JSON: \(context.debugDescription)")
            } catch DecodingError.valueNotFound(let type, let context) {
                Swift.print("could not find type \(type) in JSON: \(context.debugDescription)")
            } catch DecodingError.typeMismatch(let type, let context) {
                Swift.print("type mismatch for type \(type) in JSON: \(context.debugDescription)")
            } catch DecodingError.dataCorrupted(let context) {
                Swift.print("data found to be corrupted in JSON: \(context.debugDescription)")
            } catch let error as NSError {
                NSLog("Error in read(from:ofType:) domain= \(error.domain), description= \(error.localizedDescription)")
            }
            
           
        }
    }
}

Explanation: With ViewModel, we can use our Model to fetch the data and View to show those data. We will handle all of our network calls and error handling here.

Now select MVVM Example (Your main project), Under Frameworks, Libraries, and Embedded Content, select the + button. We will add 2 plugins / dependencies for our projects.

Enter the link in search bar : https://github.com/Alamofire/Alamofire

Click Next, Alamofire package will be added into your source code. We will use this library for API call.

Add SDWebImageSwiftUI in the same way, we will need this for loading our images efficiently.

Link: : https://github.com/SDWebImage/SDWebImageSwiftUI

Download the file below and add it in your asset folder (Assets.xcassets). Its just a simple image file which will show until your movie poster loaded successfully from internet.

Lastly, select MVVM_ExampleApp.swift and replace ContentView() with ExampleView()

Now you have to use your own API key to call this API from The Movie Database. Watch this video to obtain your own api_key and replace it. Just create an account, the api is free for testing.

Now clean and build your project.

If everything is ok then you may see the result like below.

Source code : Github


Author :

Mahbubur Rahman Turzo

Full Stack Developer

Hi, I am Turzo. As a computer professional I worked for more than 6 years in Backend server development, iOS development, Android development, Game development, Robotics. I will guide you to acquire skills easily, efficiently and perfectly. I hope you will use these skills and knowledge in your real career as well as spread among others!

Leave a Reply

Your email address will not be published. Required fields are marked *