Contents
Chào bạn, chúng ta lại tiếp tục với seri học iOS cơ bản. Bài viết này sẽ hướng dẫn bạn custom view cho giao diện ứng dụng iOS.
Nếu bạn chưa biết gì về View thì có thể đọc bài viết này: Basic iOS tutorial : View
Chuẩn bị
- MacOS 10.14.4
- Xcode 11.0
- Swift 5.1
1. Custom View là làm gì?
- Custom View là tạo ra các UI Control mới, để cho giao diện ứng dụng theo đúng với thiết kế giao diện của ứng dụng iOS.
- Các thiết kế giao diện thường sẽ phức tạp và không sử dụng các UI Control cơ bản.
1.1. Các mức cho custom
- Kế thừa:
- Tạo Sub-class
- Tuỳ chỉnh giao diện
- Kết hợp:
- Sử dụng các UI Control cơ bản để tạo thành giao diện phức tạp
- Tuỳ biến các thuộc tính
1.2. Các công việc chính trong custom view
- Tạo giao diện
- Quản lý sự kiện
2. Tạo giao diện
- Xem sơ đồ sau về các con đường tạo giao diện cho 1 custom view

- Việc quan trọng đầu tiên của bạn sẽ làm
Tạo sub-class từ class UIView để tạo class mới cho custom view của bạn.
- Phương pháp tạo thì theo 2 trường phái chính:
- Code chay
- Add Subview: Cách tạo giao diện từ việc sử dụng các UI Control đơn giản kết hợp thành UI Controll phức tạp
- Draw: vẽ, dùng cho việc tạo giao diện theo phương pháp vẽ và thao tác với canvas
- Kéo thả
- LoadNibName: load 1 file
*.xibchứa giao diện của Custom View, sau đó ép kiểu về thành kiểu class của Custom View
- LoadNibName: load 1 file
- Code chay
2.1. Code chay giao diện
- Bước 1: New file –> Tạo sub-class từ UIView

- Bước 2: Add các UI Control cơ bản. Trong ví dụ thì custom view bao gồm:
- 1 UIImageView
- 1 UILabel
- 1 UIButton
import UIKit
class MyView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .blue
// add user avatar
let userAvatar = UIImageView(image: UIImage(named: "no_avatar"))
userAvatar.frame = CGRect(x: 0,
y: 0,
width: frame.size.width,
height: frame.size.height*4/5)
userAvatar.contentMode = .scaleAspectFit
self.addSubview(userAvatar)
// add user name
let userName = UILabel(frame: CGRect(x: 0,
y: frame.size.height*4/5,
width: frame.size.width,
height: frame.size.height/5))
userName.text = "Fx Studio"
userName.backgroundColor = .lightGray
userName.textAlignment = .center
userName.textColor = .blue
self.addSubview(userName)
// add button
let button = UIButton(frame: CGRect(x: 0,
y: 0,
width: frame.size.width,
height: frame.size.height))
button.backgroundColor = .clear
self.addSubview(button)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
- Bước 4: Tạo đối tượng của custom view
let myView = MyView(frame: CGRect(x: 50, y: 100, width: 100, height: 125))
- Bước 5: add đối tượng vào view
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let myView = MyView(frame: CGRect(x: 50, y: 100, width: 100, height: 125))
view.addSubview(myView)
}
}
- Kết quả:

