
بعد المشاركة في جلسة حالة الاتحاد في WWDC 2019 ، قررت دراسة SwiftUI بالتفصيل. لقد قضيت الكثير من الوقت في العمل معه وبدأت الآن في تطوير تطبيق حقيقي يمكن أن يكون مفيدًا لمجموعة واسعة من المستخدمين.
لقد أطلق عليها اسم MovieSwiftUI - هذا تطبيق للعثور على أفلام جديدة وقديمة ، وكذلك جمعها في مجموعة باستخدام
TMDB API . لطالما أحببت الأفلام وحتى أنشأت شركة تعمل في هذا المجال ، رغم ذلك لفترة طويلة. كان من الصعب استدعاء الشركة باردة ، لكن التطبيق - نعم!
نذكرك: لجميع قراء "Habr" - خصم بقيمة 10،000 روبل عند التسجيل في أي دورة تدريبية في Skillbox باستخدام الرمز "Habr" الترويجي.
توصي Skillbox بما يلي: الدورة التعليمية عبر الإنترنت "Profession Java-developer" .
فماذا تفعل MovieSwiftUI؟
- يتفاعل مع API - وهذا هو ما يفعله أي تطبيق حديث تقريبًا.
- يقوم بتحميل بيانات الطلب غير المتزامن ويوزع JSON في نموذج Swift باستخدام Codable .
- لعرض الصور التي تم تنزيلها عند الطلب وتخزينها مؤقتًا.
- يوفر هذا التطبيق لنظام التشغيل iOS و iPadOS و macOS أفضل UX لمستخدمي أنظمة التشغيل هذه.
- يمكن للمستخدم توليد البيانات ، وإنشاء قوائم الأفلام الخاصة بهم. يقوم التطبيق بحفظ واستعادة بيانات المستخدم.
- يتم فصل المشاهدات والمكونات والنماذج بشكل واضح باستخدام نموذج Redux. دفق البيانات أحادي الاتجاه هنا. يمكن أن يكون مؤقتا بالكامل ، واستعادة والكتابة.
- يستخدم التطبيق المكونات الأساسية SwiftUI ، TabbedView ، SegmentedControl ، NavigationView ، نموذج ، مشروط ، إلخ. كما يوفر طرق عرض مخصصة وإيماءات UI / UX.
في الواقع ، الرسوم المتحركة على نحو سلس ، تحولت GIF إلى أن يكون قليلا تشنجاتأعطاني العمل في التطبيق الكثير من الخبرة ، وعموما هي تجربة إيجابية. تمكنت من كتابة تطبيق يعمل بكامل طاقته ، وفي سبتمبر سأقوم بتحسينه ووضعه في AppStore ، بالتزامن مع إصدار iOS 13.
استرجاع و BindableObject و EnvironmentObject

لقد عملت مع Redux منذ عامين تقريبًا ، لذلك أعرف ذلك جيدًا نسبيًا. على وجه الخصوص ، أستخدمه في الواجهة الأمامية لموقع
React ، وكذلك لتطوير تطبيقات iOS (Swift) و Android (Kotlin) الأصلية.
لم أندم أبدًا على اختيار Redux كهيكل تدفق البيانات لإنشاء تطبيق على SwiftUI. تعمل أصعب اللحظات عند استخدام Redux في تطبيق UIKit مع المتجر ، بالإضافة إلى الحصول على البيانات واستعادتها ومقارنتها بآرائك / مكوناتك. للقيام بذلك ، اضطررت إلى إنشاء نوع من مكتبة الموصل (على ReSwift و ReKotlin). يعمل بشكل جيد ، ولكن الكثير من التعليمات البرمجية. لسوء الحظ ، إنه (ليس بعد) مفتوح المصدر.
خبر جيد! الأشياء الوحيدة التي تقلقك مع SwiftUI - إذا كنت تخطط لاستخدام Redux - هي المتجر والحالات ومخفضات السرعة. يتولى SwiftUI التفاعل مع المتجر بفضلEnvironmentObject. لذلك ، يبدأ المتجر بـ BindableObject.
لقد أنشأت حزمة Swift بسيطة ،
SwiftUIFlux ، والتي توفر الاستخدام الأساسي لـ Redux. في حالتي ، هذا جزء من MovieSwiftUI.
كتبت أيضًا
برنامج تعليمي خطوة بخطوة لمساعدتك في استخدام هذا المكون.
كيف يعمل؟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 ، فسيقوم بإعلام SwiftUI بتغيير في قيمته باستخدام خاصية willChange التي يوفرها PassthroughSubject. وذلك لأن 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()) } }
يتم تطبيق Store كـ EnvironmentObject عند بدء تشغيل التطبيق ، ثم يتوفر في أي طريقة عرض باستخدامEnvironmentObject. لا يتم تقليل الأداء لأن الخصائص المشتقة يتم استردادها بسرعة أو حسابها من حالة التطبيق.
يغير الرمز أعلاه الصورة إذا تغير ملصق الفيلم.
ويتم ذلك في الواقع في سطر واحد ، بمساعدة وجهات النظر التي ترتبط الدولة. إذا كنت تعمل مع ReSwift على نظام iOS أو حتى
تتصل بـ React ، فستفهم ماهية سحر SwiftUI.
والآن يمكنك محاولة تنشيط الإجراء ونشر حالة جديدة. هنا مثال أكثر تعقيدًا.
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!])) } } } }
في الكود أعلاه ، أستخدم إجراء .onDelete من SwiftUI لكل IP. يتيح هذا للسطر الموجود في القائمة عرض انتقاد iOS المعتاد للحذف. لذلك ، عندما يلمس المستخدم زر الحذف ، يبدأ الإجراء المقابل ويزيل الفيلم من القائمة.
حسنًا ، نظرًا لأن خاصية القائمة مشتقة من حالة BindableObject ويتم تنفيذها كـ EnvironmentObject ، يقوم SwiftUI بتحديث القائمة ، لأن ForEach يرتبط بخاصية الفيلم المحسوبة.
هنا جزء من المخفض MoviesState:
func moviesStateReducer(state: MoviesState, action: Action) -> MoviesState { var state = state switch action {
يتم تنفيذ المخفض عند تقديم الإجراء وإرجاع حالة جديدة ، كما ذكر أعلاه.
لن أخوض في التفاصيل الآن - كيف يعرف SwiftUI حقًا ما يجب عرضه. لفهم ذلك بشكل أعمق ، يجب أن
تنظر إلى جلسة عمل WWDC حول تدفق البيانات في SwiftUI. كما يشرح بالتفصيل سبب ووقت استخدام
الحالة و @ الربط و ObjectBinding و EnvironmentObject.
توصي Skillbox بما يلي: