Contents
Chào bạn đến với Fx Studio, bài viết hôm nay sẽ là phần tiếp theo của:
Nếu bạn chưa biết về Table View thì có thể ghé link trên. Về Table View thì còn nhiều phần tiếp theo, mình xin phép chia ra thành nhiều bài với nhiều chủ đề trọng tâm. Chủ để phần này là về tuỳ chỉnh giao diện cho cell, hay giang hồ gọi với cái tên thân mật là Custom Cell.
Bắt đầu thôi!
Về giao diện của các ứng dụng iOS, hầu như 90% đều phải tuỳ chỉnh và chúng không sử dụng các giao diện mặc định của iOS. Để tuỳ chỉnh về giao diện thì chúng ta có bài Custom View:
Vẫn nguyên tình thần của Custom View, giờ chúng ta tiến hoá sang Custom UITableViewCell (hay gọi là Custom Cell). Và cũng áp dụng nguyên các cách custom tương tự như UIView.
Chuẩn bị
- MacOS 10.14.4
- Xcode 11.0
- Swift 5.1
1. Add Subview
UITableViewCell là class của các row (hay cell) trong UITableView. Nó là sub-class của UIView. Nên theo nguyên tắc của View, thì nó có các view con (subviews). Vì vậy, việc custom đơn giản nhất là addSubview cho cell.
Chúng ta sử dụng lại project của phần trước. Các UITableViewCell có 1 UILabel với tên là textLabel để hiện thị thông tin. Bây giờ, chúng ta thêm 1 label nữa để hiển thị thông tin phụ.
Mở file HomeViewController.swift và edit đoạn code sau:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = names[indexPath.section][indexPath.row]
let label = UILabel(frame: CGRect(x: 20, y: 30, width: 100, height: 20))
label.text = "sub title"
label.textColor = .red
cell.addSubview(label)
return cell
}
Trong đó:
labellà đối tượng UILabel- Xét các thuộc tính frame, text, và textColor cho
label - AddSubView cho
celllàlabel
Kết quả:

Quá đơn giản!
Tuy nhiên, việc xác định giao diện rất khó khắn và tối rất nhiều công sức tính toán toạ độ. Bên cạnh đó là việc re-use thì các cell không tạo mới. Cứ như vậy, qua mỗi hàm cellForRow chạy, thì lại có label mới tạo ra và thêm vào, chứ chúng không bị remove đi.
Khi danh sách của bạn quá dài và scroll lên xuống nhiều lần. Với ví dụ trên, bạn sẽ thấy một số cell sẽ có màu đỏ của
sub titleđậm hơn 1 số cell khác. Đó chính là vấn đề củareuseable.
Nguyên tắc làm việc với reuseable như sau:
- Có
init()thì phải códeinit() - Có
addthì phải córemove - Có
ifthì phải cóelse
Và, việc addSubview không là giải pháp tối ứu nhất. Bây giờ chúng ta chuyển sang giải pháp kinh điển hơn, là kéo thả giao diện.
2. Custom Cell
2.1. Create cell
Kích chuột phải và new file, như sau:

Chú ý:
- Chọn vào
also create XIB fileđể Xcode tạo sẵn cho bạn file giao diện cho Cell - Sub-class là
UITableViewCell
Tiến hành kéo thả giao diện và tạo các IBOutlet tương ứng.

Code của file HomeCell.swift như sau:
import UIKit
class HomeCell: UITableViewCell {
@IBOutlet weak var avatarImageView: UIImageView!
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var subTitleLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
2.2. Register Cell
Mở file HomeViewController.swift để tiến hành register cell cho Table View. Công việc này rất quan trọng và quyết định sống còn của việc Custom Cell. Tại hàm viewDidLoad, thêm đoạn code sau:
//1
let nib = UINib(nibName: "HomeCell", bundle: .main)
//2
tableview.register(nib, forCellReuseIdentifier: "cell")
Trong đó:
- Tạo 1 đối tượng
nib, với tênnibNamechính là tên file *.xib. Bundle chính làmain bundle - Sử dụng hàm register với
nib - Identifier cho cell để re-use là
cell(bạn có thể đổi theo ý riêng của bạn)
Tới đây thì xong phần register cell cho Table View.
2.3. Load cell
Tới phần extension cho delegate của Table View trong HomeViewController.swift. Edit lại hàm cellForRow
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! HomeCell
cell.nameLabel.text = names[indexPath.section][indexPath.row]
cell.subTitleLabel.text = "Sub title"
return cell
}
Trong đó:
- Vẫn sử dụng
dequeuevớiidentifiervàindexPathđể lấy đối tượng cell ra đúng với indexPath. - Ép kiểu về kiểu
HomeCell - Xét các giá trị cho các IBOutlet của cell
Tiếp tục thêm 1 function nữa của UITableViewDelegate, mục đích:
- Xác định chiều cao của Custom Cell
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
Chạy chương trình và xem kết quả.

