视图如何更新viewcontroller?

时间:2017-06-11 20:08:04

标签: ios swift model-view-controller

我从CS193P课程中学到了Swift。它建议ViewController FaceViewController使用以下API来更新其视图FaceView

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

但是,当视图更新自己的模型时,我还没有看到这个概念的扩展。例如,这没有意义:

// 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
}

在Objective-C中,这非常简单,因为您可以将getter和setter用作公共API,将后备存储用作私有状态。 Swift也可以使用计算变量来使用这种方法,但我相信Swift设计师有一些不同的想法。

那么,视图控制器在响应视图更新时表示状态更改的适当方式是什么,同时还为其他人公开合理的读/写API以检查其状态?

4 个答案:

答案 0 :(得分:8)

我还观看了cs193p 2017年冬季视频。对于FaceIt应用,mdoel需要转换为how it will be displayed on the view。它不是1 to 1翻译,而是更像3 to 2或类似的东西。这就是为什么我们有辅助方法updateUI(_:)
关于view controller如何根据model中的更改更新view的问题。在此示例中,我们无法更新model,因为我们需要找出how to map 2 values to 3 values?如果我们想要持久性,我们可以将view state存储在core datauserDefaults中。

model change need to update the viewview change need to update the model的更常规设置中,我们需要indirection来避免您想象的cycle

例如,因为FacialExpression是值类型。我们可以有类似的东西:

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
    }  
}

}

然后在imaginary delegate UIFaceViewDelegate我们可以拥有以下内容:

// 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

}

答案 1 :(得分:3)

以下是我的测试代码:

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")
    }
}

但我得到" testView didSet "在单击按钮时在控制台输出中。与您的工具有什么不同?

答案 2 :(得分:2)

Hange的解决方案很好,虽然它不适用于参考类型,正如他们所说的那样。 它还引入了另一个基本冗余(私有)变量,模仿Objective-C区分属性和后备成员变量的方式。这是一个风格问题,我个人会主要试图避免这种情况(但我也做过Hange所说的同样的事情)。

我的理由是,对于引用类型,您需要以不同的方式执行此操作,并且我尽量避免使用太多不同的编码模式(或者有太多冗余变量)。

这是一个不同的提议:

重要的是要在某个时刻打破这个循环。我通常会“只告知你的代表你是否真的改变了你的数据”。你可以在视图中自己这样做(很多时候为了渲染性能原因而这样做),但情况并非总是如此。视图控制器不是这个检查的坏地方,所以我会像这样调整你的观察者:

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

updateIsNecessary()显然确定视图是否实际需要更改,并且可能依赖于oldValue和/或模型和视图数据之间的任何映射。现在,如果更改实际上源自视图(通知视图控制器,谁通知模型,谁现在再次通知视图控制器)应该没有任何必要更新,因为视图是一个制作首先是改变。

您可能会认为这会带来不必要的开销,但我怀疑性能影响实际上很大,因为它通常只是一些简单的检查。出于同样的原因,我通常也会在模型更新时进行类似的检查。

答案 3 :(得分:1)

赋予变量表达式应该与FaceView表示同步,这意味着表达式应该具有正确的值,即使FaceView是从我们的以外的输入设置的表达式,反之亦然,您可以简单地确保调用 updateUI iff 表达式的newValue与其oldValue不同。这将避免从FaceView到表达式到updateUI和返回FaceView

的递归调用
var expression: FacialExpression = FacialExpression(eyes: .Closed, eyeBrows: .Relaxed, mouth: .Smirk) {
    didSet {
        if expression != oldValue {
            updateUI()
        }
    }
}

这意味着FacialExpression应符合Equatable,您可以通过重载==运算符来完成。

public extension FacialExpression: Equatable {
    static func ==(lhs: FacialExpression, rhs: FacialExpression) -> Bool {
        return lhs == rhs // TODO: Logic to compare FacialExpression
    }
}

我没有使用正确的文字编辑器,如果我犯了错别字,请原谅我

编辑:

当第一次使用不同的值从虚构代表设置表达式时,将会有一个不必要的具有相同表达式值的FaceView更新但是赢得了'不再重复了,因为表达式将会同步。

为了避免这种情况,您可以将表达式与FaceView中另一个包含当前表达式的表达式属性进行比较。

var expression: FacialExpression = FacialExpression(eyes: .Closed, eyeBrows: .Relaxed, mouth: .Smirk) {
    didSet {
        if expression != faceView.currentExpression {
            updateUI()
        }
    }
}