5

I am trying to implement a design pattern like MVC in order to achieve low coupling between different parts of the code. There are few materials online that I personally didn't find helpful in relation to IOS or swift UI development and MVC pattern.

What I am trying to understand is how should the controller class control or render the UI in Swift UI ?

Following the MVC pattern for example - the View shouldn't know about how the model looks like, so sending an object back from the Data Base to the view in order to visually present it wouldn't be a good Idea..

Say we have the following View and Controller, how should I go about the interaction between the controller and view when sending the data back from the DB in order to visually present it in the view ?

View:

import SwiftUI
import Foundation

struct SwiftUIView: View {

    
    var assignmentController = AssignmentController()
    

    @State var assignmentName : String = ""
    @State var notes : String = ""
  

  
    var body: some View {
        
        NavigationView {
            VStack {
                Form {
                   
                        
                        TextField("Assignment Name", text: $assignmentName)

    
                        TextField("Notes", text: $notes)
                
     
            }
                
                Button(action: {
                                                               
                    self.assignmentController.retrieveFirstAssignment()
                                       }) {
                                                                                                                                                Text("Get The First Assignment !")
                                                                                    }

            }
            .navigationBarTitle("First Assignment")
        }
        
    
}
}

Controller

var assignmentModel = AssignmentModel()

func retrieveFirstAssignment()
{
    var firstAssignment : Assignment

    firstAssignment=assignmentModel.retrieveFirstAssignment()
    
}

For now, it does nothing with the object it found.

Model

An object in the model composed of two String fields : "assignmentName" and "notes".

*We assume the assignment Model have a working function that retrieves one task from the DB in order to present it in the view.

5
  • Thanks. This is exactly my question how should you go about this interaction with the view since the View shouldn't know about how the model looks like. This is why I left it lacking for now. Commented Jun 28, 2020 at 12:55
  • 2
    Of course it’s possible to use pretty much any design pattern you want but have you considered MVVM? It seems a much better fit for the SwiftUI of doing things than MVC and Apple adopt it themselves in their tutorial series. Commented Jun 28, 2020 at 13:02
  • I am open for MVVM too, but in that case my question will still be how to make the appropriate binding connection between the View Model and the View. Commented Jun 28, 2020 at 13:05
  • 2
    ObservableObject/ObservedObject. Commented Jun 28, 2020 at 13:35
  • If someone can provide a basic code example using the demo classes I used (replacing Controller with View Model) it will be appreciated Commented Jun 28, 2020 at 13:45

2 Answers 2

8
struct SwiftUIView: View {

    @State m = AssignmentModel()
   
    var body: some View {
        // use m   
    }
    func loadAssignmentFromDB() {
        m.retrieveFirstAssignment()
    }
}

This is what I called "enhanced MVC with built-in MVVM".

It satisfies your pursuit of MVC and possibly MVVM, at the same time, with much less effort.

Now I'll argue why:

  1. function, or more specifically, mutation is Control. you don't need an object called "Controller" to have C in MVC. Otherwise we might as well stick to UIKit for 10 more years.

this makes sense when your model is value type. nothing can mutate it without your specific say-so, e.g.; @State annotation. since the only way to mutate model is via these designated endpoints, your Control takes effect only in the function that mutates these endpoints.

  1. Quoting from another reply:

It is true that SwiftUI is a much closer match with MVVM than with MVC. However, almost all example code in the Apple documentation is so simple the ViewModel (and/or Controller in MVC) is left out completely. Once you start creating bigger projects the need for something to bridge your Views and Models arises. However, IMO, the SwiftUI documentation does not (yet) fully address this in a satisfying way.

SwiftUI, if anything, enhances MVC. What is the purpose of having ViewModel?

a.) to have model view binding, which is present in my code snippet above.

b.) to manage states associated with object. if @State does not give you the impression that it is for you to manage state, then i don't know what will. it's funny how many MVVM devs are just blind to this. Just as you don't need View Controller for Control, you don't need ViewModel for VM. Design pattern is a @State of mind. Not about specific naming and rigid structure.

c.) say i'm open to MVVM without being based. which code snippet do you think have more chance in scaling to larger projects? my compact one or that suggested in another reply? hint: think how many extra files, view models, observableobjects, glue codes, pass-around-vm-as-parameters, init function to accept vm as parameters, you are going to have. and these are just for you to write some of the code in another object. it says nothing about reducing or simplifying the task at hand. hell it does even tell you how to refactor your control codes, so you are most likely to just repeat whatever you did wrong in MVC all over again. did i mention ViewModel is a shared, reference type object with implicit state managements? so what's the point of having a value type model when you are just going to override it with a reference type model?

it's funny how MVVM devs say SwiftUI in its base form is not scalable to larger projects. keeping things simple is the only way to scale.

This is what I observed as the roadmap of dev progression in 2020. Day1: beginner Day2: google some, drop MVC Day3: google some more, SwiftUI not scalable Day4: OK, I need MVVM+RxSwift+Coordinator+Router+DependencyInjection to avoid SDK short-comings.

My suggestion, due to this seems like a common beginner question, is to learn to walk before you run.

I've personally seen RxSwift developers move controller code to view so that controller appears "clean", and need 3 third-party libraries (one is a custom fork) to send a http GET.

Design pattern means nothing if you can't get simple things simple.

Sign up to request clarification or add additional context in comments.

Comments

1

