Lietojumprogrammu izstrÄde SwiftUI. 1. daļa: datu plÅ«sma un redux
PÄc piedalÄ«Å”anÄs sesijÄ āState of the Unionā WWDC 2019 es nolÄmu dziļi izpÄtÄ«t SwiftUI. Esmu pavadÄ«jis daudz laika, strÄdÄjot ar to, un tagad esmu sÄcis izstrÄdÄt reÄlu aplikÄciju, kas var bÅ«t noderÄ«ga plaÅ”am lietotÄju lokam.
Es to nosaucu par MovieSwiftUI ā Ŕī ir lietotne jaunu un vecu filmu meklÄÅ”anai, kÄ arÄ« to apkopoÅ”anai kolekcijÄ, izmantojot TMDB API. Man vienmÄr ir patikuÅ”as filmas un pat izveidoju uzÅÄmumu, kas strÄdÄ Å”ajÄ jomÄ, lai gan jau sen. KompÄniju diez vai varÄtu nosaukt par forÅ”u, bet pieteikums gan!
Mijiedarbojas ar API ā to dara gandrÄ«z jebkura moderna lietojumprogramma.
IelÄdÄ asinhronos datus par pieprasÄ«jumiem un parsÄ JSON Swift modelÄ«, izmantojot KodÄjams.
ParÄda pÄc pieprasÄ«juma ielÄdÄtos attÄlus un saglabÄ tos keÅ”atmiÅÄ.
Å Ä« iOS, iPadOS un macOS lietotne nodroÅ”ina vislabÄko UX Å”o operÄtÄjsistÄmu lietotÄjiem.
LietotÄjs var Ä£enerÄt datus un izveidot savus filmu sarakstus. Lietojumprogramma saglabÄ un atjauno lietotÄja datus.
Skati, komponenti un modeļi ir skaidri atdalÄ«ti, izmantojot Redux modeli. Datu plÅ«sma Å”eit ir vienvirziena. To var pilnÄ«bÄ saglabÄt keÅ”atmiÅÄ, atjaunot un pÄrrakstÄ«t.
Lietojumprogramma izmanto SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal u.c. pamatkomponentus. Tas nodroÅ”ina arÄ« pielÄgotus skatus, žestus, UI/UX.
PatiesÄ«bÄ animÄcija ir gluda, GIF izrÄdÄ«jÄs nedaudz saraustÄ«ts
Darbs pie lietotnes man deva lielu pieredzi, un kopumÄ tÄ bija pozitÄ«va pieredze. PaspÄju uzrakstÄ«t pilnÄ«bÄ funkcionÄjoÅ”u aplikÄciju, septembrÄ« to uzlaboÅ”u un publicÄÅ”u AppStore, vienlaikus ar iOS 13 iznÄkÅ”anu.
Redux, BindableObject un EnvironmentObject
Es strÄdÄju ar Redux jau apmÄram divus gadus, tÄpÄc esmu salÄ«dzinoÅ”i labi pÄrzinÄjis to. Jo Ä«paÅ”i es to izmantoju priekÅ”galÄ ReaÄ£Ät vietnei, kÄ arÄ« vietÄjo iOS (Swift) un Android (Kotlin) lietojumprogrammu izstrÄdei.
Es nekad neesmu nožÄlojis, ka izvÄlÄjos Redux kÄ datu plÅ«smas arhitektÅ«ru SwiftUI lietojumprogrammas izveidei. SarežģītÄkÄs daļas, izmantojot Redux UIKit lietotnÄ, ir darbs ar veikalu, datu iegÅ«Å”ana un izguve un to kartÄÅ”ana ar saviem skatiem/komponentiem. Lai to izdarÄ«tu, man bija jÄizveido sava veida savienotÄju bibliotÄka (izmantojot ReSwift un ReKotlin). Darbojas labi, bet diezgan daudz koda. DiemžÄl tas (vÄl) nav atvÄrts avots.
Labas ziÅas! VienÄ«gÄs lietas, par kurÄm jÄuztraucas saistÄ«bÄ ar SwiftUI ā ja plÄnojat izmantot Redux ā ir veikali, Å”tati un reduktori. Pateicoties @EnvironmentObject, par mijiedarbÄ«bu ar veikalu pilnÄ«bÄ rÅ«pÄjas SwiftUI. TÄtad, veikals sÄkas ar BindableObject.
Es izveidoju vienkÄrÅ”u Swift pakotni, SwiftUIFlux, kas nodroÅ”ina pamata Redux lietoÅ”anu. ManÄ gadÄ«jumÄ tÄ ir daļa no MovieSwiftUI. ES arÄ« uzrakstÄ«ja soli pa solim apmÄcÄ«bu, kas palÄ«dzÄs izmantot Å”o komponentu.
KÄ tas strÄdÄ?
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)
}
}
Katru reizi, kad aktivizÄjat darbÄ«bu, jÅ«s aktivizÄjat pÄrnesumkÄrbu. Tas novÄrtÄs darbÄ«bas atbilstoÅ”i lietojumprogrammas paÅ”reizÄjam stÄvoklim. PÄc tam tas atgriezÄ«s jaunu modificÄtu stÄvokli atbilstoÅ”i darbÄ«bas veidam un datiem.
TÄ kÄ veikals ir BindableObject, tas paziÅos SwiftUI, kad tÄ vÄrtÄ«ba mainÄ«sies, izmantojot PassthroughSubject nodroÅ”inÄto rekvizÄ«tu willChange. Tas ir tÄpÄc, ka BindableObject ir jÄnodroÅ”ina PublisherType, bet protokola ievieÅ”ana ir atbildÄ«ga par tÄ pÄrvaldÄ«bu. KopumÄ tas ir ļoti spÄcÄ«gs Apple rÄ«ks. AttiecÄ«gi nÄkamajÄ renderÄÅ”anas ciklÄ SwiftUI palÄ«dzÄs atveidot skatu pamattekstu atbilstoÅ”i stÄvokļa izmaiÅÄm.
PatiesÄ«bÄ Å”Ä« ir visa SwiftUI sirds un burvÄ«ba. Tagad jebkurÄ skatÄ, kas abonÄ stÄvokli, skats tiks atveidots atkarÄ«bÄ no tÄ, kÄdi dati ir saÅemti no valsts un kas ir mainÄ«jies.
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())
}
}
Veikals tiek ievadÄ«ts kÄ EnvironmentObject, kad lietojumprogramma tiek startÄta, un pÄc tam ir pieejams jebkurÄ skatÄ, izmantojot @EnvironmentObject. Nav veiktspÄjas soda, jo atvasinÄtie rekvizÄ«ti tiek Ätri izgÅ«ti vai aprÄÄ·inÄti no lietojumprogrammas stÄvokļa.
IepriekÅ” minÄtais kods maina attÄlu, ja mainÄs filmas plakÄts.
Un tas faktiski tiek darÄ«ts tikai ar vienu lÄ«niju, ar kuras palÄ«dzÄ«bu viedokļi tiek savienoti ar valsti. Ja esat strÄdÄjis ar ReSwift operÄtÄjsistÄmÄ iOS vai pat savienot ar React jÅ«s sapratÄ«sit SwiftUI burvÄ«bu.
Tagad varat mÄÄ£inÄt aktivizÄt darbÄ«bu un publicÄt jauno stÄvokli. Å eit ir sarežģītÄks piemÄrs.
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!]))
}
}
}
}
IepriekÅ” minÄtajÄ kodÄ katrai IP izmantoju darbÄ«bu .onDelete no SwiftUI. Tas ļauj saraksta rindÄ parÄdÄ«t parasto iOS velciet, lai dzÄstu. TÄtad, kad lietotÄjs pieskaras dzÄÅ”anas pogai, tas aktivizÄ atbilstoÅ”o darbÄ«bu un noÅem filmu no saraksta.
TÄ kÄ saraksta rekvizÄ«ts ir iegÅ«ts no BindableObject stÄvokļa un tiek ievadÄ«ts kÄ EnvironmentObject, SwiftUI atjaunina sarakstu, jo ForEach ir saistÄ«ts ar filmu aprÄÄ·inÄto rekvizÄ«tu.
Šeit ir daļa no MoviesState reduktora:
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
}
SamazinÄtÄjs tiek izpildÄ«ts, kad nosÅ«tÄt darbÄ«bu un atgriežat jaunu stÄvokli, kÄ minÄts iepriekÅ”.
Es vÄl neiedziļinÄÅ”os - kÄ SwiftUI patiesÄ«bÄ zina, ko parÄdÄ«t. Lai to saprastu dziļÄk, ir vÄrts skatÄ«t WWDC sesiju par datu plÅ«smu SwiftUI. TajÄ arÄ« detalizÄti paskaidrots, kÄpÄc un kad to lietot Valsts, @Binding, ObjectBinding un EnvironmentObject.