
Dopo aver partecipato alla sessione State of the Union a WWDC 2019, ho deciso di approfondire SwiftUI. Ho dedicato molto tempo a lavorarci e ora sto iniziando a sviluppare un'applicazione reale che potrebbe rivelarsi utile per un vasto pubblico.
L'ho chiamata MovieSwiftUI: è un'app per cercare film nuovi e vecchi, oltre a raccoglierli in una collezione tramite . Ho sempre amato i film e ho anche creato un'azienda che opera in questo settore, anche se è passato un po' di tempo. L'azienda non si può definire fantastica, ma l'applicazione sì!
Ricordiamo: per tutti i lettori di «Habr» — sconto di 10.000 rubli per l'iscrizione a qualsiasi corso Skillbox con il codice promozionale «Habr».
Skillbox consiglia: Corso online educativo .
Quindi, cosa può fare MovieSwiftUI?
- Interagisce con le API, come fa quasi ogni applicazione moderna.
- Carica dati asincroni su richiesta e analizza JSON in modelli Swift usando .
- Mostra immagini caricate su richiesta e le memorizza nella cache.
- Questa applicazione per iOS, iPadOS e macOS offre la migliore esperienza utente per gli utenti di questi OS.
- L'utente può generare dati e creare le proprie liste di film. L'app memorizza e ripristina i dati dell'utente.
- Le visualizzazioni, i componenti e i modelli sono chiaramente separati tramite il pattern Redux. Il flusso di dati è unidirezionale. Può essere completamente memorizzato nella cache, ripristinato e sovrascritto.
- L'app utilizza i componenti di base di SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal, ecc. Garantisce anche visualizzazioni personalizzate, gesti, UI/UX.

In realtà, l'animazione è fluida, mentre il GIF è risultato un po' scattoso.
Lavorare sull'applicazione mi ha dato molta esperienza e, in generale, è stata un'esperienza positiva. Sono riuscito a scrivere un'applicazione completamente funzionale; a settembre la migliorerò e la pubblicherò su App Store, insieme al rilascio di iOS 13.
Redux, BindableObject e EnvironmentObject

Attualmente lavoro con Redux da circa due anni, quindi ho una buona comprensione al riguardo. In particolare, lo uso nel frontend per siti web, oltre che per lo sviluppo di app native iOS (Swift) e Android (Kotlin).
Non ho mai rimpianto di aver scelto Redux come architettura per il flusso di dati per creare un'applicazione su SwiftUI. I momenti più complessi nell'utilizzo di Redux all'interno di un'applicazione UIKit sono lavorare con lo store, così come ottenere e prelevare dati e mappare questi ultimi alle vostre viste/componenti. Per fare ciò, ho dovuto creare una sorta di libreria di connettori (su ReSwift e ReKotlin). Funziona bene, ma c'è molto codice. Purtroppo, al momento, non è open source.
Buone notizie! L'unica cosa di cui preoccuparsi con SwiftUI — se intendete utilizzare Redux — è lo store, gli stati e i reducer. L'interazione con lo store è completamente gestita da SwiftUI grazie a @EnvironmentObject. Così, lo store inizia con BindableObject.
Ho creato un semplice pacchetto Swift, , che garantisce un utilizzo di base di Redux. Nel mio caso è parte di MovieSwiftUI. Ho anche , che aiuterà a utilizzare questo componente.
Come funziona?
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)
}
}Ogni volta che eseguite un'azione, attivate il reducer. Questo valuterà le azioni in base all'attuale stato dell'applicazione. Successivamente, restituirà un nuovo stato modificato in base al tipo di azione e ai dati.
Poiché lo store è un BindableObject, notificherà SwiftUI della modifica del proprio valore, utilizzando la proprietà willChange fornita da PassthroughSubject. Questo accade perché il BindableObject deve fornire un PublisherType, ma l'implementazione del protocollo è responsabile della sua gestione. In generale, è uno strumento molto potente di Apple. Di conseguenza, nel prossimo ciclo di rendering, SwiftUI aiuterà a visualizzare il corpo delle viste in base alla modifica dello stato.
In effetti, questo è tutto — il cuore e la magia di SwiftUI. Ora in qualsiasi vista che è sottoscritta allo stato, la vista verrà visualizzata in base a quali dati sono stati ottenuti dallo stato e cosa è cambiato.
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 viene iniettato come EnvironmentObject all'avvio dell'applicazione e poi è accessibile in qualsiasi vista tramite @EnvironmentObject. Le prestazioni non diminuiscono, poiché le proprietà derivate vengono estratte o calcolate rapidamente dallo stato dell'applicazione.
Il codice sopra modifica l'immagine se il poster del film cambia.
E ciò avviene effettivamente con una sola riga, che collega le viste allo stato. Se hai lavorato con ReSwift su iOS o anche con React, capirai qual è la magia di SwiftUI.
Ora puoi provare ad attivare un'azione e pubblicare un nuovo stato. Ecco un esempio più complesso.
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!]))
}
}
}
}Nel codice sopra, utilizzo l'azione .onDelete di SwiftUI per ogni IP. Questo consente alla riga nell'elenco di visualizzare il normale swipe di iOS per eliminare. Quindi, quando l'utente tocca il pulsante di eliminazione, attiva l'azione corrispondente ed elimina il film dall'elenco.
Poiché la proprietà dell'elenco è derivata dallo stato di BindableObject e viene iniettata come EnvironmentObject, SwiftUI aggiorna l'elenco poiché ForEach è collegato alla proprietà calcolata movies.
Ecco una parte del riduttore MoviesState:
func moviesStateReducer(state: MoviesState, action: Action) -> MoviesState {
var state = state
switch action {
// altre azioni.
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
}Il riduttore viene eseguito quando invii un'azione e restituisce un nuovo stato, come detto sopra.
Non entrerò ancora nei dettagli su come SwiftUI sappia effettivamente cosa visualizzare. Per comprendere questo più a fondo, vale la pena in SwiftUI. Viene anche spiegato in dettaglio quando e perché utilizzare , @Binding, ObjectBinding e EnvironmentObject.
Skillbox consiglia:
- Corso pratico .
- Corso online pratico .
- Corso pratico di due anni .
Fonte: habr.com
