¿Cómo actualiza la vista el viewcontroller?

Aprendí a Swift de la class CS193P. Recomienda la siguiente API para un ViewController FaceViewController para actualizar su vista FaceView :

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

Sin embargo, no he visto una extensión de este concepto para cuando una vista actualiza su propio model. Por ejemplo, esto no tiene sentido:

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

En Objective-C, esto fue muy sencillo porque podría usar getters y setters como su API pública y la tienda de respaldo como su estado privado. Swift puede usar variables calculadas para usar este enfoque también, pero creo que los diseñadores Swift tienen algo diferente en mente.

Entonces, ¿cuál es la forma adecuada para que un controller de vista represente los cambios de estado en respuesta a las actualizaciones de la vista, mientras que también expone una API de lectura / escritura razonable para que otros puedan inspeccionar su estado?

También vi los videos de cs193p Winter 2017. Para la aplicación FaceIt , el mdoel debe traducirse a how it will be displayed on the view . Y no es 1 to 1 traducción de 1 to 1 , sino más bien de 3 to 2 o algo así. Es por eso que tenemos el método helper updateUI(_:) .

En cuanto a la pregunta sobre cómo el view controller actualizaría el model function del cambio en la view . En este ejemplo, no pudimos actualizar el model ya que necesitamos averiguar how to map 2 values to 3 values . Si queremos persistencia, podríamos simplemente almacenar el view state en core data o userDefaults .

En una configuration más general, donde el model change need to update the view y view change need to update the model , entonces tendremos que tener una indirection para evitar el cycle como lo imaginamos.

Por ejemplo, dado que FacialExpression es un tipo de valor. Podríamos tener algo como:

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

}

Luego, en su imaginary delegate UIFaceViewDelegate , podríamos tener lo siguiente:

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

}

Lo que sigue es mi código de testing:

 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 = .networking testBtn.addTarget(self, action: #selector(clickToUpdateTestView), for: UIControlEvents.touchUpInside) self.view.addSubview(testBtn) } func clickToUpdateTestView() -> Void { self.testView = SubView.init(frame: CGRect.zero) } requinetworking init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } 

Pero obtengo " testView didSet " en la salida de la console cuando se hace clic en el button. ¿Cuál es la diferencia con tu implemento?

La solución de Hange es buena, aunque no funciona para los types de reference, como dijeron. También introduce otra variable, básicamente networkingundante (privada), que imita la forma de Objective-C de distinguir entre las properties y las variables de miembro de respaldo. Esta es una cuestión de estilo, personalmente, sobre todo intentaría evitar eso (pero he hecho lo mismo que Hange sugiere también).

Mi razón es que, para los types de reference, debe hacer esto de manera diferente y trato de evitar seguir demasiados patrones de encoding diferentes (o tener demasiadas variables networkingundantes).


Aquí hay una propuesta diferente:

El punto importante es romper el ciclo en algún momento. Usualmente, veo "solo informa a tus delegates si realmente cambió algo sobre tus datos". Puede hacerlo en la vista propia (muchos lo hacen de todos modos por razones de performance), pero ese no siempre es el caso. El controller de vista no es un mal lugar para esta comprobación, por lo que adaptaría su observador así:

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

updateIsNecessary() obviamente determina si la vista realmente necesita ser modificada o no y podría depender de oldValue y / o el mapeo que tenga entre el model y la vista de datos. Ahora bien, si el cambio se originó realmente en la vista en primer lugar (lo que informó al controller de vista, que informó al model, que ahora informa nuevamente al controller de vista), no debería haber nada necesario para actualizar, ya que la vista era la que estaba haciendo un cambio en el primer lugar.

Puede argumentar que esto introduce una sobrecarga innecesaria, pero dudo que el golpe de performance sea realmente grande, ya que generalmente son solo algunas comprobaciones fáciles. Por lo general, tengo una verificación similar cuando el model también se actualiza por los mismos motivos.

Dar la expresión variable debe estar sincronizada con la representación de la representación de FaceView, la expresión debería tener el valor correcto incluso si FaceView se configuró a partir de una input que no sea nuestra expresión y viceversa, puede simplemente asegurarse de que updateUI se llama iff newValue de la expresión es diferente de su viejo valor Esto evitará la llamada recursiva de FaceView a la expresión para actualizar UI y volver a FaceView

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

Esto significa que FacialExpression debe ajustarse a Equatable, lo que puede hacer sobrecargando el operador ==.

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

No utilicé un editor de text adecuado, así que perdóneme si escribo errores tipocharts

EDITAR:

Habrá una actualización innecesaria de FaceView con el mismo valor de expresión cuando la expresión se establece desde el delegado imaginario la primera vez con un valor diferente, pero ya no habrá más repeticiones ya que la expresión se sincronizará.

Para evitar eso, puede comparar la expresión con otra propiedad de expresión en FaceView que contiene la expresión actual.

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