Name of the blog

Short description of the blog

fdgdfgdf

پیاده سازی یک کنترل اختصاصی (Custom control)

در این مبحث، شما یک کنترل جهت امتیاز دهی (rating control) برای برنامه ی FoodTracker پیاده سازی خواهید کرد. در پایان، برنامه ی شما ظاهری مشابه نمونه زیر خواهد داشت:

آنچه خواهید آموخت

·         ایجاد المان های UI اختصاصی به همراه فایل های source code مرتبط. متصل کردن المان های UI در storyboard به کد آن ها.

·         تعریف کلاس اختصاصی.

·         پیاده سازی یک متد initializer (مقداردهنده ی اولیه) داخل کلاس اختصاصی مورد نظر.

·         استفاده از کلاس UIView به عنوان یک ظرف.

·         نحوه ی به نمایش گذاشتن محتوای view ها برای کاربر با کدنویسی (programmatically).

تعریف یک View اختصاصی

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

در زیر نمایی از کنترلی که جهت نمایش و تخصیص امتیاز به غذا پیاده سازی خواهید کرد را مشاهده می کنید:

کنترل نام برده به کاربران این امکان را می دهد تا بر اساس میزان محبوبیت غذا، به آن ستاره تخصیص دهند. همان طور که می بینید این کنترل در کل 5 ستاره نمایش می دهد که پنجمین ستاره طبیعتا نشانگر بالاترین امتیاز برای آن غذا می باشد. زمانی که کاربر بر روی یک ستاره ضربه می زند، تمامی ستاره های منتهی به آن (از جمله خود ستاره ی انتخاب شده) از سمت چپ پر می شوند.

برای اقدام به طراحی ظاهر (UI)، قابلیت تعامل با کاربر و رفتار این کنترل، بایستی ابتدا یک کلاس فرزند view با پیاده سازی اختصاصی از UIView ایجاد کنید (یک custom view subclass از کلاس پایه UIView ایجاد نمایید.)

جهت ایجاد یک کلاس فرزند از UIView، مراحل زیر را گام به گام دنبال نمایید:

1.      این مسیر را طی کنید: File > New > File یا Command-N را فشار دهید.

2.      یک کادر محاوره ای نمایان می شود. در سمت چپ آن، گزینه ی IOS را انتخاب نمایید.

3.      گزینه ی Cocoa Touch Class را انتخاب نموده و بر روی Next کلیک کنید.

4.      داخل فیلد Class، عبارت RatingControl را وارد نمایید.

5.      حال گزینه ی UIView را از فیلد Subclass of انتخاب نمایید.

6.      زبان برنامه نویسی پروژه را بر روی Swift تنظیم نمایید.

7.      بر روی دکمه ی Next کلیک کنید.

محل ذخیره ی فایل به صورت پیش فرض بر روی دایرکتوری پروژه ی جاری شما تنظیم می شود.

گزینه ی Group به صورت پیش فرض بر روی اسم برنامه ی فعلی، FoodTracker تنظیم می شود.

در بخش Targets، می بینید که برنامه ی شما انتخاب شده اما تست های مربوط به آن انتخاب نشده اند.

8.      لازم نیست تنظیمات پیش فرض را دستکاری نمایید. کافی است بر روی Create کلیک کرده تا فایل مورد نظر ایجاد گردد.  

Xcode یک فایل جدید، حاوی RatingControl class: RatingControl.swift ایجاد می کند. RatingControl یک کلاس اختصاصی view، ارث بری شده از کلاس پایه ی UIView است (منظور از کلاس فرزند اختصاصی، کلاسی است که متدهای کلاس پایه در آن بازنویسی شده باشند).

9.      در فایل RatingControl.swift، تمامی comment هایی که همراه با قالب آماده (template) و پیاده سازی الگو (template implementation) ارائه می شوند را حذف نمایید.

در حال حاضر پیاده سازی کلاس بایستی مشابه زیر باشد:

import UIKit

class RatingControl: UIView {

}

View معمولا به دو روش زیر ایجاد می شود: روش اول عبارت است مقدار دهی اولیه ی یک view با یک فریم که به شما امکان می دهد view را به صورت دستی به UI اضافه کنید و روش دوم، عبارت است از واگذار کردن بارگذاری view به storyboard. برای هر یک از روش های نام برده یک متد initializer اختصاصی وجود دارد: به منظور مقداردهی اولیه و ایجاد یک فریم برای view از متد init(frame:) و برای روش دوم، محول کردن بارگذاری view به storyboard، از متد init?(coder:) استفاده می کنیم. یادآور می شویم که initializer متدی است که یک نمونه از روی کلاس جاری می سازد، property های آن کلاس را مقداردهی اولیه نموده و در صورت لزوم سایر تنظیمات آغازین را انجام می دهد.

در آموزش حاضر، از روش دوم اقدام به ایجاد view خواهید نمود (در storyboard با view خود کار خواهید کرد)، از این رو لازم است ابتدا (پیاده سازی) متد init?(coder:) کلاس والد view را override (بازنویسی) نمایید.

به منظور بازنویسی پیاده سازی متد initializer، مراحل زیر را گام به گام دنبال نمایید:

1.      داخل فایل RatingControl.swift، در زیر خط تعریف کلاس، این comment را درج نمایید:

1.       // MARK: Initialization

2.      حال در زیر این comment، ابتدا واژه ی init را تایپ کنید.

خواهید دید که ابزار پیشبینی و تکمیل کد (code completion) محیط کاری Xcode نمایان می شود.

3.      سومین متد را در لیستی که پدیدار می شود (init?(coder:)) انتخاب کرده و سپس کلید Return را فشار دهید.

init?(coder aDecoder: NSCoder) {

}

Xcode خود اسکلت (کد لازم) متد را برای شما درج می کند.

4.      یک پیغام خطا با آیکون قرمز رنگ به نمایش در می آید. بر روی Fix-it کلیک کرده تا Xcode کلیدواژه ی required را به متد initializer اضافه کند.

 

required init?(coder aDecoder: NSCoder) {

}

