Alkalmazásfejlesztés SwiftUI-ban. 1. rész: adatok áramlása és Redux

Alkalmazásfejlesztés SwiftUI-ban. 1. rész: adatok áramlása és Redux

A WWDC 2019 State of the Union session után úgy döntöttem, hogy alaposan megvizsgálom a SwiftUI-t. Sok időt töltöttem a vele való munkával, és most elkezdtem egy valós alkalmazás fejlesztését, amely hasznos lehet a szélesebb közönség számára.

Ezt MovieSwiftUI-nak neveztem el — egy alkalmazás, amely új és régi filmek keresésére szolgál, valamint gyűjteménybe való összegyűjtésére a TMDB APIsegítségével. Mindig is szerettem a filmeket, és egyszer egy céget is alapítottam ezen a területen, igaz, már régen. A cég nehezen volt klassz, de az alkalmazás — az már igen!

Emlékeztetjük: minden «Habr» olvasónak 10 000 rubeles kedvezmény jár bármely Skillbox kurzus jelentkezésekor a «Habr» promóciós kód felhasználásával.

A Skillbox ajánlja: Oktatási online kurzus "Java-Fejlesztő Mesterség".

Tehát mit tud a MovieSwiftUI?

  • Kapcsolatba lép az API-val — ezt szinte minden modern alkalmazás megteszi.
  • Aszinkron adatokat tölt le a lekérdezések során, és JSON-t elemez Swift-modellekbe, használva a Codable.
  • Képek megjelenítése, amelyek kérés alapján töltődnek le, és azok cache-elése.
  • Ez az iOS, iPadOS és macOS számára készült alkalmazás biztosítja a legjobb felhasználói élményt e rendszerek felhasználói számára.
  • A felhasználó saját adatokat generálhat, saját filmes listákat készíthet. Az alkalmazás eltárolja és visszaállítja a felhasználói adatokat.
  • A nézők, komponensek és modellek világosan el vannak választva a Redux minta segítségével. Az adatok áramlása egyirányú. Teljes mértékben cache-elhető, visszaállítható és felülírható.
  • Az alkalmazás alapvető SwiftUI komponenseket használ, mint a TabbedView, SegmentedControl, NavigationView, Form, Modal stb. Ezen kívül személyre szabott nézeteket, gesztusokat, UI/UX funkciókat is biztosít.

Alkalmazásfejlesztés SwiftUI-ban. 1. rész: adatok áramlása és Redux
Valójában az animáció sima, a gif egy kicsit ugrálós lett.

Az alkalmazás fejlesztése sok tapasztalatot adott, és összességében pozitív élmény volt. Képes voltam teljes funkcionalitású alkalmazást írni, szeptemberben javítani fogom, és kiadom az App Store-ba, egy időben az iOS 13 megjelenésével.

Redux, BindableObject és EnvironmentObject

Alkalmazásfejlesztés SwiftUI-ban. 1. rész: adatok áramlása és Redux

Jelen pillanatban körülbelül két éve dolgozom Redux-szal, így viszonylag jól ismerem. Különösen a front-end fejlesztés során használom a React weboldalhoz, valamint natív iOS (Swift) és Android (Kotlin) alkalmazások fejlesztéséhez.

Soha nem bántam meg, hogy a Redux-ot választottam az adatszolgáltatás architektúrájának a SwiftUI alkalmazás létrehozásához. A legtöbb kihívást a Redux használata jelentette az UIKit alkalmazásban, különösen a store kezelése, valamint az adatok beszerzése és kinyerése, és ezek összekapcsolása a nézetekkel / komponensekkel. Ennek érdekében egyfajta csatlakoztatók könyvtárát kellett létrehoznom (ReSwift és ReKotlin alapú). Jól működik, de elég sok kódot igényel. Sajnos még nem nyílt forráskódú.

Jó hírek! Az egyetlen dolog, ami miatt aggódni kell a SwiftUI használatakor - ha Redux-t tervezel használni - a store, az állapotok és a reduktorok. A SwiftUI teljesen átveszi a kapcsolatot a store-ral az @EnvironmentObject segítségével. Így a store BindableObject-ként kezdődik.

Létrehoztam egy egyszerű Swift csomagot, SwiftUIFlux, amely az alapvető Redux használatát biztosítja. Az én esetemben ez a MovieSwiftUI része. Emellett írtam egy lépésről lépésre útmutatót, amely segít ennek a komponensnek a használatában.

Hogyan működik ez?

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

Minden alkalommal, amikor akciót hajtasz végre, aktiválod a reduktort. Ez kiértékeli az akciókat az alkalmazás aktuális állapota szerint. Ezután új, módosított állapotot ad vissza az akció típusának és az adatoknak megfelelően.

Mivel a store BindableObject, értesíti a SwiftUI-t az értékének megváltozásáról a willChange tulajdonság használatával, amelyet a PassthroughSubject biztosít. Mindez azért van, mert a BindableObject-nek PublisherType-ot kell biztosítania, de a protokoll megvalósítása felelős a kezeléséért. Összességében ez egy nagyon erőteljes eszköz az Apple-től. Ennek következtében a következő SwiftUI renderelési ciklusban a nézetek törzsét az állapot változásainak megfelelően fogja megjeleníteni.

Valójában ez az egész - a SwiftUI szíve és varázsa. Most bármely nézetben, amely feliratkozott az állapotra, a nézet a megkapott állapot adatai és a változások szerint fog megjelenni.

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

A Store EnvironmentObject-ként kerül bevezetésre az alkalmazás indításakor, majd bármely nézetben elérhető @EnvironmentObject használatával. A teljesítmény nem csökken, mert a származtatott tulajdonságokat gyorsan nyerjük ki vagy számítjuk ki az alkalmazás állapotából.

A fenti kód megváltoztatja a képet, ha a film posztere megváltozik.

Ez valójában mindössze egy sorral végrehajtható, amellyel a nézetek kapcsolódnak az állapothoz. Ha dolgozott már ReSwift-tel iOS-en vagy akár connect React-tel, meg fogja érteni SwiftUI varázsát.

Most megpróbálhatjuk aktiválni a műveletet és közzétenni egy új állapotot. Íme egy bonyolultabb példa.

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

A fenti kódban az .onDelete műveletet használom a SwiftUI-ban minden IP-hez. Ez lehetővé teszi, hogy a lista sora normális iOS gesztust mutasson a törléshez. Így amikor a felhasználó megnyomja a törlés gombot, a megfelelő művelet elindul és eltávolítja a filmet a listából.

Mivel a lista tulajdonsága a BindableObject állapotának származtatott értéke, és EnvironmentObject-ként kerül bevezetésre, ezért a SwiftUI frissíti a listát, mivel a ForEach kapcsolódik a számított movies tulajdonsághoz.

Íme a MoviesState csökkentőjének egy része:

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
}

A reduktor akkor hajtódik végre, amikor egy műveletet küldesz, és új állapotot ad vissza, ahogy fentebb említettem.

Egyelőre nem megyek bele a részletekbe — honnan tudja valójában a SwiftUI, hogy mit kell megjeleníteni. Ahhoz, hogy ezt mélyebben megértsük, érdemes megnézni a WWDC adását az adatfolyamról a SwiftUI-ban. Arról is részletesen beszélnek, hogy miért és mikor érdemes használni Device Property, @Binding, ObjectBinding és EnvironmentObject.

A Skillbox ajánlja:

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster