اولین پروژه ناموفق SwiftUI من

برای شروع سال 2021 چه راهی بهتر از فکر کردن به یکی از اهداف اصلی من در سال 2020 وجود دارد: یادگیری SwiftUI و ساخت اولین برنامه من.
در حالی که من فقط یک مبتدی در دنیای توسعه iOS بودم و هنوز هستم، احساس کردم که بهترین راه برای یادگیری ساختن یک برنامه از ابتدا است. من این ایده را برای یک برنامه هواشناسی ساده اما زیبا داشتم که ساختن آن بسیار آسان به نظر می رسید. با این حال خیلی چیزها طبق برنامه پیش نرفت 😅 چه عدم برنامه ریزی باشد، چه توقعات بیش از حد بالا، یا فقط تجربه خود توسعه دهنده، هرچه به اتمام این پروژه نزدیک تر می شدم، کمتر مطمئن می شدم که برنامه من ارزش گذاشتن در اپ استور را دارد.. بنابراین اگر بخواهید میتوانیم این پروژه را شکست خورده بنامیم، اما من هنوز به چیزی که در نهایت ساختم افتخار میکنم!
بنابراین احساس کردم که نگاهی به کل این تجربه موضوع جالبی برای یک پست وبلاگ خواهد بود. بیایید نگاهی به اولین برنامه SwiftUI من بیندازیم، آنچه ساختم، برخی از چالش هایی که با آن مواجه شدم و باعث شد چیزهای زیادی یاد بگیرم و چرا نتوانستم این پروژه را نهایی کنم.
من Sunshine، برنامه آب و هوای خود را در تابستان و پاییز 2020 ساختم. اگر من را در توییتر دنبال کنید، ممکن است تعداد زیادی اسکرین شات، ویدیوهایی را دیده باشید که نشان می دهد این برنامه در طول توسعه چگونه تکامل یافته است. برای کسانی که فرصت دیدن آنها را نداشته اند، در اینجا یک اسکرین شات کوچک برای شما وجود دارد که نشان می دهد آنچه من ایجاد کردم:
هدف من ساختن یک برنامه آب و هوای ساده و زیبا، با داراییهای «خانگی» که بر اساس آب و هوا در یک مکان معین روی صفحه متحرک میشوند.
چیزی که آن را در مقایسه با سایر برنامهها منحصربهفرد میکند، داراییای بود که من ایجاد کردم (خورشید، ابرها، باران)، تمرکز بر UX، و انیمیشنهای کوچک پراکنده در سراسر رابط. اگرچه این یک چالش بود، اما سعی کردم از این اصول حمایت کنم مرحله نمونه سازی و در طول توسعه این اپلیکیشن
این برنامه دارای سه صفحه اصلی است که هر کدام نقشی ساده دارند، اما با جزئیات کوچک برای ظاهری تمیز:
صفحه اصلی
صفحه اصلی شامل نام مکان، تاریخ و یکی از پیچیده ترین نماهای SwiftUI است که برای این برنامه ایجاد کرده ام: نقشه آب و هوا.

این نقشه برای UX مرکزی است. تمام اطلاعات مربوط به شرایط آب و هوایی فعلی را در یک نگاه نشان می دهد، مانند:
-
دما
-
توصیف آب و هوا
-
سایر شاخص ها: سرعت باد، رطوبت و غیره.
-
موقعیت خورشید در طول روز
-
زمان طلوع و غروب خورشید
-
نمایش متحرک آب و هوا: طلوع خورشید، لغزش ابرها از کناره های نقشه و غیره.
رنگ نقشه نیز با شرایط آب و هوایی و زمان روز سازگار است. در ظهر یک شیب آبی و در هنگام سحر یک گرادیان پاستلی نارنجی تر، زمانی که هوا ابری است یک رنگ خاکستری تر و غیره خواهید داشت.
پانل پیش بینی
کشیدن پانل پایین به بالا نشان می دهد پانل پیش بینی. من احساس کردم این ایده خوبی است که پیچیدگی این پنل را به دور از صفحه اصلی پنهان کنم و در عین حال کاربر را هنگام نمایش در صفحه اصلی “در متن” نگه دارم.

در این صفحه می توانید هر دو را ببینید:
هر نقشه دما را نشان می دهد و شرایط آب و هوایی از طریق ترکیبی از نماد و گرادیان پس زمینه منعکس می شود، درست مانند نقشه آب و هوا در صفحه اصلی.
پنل تنظیمات
ضربه زدن روی نماد منو در گوشه سمت چپ بالا منجر به پنل تنظیمات. اینجاست که میتوانید برخی از تنظیمات و همچنین فهرست مکانها را مدیریت کنید.