تمامی کلاس های ارث بری شده از UIView (کلاس های فرزند آن) که متد initializer را پیاده سازی می کنند، می بایست متد init?(coder:) را نیز در بدنه ی خود داشته و پیاده سازی کنند. کامپایلر زبان Swift به این امر واقف بوده و راه حل خود (جهت ویرایش کد) را در قالب fix-it برای شما ارائه می کند. Fix-it ها راه حل هایی هستند که کامپایلر در جواب خطاهای رخ داده به هنگام کدنویسی در اختیار شما قرار می دهد.

5.      کد زیر را جهت فراخوانی متد initializer کلاس والد اضافه نمایید.

super.init(coder: aDecoder)

 

هم اکنون بدنه ی متدinit?(coder:)  بایستی دربردارنده ی پیاده سازی زیر باشد:

required init?(coder aDecoder: NSCoder) {

super.init(coder: aDecoder)

}

به نمایش گذاشتن view اختصاصی

جهت نمایش view دلخواه، می بایست یک view به UI خود اضافه کرده، سپس ارتباطی بین view مورد نظر و کد متناظر آن برقرار نمایید.

جهت به نمایش گذاشتن محتوای view اختصاصی خود در رابط کاربری، مراحل زیر را به ترتیب دنبال نمایید:

1.      ابتدا فایل storyboard را باز نمایید.

2.      داخل storyboard، یک View object از کادر Object library انتخاب کرده، آن را بکشید و در سطح scene جاری جایگذاری نمایید، به طوری که این المان در قالب stackview و زیر آبجکت image view قرار گیرد. 

3.      پس از انتخاب view مورد نظر، کادر Size inspector را با کلیک بر روی آیکون ، در utility area باز نمایید. 

یادآور می شویم که Size inspector با کلیک بر روی دکمه ی پنجم از سمت چپ inspector selector bar باز می شود. در این کادر شما می توانید اندازه و مکان قرار گیری آبجکت مورد نظر را ویرایش نمایید.

4.      از منوی intrinsic size، گزینه ی Placeholder را انتخاب نمایید.  

5.      در زیر منو ی Intrinsic Size، داخل فیلد Height مقدار 44 و داخل فیلد Width مقدار عددی 240 را وارد کنید. اکنون کلید Return را فشار دهید.

در حال حاضر، UI برنامه می بایست ظاهری مشابه زیر داشته باشد.

6.      پس از انتخاب view مورد نظر، کادر  Identity inspector را باز نمایید.

یادآور می شویم که Identity inspector به شما این امکان را می دهد تا آن دسته از property ها و ویژگی های یک آبجکت که با identity آن مرتبط است، نظیر اینکه به کدام کلاس تعلق دارد را ویرایش نمایید. 

7.      داخل این کادر، فیلدی که Class نام دارد را یافته و آن را بر روی RatingControl تنظیم نمایید.

افزودن کنترل Button به View

تا اینجا، اصول اولیه ی یک کلاس فرزند از UIView با پیاده سازی اختصاصی خود (به نام RatingControl) را پایریزی کردید. حال در این بخش تعدادی دکمه به view دلخواه خود اضافه می کنید که کاربر با استفاده از آن ها می تواند به غذای مورد نظر امتیاز بدهد.

کار را با اضافه کردن یک دکمه ی ساده قرمز رنگ آغاز می کنیم که در view به نمایش در می آید.

جهت ایجاد یک دکمه در view اختصاصی خود، مراحل زیر را دنبال کنید:

1.      داخل بدنه ی متد init?(coder:)، دستورات زیر را جهت ایجاد یک دکمه ی قرمز رنگ وارد نمایید:

let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44))

button.backgroundColor = UIColor.redColor()

متد redColor()، رنگ کنترل دکمه را بر روی قرمز تنظیم می کند. در صورت تمایل می توانید سایر مقادیر پیش فرض UIColor نظیر blueColor() یا greenColor() را بکار ببرید.

2.      در زیر آخرین خط، کد زیر را وارد کنید:

1.       addSubview(button)

متد addSubview()، دکمه ای که تعریف کردید را به کلاس RatingControl اضافه می کند. 

پیاده سازی متد init?(coder:)  در بدنه بایستی به صورت زیر باشد:

required init?(coder aDecoder: NSCoder) {

super.init(coder: aDecoder)

 

let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44))

button.backgroundColor = UIColor.redColor()

addSubview(button)

}

برای اینکه به stack view اعلان کنید چگونه و با چه اندازه ای کنترل دکمه را نمایش دهد، لازم است intrinsic content size آن را مشخص نمایید. برای نیل به این هدف، کافی است پیاده سازی متد intrinsicContentSize را مانند زیر بازنویسی کرده تا پارامترهای تنظیم کننده ی اندازه ی دکمه با اندازه ای که قبلا در Interface Builder (برای کنترل مربوز) مشخص کردید، همخوانی داشته باشد:

override func intrinsicContentSize() -> CGSize {

return CGSize(width: 240, height: 44)

}

تست کنید: برنامه ی خود را اجرا کنید. برنامه هم اکنون باید یک مربع قرمز رنگ کوچک در view به نمایش بگذارد. این مربع قرمز رنگ همان کنترل دکمه است که در بدنه ی متد initializer اضافه کردید.

این دکمه و دکمه های دیگری که به برنامه اضافه می کنید، می بایست با کلیک کاربر، یک action را صدا زده و عملیات خاصی را انجام دهند. آن action تغییر امتیاز یک غذا است. 

به منظور اضافه کردن یک action به کنترل دکمه، مراحل زیر را به ترتیب دنبال نمایید:

1.      داخل فایل RatingControl.swift، قبل از آخرین (})، comment زیر را وارد کنید:

1.       // MARK: Button Action

در زیر comment، تابع زیر را وارد کنید:

func ratingButtonTapped(button: UIButton) {

print("Button pressed ")

}

