এটি অটোলেআউট প্রোগ্রাম্যাটিক্যালি ব্যবহার করে স্পোটিফাই UI ক্লোন বানানোর trên আর্টিকলের দ্বিতীয় অংশ। যদি আপনি প্রথম অংশটি মিস করে থাকেন, কোন চিন্তা নেই — শুধু জায়গাটি দেখে আসুন।
এই আর্টিকলে আমরা কিছু মক ছবি যোগ করব এবং UI-কে স্পোটিফাইর মতো दिखানোর চেষ্টা করব।
আজ আমরা যা করব সেটি নিচে দেখানো হলো:

প্রথম অংশে আমরা যেখানে থামেছিলাম সেটি নিচে:

পরবর্তী ধাপ হলো কাস্টম সেল তৈরি করা। তাই শুরু করা যাক SubCustomCell নামে একটি তৈরি করে।
প্রথমে প্রজেক্ট ফোল্ডারের ভেতরে একটি নতুন Swift ফাইল তৈরি করুন এবং নাম দিন SubCustomCell.swift। এই ফাইলটি আমাদের কাস্টম সেলকে ধারণ করবে যা প্লেলিস্টকে প্রতিনিধিত্ব করবে। ফাইল তৈরি হলে নিচের কোড যোগ করে সেলটি ইনিশিয়ালাইজ করুন, উদাহরণস্বরূপ backgroundColor দিয়ে, যাতে collectionView-এর সাথে সেলটি রেজিস্টার করলে UI পরিবর্তন দেখা যায়।
import UIKit class SubCustomCell: UICollectionViewCell { override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .red } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }তারপর CustomCell.swift-এর ভেতরে init ব্লকে SubCustomCell রেজিস্টার করুন। UICollectionViewCell.self-কে SubCustomCell.self দিয়ে রিপ্লেস করুন নিচের মত করে।
collectionView.register(SubCustomCell.self, forCellWithReuseIdentifier: cellId)এছাড়া cellForItemAt মেথডে পরিবর্তন আনা প্রয়োজন যাতে সেটি SubCustomCell-এর সাথে কনফর্ম করে। নিচের মত করে:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! SubCustomCell // cell.backgroundColor = .yellow return cell }আপনি দেখতে পাবেন backgroundColor লাল হয়ে গেছে।

