
A WWDC 2019 State of the Union session után úgy döntöttem, hogy alaposan megvizsgálom a SwiftUI-t. Sok időt töltöttem a vele való munkával, és most elkezdtem egy valós alkalmazás fejlesztését, amely hasznos lehet a szélesebb közönség számára.
Ezt MovieSwiftUI-nak neveztem el — egy alkalmazás, amely új és régi filmek keresésére szolgál, valamint gyűjteménybe való összegyűjtésére a segítségével. Mindig is szerettem a filmeket, és egyszer egy céget is alapítottam ezen a területen, igaz, már régen. A cég nehezen volt klassz, de az alkalmazás — az már igen!
Emlékeztetjük: minden «Habr» olvasónak 10 000 rubeles kedvezmény jár bármely Skillbox kurzus jelentkezésekor a «Habr» promóciós kód felhasználásával.
A Skillbox ajánlja: Oktatási online kurzus .
Tehát mit tud a MovieSwiftUI?
- Kapcsolatba lép az API-val — ezt szinte minden modern alkalmazás megteszi.
- Aszinkron adatokat tölt le a lekérdezések során, és JSON-t elemez Swift-modellekbe, használva a .
- Képek megjelenítése, amelyek kérés alapján töltődnek le, és azok cache-elése.
- Ez az iOS, iPadOS és macOS számára készült alkalmazás biztosítja a legjobb felhasználói élményt e rendszerek felhasználói számára.
- A felhasználó saját adatokat generálhat, saját filmes listákat készíthet. Az alkalmazás eltárolja és visszaállítja a felhasználói adatokat.
- A nézők, komponensek és modellek világosan el vannak választva a Redux minta segítségével. Az adatok áramlása egyirányú. Teljes mértékben cache-elhető, visszaállítható és felülírható.
- Az alkalmazás alapvető SwiftUI komponenseket használ, mint a TabbedView, SegmentedControl, NavigationView, Form, Modal stb. Ezen kívül személyre szabott nézeteket, gesztusokat, UI/UX funkciókat is biztosít.

Valójában az animáció sima, a gif egy kicsit ugrálós lett.
Az alkalmazás fejlesztése sok tapasztalatot adott, és összességében pozitív élmény volt. Képes voltam teljes funkcionalitású alkalmazást írni, szeptemberben javítani fogom, és kiadom az App Store-ba, egy időben az iOS 13 megjelenésével.
Redux, BindableObject és EnvironmentObject

Jelen pillanatban körülbelül két éve dolgozom Redux-szal, így viszonylag jól ismerem. Különösen a front-end fejlesztés során használom a weboldalhoz, valamint natív iOS (Swift) és Android (Kotlin) alkalmazások fejlesztéséhez.
Soha nem bántam meg, hogy a Redux-ot választottam az adatszolgáltatás architektúrájának a SwiftUI alkalmazás létrehozásához. A legtöbb kihívást a Redux használata jelentette az UIKit alkalmazásban, különösen a store kezelése, valamint az adatok beszerzése és kinyerése, és ezek összekapcsolása a nézetekkel / komponensekkel. Ennek érdekében egyfajta csatlakoztatók könyvtárát kellett létrehoznom (ReSwift és ReKotlin alapú). Jól működik, de elég sok kódot igényel. Sajnos még nem nyílt forráskódú.
Jó hírek! Az egyetlen dolog, ami miatt aggódni kell a SwiftUI használatakor - ha Redux-t tervezel használni - a store, az állapotok és a reduktorok. A SwiftUI teljesen átveszi a kapcsolatot a store-ral az @EnvironmentObject segítségével. Így a store BindableObject-ként kezdődik.
Létrehoztam egy egyszerű Swift csomagot, , amely az alapvető Redux használatát biztosítja. Az én esetemben ez a MovieSwiftUI része. Emellett , amely segít ennek a komponensnek a használatában.
Hogyan működik ez?
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)
}
}Minden alkalommal, amikor akciót hajtasz végre, aktiválod a reduktort. Ez kiértékeli az akciókat az alkalmazás aktuális állapota szerint. Ezután új, módosított állapotot ad vissza az akció típusának és az adatoknak megfelelően.
Mivel a store BindableObject, értesíti a SwiftUI-t az értékének megváltozásáról a willChange tulajdonság használatával, amelyet a PassthroughSubject biztosít. Mindez azért van, mert a BindableObject-nek PublisherType-ot kell biztosítania, de a protokoll megvalósítása felelős a kezeléséért. Összességében ez egy nagyon erőteljes eszköz az Apple-től. Ennek következtében a következő SwiftUI renderelési ciklusban a nézetek törzsét az állapot változásainak megfelelően fogja megjeleníteni.
Valójában ez az egész - a SwiftUI szíve és varázsa. Most bármely nézetben, amely feliratkozott az állapotra, a nézet a megkapott állapot adatai és a változások szerint fog megjelenni.
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())
}
}A Store EnvironmentObject-ként kerül bevezetésre az alkalmazás indításakor, majd bármely nézetben elérhető @EnvironmentObject használatával. A teljesítmény nem csökken, mert a származtatott tulajdonságokat gyorsan nyerjük ki vagy számítjuk ki az alkalmazás állapotából.
A fenti kód megváltoztatja a képet, ha a film posztere megváltozik.
Ez valójában mindössze egy sorral végrehajtható, amellyel a nézetek kapcsolódnak az állapothoz. Ha dolgozott már ReSwift-tel iOS-en vagy akár React-tel, meg fogja érteni SwiftUI varázsát.
Most megpróbálhatjuk aktiválni a műveletet és közzétenni egy új állapotot. Íme egy bonyolultabb 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 az .onDelete műveletet használom a SwiftUI-ban minden IP-hez. Ez lehetővé teszi, hogy a lista sora normális iOS gesztust mutasson a törléshez. Így amikor a felhasználó megnyomja a törlés gombot, a megfelelő művelet elindul és eltávolítja a filmet a listából.
Mivel a lista tulajdonsága a BindableObject állapotának származtatott értéke, és EnvironmentObject-ként kerül bevezetésre, ezért a SwiftUI frissíti a listát, mivel a ForEach kapcsolódik a számított movies tulajdonsághoz.
Íme a MoviesState csökkentőjének egy része:
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 hajtódik végre, amikor egy műveletet küldesz, és új állapotot ad vissza, ahogy fentebb említettem.
Egyelőre nem megyek bele a részletekbe — honnan tudja valójában a SwiftUI, hogy mit kell megjeleníteni. Ahhoz, hogy ezt mélyebben megértsük, érdemes a SwiftUI-ban. Arról is részletesen beszélnek, hogy miért és mikor érdemes használni , @Binding, ObjectBinding és EnvironmentObject.
A Skillbox ajánlja:
- Gyakorlati kurzus .
- Alkalmazott online kurzus .
- Kétéves gyakorlati tanfolyam .
Forrás: habr.com
