【问题标题】:How does the view update the viewcontroller?视图如何更新视图控制器?
【发布时间】:2017-11-13 06:30:15
【问题描述】:

我从 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 供其他人检查其状态?

【问题讨论】:

  • 您的意思是像在layoutSubviews() 中那样更新视图控制器中的视图吗?
  • 为什么self.expression = expression 没有意义?它是如何创建递归的?

标签: ios swift model-view-controller


【解决方案1】:

我还观看了 cs193p Winter 2017 视频。对于FaceIt 应用程序,mdoel 需要翻译成how it will be displayed on the view。而且它不是1 to 1 翻译,而是更像3 to 2 或类似的东西。这就是为什么我们有辅助方法updateUI(_:)

至于view controller 将如何根据view 的变化更新model 的问题。在此示例中,我们无法更新 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

}

【讨论】:

  • 对于简单的一对一的情况,模型是一个数字并且视图可以更新数字(例如通过滑动)这种方法仍然有意义吗?
  • 只要是value type(例如数字、字符串、数组、字典、结构、枚举),我上面写的示例代码应该仍然有效。
【解决方案2】:

以下是我的测试代码:

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”。你的工具有什么不同?

【讨论】:

  • 这不是问题。 @Full 的意思是,如果您以某种方式更新 UI,然后该更新通过委托通知底层模型,您可以引入无限递归(因为模型更改也会通过委托再次通知视图)。
  • 我不是试图解决这个问题,而是试图解释应该调用 didSet。所以应该调用“updateUI()”。因此问题应该在“updateUI()”中修复,你认为在 updateUI() 中添加 bool 值也应该可以吗?这是关于实现的逻辑,而不是关于语言的逻辑。 @Gero
  • 我理解您的意图,只是与问题无关。可能是语言障碍(无意冒犯)。 :) 我只是想帮助您看到:这不是“我的代码中的某些内容已损坏,请帮我找到它”的问题,而更像是“最佳实践是什么”的问题。您正在举一个示例,说明如何对更改视图属性完全做出反应,这与 OP 的疑惑无关。顺便说一句,我不是在抱怨,只是想消除一个误解。
【解决方案3】:

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 和/或模型和视图数据之间的任何映射。现在,如果更改实际上源自视图(它通知了视图控制器,谁通知了模型,谁现在再次通知视图控制器),那么应该没有任何必要更新,因为视图是制作的首先是变化。

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

【讨论】:

    【解决方案4】:

    赋予变量 expression 应该与 FaceView 表示同步,这意味着 expression 应该具有正确的值,即使 FaceView 是从我们的 以外的输入设置的表达式 反之亦然,您只需确保 updateUI 被调用,如果 expression 的 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
        }
    }
    

    我没有使用合适的文本编辑器,如果我有错别字请见谅

    编辑:

    expression 第一次从假想的委托中设置为不同的值但不会有相同的 expression 值时,将有一次不必要的 FaceView 更新不再重复,因为 expression 将同步。

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-18
      • 2019-10-04
      • 2017-12-13
      • 2010-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多