Rakenduste arendamine SwiftUI-s. 1. osa: andmevoog ja redux

Rakenduste arendamine SwiftUI-s. 1. osa: andmevoog ja redux

Pärast 2019. aasta WWDC liidu olukorra istungil osalemist otsustasin SwiftUI-sse põhjalikult sukelduda. Olen kulutanud sellega palju aega ja nüüd olen hakanud välja töötama tõelist rakendust, mis võib olla kasulik paljudele kasutajatele.

Ma nimetasin seda MovieSwiftUI-ks – see on rakendus uute ja vanade filmide otsimiseks ning nende kogumiseks kasutades TMDB API. Olen alati armastanud filme ja isegi loonud selles valdkonnas tegutseva ettevõtte, kuigi juba ammu. Ettevõtet vaevalt lahedaks nimetada sai, aga rakendus oli!

Tuletame meelde: kõigile "Habr" lugejatele - allahindlus 10 000 rubla, kui registreerute mis tahes Skillboxi kursusele, kasutades sooduskoodi "Habr".

Skillbox soovitab: Hariv veebikursus "Java arendaja elukutse".

Mida saab MovieSwiftUI teha?

  • Suhtleb API-ga – peaaegu iga kaasaegne rakendus teeb seda.
  • Laadib päringute kohta asünkroonsed andmed ja sõelub JSON-i Swifti mudelisse kasutades Kodeeritav.
  • Näitab nõudmisel laaditud pilte ja salvestab need vahemällu.
  • See iOS-i, iPadOS-i ja macOS-i rakendus pakub nende operatsioonisüsteemide kasutajatele parimat kasutuskogemust.
  • Kasutaja saab luua andmeid ja luua oma filmiloendeid. Rakendus salvestab ja taastab kasutajaandmeid.
  • Vaated, komponendid ja mudelid on Reduxi mustri abil selgelt eraldatud. Siin on andmevoog ühesuunaline. Seda saab täielikult vahemällu salvestada, taastada ja üle kirjutada.
  • Rakendus kasutab põhikomponente SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal jne. See pakub ka kohandatud vaateid, žeste ja kasutajaliidest/UX-i.

Rakenduste arendamine SwiftUI-s. 1. osa: andmevoog ja redux
Tegelikult on animatsioon sujuv, GIF osutus pisut tõmblevaks

Rakenduse kallal töötamine andis mulle palju kogemusi ja üldiselt oli see positiivne kogemus. Sain kirjutada täisfunktsionaalse rakenduse, septembris täiustan seda ja avaldan AppStore'is, samaaegselt iOS 13 väljalaskmisega.

Redux, BindableObject ja EnvironmentObject

Rakenduste arendamine SwiftUI-s. 1. osa: andmevoog ja redux

Olen Reduxiga töötanud umbes kaks aastat, seega olen sellega suhteliselt hästi kursis. Eelkõige kasutan seda kasutajaliideses Reageerima veebisaidil, aga ka iOS-i (Swift) ja Androidi (Kotlin) rakenduste arendamiseks.

Ma pole kunagi kahetsenud, et valisin SwiftUI rakenduse loomiseks andmevoo arhitektuuriks Reduxi. Reduxi kasutamisel UIKiti rakenduses on kõige keerulisemad osad poega töötamine ning andmete hankimine ja toomine ning nende vastendamine teie vaadete/komponentidega. Selleks pidin looma omamoodi konnektorite raamatukogu (kasutades ReSwifti ja ReKotlinit). Töötab hästi, aga üsna palju koodi. Kahjuks pole see (veel) avatud lähtekoodiga.

Head uudised! Ainsad asjad, mille pärast SwiftUI puhul muretseda – kui plaanite Reduxi kasutada –, on poed, olekud ja reduktorid. Tänu @EnvironmentObjectile hoolitseb poega suhtlemise eest täielikult SwiftUI. Niisiis, pood algab BindableObjectiga.

Tegin lihtsa Swifti paketi, SwiftUIFlux, mis pakub Reduxi põhikasutust. Minu puhul on see osa MovieSwiftUI-st. mina ka kirjutas samm-sammult õpetuse, mis aitab teil seda komponenti kasutada.

Kuidas see toimib?

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

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

Kuna pood on BindableObject, teavitab see SwiftUI-d, kui selle väärtus muutub, kasutades PassthroughSubjecti pakutavat atribuuti willChange. Selle põhjuseks on asjaolu, et BindableObject peab andma PublisherType'i, kuid protokolli juurutus vastutab selle haldamise eest. Üldiselt on see Apple'i väga võimas tööriist. Sellest lähtuvalt aitab SwiftUI järgmises renderdustsüklis vaadete põhiosa renderdada vastavalt olekumuutusele.

Tegelikult on see kõik SwiftUI süda ja maagia. Nüüd renderdatakse vaade igas olekus tellitavas vaates vastavalt sellele, milliseid andmeid osariigilt saadakse 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())
    }
}

Pood sisestatakse rakenduse käivitumisel keskkonnaobjektina ja seejärel on @EnvironmentObject abil juurdepääsetav mis tahes vaates. Jõudlustrahvi ei kohaldata, kuna tuletatud omadused hangitakse või arvutatakse rakenduse olekust kiiresti.

Ülaltoodud kood muudab pilti, kui filmi plakat muutub.

Ja seda tehakse tegelikult vaid ühe joonega, mille abil seotakse vaated riigiga. Kui olete töötanud ReSwiftiga iOS-is või isegi ühendada Reactiga mõistate SwiftUI võlu.

Nüüd saate proovida toimingut aktiveerida ja uue oleku avaldada. Siin on keerulisem näide.

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

Ülaltoodud koodis kasutan iga IP jaoks .onDelete toimingut SwiftUI-st. See võimaldab loendi real kuvada tavalist iOS-i kustutamiseks pühkima. Nii et kui kasutaja puudutab kustutamisnuppu, käivitab see vastava toimingu ja eemaldab filmi loendist.

Kuna loendi atribuut tuletatakse olekust BindableObject ja see sisestatakse keskkonnaobjektina, värskendab SwiftUI loendit, kuna ForEach on seotud filmide arvutatud atribuudiga.

Siin on osa MoviesState'i reduktorist:

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 käivitatakse, kui saadate toimingu ja tagastate uue oleku, nagu eespool öeldud.

Ma ei hakka veel detailidesse laskuma – kuidas SwiftUI tegelikult teab, mida kuvada. Selle sügavamaks mõistmiseks tasub vaadake WWDC seanssi andmevoo kohta SwiftUI-s. Samuti selgitatakse üksikasjalikult, miks ja millal seda kasutada riik, @Binding, ObjectBinding ja EnvironmentObject.

Skillbox soovitab:

Allikas: www.habr.com

Lisa kommentaar