WWDC 2019 の一般教書セッションに出席した後、私は SwiftUI を深く掘り下げることにしました。 私はこれに多くの時間を費やし、現在、幅広いユーザーにとって役立つ実際のアプリケーションの開発を開始しています。
私はそれを MovieSwiftUI と名付けました。これは新しい映画と古い映画を検索し、それらをコレクションに収集するためのアプリです。
リマインダー: 「Habr」のすべての読者が対象 - 「Habr」プロモーション コードを使用してスキルボックス コースに登録すると 10 ルーブルの割引。
スキルボックスは次のことを推奨します。 教育オンラインコース
「職業 Java 開発者」 .
では、MovieSwiftUI では何ができるのでしょうか?
- API と対話します - ほとんどすべての最新のアプリケーションがこれを行います。
- リクエストで非同期データをロードし、JSON を解析して Swift モデルに組み込みます。
コード化可能 . - リクエストに応じてロードされた画像を表示し、キャッシュします。
- iOS、iPadOS、macOS 用のこのアプリは、これらの OS のユーザーに最高の UX を提供します。
- ユーザーはデータを生成し、独自の映画リストを作成できます。 アプリケーションはユーザー データを保存および復元します。
- ビュー、コンポーネント、モデルは Redux パターンを使用して明確に分離されます。 ここでのデータ フローは一方向です。 完全にキャッシュ、復元、上書きすることができます。
- アプリケーションは、SwiftUI、TabbedView、SegmentedControl、NavigationView、Form、Modal などの基本コンポーネントを使用します。 カスタムビュー、ジェスチャー、UI/UX も提供します。
実際、アニメーションはスムーズですが、GIF は少しぎくしゃくしています。
アプリに取り組むことで多くの経験が得られ、全体的には前向きな経験でした。 完全に機能するアプリケーションを作成できたので、13 月にそれを改良して、iOS XNUMX のリリースと同時に AppStore で公開する予定です。
Redux、BindableObject、EnvironmentObject
私は Redux を約 XNUMX 年間扱っているので、比較的よく理解しています。 特に、フロントエンドで使用します。
私は、SwiftUI アプリケーションを構築するためのデータ フロー アーキテクチャとして Redux を選択したことを後悔したことはありません。 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 を提供する必要があるが、プロトコル実装がそれを管理する責任があるためです。 全体として、これは Apple の非常に強力なツールです。 したがって、次のレンダリング サイクルでは、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())
}
}
ストアはアプリケーションの起動時にEnvironmentObjectとして挿入され、@EnvironmentObjectを使用して任意のビューでアクセスできるようになります。 派生プロパティはアプリケーションの状態から迅速に取得または計算されるため、パフォーマンスの低下はありません。
上記のコードは、映画のポスターが変更されると画像を変更します。
そして、これは実際にはたった XNUMX 行で行われ、どのビューが状態に接続されているかを使用します。 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 状態から派生し、EnvironmentObject として挿入されるため、ForEach が映画の計算済みプロパティに関連付けられているため、SwiftUI はリストを更新します。
以下は 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
}
前述したように、リデューサーはアクションをディスパッチして新しい状態を返すときに実行されます。
SwiftUI が実際に何を表示するかをどのように認識するかについては、まだ詳しく説明しません。 これをより深く理解するには価値があります
スキルボックスは次のことを推奨します。
- 実践コース
「モバイルデベロッパーPRO」 .- オンラインコースの申し込み
「Pythonデータアナリスト」 .- XNUMX年間の実践コース
「私はプロのウェブ開発者です」 .
出所: habr.com