Custom UIView: khái niệm, tạo và ghi đè drawRect

Tác giả: IT Sectr Đã đăng: 2026-07-20 Thời gian đọc: 7 phút

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 — một lớp con của UIView với các phương thức được ghi đè để vẽ và hành vi tùy chỉnh
  • draw(_:) — phương thức vẽ chính, được hệ thống gọi khi view xuất hiện lần đầu trên màn hình
  • init(frame:)init(coder:) — các bộ khởi tạo bắt buộc để tạo view từ mã và Storyboard
  • layoutSubviews() — được gọi khi kích thước view thay đổi và cho phép tính toán lại hình học của các phần tử con
  • CALayer — lớp bên dưới mà qua đó có thể tối ưu hóa việc vẽ mà không cần ghi đè draw(_:)

Custom UIView là gì và khi nào cần

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.

Các trường hợp sử dụng điển hình

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.

Khi nào không cần Custom UIView

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 đủ.

Cách tạo Custom UIView trong Xcode

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.

swift
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@IBInspectable để xem trước trực tiếp.

Các phương thức vòng đời của UIView

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ứcKhi nào được gọiMụ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/XIBGiải tuần tự và thiết lập ban đầu
layoutSubviews()Khi khung thay đổiTí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ấpThiế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(_:).

Ghi đè draw(_:) để vẽ

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.

swift
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.

Quy tắc draw(_:) hiệu quả

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 so với draw(_:): chọn cái nào

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ốngCách tiếp cận được khuyến nghịHiệu suất
Bo góclayer.cornerRadiusGPU, cao
Đổ bóng và chuyển màuCAGradientLayer, shadowPathGPU, cao
Hình dạng tùy ýCAShapeLayer với UIBezierPathGPU, cao
Đồ họa phức tạpdraw(_:) với Core GraphicsCPU, trung bình
Văn bản với định dạng tùy chỉnhCATextLayer 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(_:).

Tối ưu hóa hiệu suất Custom UIView

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.

Tránh vẽ lại không cần thiết

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.

Lưu vào bộ nhớ đệm nội dung đã render

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.

swift
func renderToImage() -> UIImage {
    let renderer = UIGraphicsImageRenderer(size: bounds.size)
    return renderer.image { ctx in
        drawHierarchy(in: bounds, afterScreenUpdates: true)
    }
}

Sử dụng shouldRasterize cho các lớp tĩnh

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

Có bắt buộc phải ghi đè draw(_:) trong Custom UIView không?

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.

Làm thế nào để thêm Custom UIView vào Storyboard?

Đặ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.

Sự khác biệt giữa init(frame:) và init(coder:) là gì?

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.

Tại sao draw(_:) không được gọi?

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.

Làm thế nào để cập nhật nội dung Custom UIView mà không vẽ lại toàn bộ?

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

  • Custom UIView — một lớp con của UIView với các phương thức được ghi đè để vẽ tùy chỉnh, được sử dụng khi các thành phần UIKit tiêu chuẩn không đủ
  • draw(_:) — phương thức vẽ tùy chỉnh qua Core Graphics, được hệ thống tự động gọi; không được gọi trực tiếp
  • CALayer — cách tiếp cận ưu tiên cho đồ họa tăng tốc GPU (bóng, bo tròn, hình dạng), thay thế cho draw(_:) với hiệu suất cao hơn
  • init(frame:)init(coder:) — các bộ khởi tạo bắt buộc; cả hai phải được triển khai để hoạt động chính xác từ mã và Storyboard
  • setNeedsDisplay() — tín hiệu cho hệ thống gọi lại draw(_:); sử dụng quá tải với CGRect để vẽ lại một phần
  • @IBDesignable — thuộc tính để xem trước trực tiếp Custom UIView trong Interface Builder, đơn giản hóa phát triển trực quan
  • Đối với đồ họa tĩnh, hãy lưu kết quả vào bộ nhớ đệm qua UIGraphicsImageRenderer và vẽ hình ảnh đã chuẩn bị — điều này giảm tải CPU

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.

Thảo luận dự án

Đọc thêm