यह लेख बड़े कोड बेस वाली परियोजनाओं को स्केल करते समय प्रोटोकॉल-उन्मुख दृष्टिकोण का उपयोग करने पर मेरी श्रृंखला की अगली कड़ी है।
यदि आपने यह लेख नहीं पढ़ा है तोUICollectionViewFlowLayout
पर आधारित स्क्रॉलिंग सूचियों का उपयोग करने के लिए एक निर्माता के निर्माण की अनुमति देगा।
इस दृष्टिकोण की प्रेरणा बहुत सरल है, हम सार्वभौमिक उपकरण बनाकर बॉयलरप्लेट कोड की मात्रा को कम करना चाहते हैं जिससे रूटीन की मात्रा कम हो जाएगी और साथ ही लचीलापन भी नहीं खोएगा।
इस लेख में, हम इसी प्रकार के कार्य पर विचार करना जारी रखेंगे
जैसा कि हमने पहले किया था, हम इस समस्या को 4 चरणों में हल करेंगे:
अमूर्तता का निर्माण निस्संदेह डिजाइन का सबसे महत्वपूर्ण चरण है। स्केलिंग के लिए खुली प्रणाली की नींव रखने के लिए, स्क्रॉलिंग तत्वों की गुणात्मक और मात्रात्मक विशेषताओं से अमूर्त होना आवश्यक है। समान प्रकार के लेआउट के लिए आवश्यकताओं का अनुपालन करना भी महत्वपूर्ण है।
आइए हम ऐसी अवधारणा को पेश करें; एक अनुभाग के रूप में। एक अनुभाग एक या एक से अधिक तत्व होते हैं जिनका लेआउट समान होता है।
हम इस अनुभाग का उपयोग स्क्रॉल करने योग्य तत्वों पर एक अमूर्तता के रूप में करते हैं:
protocol BaseSection { var numberOfElements: Int { get } func registrate(collectionView: UICollectionView) func cell(for collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell func header(for collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionReusableView func footer(for collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionReusableView func section() -> NSCollectionLayoutSection func select(row: Int) }
हम लेआउट को कॉन्फ़िगर करने की जिम्मेदारी अनुभाग को सौंप देंगे। हेडर या फ़ुटर जैसे पूरक दृश्यों की उपस्थिति भी वहाँ निर्धारित की जाएगी।
बेस क्लास का उपयोग स्क्रॉल करने योग्य सूची के रूप में किया जाएगा। बेस क्लास का कार्य BaseSection का सार डेटा लेना और उसे रेंडर करना है। हमारे मामले में, UICollectionView
और UICollectionViewCompositionalFlowLayout
को विज़ुअलाइज़ेशन टूल के रूप में उपयोग किया जाएगा:
class SectionView: UIView { override init(frame: CGRect) { super.init(frame: frame) commonInit() } required init?(coder: NSCoder) { super.init(coder: coder) commonInit() } private func commonInit() { collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] collectionView.frame = bounds addSubview(collectionView) } private(set) lazy var flowLayout: UICollectionViewCompositionalLayout = { let layout = UICollectionViewCompositionalLayout { [weak self] index, env in self?.sections[index].section() } return layout }() private(set) lazy var collectionView: UICollectionView = { let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) collectionView.backgroundColor = .clear collectionView.delegate = self collectionView.dataSource = self return collectionView }() private var sections: [BaseSection] = [] public func set(sections: [BaseSection], append: Bool) { sections.forEach { $0.registrate(collectionView: collectionView) } if append { self.sections.append(contentsOf: sections) } else { self.sections = sections } collectionView.reloadData() } public func set(contentInset: UIEdgeInsets) { collectionView.contentInset = contentInset } } extension SectionView: UICollectionViewDataSource { func numberOfSections(in collectionView: UICollectionView) -> Int { sections.count } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { sections[section].numberOfElements } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { sections[indexPath.section].cell(for: collectionView, indexPath: indexPath) } func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { kind == UICollectionView.elementKindSectionHeader ? sections[indexPath.section].header(for: collectionView, indexPath: indexPath) : sections[indexPath.section].footer(for: collectionView, indexPath: indexPath) } } extension SectionView: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { sections[indexPath.section].select(row: indexPath.row) } }
UICollectionViewCompositionalLayout
, UICollectionViewFlowLayout
के उपयोग की तुलना में, आपको सेल, हेडर और फुटर लेआउट कॉन्फ़िगरेशन को डेलिगेट विधियों से लेआउट बॉडी में स्थानांतरित करने की अनुमति देता है।
इस तथ्य के आधार पर कि अनुभाग, जिसमें पाद लेख और शीर्ष लेख दिखाने की क्षमता शामिल है, को एक अमूर्तता के रूप में लिया गया था, कार्यान्वयन वर्ग में इसे ध्यान में रखना भी आवश्यक है।
इस मामले में, किसी भी सेल के लिए आवश्यकताएँ इस प्रकार होंगी:
protocol SectionCell: UICollectionViewCell { associatedtype CellData: SectionCellData func setup(with data: CellData) -> Self static func groupSize() -> NSCollectionLayoutGroup } protocol SectionCellData { var onSelect: VoidClosure? { get } } typealias VoidClosure = () -> Void
हम सेल आकार के विन्यास को सेल की जिम्मेदारी के क्षेत्र में ले जाते हैं, हम किसी भी सेल पर टैप करके कार्रवाई प्राप्त करने की संभावना को भी ध्यान में रखते हैं।
हेडर और फ़ुटर की आवश्यकताएं इस प्रकार होंगी:
protocol SectionHeader: UICollectionReusableView { associatedtype HeaderData func setup(with data: HeaderData?) -> Self static func headerItem() -> NSCollectionLayoutBoundarySupplementaryItem? } protocol SectionFooter: UICollectionReusableView { associatedtype FooterData func setup(with data: FooterData?) -> Self static func footerItem() -> NSCollectionLayoutBoundarySupplementaryItem? }
स्क्रॉलिंग तत्वों की आवश्यकताओं के आधार पर, हम अनुभाग के कार्यान्वयन को डिज़ाइन कर सकते हैं:
class Section<Cell: SectionCell, Header: SectionHeader, Footer: SectionFooter>: BaseSection { init(items: [Cell.CellData], headerData: Header.HeaderData? = nil, footerData: Footer.FooterData? = nil) { self.items = items self.headerData = headerData self.footerData = footerData } private(set) var items: [Cell.CellData] private let headerData: Header.HeaderData? private let footerData: Footer.FooterData? var numberOfElements: Int { items.count } func registrate(collectionView: UICollectionView) { collectionView.register(Cell.self) collectionView.registerHeader(Header.self) collectionView.registerFooter(Footer.self) } func cell(for collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell { collectionView .dequeue(Cell.self, indexPath: indexPath)? .setup(with: items[indexPath.row]) ?? UICollectionViewCell() } func header(for collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionReusableView { collectionView .dequeueHeader(Header.self, indexPath: indexPath)? .setup(with: headerData) ?? UICollectionReusableView() } func footer(for collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionReusableView { collectionView .dequeueFooter(Footer.self, indexPath: indexPath)? .setup(with: footerData) ?? UICollectionReusableView() } func section() -> NSCollectionLayoutSection { let section = NSCollectionLayoutSection(group: Cell.groupSize()) if let headerItem = Header.headerItem() { section.boundarySupplementaryItems.append(headerItem) } if let footerItem = Footer.footerItem() { section.boundarySupplementaryItems.append(footerItem) } return section } func select(row: Int) { items[row].onSelect?() } }
उनके लिए आवश्यकताओं को क्रियान्वित करने वाले जेनरिक सेल, हेडर या फ़ुटर प्रकार के रूप में कार्य करते हैं।
सामान्य तौर पर, कार्यान्वयन पूरा हो गया है, लेकिन मैं कुछ सहायक जोड़ना चाहूँगा जो बॉयलरप्लेट कोड की मात्रा को और कम कर देंगे। विशेष रूप से, व्यवहार में, ऐसा सामान्य अनुभाग रखना हमेशा उपयोगी नहीं होगा, इसका सीधा कारण यह है कि फ़ुटर या हेडर का हमेशा उपयोग नहीं किया जाता है।
आइए यहां एक अनुभाग जोड़ें जो समान मामलों को ध्यान में रखता है:
class SectionWithoutHeaderFooter<Cell: SectionCell>: Section<Cell, EmptySectionHeader, EmptySectionFooter> {} class EmptySectionHeader: UICollectionReusableView, SectionHeader { func setup(with data: String?) -> Self { self } static func headerItem() -> NSCollectionLayoutBoundarySupplementaryItem? { nil } } class EmptySectionHeader: UICollectionReusableView, SectionHeader { func setup(with data: String?) -> Self { self } static func headerItem() -> NSCollectionLayoutBoundarySupplementaryItem? { nil } }
इस पर, डिज़ाइन को पूरा माना जा सकता है, मैं स्वयं उपयोग के मामलों पर आगे बढ़ने का प्रस्ताव करता हूं।
आइए एक निश्चित आकार वाले कक्षों का एक अनुभाग बनाएं और उसे स्क्रीन पर प्रदर्शित करें:
class ColorCollectionCell: UICollectionViewCell, SectionCell { func setup(with data: ColorCollectionCellData) -> Self { contentView.backgroundColor = data.color return self } static func groupSize() -> NSCollectionLayoutGroup { let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(0.5)) let item = NSCollectionLayoutItem(layoutSize: itemSize) let group = NSCollectionLayoutGroup.horizontal(layoutSize: itemSize, subitem: item, count: 2) group.interItemSpacing = .fixed(16) return group } } class ColorCollectionCellData: SectionCellData { let onSelect: VoidClosure? let color: UIColor init(color: UIColor, onSelect: VoidClosure? = nil) { self.onSelect = onSelect self.color = color } }
आइये हेडर और फ़ुटर का कार्यान्वयन बनाएं:
class DefaultSectionHeader: UICollectionReusableView, SectionHeader { let textLabel: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 32, weight: .bold) return label }() override init(frame: CGRect) { super.init(frame: frame) commonInit() } required init?(coder: NSCoder) { super.init(coder: coder) commonInit() } private func commonInit() { addSubview(textLabel) textLabel.numberOfLines = .zero textLabel.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ textLabel.topAnchor.constraint(equalTo: topAnchor), textLabel.bottomAnchor.constraint(equalTo: bottomAnchor), textLabel.leftAnchor.constraint(equalTo: leftAnchor), textLabel.rightAnchor.constraint(equalTo: rightAnchor) ]) } func setup(with data: String?) -> Self { textLabel.text = data return self } static func headerItem() -> NSCollectionLayoutBoundarySupplementaryItem? { let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(20)) let header = NSCollectionLayoutBoundarySupplementaryItem( layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top, absoluteOffset: .zero ) header.pinToVisibleBounds = true return header } } class DefaultSectionFooter: UICollectionReusableView, SectionFooter { let textLabel: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 12, weight: .light) return label }() override init(frame: CGRect) { super.init(frame: frame) commonInit() } required init?(coder: NSCoder) { super.init(coder: coder) commonInit() } private func commonInit() { addSubview(textLabel) textLabel.numberOfLines = .zero textLabel.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ textLabel.topAnchor.constraint(equalTo: topAnchor), textLabel.bottomAnchor.constraint(equalTo: bottomAnchor), textLabel.leftAnchor.constraint(equalTo: leftAnchor), textLabel.rightAnchor.constraint(equalTo: rightAnchor) ]) } func setup(with data: String?) -> Self { textLabel.text = data return self } static func footerItem() -> NSCollectionLayoutBoundarySupplementaryItem? { let footerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(20)) let footer = NSCollectionLayoutBoundarySupplementaryItem( layoutSize: footerSize, elementKind: UICollectionView.elementKindSectionFooter, alignment: .bottom, absoluteOffset: .zero ) return footer } }
आइए स्क्रॉलिंग सूची में एक नया अनुभाग जोड़ें:
class ViewController: UIViewController { let sectionView = SectionView() override func loadView() { view = sectionView } override func viewDidLoad() { super.viewDidLoad() sectionView.backgroundColor = .white sectionView.set( sections: [ Section<ColorCollectionCell, DefaultSectionHeader, DefaultSectionFooter>( items: [ .init(color: .blue) { print(#function) }, .init(color: .red) { print(#function) }, .init(color: .yellow) { print(#function) }, .init(color: .green) { print(#function) }, .init(color: .blue) { print(#function) } ], headerData: "COLOR SECTION", footerData: "footer text for color section" ) ], append: false ) } }
कुल मिलाकर, कोड की कुछ ही पंक्तियों में, हमने स्क्रीन के अनुपात में आकार वाले 5 बहुरंगी कक्षों, एक हेडर और एक फ़ूटर का एक भाग क्रियान्वित किया।
आइए गतिशील आकार वाली कोशिकाओं के लिए भी इसी प्रकार का दृष्टिकोण अपनाने का प्रयास करें।
class DynamicCollectionCell: UICollectionViewCell, SectionCell { let textLabel = UILabel() override init(frame: CGRect) { super.init(frame: frame) commonInit() } required init?(coder: NSCoder) { super.init(coder: coder) commonInit() } private func commonInit() { contentView.addSubview(textLabel) textLabel.numberOfLines = .zero textLabel.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ textLabel.topAnchor.constraint(equalTo: contentView.topAnchor), textLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), textLabel.leftAnchor.constraint(equalTo: contentView.leftAnchor), textLabel.rightAnchor.constraint(equalTo: contentView.rightAnchor) ]) } func setup(with data: DynamicCollectionCellData) -> Self { textLabel.text = data.text return self } static func groupSize() -> NSCollectionLayoutGroup { let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(20)) let item = NSCollectionLayoutItem(layoutSize: itemSize) let group = NSCollectionLayoutGroup.vertical(layoutSize: itemSize, subitems: [item]) return group } } class DynamicCollectionCellData: SectionCellData { let text: String var onSelect: VoidClosure? init(text: String) { self.text = text } } class ViewController: UIViewController { ... override func viewDidLoad() { super.viewDidLoad() ... sectionView.set( sections: [ SectionWithoutHeaderFooter<DynamicCollectionCell>( items: [ .init(text: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s"), .init(text: "when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged."), .init(text: "It was popularised"), .init(text: "the 1960s with the release of Letraset sheets containing"), .init(text: "Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.") ] ), ... ], append: false ) } }
परिणामस्वरूप, हमें UICollectionViewCompositionalLayout
पर आधारित स्क्रॉलिंग सूचियाँ बनाते समय बॉयलरप्लेट कोड लिखने से छुटकारा मिल गया।
स्रोत कोड देखा जा सकता है
मुझसे संपर्क करने में संकोच न करें