Applikationsutveckling på SwiftUI. Del 1: Dataflöde och Redux

Applikationsutveckling på SwiftUI. Del 1: Dataflöde och Redux

Efter att ha deltagit i State of the Union-sessionen på WWDC 2019 bestämde jag mig för att ta en djupdykning i SwiftUI. Jag har ägnat mycket tid åt att arbeta med det och har nu börjat utveckla en riktig applikation som kan vara användbar för ett brett spektrum av användare.

Jag kallade det MovieSwiftUI - det här är en app för att söka efter nya och gamla filmer, samt samla dem i en samling med TMDB API. Jag har alltid älskat filmer och till och med skapat ett företag som arbetar inom detta område, men för länge sedan. Företaget kunde knappast kallas coolt, men ansökan var det!

Påminnelse: för alla läsare av "Habr" - en rabatt på 10 000 rubel när du anmäler dig till någon Skillbox-kurs med hjälp av "Habr"-kampanjkoden.

Skillbox rekommenderar: Pedagogisk onlinekurs "Yrke Java-utvecklare".

Så vad kan MovieSwiftUI göra?

  • Interagerar med API - nästan alla moderna applikationer gör detta.
  • Laddar asynkron data på förfrågningar och analyserar JSON i Swift-modellen med hjälp av Kodbar.
  • Visar bilder laddade på begäran och cachar dem.
  • Denna app för iOS, iPadOS och macOS ger den bästa användarupplevelsen för användare av dessa operativsystem.
  • Användaren kan generera data och skapa sina egna filmlistor. Applikationen sparar och återställer användardata.
  • Vyer, komponenter och modeller är tydligt åtskilda med hjälp av Redux-mönstret. Dataflödet här är enkelriktat. Det kan cachelagras, återställas och skrivas över.
  • Applikationen använder de grundläggande komponenterna i SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal, etc. Det ger också anpassade vyer, gester, UI/UX.

Applikationsutveckling på SwiftUI. Del 1: Dataflöde och Redux
Faktum är att animeringen är smidig, GIF:en blev lite ryckig

Att arbeta med appen gav mig mycket erfarenhet och överlag var det en positiv upplevelse. Jag kunde skriva en fullt fungerande applikation, i september kommer jag att förbättra den och publicera den i AppStore, samtidigt med lanseringen av iOS 13.

Redux, BindableObject och EnvironmentObject

Applikationsutveckling på SwiftUI. Del 1: Dataflöde och Redux

Jag har jobbat med Redux i ungefär två år nu, så jag är relativt väl insatt i det. I synnerhet använder jag den i frontend för Reagera webbplats, samt för att utveckla inbyggda iOS (Swift) och Android (Kotlin) applikationer.

Jag har aldrig ångrat att jag valde Redux som dataflödesarkitektur för att bygga en SwiftUI-applikation. De mest utmanande delarna när du använder Redux i en UIKit-app är att arbeta med butiken och hämta och hämta data och mappa den till dina vyer/komponenter. För att göra detta var jag tvungen att skapa ett slags bibliotek av kontakter (med hjälp av ReSwift och ReKotlin). Fungerar bra, men ganska mycket kod. Tyvärr är det inte (ännu) öppen källkod.

Goda nyheter! De enda sakerna att oroa sig för med SwiftUI - om du planerar att använda Redux - är butiker, stater och reducerare. Interaktionen med butiken sköts helt av SwiftUI tack vare @EnvironmentObject. Så, butiken börjar med ett BindableObject.

Jag skapade ett enkelt Swift-paket, SwiftUIFlux, som ger grundläggande användning av Redux. I mitt fall är det en del av MovieSwiftUI. jag med skrev en steg-för-steg tutorial, som hjälper dig att använda den här komponenten.

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

Varje gång du utlöser en åtgärd aktiverar du växellådan. Den kommer att utvärdera åtgärder enligt det aktuella läget för ansökan. Den kommer då att returnera ett nytt modifierat tillstånd i enlighet med åtgärdstyp och data.

Tja, eftersom butiken är ett BindableObject kommer den att meddela SwiftUI när dess värde ändras med hjälp av egenskapen willChange som tillhandahålls av PassthroughSubject. Detta beror på att BindableObject måste tillhandahålla en PublisherType, men protokollimplementeringen är ansvarig för att hantera den. Sammantaget är detta ett mycket kraftfullt verktyg från Apple. Följaktligen, i nästa renderingscykel, kommer SwiftUI att hjälpa till att rendera kroppen av vyerna enligt tillståndsändringen.

Egentligen är detta allt hjärtat och magin i SwiftUI. Nu, i alla vyer som prenumererar på ett tillstånd, kommer vyn att återges enligt vilken data som tas emot från staten och vad som har ändrats.

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

Butiken injiceras som ett EnvironmentObject när applikationen startar och är sedan tillgänglig i alla vyer med @EnvironmentObject. Det finns ingen prestationspåföljd eftersom härledda egenskaper snabbt hämtas eller beräknas från applikationstillstånd.

Koden ovan ändrar bilden om filmaffischen ändras.

Och detta sker faktiskt med bara en rad, med vars hjälp synpunkter kopplas till staten. Om du har arbetat med ReSwift på iOS eller till och med ansluta med React kommer du att förstå magin med SwiftUI.

Nu kan du försöka aktivera åtgärden och publicera det nya tillståndet. Här är ett mer komplext exempel.

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 ovan använder jag åtgärden .onDelete från SwiftUI för varje IP. Detta gör att raden i listan visar den normala iOS-svepningen för att radera. Så när användaren trycker på raderingsknappen utlöser den motsvarande åtgärd och tar bort filmen från listan.

Tja, eftersom listegenskapen härleds från BindableObject-tillståndet och injiceras som ett EnvironmentObject, uppdaterar SwiftUI listan eftersom ForEach är associerat med den filmberäknade egenskapen.

Här är en del av MoviesState-reduceraren:

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
}

Reduceraren exekveras när du skickar en åtgärd och returnerar ett nytt tillstånd, enligt ovan.

Jag kommer inte att gå in i detalj ännu - hur SwiftUI faktiskt vet vad som ska visas. För att förstå detta djupare är det värt se WWDC-session om dataflöde i SwiftUI. Den förklarar också i detalj varför och när den ska användas Ange, @Binding, ObjectBinding och EnvironmentObject.

Skillbox rekommenderar:

Källa: will.com

Lägg en kommentar