Zhvillimi i aplikacionit në SwiftUI. Pjesa 1: Rrjedha e të dhënave dhe Redux

Zhvillimi i aplikacionit në SwiftUI. Pjesa 1: Rrjedha e të dhënave dhe Redux

Pasi mora pjesë në sesionin e Gjendjes së Unionit në WWDC 2019, vendosa të bëj një zhytje të thellë në SwiftUI. Kam kaluar shumë kohë duke punuar me të dhe tani kam filluar të zhvilloj një aplikacion të vërtetë që mund të jetë i dobishëm për një gamë të gjerë përdoruesish.

E quajta MovieSwiftUI - ky është një aplikacion për kërkimin e filmave të rinj dhe të vjetër, si dhe për mbledhjen e tyre në një koleksion duke përdorur TMDB API. I kam dashur gjithmonë filmat dhe madje kam krijuar një kompani që punon në këtë fushë, megjithëse shumë kohë më parë. Kompania vështirë se mund të quhej e lezetshme, por aplikacioni ishte!

Kujtojmë: për të gjithë lexuesit e "Habr" - një zbritje prej 10 rubla kur regjistroheni në çdo kurs Skillbox duke përdorur kodin promovues "Habr".

Skillbox rekomandon: Kurs edukativ online "Profesioni zhvillues Java".

Pra, çfarë mund të bëjë MovieSwiftUI?

  • Ndërvepron me API - pothuajse çdo aplikacion modern e bën këtë.
  • Ngarkon të dhëna asinkrone në kërkesat dhe analizon JSON në modelin Swift duke përdorur E kodueshme.
  • Shfaq imazhet e ngarkuara sipas kërkesës dhe i ruan ato.
  • Ky aplikacion për iOS, iPadOS dhe macOS ofron UX-në më të mirë për përdoruesit e këtyre OS-ve.
  • Përdoruesi mund të gjenerojë të dhëna dhe të krijojë listat e tyre të filmave. Aplikacioni ruan dhe rikthen të dhënat e përdoruesit.
  • Pamjet, komponentët dhe modelet janë të ndara qartë duke përdorur modelin Redux. Rrjedha e të dhënave këtu është e njëanshme. Mund të ruhet plotësisht, të restaurohet dhe të mbishkruhet.
  • Aplikacioni përdor komponentët bazë të SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal, etj. Ai gjithashtu ofron pamje të personalizuara, gjeste, UI/UX.

Zhvillimi i aplikacionit në SwiftUI. Pjesa 1: Rrjedha e të dhënave dhe Redux
Në fakt, animacioni është i qetë, GIF doli pak i vrullshëm

Puna në aplikacion më dha shumë përvojë dhe në përgjithësi ishte një përvojë pozitive. Munda të shkruaj një aplikacion plotësisht funksional, në shtator do ta përmirësoj dhe do ta publikoj në AppStore, njëkohësisht me lëshimin e iOS 13.

Redux, BindableObject dhe EnvironmentObject

Zhvillimi i aplikacionit në SwiftUI. Pjesa 1: Rrjedha e të dhënave dhe Redux

Unë kam punuar me Redux për rreth dy vjet tani, kështu që jam relativisht i aftë për të. Në veçanti, e përdor në frontend për Reagoj faqe interneti, si dhe për zhvillimin e aplikacioneve vendase iOS (Swift) dhe Android (Kotlin).

Nuk jam penduar kurrë që kam zgjedhur Redux si arkitekturën e rrjedhës së të dhënave për ndërtimin e një aplikacioni SwiftUI. Pjesët më sfiduese kur përdorni Redux në një aplikacion UIKit janë puna me dyqanin dhe marrja dhe marrja e të dhënave dhe vendosja e tyre në pamjet/komponentët tuaj. Për ta bërë këtë, më duhej të krijoja një lloj bibliotekë lidhësish (duke përdorur ReSwift dhe ReKotlin). Punon mirë, por shumë kod. Fatkeqësisht, nuk është (ende) me burim të hapur.