এতক্ষণ পর্যন্ত সবকিছু সহজ ও স্পষ্ট হতে পারে।
এখন আমরা সেলগুলোকে কিছু মক ছবি দিয়ে ভরব এবং প্রতিটি সেলের ভেতরে একটি ImageView তৈরি করব। আমি ইতিমধ্যে pexels.com থেকে কিছু র্যান্ডম ছবি ডাউনলোড করে রেখেছি, কিন্তু আপনি যেকোনো ছবি ব্যবহার করতে পারেন (এগুলিও ব্যবহার করতে পারেন)। GitHub-এ প্রজেক্ট ফাইলগুলিতে পাবেন।
চলুন SubCustomCell.swift-এর ভেতরে UIImageView তৈরি করি এবং কিছু কনস্ট্রেইন্ট যোগ করি।
let ImageView: UIImageView = { let iv = UIImageView() iv.backgroundColor = .yellow return iv }()এবং init ব্লকে addSubView ব্যবহার করে ভিউতে যোগ করুন।
override init(frame: CGRect) { super.init(frame: frame) addSubview(ImageView) }এখন নিচের কনস্ট্রেইন্টগুলো দিয়ে ImageView-কে সেলের সকল জায়গা দখল করতে দিন।
ImageView.translatesAutoresizingMaskIntoConstraints = false ImageView.topAnchor.constraint(equalTo: topAnchor).isActive = true ImageView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true ImageView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true ImageView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = trueleftAnchorসেলের বাম এংকারকে নির্দেশ করেrightAnchorসেলের ডান এংকারকে নির্দেশ করেbottomAnchorসেলের নিচের এংকারকে নির্দেশ করেtopAnchorসেলের উপরের এংকারকে নির্দেশ করে
ImageView-এর টপ, লেফট, রাইট এবং বটম এংকারকে সেলের সংশ্লিষ্ট এংকারের সাথে সমান করলে ImageView পুরো SubCustomCell-কে আচ্ছাদন করে।
নোট: প্রথমে translatesAutoresizingMaskIntoConstraints ব্যবহার করতে হবে কনস্ট্রেইন্ট লাগাতে। এছাড়া isActive প্রপার্টিকে true সেট করতে ভুলবেন না — না হলে কনস্ট্রেইন্ট কাজ করবে না।
ImageView-এ ছবি থাকা উচিত, তাই একটি যোগ করা যাক।
let ImageView: UIImageView = { let iv = UIImageView() iv.backgroundColor = .yellow // প্রজেক্টের ভেতরে image1 ফাইল আছে iv.image = UIImage(named: "image1") iv.contentMode = .scaleAspectFill iv.clipsToBounds = true return iv }()অ্যাপ বিল্ড ও রান করলে আপনি SubCustomCell-এ যোগ করা ছবিটি দেখতে পাবেন।

খুব ভালো! এখন SubCustomCell-এ একটি এলিমেন্ট যোগ করা বাকি। প্লেলিস্টের টাইটেল উপস্থাপন করার জন্য একটি UILabel প্রয়োজন।
টাইটেলের জন্য কোডটি নিচের মত হবে:
let TitleLabel: UILabel = { let lb = UILabel() lb.textColor = UIColor.lightGray lb.font = UIFont.systemFont(ofSize: 16) lb.font = UIFont.boldSystemFont(ofSize: 20) lb.text = "Evening Music" return lb }()এখানে র্যান্ডম টেক্সট দিয়েছি — আপনি যা ইচ্ছে দিতে পারেন। পরবর্তী ধাপ হলো এলিমেন্টটি ভিউতে যোগ করে কিছু কনস্ট্রেইন্ট দেওয়া। টাইটেলটি ImageView-এর নিচে থাকবে।
ভিউতে যোগ করুন:
addSubview(TitleLabel)ImageView এবং TitleLabel-এর জন্য কনস্ট্রেইন্ট প্রয়োগ
ImageView.translatesAutoresizingMaskIntoConstraints = false ImageView.topAnchor.constraint(equalTo: topAnchor).isActive = true ImageView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true ImageView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true ImageView.heightAnchor.constraint(equalToConstant: 240).isActive = true ImageView.bottomAnchor.constraint(equalTo: TitleLabel.topAnchor).isActive = true TitleLabel.translatesAutoresizingMaskIntoConstraints = false TitleLabel.topAnchor.constraint(equalTo: ImageView.bottomAnchor, constant: 10).isActive = true TitleLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 5).isActive = true TitleLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: -5).isActive = trueহয়ে গেলো!

আমরা ছবিটি সেলের অধিকাংশ জায়গা দখল করিয়েছি, বাকি অংশ টাইটেলের জন্য। আপনি দেখতে পাচ্ছেন প্রতিটি সেকশনে হরিজন্টালি স্ক্রোল করা যায় এবং পুরো স্ক্রিনে ভার্টিকালি স্ক্রোল করা যায়।

