Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does the view update the viewcontroller?

I learned Swift from the CS193P class. It recommends the following API for a ViewController FaceViewController to update its view FaceView:

var expression = FacialExpression(eyes: .Closed, eyeBrows: .Relaxed, mouth: .Smirk) {
    didSet {
        updateUI() // Model changed, so update the View
    }
}

However, I have not seen an extension of this concept for when a view updates its own model. For example this does not make sense:

// Implementing an imaginary delegate UIFaceViewDelegate
func faceView(_ faceView: FaceView, didEpdateExpressionTo expression: FacialExpression {
    self.expression = expression
    // This triggers another update to the view, and possibly infinite recursion
}

In Objective-C, this was very straightforward because you could use getters and setters as your public API and the backing store as your private state. Swift can use calculated variables to use this approach as well but I believe the Swift designers have something different in mind.

So, what is an appropriate way for a view controller to represent state changes in response to view updates, while also exposing a reasonable read/write API for others to inspect its state?

like image 787
William Entriken Avatar asked Jun 11 '17 20:06

William Entriken


3 Answers

I also watched the cs193p Winter 2017 videos. For the FaceIt app, the mdoel need to be translated to how it will be displayed on the view. And it's not 1 to 1 translation, but more like 3 to 2 or something like that. That's why we have helper method updateUI(_:).

As for the question on how the view controller would update model based on the change in the view. In this example, we couldn't update the model as we need to figure out how to map 2 values to 3 values? If we want persistence, we could just stored the view state in core data or userDefaults.

In a more general settings, where the model change need to update the view and view change need to update the model, then we'll need to have indirection to avoid the cycle like you envision.

For example, since the FacialExpression is a value type. We could have something like:

private var realExpression = FacialExpression(eyes: .Closed, eyeBrows: .Relaxed, mouth: .Smirk)
var expression: FacialExpression  {
    get { return realExpression } 
    set { 
         realExpression = newValue 
         updateUI() // Model changed, so update the View
    }  
}

}

Then in your imaginary delegate UIFaceViewDelegate we could have the following:

// Implementing an imaginary delegate UIFaceViewDelegate
func faceView(_ faceView: FaceView, didEpdateExpressionTo expression: FacialExpression {
    self.realExpression = expression
// This WILL NOT triggers another update to the view, and AVOID THE possibly of infinite recursion

}

like image 54
Hange Avatar answered Nov 14 '22 07:11

Hange


Following is my test code:

class SubView:UIView{

}

class TestVC: UIViewController {
    var testView : SubView = SubView.init(frame: CGRect.zero)  {
        didSet{
            print("testView didSet")
        }
    }

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)

        var testBtn = UIButton.init(frame: CGRect(x: 0, y: 0, width: 264, height: 45))
        testBtn.backgroundColor = .red
        testBtn.addTarget(self, action: #selector(clickToUpdateTestView), for: UIControlEvents.touchUpInside)
        self.view.addSubview(testBtn)
    }

    func clickToUpdateTestView() -> Void {
        self.testView = SubView.init(frame: CGRect.zero)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

But I get "testView didSet" in console output when button is clicked. What's the difference with your implement?

like image 42
simalone Avatar answered Nov 14 '22 08:11

simalone


Hange's solution is good, although it does not work for reference types, as they said. It also introduces another, basically redundant (private) variable, mimicking Objective-C's way of distinguishing between properties and backing member variables. This is a matter of style, personally I would mostly try to avoid that (but I have done the same thing Hange suggests, too).

My reason is that for reference types you need to do this differently anyways and I try to avoid following too many different coding patterns (or having too many redundant variables).


Here's a different proposal:

The important point is to break the cycle at some point. I usually go by "only inform your delegates if you actually did change something about your data". You can do so either in the view's themselves (many do so anyways for rendering performance reasons), but that's not always the case. The view controller isn't a bad place for this check, so I would adapt your observer like this:

var expression = FacialExpression(eyes: .Closed, eyeBrows: .Relaxed, mouth: .Smirk) {
    didSet {
        if updateIsNecessary() {
            updateUI() // Model changed, so update the View
        }
    }
}

updateIsNecessary() obviously determines whether the view actually needs to be changed or not and might rely on oldValue and/or whatever mapping you have between model and view data. Now if the change actually originated from the view in the first place (which informed the view controller, who informed the model, who now informs the view controller again) there shouldn't be anything necessary to update, as the view was the one making a change in the first place.

You might argue that this introduces unnecessary overhead, but I doubt the performance hit is actually big, as it's usually just some easy checks. I usually have a similar check when the model gets updated, too, for the same reasons.

like image 2
Gero Avatar answered Nov 14 '22 07:11

Gero