Desenvolvemento de aplicacións en SwiftUI. Parte 1: Fluxo de datos e Redux

Desenvolvemento de aplicacións en SwiftUI. Parte 1: Fluxo de datos e Redux

Despois de asistir á sesión do Estado da Unión na WWDC 2019, decidín mergullarme a fondo en SwiftUI. Levo moito tempo traballando con el e agora comecei a desenvolver unha aplicación real que pode ser útil para unha ampla gama de usuarios.

Chameino MovieSwiftUI: esta é unha aplicación para buscar películas novas e antigas, así como recollelas nunha colección usando API TMDB. Sempre me gustaron as películas e mesmo creou unha empresa que traballa neste campo, aínda que hai moito tempo. A empresa dificilmente podería chamarse xenial, pero a aplicación si!

Recordámolo: para todos os lectores de "Habr" - un desconto de 10 rublos ao inscribirse en calquera curso de Skillbox usando o código promocional "Habr".

Skillbox recomenda: Curso educativo online "Profesional Java Developer".

Entón, que pode facer MovieSwiftUI?

  • Interactúa coa API; case calquera aplicación moderna fai isto.
  • Carga datos asíncronos en solicitudes e analiza JSON no modelo Swift usando Codificable.
  • Mostra as imaxes cargadas baixo petición e as almacena na caché.
  • Esta aplicación para iOS, iPadOS e macOS ofrece a mellor experiencia de usuario para os usuarios destes sistemas operativos.
  • O usuario pode xerar datos e crear as súas propias listas de películas. A aplicación garda e restaura os datos do usuario.
  • As vistas, os compoñentes e os modelos están claramente separados mediante o patrón Redux. O fluxo de datos aquí é unidireccional. Pódese almacenar na caché, restauralo e sobrescribirse completamente.
  • A aplicación usa os compoñentes básicos de SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal, etc. Tamén ofrece vistas personalizadas, xestos, UI/UX.

Desenvolvemento de aplicacións en SwiftUI. Parte 1: Fluxo de datos e Redux
De feito, a animación é suave, o GIF resultou un pouco irregular

Traballar na aplicación deume moita experiencia e, en xeral, foi unha experiencia positiva. Puiden escribir unha aplicación totalmente funcional, en setembro mellorarei e publicarei na AppStore, simultaneamente co lanzamento de iOS 13.

Redux, BindableObject e EnvironmentObject

Desenvolvemento de aplicacións en SwiftUI. Parte 1: Fluxo de datos e Redux

Levo uns dous anos traballando con Redux, polo que estou relativamente ben versado. En particular, úsoo no frontend para Reaccionar sitio web, así como para desenvolver aplicacións nativas para iOS (Swift) e Android (Kotlin).

Nunca me arrepintei de escoller Redux como a arquitectura de fluxo de datos para construír unha aplicación SwiftUI. As partes máis difíciles ao usar Redux nunha aplicación UIKit son traballar coa tenda e obter e recuperar datos e asignalos ás túas vistas/compoñentes. Para iso, tiven que crear unha especie de biblioteca de conectores (usando ReSwift e ReKotlin). Funciona ben, pero hai moito código. Desafortunadamente, non é (aínda) de código aberto.

Boas novas! As únicas cousas das que te preocupes con SwiftUI, se pensas usar Redux, son as tendas, os estados e os reductores. A interacción coa tenda está totalmente atendida por SwiftUI grazas a @EnvironmentObject. Entón, a tenda comeza cun BindableObject.

Creei un paquete Swift sinxelo, SwiftUIFlux, que proporciona o uso básico de Redux. No meu caso é parte de MovieSwiftUI. eu tamén escribiu un tutorial paso a paso, que che axudará a utilizar este compoñente.

Como funciona isto?

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)
    }
}

Cada vez que activas unha acción, activas a caixa de cambios. Avaliará as actuacións segundo o estado actual da solicitude. A continuación, devolverá un novo estado modificado segundo o tipo de acción e os datos.

Ben, xa que store é un BindableObject, notificará a SwiftUI cando o seu valor cambie mediante a propiedade willChange proporcionada por PassthroughSubject. Isto débese a que BindableObject debe proporcionar un PublisherType, pero a implementación do protocolo é responsable de xestionalo. En xeral, esta é unha ferramenta moi poderosa de Apple. En consecuencia, no próximo ciclo de renderizado, SwiftUI axudará a renderizar o corpo das vistas segundo o cambio de estado.

En realidade, este é todo o corazón e a maxia de SwiftUI. Agora, en calquera vista que se subscriba a un estado, a vista renderase segundo os datos recibidos do estado e o que cambiou.

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 Tenda inxéctase como un EnvironmentObject cando se inicia a aplicación e despois pódese acceder en calquera vista mediante @EnvironmentObject. Non hai ningunha penalización de rendemento porque as propiedades derivadas son rapidamente recuperadas ou calculadas a partir do estado da aplicación.

O código anterior cambia a imaxe se cambia o cartel da película.

E isto realízase con só unha liña, coa axuda da cal as vistas están conectadas ao estado. Se traballaches con ReSwift en iOS ou mesmo conectarse con React, entenderás a maxia de SwiftUI.

Agora podes tentar activar a acción e publicar o novo estado. Aquí tes un exemplo máis complexo.

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!]))
            }
        }
    }
}

No código anterior estou usando a acción .onDelete de SwiftUI para cada IP. Isto permite que a fila da lista mostre o paso normal de iOS para eliminar. Entón, cando o usuario toca o botón Eliminar, activa a acción correspondente e elimina a película da lista.

Ben, dado que a propiedade da lista deriva do estado BindableObject e se inxecta como un EnvironmentObject, SwiftUI actualiza a lista porque ForEach está asociado coa propiedade calculada de películas.

Aquí está parte do reductor MoviesState:

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
}

O reductor execútase cando envía unha acción e devolve un novo estado, como se indicou anteriormente.

Aínda non entrarei en detalles: como SwiftUI sabe realmente o que mostrar. Para entender isto máis profundamente, paga a pena ver a sesión WWDC sobre o fluxo de datos en SwiftUI. Tamén explica detalladamente por que e cando usar estado, @Binding, ObjectBinding e EnvironmentObject.

Skillbox recomenda:

Fonte: www.habr.com

Engadir un comentario