
Efter at have deltaget i State of the Union-sessionen ved WWDC 2019 besluttede jeg mig for at dykke ned i SwiftUI. Jeg har brugt meget tid på at arbejde med det og er nu begyndt at udvikle en rigtig applikation, der kan være nyttig for en bred vifte af brugere.
Jeg har kaldt den MovieSwiftUI — en app til at finde nye og gamle film samt samle dem i en samling ved hjælp af . Jeg har altid elsket film og har endda startet et firma, der arbejder inden for dette område, for længe siden. Det var svært at kalde firmaet cool, men appen er det!
Vi minder om: til alle læsere af 'Habr' — rabat på 10.000 rubler ved tilmelding til et hvilket som helst Skillbox-kursus med rabatkoden 'Habr'.
Skillbox anbefaler: Online uddannelseskursus .
Så hvad kan MovieSwiftUI?
- Interagerer med API — det gør næsten enhver moderne applikation.
- Henter asynkrone data efter forespørgsler og parser JSON til Swift-modeller ved hjælp af .
- Viser billeder, der hentes efter forespørgsel, og cacher dem.
- Denne applikation til iOS, iPadOS og macOS leverer en bedre UX for brugere af disse OS.
- Brugeren kan generere data og oprette egne film lister. Appen gemmer og genopretter brugerdata.
- Visninger, komponenter og modeller er klart adskilt ved hjælp af Redux-mønsteret. Dataflowet her er ensrettet. Det kan caches, genoprettes og overskrives fuldstændigt.
- Appen bruger grundlæggende SwiftUI-komponenter, TabbedView, SegmentedControl, NavigationView, Form, Modal osv. Den giver også brugerdefinerede visninger, gestus, UI/UX.

Faktisk er animationen glat, men GIF'en blev lidt rystet.
Arbejdet med appen har givet mig meget erfaring, og samlede set er det en positiv oplevelse. Jeg har formået at skrive en funktionsdygtig app, som jeg vil forbedre i september og udgive i App Store, samtidig med at iOS 13 lanceres.
Redux, BindableObject og EnvironmentObject

I øjeblikket har jeg arbejdet med Redux i næsten to år, så jeg har en relativt god forståelse af det. Især bruger jeg det i frontend til webstedet, samt til udvikling af native iOS (Swift) og Android (Kotlin) applikationer.
Jeg har ikke fortrudt valget af Redux som dataflowarkitektur til at oprette en applikation i SwiftUI. De mest udfordrende aspekter ved brugen af Redux i en UIKit-applikation er arbejdet med store, samt at hente og udtrække data og matche dem med dine visninger / komponenter. Til dette var jeg nødt til at oprette en slags bibliotek med connectores (på ReSwift og ReKotlin). Det fungerer godt, men der er ret meget kode. Desværre er det (indtil videre) ikke open source.
Gode nyheder! Det eneste, du skal bekymre dig om med SwiftUI — hvis du planlægger at bruge Redux — er store, tilstande og reducere. Interaktionen med store tager SwiftUI sig fuldstændigt af ved hjælp af @EnvironmentObject. Således starter store med BindableObject.
Jeg har oprettet en simpel Swift-pakke, , som giver grundlæggende brug af Redux. I mit tilfælde er det en del af MovieSwiftUI. Jeg har også , som hjælper med at bruge denne komponent.
Hvordan fungerer det?
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)
}
}Hver gang du aktiverer en handling, aktiverer du reduceren. Den vil evaluere handlinger i henhold til den nuværende tilstand af applikationen. Derefter vil den returnere en ny modificeret tilstand i henhold til typen af handling og data.
Og eftersom store er et BindableObject, vil det informere SwiftUI om ændringen i sin værdi ved hjælp af willChange-egenskaben leveret af PassthroughSubject. Dette er, fordi BindableObject skal levere PublisherType, men implementeringen af protokollen har ansvaret for at styre det. Generelt er det et meget kraftfuldt værktøj fra Apple. Tilsvarende, i den næste rendercyklus, vil SwiftUI hjælpe med at vise indholdet af visningerne i forhold til ændringen i tilstanden.
Faktisk er det alt — hjertet og magien ved SwiftUI. Nu, i enhver visning, der abonnerer på tilstanden, vil visningen blive præsenteret i overensstemmelse med hvilke data der er hentet fra tilstanden, og hvad der er ændret.
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 implementeres som EnvironmentObject ved opstart af applikationen og er derefter tilgængeligt i enhver visning ved hjælp af @EnvironmentObject. Ydelsen falder ikke, da afledte egenskaber hurtigt hentes eller beregnes fra applikationens tilstand.
Koden ovenfor ændrer billedet, hvis posteren til filmen ændres.
Og det gøres faktisk med kun én linje, hvor visningerne forbindes til tilstanden. Hvis du har arbejdet med ReSwift på iOS eller endda med React, vil du forstå magien ved SwiftUI.
Nu kan du prøve at aktivere en handling og offentliggøre en ny tilstand. Her er et mere komplekst eksempel.
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!]))
}
}
}
}I koden ovenfor bruger jeg handlingen .onDelete fra SwiftUI for hver IP. Dette gør det muligt for en række i listen at vise det sædvanlige swipe iOS for at slette. Så når brugeren trykker på sletteknappen, aktiverer det den relevante handling og fjerner filmen fra listen.
Og da listeegenskaben er afledt fra tilstanden BindableObject og implementeres som EnvironmentObject, opdaterer SwiftUI listen, da ForEach er knyttet til den beregnede egenskab movies.
Her er en del af reduceren MoviesState:
func moviesStateReducer(state: MoviesState, action: Action) -> MoviesState {
var state = state
switch action {
// andre handlinger.
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
}Reducerens opgave er at udføre, når du sender en handling og returnerer den nye tilstand, som nævnt ovenfor.
Jeg vil ikke gå for meget i detaljer lige nu — hvordan SwiftUI faktisk ved, hvad der skal vises. For at forstå det mere grundlæggende, er det værd at i SwiftUI. Der beskrives også grundigt, hvorfor og hvornår man skal bruge , @Binding, ObjectBinding og EnvironmentObject.
Skillbox anbefaler:
- Praktisk kursus .
- Anvendt online-kursus .
- Toårigt praktisk kursus .
Kilde: habr.com
