ahmdsat
ahmdsat
عضو شرف
المساهمات :
42
النقاط :
82
التقيم :
1
الاوسمة :
الوصفة السحرية لإنشاء واجهات المستخدم لل iOS Crown_gold

 
[size=33]بسم الله الرحمن الرحيم[/size]

 
في هذا المقال راح نستعرض طريقتي المفضلة في تصميم واجهات المستخدم باستخدام ال CollectionView و يعتبر هذا الدرس متوسط الصعوبة.
 
[size=33]المتطلبات:

- المام بنظام ال iOS
- المام بال CollectionView
- المام بال Delegate Design Pattern
- معرفة بال Auto Layout
 
[size=33]الفكرة:[/size]
سوف نقوم بالاستفادة من ال 
[/size]
الكود:
UICollectionView
[size][size]
 بتجهيز قوالب واجهة المستخدم و هي عبارة عن 
[/size][/size]
الكود:
UICollectionViewCell 
[size][size]
وهذه القوالب سوف تحتوي على أساس واجهة المستخدم من أزرار و عبارات و غيرها. وبالمثال راح يتضح المقال بإذن الله.[/size]
 
[size=33]الخطوات:[/size]
بعد إنشاء المشروع و تجهيزه نقوم بعمل class جديد يرث 
[/size]
الكود:
UICollectionViewController
[size][size]
 و نسميه 
[/size][/size]
الكود:
DetailsCollectionViewController
[size][size]
(راح نترك الكلاس هذا و نقوم بتجهيز ال 
[/size][/size]
الكود:
ViewModel
[size][size]
 )[/size]
الآن سوف نقوم بتجهيز ال 
[/size]
الكود:
ViewModel
[size][size]
 وسيكون هو الأساس اللي راح يغذي ال 
[/size][/size]
الكود:
DetailsCollectionViewController
[size]
نقوم بإنشاء كلاس و نسميه 
[/size]
الكود:
DetailsViewModel
[size][size]
 ونضع هذا الكود بداخله[/size]
[/size]
الكود:
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"

        }

    }

}
[size]
 
 
نعود الآن الى 
[/size]
الكود:
DetailsCollectionViewController
[size][size]
 ونضيف متغير من نوع 
[/size][/size]
الكود:
Array
[size][size]
 من نوع 
[/size][/size]
الكود:
DetailsViewModel

 
الكود:
    var viewModels = [DetailsViewModel]()
[size]
ومن ثم نقوم بتجهيز ال 
[/size]
الكود:
CollectionViewDataSource
[size][size]
  في كلاس 
[/size][/size]
الكود:
DetailsCollectionViewController
[size][size]
 كالتالي[/size]
 
[/size]
الكود:
    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
    }
[size]
 
 
نقوم الآن بتجهيز الخلايا حتى نستخدمها لعرضها. و نبدأ بال 
[/size]
الكود:
HeaderCollectionViewCell
[size][size]
 حيث نقوم بإنشاء كلاس بهذا الاسم يرث 
[/size][/size]
الكود:
UICollectionViewCell
[size][size]
 وكذلك نقوم بإنشاء ملف xib بنفس الاسم ونقوم بوضع الكود التالي:[/size]
 
[/size]
الكود:
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
    }

}
[size]
 
وهذا شكل ملف ال xib 
[ندعوك للتسجيل في المنتدى أو التعريف بنفسك لمعاينة هذه الصورة]
 
وهذه هي ال Constraints
[ندعوك للتسجيل في المنتدى أو التعريف بنفسك لمعاينة هذه الصورة]
**ولا ننسى من استخدام ال ReuseIdentifier كما هو في ال ViewModel
 
الآن سوف نقوم بالعودة الى 
[/size]
الكود:
DetailsCollectionViewController
[size][size]
 لنختبر أن ما قمنا به الى الان يعمل بالشكل الصحيح وطريقتي المفضلة هي أن نقوم بعمل Extension على كلاس 
[/size][/size]
الكود:
DetailsCollectionViewController
[size][size]
 و يحتوي على الدوال المساعدة لإضافة هذه الخلايا. ويكون هذا الكود بداخل ال Extension [/size]
[/size]
الكود:
extension DetailsCollectionViewController {
    func addHeaderNameWithTitle(title: String) {
        let headerCellProperty = HeaderCellProperty(title: title)
        let viewModel = DetailsViewModel(headerCellProperty: headerCellProperty)
        self.viewModels.append(viewModel)
    }
   
}
[size]
 
و نقوم الآن بتسجيل الخلية الجديدة في ال 
[/size]
الكود:
CollectionView
[size][size]
 ومن ثم استخدامها في 
[/size][/size]
الكود:
CellForItemAtIndexPath
[size][size]
 كالتالي:[/size]
 
[/size]
الكود:
    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
        }
    }
[size]
 
