Alkalmazásfejlesztés SwiftUI-n. 1. rész: Adatfolyam és Redux

Alkalmazásfejlesztés SwiftUI-n. 1. rész: Adatfolyam és Redux

Miután részt vettem a 2019-es WWDC-n az Unió állapotáról szóló ülésén, úgy döntöttem, hogy mélyen belemerülök a SwiftUI-ba. Sok időt töltöttem vele, és most elkezdtem fejleszteni egy valódi alkalmazást, amely a felhasználók széles köre számára hasznos lehet.

MovieSwiftUI-nak hívtam – ez egy olyan alkalmazás, amellyel új és régi filmeket kereshet, valamint egy gyűjteménybe gyűjtheti. TMDB API. Mindig is szerettem a filmeket, és még egy céget is alapítottam, amely ezen a területen dolgozott, bár nagyon régen. A céget aligha lehetett menőnek nevezni, de a jelentkezés igen!

Emlékeztetünk: a "Habr" minden olvasója számára - 10 000 rubel kedvezmény, ha a "Habr" promóciós kóddal bármely Skillbox tanfolyamra jelentkezik.

A Skillbox a következőket ajánlja: Oktató online tanfolyam "Profession Java Developer".

Tehát mire képes a MovieSwiftUI?

  • Kölcsönhatásba lép az API-val – szinte minden modern alkalmazás ezt teszi.
  • Aszinkron adatokat tölt be a kérésekre, és elemzi a JSON-t a Swift-modellbe a használatával Kódolható.
  • Megjeleníti a kérésre betöltött képeket, és gyorsítótárazza őket.
  • Ez az iOS, iPadOS és macOS rendszerhez készült alkalmazás a legjobb felhasználói élményt nyújtja ezen operációs rendszerek felhasználóinak.
  • A felhasználó adatokat generálhat és saját filmlistákat hozhat létre. Az alkalmazás menti és visszaállítja a felhasználói adatokat.
  • A nézetek, a komponensek és a modellek jól elkülöníthetők a Redux minta segítségével. Az adatáramlás itt egyirányú. Teljesen gyorsítótárazható, visszaállítható és felülírható.
  • Az alkalmazás a SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal stb. alapvető összetevőit használja. Egyéni nézeteket, gesztusokat, felhasználói felületet/UX-et is biztosít.

Alkalmazásfejlesztés SwiftUI-n. 1. rész: Adatfolyam és Redux
Valójában az animáció sima, a GIF kicsit szaggatott lett

Az alkalmazáson való munka sok tapasztalatot adott, és összességében pozitív élmény volt. Egy teljesen működőképes alkalmazást tudtam írni, szeptemberben javítom és közzéteszem az AppStore-ban, az iOS 13 megjelenésével egy időben.

Redux, BindableObject és EnvironmentObject

Alkalmazásfejlesztés SwiftUI-n. 1. rész: Adatfolyam és Redux

Körülbelül két éve dolgozom a Reduxszal, így viszonylag jól ismerem. Különösen a frontendben használom Reagál weboldalon, valamint natív iOS (Swift) és Android (Kotlin) alkalmazások fejlesztéséhez.

Soha nem bántam meg, hogy a Reduxot választottam adatfolyam-architektúraként egy SwiftUI-alkalmazás elkészítéséhez. A Redux UIKit alkalmazásban való használata során a legnagyobb kihívást az áruházzal való együttműködés, az adatok lekérése és lekérése, valamint a nézetekhez/összetevőkhöz való hozzárendelése jelenti. Ehhez létre kellett hoznom egyfajta csatlakozók könyvtárát (ReSwift és ReKotlin segítségével). Jól működik, de elég sok kód. Sajnos (még) nem nyílt forráskódú.

Jó hírek! Az egyetlen dolog, ami miatt aggódnia kell a SwiftUI-val – ha a Redux használatát tervezi – az üzletek, állapotok és szűkítők. Az üzlettel való interakciót az @EnvironmentObject-nek köszönhetően teljes mértékben a SwiftUI biztosítja. Tehát az áruház egy BindableObject-el kezdődik.

Készítettem egy egyszerű Swift csomagot, SwiftUIFlux, amely a Redux alapvető használatát biztosítja. Az én esetemben a MovieSwiftUI része. én is írt egy lépésről lépésre bemutató oktatóanyagot, amely segít az összetevő használatában.

Hogyan működik?

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

Minden alkalommal, amikor elindít egy műveletet, aktiválja a sebességváltót. A műveleteket az alkalmazás aktuális állapotának megfelelően értékeli. Ezután a művelet típusának és az adatoknak megfelelően új módosított állapotot ad vissza.

Nos, mivel a bolt egy BindableObject, a PassthroughSubject által biztosított willChange tulajdonság segítségével értesíti a SwiftUI-t, ha az értéke megváltozik. Ennek az az oka, hogy a BindableObject-nek biztosítania kell egy PublisherType-ot, de a protokoll megvalósítása felelős a kezeléséért. Összességében ez egy nagyon hatékony eszköz az Apple-től. Ennek megfelelően a következő renderelési ciklusban a SwiftUI segít a nézetek törzsének megjelenítésében az állapotváltozásnak megfelelően.

Valójában ez a SwiftUI szíve és varázsa. Mostantól minden olyan nézetben, amely előfizet egy állapotra, a nézet aszerint jelenik meg, hogy milyen adatokat kapott az állapot, és mi változott.

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

Az Áruház környezeti objektumként kerül beillesztésre az alkalmazás indításakor, majd az @EnvironmentObject használatával bármely nézetben elérhető. Nincs teljesítménybüntetés, mert a származtatott tulajdonságok gyorsan lekérhetők vagy kiszámíthatók az alkalmazás állapotából.

A fenti kód megváltoztatja a képet, ha a filmplakát megváltozik.

Ez pedig tulajdonképpen egyetlen vonallal történik, aminek segítségével a nézetek az államhoz kapcsolódnak. Ha dolgozott már a ReSwifttel iOS-en vagy akár connect a React segítségével megértheti a SwiftUI varázslatát.

Most megpróbálhatja aktiválni a műveletet, és közzétenni az új állapotot. Íme egy összetettebb 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 a SwiftUI .onDelete műveletét használom minden IP-címhez. Ez lehetővé teszi, hogy a lista sora megjelenítse a normál iOS-elhúzást a törléshez. Tehát amikor a felhasználó megérinti a törlés gombot, az elindítja a megfelelő műveletet, és eltávolítja a filmet a listáról.

Nos, mivel a listatulajdonság a BindableObject állapotból származik, és EnvironmentObject-ként van beillesztve, a SwiftUI frissíti a listát, mivel a ForEach a filmek kiszámított tulajdonságához van társítva.

Íme egy része a MoviesState reduktornak:

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 kerül végrehajtásra, amikor elküld egy műveletet, és új állapotot ad vissza, a fent leírtak szerint.

Még nem megyek bele a részletekbe – hogyan tudja valójában a SwiftUI, hogy mit jelenítsen meg. Ennek mélyebb megértéséhez érdemes megtekintheti a WWDC munkamenetet az adatáramlásról a SwiftUI-ban. Azt is részletesen elmagyarázza, hogy miért és mikor kell használni Állami, @Binding, ObjectBinding és EnvironmentObject.

A Skillbox a következőket ajánlja:

Forrás: will.com

Hozzászólás