在參加 WWDC 2019 的國情咨文會議後,我決定深入研究 SwiftUI。 我花了很多時間來使用它,現在已經開始開發一個對廣泛用戶有用的真實應用程式。
我將其命名為 MovieSwiftUI - 這是一個用於搜尋新舊電影以及使用以下命令將它們收集到集合中的應用程式
提醒: 對於“Habr”的所有讀者 - 使用“Habr”促銷代碼註冊任何 Skillbox 課程可享受 10 盧布的折扣。
技能箱推薦: 教育在線課程
《職業Java開發人員》 .
那麼 MovieSwiftUI 能做什麼呢?
- 與 API 互動——幾乎所有現代應用程式都會這樣做。
- 根據請求載入異步數據,並使用以下命令將 JSON 解析為 Swift 模型
可編碼 . - 顯示根據請求加載的圖像並快取它們。
- 這款適用於 iOS、iPadOS 和 macOS 的應用程式為這些作業系統的使用者提供了最佳的使用者體驗。
- 用戶可以生成數據並創建自己的電影列表。 該應用程式保存和恢復用戶資料。
- 使用 Redux 模式將視圖、元件和模型清晰地分開。 這裡的資料流是單向的。 它可以被完全快取、恢復和覆蓋。
- 該應用程式使用了SwiftUI、TabbedView、SegmentedControl、NavigationView、Form、Modal等基本元件。 它還提供自訂視圖、手勢、UI/UX。
其實動畫很流暢,GIF結果有點生澀
開發該應用程式給了我很多經驗,總的來說,這是一次積極的體驗。 我能夠編寫一個功能齊全的應用程序,13月份我將對其進行改進並將其發佈在AppStore中,與iOS XNUMX的發布同時進行。
Redux、BindableObject 和 EnvironmentObject
我使用 Redux 已經大約兩年了,所以我對它比較熟悉。 特別是,我在前端使用它
我從未後悔選擇 Redux 作為建立 SwiftUI 應用程式的資料流架構。 在 UIKit 應用程式中使用 Redux 時,最具挑戰性的部分是使用儲存、獲取和檢索資料並將其映射到視圖/元件。 為此,我必須建立一個連接器庫(使用 ReSwift 和 ReKotlin)。 效果很好,但是程式碼量很大。 不幸的是,它(還)不是開源的。
好消息! 如果您打算使用 Redux,那麼使用 SwiftUI 唯一需要擔心的是儲存、狀態和化簡器。 透過 @EnvironmentObject,與商店的互動完全由 SwiftUI 負責。 因此,儲存以 BindableObject 開始。
我創建了一個簡單的 Swift 包,
它是如何工作的呢?
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)
}
}
每次觸發動作時,都會啟動變速箱。 它將根據應用程式的當前狀態評估操作。 然後它將根據操作類型和資料傳回新的修改狀態。
好吧,由於 store 是一個 BindableObject,當它的值改變時,它會使用 PassthroughSubject 提供的 willChange 屬性通知 SwiftUI。 這是因為 BindableObject 必須提供 PublisherType,但協定實作負責管理它。 總的來說,這是蘋果公司的一個非常強大的工具。 因此,在下一個渲染週期中,SwiftUI 將根據狀態變化來幫助渲染視圖的主體。
實際上,這就是 SwiftUI 的全部核心和魔力。 現在,在訂閱狀態的任何視圖中,視圖將根據從狀態接收的資料以及發生的變更來呈現。
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())
}
}
應用程式啟動時,Store 將作為環境物件注入,然後可以使用 @EnvironmentObject 在任何視圖中存取。 由於可以從應用程式狀態快速檢索或計算派生屬性,因此不會造成效能損失。
如果電影海報發生變化,上面的程式碼也會更改影像。
這實際上只需一行即可完成,並藉助該行將視圖連接到狀態。 如果您曾在 iOS 上使用過 ReSwift,甚至
現在您可以嘗試啟動操作並發布新狀態。 這是一個更複雜的例子。
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!]))
}
}
}
}
在上面的程式碼中,我對每個 IP 使用 SwiftUI 中的 .onDelete 操作。 這允許清單中的行顯示正常的 iOS 滑動刪除。 因此,當使用者觸摸刪除按鈕時,它會觸發相應的操作並將影片從清單中刪除。
好吧,由於列表屬性派生自 BindableObject 狀態並作為環境物件注入,因此 SwiftUI 會更新列表,因為 ForEach 與電影計算屬性相關聯。
這是 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
}
當您分派一個操作並傳回一個新狀態時,reducer 就會被執行,如上所述。
我不會詳細介紹 - SwiftUI 實際上如何知道要顯示什麼。 為了更深入地理解這一點,值得
技能箱推薦:
- 實踐課程
“移動開發者專業版” .- 應用在線課程
《Python資料分析師》 .- 兩年實踐課程
“我是專業網頁開發人員” .
來源: www.habr.com