الوصفة السحرية لإنشاء واجهات المستخدم لل iOS
في هذا المقال اعرض لكم طريقتي في تصميم واجهات المستخدم باستخدام ال CollectionView
بسم الله الرحمن الرحيم
في هذا المقال راح نستعرض طريقتي المفضلة في تصميم واجهات المستخدم باستخدام ال CollectionView و يعتبر هذا الدرس متوسط الصعوبة.
المتطلبات:
- المام بنظام ال iOS
- المام بال CollectionView
- المام بال Delegate Design Pattern
- معرفة بال Auto Layout
الفكرة:
سوف نقوم بالاستفادة من ال UICollectionView
بتجهيز قوالب واجهة المستخدم و هي عبارة عن UICollectionViewCell
وهذه القوالب سوف تحتوي على أساس واجهة المستخدم من أزرار و عبارات و غيرها. وبالمثال راح يتضح المقال بإذن الله.
الخطوات:
بعد إنشاء المشروع و تجهيزه نقوم بعمل class جديد يرث UICollectionViewController
و نسميه DetailsCollectionViewController
(راح نترك الكلاس هذا و نقوم بتجهيز ال ViewModel
)
الآن سوف نقوم بتجهيز ال ViewModel
وسيكون هو الأساس اللي راح يغذي ال DetailsCollectionViewController
نقوم بإنشاء كلاس و نسميه DetailsViewModel
ونضع هذا الكود بداخله
import Foundation
import UIKit
// هذا ال Enum يمثل انواع الواجهات التي سنحتاجها في تطبيقنا
// مثلا هنا نحتاج ترأيس للمحتويات و نحتاج عبارات وأزرار و خلية بدون محتويات لإضافة فراغ بين المحتويات
enum DetailsCellType {
case header
case details
case button
case empty
}
// أي Struct ينتهي بكلمة Property سوف يحمل المعلومات اللتي تحتاجها كل خلية لعرض محتوياتها
struct HeaderCellProperty {
let title: String
}
struct DetailsCellProperty {
let title: String
let subtitle: String
}
struct ButtonCellProperty {
let buttonTitle: String
let buttonColor: UIColor
let buttonTitleColor: UIColor
let buttonClickHandler: () -> Void
}
struct EmptyCellProperty {
}
// هذا هو ال View Model ويحتوي على جميع الخصائص المذكورة سابقا
struct DetailsViewModel {
var cellType: DetailsCellType
var headerCellProperty: HeaderCellProperty?
var detailsCellProperty: DetailsCellProperty?
var buttonCellProperty: ButtonCellProperty?
var emptyCellProperty: EmptyCellProperty?
init(headerCellProperty: HeaderCellProperty) {
self.cellType = .header
self.headerCellProperty = headerCellProperty
}
init(detailsCellProperty: DetailsCellProperty) {
self.cellType = .details
self.detailsCellProperty = detailsCellProperty
}
init(buttonCellProperty: ButtonCellProperty) {
self.cellType = .button
self.buttonCellProperty = buttonCellProperty
}
init(emptyCellProperty: EmptyCellProperty) {
self.cellType = .empty
self.emptyCellProperty = emptyCellProperty
}
func reuseIdentifier() -> String {
switch self.cellType {
case .header:
return "HeaderNameCollectionViewCell"
case .details:
return "DetailTextCollectionViewCell"
case .button:
return "ButtonCollectionViewCell"
case .empty:
return "EmptyCollectionViewCell"
}
}
}
نعود الآن الى DetailsCollectionViewController
ونضيف متغير من نوع Array
من نوع DetailsViewModel
var viewModels = [DetailsViewModel]()
ومن ثم نقوم بتجهيز ال CollectionViewDataSource
في كلاس DetailsCollectionViewController
كالتالي
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.viewModels.count
}
// لا نستطيع القيام بشيد هنا حتى نقوم بتجهيز الخلايا
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
// Configure the cell
return cell
}
نقوم الآن بتجهيز الخلايا حتى نستخدمها لعرضها. و نبدأ بال HeaderCollectionViewCell
حيث نقوم بإنشاء كلاس بهذا الاسم يرث UICollectionViewCell
وكذلك نقوم بإنشاء ملف xib بنفس الاسم ونقوم بوضع الكود التالي:
import UIKit
class HeaderCollectionViewCell: UICollectionViewCell {
@IBOutlet weak var headerLabel: UILabel!
@IBOutlet weak var seperatorView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.headerLabel.textColor = .black
self.headerLabel.font = UIFont.preferredFont(forTextStyle: .headline)
self.seperatorView.backgroundColor = .black
}
}
وهذا شكل ملف ال xib
وهذه هي ال Constraints
**ولا ننسى من استخدام ال ReuseIdentifier كما هو في ال ViewModel
الآن سوف نقوم بالعودة الى DetailsCollectionViewController
لنختبر أن ما قمنا به الى الان يعمل بالشكل الصحيح وطريقتي المفضلة هي أن نقوم بعمل Extension على كلاس DetailsCollectionViewController
و يحتوي على الدوال المساعدة لإضافة هذه الخلايا. ويكون هذا الكود بداخل ال Extension
extension DetailsCollectionViewController {
func addHeaderNameWithTitle(title: String) {
let headerCellProperty = HeaderCellProperty(title: title)
let viewModel = DetailsViewModel(headerCellProperty: headerCellProperty)
self.viewModels.append(viewModel)
}
}
و نقوم الآن بتسجيل الخلية الجديدة في ال CollectionView
ومن ثم استخدامها في CellForItemAtIndexPath
كالتالي:
override func viewDidLoad() {
super.viewDidLoad()
let headerNameNib = UINib(nibName: "HeaderCollectionViewCell", bundle: Bundle.main)
self.collectionView?.register(headerNameNib, forCellWithReuseIdentifier: "HeaderNameCollectionViewCell")
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let viewModel = self.viewModels[indexPath.item]
switch viewModel.cellType {
case .header:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: viewModel.reuseIdentifier(), for: indexPath) as! HeaderCollectionViewCell
cell.headerLabel.text = viewModel.headerCellProperty?.title
return cell
default:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
return cell
}
}
الآن قمنا بتجهيز جميع ما نحتاجه و لكن تبقى شيء واحد و هو أن نضمن أن أبعاد الخلية كما يجب و حتى نتمكن من ذلك نقوم بإضافة الكود التالي:
//نحتاج لهذا البروتوكول حتى يقوم النظام باستدعاء الدوال للأحجام
class WalletDetailsViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var height: CGFloat
let viewItem = self.viewModels[indexPath.item]
switch viewItem.cellType {
case .header:
height = 50
case .details:
height = 50
case.button:
height = 100
case .empty:
height = 20
}
let width = self.view.frame.size.width
return CGSize(width: width, height: height)
}
الحمد لله. الآن كل ما تبقى علينا هو استخدام هذا الكود و سوف نقوم بالتعديل على كلاس ViewController
الذي تم إنشائه من ال XCode حسب التالي:
import UIKit
//فقط نجعل الكلاس يرث DetailsCollectionViewController
class ViewController: DetailsCollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
//نقوم باستدعاء الدالة المساعدة لإضافة العنوان/الترأيس
self.addHeaderNameWithTitle(title: "My First Title")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
ولكن قبل ذلك نحتاج أن نضيف في ال Storyboard واجهة جديدة من نوع Collection View Controller وتسنيدها الى الكلاس السابق وتعيينها ك initial view controller و حذف الواجهة القديمة من الStoryboard
وتكون هذه هي النتيجة:
الان سوف نقوم بإعادة الخطوات نفسها لإضافة عبارات من نوع UILabel الى واجهة المستخدم. و سوف تكون من نوع key/value بحيث يكون Label للعنوان و Label آخر للقيمة
نبدأ بتجهيز الخلية كما فعلنا في السابق مع العنوان وننشئ كلاس يرث UICollectionViewCell ونسميه LabelCollectionViewCell ونقوم بإنشاء ملف ال xib كذلك و نقوم بتجهيزه كالتالي:
import UIKit
class LabelCollectionViewCell: UICollectionViewCell {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var subtitleLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.titleLabel.font = UIFont.preferredFont(forTextStyle: .body)
self.subtitleLabel.font = UIFont.preferredFont(forTextStyle: .body)
self.titleLabel.textColor = UIColor.darkText
}
}
ونجهز ملف xib كالتالي:
وال constraints كالتالي:
نقوم الآن بالتعديل على كلاس DetailsCollectionViewController
وتجهيز الدوال المساعدة لإضافة الخلية الجديدة.
func addDetails(with title: String, and subtitle: String) {
let detailsCellProperty = DetailsCellProperty(title: title, subtitle: subtitle)
let viewModel = DetailsViewModel(detailsCellProperty: detailsCellProperty)
self.viewModels.append(viewModel)
}
** ولا ننسى إضافة ال reuseIdentifier كما هو من ال ViewModel
و من ثم نقوم بتسجيل الخلية لاستخدامها في ال CollectionView
كالتالي:
let detailsNameNib = UINib(nibName: "LabelCollectionViewCell", bundle: Bundle.main)
self.collectionView?.register(detailsNameNib, forCellWithReuseIdentifier: "DetailTextCollectionViewCell")
نقوم الآن بالتعديل على CellForItemAtIndexPath
لتجهيز الخلية من النوع الجديد بإضافة case جديد كالتالي:
case .details:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: viewModel.reuseIdentifier(), for: indexPath) as! LabelCollectionViewCell
cell.titleLabel.text = viewModel.detailsCellProperty?.title
cell.subtitleLabel.text = viewModel.detailsCellProperty?.subtitle
return cell
الآن فقط نقوم باستخدام الدالة الجديدة في كلاس ViewController
لإضافة واجهة مستخدم من نوع Key/Value كالتالي:
self.addDetails(with: "Title 1", and: "This is the first subtitle")
self.addDetails(with: "Title 2", and: "This is the second subtitle")
self.addDetails(with: "Title 3", and: "This is the third subtitle")
self.addDetails(with: "Title 4", and: "This is the fourth subtitle")
self.addDetails(with: "Title 5", and: "This is the fifth subtitle")
و تكون هذه هي النتيجة:
الى هنا تنتهي هذه المقالة!!!!
ولكن ماذا عن إضافة الأازرار والخلايا الفراغة؟ سوف أترك عمل هذا كتمرين لكم واستقبل أي استفسارات بهذا الخصوص.
ستجدون الأكواد كاملة هنا على GitHub
الفوائد
- يمكننا انشاء العديد من الواجهات بدون إعادة كتابة الأكواد فقط نقوم بإنشاء كلاس يرث DetailsCollectionViewController
- للشاشات الطويلة سيتكفل ال CollectionView بال scroll ولا نحتاج إلى إضافة أي سطر لعمل ال Scrolling
في الختام شكرا لكم على القراءة واتمنى أن المقالة كانت لها فائدة.
التعليقات (3)
هل الكلمات المحددة بالهيلاتير هي برامج ؟ يعني كيف اطبق ؟ فقط باستخدام الاكواد
وعليكم السلام ورحمة الله وبركاته
عمر طريقتك هذه تحتاج لشخص متفرغ لتصميم الواجهات بال ios ، بمعنى يكون موظف ومتخصص بنوع واحد من العمل حتى يكون ملم فيه بشكل كامل وباحترافية، هذا رأيي.
ووفقت بإذن الله بهذا المقال الفريد من نوعه، بارك الله فيك ولك.
العنود: الكلمات المحددة بالهايلايت هي الاكواد المستخدمة. المقالة للناس اللي عندهم خبرة في برمجة تطبيقات الايفون.
عبدالله: شكرا على مرورك. ماني عارف ليش تحتاج واحد متفرغ؟
لايوجد لديك حساب في عالم البرمجة؟
تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !