
App Intents: Ako sprístupniť vašu aplikáciu pomocou Siri, Spotlight a Widgetov

Návrhový vzor koordinátor nieje nič nové. Existuje mnoho návodov, ako ho možno použiť. Dnes ti ukážem, ako ho používame my.


Návrhový vzor koordinátor napomáha pri rozdeľovaní zodpovedností v toku aplikácie. Logika navigácie tak prechádza z view controllera na koordinátor a zároveň tým nadobudneme väčšiu kontrolu nad navigačným tokom.
Náš koordinátor je prispôsobený aby fungoval s jednosmerným tokom podobným reduxu. Ukážem ti jednoduchý príklad koordinátora, ako ho robíme my a na konci článku pridám odkaz na knižnicu ktorej súčasťou je GoodCoordinator, ktorý si môžeš stiahnuť do projektu cez SPM
Začneme niečím jednoduchým. Najskôr vytvoríme enum súbor s názvom Coordinator.swift, ktorý bude obsahovať enum s akciami, ktoré hovoria o type navigácie. Mala by obsahovať základné akcia ako push, present, dismiss, pop a predvolenú hodnotu none.
import Combine
import UIKit
enum StepAction {
case push(UIViewController)
case present(UIViewController, UIModalPresentationStyle)
case dismiss
case pop
case none
}Následne si vytvoríme generickú triedu Coordinator. Táto trieda bude slúžiť, ako vzor pre ostatné koordinátori, ktoré po ňom budú dediť. Bude obsahovať navigationController, ktorý sa bude vkladať v konštruktore. Bude optional.
Následne potrebujeme step a cancellables. Sú súčasťou combine knižnice dostupnej od iOS 13.
Step bude mať property wrapper published, čo znamená, že sa naň vieme subscribenuť a počúvať na zmeny podobne, ako keby sme použili didSet closure.
class Coordinator<Step> {
weak var navigationController: UINavigationController?
var cancellables: Set<AnyCancellable> = Set()
@Published var step: Step?
}Do triedy coordinator pridáme funkciu navigate, ktorá bude slúžiť, ako rozhranie pre child coordinatory ale bude prázdna a nebude vykonávať žiadnu funkciu.
Následne zadefinujeme privátnú funkciu navigate, ktorá bude vykonávať štandardné navigačné akcie, ktoré sme si zadefinovali hore nad navigation controllerom, ktorý sme si uložili rovnako, ako v triede.
A napokon si zadefinujeme public funkciu start, ktorá bude slúžiť ako rozhranie child koordinátorov a zároveň bude pri každom spustení počúvať na zmenu premennej step, ako už som vyššie spomenul. Keďže je step optional používame compactMap, aby boli brané do úvahy iba nenulové hodnoty a v sink closure voláme našu private navigate funkciu.
@discardableResult
func navigate(to stepper: Step) -> StepAction {
return .none
}
private func navigate(flowAction: StepAction) {
switch flowAction {
case .dismiss:
if let presentedViewController = navigationController?.presentedViewController {
presentedViewController.dismiss(animated: true, completion: nil)
} else {
navigationController?.topViewController?.dismiss(animated: true, completion: nil)
}
case .push(let controller):
navigationController?.pushViewController(controller, animated: true)
case .pop:
navigationController?.popViewController(animated: true)
case .present(let controller, let style):
controller.modalPresentationStyle = style
if let presentedViewController = navigationController?.presentedViewController {
presentedViewController.present(controller, animated: true, completion: nil)
} else {
navigationController?.topViewController?.present(
controller,
animated: true,
completion: nil
)
}
case .none:
break
}
}
@discardableResult
public func start() -> UIViewController? {
$step
.compactMap { $0 }
.sink { [weak self] in
guard let self = self else { return }
self.navigate(flowAction: self.navigate(to: $0))
}
.store(in: &cancellables)
return navigationController
}Teraz si poďme zadefinovať konkrétné navigačné akcie. Vytvoríme si súbor AppStep.swift a napíšeme 2 jednoduché case. Neskôr je možné v tomto enume vrstviť štruktúru na ďalšie koordinátori pričom, každý case by reprezentoval jeden koordinátor a associated value jeho AppSteps. Teraz poďme zadefinovať ten najjednoduchší prípad.
enum AppStep {
case showFirstViewController
case showSecondViewController
}Po úspešnom nastavení parentKoordinátor ho poďme použit. Vytvoríme final class AppCoordinator, ktorá bude dediť po Coordinator typu AppStep, ktorý sme si nižšie zadefinovali.
V tomto koordinatori si zadefinujeme appwindow. Vo väčšine praktických prípadov sa logika okien rieši práve tu pretože AppCoordinator zvyčajne rozhoduje o tom, či sme na Login okne alebo už sme nalogovaný a vo vnútri appky. V našom prípade použijeme len jedno okno, ktoré bude automaticky zvolené od začiatku.
V inite okno nastavíme. A vo funkcii štart mu vytvoríme navigation controller. A okno zobrazíme.
import Foundation
import UIKit
import Combine
// MARK: - Navigation & Initializers
final class AppCoordinator: Coordinator<AppStep> {
// MARK: - Properties
private let appWindow: UIWindow
// MARK: - Init
override init() {
appWindow = UIWindow()
appWindow.frame = UIScreen.main.bounds
}
// MARK: - Overrides
@discardableResult
override func start() -> UIViewController? {
super.start()
let navigationController = UINavigationController()
self.navigationController = navigationController
appWindow.rootViewController = navigationController
appWindow.makeKeyAndVisible()
return navigationController
}
}Teraz sme hotoví s prípravou a poďme do navigacie niečo pushnút. Overrideneme si funkciu a cez switch nad AppStepom vytvoríme rozhodovaciu logiku.
V každom appstep vieme pushnúť viewController tak, že vrátime appstep s viewcontrollerom ako parameter. Takisto by sa tu dalo vrátiť .none a do navigation controlleru pridať ďalší navigationController z iného coordinátoru.
override func navigate(to step: AppStep) -> StepAction {
switch step {
case .showFirstViewController(let animated):
let firstViewController = DocumentBrowserViewController(forOpeningFilesWithContentTypes: ["two", "one"])
return .push(firstViewController)
case .showSecondViewController(let animated):
let secondViewController = DocumentBrowserViewController(forOpeningFilesWithContentTypes: ["hone", "two"])
return .push(secondViewController)
default:
return .none
}
}Teraz to skúsme zapojiť do appky. V triede Appdelegate si vytvoríme premennú appCoordinator a vytvoríme ho pri zapínaní appky. Koordinátor následne zavoláme start a teraz stačí nastaviť step na AppStep, ktorý je potrebný.
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
private var appCoordinator: AppCoordinator!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let appCoordinator = AppCoordinator()
self.appCoordinator = appCoordinator
appCoordinator.start()
appCoordinator.step = .showFirstViewController
appCoordinator.step = .showSecondViewController
return true
}
}Hotovo. Práve sme využili Coordinator Pattern s použitím combinu. Koordinátor sa dá veľmi ľahko škálovať a tak je vhodný skoro všade.
Poďme sa pozrieť, ako to vyzerá pri spájaní dvoch koordinátorov. Vytvorme si koordinátor AboutAppCoordinator.swift
//
// AboutAppCoordinator.swift
// CoordinatorArticle
//
// Created by Andrej Jasso on 27/09/2021.
//
import UIKit
// MARK: - Steps
final class AboutAppCoordinator: Coordinator<AppStep> {
// MARK: - Overrides
override func navigate(to step: AppStep) -> StepAction {
switch step {
default:
return .none
}
}
@discardableResult
override func start() -> UIViewController? {
super.start()
let controller = UIViewController()
let navigationController = UINavigationController()
navigationController.viewControllers = [controller]
controller.view.backgroundColor = .green
return navigationController
}
}Tento koordinátor teraz prezentujeme cez AppCoordinator step tak, že nahradíme obsah logiky druhého kroku
case .showSecondViewController(let animated):
let secondViewController = AboutAppCoordinator().start()
guard let secondViewController = secondViewController else {
return .none
}
return .present(secondViewController, .automatic)Hotovo. Úspešne sme prešli procesom koordinátorov. Pomocou tohto návodu by si mal vedieť vytvoriť funkčnú navigáciu projektu cez koordinátori.
Hotový sample nájdeš na mojom GitHube.
Knižnicu GoodIOSExtensions aj s GoodCoordinatorom najdete na GoodRequest GitHube.






