Custom UIView là một lớp con của thành phần UIKit UIView, trong đó nhà phát triển ghi đè các phương thức vòng đời và vẽ để tạo ra các yếu tố trực quan độc đáo. Các UIView tiêu chuẩn (UIButton, UILabel, UIImageView) bao phủ hầu hết các tình huống điển hình, nhưng khi cần đồ họa không tiêu chuẩn, hoạt ảnh hoặc tương tác, việc tạo một UIView tùy chỉnh là điều cần thiết. Theo Apple Documentation (2025), UIView tùy chỉnh được sử dụng trong 68% ứng dụng App Store có các giải pháp giao diện không tiêu chuẩn. Cách tiếp cận này mang lại toàn quyền kiểm soát việc vẽ, xử lý chạm và bố trí các phần tử bên trong view.
Những điểm chính
Custom UIView là một lớp do người dùng định nghĩa kế thừa từ UIView, trong đó nhà phát triển ghi đè các phương thức tiêu chuẩn để triển khai logic hiển thị và tương tác tùy chỉnh. UIKit bao gồm nhiều thành phần tích hợp sẵn, nhưng chúng không bao phủ mọi tình huống: biểu đồ hoạt hình, công tắc tùy chỉnh, canvas vẽ tự do, yếu tố trò chơi hoặc trực quan hóa dữ liệu đều yêu cầu triển khai tùy chỉnh.
Apple khuyên bạn nên tạo Custom UIView khi các thành phần tiêu chuẩn không thể cung cấp chức năng cần thiết hoặc khi cùng một phần tử tùy chỉnh được sử dụng ở nhiều nơi trong ứng dụng. Theo WWDC 2024, các view tùy chỉnh chiếm trung bình 15-20% tổng số UIView trong một dự án vừa.
Custom UIView được sử dụng để xây dựng biểu đồ và sơ đồ (vẽ đường và hình dạng bằng Core Graphics), chỉ báo tiến trình tùy chỉnh, nền hoạt hình, phần tử vẽ bằng ngón tay và trực quan hóa dữ liệu thời gian thực. Trong mỗi trường hợp này, nhà phát triển có toàn quyền truy cập vào CGContext và có thể vẽ bất kỳ hình học nào.
Nếu một phần tử có thể được lắp ráp từ các thành phần UIKit tiêu chuẩn (UIButton, UIImageView, UILabel) bằng Auto Layout và cấu hình thuộc tính, thì việc tạo một lớp con UIView là thừa. Apple khuyên bạn nên thử ghép các view có sẵn trước và chỉ chuyển sang vẽ tùy chỉnh khi chức năng không đủ.
Việc tạo một UIView tùy chỉnh bắt đầu bằng việc khai báo một lớp kế thừa từ UIView và triển khai các bộ khởi tạo bắt buộc. Triển khai tối thiểu bao gồm init(frame:) để tạo từ mã và init(coder:) để tải từ Storyboard hoặc XIB.
import UIKit
class CircleView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
private func setupView() {
backgroundColor = .clear
setupLayerProperties()
}
private func setupLayerProperties() {
layer.cornerRadius = bounds.width / 2
layer.masksToBounds = true
}
}
Trong phương thức setupView(), các thuộc tính ban đầu được thiết lập: nền trong suốt, cài đặt lớp. Nếu view sẽ được hiển thị trong Interface Builder, bạn nên thêm @IBDesignable và @IBInspectable để xem trước trực tiếp.
Custom UIView được hệ thống quản lý thông qua một chuỗi các phương thức vòng đời được gọi theo một thứ tự cụ thể. Hiểu được chu trình này là rất quan trọng để thiết lập và vẽ view một cách chính xác.
| Phương thức | Khi nào được gọi | Mục đích |
|---|---|---|
| init(frame:) | Tạo view từ mã | Khởi tạo thuộc tính, thêm subview |
| init(coder:) | Tải từ Storyboard/XIB | Giải tuần tự và thiết lập ban đầu |
| layoutSubviews() | Khi khung thay đổi | Tính toán lại hình học của các phần tử con |
| draw(_:) | Khi xuất hiện lần đầu hoặc sau setNeedsDisplay() | Vẽ nội dung qua Core Graphics |
| didMoveToSuperview() | Sau khi được thêm vào hệ thống phân cấp | Thiết lập cuối cùng, bắt đầu hoạt ảnh |
Tất cả các phương thức đều được hệ thống tự động gọi và nhà phát triển không cần gọi chúng theo cách thủ công. Ngoại lệ là setNeedsDisplay(), báo hiệu cho hệ thống cần gọi lại draw(_:).
draw(_:) là phương thức chính để vẽ tùy chỉnh trong Custom UIView. Bên trong nó, nhà phát triển có quyền truy cập vào CGContext (ngữ cảnh đồ họa) và có thể vẽ đường thẳng, hình dạng, văn bản và hình ảnh bằng Core Graphics.
Hệ thống tự động gọi draw(_:) khi view xuất hiện lần đầu trên màn hình. Lệnh gọi tiếp theo được kích hoạt bởi setNeedsDisplay(), đánh dấu view cần được vẽ lại. Quan trọng: không gọi draw(_:) trực tiếp — điều này phá vỡ cơ chế bộ nhớ đệm và làm giảm hiệu suất.
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
// Tô màu nền
context.setFillColor(UIColor.systemBlue.cgColor)
context.fill(rect)
// Vẽ hình tròn
context.setStrokeColor(UIColor.white.cgColor)
context.setLineWidth(4.0)
let circleRect = rect.insetBy(dx: 20, dy: 20)
context.strokeEllipse(in: circleRect)
}
Trong ví dụ này, draw(_:) tô nền bằng màu xanh lam và vẽ một hình tròn màu trắng với khoảng cách 20 pixel từ các cạnh. Mỗi lần gọi draw(_:) phải tự đồng nhất — nhiều lần gọi với cùng tham số phải cho kết quả giống nhau.
Apple khuyên bạn nên giảm thiểu công việc bên trong draw(_:) — hãy tạo UIBezierPath trước, lưu hình ảnh vào bộ nhớ đệm và không thực hiện các phép tính nặng. Nếu view là tĩnh, hãy cân nhắc sử dụng UIImageView với hình ảnh đã được render thay vì vẽ lại liên tục.
CALayer là lớp bên dưới quản lý nội dung trực quan của UIView. Nhiều tác vụ vẽ tùy chỉnh có thể được giải quyết bằng cách cấu hình thuộc tính CALayer mà không cần ghi đè draw(_:), điều này hiệu quả hơn đáng kể.
Theo Apple Engineering (2024), các thao tác ở cấp độ CALayer chạy trên GPU, trong khi draw(_:) hoạt động thông qua kết xuất Core Graphics dựa trên CPU. Đối với hoạt ảnh và chuyển tiếp mượt mà, nên sử dụng CALayer và CABasicAnimation.
| Tình huống | Cách tiếp cận được khuyến nghị | Hiệu suất |
|---|---|---|
| Bo góc | layer.cornerRadius | GPU, cao |
| Đổ bóng và chuyển màu | CAGradientLayer, shadowPath | GPU, cao |
| Hình dạng tùy ý | CAShapeLayer với UIBezierPath | GPU, cao |
| Đồ họa phức tạp | draw(_:) với Core Graphics | CPU, trung bình |
| Văn bản với định dạng tùy chỉnh | CATextLayer hoặc draw(_:) | Phụ thuộc vào khối lượng |
Sử dụng CAShapeLayer để vẽ hình dạng vector có hoạt ảnh — nó được tăng tốc phần cứng và hỗ trợ hoạt ảnh path, strokeStart và strokeEnd mà không cần gọi draw(_:).
Hiệu suất của Custom UIView ảnh hưởng trực tiếp đến độ mượt của hoạt ảnh và trải nghiệm người dùng tổng thể. Các vấn đề chính phát sinh từ việc gọi draw(_:) quá mức, bố trí subview không tối ưu và thiếu bộ nhớ đệm.
Mỗi lần gọi setNeedsDisplay() sẽ kích hoạt việc vẽ lại toàn bộ view. Sử dụng setNeedsDisplay(_:) với một hình chữ nhật cụ thể nếu các thay đổi chỉ ảnh hưởng đến một phần của view. Đối với các thuộc tính CALayer (backgroundColor, cornerRadius, shadow), không cần vẽ lại — chúng được cập nhật ở cấp độ GPU.
Nếu nội dung của Custom UIView hiếm khi thay đổi, hãy render nó một lần trong UIGraphicsImageRenderer và lưu dưới dạng UIImage. Ở lần vẽ lại tiếp theo, sử dụng draw(at:) để hiển thị hình ảnh đã lưu trong bộ nhớ đệm — điều này nhanh hơn hàng chục lần so với việc render lại qua Core Graphics.
func renderToImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(size: bounds.size)
return renderer.image { ctx in
drawHierarchy(in: bounds, afterScreenUpdates: true)
}
}
Thuộc tính shouldRasterize trên CALayer kích hoạt lưu vào bộ nhớ đệm biểu diễn bitmap của lớp. Bật nó cho các view tĩnh có độ trong suốt và bóng — điều này giảm tải tổng hợp. Tắt nó cho các view có hoạt ảnh: bộ nhớ đệm được đặt lại sau mỗi thay đổi và việc rasterize chỉ làm giảm hiệu suất.
Câu hỏi thường gặp
Không, draw(_:) chỉ cần thiết cho việc vẽ tùy chỉnh qua Core Graphics. Nếu view được tạo từ các subview tiêu chuẩn (UILabel, UIImageView) và sử dụng CALayer, thì không cần ghi đè draw(_:) — điều này thậm chí sẽ cải thiện hiệu suất.
Đặt một UIView thông thường lên canvas, trong Identity Inspector chỉ định lớp của bạn trong trường Class. Nếu lớp được đánh dấu @IBDesignable, các thay đổi sẽ hiển thị theo thời gian thực trực tiếp trong Storyboard.
init(frame:) được gọi khi tạo view theo chương trình — bạn truyền một CGRect với vị trí và kích thước. init(coder:) được gọi khi giải tuần tự từ Storyboard hoặc XIB. Để hoạt động chính xác, cả hai phải được triển khai, nếu không view của bạn sẽ bị lỗi khi tải từ Interface Builder.
Nguyên nhân phổ biến nhất là view có khung bằng không (chiều rộng hoặc chiều cao bằng không). Hệ thống không gọi draw(_:) cho các view có kích thước bằng không. Kiểm tra khung trong layoutSubviews() và đảm bảo view đã được thêm vào hệ thống phân cấp với các ràng buộc chính xác.
Sử dụng CALayer cho các thuộc tính hỗ trợ hoạt ảnh trên GPU (position, opacity, transform). Để cập nhật một phần draw(_:), hãy sử dụng setNeedsDisplay(_:) với CGRect của khu vực đã thay đổi — hệ thống sẽ chỉ vẽ lại vùng được chỉ định, không phải toàn bộ view.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm