Desenvolvimento de aplicativo em SwiftUI. Parte 1: fluxo de dados e Redux

Desenvolvimento de aplicativo em SwiftUI. Parte 1: fluxo de dados e Redux

Após participar da sessão State of the Union na WWDC 2019, decidi me aprofundar no SwiftUI. Passei muito tempo trabalhando com ele e agora comecei a desenvolver um aplicativo real que pode ser útil para um grande número de usuários.

Chamei-o de MovieSwiftUI — é um app para buscar filmes novos e antigos, além de coletá-los em uma coleção através do TMDB API. Sempre amei filmes e até criei uma empresa atuando nessa área, embora isso tenha sido há um tempo. A empresa era difícil de chamar de legal, mas o aplicativo — sim!

Lembramos: para todos os leitores do «Habr» — desconto de 10.000 rublos na inscrição em qualquer curso Skillbox com o código promocional «Habr».

A Skillbox recomenda: Curso online educacional «Profissão: Desenvolvedor Java».

E então, o que o MovieSwiftUI pode fazer?

  • Interage com APIs — isso é uma característica de quase qualquer aplicativo moderno.
  • Carrega dados assíncronos por meio de requisições e analisa JSON em modelos Swift, utilizando Codable.
  • Exibe imagens carregadas sob demanda e as armazena em cache.
  • Este aplicativo para iOS, iPadOS e macOS proporciona a melhor experiência para os usuários desses sistemas operacionais.
  • O usuário pode gerar dados e criar suas próprias listas de filmes. O aplicativo armazena e recupera os dados do usuário.
  • As views, componentes e modelos são claramente separados usando o padrão Redux. O fluxo de dados aqui é unidirecional. Ele pode ser totalmente cacheado, recuperado e reescrito.
  • O aplicativo utiliza os componentes básicos do SwiftUI, como TabbedView, SegmentedControl, NavigationView, Form, Modal, etc. Também fornece views personalizadas, gestos, UI/UX.

Desenvolvimento de aplicativo em SwiftUI. Parte 1: fluxo de dados e Redux
Na verdade, a animação é suave; o gif acabou ficando um pouco tremido.

Trabalhar no aplicativo me deu muita experiência, e no geral foi uma experiência positiva. Consegui escrever um aplicativo totalmente funcional, e em setembro vou melhorá-lo e publicá-lo na AppStore, juntamente com o lançamento do iOS 13.

Redux, BindableObject e EnvironmentObject

Desenvolvimento de aplicativo em SwiftUI. Parte 1: fluxo de dados e Redux

Até o momento, estou trabalhando com Redux há cerca de dois anos, então entendo isso relativamente bem. Em particular, o utilizo no frontend para React o site, além de desenvolver aplicativos nativos para iOS (Swift) e Android (Kotlin).

Nunca me arrependi de escolher Redux como a arquitetura de fluxo de dados para criar um aplicativo em SwiftUI. Os momentos mais desafiadores ao usar Redux em um aplicativo UIKit são trabalhar com o store, assim como obter e extrair dados e relacioná-los com suas views/componentes. Para isso, tive que criar uma espécie de biblioteca de conectores (usando ReSwift e ReKotlin). Funciona bem, mas há bastante código. Infelizmente, ainda não é open source.

Boas notícias! A única coisa com a qual você deve se preocupar ao usar SwiftUI — se planeja usar Redux — é o store, estados e reducers. O SwiftUI cuida completamente da interação com o store através do @EnvironmentObject. Assim, o store começa com BindableObject.

Criei um pacote Swift simples, SwiftUIFlux, que oferece uma utilização básica do Redux. No meu caso, isso faz parte do MovieSwiftUI. Também escrevi um tutorial passo a passo , que ajudará a usar esse componente.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) } }

Como funciona?

Toda vez que você dispara uma ação, ativa o reducer. Ele avaliará as ações de acordo com o estado atual do aplicativo. Em seguida, retornará um novo estado modificado de acordo com o tipo de ação e dados.

Como o store é um BindableObject, ele notificará o SwiftUI sobre a alteração de seu valor, utilizando a propriedade willChange, fornecida pelo PassthroughSubject. Isso ocorre porque o BindableObject deve fornecer um PublisherType, mas a implementação do protocolo é responsável por gerenciá-lo. Em geral, é uma ferramenta muito poderosa da Apple. Assim, no próximo ciclo de renderização, o SwiftUI ajudará a exibir o corpo das views de acordo com a mudança de estado.

Basicamente, é isso — o coração e a mágica do SwiftUI. Agora, em qualquer view que esteja inscrita no estado, a view será exibida de acordo com os dados recebidos do estado e o que foi alterado.

Este é o coração e a mágica do SwiftUI. Agora, em qualquer view que esteja inscrita no estado, a view será exibida de acordo com quais dados foram recebidos do estado e o que mudou.

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

O Store é injetado como EnvironmentObject ao iniciar o aplicativo, e depois está disponível em qualquer visualização por meio de @EnvironmentObject. O desempenho não diminui, pois as propriedades derivadas são rapidamente extraídas ou calculadas a partir do estado do aplicativo.

O código acima altera a imagem se o poster do filme mudar.

E isso é feito, na verdade, com apenas uma linha, que conecta as visualizações ao estado. Se você trabalhou com ReSwift no iOS ou até mesmo conectar com React, você entenderá qual é a mágica do SwiftUI.

Agora você pode tentar ativar uma ação e publicar um novo estado. Aqui está um exemplo mais 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 acima, eu uso a ação .onDelete do SwiftUI para cada IP. Isso permite que a linha na lista exiba o gesto normal de deslizar do iOS para exclusão. Portanto, quando o usuário toca no botão de excluir, ele aciona a ação correspondente e remove o filme da lista.

E como a propriedade da lista é derivada do estado BindableObject e é injetada como EnvironmentObject, o SwiftUI atualiza a lista, já que o ForEach está vinculado à propriedade computada movies.

Aqui está uma parte do redutor MoviesState:

func moviesStateReducer(state: MoviesState, action: Action) -> MoviesState {
    var state = state
    switch action {
    
    // outras ações.
    
    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 redutor é executado quando você envia uma ação e retorna um novo estado, como mencionado acima.

Ainda não vou entrar em detalhes — de onde o SwiftUI realmente sabe o que exibir. Para entender isso mais profundamente, vale a pena assistir à sessão WWDC sobre fluxo de dados no SwiftUI. Também é discutido em detalhes por que e quando usar Estado, @Binding, ObjectBinding e EnvironmentObject.

A Skillbox recomenda:

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster