paint-brush
প্রোটোকল-ওরিয়েন্টেড ডিজাইন এবং UICollectionViewCompositional Layout সহ কীভাবে স্ক্রোলযোগ্য তালিকা তৈরি করবেনদ্বারা@bugorbn
699 পড়া
699 পড়া

প্রোটোকল-ওরিয়েন্টেড ডিজাইন এবং UICollectionViewCompositional Layout সহ কীভাবে স্ক্রোলযোগ্য তালিকা তৈরি করবেন

দ্বারা Boris Bugor19m2024/06/18
Read on Terminal Reader

অতিদীর্ঘ; পড়তে

এই পদ্ধতির অনুপ্রেরণা খুবই সহজ, আমরা সার্বজনীন টুল তৈরি করে বয়লারপ্লেট কোডের পরিমাণ কমাতে চাই। আমরা 4 ধাপে এই সমস্যার সমাধান করব। স্ক্রলিং উপাদানের ডেটা টাইপের একটি বিমূর্ততা লেখা; স্ক্রোলযোগ্য উপাদানগুলির জন্য একটি বেস ক্লাস লেখা; তালিকার জন্য একটি বাস্তবায়ন লেখা; এবং তালিকার জন্য একটি বাস্তবায়ন লেখা।
featured image - প্রোটোকল-ওরিয়েন্টেড ডিজাইন এবং UICollectionViewCompositional Layout সহ কীভাবে স্ক্রোলযোগ্য তালিকা তৈরি করবেন
Boris Bugor HackerNoon profile picture
0-item

এই নিবন্ধটি একটি বৃহৎ কোড বেস সহ প্রকল্পগুলিকে স্কেলিং করার সময় একটি প্রোটোকল-ভিত্তিক পদ্ধতি ব্যবহার করার উপর আমার সিরিজের একটি ধারাবাহিকতা।


আপনি যদি না পড়ে থাকেন তাহলে আগে নিবন্ধ , আমি দৃঢ়ভাবে সুপারিশ করছি যে আপনি এতে তৈরি পদ্ধতি এবং উপসংহারগুলির সাথে নিজেকে পরিচিত করুন। সংক্ষেপে, একটি সার্বজনীন ক্লাস তৈরির সাথে একটি কেস বিবেচনা করা হয়েছিল যা UICollectionViewFlowLayout এর উপর ভিত্তি করে স্ক্রলিং তালিকা ব্যবহার করার জন্য একটি কনস্ট্রাক্টর তৈরির অনুমতি দেবে।


এই পদ্ধতির অনুপ্রেরণা খুবই সহজ, আমরা সার্বজনীন সরঞ্জাম তৈরি করে বয়লারপ্লেট কোডের পরিমাণ কমাতে চাই যা রুটিনের পরিমাণ কমিয়ে দেবে এবং একই সাথে নমনীয়তা হারাবে না।


এই নিবন্ধে, আমরা ব্যবহার করে একটি অনুরূপ কাজ বিবেচনা চালিয়ে যাবে UICollectionViewCompositionalLayout , iOS 13+ দ্বারা সমর্থিত, এবং দেখুন এই কাঠামোটি কী কী সূক্ষ্মতা নিয়ে আসে৷


আমরা আগে যেমন করেছিলাম, আমরা এই সমস্যাটি 4টি পর্যায়ে সমাধান করব:


  1. স্ক্রলিং উপাদানের ডেটা টাইপের একটি বিমূর্ততা লেখা;
  2. স্ক্রোলযোগ্য উপাদানগুলির জন্য একটি বেস ক্লাস লেখা;
  3. তালিকার জন্য একটি বাস্তবায়ন লেখা;
  4. ব্যবহারের ক্ষেত্রে


1. বিমূর্ত স্ক্রোলিং উপাদান

বিমূর্ততা সৃষ্টি নিঃসন্দেহে নকশার সবচেয়ে গুরুত্বপূর্ণ পর্যায়। স্কেলিং-এর জন্য উন্মুক্ত একটি সিস্টেমের ভিত্তি স্থাপন করার জন্য, স্ক্রলিং উপাদানগুলির গুণগত এবং পরিমাণগত বৈশিষ্ট্যগুলি থেকে বিমূর্ত করা প্রয়োজন। একই ধরনের লেআউটের জন্য প্রয়োজনীয়তা মেনে চলাও গুরুত্বপূর্ণ।


আসুন আমরা এমন একটি ধারণা প্রবর্তন করি; একটি বিভাগ হিসাবে। একটি বিভাগ একই লেআউট সহ এক বা একাধিক উপাদান।


আমরা স্ক্রোলযোগ্য উপাদানগুলির উপর একটি বিমূর্ততা হিসাবে বিভাগটি ব্যবহার করি:

 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) }


আমরা সেকশনে লেআউট কনফিগার করার দায়িত্ব হস্তান্তর করব। একটি শিরোনাম বা ফুটারের মতো সম্পূরক দৃশ্যের উপস্থিতিও সেখানে নির্ধারিত হবে।


2. স্ক্রলিং তালিকা

বেস ক্লাস স্ক্রোলযোগ্য তালিকা হিসাবে ব্যবহার করা হবে। বেস ক্লাসের কাজ হল বেস সেকশনের বিমূর্ত ডেটা নেওয়া এবং রেন্ডার করা। আমাদের ক্ষেত্রে, 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) } }


UICollectionViewFlowLayout ব্যবহারের তুলনায় UICollectionViewCompositionalLayout , আপনাকে ডেলিগেট পদ্ধতি থেকে লেআউট বডিতে সেল, হেডার এবং ফুটার লেআউট কনফিগারেশন স্থানান্তর করতে দেয়।

3. স্ক্রোলযোগ্য উপাদান বাস্তবায়ন করা

এই বিষয়টির উপর ভিত্তি করে যে বিভাগটি, যার মধ্যে ফুটার এবং শিরোনাম দেখানোর ক্ষমতা রয়েছে, একটি বিমূর্ততা হিসাবে নেওয়া হয়েছিল, এটি বাস্তবায়নের শ্রেণীতেও এটি বিবেচনায় নেওয়া প্রয়োজন।


এই ক্ষেত্রে, যে কোনও ঘরের প্রয়োজনীয়তাগুলি এইরকম দেখাবে:

 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 } }


এই উপর, নকশা সম্পূর্ণ বিবেচনা করা যেতে পারে, আমি নিজেদের ব্যবহার ক্ষেত্রে এগিয়ে যাওয়ার প্রস্তাব.

4. ক্ষেত্রে ব্যবহার করুন

আসুন একটি নির্দিষ্ট আকার সহ ঘরগুলির একটি বিভাগ তৈরি করি এবং এটি স্ক্রিনে প্রদর্শন করি:

 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 এর উপর ভিত্তি করে স্ক্রলিং তালিকা তৈরি করার সময় আমরা বয়লারপ্লেট কোড লেখা থেকে মুক্তি পেয়েছি।



সোর্স কোড দেখা যাবে এখানে .


আমার সাথে যোগাযোগ করতে দ্বিধা করবেন না টুইটার যদি আপনার কোন প্রশ্ন থাকে তাহলে. এছাড়াও, আপনি সবসময় করতে পারেন আমাকে একটা কফি কিনে দাও .