Rakenduse arendus SwiftUI-s. Osa 1: andmevoog ja Redux

Rakenduse arendus SwiftUI-s. Osa 1: andmevoog ja Redux

Pärast osalemist WWDC 2019 State of the Union sessioonil otsustasin süveneda SwiftUI-sse. Olen sellele palju aega pühendanud ja nüüd olen alustanud reaalse rakenduse arendamist, mis võib olla kasulik laiemale kasutajaskonnale.

Nimelt MovieSwiftUI — see on rakendus uute ja vanade filmide otsimiseks ning nende kogumise kogumiks, kasutades TMDB API. Olen alati armastanud filme ja isegi loonud ettevõtte, mis selles valdkonnas tegutseb, kuigi see oli juba ammu. Ettevõtet oli raske nimetada ägedaks, aga rakendust küll!

Tuletame meelde: kõigile «Habra» lugejatele — 10 000 rubla soodustus, kui registreerite end Skillboxi mis tahes kursusele promokoodi «Habr» abil.

Skillbox soovitab: Hariduslik veebikursus „Java-arendaja amet“.

Nii et, mida MovieSwiftUI oskab?

  • Interakteerub API-ga — see on midagi, mida peaaegu iga kaasaegne rakendus teeb.
  • Laeb asünkroonselt andmeid päringute kaudu ja parsib JSON Swift mudelitesse, kasutades Codable.
  • Kuvab päringu kaudu laaditud pilte ja salvestab neid vahemällu.
  • See iOS, iPadOS ja macOS rakendus tagab parima kasutajakogemuse nende opsüsteemide kasutajatele.
  • Kasutaja saab genereerida andmeid, luua oma filmi nimekirju. Rakendus salvestab ja taastab kasutaja andmed.
  • Esitlused, komponendid ja mudelid on selgelt eraldatud Reduxi mustrit kasutades. Andmevoog on siin ühesuunaline. Seda saab täielikult vahemällu salvestada, taastada ja kirjutada ümber.
  • Rakendus kasutab põhikomponente SwiftUI-st, nagu TabbedView, SegmentedControl, NavigationView, Form, Modal jne. Samuti pakub see kohandatud esitusi, žeste, UI/UX-i.

Rakenduse arendus SwiftUI-s. Osa 1: andmevoog ja Redux
Tegelikult on animatsioon sujuv, kuid gif tuli natuke kangeks.

Rakenduse arendamine andis mulle palju kogemusi ja üldiselt on see olnud positiivne. Suutsin kirjutada täielikult funktsionaalse rakenduse, mille parandan septembris ja avaldan AppStore'is koos iOS 13 ilmumisega.

Redux, BindableObject ja EnvironmentObject

Rakenduse arendus SwiftUI-s. Osa 1: andmevoog ja Redux

Praeguseks olen Reduxiga töötanud juba umbes kaks aastat, nii et ma olen selles suhteliselt hästi kursis. Eelkõige kasutan seda frontendis veebisaidi jaoks React , samuti natiivsete iOS (Swift) ja Android (Kotlin) rakenduste arendamiseks.

Ma ei ole kunagi kahetsenud Reduxi valimist andmevoo arhitektuuriks SwiftUI rakenduse loomisel. Kõige keerulisemad aspektid Reduxi kasutamisel UIKit rakenduses on töö tegemine store'iga, andmete saamine ja ekstraheerimine ning nende vastavusse viimine teie vaadete/komponentidega. Selleks pidin looma midagi sellist nagu konnektorite teegi (ReSwift ja ReKotlin põhjal). Töötab hästi, kuid koodi on üsna palju. Kahjuks ei ole see (praegu) avatud lähtekoodiga.

Head uudised! Ainus, millest tasub SwiftUI puhul muretseda – kui kavatsete kasutada Reduxi – on store, olekud ja reducereid. Suhtlemise store'iga võtab täielikult enda kanda SwiftUI tänu @EnvironmentObject'ile. Nii et store algab BindableObject'ist.

Loomulikult olen loonud lihtsa Swift paketi, SwiftUIFlux, mis tagab Reduxi põhikasutuse. Minu puhul on see osa MovieSwiftUI-st. Samuti olen kirjutanud samm-sammult juhendi, mis aitab selle komponendi kasutamisel.

Kuidas see töötab?

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

Iga kord, kui käivitate toimingu, aktiveerite reduktori. See hindab toimingut vastavalt rakenduse praegusele olekule. Seejärel tagastab see uue muudetud oleku vastavalt toimingu tüübile ja andmetele.

Kuna store on BindableObject, teavitab see SwiftUI oma väärtuse muutumisest, kasutades PassthroughSubject'i pakutavat willChange omadust. See on nii, sest BindableObject peab pakkuma PublisherType'i, kuid protokolli rakendamine vastutab selle haldamise eest. Üldiselt on see Apple'i poolt väga võimas tööriist. Seetõttu aitab SwiftUI järgmisel renderdamise tsüklil kuvada vaate keha vastavalt oleku muutusele.

Oactually, see on kõik — SwiftUI süda ja maagia. Nüüd igas vaates, mis on tellitud olekule, kuvatakse vaade vastavalt sellele, millised andmed on saadud olekust ja mis on muutunud.

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 tuuakse sisse kui EnvironmentObject rakenduse käivitamisel, ja see on seejärel saadaval igas vaates @EnvironmentObject abil. Tulemuslikkus ei lange, kuna tuletatud omadused tõmmatakse kiiresti välja või arvutatakse rakenduse olekust.

Ülaltoodud kood muudab pilti, kui filmi plakat muutub.

Ja seda tehakse tegelikult vaid ühe rea abil, millega ühendatakse vaated olekuga. Kui olete töötanud ReSwiftiga iOS-is või isegi ühenda Reactiga, siis mõistate, mis on SwiftUI võlu.

Nüüd saate proovida tegevust aktiveerida ja avaldada uut staatust. Siin on keerulisem näide.

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

Ülaltoodud koodis kasutan SwiftUI jaoks .onDelete toimet iga IP jaoks. See võimaldab loendi rida kuvada tavalist iOS-i pühkimist kustutamiseks. Seega, kui kasutaja puudutab kustutamisnuppu, käivitab see vastava toimingu ja eemaldab filmi loendist.

Kuna loendi omadus on tütar BindableObject seisundist ja süstitakse kui EnvironmentObject, uuendab SwiftUI loendi, kuna ForEach on seotud arvutatud omadusega movies.

Siin on osa reducerist MoviesState:

func moviesStateReducer(state: MoviesState, action: Action) -> MoviesState {
    var state = state
    switch action {
    
    // muud toimingud.
    
    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
}

Reduceri teostatakse, kui saadate tegevuse ja tagastate uue oleku, nagu ülalpool öeldud.

Ma ei hakka veel üksikasjadesse laskuma — kust SwiftUI tegelikult teab, mida kuvada. Selle sügavamaks mõistmiseks tasub vaadata WWDC sessiooni andmevoogude kohta SwiftUIS. Seal räägitakse ka põhjalikult, miks ja millal kasutada State, @Binding, ObjectBinding ja EnvironmentObject.

Skillbox soovitab:

Allikas: habr.com

Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster