
بعد از شرکت در جلسه سخنرانی سالانه در کنفرانس توسعهدهندگان جهانی (WWDC 2019)، تصمیم گرفتم SwiftUI را عمیقاً مطالعه کنم. زمان زیادی را صرف کار با آن کردم و اکنون شروع به توسعه یک اپلیکیشن کاربردی کردهام که میتواند برای طیف وسیعی از کاربران مفید باشد.
من آن را MovieSwiftUI نامیدم - این برنامهای برای جستجوی فیلمهای جدید و قدیمی و همچنین جمعآوری آنها در یک مجموعه با استفاده از من همیشه عاشق فیلم و سینما بودهام و حتی مدتها پیش یک شرکت در این صنعت تأسیس کردم. آن شرکت خیلی جالب نبود، اما اپلیکیشنش جالب بود!
یادآوری می کنیم: برای همه خوانندگان "Habr" - تخفیف 10 روبل هنگام ثبت نام در هر دوره Skillbox با استفاده از کد تبلیغاتی "Habr".
Skillbox توصیه می کند: دوره آموزشی آنلاین .
خب، MovieSwiftUI چه کاری میتواند انجام دهد؟
- با APIها تعامل دارد - تقریباً هر برنامه مدرنی این کار را انجام میدهد.
- دادههای ناهمزمان را بنا به درخواست بارگیری میکند و JSON را با استفاده از [فایل JSON] به مدلهای Swift تجزیه میکند. .
- تصاویر بارگذاری شده بر اساس درخواست را نمایش میدهد و آنها را ذخیره میکند.
- این برنامه برای iOS، iPadOS و ... است. macOS بهترین تجربه کاربری (UX) را برای کاربران این سیستم عامل فراهم میکند.
- کاربران میتوانند دادهها را تولید کرده و فهرست فیلمهای خود را ایجاد کنند. این برنامه دادههای کاربر را ذخیره و بازیابی میکند.
- نماها، اجزا و مدلها با استفاده از الگوی Redux به وضوح از هم جدا شدهاند. جریان دادهها یکطرفه است و میتوان آنها را بهطور کامل ذخیره، بازیابی و بازنویسی کرد.
- این برنامه از اجزای اصلی SwiftUI، از جمله TabbedView، SegmentedControl، NavigationView، Form، Modal و موارد دیگر استفاده میکند. همچنین از نماها، حرکات و UI/UX سفارشی پشتیبانی میکند.

انیمیشن در واقع روان است، اما گیف کمی بریدهبریده است.
کار روی این اپلیکیشن تجربه زیادی به من داد و در کل تجربه مثبتی بود. توانستم یک اپلیکیشن کاملاً کاربردی بنویسم و در ماه سپتامبر، همزمان با انتشار iOS 13، آن را بهبود بخشیده و در اپ استور منتشر خواهم کرد.
Redux، BindableObject و EnvironmentObject

من حدود دو سال است که با Redux کار میکنم، بنابراین درک نسبتاً خوبی از آن دارم. به طور خاص، من از آن در frontend برای موارد زیر استفاده میکنم: وبسایت، و همچنین برای توسعه iOS بومی (Swift) و Android برنامههای (کاتلین).
من هرگز از انتخاب Redux به عنوان معماری جریان داده برای ساخت یک برنامه SwiftUI پشیمان نشدهام. چالشبرانگیزترین جنبههای استفاده از Redux در یک برنامه UIKit، کار با Store و همچنین دریافت و بازیابی دادهها و نگاشت آنها به Viewها/کامپوننتهای شما است. برای انجام این کار، مجبور شدم نوعی کتابخانه کانکتور (در ReSwift و ReKotlin) ایجاد کنم. خوب کار میکند، اما مقدار زیادی کد دارد. متأسفانه، (هنوز) متنباز نیست.
خبر خوب! تنها چیزی که باید در مورد SwiftUI - اگر قصد استفاده از Redux را دارید - نگران آن باشید، store، state و reducerها است. SwiftUI به لطف @EnvironmentObject تمام تعاملات با store را مدیریت میکند. بنابراین، store با یک BindableObject شروع میشود.
من یک پکیج ساده Swift ایجاد کردم، که استفاده اولیه از 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)
}
}هر بار که یک اکشن را اجرا میکنید، کاهنده را فعال میکنید. کاهنده اکشنها را بر اساس وضعیت فعلی برنامه ارزیابی میکند. سپس یک وضعیت جدید و اصلاحشده را بر اساس نوع اکشن و دادهها برمیگرداند.
از آنجایی که این مخزن یک BindableObject است، با استفاده از ویژگی willChange که توسط PassthroughSubject ارائه میشود، 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 در هنگام راهاندازی برنامه تزریق میشود و سپس با استفاده از @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!]))
}
}
}
}در کد بالا، من از اکشن SwiftUI .onDelete برای هر IP استفاده میکنم. این به ردیف لیست اجازه میدهد تا کشیدن انگشت استاندارد iOS برای حذف را نمایش دهد. بنابراین، وقتی کاربر روی دکمه حذف ضربه میزند، اکشن مربوطه فعال میشود و فیلم را از لیست حذف میکند.
خب، از آنجایی که ویژگی list از حالت BindableObject مشتق شده و به عنوان یک EnvironmentObject تزریق شده است، SwiftUI لیست را بهروزرسانی میکند زیرا ForEach به ویژگی computed فیلمهای movies متصل است.
بخشی از کاهشدهندهی 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 چگونه میداند چه چیزی را نمایش دهد. برای درک عمیقتر این موضوع، ارزشش را دارد که در SwiftUI. همچنین به تفصیل توضیح داده شده است که چرا و چه زمانی باید از آن استفاده کرد. ، @Binding ، ObjectBinding و EnvironmentObject.
Skillbox توصیه می کند:
- دوره عملی .
- دوره آنلاین کاربردی .
- دوره عملی دو ساله .
منبع: www.habr.com