الآن قمنا بتجهيز جميع ما نحتاجه و لكن تبقى شيء واحد و هو أن نضمن أن أبعاد الخلية كما يجب و حتى نتمكن من ذلك نقوم بإضافة الكود التالي:
[/size]
الكود:
//نحتاج لهذا البروتوكول حتى يقوم النظام باستدعاء الدوال للأحجام

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)
    }
[size]
 
الحمد لله. الآن كل ما تبقى علينا هو استخدام هذا الكود و سوف نقوم بالتعديل على كلاس 
[/size]
الكود:
ViewController
[size][size]
 الذي تم إنشائه من ال XCode حسب التالي:[/size]
[/size]
الكود:
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.
    }


}
[size]
ولكن قبل ذلك نحتاج أن نضيف في ال Storyboard واجهة جديدة من نوع Collection View Controller وتسنيدها الى الكلاس السابق وتعيينها ك initial view controller و حذف الواجهة القديمة من الStoryboard
وتكون هذه هي النتيجة:
[ندعوك للتسجيل في المنتدى أو التعريف بنفسك لمعاينة هذه الصورة]
 
الان سوف نقوم بإعادة الخطوات نفسها لإضافة عبارات من نوع UILabel الى واجهة المستخدم.  و سوف تكون من نوع key/value بحيث يكون Label للعنوان و Label آخر للقيمة
 
نبدأ بتجهيز الخلية كما فعلنا في السابق مع العنوان وننشئ كلاس يرث UICollectionViewCell ونسميه LabelCollectionViewCell ونقوم بإنشاء ملف ال xib كذلك و نقوم بتجهيزه كالتالي:
 
[/size]
الكود:
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
    }

}

[size]
 
ونجهز ملف xib كالتالي: 
[ندعوك للتسجيل في المنتدى أو التعريف بنفسك لمعاينة هذه الصورة]
وال constraints كالتالي:
[ندعوك للتسجيل في المنتدى أو التعريف بنفسك لمعاينة هذه الصورة]
 
نقوم الآن بالتعديل على كلاس 
[/size]
الكود:
DetailsCollectionViewController
[size][size]
 وتجهيز الدوال المساعدة لإضافة الخلية الجديدة.[/size]
[/size]
الكود:
    func  addDetails(with title: String, and subtitle: String) {
        let detailsCellProperty = DetailsCellProperty(title: title, subtitle: subtitle)
        let viewModel = DetailsViewModel(detailsCellProperty: detailsCellProperty)
        self.viewModels.append(viewModel)
    }
[size]
** ولا ننسى إضافة ال reuseIdentifier كما هو من ال ViewModel
 
و من ثم نقوم بتسجيل الخلية لاستخدامها في ال 
[/size]
الكود:
CollectionView
[size][size]
 كالتالي:[/size]
[/size]
الكود:
        let detailsNameNib = UINib(nibName: "LabelCollectionViewCell", bundle: Bundle.main)
        self.collectionView?.register(detailsNameNib, forCellWithReuseIdentifier: "DetailTextCollectionViewCell")
[size]
نقوم الآن بالتعديل على 
[/size]
الكود:
CellForItemAtIndexPath
[size][size]
 لتجهيز الخلية من النوع الجديد بإضافة case جديد كالتالي:[/size]
[/size]
الكود:
        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
[size]
 
الآن فقط نقوم باستخدام الدالة الجديدة في كلاس 
[/size]
الكود:
ViewController
[size][size]
 لإضافة واجهة مستخدم من نوع Key/Value كالتالي:[/size]
[/size]
الكود:
        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")
[size]
 
و تكون هذه هي النتيجة:
[ندعوك للتسجيل في المنتدى أو التعريف بنفسك لمعاينة هذه الصورة]
 
 
الى هنا تنتهي هذه المقالة!!!!
 
 
ولكن ماذا عن إضافة الأازرار والخلايا الفراغة؟ سوف أترك عمل هذا كتمرين لكم واستقبل أي استفسارات بهذا الخصوص. 
 
ستجدون الأكواد كاملة هنا على GitHub
 
[size=33]الفوائد[/size]
[/size]

  • يمكننا انشاء العديد من الواجهات بدون إعادة كتابة الأكواد فقط نقوم بإنشاء كلاس يرث DetailsCollectionViewController 
  • للشاشات الطويلة سيتكفل ال CollectionView بال scroll ولا نحتاج إلى إضافة أي سطر لعمل ال Scrolling

[size]
 
 
في الختام شكرا لكم على القراءة واتمنى أن المقالة كانت لها فائدة. [/size]
avatar
Dr.MaYaR
المؤسس
المساهمات :
123
النقاط :
241
التقيم :
34
يعطيك العافية على الموضوع
صلاحيات هذا المنتدى:
لاتستطيع الرد على المواضيع في هذا المنتدى