with tutorial example different ios swift uitableview design-patterns mvvm

ios - tutorial - tableview with different cells



Crear view-model para cada UITableViewCell (2)

Estoy atascado en una decisión de diseño con la creación de modelos de vista para las celdas de la vista de tabla. Los datos para cada celda son proporcionados por una clase de fuente de datos (tiene una matriz de Contacts ). En MVVM solo el modelo de vista puede hablar con el modelo, pero no tiene sentido poner el origen de datos en el modelo de vista porque permitiría acceder a los datos de todas las celdas, y también es incorrecto colocar el origen de datos en el controlador de vista, ya que no debe tener referencia a los datos. Hay algunos otros momentos clave:

  • Cada celda debe tener su propia instancia de view-model no compartida
  • cellForRowAtindexPath no debe colocarse en un modelo de vista porque no debe contener ninguna referencia de UI
  • El modelo de vista View / ViewController no debe interactuar con el modelo de vista de la celda

¿Cuál es la forma correcta de "insertar" la fuente de datos para las células en la relación de MVVM ? Gracias.


A menos que tenga un problema específico que se resuelva con Model-View-ViewModel entonces intentar adoptarlo solo para ''mejores prácticas'' terminará introduciendo una gran cantidad de complejidad innecesaria.

Su fuente de datos es la responsable de llenar su tabla. Nada más que su fuente de datos necesita una referencia a los contacts ya que actualizará su tabla con estos datos.

View Models solo entran en juego cuando necesita realizar interacciones e interacciones de IU complejas. La VM es responsable de encapsular el estado de su vista, cosas como ...

  1. Valores de los campos de texto
  2. Qué casillas de verificación / botones de opción están seleccionados
  3. Colores de los elementos
  4. Lógica de animación
  5. Dependencias entre elementos de UI

Cuando se realizan cambios en su Vista, su View Model es responsable de realizar actualizaciones a su Model (cuando sea necesario) para reflejar los cambios que se han realizado a ese Model través de la IU.

Con todo lo dicho, los Modelos de Vista no tienen sentido en IOS , porque IOS utiliza View Controllers en la metodología de diseño llamada MVC (Model-View-Controller)


Déjame comenzar con algo de teoría. MVVM es una especialización del modelo de presentación (o modelo de aplicación) para Silverlight y WPF de Microsoft. Las ideas principales detrás de este patrón de arquitectura de interfaz de usuario son:

  • La parte de vista es la única que depende del marco de GUI. Esto significa que para iOS, el controlador de vista es parte de la vista.
  • La vista solo puede hablar con el modelo de vista. Nunca al modelo.
  • El modelo de vista contiene el estado de la vista. Este estado se ofrece a la vista a través de las propiedades del modelo de visualización. Estas propiedades contienen no solo el valor de las etiquetas, sino también otra información relacionada con la vista, como si el botón Guardar está habilitado o el color para una vista de calificación. Pero la información del estado debe ser independiente de la interfaz de usuario. Entonces, en el caso de iOS, la propiedad del color debe ser una enumeración, por ejemplo, en lugar de un UIColor.
  • El modelo de vista también proporciona métodos que se encargarán de las acciones de UI. Estas acciones hablarán con el modelo, pero nunca cambian el estado de la vista directamente relacionada con los datos. En cambio, habla con el modelo y le pide los cambios necesarios.
  • El modelo debe ser autónomo , es decir, debe poder usar el mismo código para el modelo para una aplicación de línea de comando y una interfaz de interfaz de usuario. Cuidará toda la lógica comercial.
  • El modelo no sabe sobre el modelo de vista. Por lo tanto, los cambios en el modelo de vista se propagan a través de un mecanismo de observación. Para iOS y un modelo con subclases simples de NSObject o incluso Core Data, KVO se puede usar para eso (también para Swift).
  • Una vez que el modelo de vista conoce los cambios en el modelo, debe actualizar el estado que contiene (si usa tipos de valor, debe crear uno actualizado y reemplazarlo).
  • El modelo de vista no sabe sobre la vista. En su concepción original utiliza el enlace de datos, que no está disponible para iOS. Por lo tanto, los cambios en el modelo de vista se propagan a través de un mecanismo de observación. También puede usar KVO aquí, o como lo menciona en la pregunta, un patrón de delegación simple, incluso mejor si se combina con los observadores de propiedad de Swift. Algunas personas prefieren marcos reactivos, como RxSwift, ReactiveCocoa o incluso Swift Bond.

Los beneficios son los que mencionaste:

  • Mejor separación de preocupaciones.
  • Independencia de UI: migración más fácil a otras IU.
  • Mejor capacidad de prueba debido a la separación de preocupaciones y la naturaleza desacoplada del código.

Volviendo a su pregunta, la implementación del protocolo UITableViewDataSource pertenece a la parte de vista de la arquitectura, debido a sus dependencias en el marco de la interfaz de usuario. Tenga en cuenta que para usar ese protocolo en su código, ese archivo debe importar UIKit. También métodos como tableView(:cellForRowAt:) que devuelve una vista dependen en gran medida de UIKit.

Entonces, su matriz de Contacts , que de hecho es su modelo, no puede ser operada o consultada a través de la vista (fuente de datos o de otro modo). En su lugar, pasa un modelo de vista a su controlador de vista de tabla, que, en el caso más simple, tiene dos propiedades (recomiendo que sean almacenadas, no propiedades calculadas). Uno de ellos es el número de secciones y el otro es el número de filas por sección:

var numberOfSections: Int = 0 var rowsPerSection: [Int] = []

El modelo de vista se inicializa con una referencia al modelo y, como último paso de la inicialización, establece el valor de esas dos propiedades.

La fuente de datos en el controlador de vista usa los datos del modelo de vista:

override func numberOfSections(in tableView: UITableView) -> Int { return viewModel.numberOfSections } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return viewModel.rowsPerSection[section] }

Finalmente, puede tener una estructura de modelo de vista diferente para cada una de las celdas:

struct ContactCellViewModel { let name: String init(contact: Contact) { name = contact.name ?? "" } }

Y la subclase UITableViewCell sabrá cómo usar esa estructura:

class ContactTableViewCell: UITableViewCell { var viewModel: ContactCellViewModel! func configure() { textLabel!.text = viewModel.name } }

Para tener el modelo de vista correspondiente para cada una de las celdas, el modelo de vista de la vista de tabla proporcionará un método que las genera, y que se puede usar para poblar la matriz de modelos de vista:

func viewModelForCell(at index: Int) -> ContactCellViewModel { return ContactCellViewModel(contact: contacts[index]) }

Como puede ver, los modelos de vista aquí son los únicos que hablan con el modelo (su matriz de Contacts ) y las vistas solo hablan con los modelos de vista.

Espero que esto ayude.