
Po účasti na schůzi State of the Union na WWDC 2019 jsem se rozhodl podrobně prozkoumat SwiftUI. Strávil jsem spoustu času prací s ním a nyní jsem zahájil vývoj skutečné aplikace, která by mohla být užitečná širokému okruhu uživatelů.
Nazval jsem ji MovieSwiftUI — je to aplikace pro vyhledávání nových a starých filmů, stejně jako pro shromažďování do kolekcí pomocí . Vždy jsem miloval filmy a dokonce jsem založil společnost, která pracovala v tomto oboru, ovšem před delší dobou. Společnost se těžko nazývala skvělou, ale aplikace — to ano!
Připomínáme: pro všechny čtenáře "Habra" – sleva 10 000 korun při registraci na jakýkoli kurz Skillbox s promokódem "Habr".
Skillbox doporučuje: Vzdělávací online kurz .
Co tedy MovieSwiftUI umí?
- Interaguje s API — to dělá téměř každá moderní aplikace.
- Načítá asynchronní data na základě požadavků a parsuje JSON do Swift modelů s použitím .
- Zobrazuje obrázky, které jsou načítány na základě požadavků, a ukládá je do mezipaměti.
- Tato aplikace pro iOS, iPadOS a macOS zajišťuje nejlepší UX pro uživatele těchto operačních systémů.
- Uživatel může generovat data a vytvářet vlastní seznamy filmů. Aplikace ukládá a obnovuje uživatelská data.
- Zobrazovací prvky, komponenty a modely jsou jasně oddělené pomocí vzoru Redux. Tok dat je zde jednosměrný. Může být plně uložen do mezipaměti, obnoven a přepsán.
- Aplikace používá základní komponenty SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal atd. Také poskytuje vlastní zobrazení, gesta, UI/UX.

Ve skutečnosti je animace plynulá, ale GIF vyšel trochu trhaně.
Práce na aplikaci mi poskytla mnoho zkušeností, a celkově to byla pozitivní zkušenost. Dokázal jsem napsat plně funkční aplikaci, v září ji vylepším a zveřejním na AppStore, současně s vydáním iOS 13.
Redux, BindableObject a EnvironmentObject

V současné době pracuji s Reduxem už asi dva roky, takže se v něm docela dobře orientuji. Konkrétně ho používám na frontendu pro webovou stránku, a také pro vývoj nativních iOS (Swift) a Android (Kotlin) aplikací.
Nikdy jsem nelitoval volby Redux jako architektury toku dat pro vytváření aplikace ve SwiftUI. Nejtěžší momenty při použití Redux v aplikaci UIKit jsou práce se storem, stejně jako získávání a extrakce dat a jejich porovnávání s vašimi pohledy / komponenty. K tomu jsem musel vytvořit jakousi knihovnu konektorů (na ReSwift a ReKotlin). Funguje to dobře, ale je to docela dost kódu. Bohužel, zatím není open source.
Dobré zprávy! Jediná věc, na kterou si musíte dávat pozor se SwiftUI — pokud plánujete použít Redux — je store, stavy a reducery. Interakci se storem plně přebírá SwiftUI díky @EnvironmentObject. Takže, store začíná s BindableObject.
Vytvořil jsem jednoduchý balíček Swift, , který zajišťuje základní použití Redux. V mém případě je to součást MovieSwiftUI. Také jsem , který pomůže použít tuto komponentu.
Jak to funguje?
final public class Store: BindableObject {
public let willChange = PassthroughSubject()
private(set) public var state: State
private func _dispatch(action: Action) {
willChange.send()
state = reducer(state, action)
}
}Každýkrát, když spustíte akci, aktivujete reducer. Ten vyhodnotí akce podle aktuálního stavu aplikace. Následně vrátí nový modifikovaný stav podle typu akce a dat.
Jelikož je store BindableObject, bude upozorňovat SwiftUI na změnu své hodnoty pomocí vlastnosti willChange, kterou poskytuje PassthroughSubject. Je to tak, protože BindableObject musí poskytovat PublisherType, ale implementace protokolu je zodpovědná za jeho správu. Celkově je to velmi mocný nástroj od Apple. Tím pádem, v dalším vykreslovacím cyklu SwiftUI pomůže zobrazit tělo pohledů podle změny stavu.
To je v podstatě vše — srdce a magie SwiftUI. Nyní v jakémkoli pohledu, který je přihlášen k stavu, se pohled zobrazí podle toho, jaká data jsou získána ze stavu a co se změnilo.
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
let controller = UIHostingController(rootView: HomeView().environmentObject(store))
window.rootViewController = controller
self.window = window
window.makeKeyAndVisible()
}
}
}
struct CustomListCoverRow : View {
@EnvironmentObject var store: Store
let movieId: Int
var movie: Movie! {
return store.state.moviesState.movies[movieId]
}
var body: some View {
HStack(alignment: .center, spacing: 0) {
Image(movie.poster)
}.listRowInsets(EdgeInsets())
}
}Store se zavádí jako EnvironmentObject při spuštění aplikace a je pak dostupný v jakémkoli zobrazení pomocí @EnvironmentObject. Výkon se nezhoršuje, protože odvozené vlastnosti jsou rychle získány nebo vypočítány ze stavu aplikace.
Kód, který je uveden výše, mění obrázek, pokud se změní plakát k filmu.
A to se ve skutečnosti provádí pouhou jednou řádkou, která spojuje zobrazení se stavem. Pokud jste pracovali s ReSwift na iOS nebo dokonce s React, pochopíte, v čem je magie SwiftUI.
Nyní můžete zkusit aktivovat akci a zveřejnit nový stav. Zde je složitější příklad.
struct CustomListDetail : View {
@EnvironmentObject var store: Store
let listId: Int
var list: CustomList {
store.state.moviesState.customLists[listId]!
}
var movies: [Int] {
list.movies.sortedMoviesIds(by: .byReleaseDate, state: store.state)
}
var body: some View {
List {
ForEach(movies) { movie in
NavigationLink(destination: MovieDetail(movieId: movie).environmentObject(self.store)) {
MovieRow(movieId: movie, displayListImage: false)
}
}.onDelete { (index) in
self.store.dispatch(action: MoviesActions.RemoveMovieFromCustomList(list: self.listId, movie: self.movies[index.first!]))
}
}
}
}V kódu výše používám akci .onDelete ze SwiftUI pro každé IP. To umožňuje řádku v seznamu zobrazit běžné přejetí prstem iOS pro smazání. Takže když uživatel stiskne tlačítko pro odstranění, spustí se odpovídající akce a film se odstraní ze seznamu.
Jelikož je vlastnost seznamu odvozená od stavu BindableObject a zaváděna jako EnvironmentObject, SwiftUI seznam aktualizuje, protože ForEach je spojen s vypočtenou vlastností movies.
Toto je část reduktoru MoviesState:
func moviesStateReducer(state: MoviesState, action: Action) -> MoviesState {
var state = state
switch action {
// další akce.
case let action as MoviesActions.AddMovieToCustomList:
state.customLists[action.list]?.movies.append(action.movie)
case let action as MoviesActions.RemoveMovieFromCustomList:
state.customLists[action.list]?.movies.removeAll{ $0 == action.movie }
default:
break
}
return state
}Reduktor se provádí, když odešlete akci a vrátíte nový stav, jak bylo uvedeno výše.
Zatím se nebudu zabývat podrobnostmi – odkud přesně SwiftUI ví, co zobrazit. Abyste to pochopili hlouběji, měli byste ve SwiftUI. Také se tam podrobně vysvětluje, proč a kdy používat , @Binding, ObjectBinding a EnvironmentObject.
Skillbox doporučuje:
- Praktický kurz .
- Aplikovaný online kurz .
- Dvouroční praktický kurz .
Zdroj: habr.com