Nếu bạn ra được kết quả như hình, thì chúc mừng bạn đã hoàn thành việc custom cell. Tuy nhiên, vẫn còn 1 xí nữa cho phần dữ liệu của TableView.
3. Dynamic cell
Qua ví dụ trên, thì bạn sẽ thấy là:
- Dữ liệu cung cấp cho cell là 1 array String.
- Dữ liệu cần cho Custom Cell sẽ gồm nhiều trường. Tức là cần nhiều hơn 1 array.
Có thể nhiều bạn sẽ suy nghĩ như thế này để cung cấp dữ liệu cho Table View.
var names: [String] = ... var ages: [Int] = ... var genders: [Bool] = ...
Điều này thì …
Ta không thể nào tạo nhiều array để cung cấp dữ liệu cho cell. Nó làm việc quản lý dữ liệu sẽ gặp rất nhiều khó khăn. Giải pháp là nhóm tụi nó về 1 array với 1 kiểu dữ liệu mới.
Nói dễ hiểu thì tạo class/struct cho dữ liệu của Table View. Hehe!
Kích chuột phải và tiếp tục new file mới, tên là User.swift. Trong đó:
- tạo 1 class
User - các thuộc tính: tên, tuổi và giới tính
- hàm trả về thông tin của user
final class User {
var name: String
var age: Int
var gender: Bool
init(name: String, age: Int, gender: Bool) {
self.name = name
self.age = age
self.gender = gender
}
func getSubTitle() -> String {
return "\(gender ? "Male" : "Female") - \(age) year olds"
}
}
Tiếp tục, viết 1 function để trả về dữ liệu giả là 1 array User. Trong đó:
- Tạo 1 array user rỗng
- Lặp từ 0 đến 30 lần
- Tạo mới 1 đối tượng user với dữ liệu cung cấp là random
- Thêm đối tượng mới vào mãng.
func getUser() -> [User] {
//1
var users = [User]()
//2
for i in 0...30 {
//3
let user = User(name: "Name \(i+1)", age: Int.random(in: 10...30), gender: Bool.random())
//4
users.append(user)
}
return users
}
Tiến hành chỉnh sửa lại dữ liệu cho Table View của HomeViewController.swift
var users: [User] = []
Cập nhật dữ liệu cho users
override func viewDidLoad() {
super.viewDidLoad()
title = "Home"
let nib = UINib(nibName: "HomeCell", bundle: .main)
tableview.register(nib, forCellReuseIdentifier: "cell")
tableview.delegate = self
tableview.dataSource = self
users = getUser()
}
Cập nhật lại các function của delegate & datasouce của Table View.
extension HomeViewController : UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! HomeCell
let user = users[indexPath.row]
cell.nameLabel.text = user.name
cell.subTitleLabel.text = user.getSubTitle()
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let user = users[indexPath.row]
let vc = DetailViewController()
vc.name = user.name
self.navigationController?.pushViewController(vc, animated: true)
}
}
Chú ý:
let user = users[indexPath.row]sẽ giúp bạn lấy ra đối tượng cần thiết để hiển thị hay sử dụng dữ liệu.
Sau này, dữ liệu cũng cấp cho Table View sẽ lấy từ API/Webservice/Database … và không lệ thuộc vào việc
hardcode. Khuyến khích khi làm việc với Table View thì nên tạo các function với dữ liệu giả để cũng cấp dữ liệu cho các cell.
Tới đây, thì bạn đã xong phần Custom Cell, hẹn gặp lại bạn ở các phần sau. Cảm ơn đã đọc!
Tạm kết
- AddSubview vào Cell
- Custom Cell
- Dynamic cell
Related Posts:
Written by chuotfx
Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!
Leave a Reply Cancel reply
Fan page
Tags
Recent Posts
- Multi-Layer Prompt Architecture – Chìa khóa Xây dựng Hệ thống AI Phức tạp
- Khi “Prompt Template” Trở Thành Chiếc Hộp Pandora
- Vòng Lặp Ảo Giác
- Giàn Giáo Nhận Thức (Cognitive Scaffold) trong Prompt Engineering
- Bản Thể Học (Ontology) trong Prompt Engineering
- Hướng Dẫn Vibe Coding với Gemini CLI
- Prompt Bản Thể Học (Ontological Prompt) và Kiến Trúc Nhận Thức (Cognitive Architecture Prompt) trong AI
- Prompt for Coding – Code Translation Nâng Cao & Đối Phó Rủi Ro và Đảm Bảo Chất Lượng
- Tại sao cần các Chiến Lược Quản Lý Ngữ Cảnh khi tương tác với LLMs thông qua góc nhìn AI API
- Prompt for Coding – Code Translation với Kỹ thuật Exemplar Selection (k-NN)
You may also like:
Archives
- October 2025 (1)
- September 2025 (4)
- August 2025 (5)
- July 2025 (10)
- June 2025 (1)
- May 2025 (2)
- April 2025 (1)
- March 2025 (8)
- January 2025 (7)
- December 2024 (4)
- September 2024 (1)
- July 2024 (1)
- June 2024 (1)
- May 2024 (4)
- April 2024 (2)
- March 2024 (5)
- January 2024 (4)
- February 2023 (1)
- January 2023 (2)
- November 2022 (2)
- October 2022 (1)
- September 2022 (5)
- August 2022 (6)
- July 2022 (7)
- June 2022 (8)
- May 2022 (5)
- April 2022 (1)
- March 2022 (3)
- February 2022 (5)
- January 2022 (4)
- December 2021 (6)
- November 2021 (8)
- October 2021 (8)
- September 2021 (8)
- August 2021 (8)
- July 2021 (9)
- June 2021 (8)
- May 2021 (7)
- April 2021 (11)
- March 2021 (12)
- February 2021 (3)
- January 2021 (3)
- December 2020 (3)
- November 2020 (9)
- October 2020 (7)
- September 2020 (17)
- August 2020 (1)
- July 2020 (3)
- June 2020 (1)
- May 2020 (2)
- April 2020 (3)
- March 2020 (20)
- February 2020 (5)
- January 2020 (2)
- December 2019 (12)
- November 2019 (12)
- October 2019 (19)
- September 2019 (17)
- August 2019 (10)


