
Na afloop van de State of the Union-sessie op WWDC 2019 besloot ik me grondig te verdiepen in SwiftUI. Ik heb veel tijd besteed aan het werken ermee en ben nu begonnen met de ontwikkeling van een echte applicatie die nuttig kan zijn voor een breed publiek.
Ik noemde het MovieSwiftUI — een app voor het zoeken naar nieuwe en oude films, en het verzamelen ervan in een collectie met behulp van . Ik heb altijd van films gehouden en heb ooit een bedrijf opgericht dat in deze sector werkzaam was, maar dat is al enige tijd geleden. Het bedrijf was moeilijk als 'cool' te classificeren, maar de app – dat zeker!
Ter herinnering: voor alle lezers van «Habr» — een korting van 10.000 roebel bij inschrijving voor elke cursus van Skillbox met de promocode «Habr».
Skillbox raadt aan: Online opleidingscursus .
Dus, wat kan MovieSwiftUI?
- Het communiceert met de API — dat kan bijna elke moderne applicatie.
- Het laadt asynchrone gegevens op aanvraag en parseert JSON naar Swift-modellen met behulp van .
- Het toont afbeeldingen die op aanvraag worden geladen en cached ze.
- Deze applicatie voor iOS, iPadOS en macOS biedt de beste UX voor gebruikers van deze besturingssystemen.
- De gebruiker kan gegevens genereren en zijn eigen filmlijsten aanmaken. De applicatie slaat gebruikersgegevens op en herstelt ze.
- De weergaven, componenten en modellen zijn duidelijk gescheiden met behulp van het Redux-patroon. De datastroom is eenrichtingsverkeer. Het kan volledig worden gecached, hersteld en herschreven.
- De app gebruikt basiscomponenten van SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal, enzovoort. Het biedt ook aangepaste weergaven, gebaren, UI/UX.

In feite is de animatie soepel, hoewel de gif een beetje schokkerig is.
Het werken aan de app heeft me veel ervaring opgeleverd, en over het geheel genomen was het een positieve ervaring. Ik ben in staat geweest om een volledig functionele app te schrijven, die ik in september zal verbeteren en in de App Store zal plaatsen, gelijktijdig met de lancering van iOS 13.
Redux, BindableObject en EnvironmentObject

Momenteel werk ik al zo'n twee jaar met Redux, dus ik heb er een redelijk goede kennis van. In het bijzonder gebruik ik het aan de frontend voor de website, evenals voor de ontwikkeling van native iOS (Swift) en Android (Kotlin) applicaties.
Ik heb nog nooit spijt gehad van de keuze voor Redux als gegevensstroomarchitectuur voor het maken van een app met SwiftUI. De meest complexe aspecten bij het gebruik van Redux in een UIKit-app zijn het werken met de store, evenals het verkrijgen en extraheren van gegevens en het afstemmen hiervan op uw weergaven/componenten. Hiervoor moest ik een soort bibliotheek van connectors maken (met ReSwift en ReKotlin). Het werkt goed, maar er is behoorlijk veel code. Helaas is het (nog) geen open source.
Goed nieuws! Het enige waar je je zorgen over hoeft te maken met SwiftUI — als je van plan bent Redux te gebruiken — zijn de store, toestanden en reducers. De interactie met de store wordt volledig overgenomen door SwiftUI dankzij @EnvironmentObject. Zo begint de store met BindableObject.
Ik heb een eenvoudig Swift-pakket gemaakt, , dat de basisfunctionaliteit van Redux biedt. In mijn geval is dit onderdeel van MovieSwiftUI. Ook heb ik , die helpt bij het gebruiken van deze component.
How does it work?
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)
}
}Elke keer dat je een actie uitvoert, activeer je de reducer. Deze zal de acties evalueren op basis van de huidige toestand van de app. Vervolgens retourneert het een nieuwe, gewijzigde toestand op basis van het soort actie en de gegevens.
Aangezien de store een BindableObject is, zal deze SwiftUI informeren over de wijziging van zijn waarde door gebruik te maken van de willChange-eigenschap, die wordt geleverd door PassthroughSubject. Dit is omdat BindableObject een PublisherType moet bieden, maar de implementatie van het protocol verantwoordelijk is voor het beheer ervan. Over het algemeen is het een zeer krachtige tool van Apple. In de volgende rendercyclus helpt SwiftUI om de inhoud van de weergaven weer te geven in overeenstemming met de wijziging in de toestand.
Dat is het eigenlijk — het hart en de magie van SwiftUI. Nu in elke weergave die is ingeschreven op de toestand, zal de weergave worden weergegeven op basis van welke gegevens zijn verkregen uit de toestand en wat er is gewijzigd.
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 wordt geïmplementeerd als EnvironmentObject bij het opstarten van de applicatie en is daarna beschikbaar in elke weergave via @EnvironmentObject. De prestaties worden niet verminderd, omdat afgeleide eigenschappen snel worden opgehaald of berekend vanuit de applicatiestatus.
De code hierboven verandert de afbeelding als de poster van de film verandert.
En dit gebeurt eigenlijk met slechts één regel, die het verbinden van weergaven met de status mogelijk maakt. Als je met ReSwift op iOS of zelfs met React hebt gewerkt, begrijp je wat de magie van SwiftUI is.
Nu kun je proberen een actie te activeren en een nieuwe status te publiceren. Hier is een complexer voorbeeld.
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!]))
}
}
}
}In de bovenstaande code gebruik ik de .onDelete actie van SwiftUI voor elke rij. Dit maakt het mogelijk om de gebruikelijke iOS-swipe voor verwijderen te tonen. Daarom, wanneer de gebruiker op de verwijderknop tikt, wordt de bijbehorende actie gestart en wordt de film uit de lijst verwijderd.
En omdat de eigenschap van de lijst afgeleid is van de BindableObject status en als EnvironmentObject wordt geïmplementeerd, werkt SwiftUI de lijst bij omdat ForEach is gekoppeld aan de berekende eigenschap movies.
Hier is een deel van de reducer MoviesState:
func moviesStateReducer(state: MoviesState, action: Action) -> MoviesState {
var state = state
switch action {
// andere acties.
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
}De reducer wordt uitgevoerd wanneer u een actie verzendt en retourneert een nieuwe staat, zoals hierboven vermeld.
Ik ga nog niet in detail treden — waar SwiftUI eigenlijk weet wat het moet weergeven. Om dit dieper te begrijpen, is het de moeite waard in SwiftUI te bekijken. Daar wordt ook uitgebreid besproken waarom en wanneer u , @Binding, ObjectBinding en EnvironmentObject moet gebruiken.
Skillbox raadt aan:
- Praktische cursus .
- Toegepaste online cursus .
- Praktische tweejarenopleiding .
Bron: habr.com
