Razvoj aplikacij na SwiftUI. 1. del: Dataflow in Redux

Razvoj aplikacij na SwiftUI. 1. del: Dataflow in Redux

Po obisku zasedanja o stanju unije na WWDC 2019 sem se odločil, da se poglobljeno poglobim v SwiftUI. Veliko časa sem delal z njim in zdaj sem začel razvijati pravo aplikacijo, ki je lahko uporabna širokemu krogu uporabnikov.

Poimenoval sem ga MovieSwiftUI - to je aplikacija za iskanje novih in starih filmov ter njihovo zbiranje v zbirki z TMDB API. Vedno sem imel rad filme in sem celo ustvaril podjetje, ki se ukvarja s tem področjem, čeprav že dolgo nazaj. Težko bi rekli, da je podjetje kul, aplikacija pa je!

Spomnimo: za vse bralce "Habr" - popust v višini 10 rubljev ob vpisu v kateri koli tečaj Skillbox s promocijsko kodo "Habr".

Skillbox priporoča: Izobraževalni spletni tečaj "Poklic Java razvijalec".

Kaj torej lahko stori MovieSwiftUI?

  • Interakcija z API-jem – to počne skoraj vsaka sodobna aplikacija.
  • Naloži asinhrone podatke o zahtevah in razčleni JSON v model Swift z uporabo Možnost kodiranja.
  • Prikaže slike, naložene na zahtevo, in jih shrani v predpomnilnik.
  • Ta aplikacija za iOS, iPadOS in macOS zagotavlja najboljši UX za uporabnike teh operacijskih sistemov.
  • Uporabnik lahko ustvari podatke in ustvari lastne sezname filmov. Aplikacija shrani in obnovi uporabniške podatke.
  • Pogledi, komponente in modeli so jasno ločeni z vzorcem Redux. Pretok podatkov je tukaj enosmeren. Lahko se v celoti shrani v predpomnilnik, obnovi in ​​prepiše.
  • Aplikacija uporablja osnovne komponente SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal itd. Ponuja tudi poglede po meri, kretnje, UI/UX.

Razvoj aplikacij na SwiftUI. 1. del: Dataflow in Redux
Pravzaprav je animacija gladka, GIF je izpadel nekoliko sunkovit

Delo na aplikaciji mi je dalo veliko izkušenj in na splošno je bila to pozitivna izkušnja. Uspelo mi je napisati popolnoma delujočo aplikacijo, septembra jo bom izboljšal in objavil v AppStore, hkrati z izdajo iOS 13.

Redux, BindableObject in EnvironmentObject

Razvoj aplikacij na SwiftUI. 1. del: Dataflow in Redux

Z Reduxom delam že približno dve leti, tako da sem relativno dobro seznanjen z njim. Še posebej ga uporabljam v sprednjem delu za Reagirajo spletne strani, kot tudi za razvoj izvornih aplikacij za iOS (Swift) in Android (Kotlin).

Nikoli mi ni bilo žal, da sem izbral Redux kot arhitekturo pretoka podatkov za izdelavo aplikacije SwiftUI. Najzahtevnejši deli pri uporabi Reduxa v aplikaciji UIKit so delo s trgovino ter pridobivanje in pridobivanje podatkov ter njihovo preslikavo v vaše poglede/komponente. Za to sem moral ustvariti nekakšno knjižnico konektorjev (z uporabo ReSwift in ReKotlin). Deluje dobro, vendar precej kode. Na žalost (še) ni odprtokoden.

Dobre novice! Edine stvari, o katerih morate skrbeti pri SwiftUI – če nameravate uporabljati Redux – so trgovine, stanja in reduktorji. Za interakcijo s trgovino v celoti skrbi SwiftUI zahvaljujoč @EnvironmentObject. Torej se trgovina začne z BindableObject.

Ustvaril sem preprost paket Swift, SwiftUIFlux, ki zagotavlja osnovno uporabo Reduxa. V mojem primeru je del MovieSwiftUI. tudi jaz napisal vadnico po korakih, ki vam bo pomagal pri uporabi te komponente.

Kako deluje?

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

Vsakič, ko sprožite akcijo, aktivirate menjalnik. Ocenil bo dejanja glede na trenutno stanje aplikacije. Nato bo vrnil novo spremenjeno stanje v skladu z vrsto dejanja in podatki.

No, ker je trgovina BindableObject, bo SwiftUI obvestila, ko se njena vrednost spremeni z uporabo lastnosti willChange, ki jo zagotavlja PassthroughSubject. To je zato, ker mora BindableObject zagotoviti PublisherType, vendar je implementacija protokola odgovorna za njegovo upravljanje. Na splošno je to zelo zmogljivo Applovo orodje. V skladu s tem bo SwiftUI v naslednjem ciklu upodabljanja pomagal upodobiti telo pogledov glede na spremembo stanja.

Pravzaprav je to srce in čar SwiftUI. Zdaj bo v vsakem pogledu, ki je naročen na stanje, pogled upodobljen glede na to, kateri podatki so prejeti iz stanja in kaj se je spremenilo.

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

Trgovina je vstavljena kot EnvironmentObject, ko se aplikacija zažene, nato pa je dostopna v katerem koli pogledu z @EnvironmentObject. Ni kazni za zmogljivost, ker se izpeljane lastnosti hitro pridobijo ali izračunajo iz stanja aplikacije.

Zgornja koda spremeni sliko, če se spremeni filmski plakat.

In to pravzaprav z eno linijo, s pomočjo katere se pogledi povezujejo z državo. Če ste delali z ReSwiftom v sistemu iOS ali celo povezati z Reactom boste razumeli čar SwiftUI.

Zdaj lahko poskusite aktivirati dejanje in objaviti novo stanje. Tukaj je bolj zapleten primer.

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 zgornji kodi uporabljam dejanje .onDelete iz SwiftUI za vsak IP. To omogoča, da vrstica na seznamu prikaže običajno potezo iOS za brisanje. Torej, ko se uporabnik dotakne gumba za brisanje, sproži ustrezno dejanje in odstrani film s seznama.

No, ker lastnost seznama izhaja iz stanja BindableObject in je vstavljena kot EnvironmentObject, SwiftUI posodobi seznam, ker je ForEach povezan z izračunano lastnostjo filmov.

Tukaj je del reduktorja 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 se izvede, ko odpošljete dejanje in vrnete novo stanje, kot je navedeno zgoraj.

Ne bom se še spuščal v podrobnosti – kako SwiftUI dejansko ve, kaj prikazati. Da bi to razumeli globlje, je vredno ogled seje WWDC o pretoku podatkov v SwiftUI. Prav tako podrobno pojasnjuje, zakaj in kdaj uporabiti Država, @Binding, ObjectBinding in EnvironmentObject.

Skillbox priporoča:

Vir: www.habr.com

Dodaj komentar