در حالی که Sunshine از آنچه که اخیراً دیدیم ساده به نظر میرسد، اما مجموعهای از چالشها و ناکامیهای خود را در طول توسعه ارائه کرد… که بسیار عالی بود! 🎉 این چالشها به من این امکان را میدهد که با تمرکز صرف بر پروژههای کوچک حول یک جنبه خاص از SwiftUI خیلی بیشتر از آنچه میتوانم یاد بگیرم، بنابراین اگر اکنون از من بپرسید، تمام این ناامیدی ارزشش را داشت!
چالش ها، شکست ها و آنچه در این مسیر آموختم
ساختن یک برنامه کامل SwiftUI از ابتدا می تواند کمی سخت باشد. من اکثراً همانطور که معمولاً با هر پروژه پیچیده پیش می رفتم: یک ویژگی در یک زمان، مراحل کوچک، تجزیه هر مشکل به وظایف کوچکتر قابل دستیابی.
با این حال، چند مسئله وجود داشت که در طول توسعه ویژگیهای چالش برانگیز به وجود آمد. در اینجا لیستی از موارد جالب است که من شخصا انتخاب کردم:
TabView با PageTabViewStyle
من از قطعه کد زیر برای پیاده سازی یک TabView ساده با صفحات قابل پیمایش چپ/راست استفاده کردم:
اجرای اولیه TabView با PageTabViewStyle مورد استفاده در Sunshine
11
print("Call API to fetch weather data")12
fetchWeatherData(city)17
struct ContentView: View {18
@State private var selected = 021
TabView(selection: $selected) {22
MainView(city: "New York").tag(0)23
MainView(city: "San Francisco").tag(1)25
.tabViewStyle(PageTabViewStyle())
در مورد من، من می خواستم این کامپوننت TabView کارهای زیر را انجام دهد:
-
هر “صفحه” زمان را در یک مکان مشخص نشان می دهد
-
کشیدن انگشت به صفحه دیگر، زمان مکان قبلی/بعدی را نشان می دهد
-
وقتی کشیدن انگشتتان تمام شد، یعنی. ایندکس صفحه نمایش داده شده در حال حاضر تغییر کرده است، من استفاده می کنم
onAppearاصلاح کننده برای تشخیص قابل مشاهده بودن صفحه و یک تماس API برای بازیابی داده های زمانی مکان قابل مشاهده فعلی.
کل برنامه حول همین چند خط و ایده صفحات طراحی شد و کار کرد… تا iOS 14.2 🤦♂️ . اگر کد بالا را کپی کنید و امروز آن را امتحان کنید، خواهید دید onAppear به جای یک بار، چندین بار فراخوانی شود! من این مشکل را به انجمن SwiftUI در Reddit گزارش کردم، و متأسفانه به نظر میرسد که هر توسعهدهنده iOS به چنین اتفاقی عادت کرده است. خیلی راحت نیست، می دانم… و بسیاری از توسعه دهندگان این ناامیدی را به اشتراک می گذارند:
آیا ارتقاء سیستم عامل، حتی یک مورد جزئی، برنامه شما را خراب می کند؟ این دیوانگی است. کلیک دکمه کار نمی کند زیرا کاربر من در حال ارتقاء از iOS 13 به iOS 14 است. برنامه من نیز از کار می افتد زیرا هنگام ارتقاء به BigSur از opacity 0 استفاده می کنم. — فیلیپ یانگ، خالق Session
من به عنوان کسی که بیشتر در وب کار می کنم، به آن عادت ندارم اصلا به این نوع سوالات وقتی این پروژه را شروع کردم حتی به ذهنم خطور نکرد که این امکان وجود داشته باشد.
رفع؟ بهجای بررسی اینکه آیا یک نمای در TabView «ظاهر میشود»، وضعیت ایندکس را به «تماشا شده» منتقل میکنم و هنگامی که تغییری در نمایه مشاهده میشود، تماس API خود را راهاندازی میکنم:
آخرین پیاده سازی TabView با PageTabViewStyle که در Sunshine استفاده شده است
3
class PageViewModel: ObservableObject {8
@Published var selectTabIndex = 011
struct MainView: View {23
struct ContentView: View {24
@StateObject var vm = PageViewModel()26
var cities: [String] {27
return ["New York", "San Francisco"]38
TabView(selection: $vm.selectTabIndex) {39
MainView(city: cities[0]).tag(0)40
MainView(city: cities[1]).tag(1)42
.onReceive(vm.$selectTabIndex, perform: { idx in44
print("PageView :: body :: onReceive" + idx.description)45
print("Call API to fetch weather data")46
fetchWeatherData(cities[idx])48
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
با توجه به این مشکل، برنامه در iOS 14.2 به حالت نیمه شکسته در آمد و من برای انجام این کار بازسازی زیادی داشتم. من توسعه Sunshine را تقریباً از ابتدا مجدداً راه اندازی کردم 😬
استفاده از MapKit برای ایجاد یک سرویس مکان
یکی از چیزهایی که هنگام شروع توسعه iOS می تواند عجیب باشد، دانستن این موضوع است که SwiftUI هنوز تا به امروز “ناتمام” است. بسیاری از APIهای اصلی هنوز برای SwiftUI در دسترس نیستند و تنها راه تعامل با آنها از طریق UIKit است. یکی از آن API هایی که باید با آن کار می کردم این بود MapKit.
Sunshine برای جستجوی شهرها و دریافت مختصات lat/long مربوطه به یک “سرویس مکان” ساده نیاز داشت. برای این کار مجبور شدم از MapKit استفاده کنم و اینجاست که همه چیز بسیار پیچیده شد:
سخت ترین قسمت در واقع دانستن بود کلمات کلیدی مناسب برای جستجو برای چیزی که من باید استفاده می کردم ترکیبی از موارد زیر بود:
-
MKSearchCompleter: یک ابزار MapKit برای خروجی لیستی از مکان ها بر اساس یک رشته جزئی: به عنوان مثال. نمایش نوو نیویورک، نیوجرسی را نشان خواهد داد -
MKLocalSearch: یک ابزار MapKit با تمام ابزارهای جستجوی POI: این چیزی است که من برای دریافت مختصات مرتبط با یک نتیجه MKSearchCompleter استفاده کردم.
با دانستن اینکه اینها توابع کمکی MapKit هستند که باید برای ایجاد “سرویس موقعیت مکانی” خود از آنها استفاده کنم، زمان زیادی را صرف کندوکاو در اسناد کردم. این می تواند در ابتدا کمی ناامید کننده باشد، به خصوص به عنوان یک توسعه دهنده ظاهری که در آن عادت دارم از طریق یک مشکل یا ناشناخته “راه خود را در گوگل جستجو کنم”.
در صورتی که شخصی نیاز به ساخت این نوع «سرویس مکان» داشته باشد، کد را دقیقاً در زیر پیدا خواهید کرد. من برخی از نظرات را اضافه کرده ام تا تا آنجا که می توانم در قالب کوچک توضیح دهم، اما ممکن است در آینده یک پست وبلاگ اختصاصی در این مورد بنویسم:
یک سرویس مکان یابی برای جستجوی شهرها و دریافت مختصات آنها پیاده سازی کنید
7
class LocationSearchService: NSObject, ObservableObject, MKLocalSearchCompleterDelegate {13
@Published var searchQuery = ""15
@Published var completions: [MKLocalSearchCompletion] = []17
var completer: MKLocalSearchCompleter18
var cancellable: AnyCancellable?21
completer = MKLocalSearchCompleter()24
cancellable = $searchQuery.assign(to: \.queryFragment, on: self.completer)25
completer.delegate = self26
completer.resultTypes = .address34
func completer(_ completer: MKLocalSearchCompleter, didFailWithError: Error) {45
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {46
self.completions = completer.results50
extension MKLocalSearchCompletion: Identifiable {}54
struct ContentView: View {55
@ObservedObject var locationSearchService: LocationSearchService59
TextField("type something...", text: $locationSearchService.searchQuery)60
}.onChange(of: locationSearchService.completions) {68
getCoordinatesLocation(locationSearchService.completions[0])71
func getCoordinatesLocation(_ completion: MKLocalSearchCompletion) {73
let searchRequest = MKLocalSearch.Request(completion: completion)75
let search = MKLocalSearch(request: searchRequest)77
search.start { response, error in82
guard let coordinates = response?.mapItems[0].placemark.coordinate else {86
guard let name = response?.mapItems[0].name else {
کاربر پیش فرض در مقابل داده اصلی
SwiftUI سیستمی به نام ارائه می کند UserDefaults برای ذخیره تنظیمات کاربر، بسیار شبیه به LocalStorage در وب ادغام آن در یک پایگاه کد موجود ساده و آسان است:
یک مثال کوچک که نحوه استفاده از UserDefaults را نشان می دهد
1
let defaults = UserDefaults.standard2
defaults.set("celsius", forKey: "temperature")3
defaults.set("mph", forKey: "speed")
من برنامه ریزی کردم استفاده کنم UserDefaults برای ذخیره برخی تنظیمات برگزیده کاربر: کاربر میخواهد از کدام واحد بین کلوین، سلسیوس یا فارنهایت برای نمایش دما و همچنین لیستهای «مکانها» استفاده کند.
همونجا بود که زدم به دیوار 🤕… مستندات رو با دقت نخوندم UserDefaults: شما نمی توانید انواع کاربر را در این سیستم ذخیره کنید (حداقل خارج از جعبه) و در مورد من “مکان” من به عنوان یک نوع سفارشی تعریف شده است:
نوع مکان مورد استفاده در Sunshine
تنها راه حرکت رو به جلو استفاده بود CoreData، سیستم دیگری که به ذخیره داده هایی که با انواع پیچیده تری تعریف شده اند کمک می کند.
با این حال، ادغام CoreData در میانه پروژه بسیار پیچیده به نظر می رسید، بنابراین من به تازگی تصمیم گرفتم یک پروژه کاملاً جدید XCode را دوباره راه اندازی کنم، با فعال بودن CoreData این بار و کد را کپی کنید 😅. عدم برنامه ریزی کامل از طرف من
اجرای ناموفق پروژه
اسکرین شات ها و ویدیوهای قسمت اول و جزئیاتی که در مورد مشکلاتی که با آن مواجه شدم و در نهایت در قسمت دوم حل کردم، ممکن است باعث شود تعجب کنید که چرا این برنامه در پایان منتشر نشد.
پاسخ به آن این است من فقط روی آن کار نکردم. من چندین دلیل دارم که چرا و این قسمت روی موارد اصلی تمرکز دارد.
بیشتر از چیزی که بتوانم بجوم گاز گرفتم
بیایید با چیز بدیهی شروع کنیم، که در نیمه راه توسعه برنامه متوجه شدم: برای پروژه اول خیلی جاه طلبانه بود. می توان یک برنامه آب و هوای بسیار ساده ایجاد کرد، اما دیدگاهی که من برای خود داشتم کمی پیچیده تر و دشوارتر بود. من تعداد زیادی نماهای سفارشی ایجاد کردم، مجبور شدم برخی از ابزارهای UIKit را ادغام کنم، تماس های API و هزاران انیمیشن برقرار کنم..
شاید اولین برنامه من باید کمی سادهتر میبود، مانند یک برنامه نمایشی که فقط بر روی UX متمرکز شده بود (که در اصل همان چیزی بود که من میخواستم بیشتر روی آن تمرکز کنم).
تعهد
"حادثه به روز رسانی iOS 14.2" که برنامه من را شکست، طعم بدی را در دهانم گذاشت. این باعث شد به تعهدی که باید در پروژه iOS بگذاریم فکر کنم.
یک آپدیت ساده iOS می تواند به راحتی اپلیکیشن شما را خراب کند، به خصوص بر اساس SwiftUI، تا جایی که می تواند کاملا غیر قابل استفاده باشد. تنها راه جلوگیری از این امر به عنوان یک توسعه دهنده iOS این است که برنامه خود را به محض انتشار روی هر نسخه بتا iOS آزمایش کنید. اگر قرار باشد خودم را تماماً وقف این پروژه کنم من در یک مسابقه دائمی با چرخه به روز رسانی اپل خواهم بود و نمیتوانستم یک بهروزرسانی را با خطر گرفتن رتبهبندی بد یا ناامید کردن کاربرانم از دست بدهم.
این چیزی نیست که من معمولاً هنگام کار بر روی یک پروژه مبتنی بر وب نگران آن باشم.
در بالای آن انتشار یک پچ یا نسخه جدید یک برنامه iOS به طور قابل توجهی کندتر و پیچیده تر از وصله برنامه وب شما است.: هیچ شرکت شخص ثالثی هنگام به روز رسانی وب سایت یا SaaS شما را بررسی نمی کند. شما فقط مشکلات را برطرف می کنید، اسکریپت های استقرار خود را اجرا می کنید و کارتان تمام است! برای اپلیکیشنهای iOS، باید مراحل بررسی App Store را طی کنید، که میتواند زمان قابل توجهی را ببرد. وقتی این پروژه را شروع کردم همه این عناصر را در نظر نگرفتم.
این منتقد اکوسیستم اپل نیست، به دور از آن. من مطمئن هستم که اگر پروژه من پیچیدگی کمتری داشت، این ایرادات را به راحتی می توان به حداقل رساند.
نتیجه انتظارات را برآورده نکرد
اگرچه ممکن است Sunshine در ویدیوها و اسکرین شات ها عالی به نظر برسد، اما در واقعیت داستان متفاوتی است.
این برنامه در برخی مواقع کند بود. کشیدن صفحات به صورت تصادفی فریم ها را کاهش می دهد حتی اگر همه انیمیشن ها را غیرفعال کنم یا نماهای پیچیده را پنهان کنم. چند نشت حافظه وجود دارد که من تمام تلاش خود را برای ردیابی آنها انجام دادم. با این حال، پس از هفته ها تحقیق و بدون پیشرفت، من فقط تسلیم شدم.
آیا دلایل اصلی مربوط به خود SwiftUI است؟ یا نحوه استفاده من از آن؟ من هنوز راهی برای دانستن ندارم SwiftUI هنوز در مراحل ابتدایی خود است و اگرچه اپل سرمایه گذاری زیادی روی آن انجام داده است، اما گاهی اوقات احساس می کند هنوز در برخی زمینه های خاص آماده نیست.
این آخری بعد از آن همه کار بسیار دلسرد کننده بود. احتمالا اینطور است دلیل اصلی من به طور کامل کار در Sunshine را متوقف کردم و چرا در حالت ناتمام گیر کرده است. نتیجه فقط به چیزی که من در ابتدا تصور می کردم و می خواستم منتشر کنم، نبود.
همچنین، نقاشی دارایی های خودم خیلی بیشتر از آنچه فکر می کردم طول کشید. شرایط آب و هوایی بسیار زیادی وجود داشت که نمیتوانم آن را مدیریت کنم و من نتوانستم با مهارتهای فعلی فیگما برای برخی از آنها نتیجه رضایتبخشی ارائه دهم.
قیمت
احتمالاً کم اهمیت ترین دلیل، اما هنوز هم قابل ذکر است. من از API یک تماسی Open Weather Map برای ارائه اطلاعات دقیق آب و هوا استفاده کردم. آنها یک ردیف آزاد مناسب دارند که برای توسعه عالی است. با این حال اگر آن را رها کنم، به سرعت از حد مجاز تماس در ساعت/روز فراتر میروم.
سطح بعدی 40 دلار در ماه است که من به راحتی می توانم آن را بپردازم، اما سطح بعدی 180 دلار در ماه است که باعث شد کمی بیشتر فکر کنم: آیا آنقدر در مورد این پروژه جدی بودم که شروع به خرج کردن مبلغ قابل توجهی برای مدیریت آن در طول زمان کنم یا فقط برای سرگرمی بود؟
با وجود همه ناکامی ها و "عذاب" قریب الوقوع این پروژه، من هنوز خیلی لذت بردم! من دوست داشتم سفرم و راه حل هایم را برای مشکلات کوچکی که در طول مسیر با آن مواجه می شوم با همه شما در توییتر به اشتراک بگذارم. دیدن این برنامه که به آرامی شکل می گیرد فوق العاده رضایت بخش بوده است. من مطمئن هستم که درس های آموخته شده در اینجا بسیار مفید خواهد بود و موفقیت پروژه های SwiftUI آینده من را تضمین می کند.
این پروژه همچنین به من کمک کرد تا بفهمم چقدر ما توسعه دهندگان فرانت اند/وب خوش شانس هستیم. سرعتی که با آن می توانیم ایده ای را از نمونه اولیه تا محصول، ابزارها و جامعه ای که داریم توسعه دهیم، چیزی است که باید برای ما ارزش قائل باشیم.
با این حال، من به ساختن چیزها با SwiftUI ادامه خواهم داد. پروژه بعدی من احتمالاً بسیار ساده خواهد بود، مانند مواردی که در قسمت قبل ذکر کردم، یا شاید فقط یک سری برنامه های کاربردی / آزمایش های کوچک مانند @jsngr خیلی خوب انجام می دهد این اولین پروژه شکست خورده SwiftUI من بود، احتمالا آخرین پروژه من نخواهد بود. هنوز چیزهای زیادی برای یادگیری و ساختن چیزهای سرگرم کننده زیادی وجود دارد.
آیا میخواهید بیشتر از محتوای مرتبط با SwiftUI من را بررسی کنید؟