در زمان حاضر، تابع print() را صرفا جهت کسب اطمینان از متصل بودن متد ratingButtonTapped(_:) به دکمه ی مورد نظر فراخوانی می کنیم. این تابع یک پیغام را در خروجی چاپ می کند. در اینجا منظور از خروجی همان ابزار console در پایین محیط کاری Xcode است که به منظور ثبت گزارش (درباره ی خطاها) و اشکال زدایی کد مورد استفاده قرار می گیرد.  

به زودی پیاده سازی این متد را که در حال حاضر صرفا پیغامی را در console چاپ می کند، با یک عملیات (پیاده سازی) واقعی جایگزین خواهید نمود. 

2.      متد init?(coder:) را پیدا کنید:

required init?(coder aDecoder: NSCoder) {

super.init(coder: aDecoder)

let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44))

button.backgroundColor = UIColor.redColor()

addSubview(button)

}

3.      قبل از متد addSubview(button)، این کد را درج نمایید:

2.       button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), forControlEvents: .TouchDown)

از قبل با الگوی target-action آشنایی دارید و از آن برای متصل کردن المان های رابط کاربری در storyboard به action method ها در کد استفاده کردید. در بالا همین کار را انجام می دهید، با این تفاوت که اتصال بین کد و المان UI در storyboard را با کدنویسی بر قرار می کنید. بدین صورت که شما متد ratingButtonTapped(_:) را به آبجکت button متصل می کنید و به مجرد اینکه کاربر بر روی دکمه کلیک می کند، رخداد .TouchDown اتفاق افتاده و به دنبال آن متد مذکور اجرا می شود. این رخداد، همان طور که از نامش پیدا است، به برنامه اعلان می کند که کاربر دکمه مورد نظر را فشار داده است.

از آنجایی که action را در سطح کلاس RatingControl تعریف کردید، به واسطه ی کلیدواژه ی self که به کلاس جاری اشاره دارد، همین کلاس را target و دریافت کننده ی پیغام رخداد فشرده شدن دکمه قرار می دهید.

عبارت #selector در خروجی مقدار Selector را برای متد ارائه شده برمی گرداند. Selector یک مقدار opaque است که با استفاده از آن متد خاصی را انتخاب می کنید و در واقع آن متد را به عنوان خروجی برمی گردانید (مقدار opaque مقداری است که جز نوع، هیچ اطلاعات دیگری درباره ی خود بروز نمی دهد).

 API های قدیمی معمولا از selector برای فراخوانی داینامیک متدها در زمان اجرا استفاده می کردند. اگرچه اغلب API های جدید block ها را جایگزین selector کرده اند، با این حال برخی از متدهای قدیمی همچون performSelector(_:) و addTarget(_:action:forControlEvents:) هنوز selector را (برای اشاره به متدی خاص) به عنوان آرگومان می گیرند.

در مثال جاری، عبارت #selector(RatingControl.ratingButtonTapped(_:)) مقدار selector را به عنوان خروجی برای متد #selector(RatingControl.ratingButtonTapped(_:)) برمی گرداند. با این کار به سیستم اجازه می دهید تا به محض فشرده شدن دکمه، متدی (action method) که شما تعریف نموده و به این دکمه متصل کردید را اجرا کند.

از آنجایی که برای ساخت کنترل دکمه از interface builder استفاده نکردید، برای تعریف action method نیز نیازی به IBAction ندارید. می توانید action را مانند یک متد معمولی تعریف کنید.   

پس از افزودن دستورات فوق، بدنه ی متد init?(coder:) می بایست به صورت زیر باشد:

required init?(coder aDecoder: NSCoder) {

super.init(coder: aDecoder)

let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44))

button.backgroundColor = UIColor.redColor()

button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), forControlEvents: .TouchDown)

addSubview(button)

}

تست کنید: برنامه ی خود را اجرا نمایید. به محض کلیک بر روی دکمه ی قرمز رنگ، بایستی پیغام Button pressed را در Console مشاهده نمایید.

اکنون زمان آن فرا رسیده تا اطلاعات لازم جهت امتیازدهی به غذا و نمایش آن امتیاز برای کاربر را در اختیار کلاس RatingControl قرار دهید. در وهله ی اول بایستی مقدار امتیاز –0،1،2،3،4 یا 5- را ذخیره کرده و رصد کنید. سپس دکمه هایی که کاربر با فشردن آن ها امتیاز غذا را مشخص می کند، ایجاد نمایید. می توانید مقدار امتیاز را با نوع int و مجموعه دکمه ها را به وسیله ی آرایه ای از آبجکت های UIButton تعریف نمایید.  

برای این منظور مراحل زیر را دنبال نمایید:

1.      داخل فایل RatingControl.swift، خط تعریف کلاس را پیدا کنید:

class RatingControl: UIView {

2.      در زیر این خط، کد زیر را وارد نمایید:

// MARK: Properties

var rating = 0

var ratingButtons = [UIButton]()

در حال حاضر تنها یک دکمه در view خود دارید، در حالی که برای نمایش میزان محبوبیت غذا و امتیاز آن در کل به 5 دکمه احتیاج دارید. جهت ایجاد این تعداد کنترل دکمه از یک حلقه ی for-in بهره می گیرد. این حلقه در یک دنباله، برای مثال مجموعه ای از اعداد صحیح، پیمایش کرده و یک مجموعه دستور را به دفعات مشخصی تکرار می کند. بجای یک دکمه، این ساختمان کد تعداد کل 5 دکمه را ایجاد می کند.

جهت ایجاد این تعداد کنترل دکمه، مراحل زیر را دنبال نمایید: 

1.      در فایل RatingControl.swift، متد init?(coder:) را پیدا کنید:

required init?(coder aDecoder: NSCoder) {

super.init(coder: aDecoder)

let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44))

button.backgroundColor = UIColor.redColor()

button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), forControlEvents: .TouchDown)

addSubview(button)

}

2.      حال آن را به صورت زیر ویرایش نمایید:

for _ in 0..<5 {

let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44))

button.backgroundColor = UIColor.redColor()

button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), forControlEvents: .TouchDown)

addSubview(button)

}

برای سازمان دهی کد و اطمینان از توگذاری صحیح تمامی خط های آن، می توانید کل ساختمان کد را انتخاب کرده و سپس Control-I را فشار دهید. 

