Ejemplos de configuración de UIViewControllers que utilizan RouteComposer

En un artículo anterior , hablé sobre el enfoque que usamos para componer y navegar entre los controladores de vista en varias aplicaciones en las que trabajo, lo que, como resultado, resultó en una biblioteca RouteComposer separada. Recibí una cantidad significativa de comentarios agradables sobre el artículo anterior y algunos consejos prácticos, lo que me llevó a escribir otro que explicaría un poco más cómo configurar la biblioteca. Debajo del corte, intentaré distinguir algunas de las configuraciones más comunes.



Cómo el enrutador analiza la configuración


Para comenzar, considere cómo el enrutador analiza la configuración que escribió. Tome el ejemplo del artículo anterior:


let productScreen = StepAssembly(finder: ClassFinder(options: [.current, .visible]), factory: ProductViewControllerFactory()) .using(UINavigationController.pushToNavigation()) .from(SingleContainerStep(finder: NilFinder(), factory: NavigationControllerFactory())) .using(GeneralAction.presentModally()) .from(GeneralStep.current()) .assemble() 

El enrutador pasará por la cadena de pasos comenzando desde el primer momento, hasta que uno de los pasos (usando el Finder proporcionado) "informa" que el UIViewController deseado ya UIViewController presente en la pila. (Por ejemplo, GeneralStep.current() garantizado de estar presente en la pila del controlador). Luego, el enrutador comenzará a retroceder a lo largo de la cadena de pasos creando los UIViewController requeridos usando los UIViewController proporcionados e integrándolos usando los Action s especificados. Gracias a la verificación de tipos incluso en la etapa de compilación, la mayoría de las veces, no podrá usar UITabBarController.addTab incompatibles con el Fabric proporcionado (es decir, no podrá usar UITabBarController.addTab en el controlador de UITabBarController.addTab creado por NavigationControllerFactory ).