Lajme te mira! E vetmja gjë për t'u shqetësuar me SwiftUI - nëse planifikoni të përdorni Redux - janë dyqanet, shtetet dhe reduktuesit. Ndërveprimi me dyqanin kujdeset plotësisht nga SwiftUI falë @EnvironmentObject. Pra, dyqani fillon me një BindableObject.

Kam krijuar një paketë të thjeshtë Swift, SwiftUIFlux, i cili ofron përdorimin bazë të Redux. Në rastin tim është pjesë e MovieSwiftUI. unë gjithashtu shkroi një tutorial hap pas hapi, e cila do t'ju ndihmojë të përdorni këtë komponent.

Si funksionon kjo gjë?

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

Sa herë që aktivizoni një veprim, aktivizoni kutinë e marsheve. Ai do të vlerësojë veprimet sipas gjendjes aktuale të aplikacionit. Më pas do të kthejë një gjendje të re të modifikuar në përputhje me llojin e veprimit dhe të dhënat.

Epo, duke qenë se dyqani është një BindableObject, ai do të njoftojë SwiftUI kur vlera e tij të ndryshojë duke përdorur veçorinë willChange të ofruar nga PassthroughSubject. Kjo ndodh sepse BindableObject duhet të sigurojë një PublisherType, por zbatimi i protokollit është përgjegjës për menaxhimin e tij. Në përgjithësi, ky është një mjet shumë i fuqishëm nga Apple. Prandaj, në ciklin e ardhshëm të interpretimit, SwiftUI do të ndihmojë në paraqitjen e trupit të pamjeve sipas ndryshimit të gjendjes.

Në fakt, kjo është e gjithë zemra dhe magjia e SwiftUI. Tani, në çdo pamje që pajtohet me një shtet, pamja do të jepet sipas të dhënave që merren nga shteti dhe çfarë ka ndryshuar.

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

Dyqani injektohet si një objekt mjedisi kur fillon aplikacioni dhe më pas është i aksesueshëm në çdo pamje duke përdorur @EnvironmentObject. Nuk ka asnjë ndëshkim për performancën sepse vetitë e prejardhura merren shpejt ose llogariten nga gjendja e aplikimit.

Kodi i mësipërm ndryshon imazhin nëse ndryshon posteri i filmit.

Dhe kjo në fakt bëhet vetëm me një linjë, me ndihmën e së cilës lidhen pikëpamjet me shtetin. Nëse keni punuar me ReSwift në iOS apo edhe lidh me React, do të kuptoni magjinë e SwiftUI.

Tani mund të provoni të aktivizoni veprimin dhe të publikoni gjendjen e re. Këtu është një shembull më kompleks.

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

Në kodin e mësipërm, unë jam duke përdorur veprimin .onDelete nga SwiftUI për çdo IP. Kjo lejon që rreshti në listë të shfaqë rrëshqitjen normale të iOS për t'u fshirë. Pra, kur përdoruesi prek butonin e fshirjes, ai aktivizon veprimin përkatës dhe e heq filmin nga lista.

Epo, meqenëse vetia e listës rrjedh nga gjendja BindableObject dhe injektohet si një Objekt Mjedisor, SwiftUI përditëson listën sepse ForEach shoqërohet me vetinë e llogaritur të filmave.

Këtu është një pjesë e reduktuesit MoviesState:

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
}

Reduktuesi ekzekutohet kur dërgoni një veprim dhe ktheni një gjendje të re, siç u tha më sipër.

Nuk do të hyj ende në detaje - si SwiftUI e di në të vërtetë se çfarë të shfaqë. Për ta kuptuar më thellë këtë, ia vlen shikoni sesionin WWDC mbi rrjedhën e të dhënave në SwiftUI. Ai gjithashtu shpjegon në detaje pse dhe kur të përdoret shtet, @Binding, ObjectBinding dhe EnvironmentObject.

Skillbox rekomandon:

Burimi: www.habr.com

Shto një koment