2.2. Thay đổi thuộc tính của Custom View
- Việc custom view có ý nghĩa thì nó như 1 UIView bình thường
- Nghĩa là:
- Có thể tạo nhiều đối tượng
- Tái sử dụng được nhiều lần
- Tuỳ biến được giao diện
- Đảm bảo cho các việc đó thì chúng ta phải khai báo
- Thuộc tính là public
- Các hàm
initkhác nhau với tham số khác nhau truyền vào
- Quay lại file
MyView.swift- Khai báo 3 thuộc tính public
- Viết lại hàm
init
import UIKit
class MyView: UIView {
var avatarImageView: UIImageView?
var nameLabel: UILabel?
var markButton: UIButton?
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .blue
// add user avatar
avatarImageView = UIImageView(image: UIImage(named: "no_avatar"))
avatarImageView!.frame = CGRect(x: 0,
y: 0,
width: frame.size.width,
height: frame.size.height*4/5)
avatarImageView!.contentMode = .scaleAspectFill
self.addSubview(avatarImageView!)
// add user name
nameLabel = UILabel(frame: CGRect(x: 0,
y: frame.size.height*4/5,
width: frame.size.width,
height: frame.size.height/5))
nameLabel!.text = "Fx Studio"
nameLabel!.backgroundColor = .lightGray
nameLabel!.textAlignment = .center
nameLabel!.textColor = .blue
self.addSubview(nameLabel!)
// add button
markButton = UIButton(frame: CGRect(x: 0,
y: 0,
width: frame.size.width,
height: frame.size.height))
markButton!.backgroundColor = .clear
self.addSubview(markButton!)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
- Mở file
ViewController.swift- Tạo 2 đối tương
MyView addSubviewvào View của ViewController
- Tạo 2 đối tương
override func viewDidLoad() {
super.viewDidLoad()
let hamster = MyView(frame: CGRect(x: 50, y: 100, width: 100, height: 125))
hamster.nameLabel?.text = "hamster"
hamster.avatarImageView?.image = UIImage(named: "hamster")
view.addSubview(hamster)
let husky = MyView(frame: CGRect(x: 200, y: 100, width: 100, height: 125))
husky.nameLabel?.text = "husky"
husky.avatarImageView?.image = UIImage(named: "husky")
view.addSubview(husky)
}
- Kết quả:

2.3. Bắt sự kiện người dùng
- Với việc bắt sự kiện người dùng bằng phương pháp code chay thì chúng ta có
addTargetcho UI Control - Mở file
MyView.swift- Khai báo thêm 1 biến class
count
- Khai báo thêm 1 biến class
var count = 0
-
addTargetcho markButton
// add button
markButton = UIButton(frame: CGRect(x: 0,
y: 0,
width: frame.size.width,
height: frame.size.height))
markButton!.backgroundColor = .clear
markButton?.addTarget(self, action: #selector(tap), for: .touchUpInside)
self.addSubview(markButton!)
-
- Thêm function đếm số lần tap vào
@objc func tap() {
count += 1
nameLabel?.text = "\(count)"
}
- Kết quả:

2.4. Kéo thả giao diện
- Khó khăn khi code chay
- Việc code chay thì rất thú vị nhưng cũng rất tốn công sức.
- Bạn không thể hình dung ra giao diện của mình như thế nào trong lúc code
- Thay đổi vị trí các control sẽ gặp khó khăn
- Giải pháp:
Sử dụng phương pháp
LoadNibNameđể load 1 file giao diện (*.xib) và ép kiểu của nó về với kiểu class của custom và sử dụng như bình thường.
NibNamelà gì?- Là tên gọi của các file giao diện *.xib
- Format chính là
xml - 1 Custom có thể có nhiều file *.xib khác nhau
- 1 cái tên Nib có thể cho nhiều file *.xib khác nhau
Các bước thực hiện như sau:
- Bước 1: Tạo sub-class

- Bước 2: Tạo file *.xib với tên là tên của sub-class
- New File > User Interface > View
- Đặt tên giống với sub-class custom view

- Bước 3: tiến hành kéo thả giao diện, tạo outlet & action
- Tuỳ chỉnh
Freeformđể có thể kéo thả size của view
- Tuỳ chỉnh

-
- Kéo thả outlet và action
- Chú ý class name cho custom view

-
- Code xử lý ở trong
import UIKit
class UserView: UIView {
var count = 0
@IBOutlet weak var avatarImageView: UIImageView!
@IBOutlet weak var nameLabel: UILabel!
@IBAction func tap(_ sender: Any) {
count += 1
nameLabel.text = "\(count)"
}
}
- Bước 4: loadNibName và addSubView
- Dùng main Bundle để load file *.xib với tham số là tên file
- Chú ý tên của file Nib nếu sai thì app sẽ bị crash
- Vì dữ liệu trả về là 1 array nên sử dụng phần tử đầu tiên
.first - Ép kiểm về
UserView
let userView = Bundle.main.loadNibNamed("UserView", owner: self, options: nil)?.first as? UserView
userView?.frame = CGRect(x: 50, y: 250, width: 100, height: 125)
view.addSubview(userView!)
- Kết quả

- Các Action vẫn hoạt động như bình thường
3. Quản lý sự kiện

- Đây là sơ đồ các kiểu bắt sự kiện của người dùng tác động lên Custom View
- Cần lưu ý điểm sau
- Sự kiện mà Custom view bắt được thì sẽ được xử lý ở đối tượng custom view
- Chúng không thuộc ViewController nên ViewController không thể xử lý được các sự kiện này
- Muốn truyền sự kiện về ViewController thì sử dụng phương pháp an toán nhất là
protocol
Truyền sự kiện và dữ liệu về ViewController
3.1. Tạo protocol cho custom view
- Mở file
UserView.swiftthêm đoạn code sau:
protocol UserViewDelegate {
func didTap(view: UserView, count: Int)
}
- Tạo đối tượng protocol và sử dụng, chú ý:
- Phải kiểu
optional - Kiểm tra khác
niltrước khi gọi hàm
- Phải kiểu
import UIKit
protocol UserViewDelegate {
func didTap(view: UserView, count: Int)
}
class UserView: UIView {
var count = 0
var delegate: UserViewDelegate?
@IBOutlet weak var avatarImageView: UIImageView!
@IBOutlet weak var nameLabel: UILabel!
@IBAction func tap(_ sender: Any) {
count += 1
nameLabel.text = "\(count)"
if let delegate = delegate {
delegate.didTap(view: self, count: count)
}
}
}
3.2. Implement Protocol trong ViewController
- Mở file
ViewController.swiftthêm 1extention- Kế thừa lại
UserViewDelegate - Định nghĩa lại hàm
didTap
- Kế thừa lại
extension ViewController: UserViewDelegate {
func didTap(view: UserView, count: Int) {
print("Count = \(count)")
}
}
- Set
delegatecủa đối tượng custom view bằng ViewController- Vì ViewController đã kế thừa protocol
UserViewDelegatenên cùng kiểu với thuộc tínhdelegatecủa đối tượng custom view
- Vì ViewController đã kế thừa protocol
let userView = Bundle.main.loadNibNamed("UserView", owner: self, options: nil)?.first as? UserView
userView?.frame = CGRect(x: 50, y: 250, width: 100, height: 125)
userView?.delegate = self
view.addSubview(userView!)
- Khi sự kiện được bắt ở Custom View thì nó sử dụng
delegatecủa mình- Gọi function
didTapđể truyền sự kiện đi - Vì ViewController đã xét cho
delegatenêndelegatechính là ViewController - Trong ViewController đã cài đặt lại function
didTapnên khi delegate gọi hàm đó thực thi với các đối số truyền vào. Thì cũng chính là ViewController gọi hàmdidTapvà nhận được các giá trị mới thông qua các đối số.
- Gọi function
- Kết quả

Tới đây thì tạm thời chúng ta xong được phần Custom View. Các giao diện phức tạp vẫn cấu thành từ các giao diện đơn giản hơn. Nên bạn cần phải phân tích giao diện và custom cho phù hợp.
Tạm kết:
- Custom View bằng code chay và LoadNibName
- Xử lý được các sự kiện người dùng
- Truyền sự kiện và dữ liệu về ViewController
- Sử dụng được Protocol
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!
2 comments
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)



Broadly speaking, there are three types of user interface design approaches that you can take, each with its pros and cons, its fans and haters: iOS Storyboards : A visual tool for laying out multiple application views and the transitions between them. NIBs or XIBs : Each NIB file corresponds to a single view element and can be laid out in the Interface Builder, making it a visual tool as well. Custom Code : i.e., no GUI tools, but rather, handling all custom positioning, animation, etc.
Thank you! 👍