لازم به ذکر است که عملگر (..<) خود عدد بزرگ را شامل نمی شود، بنابراین این بازه از 0 شروع شده تا 4 را پیمایش می کند که در کل به 5 بار اجرای دستورات منتهی می شود. در نتیجه ی بجای یک دکمه، با یک حلقه 5 دکمه یکجا ایجاد می کنید. در صورتی که نیازی به آگاهی از گام جاری اجرای (گام تکرار) حلقه نیست، می توانید از (_) استفاده کنید.   

3.      در بالای خط addSubview(button)، این دستور را اضافه کنید:

ratingButtons += [button]

هر کنترل دکمه ای که ایجاد می کنید، جهت ذخیره و رصد مقدارش، آن را به آرایه ی RatingsButtons اضافه می نمایید (تک تک دکمه هایی که با هر بار اجرای حلقه ایجاد می شود را داخل خانه های آرایه مزبور می ریزید).

هم اکنون بدنه ی متدinit?(coder:) می بایست مشابه نمونه ی زیر باشد:

required init?(coder aDecoder: NSCoder) {

super.init(coder: aDecoder)

for _ in 0..<5 {

let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44))

button.backgroundColor = UIColor.redColor()

button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), forControlEvents: .TouchDown)

ratingButtons += [button]

addSubview(button)

}

}

تست کنید: حال برنامه ی خود را اجرا نمایید. شاید این طور به نظر برسد که در UI برنامه تنها یک دکمه قابل مشاهده است. اما جالب است بدانید که حلقه ی for-in دکمه های ایجاد شده را مانند پشته بر روی هم قرار داده است. به منظور نمایش صحیح کنترل امتیاز دهی، شما باید چیدمان کنترل های دکمه در view را خود تنظیم نمایید.

عملیات لازم جهت تنظیم چیدمان و نمایش صحیح المان ها در UI، داخل بدنه ی متدی به نام layoutSubviews از کلاس UIView تعریف می شود. سیستم این متد را خود در زمان مناسب فراخوانی کرده و از این طریق به کلاس های ارث بری شده از UIView اجازه می دهد تا subview های مورد نظر را به نحو صحیح و با چیدمان دلخواه برنامه نویس در UI نمایش دهند.

برای تنظیم چیدمان کنترل های دکمه، لازم است پیاده سازی متد نام برده را بازنویسی (override) نمایید.

جهت بازنویسی این متد و نمایش دکمه ها در کنار هم، مراحل زیر را به ترتیب طی نمایید:

1.      داخل فایل RatingControl.swift، در زیر متد init?(coder:)، تابع زیر را وارد نمایید:

override func layoutSubviews() {

}

یادآور می شویم که با استفاده از قابلیت code completion محیط کاری Xcode می توانید (اسکلت) این متد را به صورت آماده در کد خود قرار دهید. 

2.      داخل بدنه ی این متد، کد زیر را تایپ کنید:

var buttonFrame = CGRect(x: 0, y: 0, width: 44, height: 44)

// Offset each button's origin by the length of the button plus spacing.

for (index, button) in ratingButtons.enumerate() {

buttonFrame.origin.x = CGFloat(index * (44 + 5))

button.frame = buttonFrame

}

این کد frame دکمه ها را ایجاد می کند، سپس با استفاده از حلقه ی for-in داخل آرایه ی دکمه ها پیمایش کرده و در هر بار اجرا مقادیر (مربوط به) frame هر یک از کنترل های مزبور را تنظیم می نماید.

متد enumerate() در خروجی یک collection برمی گرداند که داخل این collection تک تک المان های آرایه ی ratingButtons همراه با اندیس متناظر آن ها قرار داده شده است. این collection در واقع مجموعه ای از tuple ها (مقادیر گروهی مثال کلید-مقدار) است و در این مثال، هر tuple شامل یک دکمه + اندیس مربوطه ی آن می باشد. حلقه ی for-in که داخل بدنه ی تابع layoutSubviews مشاهده می کنید، مقدار دکمه و اندیس آن را به ترتیب داخل متغیرهای محلی button و index ذخیره می کند (مقدار آن ها را به متغیرهای محلی ذکر شده bind می کند). با استفاده از متغیر index، محل جدید فریم هر دکمه را محاسبه کرده و بعد آن را داخل متغیر button ذخیره می کند. مکان قرارگیری فریم ها برابر اندازه ی استاندارد دکمه با عرض و طول = 44 + padding = 5 (فاصله ی بین دکمه ها) ، ضرب در اندیس (شماره ی مکان قرار گیری) آن دکمه محاسبه و مقداردهی می شود.

در حال حاضر کد موجود در بدنه ی متد layoutSubviews() می بایست مشابه زیر باشد:

override func layoutSubviews() {

var buttonFrame = CGRect(x: 0, y: 0, width: 44, height: 44)

// Offset each button's origin by the length of the button plus spacing.

for (index, button) in ratingButtons.enumerate() {

buttonFrame.origin.x = CGFloat(index * (44 + 5))

button.frame = buttonFrame

}

}

تست کنید: برنامه ی خود را اجرا نمایید. خواهید دید که این بار دکمه ها در کنار هم قرار گرفته اند. یادآور می شویم که کلیک بر روی هر یک از دکمه ها، با توجه به کدی که تا به اینجا برای برنامه ی خود نوشته اید، صرفا سبب فراخوانی متد ratingButtonTapped(_:) و چاپ شدن پیغامی در console می شود.

می توانید console را برای داشتن فضای کاری بیشتر، پنهان نمایید. برای این منظور کافی است بر روی دکمه ی اشاره شده در تصویر زیر کلیک نمایید:

تنظیم میزان فاصله ی دکمه ها از هم و تعداد آن ها (در قالب دو متغیر/property مجزا)

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

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

جهت تعریف دو متغیر (property) مجزا برای نگهداری فاصله ی بین دکمه ها و تعداد آن ها، مراحل زیر را دنبال نمایید:

