Zhvillimi i aplikacioneve në SwiftUI. Pjesa 1: rrjedha e të dhënave dhe Redux

Zhvillimi i aplikacioneve në SwiftUI. Pjesa 1: rrjedha e të dhënave dhe Redux

Pas pasjes së seancës State of the Union në WWDC 2019, vendosa të shqyrtoj me kujdes SwiftUI. Kam kaluar shumë kohë duke punuar me të dhe tani kam filluar zhvillimin e një aplikacioni real 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 grumbullimin e tyre nĂ« koleksion pĂ«rmes TMDB API. Kam dashur gjithmonĂ« filmat dhe madje kam krijuar njĂ« kompani qĂ« punon nĂ« kĂ«tĂ« fushĂ«, megjithatĂ«, kjo ka ndodhur njĂ« kohĂ« tĂ« gjatĂ«. Kompania ishte e vĂ«shtirĂ« pĂ«r t'u quajtur e shkĂ«lqyer, por aplikacioni, po!

KujtojmĂ«: pĂ«r tĂ« gjithĂ« lexuesit e «Habra» — zbritje prej 10,000 rublesh pĂ«r regjistrimin nĂ« çdo kurs Skillbox me kodin promovues «Habr».

Skillbox rekomandon: Kurs online edukativ «Profesia Java-zhvillues».

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

  • NdĂ«rvepron me API — kjo e bĂ«n pothuajse çdo aplikacion modern.
  • Shkarkon tĂ« dhĂ«na asinkrone pĂ«rmes kĂ«rkesave dhe analizojnĂ« JSON nĂ« modele Swift, duke pĂ«rdorur Codable.
  • Tregon imazhe qĂ« shkarkohen sipas kĂ«rkesĂ«s dhe i ruan ato.
  • Ky aplikacion pĂ«r iOS, iPadOS dhe macOS ofron pĂ«rvojĂ«n mĂ« tĂ« mirĂ« tĂ« pĂ«rdoruesve tĂ« kĂ«tyre sistemeve operative.
  • PĂ«rdoruesi mund tĂ« gjenerojĂ« tĂ« dhĂ«na, tĂ« krijojĂ« lista tĂ« tij tĂ« filmave. Aplikacioni ruan dhe rikthen tĂ« dhĂ«nat e pĂ«rdoruesit.
  • Pamjet, komponentĂ«t dhe modelet janĂ« ndarĂ« qartĂ« duke pĂ«rdorur modelin Redux. Rryma e tĂ« dhĂ«nave kĂ«tu Ă«shtĂ« njĂ« drejtim. Ajo mund tĂ« ruhet, rikthehet dhe shkruhet pĂ«rsĂ«ri plotĂ«sisht.
  • Aplikacioni pĂ«rdor komponentĂ« tĂ« bazuar nĂ« SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal etj. Ai gjithashtu ofron pamje tĂ« personalizuara, gjeste, UI/UX.

Zhvillimi i aplikacioneve në SwiftUI. Pjesa 1: rrjedha e të dhënave dhe Redux
Në të vërtetë, animacioni është i qetë, GIF-i doli pak i çrregullt.

Puna mbi aplikacionin më dha shumë përvojë, dhe në përgjithësi, kjo është një përvojë pozitive. Arrita të shkruaj një aplikacion me funksionalitet të plotë, në Shtator do ta përmirësoj dhe do ta publikoj në AppStore, në të njëjtën kohë me lansimin e iOS 13.

Redux, BindableObject dhe EnvironmentObject

Zhvillimi i aplikacioneve në SwiftUI. Pjesa 1: rrjedha e të dhënave dhe Redux

Në këtë moment, kam punuar me Redux për rreth dy vjet, kështu që kam një njohuri të mirë për të. Në veçanti, e përdor atë në frontend për React uebfaqen, si dhe për zhvillimin e aplikacioneve natyrore iOS (Swift) dhe Android (Kotlin).

Nuk kam penduar asnjëherë për zgjedhjen e Redux si arkitekturë për rrjedhën e të dhënave për krijimin e një aplikacioni në SwiftUI. Momentet më sfiduese kur përdorni Redux në aplikacionin UIKit janë menaxhimi i store-it, si dhe marrja dhe nxjerrja e të dhënave dhe përputhja e tyre me pamjet tuaja / komponentët. Për këtë, duhej të krijoja një lloj biblioteke konnektorësh (në ReSwift dhe ReKotlin). Punon mirë, por ka mjaft kod. Fatkeqësisht, ai (deri tani) nuk është open source.

