Razvoj aplikacija na SwiftUI. Dio 1: Dataflow i Redux

Razvoj aplikacija na SwiftUI. Dio 1: Dataflow i Redux

Nakon što sam prisustvovao sjednici o stanju unije na WWDC 2019., odlučio sam duboko zaroniti u SwiftUI. Proveo sam dosta vremena radeći s njim i sada sam počeo razvijati pravu aplikaciju koja može biti korisna širokom krugu korisnika.

Nazvao sam to MovieSwiftUI - ovo je aplikacija za traženje novih i starih filmova, kao i njihovo prikupljanje u kolekciju pomoću TMDB API. Oduvijek sam volio filmove i čak sam stvorio tvrtku koja se bavi tim područjem, iako davno. Tvrtka se teško mogla nazvati cool, ali aplikacija jest!

Podsjećamo: za sve čitatelje "Habra" - popust od 10 000 rubalja pri upisu na bilo koji tečaj Skillbox koristeći promotivni kod "Habr".

Skillbox preporučuje: Edukativni online tečaj "Profesija Java developer".

Dakle, što MovieSwiftUI može učiniti?

  • Interakcija s API-jem - to radi gotovo svaka moderna aplikacija.
  • Učitava asinkrone podatke o zahtjevima i analizira JSON u Swift model pomoću Može se kodirati.
  • Prikazuje slike učitane na zahtjev i sprema ih u predmemoriju.
  • Ova aplikacija za iOS, iPadOS i macOS pruža najbolji korisnički doživljaj za korisnike ovih operativnih sustava.
  • Korisnik može generirati podatke i kreirati vlastite popise filmova. Aplikacija sprema i vraća korisničke podatke.
  • Pogledi, komponente i modeli jasno su odvojeni Redux uzorkom. Protok podataka ovdje je jednosmjeran. Može se u potpunosti spremiti u predmemoriju, vratiti i prebrisati.
  • Aplikacija koristi osnovne komponente SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal itd. Također pruža prilagođene prikaze, geste, UI/UX.

Razvoj aplikacija na SwiftUI. Dio 1: Dataflow i Redux
Zapravo, animacija je glatka, GIF je ispao malo trzav

Rad na aplikaciji dao mi je puno iskustva i sveukupno je to bilo pozitivno iskustvo. Uspio sam napisati potpuno funkcionalnu aplikaciju, u rujnu ću je unaprijediti i objaviti u AppStoreu, istovremeno s izdanjem iOS-a 13.

Redux, BindableObject i EnvironmentObject

Razvoj aplikacija na SwiftUI. Dio 1: Dataflow i Redux

S Reduxom radim već oko dvije godine, tako da sam relativno dobro upućen u njega. Konkretno, koristim ga u sučelju za Reagovati web stranice, kao i za razvoj izvornih iOS (Swift) i Android (Kotlin) aplikacija.

Nikada nisam požalio što sam odabrao Redux kao arhitekturu protoka podataka za izradu SwiftUI aplikacije. Najzahtjevniji dijelovi pri korištenju Reduxa u UIKit aplikaciji su rad s pohranom i dobivanje i dohvaćanje podataka i njihovo mapiranje u vaše prikaze/komponente. Da bih to učinio, morao sam stvoriti neku vrstu biblioteke konektora (koristeći ReSwift i ReKotlin). Radi dobro, ali ima dosta koda. Nažalost, (još) nije open source.

Dobre vijesti! Jedine stvari o kojima treba brinuti sa SwiftUI - ako planirate koristiti Redux - su trgovine, stanja i reduktori. Za interakciju s trgovinom u potpunosti se brine SwiftUI zahvaljujući @EnvironmentObject. Dakle, trgovina počinje s BindableObject.

Napravio sam jednostavan Swift paket, SwiftUIFlux, koji omogućuje osnovnu upotrebu Reduxa. U mom slučaju to je dio MovieSwiftUI. ja također napisao je poduku korak po korak, koji će vam pomoći u korištenju ove komponente.

Kako se to radi?

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

Svaki put kad pokrenete radnju, aktivirate mjenjač. Procijenit će radnje prema trenutnom stanju aplikacije. Zatim će vratiti novo modificirano stanje u skladu s vrstom akcije i podacima.

Pa, budući da je trgovina BindableObject, obavijestit će SwiftUI kada se njegova vrijednost promijeni pomoću svojstva willChange koje pruža PassthroughSubject. To je zato što BindableObject mora dati PublisherType, ali je implementacija protokola odgovorna za upravljanje njime. Sve u svemu, ovo je vrlo moćan Appleov alat. Sukladno tome, u sljedećem ciklusu renderiranja, SwiftUI će pomoći u renderiranju tijela prikaza prema promjeni stanja.

Zapravo, ovo je srce i magija SwiftUI. Sada, u svakom prikazu koji je pretplaćen na stanje, prikaz će se prikazati prema tome koji su podaci primljeni od stanja i što se promijenilo.

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

Store se ubacuje kao EnvironmentObject kada se aplikacija pokrene, a zatim je dostupan u bilo kojem prikazu pomoću @EnvironmentObject. Nema smanjenja performansi jer se izvedena svojstva brzo dohvaćaju ili izračunavaju iz stanja aplikacije.

Gornji kod mijenja sliku ako se promijeni poster filma.

A to se zapravo radi samo jednom linijom, uz pomoć koje se pogledi povezuju s državom. Ako ste radili s ReSwiftom na iOS-u ili čak povezati s Reactom ćete shvatiti magiju SwiftUI-ja.

Sada možete pokušati aktivirati akciju i objaviti novo stanje. Evo složenijeg primjera.

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

U gornjem kodu koristim radnju .onDelete iz SwiftUI za svaku IP adresu. To omogućuje da redak na popisu prikaže uobičajeni iOS prijelaz za brisanje. Dakle, kada korisnik dodirne gumb za brisanje, on pokreće odgovarajuću radnju i uklanja film s popisa.

Pa, budući da je svojstvo popisa izvedeno iz stanja BindableObject i umetnuto je kao EnvironmentObject, SwiftUI ažurira popis jer je ForEach povezan s izračunatim svojstvom filmova.

Ovdje je dio MoviesState reduktora:

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 izvršava kada pošaljete akciju i vratite novo stanje, kao što je gore navedeno.

Neću još ulaziti u detalje - kako SwiftUI zapravo zna što prikazati. Da bismo ovo dublje razumjeli, vrijedi pogledajte WWDC sesiju o protoku podataka u SwiftUI. Također detaljno objašnjava zašto i kada koristiti država, @Binding, ObjectBinding i EnvironmentObject.

Skillbox preporučuje:

Izvor: www.habr.com

Dodajte komentar