1.      داخل فایل RatingControl.swift، بخش // MARK: Properties را پیدا کنید:

// MARK: Properties

var rating = 0

var ratingButtons = [UIButton]()

می توانید با استفاده از functions menu، به سرعت به بخش دلخواه در کد پیمایش کنید (بپرید). این منو با کلیک بر روی اسم فایل، در بالای editor area نمایان می شود.

2.      در زیر property های موجود، کد زیر را وارد نمایید:

let spacing = 5

این ثابت (constant) را جهت تعیین میزان فاصله ی بین دکمه ها در UI، تعریف می کنید.

3.      داخل بدنه ی متد layoutSubviews، مقدار نوشتاری (literal) که برای تعیین میزان فاصله ی بین دکمه ها استفاده می کردید را با ثابت spacing جایگزین نمایید:

buttonFrame.origin.x = CGFloat(index * (44 + spacing))

4.      در زیر ثابت spacing، ثابت دیگری به صورت زیر اضافه نمایید:

let starCount = 5

می توانید این ثابت را جهت تعیین تعداد دکمه هایی (ستاره ها) که کنترل برای کاربر به نمایش می گذارد، بکار ببرید.

5.      اکنون، داخل بدنه ی init?(coder:)، مقدار نوشتاری (literal) که به نشانه ی تعداد دکمه ها در متن برنامه استفاده کردید را با ثابت starCount جایگزین نمایید:

for _ in 0..<starCount {

تست کنید: اپلیکیشن خود را اجرا کنید. رفتار و ظاهر برنامه نباید نسبت به قبل تغییری کرده باشد.

تعریف یک ثابت جهت تعیین اندازه ی کنترل دکمه

اگر بخاطر داشته باشید، مقدار 44 را به نشانه ی اندازه ی کنترل دکمه، در کد خود بکار بردید. اما همان طور که قبلا گفته شد، مقادیر hardcode شده به صورت پراکنده در کد مطلوب نیست و بهتر است این روش کدنویسی را از متن برنامه ی خود حذف کنید. این بار دکمه ها را طوری تنظیم می کنید که خود را با اندازه ی view میزبان تطبیق دهند (container view ای که در ابتدای مبحث به storyboard خود اضافه کردید). این کار را با بازیابی مقدار طول (height) view میزبان و ذخیره ی آن داخل یک ثابت محلی انجام می دهید، سپس به راحتی و تنها یکبار از داخل هر متد به آن دست پیدا می کنید.    

جهت تعریف یک ثابت، برای ذخیره و نگهداری اندازه ی دکمه های کنترل امتیازدهی، مراحل زیر طی نمایید:

1.      داخل بدنه ی متد layoutSubviews()، این کد را قبل از اولین خط پیاده سازی اضافه نمایید:

// Set the button's width and height to a square the size of the frame's height.

let buttonSize = Int(frame.size.height)

با افزودن این کد layout را به طور قابل توجهی انعطاف پذیرتر می سازید.

2.      حال ثابت buttonSize را جایگزین مقدار 44 در بقیه ی کد نمایید:

var buttonFrame = CGRect(x: 0, y: 0, width: buttonSize, height: buttonSize)

// Offset each button's origin by the length of the button plus spacing.

for (index, button) in ratingButtons.enumerate() {

buttonFrame.origin.x = CGFloat(index * (buttonSize + 5))

button.frame = buttonFrame

}

برای اینکه stack view کنترل امتیاز دهی را به صورت دلخواه نمایش دهد، لازم است intrinsic content size (اندازه ی درونی) کنترل مورد نظر را بروز رسانی نمایید. این بار محاسبه ی اندازه ی هر یک از دکمه ها (ستاره ها) و فاصله ی بین آن ها را به متد intrinsicContentSize() واگذار می کنید.

برای این منظور کافی است کد زیر را بکار ببرید:

let buttonSize = Int(frame.size.height)

let width = (buttonSize * starCount) + (spacing * (starCount - 1))

return CGSize(width: width, height: buttonSize)

در متد init?(coder:)، اولین خط کد داخل ساختمان حلقه ی for-in را به صورت زیر ویرایش کنید:

let button = UIButton()

از آنجایی که پراپرتی frame دکمه ها را داخل متد layoutSubviews() تنظیم می کنید، دیگر در زمان ایجاد دکمه ها نیازی به مقدار دهی آن ها نیست.

کد موجود در بدنه ی متد layoutSubviews() اکنون می بایست مشابه نمونه ی زیر باشد:

override func layoutSubviews() {

// Set the button's width and height to a square the size of the frame's height.

let buttonSize = Int(frame.size.height)

var buttonFrame = CGRect(x: 0, y: 0, width: buttonSize, height: buttonSize)

// Offset each button's origin by the length of the button plus some spacing.

for (index, button) in ratingButtons.enumerate() {

buttonFrame.origin.x = CGFloat(index * (buttonSize + 5))

button.frame = buttonFrame

}

}

کد موجود در بدنه ی متد intrinsicContentSize می بایست مشابه نمونه ی زیر باشد:

override func intrinsicContentSize() -> CGSize {

let buttonSize = Int(frame.size.height)

let width = (buttonSize * starCount) + (spacing * (starCount - 1))

return CGSize(width: width, height: buttonSize)

}

بدنه ی متد init?(coder:) نیز بایستی مانند زیر باشد:

required init?(coder aDecoder: NSCoder) {

super.init(coder: aDecoder)

for _ in 0..<5 {

let button = UIButton()

button.backgroundColor = UIColor.redColor()

button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), forControlEvents: .TouchDown)

ratingButtons += [button]

addSubview(button)

}

}

تست کنید: برنامه را اجرا کنید. رفتار و ظاهر برنامه نسبت به قبل تغییر خاصی نکرده است. دکمه ها باید در کنار هم قرار گرفته باشند و کلیک بر روی هر کدام از دکمه ها هنوز بایستی سبب فراخوانی متد ratingButtonTapped(_:) شده و پیغامی را  در console ثبت کند. 

جایگزین کردن عکس ستاره بجای مربع های قرمز رنگ

