Applikasjonsutvikling på SwiftUI. Del 1: Dataflyt og Redux

Applikasjonsutvikling på SwiftUI. Del 1: Dataflyt og Redux

Etter å ha deltatt på State of the Union-sesjonen på WWDC 2019, bestemte jeg meg for å ta et dypdykk i SwiftUI. Jeg har brukt mye tid på å jobbe med det og har nå begynt å utvikle en ekte applikasjon som kan være nyttig for et bredt spekter av brukere.

Jeg kalte det MovieSwiftUI - dette er en app for å søke etter nye og gamle filmer, samt samle dem i en samling ved hjelp av TMDB API. Jeg har alltid elsket filmer og til og med opprettet et selskap som jobber på dette feltet, men for lenge siden. Selskapet kunne knapt kalles kult, men søknaden var det!

Vi minner om: for alle lesere av "Habr" - en rabatt på 10 000 rubler når du melder deg på et hvilket som helst Skillbox-kurs ved å bruke kampanjekoden "Habr".

Skillbox anbefaler: Pedagogisk nettkurs "Profession Java-utvikler".

Så hva kan MovieSwiftUI gjøre?

  • Samhandler med API - nesten alle moderne applikasjoner gjør dette.
  • Laster inn asynkrone data på forespørsler og analyserer JSON inn i Swift-modellen ved hjelp av Kodbar.
  • Viser bilder lastet på forespørsel og cacher dem.
  • Denne appen for iOS, iPadOS og macOS gir den beste brukeropplevelsen for brukere av disse operativsystemene.
  • Brukeren kan generere data og lage sine egne filmlister. Applikasjonen lagrer og gjenoppretter brukerdata.
  • Visninger, komponenter og modeller er tydelig atskilt ved hjelp av Redux-mønsteret. Dataflyten her er ensrettet. Den kan hurtigbufres, gjenopprettes og overskrives.
  • Applikasjonen bruker de grunnleggende komponentene til SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal, etc. Det gir også tilpassede visninger, bevegelser, UI/UX.

Applikasjonsutvikling på SwiftUI. Del 1: Dataflyt og Redux
Faktisk er animasjonen jevn, GIF-en ble litt rykkete

Arbeidet med appen ga meg mye erfaring og totalt sett var det en positiv opplevelse. Jeg var i stand til å skrive en fullt funksjonell applikasjon, i september vil jeg forbedre den og publisere den i AppStore, samtidig med utgivelsen av iOS 13.

Redux, BindableObject og EnvironmentObject

Applikasjonsutvikling på SwiftUI. Del 1: Dataflyt og Redux

Jeg har jobbet med Redux i omtrent to år nå, så jeg er relativt godt kjent med det. Spesielt bruker jeg den i frontend for Reager nettsted, samt for å utvikle native iOS (Swift) og Android (Kotlin) applikasjoner.

Jeg har aldri angret på at jeg valgte Redux som dataflytarkitektur for å bygge en SwiftUI-applikasjon. De mest utfordrende delene når du bruker Redux i en UIKit-app er å jobbe med butikken og hente og hente data og kartlegge det til dine visninger/komponenter. For å gjøre dette måtte jeg lage et slags bibliotek med koblinger (ved å bruke ReSwift og ReKotlin). Fungerer bra, men ganske mye kode. Dessverre er det (ennå) ikke åpen kildekode.

Gode ​​nyheter! De eneste tingene å bekymre seg for med SwiftUI - hvis du planlegger å bruke Redux - er butikker, stater og reduksjonsmidler. Interaksjon med butikken er fullstendig ivaretatt av SwiftUI takket være @EnvironmentObject. Så, butikken starter med et BindableObject.

Jeg laget en enkel Swift-pakke, SwiftUIFlux, som gir grunnleggende bruk av Redux. I mitt tilfelle er det en del av MovieSwiftUI. jeg også skrev en trinn-for-trinn-veiledning, som vil hjelpe deg med å bruke denne komponenten.

Hvordan virker det?

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

Hver gang du utløser en handling, aktiverer du girkassen. Den vil evaluere handlinger i henhold til gjeldende status for søknaden. Den vil da returnere en ny modifisert tilstand i samsvar med handlingstypen og dataene.

Vel, siden butikken er et BindableObject, vil den varsle SwiftUI når verdien endres ved å bruke egenskapen willChange levert av PassthroughSubject. Dette er fordi BindableObject må gi en PublisherType, men protokollimplementeringen er ansvarlig for å administrere den. Totalt sett er dette et veldig kraftig verktøy fra Apple. Følgelig vil SwiftUI i neste gjengivelsessyklus hjelpe til med å gjengi hoveddelen av visningene i henhold til tilstandsendringen.

Faktisk er dette hele hjertet og magien til SwiftUI. Nå, i enhver visning som abonnerer på en stat, vil visningen bli gjengitt i henhold til hvilke data som mottas fra staten og hva som er endret.

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

Butikken injiseres som et EnvironmentObject når applikasjonen starter og er deretter tilgjengelig i alle visninger ved å bruke @EnvironmentObject. Det er ingen ytelsesstraff fordi avledede egenskaper raskt hentes eller beregnes fra applikasjonstilstand.

Koden ovenfor endrer bildet hvis filmplakaten endres.

Og dette gjøres faktisk med bare én linje, ved hjelp av hvilke synspunkter er knyttet til staten. Hvis du har jobbet med ReSwift på iOS eller til og med koble med React vil du forstå magien til SwiftUI.

Nå kan du prøve å aktivere handlingen og publisere den nye tilstanden. Her er et mer komplekst eksempel.

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

I koden ovenfor bruker jeg .onDelete-handlingen fra SwiftUI for hver IP. Dette lar raden i listen vise den vanlige iOS-sveipen for å slette. Så når brukeren trykker på sletteknappen, utløser den den tilsvarende handlingen og fjerner filmen fra listen.

Vel, siden listeegenskapen er avledet fra BindableObject-tilstanden og injiseres som et EnvironmentObject, oppdaterer SwiftUI listen fordi ForEach er knyttet til filmens beregnede egenskap.

Her er en del av MoviesState-reduseringen:

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
}

Reduksjonen utføres når du sender en handling og returnerer en ny tilstand, som nevnt ovenfor.

Jeg vil ikke gå i detalj ennå - hvordan SwiftUI faktisk vet hva som skal vises. For å forstå dette dypere, er det verdt se WWDC-sesjonen om dataflyt i SwiftUI. Den forklarer også i detalj hvorfor og når den skal brukes Tilstand, @Binding, ObjectBinding og EnvironmentObject.

Skillbox anbefaler:

Kilde: www.habr.com

Legg til en kommentar