draw(_:) / drawRect เป็นเมธอดของคลาส UIView (ใน Swift) และสิ่งที่เทียบเท่าใน Objective-C (drawRect:) ซึ่งรับผิดชอบในการเรนเดอร์เนื้อหาของวิวโดยใช้ Core Graphics ระบบจะเรียกเมธอดนี้โดยอัตโนมัติเมื่อวิวปรากฏบนหน้าจอครั้งแรกและหลังจากเรียก setNeedsDisplay() ตาม Apple Documentation (2025) draw(_:) เป็นเมธอดเดียวที่นักพัฒนาสามารถเข้าถึงบริบทกราฟิกของหน้าจอปัจจุบันสำหรับการวาดแบบกำหนดเอง การโอเวอร์ไรด์ draw(_:) ให้ควบคุมรูปลักษณ์ของคอมโพเนนต์ได้อย่างสมบูรณ์ ตั้งแต่รูปทรงเรขาคณิตธรรมดาไปจนถึงกราฟิกแอนิเมชันที่ซับซ้อน
ประเด็นสำคัญ
draw(_:) เป็นเมธอดอินสแตนซ์ของ UIView ที่ UIKit เรียกเพื่อเรนเดอร์เนื้อหาของวิว ภายในเมธอดนี้ นักพัฒนาสามารถเข้าถึงบริบทกราฟิก CGContext และใช้ API ของ Core Graphics เพื่อวาดเส้น การเติมสี ข้อความ และรูปภาพ drawRect: ใน Objective-C ทำหน้าที่เดียวกันแต่มีไวยากรณ์ต่างกัน พารามิเตอร์เดียวคือ CGRect ที่ระบุพื้นที่ที่ต้องวาดใหม่
ตาม Apple Engineering (2024) draw(_:) ทำงานผ่านการเรนเดอร์ Bitmap Graphics Context บน CPU ซึ่งให้ความยืดหยุ่นสูงสุดแต่ต้องการทรัพยากรมากกว่าเมื่อเทียบกับ CALayer การตัดสินใจใช้ draw(_:) ขึ้นอยู่กับความซับซ้อนของกราฟิกและข้อกำหนดด้านประสิทธิภาพ
ใน Swift เมธอดถูกประกาศเป็น override func draw(_ rect: CGRect) โดยที่ rect คือสี่เหลี่ยมผืนผ้าที่ต้องวาดใหม่ ใน Objective-C ลายเซ็นคือ - (void)drawRect:(CGRect)rect พารามิเตอร์ rect อาจเล็กกว่า bounds ของวิวในระหว่างการวาดใหม่บางส่วนผ่าน setNeedsDisplay(_:)
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillRect(context, rect);
}
ระบบเรียก draw(_:) โดยอัตโนมัติในสถานการณ์ที่กำหนดไว้อย่างชัดเจน การทำความเข้าใจทริกเกอร์เหล่านี้ช่วยหลีกเลี่ยงการวาดใหม่ที่ไม่จำเป็นและเพิ่มประสิทธิภาพของวิว ด้านล่างคือกรณีทั้งหมดของการเรียกเมธอดโดยอัตโนมัติ
เอกสารของ Apple เตือน: อย่าเรียก draw(_:) โดยตรง ระบบเป็นผู้ตัดสินใจเองว่าเมื่อใดควรวาด และการเรียกโดยตรงจะทำลายกลไกการแคชภายใน หากต้องการขอให้วาดใหม่ ให้ใช้ setNeedsDisplay() หรือ setNeedsDisplay(_:) เสมอ
การโอเวอร์ไรด์ draw(_:) ใน Swift เริ่มต้นด้วยการรับบริบทกราฟิกและการเรียก Core Graphics ตามมา แนะนำให้สร้างเมธอดแยกต่างหากสำหรับบล็อกการวาดเชิงตรรกะ ซึ่งช่วยปรับปรุงความสามารถในการอ่านและการทดสอบโค้ด
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()
}
ในตัวอย่างนี้ draw(_:) วาดรูปสามเหลี่ยมที่มีโครงร่างสีเทาเข้มหนา 2 พิกเซล การเรียก super.draw(rect) ที่จุดเริ่มต้นของเมธอดเป็นสิ่งที่ Apple แนะนำเพื่อรักษาตรรกะการวาดของพ่อแม่ แม้ว่าการใช้งาน draw(_:) เริ่มต้นของ UIView จะว่างเปล่า
การเรียก draw(_:) แต่ละครั้งควรให้ผลลัพธ์ที่เหมือนกันกับข้อมูลนำเข้าเดียวกัน วิธีนี้ช่วยให้ระบบแคชผลลัพธ์และไม่เรียก draw(_:) ซ้ำหากเนื้อหาของวิวไม่เปลี่ยนแปลง ห้ามใช้ ค่าสุ่ม เวลาระบบ หรือคำขอเครือข่ายภายใน draw(_:)
drawRect: เป็นเมธอดรุ่นแรกในประวัติศาสตร์ที่ปรากฏใน iOS 2.0 พร้อมกับ Objective-C ใน Swift เมธอดถูกเปลี่ยนชื่อเป็น draw(_:) โดยใช้พารามิเตอร์ภายนอก _ ในทางหน้าที่ เมธอดเหล่านี้เหมือนกัน ทั้งคู่รับ CGRect ของพื้นที่ที่ต้องวาดใหม่และใช้ UIGraphicsGetCurrentContext() เพื่อเข้าถึงบริบทกราฟิก
| ลักษณะ | draw(_:) (Swift) | drawRect: (Objective-C) |
|---|---|---|
| ลายเซ็น | override func draw(_ rect: CGRect) | - (void)drawRect:(CGRect)rect |
| การเรียก super | super.draw(rect) | [super drawRect:rect] |
| บริบท | UIGraphicsGetCurrentContext() | UIGraphicsGetCurrentContext() |
| พารามิเตอร์ rect | rect: CGRect | CGRect rect |
| ประสิทธิภาพ | เหมือนกัน | เหมือนกัน |
เมื่อโยกย้ายโปรเจกต์จาก Objective-C ไปยัง Swift การเปลี่ยนชื่อเมธอดเป็นหนึ่งในงานแรก Xcode มีตัวแปลงอัตโนมัติ แต่ drawRect: ต้องอัปเดตเป็น draw(_:) ด้วยตนเอง ตาม Apple (2024) เวอร์ชัน Swift draw(_:) เหมาะสำหรับโปรเจกต์ใหม่มากกว่า
draw(_:) ทำงานบน CPU และการใช้งานที่ไม่เหมาะสมอาจทำให้เฟรมดรอปและประสิทธิภาพต่ำ Apple แนะนำวิธีการที่พิสูจน์แล้วหลายวิธีเพื่อเร่งการวาด
แต่ละการดำเนินการ Core Graphics (move(to:), addLine(to:), strokePath) มีโอเวอร์เฮด จัดกลุ่มการดำเนินการและใช้ CGPath สำหรับรูปทรงที่ซับซ้อน — เส้นทางถูกสร้างครั้งเดียวและนำมาใช้ใหม่ทุกครั้งที่เรียก draw(_:)
UIBezierPath เป็นตัวหุ้ม Objective-C รอบ CGPath ที่ให้ API ง่ายๆ สำหรับการสร้างรูปทรง สร้าง UIBezierPath ล่วงหน้า (เช่น ในตัวเริ่มต้น) และเรียก fill() หรือ stroke() ภายใน 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()
}
นักพัฒนามักทำผิดพลาดเหมือนกันเมื่อโอเวอร์ไรด์ draw(_:) การรู้จักรูปแบบเหล่านี้ช่วยหลีกเลี่ยงบั๊กและการลดประสิทธิภาพ มาดูปัญหาที่พบบ่อยที่สุดและวิธีแก้ไขกัน
การปฏิบัติตามกฎเหล่านี้จะช่วยให้คุณวาดวิวแบบกำหนดเองได้อย่างเสถียรและรวดเร็วในทุกโปรเจกต์ iOS โปรไฟล์ draw(_:) ผ่าน Instruments (Core Animation) เพื่อดูเวลาการทำงานจริงและจุดคอขวด
คำถามที่พบบ่อย
ไม่ได้ การเรียก draw(_:) โดยตรงถูกห้ามโดยเอกสารของ Apple ระบบจัดการรอบการวาดด้วยตัวเอง หากต้องการขอให้วาดใหม่ ให้ใช้ setNeedsDisplay() ซึ่งทำเครื่องหมายวิวว่าต้องการอัปเดตในรอบการเรนเดอร์ถัดไปอย่างถูกต้อง
ในทางหน้าที่เมธอดเหล่านี้เหมือนกัน drawRect: ใช้ใน Objective-C draw(_:) ใช้ใน Swift ทั้งคู่รับ CGRect ของพื้นที่ที่ต้องวาดใหม่และใช้บริบท Core Graphics เดียวกันผ่าน UIGraphicsGetCurrentContext()
Apple ปรับปรุงประสิทธิภาพการเรนเดอร์: หาก UIView ไม่มี draw(_:) ที่ถูกโอเวอร์ไรด์ ระบบจะไม่สร้างบริบทบิตแมปให้ ซึ่งประหยัดหน่วยความจำ หากมีการโอเวอร์ไรด์แต่เมธอดไม่ถูกเรียก — ตรวจสอบว่าเฟรมของวิวไม่เป็นศูนย์และวิวมองเห็นได้
เฉพาะเมื่อจำเป็น: การเรนเดอร์ครั้งแรก หลังจาก setNeedsDisplay() เมื่อ bounds เปลี่ยนด้วย contentMode = .redraw ในสถานะคงที่ draw(_:) จะไม่ถูกเรียกซ้ำ ช่วยประหยัดทรัพยากร CPU และแบตเตอรี่
Apple แนะนำให้เรียก super.draw(rect) ที่จุดเริ่มต้นของเมธอดที่ถูกโอเวอร์ไรด์ แม้ว่าการใช้งาน UIView ปัจจุบันจะว่างเปล่า การเรียก super ช่วยให้เข้ากันได้กับ UIKit เวอร์ชันอนาคตและเป็นแนวปฏิบัติที่ดี
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