To me this is a very good question. It is true that SwiftUI is a much closer match with MVVM than with MVC. However, almost all example code in the Apple documentation is so simple the ViewModel (and/or Controller in MVC) is left out completely. Once you start creating bigger projects the need for something to bridge your Views and Models arises. However, IMO, the SwiftUI documentation does not (yet) fully address this in a satisfying way. I would love other developers to correct me or expand on this (I'm still learning), but here's what I found out so far:

  • For managing updating your views in non-example project you almost always want to use ObservableObject/ObservedObject.
  • Views should only observe an object if they need to be updated if it changes. It is better if you can delegate the updates to a child view.
  • It may be tempting to create a large ObservableObject and add @Published for all of its properties. However, this means that a view that observes that object gets updated (sometimes visibly) even if a property changes on which the view does not even depend.
  • Binding is the most natural interface for Views that represent controls that can modify data. Beware that a Binding does NOT trigger updating views. Updating the view should be managed either @State or @ObservedObject (this can done by the parent view of the control).
  • Constants are the natural interface for Views that only display data (and not modify it).

Here is how I would apply this to your example:

import SwiftUI

//
// Helper class for observing value types
//
class ObservableValue<Value: Hashable>: ObservableObject {
    @Published var value: Value
    
    init(initialValue: Value) {
        value = initialValue
    }
}

//
// Model
//
struct Assignment {
    let name : String
    let notes: String
}

//
// ViewModel?
//
// Usually a view model transforms data so it is usable by the view. Strings are already
// usable in our components. The only change here is to wrap the strings in an
// ObservableValue so views can listen for changes to the individual properties.
//
// Note: In Swift you often see transformations of the data implemented as extensions to
// the model rather than in a separate ViewModel.

class AssignmentModelView {
    var name : ObservableValue<String>
    var notes: ObservableValue<String>
    
    init(assignment: Assignment) {
        name  = ObservableValue<String>(initialValue: assignment.name)
        notes = ObservableValue<String>(initialValue: assignment.notes)
    }
    
    var assignment: Assignment {
        Assignment(name: name.value, notes: notes.value)
    }
}

//
// Controller
//
// Publish the first assignment so Views depending on it can update whenever we change
// the first assignment (**not** update its properties)
class AssignmentController: ObservableObject {
    @Published var firstAssignment: AssignmentModelView?

    func retrieveFirstAssignment() {
        let assignment = Assignment(name: "My First Assignment", notes: "Everyone has to start somewhere...")
        
        firstAssignment = AssignmentModelView(assignment: assignment)
    }
}

struct ContentView: View {

    // In a real app you should use dependency injection here
    // (i.e. provide the assignmentController as a parameter)
    @ObservedObject var assignmentController = AssignmentController()
  
    var body: some View {
        
        NavigationView {
            VStack {
            
                // I prefer to use `map` instead of conditional views, since it
                // eliminates the need for forced unwrapping
                self.assignmentController.firstAssignment.map { assignmentModelView in
                    Form {
                        ObservingTextField(title: "Assignment Name", value:  assignmentModelView.name)
                        ObservingTextField(title: "Notes", value: assignmentModelView.notes)
                    }
                }
               
                Button(action: { self.retrieveFirstAssignment() }) {
                    Text("Get The First Assignment !")
                }
            }
            .navigationBarTitle("First Assignment")
        }
    }
    
    func retrieveFirstAssignment() {
        assignmentController.retrieveFirstAssignment()
    }
}

//
// Wrapper for TextField that correctly updates whenever the value
// changes
//
struct ObservingTextField: View {
    let title: String
    @ObservedObject var value: ObservableValue<String>
    
    var body: some View {
        TextField(title, text: $value.value)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

This may be overkill for your app. There is a more straightforward version, but it has the disadvantage that TextFields are updated even though their contents hasn't changed. In this particular example I do not think that matters much. For larger projects it may become important, not (just) for performance reasons, but updates are sometimes very visible. For reference: here's the simpler version.

import SwiftUI

// Model
struct Assignment {
    let name : String
    let notes: String
}

// ViewModel
class AssignmentViewModel: ObservableObject {
    @Published var name : String
    @Published var notes: String
    
    init(assignment: Assignment) {
        name  = assignment.name
        notes = assignment.notes
    }
}

// Controller
class AssignmentController: ObservableObject {
    @Published var firstAssignment: AssignmentViewModel?

    func retrieveFirstAssignment() {
        let assignment = Assignment(name: "My First Assignment", notes: "Everyone has to start somewhere...")
        
        firstAssignment = AssignmentViewModel(assignment: assignment)
    }
}

struct ContentView: View {
    // In a real app you should use dependency injection here
    // (i.e. provide the assignmentController as a parameter)
    @ObservedObject var assignmentController = AssignmentController()
  
    var body: some View {
        
        NavigationView {
            VStack {
                self.assignmentController.firstAssignment.map { assignmentModelView in
                    FirstAssignmentView(firstAssignment: assignmentModelView)
                }
               
                Button(action: { self.retrieveFirstAssignment() }) {
                    Text("Get The First Assignment !")
                }
            }
                .navigationBarTitle("First Assignment")
        }
    }
    
    func retrieveFirstAssignment() {
        assignmentController.retrieveFirstAssignment()
    }
}

struct FirstAssignmentView: View {
    @ObservedObject var firstAssignment: AssignmentViewModel
    
    var body: some View {
        Form {
            TextField("Assignment Name", text: $firstAssignment.name)
            TextField("Notes", text: $firstAssignment.notes)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.