در این بخش عکس یک ستاره ی پر شده و خالی را به هر یک از دکمه های کنترل اضافه می کنید:

 

می توانید تصاویر فوق را در پوشه ی Images/ از فایل پروژه ی دانلود شده انتخاب کنید یا در صورت تمایل تصاویر دلخواه خود را بکار ببرید. (کافی است مطمئن شوید اسم عکس های مورد استفاده ی شما با اسم عکس ها در کد کاملا همخوانی داشته باشد).  

به منظور افزودن تصویر به پروژه ی خود، مراحل زیر را طی نمایید:

1.      داخل project navigator، با کلیک بر روی Assets.xcassets، ابزار asset catalog را باز کنید.  

یادآور می شویم که asset catalog مکانی است که در آن محتوای برنامه ی خود نظیر عکس، متن و غیره را مدیریت می کنید.

2.      در گوشه ی سمت چپ، بر روی دکمه ی + کلیک کرده و پوشه ی New Folder را از منوی pop-up انتخاب نمایید.

3.      بر روی اسم پوشه دابل کلیک کرده و آن را به Rating Images تغییر دهید.

4.      پس از انتخاب پوشه ی مذکور، در گوشه ی سمت چپ، بر روی دکمه ی (+) کلیک کرده و سپس گزینه ی New Image Set را از منوی pop-up انتخاب نمایید.

هر Image set به منزله ی یک image asset واحد است اما می تواند نسخه های مختلفی از یک عکس را در وضوح تصویر متفاوت داشته باشد.

5.      بر روی اسم image set دابل کلیک کرده و سپس آن را به emptyStar تغییر دهید.

6.      حال عکس ستاره ی خالی را از حافظه ی کامپیوتر انتخاب نمایید.

7.      عکس را کشیده و آن را در فضای خالی 2x داخل image set جاری جایگذاری نمایید. 

2x وضوح تصویر نمایشگر شبیه ساز iPhone 6 می باشد که برنامه ی خود را در محیط آن تست می کنید. عکس مورد نظر قاعدتا باید در این وضوح با بهترین کیفیت نمایش داده شود.

8.      در پایین محیط، گوشه ی سمت چپ، بر روی دکمه ی (+) کلیک کنید و سپس گزینه ی New Image Set را از منوی pop-up انتخاب نمایید.

9.      بر روی اسم image set دو بار کلیک کرده و آن را به filledStar تغییر دهید.

10.   اکنون عکس ستاره ی پر شده را از حافظه ی کامپیوتر خود انتخاب نمایید.

11.   عکس مورد نظر را کشیده و آن را در فضای خالی 2x، داخل image set جاری جایگذاری نمایید.

Asset catalog شما هم اکنون می بایست مشابه زیر باشد:

در گام بعدی کدی می نویسید که عکس مربوطه را در زمان مناسب برای دکمه تنظیم کرده و نمایش می دهد.

جهت تنظیم عکس برای نمایش، مراحل زیر را به ترتیب دنبال نمایید:

1.      فایل RatingControl.swift را باز کنید.

2.      داخل بدنه ی متد init?(coder:)، دو خط زیر را به قبل از ساختمان حلقه ی for-in اضافه نمایید:

let filledStarImage = UIImage(named: "filledStar")

let emptyStarImage = UIImage(named: "emptyStar")

3.      داخل قطعه کد for-in، پس از آن خطی از کد که دکمه در آن مقداردهی اولیه (initialize) می شود، دستورات زیر را درج کنید:

button.setImage(emptyStarImage, forState: .Normal)

button.setImage(filledStarImage, forState: .Selected)

button.setImage(filledStarImage, forState: [.Highlighted, .Selected])

شما برای هر یک از دو حالت دکمه یک عکس مجزا تخصیص می دهید تا بتوانید به راحتی تشخیص دهید چه زمانی دکمه انتخاب شده و چه زمانی کاربر هنوز بر روی دکمه کلیک نکرده است. عکس ستاره ی خالی زمانی نمایش داده می شود که دکمه انتخاب نشده باشد (وضعیت.Normal ). عکس ستاره ی پر شده زمانی نمایش داده می شود که دکمه انتخاب شده (.Selected ) و همچنین زمانی که دکمه همزمان انتخاب و هایلایت شده باشد (.Selected و .Highlighted= وضعیتی که کاربر در حال ضربه زدن بر روی دکمه باشد.)

4.      آن خطی از برنامه که رنگ پس زمینه ی دکمه را بر روی قرمز تنظیم می کند، حذف نمایید:

button.backgroundColor = UIColor.redColor()

از آنجایی که دکمه های شما اکنون عکس های خود را دارند، زمان آن رسیده تا رنگ پس زمینه ی دکمه را حذف نمایید.

5.      کد زیر را اضافه نمایید:

button.adjustsImageWhenHighlighted = false

دستور فوق را به این خاطر اضافه می کنید که عکس به هنگام تغییر بین دو حالت بیش از یکبار هایلایت نشود.

در حال حاضر کد init?(coder:) شما بایستی به صورت زیر باشد:

required init?(coder aDecoder: NSCoder) {

super.init(coder: aDecoder)

let emptyStarImage = UIImage(named: "emptyStar")

let filledStarImage = UIImage(named: "filledStar")

for _ in 0..<5 {

let button = UIButton()

button.setImage(emptyStarImage, forState: .Normal)

button.setImage(filledStarImage, forState: .Selected)

button.setImage(filledStarImage, forState: [.Highlighted, .Selected])

button.adjustsImageWhenHighlighted = false

button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), forControlEvents: .TouchDown)

ratingButtons += [button]

addSubview(button)

}

}

تست کنید: برنامه ی خود را اجرا نمایید. این بار باید بجای مربع های قرمز رنگ، عکس ستاره در UI نمایش داده شوند. با کلیک بر روی هر یک از دکمه ها همچنان فقط متد ratingButtonTapped(_:) صدا خورده می شود و پیغامی در console چاپ می گردد. در این برهه از زمان، با توجه به کدی که برای برنامه نوشته اید، FoodTracker هنوز قادر به تغییر عکس و هایلایت کردن آن به هنگام کلیک کاربر نیست. این رفتار را در گام بعدی برای اپلیکیشن خود تعریف خواهید کرد.  