Lajme tĂ« mira! E vetmja gjĂ« pĂ«r tĂ« cilĂ«n duhet tĂ« shqetĂ«soheni me SwiftUI — nĂ«se planifikoni tĂ« pĂ«rdorni Redux — Ă«shtĂ« store, gjendjet dhe reduktuesit. Interaksioni me store-in merret plotĂ«sisht pĂ«rsipĂ«r nga SwiftUI pĂ«rmes @EnvironmentObject. KĂ«shtu, store fillon me BindableObject.

Kam krijuar një paketë të thjeshtë Swift, SwiftUIFlux, e cila ofron përdorim bazik të Redux. Në rastin tim, kjo është një pjesë e MovieSwiftUI. Gjithashtu, unë kam shkruar një tutorial hap pas hapi, i cili ndihmon në përdorimin e këtij komponenti.

Si funksionon kjo?

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

Çdo herĂ« qĂ« ekzekutoni njĂ« veprim, aktivizoni reduktorin. Ai do tĂ« vlerĂ«sojĂ« veprimet nĂ« pĂ«rputhje me gjendjen aktuale tĂ« aplikacionit. MĂ« pas, ai do tĂ« kthejĂ« njĂ« gjendje tĂ« re tĂ« modifikuar nĂ« pĂ«rputhje me llojin e veprimit dhe tĂ« dhĂ«nat.

Dhe sepse store është një BindableObject, ai do ta njoftojë SwiftUI për ndryshimin e vlerës së tij, duke përdorur pronën willChange, e cila ofrohet nga PassthroughSubject. Kjo ndodh sepse BindableObject duhet të ofrojë PublisherType, por implementimi 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. Në përputhje, në ciklin e ardhshëm të renderimit, SwiftUI do të ndihmojë në shfaqjen e trupit të pamjeve në përputhje me ndryshimin e gjendjes.

Pra, ky është thelbi dhe magia e SwiftUI. Tani në çdo pamje që është e nënshkruar për gjendjen, pamja do të shfaqet në përputhje me të dhënat që merren nga gjendja 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
    
    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())
    }
}

Store është integruar si EnvironmentObject gjatë nisjes së aplikacionit, dhe më pas është e aksesueshme në çdo pamje përmes @EnvironmentObject. Performanca nuk zvogëlohet, pasi pronat e nxjerra janë shpejt të nxjerra ose të llogaritura nga gjendja e aplikacionit.

Kodi i mësiperm ndryshon imazhin nëse poster-i i filmit ndryshon.

Dhe kjo bëhet në fakt me vetëm një rresht, i cili lidh pamjet me gjendjen. Nëse keni punuar me ReSwift në iOS ose madje connect me React, do të kuptoni se çfarë është magjia e SwiftUI.

Tani mund të provoni të aktivizoni një veprim dhe të publikoni një gjendje të re. Ky është një shembull më i ndërlikuar.

struct CustomListDetail : View {
    @EnvironmentObject var store: Store
 
    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ë përdor veprimin .onDelete nga SwiftUI për çdo IP. Kjo lejon që një rresht në listë të tregojë një tërheqje të zakonshme iOS për përjashtim. Kështu, kur përdoruesi prek butonin e fshirjes, ai aktivizon veprimin përkatës dhe heq filmin nga lista.

Dhe, duke qenë se prona e listës është johumbur nga gjendja e BindableObject dhe implementohet si EnvironmentObject, SwiftUI azhurnon listën, pasi ForEach është i lidhur me pronën e llogaritur movies.

Këtu është një pjesë e reduktorit 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
}

Reduktori ekzekutohet kur ju dërgoni një veprim dhe kthen një gjendje të re, siç u tha më lart.

Nuk do tĂ« hyj nĂ« detaje pĂ«r momentin — nga e di SwiftUI se çfarĂ« tĂ« shfaqĂ«. PĂ«r ta kuptuar kĂ«tĂ« mĂ« thellĂ«, Ă«shtĂ« e dobishme tĂ« shqyrtoni sesionin WWDC mbi rrjedhĂ«n e tĂ« dhĂ«nave nĂ« SwiftUI. Gjithashtu aty shpjegohet nĂ« detaje pse dhe kur tĂ« pĂ«rdorni State, @Binding, ObjectBinding dhe EnvironmentObject.

Skillbox rekomandon:

Burimi: habr.com

Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster