Vývoj aplikace na SwiftUI. Část 1: tok dat a Redux

Vývoj aplikace na SwiftUI. Část 1: tok dat a Redux

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í TMDB API. 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 „Profese Java vývojář“.

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 Codable.
  • 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.

Vývoj aplikace na SwiftUI. Část 1: tok dat a Redux
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ývoj aplikace na SwiftUI. Část 1: tok dat a Redux

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 React 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, SwiftUIFlux, který zajišťuje základní použití Redux. V mém případě je to součást MovieSwiftUI. Také jsem napsal podrobný tutoriál, 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 connect 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 prozkoumat sezení WWDC o toku dat ve SwiftUI. Také se tam podrobně vysvětluje, proč a kdy používat Stav, @Binding, ObjectBinding a EnvironmentObject.

Skillbox doporučuje:

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster