draw(_:) / drawRect là một phương thức của lớp UIView (trong Swift) và tương ứng trong Objective-C (drawRect:), chịu trách nhiệm hiển thị nội dung của view bằng Core Graphics. Hệ thống tự động gọi phương thức này khi view xuất hiện lần đầu trên màn hình và sau khi gọi setNeedsDisplay(). Theo Tài liệu Apple (2025), draw(_:) là phương thức duy nhất mà nhà phát triển có quyền truy cập vào ngữ cảnh đồ họa của màn hình hiện tại để vẽ tùy chỉnh. Ghi đè draw(_:) mang lại toàn quyền kiểm soát giao diện của thành phần — từ các hình dạng hình học đơn giản đến đồ họa hoạt ảnh phức tạp.
Những điểm chính
draw(_:) là một phương thức instance của UIView mà UIKit gọi để hiển thị nội dung của view. Bên trong phương thức này, nhà phát triển có quyền truy cập vào ngữ cảnh đồ họa CGContext và sử dụng API Core Graphics để vẽ đường thẳng, tô màu, văn bản và hình ảnh. drawRect: trong Objective-C thực hiện cùng chức năng nhưng với cú pháp khác: tham số duy nhất là một CGRect chỉ định khu vực cần vẽ lại.
Theo Apple Engineering (2024), draw(_:) hoạt động thông qua kết xuất Bitmap Graphics Context dựa trên CPU, mang lại sự linh hoạt tối đa nhưng yêu cầu nhiều tài nguyên hơn so với CALayer. Quyết định sử dụng draw(_:) được đưa ra dựa trên độ phức tạp của đồ họa và yêu cầu hiệu suất.
Trong Swift, phương thức được khai báo là override func draw(_ rect: CGRect), trong đó rect là hình chữ nhật cần được vẽ lại. Trong Objective-C, chữ ký là - (void)drawRect:(CGRect)rect. Tham số rect có thể nhỏ hơn bounds của view khi vẽ lại một phần qua setNeedsDisplay(_:).
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillRect(context, rect);
}
Hệ thống tự động gọi draw(_:) trong các kịch bản được xác định chặt chẽ. Hiểu được các bộ kích hoạt này giúp tránh vẽ lại không cần thiết và tối ưu hóa hiệu suất của view. Dưới đây là tất cả các trường hợp gọi tự động của phương thức.
Tài liệu Apple cảnh báo: không bao giờ gọi draw(_:) trực tiếp. Hệ thống tự quyết định khi nào thực hiện vẽ, và việc gọi trực tiếp phá vỡ cơ chế lưu cache nội bộ. Để yêu cầu vẽ lại, luôn sử dụng setNeedsDisplay() hoặc setNeedsDisplay(_:).
Ghi đè draw(_:) trong Swift bắt đầu bằng việc lấy ngữ cảnh đồ họa và các lệnh gọi Core Graphics tiếp theo. Nên tạo các phương thức riêng cho các khối vẽ logic — điều này cải thiện khả năng đọc và kiểm thử của mã.
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let context = UIGraphicsGetCurrentContext() else { return }
// Line parameters
context.setStrokeColor(UIColor.darkGray.cgColor)
context.setLineWidth(2.0)
// Drawing a triangle
context.move(to: CGPoint(x: rect.midX, y: rect.minY + 10))
context.addLine(to: CGPoint(x: rect.maxX - 10, y: rect.maxY - 10))
context.addLine(to: CGPoint(x: rect.minX + 10, y: rect.maxY - 10))
context.closePath()
context.strokePath()
}
Trong ví dụ này, draw(_:) vẽ một hình tam giác với đường viền màu xám đậm dày 2 pixel. Gọi super.draw(rect) ở đầu phương thức được Apple khuyến nghị để bảo tồn logic vẽ của lớp cha, mặc dù triển khai mặc định của draw(_:) trong UIView là trống.
Mỗi lần gọi draw(_:) phải tạo ra kết quả giống hệt nhau với cùng dữ liệu đầu vào. Điều này cho phép hệ thống lưu cache kết quả và không gọi lại draw(_:) nếu nội dung của view không thay đổi. Không sử dụng giá trị ngẫu nhiên, thời gian hệ thống hoặc yêu cầu mạng bên trong draw(_:).
drawRect: là phiên bản đầu tiên của phương thức, xuất hiện trong iOS 2.0 cùng với Objective-C. Trong Swift, phương thức được đổi tên thành draw(_:) sử dụng tham số ngoài _. Về mặt chức năng, các phương thức giống hệt nhau: cả hai đều nhận CGRect của khu vực cần vẽ lại và sử dụng UIGraphicsGetCurrentContext() để truy cập ngữ cảnh đồ họa.
| Đặc điểm | draw(_:) (Swift) | drawRect: (Objective-C) |
|---|---|---|
| Chữ ký | override func draw(_ rect: CGRect) | - (void)drawRect:(CGRect)rect |
| Gọi super | super.draw(rect) | [super drawRect:rect] |
| Ngữ cảnh | UIGraphicsGetCurrentContext() | UIGraphicsGetCurrentContext() |
| Tham số rect | rect: CGRect | CGRect rect |
| Hiệu suất | Giống hệt | Giống hệt |
Khi di chuyển dự án từ Objective-C sang Swift, việc đổi tên phương thức là một trong những nhiệm vụ đầu tiên. Xcode cung cấp bộ chuyển đổi tự động, nhưng drawRect: yêu cầu cập nhật thủ công thành draw(_:). Theo Apple (2024), phiên bản Swift draw(_:) được ưu tiên cho các dự án mới.
draw(_:) chạy trên CPU, và triển khai không tối ưu có thể gây giảm khung hình và hiệu suất kém. Apple khuyến nghị một số cách tiếp cận đã được kiểm chứng để tăng tốc độ vẽ.
Mỗi thao tác Core Graphics (move(to:), addLine(to:), strokePath) có chi phí overhead. Nhóm các thao tác và sử dụng CGPath cho các hình dạng phức tạp — đường dẫn được tạo một lần và tái sử dụng ở mỗi lần gọi draw(_:).
UIBezierPath là một wrapper Objective-C của CGPath cung cấp API đơn giản để tạo hình dạng. Tạo UIBezierPath trước (ví dụ trong bộ khởi tạo) và chỉ cần gọi fill() hoặc stroke() bên trong draw(_:).
private let starPath: UIBezierPath = {
let path = UIBezierPath()
// Building a star shape
path.move(to: CGPoint(x: 50, y: 0))
for i in 1...5 {
let angle = CGFloat(i) * 4 * CGFloat.pi / 5
path.addLine(to: CGPoint(x: 50 + 40 * cos(angle),
y: 50 + 40 * sin(angle)))
}
path.close()
return path
}()
override func draw(_ rect: CGRect) {
UIColor.systemYellow.setFill()
starPath.fill()
}
Các nhà phát triển thường mắc cùng một lỗi khi ghi đè draw(_:). Biết được các mẫu này giúp tránh lỗi và suy giảm hiệu suất. Hãy xem các vấn đề phổ biến nhất và giải pháp của chúng.
Tuân theo các quy tắc này, bạn sẽ đảm bảo vẽ tùy chỉnh ổn định và nhanh chóng trong bất kỳ dự án iOS nào. Hãy phân tích draw(_:) thông qua Instruments (Core Animation) để xem thời gian thực thi thực tế và các điểm nghẽn.
Câu hỏi thường gặp
Không, việc gọi trực tiếp draw(_:) bị cấm bởi tài liệu Apple. Hệ thống tự quản lý chu kỳ vẽ. Để yêu cầu vẽ lại, hãy sử dụng setNeedsDisplay(), đánh dấu view là cần cập nhật trong chu kỳ kết xuất gần nhất.
Về mặt chức năng, các phương thức này giống hệt nhau. drawRect: được sử dụng trong Objective-C, draw(_:) trong Swift. Cả hai đều nhận CGRect của khu vực cần vẽ lại và sử dụng cùng ngữ cảnh Core Graphics qua UIGraphicsGetCurrentContext().
Apple tối ưu hóa kết xuất: nếu UIView không có draw(_:) được ghi đè, hệ thống sẽ không tạo ngữ cảnh bitmap cho nó. Điều này tiết kiệm bộ nhớ. Nếu ghi đè tồn tại nhưng phương thức không được gọi — hãy kiểm tra frame của view không bằng 0 và view có hiển thị hay không.
Chỉ khi cần thiết: lần hiển thị đầu tiên, sau setNeedsDisplay(), khi bounds thay đổi với contentMode = .redraw. Ở trạng thái tĩnh, draw(_:) không được gọi lại, tiết kiệm tài nguyên CPU và pin.
Apple khuyến nghị gọi super.draw(rect) ở đầu phương thức được ghi đè. Mặc dù triển khai hiện tại của UIView là trống, việc gọi super đảm bảo tương thích với các phiên bản UIKit trong tương lai và là một thực hành tốt.
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