ios - ¿Cómo actualiza la vista el viewcontroller?
swift model-view-controller (4)
A continuación está mi código de prueba:
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")
}
}
Pero obtengo " testView didSet " en la salida de la consola cuando se hace clic en el botón. ¿Cuál es la diferencia con tu implemento?
Aprendí Swift de la clase CS193P. Recomienda la siguiente API para 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 modelo. 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 de Swift tienen algo diferente en mente.
Entonces, ¿cuál es una forma apropiada para que un controlador de vista represente los cambios de estado en respuesta a las actualizaciones de vista, mientras que también expone una API de lectura / escritura razonable para que otros inspecten 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
función 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 configuración más general, donde el model change need to update the view
y view change need to update the model
, entonces necesitaremos un direccionamiento indirection
para evitar el cycle
como usted imagina.
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 tu 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
}
La solución de Hange es buena, aunque no funciona para los tipos de referencia, como decían. También introduce otra variable, básicamente redundante (privada), que imita la forma en que Objective-C distingue entre propiedades y variables de miembros de respaldo. Esta es una cuestión de estilo, personalmente, principalmente trataría de evitar eso (pero he hecho lo mismo que sugiere Hange también).
Mi razón es que, para los tipos de referencia, debe hacerlo de forma diferente, de todos modos, y trato de evitar seguir demasiados patrones de codificación diferentes (o tener demasiadas variables redundantes).
Aquí hay una propuesta diferente:
El punto importante es romper el ciclo en algún punto. Suelo ir "solo informe a sus delegados si realmente cambió algo sobre sus datos". Puede hacerlo en la vista misma (muchos lo hacen de todos modos por razones de rendimiento del rendimiento), pero ese no es siempre el caso. El controlador de vista no es un mal lugar para esta comprobación, por lo que adaptaría su observador de esta manera:
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 puede confiar en oldValue
y / o en cualquier mapeo que tenga entre el modelo y los datos de visualización. Ahora bien, si el cambio realmente se originó desde la vista en primer lugar (lo que informó al controlador de vista, quién informó al modelo, quién ahora informa al controlador de vista nuevamente) no debería haber nada necesario para actualizar, ya que la vista era la que estaba haciendo un cambio en el primer lugar.
Se podría argumentar que esto introduce una sobrecarga innecesaria, pero dudo que el rendimiento alcanzado sea realmente grande, ya que generalmente solo se trata de algunos controles fáciles. Por lo general, tengo un cheque similar cuando el modelo se actualiza también por las mismas razones.
Dar la expresión variable debe estar sincronizado con la representación de FaceView, lo que significa que la expresión debe tener el valor correcto incluso si FaceView se configuró a partir de una entrada que no sea nuestra expresión y viceversa. Simplemente puedes asegurarte de que se llama updateUI. Iff expression ''s newValue es diferente de su antiguo Valor. Esto evitará la llamada recursiva de FaceView a la expresión para actualizar la 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 se 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 usé un editor de texto adecuado, así que perdónenme si hice errores tipográficos
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 por primera vez con un valor diferente, pero ya no habrá repetición, ya que la expresión estará sincronizada.
Para evitar incluso 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()
}
}
}