جایگزین کردن پیاده سازی debugging متد ratingButtonTapped() با پیاده سازی واقعی (پیاده سازی action متصل به UIButton)

برنامه ی شما باید به کاربر اجازه دهد تا با کلیک بر روی ستاره، به غذای مورد نظر امتیاز بدهد. برای این منظور باید پیاده سازی متدratingButtonTapped(_:)  را که در حال حاضر صرفا پیغامی را در console ثبت می کند با عملیات واقعی در بدنه ی متد جایگزین کنید.

جهت پیاده سازی عملیات امتیازدهی در بدنه ی متد ذکر شده، مراحل زیر را دنبال نمایید:

1.      داخل فایل RatingControl.swift، متد ratingButtonTapped(_:) را پیدا کنید:

func ratingButtonTapped(button: UIButton) {

print("Button pressed ")

}

2.      کد زیر را در بدنه ی این متد جایگزین دستور print نمایید:

rating = ratingButtons.indexOf(button)! + 1

متد indexOf(:) دکمه ی مورد نظر را بر اساس اندیس آن در آرایه ای از دکمه ها پیدا کرده و سپس اندیس (شماره ی مکان قرارگیری) آن المان در آرایه را به عنوان خروجی برمی گرداند. از آنجایی که احتمال دارد نمونه ی مورد جستجو در مجموعه ی مورد نظر وجود نداشته باشد، این متد طوری نوشته شده که در خروجی یک عدد صحیح (Int) از نوع optional بازگردانی نماید.

شما می دانید تنها دکمه هایی که action را فعال و سبب اجرای آن می شود، همان دکمه هایی هستند که خودتان تعریف کرده و به آرایه اضافه نمودید. از اینرو مطمئن هستید که متد indexOf به عنوان خروجی شماره ی مکان قرار گیری المان مد نظر را برمی گرداند. می توانید با استفاده از عملگر !، اندیس المان را استخراج نموده و به آن دسترسی داشته باشید. حال جهت بدست آوردن امتیاز مربوطه، عدد 1 را به اندیس المان اضافه می کنید.

توجه داشته باشید که اضافه کردن مقدار 1 ضروری است زیرا اندیس آرایه از 0 آغاز می شود.  

3.      داخل فایل RatingControl.swift، به قبل از آخرین (})، کد زیر را اضافه نمایید:

func updateButtonSelectionStates() {

}

این تابع در واقع یک متد کمکی است که با استفاده از آن وضعیت انتخاب دکمه ها را بروز رسانی می نمایید.  

4.      داخل بدنه ی متد updateButtonSelectionStates()، این حلقه ی for-in را اضافه نمایید:

for (index, button) in ratingButtons.enumerate() {

// If the index of a button is less than the rating, that button should be selected.

button.selected = index < rating

}

این کد داخل آرایه ای از دکمه ها پیمایش کرده، سپس در هر بار تکرار، وضعیت دکمه ها را بر اساس اینکه آیا مقدار اندیس (متغیر index) آن از امتیاز ( مقدار متغیر rating) کوچکتر است یا خیر، تنظیم می کند. در صورت بر قرار بودن شرط، بدین معنی که اگر index < rating نتیجه ی true را بر گرداند، حلقه ی for-in وضعیت دکمه را بر روی selected تنظیم نموده و تصویر ستاره ی پر شده را به نمایش می گذارد. در غیر این صورت، دکمه انتخاب نشده و تصویر ستاره ی خالی به نمایش در می آید.

5.      داخل بدنه ی ratingButtonTapped(_:)، در آخرین خط پیاده سازی، متدupdateButtonSelectionStates() را فراخوانی نمایید:

func ratingButtonTapped(button: UIButton) {

rating = ratingButtons.indexOf(button)! + 1

updateButtonSelectionStates()

}

6.      در بدنه ی متد layoutSubviews()، متد updateButtonSelectionStates() را در آخرین خط پیاده سازی، فراخوانی نمایید:

override func layoutSubviews() {

// Set the button's width and height to a square the size of the frame's height.

let buttonSize = Int(frame.size.height)

var buttonFrame = CGRect(x: 0, y: 0, width: buttonSize, height: buttonSize)

// Offset each button's origin by the length of the button plus some spacing.

for (index, button) in ratingButtons.enumerate() {

buttonFrame.origin.x = CGFloat(index * (buttonSize + 5))

button.frame = buttonFrame

}

updateButtonSelectionStates()

}

لازم است وضعیت انتخاب دکمه هم به هنگام تغییر امتیاز و هم به هنگام بارگذاری view بروز رسانی شود.

7.      در بخش // MARK: Properties، متغیر rating را پیدا کنید:

var rating = 0                                                                                                                                                                                          

8.      اکنون property observer را به متغیر rating اضافه نمایید. property observer تغییرات در مقدار property را رصد کرده و به آن ها واکنش نشان می دهد.

var rating = 0 {

didSet {

setNeedsLayout()

}

}

property observer همان طور که در بالا نیز ذکر شد، یک تکه کد است که به property اضافه شده و تغییرات اعمال شده در آن را دنبال می کند. سپس هربار که مقدار property تنظیم می شود، فراخوانی شده و می توان از آن برای اجرای عملیات بلافاصله قبل/بعد از تغییر مقدار استفاده نمود. به عبارت دقیق تر، didSet یک property observer است که بلافاصله پس از تنظیم مقدار متغیر صدا خورده می شود. در مثال جاری، متد setNeedsLayout() را داخل بدنه ی property observer فراخوانی می کنید که با هر بار تغییر در مقدار متغیر rating، ظاهر/layout را در UI تغییر می دهد. با این کار، اطمینان حاصل می شود که UI همیشه مقدار دقیق متغیر rating را منعکس می کند.

بدنه ی متد updateButtonSelectionStates() هم اکنون بایستی مشابه نمونه ی زیر باشد:

func updateButtonSelectionStates() {

for (index, button) in ratingButtons.enumerate() {

// If the index of a button is less than the rating, that button shouldn't be selected.

button.selected = index < rating

}

}

تست کنید: برنامه ی خود را اجرا کنید. برنامه بایستی 5 ستاره به نمایش گذاشته و شما باید بتوانی با استفاده از آن ها به غذای مورد نظر امتیاز بدهید. به منظور تست، بر روی ستاره ی سوم کلیک کرده تا امتیاز بر روی 3 تنظیم شود.

متصل کردن کنترل امتیاز دهی به کد view controller

آخرین کاری که باید برای تکمیل و راه اندازی کنترل امتیازدهی انجام دهید، این است که یک اشاره گر (در قالب outlet) به این کنترل در کلاس view controller تعریف نمایید.

جهت متصل کردن outlet کنترل امتیازدهی به ViewController.swift، مراحل زیر را به ترتیب دنبال نمایید:

1.      Storyboard خود را باز کنید.

2.      جهت باز کردن ویرایشگر کمکی، بر روی دکمه ی Assistant در نوارابزار محیط کاری Xcode کلیک نمایید:

3.      در صورت نیاز به فضای کاری بیشتر، می توانید project navigator و utility area را با کلیک بر روی دکمه های مربوطه در نوارابزار محیط Xcode، ببندید.  

در صورت لزوم می توانید کادر outline view را نیز پنهان نمایید.

4.      کنترل امتیازدهی را انتخاب نمایید.

محتویات فایل ViewController.swift داخل ویرایشگر کمکی (assistant editor) در سمت راست محیط به نمایش در می آید (اگر محتوای فایل نام برده به نمایش گذاشته نشد، آنگاه می بایست در editor selector bar مراحل روبرو را طی نمایید: Automatic > ViewController.swift ).

5.      بر روی کنترل امتیازدهی کلیک کرده، آن را با اشاره گر موس از canvas به داخل ناحیه ی ویرایشگر بکشید و سپس کنترل مورد نظر را در زیر متغیر photoImageView (داخل فایل ViewController.swift) جایگذاری نمایید.

 

6.      داخل کادر محاوره ای که نمایان می شود، در فیلد Name آن، مقدار ratingControl را وارد نمایید.

لازم نیست به دیگر تنظیمات در این کادر محاوره ای دست بزنید. کادر محاوره ای شما هم اکنون می بایست ظاهری مشابه نمونه ی زیر داشته باشد:

7.      دکمه ی Connect را کلیک نمایید.

اکنون کنترل امتیازدهی در storyboard یک اشاره گر به خود در کلاس ViewController دارد.

پاک سازی کد و اطلاعات غیرضروری از پروژه (Cleanup)

به مراحل نهایی و تکمیل scene جاری در رابط کاربری برنامه ی FoodTracker نزدیک می شوید. اما پیش از تکمیل این صفحه ی محتوا می بایست نسبت به پاک سازی پروژه اقدام نمایید. در حال حاضر برنامه ی FoodTracker رفتار به مراتب پیچیده تری را پیاده سازی کرده و UI متفاوت تری را نسبت به مباحث قبلی به نمایش می گذارد. از اینرو توصیه می شود کدهای غیر ضروری و بلااستفاده را از پروژه حذف نمایید.

در این بخش همچنین المان های رابط کاربری را در stack view وسط چین می کنید تا UI توازن بیشتری پیدا کند.    

جهت پاک سازی لایه ی UI برنامه از کدهای غیرضروری، مراحل زیر را دنبال نمایید:

1.      با کلیک بر روی دکمه ی Standard، ویرایشگر اصلی محیط Xcode (standard editor) را باز نمایید:

2.      حال project navigator و utility area را با کلیک بر روی دکمه های مربوطه در نوارابزار محیط کاری Xcode باز نمایید.

3.      Storyboard را باز نمایید.

4.      دکمه ی Set Default Label Text را انتخاب کنید، سپس با فشردن دکمه ی Delete آن را حذف نمایید.

Stack view با تنظیم مجدد چیدمان المان های UI، فاصله ی خالی که در اثر حذف دکمه ی مورد نظر ایجاد شده را پر می کند.

5.      در صورت لزوم outline view را باز کرده و آبجکت Stack View را از آن انتخاب نمایید.

6.      کادر Attribute inspector را باز کنید.

7.      در Attribute inspector، فیلد Alignment را پیدا کرده و سپس گزینه ی Center را انتخاب نمایید.

المان های موجود در stack view به صورت افقی وسط چین می شوند.  

حال action method متصل به دکمه ی حذف شده را از کد پاک می کنید.

برای این منظور (پاک سازی پروژه از کدهای بلااستفاده)، مراحل زیر را دنبال نمایید:

1.      فایل ViewController.swift را باز کنید.

2.      متد setDefaultLabelText(_:) را از فایل ذکر شده حذف نمایید.

@IBAction func setDefaultLabelText(sender: UIButton) {

mealNameLabel.text = "Default Text"

}

در حال حاضر حذف همین قطعه کد کفایت می کند. در مبحث بعدی تغییراتی را نیز به outlet لیبل اعمال خواهید کرد.

تست کنید: برنامه ی خود را اجرا نمایید. رفتار برنامه نباید نسبت به قبل تغییر کرده باشد. اما این بار دکمه ی Set Default Label Text دیگر وجود ندارد و المان های UI به صورت افقی در وسط صفحه بر روی هم قرار گرفته اند. علاوه بر آن دکمه ها بایستی در یک سطر و کنار هم چیده شده باشند. با کلیک بر روی هر یک از دکمه ها، همچنان متدratingButtonTapped(_:) صدا خورده می شود اما با توجه به تغییراتی که در کد برنامه اعمال کردید، این بار عکس دکمه ها به دنبال اجرای متد نام برده، تغییر می کنند.

مهم: چنانچه در زمان build/کامپایل با مشکل مواجه شدید، می توانید کلیدهای Command-Shift-K را جهت پاک سازی پروژه فشار دهید.

ارسال نظر

Loading