Si imagina la configuración descrita anteriormente, entonces si solo tiene un determinado ProductViewController en la pantalla, se realizarán los siguientes pasos:


  1. ClassFinder no encontrará ProductViewController y el enrutador continuará
  2. NilFinder nunca encontrará nada y el enrutador seguirá adelante
  3. GeneralStep.current siempre devolverá el UIViewController superior en la pila.
  4. Iniciar UIViewController encuentra, el enrutador volverá
  5. Construye un UINavigationController usando el `NavigationControllerFactory
  6. Lo mostrará modalmente usando GeneralAction.presentModally
  7. ProductViewController ProductViewController ProductViewControllerFactory
  8. Integra el ProductViewController creado en el UINavigationController anterior usando UINavigationController.pushToNavigation
  9. Terminar la navegación

NB: Debe entenderse que en realidad es imposible mostrar un UINavigationController sin algún UIViewController dentro de él. Por lo tanto, el router realizará los pasos 5 a 8 en un orden ligeramente diferente. Pero no deberías pensarlo. La configuración se describe secuencialmente.


Una buena práctica al escribir una configuración es suponer que el usuario puede estar ubicado en cualquier lugar de su aplicación en ese momento y, de repente, recibe un mensaje push con una solicitud para llegar a la pantalla que está describiendo, y tratar de responder la pregunta: "¿Cómo debería comportarse la aplicación? ? "," ¿Cómo se comportará el Finder en la configuración que estoy describiendo? ". Si se tienen en cuenta todas estas preguntas, obtendrá una configuración que garantiza mostrar al usuario la pantalla deseada donde quiera que esté. Y este es el requisito principal para las aplicaciones modernas de los equipos involucrados en la comercialización y la atracción de usuarios.


StackIteratingFinder y sus opciones:


Puede implementar el concepto Finder de la forma que considere más aceptable. Sin embargo, la forma más fácil es recorrer en iteración el gráfico de los controladores de vista en la pantalla. Para simplificar este objetivo, la biblioteca proporciona StackIteratingFinder y varias implementaciones que se encargarán de esta tarea. Solo tiene que responder la pregunta: ¿es este el UIViewController que espera?


Para influir en el comportamiento de StackIteratingFinder y decirle en qué partes del gráfico (pila) de los controladores de vista desea que busque, puede especificar una combinación de SearchOptions al crearlo. Y deberían detenerse con más detalle:


  • current : el controlador de vista superior en la pila. (El que es el rootViewController la UIWindow o el que se muestra modalmente en la parte superior)
  • visible : si el UIViewController es un contenedor, mire en su UIViewController visible (por ejemplo: UINavigationController siempre tiene un UIViewController visible, UISplitController puede tener uno o dos dependiendo de cómo se presente).
  • contained : en caso de que el UIViewController sea ​​un contenedor, busque en todos sus UIViewController anidados (por ejemplo: UIViewController todos los controladores de vista del UINavigationController incluido el visible)
  • presenting : Buscar también en todos los UIViewController ah debajo del UIViewController (si hay, por supuesto)
  • presented : Busque UIViewController para el que se proporciona (para StackIteratingFinder esta opción no tiene sentido, ya que siempre comienza desde arriba)

La siguiente figura puede hacer que la explicación anterior sea más obvia:


Recomendaría familiarizarse con el concepto de contenedores en un artículo anterior.


Ejemplo Si desea que su Finder busque un AccountViewController en toda la pila, pero solo entre los UIViewController visibles, esto debería escribirse así:


 ClassFinder<AccountViewController, Any?>(options: [.current, .visible, .presenting]) 

NB Si, por algún motivo, la configuración proporcionada es poca, siempre puede escribir fácilmente su implementación de Finder a. Un ejemplo estará en este artículo.


Pasemos, de hecho, a los ejemplos.


Ejemplos de configuraciones con explicaciones.


Tengo un cierto UIViewController , que es el rootViewController UIWindow , y quiero HomeViewController con un cierto HomeViewController al final de la navegación:


 let screen = StepAssembly( finder: ClassFinder<HomeViewController, Any?>(), factory: XibFactory()) .using(GeneralAction.replaceRoot()) .from(GeneralStep.root()) .assemble() 

XibFactory cargará HomeViewController desde el archivo xib de HomeViewController.xib


No olvide que si usa implementaciones abstractas de Finder y Factory en combinación, debe especificar el tipo de UIViewController y el contexto para al menos una de las entidades: ClassFinder<HomeViewController, Any?>


¿Qué sucede si, en el ejemplo anterior, reemplazo GeneralStep.root con GeneralStep.current ?


La configuración funcionará hasta que se llame en el momento en que haya algún UIViewController modal en la pantalla. En este caso, GeneralAction.replaceRoot no podrá reemplazar el controlador raíz, ya que hay un controlador modal por encima y el enrutador informará un error. Si desea que esta configuración funcione de todos modos, debe explicarle al enrutador que desea que GeneralAction.replaceRoot se aplique específicamente al UIViewController raíz. Luego, el enrutador eliminará todos los UIViewController representados UIViewController y la configuración funcionará en cualquier situación.


Quiero mostrar algunos AccountViewController , si todavía se muestra bien, dentro de cualquier UINavigationController y que está actualmente en la pantalla en algún lugar (incluso si este UINavigationController bajo algún UIViewController modal):


 let screen = StepAssembly( finder: ClassFinder<AccountViewController, Any?>(), factory: XibFactory()) .using(UINavigationController.pushToNavigation()) .from(SingleStep(ClassFinder<UINavigationController, Any?>(), NilFactory())) .from(GeneralStep.current()) .assemble() 

¿Qué significa NilFactory en esta configuración? Con esto, le dice al enrutador que si no puede encontrar ningún UINavigationController en la pantalla, no quiere que lo cree y simplemente no haga nada en este caso. Por cierto, dado que esto es NilFactory , no puedes usar Action después de él.


Quiero mostrar algunos AccountViewController , si aún no se muestran, dentro de cualquier UINavigationController y que actualmente se encuentra en algún lugar de la pantalla, y si no es así, UINavigationController y UINavigationController :


 let screen = StepAssembly( finder: ClassFinder<AccountViewController, Any?>(), factory: XibFactory()) .using(UINavigationController.PushToNavigation()) .from(SwitchAssembly<UINavigationController, Any?>() .addCase(expecting: ClassFinder<UINavigationController, Any?>(options: .visible)) //   -    .assemble(default: { //      return ChainAssembly() .from(SingleContainerStep(finder: NilFinder(), factory: NavigationControllerFactory())) .using(GeneralAction.presentModally()) .from(GeneralStep.current()) .assemble() }) ).assemble() 

Quiero mostrar UITabBarController con UITabBarController contienen AccountViewController y AccountViewController reemplazándolos con la raíz actual:


 let tabScreen = SingleContainerStep( finder: ClassFinder(), factory: CompleteFactoryAssembly(factory: TabBarControllerFactory()) .with(XibFactory<HomeViewController, Any?>(), using: UITabBarController.addTab()) .with(XibFactory<AccountViewController, Any?>(), using: UITabBarController.addTab()) .assemble()) .using(GeneralAction.replaceRoot()) .from(GeneralStep.root()) .assemble() 

¿Puedo usar el UIViewControllerTransitioningDelegate personalizado con la acción GeneralAction.presentModally :


 let transitionController = CustomViewControllerTransitioningDelegate() //     .using(GeneralAction.PresentModally(transitioningDelegate: transitionController)) 

Quiero ir a AccountViewController , donde sea que AccountViewController el usuario, en otra pestaña o incluso en algún tipo de ventana modal:


 let screen = StepAssembly( finder: ClassFinder<AccountViewController, Any?>(), factory: NilFactory()) .from(tabScreen) .assemble() 

¿Por qué estamos usando NilFactory ? No necesitamos construir un AccountViewController si no se encuentra. Se construirá en la configuración de tabScreen . Mírala arriba.


Quiero mostrar ForgotPasswordViewController , pero, ciertamente, después de LoginViewController dentro de UINavigationController :


 let loginScreen = StepAssembly( finder: ClassFinder<LoginViewController, Any?>(), factory: XibFactory()) .using(UINavigationController.pushToNavigation()) .from(NavigationControllerStep()) .using(GeneralAction.presentModally()) .from(GeneralStep.current()) .assemble() let forgotPasswordScreen = StepAssembly( finder: ClassFinder<ForgotPasswordViewController, Any?>(), factory: XibFactory()) .using(UINavigationController.pushToNavigation()) .from(loginScreen.expectingContainer()) .assemble() 

Puede usar la configuración en el ejemplo para la navegación en ForgotPasswordViewController y LoginViewController


¿Por qué expectingContainer en el ejemplo anterior?


Dado que la acción pushToNavigation requiere la presencia de un UINavigationController y en la configuración posterior, el método expectingContainer nos permite evitar un error de compilación al garantizar que tengamos cuidado de que cuando el enrutador llegue a loginScreen en loginScreen de loginScreen , el UINavigationController esté allí.


¿Qué sucede si en la configuración anterior reemplazo GeneralStep.current con GeneralStep.root ?


Funcionará, pero dado que le dice al enrutador que desea que comience a construir una cadena desde el UIViewController raíz, si se abre algún UIViewController modal encima, el enrutador los ocultará antes de comenzar a construir la cadena.


Mi aplicación tiene un UITabBarController contiene BagViewController y BagViewController como pestañas. Quiero que el usuario pueda cambiar entre ellos usando los iconos en las pestañas como de costumbre. Pero si llamo a la configuración mediante programación (por ejemplo, el usuario hace clic en "Ir a la bolsa" dentro del HomeViewController ), la aplicación no debería cambiar la pestaña, sino mostrar el BagViewController .


Hay 3 formas de lograr esto en la configuración:


  1. Configure StackIteratingFinder para buscar solo en los visibles usando [.current, .visible]
  2. Utilice NilFinder que significa que el enrutador nunca encontrará el BagViewController en las BagViewController y siempre lo creará. Sin embargo, este enfoque tiene un efecto secundario: si, por ejemplo, un usuario que ya está en BagViewController se presenta modalmente y, por ejemplo, hace clic en un enlace universal que BagViewController debería mostrarle, el enrutador no lo encontrará y creará otra instancia y la mostrará encima de ella modalmente. Esto puede no ser lo que quieres.
  3. Cambie un poco de ClassFinder para que encuentre solo el BagViewController muestra BagViewController e ignore el resto, y ya lo use en la configuración.

 struct ModalBagFinder: StackIteratingFinder { func isTarget(_ viewController: BagViewController, with context: Any?) -> Bool { return viewController.presentingViewController != nil } } let screen = StepAssembly( finder: ModalBagFinder(), factory: XibFactory()) .using(UINavigationController.pushToNavigation()) .from(NavigationControllerStep()) .using(GeneralAction.presentModally()) .from(GeneralStep.current()) .assemble() 

En lugar de una conclusión


Espero que los métodos de configuración del enrutador se hayan vuelto algo más claros. Como dije, usamos este enfoque en 3 aplicaciones y aún no hemos encontrado una situación en la que no fuera lo suficientemente flexible. La biblioteca, así como la implementación del enrutador que se le proporciona, no utiliza ningún truco objetivo con el tiempo de ejecución y sigue completamente todos los conceptos de Cocoa Touch, solo ayuda a dividir el proceso de composición en pasos y ejecutarlos en la secuencia dada y probado con las versiones de iOS 9 a 12. Además , este enfoque se ajusta a todos los patrones arquitectónicos que implican trabajar con la pila UIViewController (MVC, MVVM, VIP, RIB, VIPER, etc.)


Estaría encantado de sus comentarios y sugerencias. Especialmente si crees que vale la pena detenerse en algunos aspectos con más detalle. Quizás el concepto de contextos necesita aclaración.

Source: https://habr.com/ru/post/es428990/


All Articles