
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 . 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 .
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 .
- 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.

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

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 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, , 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 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 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 no SwiftUI. Também é discutido em detalhes por que e quando usar , @Binding, ObjectBinding e EnvironmentObject.
A Skillbox recomenda:
- Curso Prático .
- Curso Online Aplicado .
- Curso Prático de Dois Anos .
Fonte: habr.com
