Vývoj aplikácií na SwiftUI. Časť 1: Dátový tok a redukcia

Vývoj aplikácií na SwiftUI. Časť 1: Dátový tok a redukcia

Po účasti na stretnutí o stave únie na WWDC 2019 som sa rozhodol ponoriť sa do SwiftUI. Strávil som s ním veľa času a teraz som začal vyvíjať skutočnú aplikáciu, ktorá môže byť užitočná pre široké spektrum používateľov.

Nazval som to MovieSwiftUI – toto je aplikácia na vyhľadávanie nových a starých filmov, ako aj ich zhromažďovanie do zbierky pomocou TMDB API. Vždy som miloval filmy a dokonca som vytvoril spoločnosť pracujúcu v tejto oblasti, hoci už dávno. Spoločnosť sa sotva dala nazvať cool, ale aplikácia bola!

Pripomíname vám: pre všetkých čitateľov „Habr“ - zľava 10 000 rubľov pri registrácii do akéhokoľvek kurzu Skillbox pomocou propagačného kódu „Habr“.

Skillbox odporúča: Vzdelávací online kurz "Profesia Java Developer".

Čo teda dokáže MovieSwiftUI robiť?

  • Interaguje s API – robí to takmer každá moderná aplikácia.
  • Načíta asynchrónne údaje o požiadavkách a analyzuje JSON do modelu Swift pomocou Kódovateľné.
  • Zobrazuje obrázky načítané na požiadanie a ukladá ich do vyrovnávacej pamäte.
  • Táto aplikácia pre iOS, iPadOS a macOS poskytuje používateľom týchto OS to najlepšie UX.
  • Používateľ môže generovať údaje a vytvárať vlastné zoznamy filmov. Aplikácia ukladá a obnovuje používateľské dáta.
  • Pohľady, komponenty a modely sú jasne oddelené pomocou vzoru Redux. Dátový tok je tu jednosmerný. Dá sa úplne uložiť do vyrovnávacej pamäte, obnoviť a prepísať.
  • Aplikácia využíva základné komponenty SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal atď. Poskytuje tiež vlastné zobrazenia, gestá, UI/UX.

Vývoj aplikácií na SwiftUI. Časť 1: Dátový tok a redukcia
V skutočnosti je animácia plynulá, GIF sa ukázal byť trochu trhaný

Práca na aplikácii mi dala veľa skúseností a celkovo to bola pozitívna skúsenosť. Podarilo sa mi napísať plne funkčnú aplikáciu, v septembri ju vylepším a zverejním v AppStore, súčasne s vydaním iOS 13.

Redux, BindableObject a EnvironmentObject

Vývoj aplikácií na SwiftUI. Časť 1: Dátový tok a redukcia

S Reduxom pracujem už asi dva roky, takže sa v ňom pomerne dobre orientujem. Najmä ho používam vo frontende Reagovať webovej stránky, ako aj na vývoj natívnych aplikácií pre iOS (Swift) a Android (Kotlin).

Nikdy som neľutoval, že som si vybral Redux ako architektúru toku údajov na vytvorenie aplikácie SwiftUI. Najnáročnejšie časti pri používaní Redux v aplikácii UIKit sú práca s obchodom a získavanie a získavanie údajov a ich mapovanie na vaše zobrazenia/komponenty. Aby som to mohol urobiť, musel som vytvoriť akúsi knižnicu konektorov (pomocou ReSwift a ReKotlin). Funguje dobre, ale má veľa kódu. Bohužiaľ to (zatiaľ) nie je open source.

Dobré správy! Jediné, čoho sa treba so SwiftUI obávať – ak plánujete používať Redux – sú obchody, stavy a redukcie. O interakciu s obchodom sa kompletne stará SwiftUI vďaka @EnvironmentObject. Obchod teda začína BindableObject.

Vytvoril som jednoduchý balík Swift, SwiftUIFlux, ktorý poskytuje základné využitie Reduxu. V mojom prípade je to súčasť MovieSwiftUI. ja tiež napísal návod krok za krokom, ktorý vám pomôže používať tento komponent.

Ako to funguje?

final public class Store<State: FluxState>: BindableObject {
    public let willChange = PassthroughSubject<Void, Never>()
        
    private(set) public var state: State
    
    private func _dispatch(action: Action) {
        willChange.send()
        state = reducer(state, action)
    }
}

Zakaždým, keď spustíte akciu, aktivujete prevodovku. Bude vyhodnocovať akcie podľa aktuálneho stavu aplikácie. Potom vráti nový upravený stav v súlade s typom akcie a údajmi.

No, keďže obchod je BindableObject, upozorní SwiftUI, keď sa jeho hodnota zmení, pomocou vlastnosti willChange poskytovanej PassthroughSubject. Je to preto, že BindableObject musí poskytovať PublisherType, ale za jeho správu je zodpovedná implementácia protokolu. Celkovo ide o veľmi výkonný nástroj od Apple. Preto v nasledujúcom cykle vykresľovania SwiftUI pomôže vykresliť telo pohľadov podľa zmeny stavu.

V skutočnosti je to celé srdce a kúzlo SwiftUI. Teraz, v každom zobrazení, ktoré sa prihlási k štátu, sa zobrazenie vykreslí podľa toho, aké údaje sú prijaté od štátu a čo sa zmenilo.

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<AppState>
    
    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())
    }
}

Obchod sa vloží ako EnvironmentObject pri spustení aplikácie a potom je prístupný v akomkoľvek zobrazení pomocou @EnvironmentObject. Neexistuje žiadna penalizácia výkonu, pretože odvodené vlastnosti sa rýchlo získajú alebo vypočítajú zo stavu aplikácie.

Vyššie uvedený kód zmení obrázok, ak sa zmení filmový plagát.

A to sa robí vlastne len jednou líniou, pomocou ktorej sú pohľady napojené na štát. Ak ste pracovali s ReSwift na iOS alebo dokonca pripojiť s Reactom pochopíte kúzlo SwiftUI.

Teraz môžete skúsiť aktivovať akciu a zverejniť nový stav. Tu je zložitejší príklad.

struct CustomListDetail : View {
    @EnvironmentObject var store: Store<AppState>
 
    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óde vyššie používam pre každú IP akciu .onDelete zo SwiftUI. To umožňuje riadku v zozname zobraziť normálne potiahnutie prstom v systéme iOS, ktoré chcete odstrániť. Keď sa teda používateľ dotkne tlačidla Odstrániť, spustí zodpovedajúcu akciu a odstráni film zo zoznamu.

Keďže vlastnosť zoznamu je odvodená od stavu BindableObject a je vložená ako EnvironmentObject, SwiftUI aktualizuje zoznam, pretože ForEach je priradený k vypočítanej vlastnosti filmov.

Tu je časť redukcie MoviesState:

func moviesStateReducer(state: MoviesState, action: Action) -> MoviesState {
    var state = state
    switch action {
    
    // other actions.
    
    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 sa vykoná, keď odošlete akciu a vrátite nový stav, ako je uvedené vyššie.

Zatiaľ sa nebudem rozpisovať – ako vlastne SwiftUI vie, čo má zobraziť. Aby sme to pochopili hlbšie, stojí za to zobraziť reláciu WWDC o toku údajov v SwiftUI. Tiež podrobne vysvetľuje, prečo a kedy použiť stáť, @Binding, ObjectBinding a EnvironmentObject.

Skillbox odporúča:

Zdroj: hab.com

Pridať komentár