এখন সেলগুলোকে কিছু মক ডেটা দিয়ে ভরব যাতে এটা আসল মনে হয়। এর জন্য আমি একটি JSON ফাইল তৈরি করেছি যাতে সেকশন ও প্লেলিস্টের জন্য কিছু র্যান্ডম ডেটা থাকে।
প্রথমে দুটি স্ট্রাক্ট তৈরি করা যাক: Section এবং Playlist। প্রতিটি স্ট্রাক্টের জন্য আলাদা ফাইল তৈরি করুন।
section.swift
import Foundation struct Section { var title: String var playlists: NSArray init(dictionary: [String: Any]) { self.title = dictionary["title"] as? String ?? "" self.playlists = dictionary["playlists"] as? NSArray ?? [] } }playlist.swift
import Foundation struct PlayList { var title: String var image: String init(dictionary: [String: Any]) { self.title = dictionary["title"] as? String ?? "" self.image = dictionary["image"] as? String ?? "" } }তারপর ViewController.swift-এ একটি ফাংশন তৈরি করুন যা JSON ফেচ করে এবং ফলাফলগুলো একটি অ্যারেতে স্টোর করে।
print("attempt to fetch Json") if let path = Bundle.main.path(forResource: "test", ofType: "json") { do { let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe) let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves) if let jsonResult = jsonResult as? [Any] { jsonResult.forEach { (item) in let section = Section(dictionary: item as! [String: Any]) self.sections.append(section) } self.collectionView.reloadData() } } catch { // handle error } } }fetchJson ফাংশনটি ViewDidLoad মেথডে কল করা হয়। একটি sections ভেরিয়েবলও আছে যেখানে ফলাফলগুলো স্টোর হয়:
var sections = [Section]()পরবর্তী ধাপ হলো ViewController- থেকে CustomCell-এ ডেটা পাস করা। এর জন্য CustomCell-এ একটি ভেরিয়েবল তৈরি করুন যা প্রতিটি সেকশনের ডেটা গ্রহণ করবে:
var section: Section? { didSet { print("section ✅", self.section) } }ViewController-এর cellForItemAt মেথডে ডেটা সরাসরি CustomCell-এ পাস করুন।
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CustomCell cell.section = sections[indexPath.item] return cell }নোট: fetchJson কল হওয়ার প্রতিবার self.collectionView.reloadData() কল করুন যাতে CustomCell-এর নিচের ব্লকও কল হয়। কনসোল চেক করুন (shift + command + C):
var section: Section? { didSet { print("section ✅", self.section) } }প্রথম পরিবর্তন হলো সেকশন টাইটেল সেট করা:
var section: Section? { didSet { print("section ✅", self.section) guard let section = self.section else { return } self.titleLabel.text = section.title } }এখন প্রতিটি সেকশনে নির্দিষ্ট টাইটেল দেখতে পাবেন।

এখন SubCustomCell-এ ডেটা পাস করার পালা। উপরের মতোই কাজ করব। playlists অ্যারে পাস করতে হবে, তাই CustomCell-এ playlists নামে একটি ভেরিয়েবল তৈরি করুন।
var playlists: [PlayList] = [] // খালিপ্রথমে JSON-এ থেকে playlists ম্যাপ করুন। তারপর প্রতিটি প্লেলিস্ট playlists ভেরিয়েবলে যোগ করুন।
var section: Section? { didSet { print("section ✅", self.section) guard let section = self.section else { return } self.titleLabel.text = section.title // playlists অ্যারেতে যোগ করুন self.section?.playlists.forEach({ (item) in let playlist = PlayList(dictionary: item as! [String: Any]) self.playlists.append(playlist) }) self.collectionView.reloadData() } }সাবধান! অ্যাপ রান করলে ক্র্যাশ করতে পারে। কারণ আমরা সেকশনের সংখ্যা সেট করা ভুলে গেছি। JSON থেকে ডেটা আসছে, তাই সেকশনের সংখ্যা ডাইনামিক হতে হবে। JSON-এ যতগুলো সেকশন আছে ততগুলো হওয়া উচিত। তাই ViewController-এর numberOfItemsInSection মডিফাই করুন নিচের মত করে:
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return sections.count }CustomCell.swift-এও একই মেথডে পরিবর্তন আনা লাগবে — কিন্তু এখানে playlists-এর সংখ্যা বিবেচনা করতে হবে।
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return self.playlists.count }শেষ ধাপ হলো CustomCell.swift-এর cellForItemAt-এ প্রতিটি প্লেলিস্ট Object কে SubCustomCell-এ পাস করা।
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! SubCustomCell // এখানে cell.playlist = playlists[indexPath.item] return cell }এবং SubCustomCell-এ playlist ভেরিয়েবল দিয়ে ডেটা নিয়ে প্লেলিস্টের টাইটেল ও ইমেজ দেখাব।
var playlist: PlayList? { didSet { print("Playlist ?", self.playlist) guard let playlist = self.playlist else { return } // ইমেজ self.ImageView.image = UIImage(named: playlist.image) // প্লেলিস্ট টাইটেল self.TitleLabel.text = self.playlist?.title } }আমার মনে হয় এখন সবকিছু ঠিকমতো কাজ করবে, নিচের মত করে:

UI-এ এক ফাইনাল আপডেট: সেকশন ও প্লেলিস্ট টাইটেলের জন্য কিছু প্যাডিং এবং মার্জিন যোগ করব এবং প্লেলিস্টকে কিছুটা ছোট করব।
প্রথমে সেকশন টাইটেলের জন্য প্যাডিং যোগ করা যাক। CustomCell-এ setupSubCells-এ constant প্রপার্টিতে কিছু মান দিন:
collectionView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 15).isActive = trueযদি পুরো collectionView titleLabel-এর নিচে আসে, তবে ১৫ যোগ করলে আরও স্পেস তৈরি হবে।

এখন প্লেলিস্টের টাইটেল। এটি SubCustomCell-এ আছে, এবং আমরা ImageView-এর নিচে আরও স্পেস যোগ করব।
ImageView.bottomAnchor.constraint(equalTo: TitleLabel.topAnchor, constant: -15).isActive = trueএখানে ইতিমধ্যে কনস্ট্রেইন্ট আছে। কাজ করার জন্য মানটি -15 হতে হবে।

শেষে প্লেলিস্টকে কিছুটা ছোট করতে হবে। সহজেই: প্লেলিস্ট সেলের হাইট এবং ওয়ิดথকে সেকশন সেলের হাইটের অর্ধেক করুন, নিচের মত করে:
CustomCell.swift
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let width = frame.height / 2 let height = frame.height / 2 return CGSize(width: width, height: height) }ImageView-এর হাইটও 150 করুন।
// SubCustomCell.swift ImageView.heightAnchor.constraint(equalToConstant: 150).isActive = trueফলাফল:

বার্হত্তর! আজ এইটুকুই থাকাক — আর্টিকেলটি খুব বড় করব না। তৃতীয় অংশে আমরা TabBar, ডেসক্রিপশন এবং প্লেলিস্টের জন্য কিছু আইকন যোগ করব।
GitHub-এ পূর্ণ সোর্স কোড দেখুন।
আপনার সময়ের জন্য ধন্যবাদ। আশা করি কোনো কিছু মিস করিনি। যদি করি তবে টুইটারে @মেনশন করুন, অথবা কোনো প্রশ্ন বা সংযোজন থাকলে দরজা সবার জন্য খোলা। ধন্যবাদ!
সাবস্ক্রাইব করুন আমার ইমেইল লিস্টে যাতে তৃতীয় অংশ পাবলিশ হলে আপনাকে নোটিফিকেশন যায়।
প্রসংগবশত বলি, আমি সম্প্রতি আমার একটি মোবাইল অ্যাপ্লিকেশনের জন্য সফটওয়্যার ইঞ্জিনিয়ারদের একটি শক্তিশালী গ্রুপের সাথে কাজ করেছি। প্রতিষ্ঠানটি খুব ভালো ছিল, এবং প্রোডাক্টটি অন্যান্য ফার্ম ও ফ্রিল্যান্সারদের চেয়ে অনেক দ্রুত ডেলিভার করা হয়েছিল, এবং আমি মনে করি সত্যিকারের तौर पर অন্য প্রজেক্টের জন্য তাদের রিকমেন্ড করতে পারি। যোগাযোগ করতে চাইলে ইমেইল করুন — said@devsdata.com।