draw(_:) และ drawRect — คืออะไร การเรียกและการโอเวอร์ไรด์

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-07-20 เวลาอ่าน: 7 นาที

draw(_:) / drawRect เป็นเมธอดของคลาส UIView (ใน Swift) และสิ่งที่เทียบเท่าใน Objective-C (drawRect:) ซึ่งรับผิดชอบในการเรนเดอร์เนื้อหาของวิวโดยใช้ Core Graphics ระบบจะเรียกเมธอดนี้โดยอัตโนมัติเมื่อวิวปรากฏบนหน้าจอครั้งแรกและหลังจากเรียก setNeedsDisplay() ตาม Apple Documentation (2025) draw(_:) เป็นเมธอดเดียวที่นักพัฒนาสามารถเข้าถึงบริบทกราฟิกของหน้าจอปัจจุบันสำหรับการวาดแบบกำหนดเอง การโอเวอร์ไรด์ draw(_:) ให้ควบคุมรูปลักษณ์ของคอมโพเนนต์ได้อย่างสมบูรณ์ ตั้งแต่รูปทรงเรขาคณิตธรรมดาไปจนถึงกราฟิกแอนิเมชันที่ซับซ้อน

ประเด็นสำคัญ

  • draw(_:) — เมธอด UIView สำหรับการวาดแบบกำหนดเองผ่าน Core Graphics ไม่ได้ถูกเรียกโดยตรงโดยนักพัฒนา
  • drawRect: — สิ่งที่เทียบเท่า draw(_:) ใน Objective-C แตกต่างทางไวยากรณ์ แต่เหมือนกันทางหน้าที่
  • CGContext — บริบทกราฟิกที่พร้อมใช้งานภายใน draw(_:) สำหรับการดำเนินการวาดทั้งหมด
  • setNeedsDisplay() — วิธีที่ถูกต้องเพียงวิธีเดียวในการขอให้วาด draw(_:) ใหม่จากฝั่งนักพัฒนา
  • UIGraphicsGetCurrentContext() — ฟังก์ชันรับบริบทปัจจุบันภายใน draw(_:) ขั้นตอนบังคับก่อนการวาด

draw(_:) / drawRect คืออะไร

draw(_:) เป็นเมธอดอินสแตนซ์ของ UIView ที่ UIKit เรียกเพื่อเรนเดอร์เนื้อหาของวิว ภายในเมธอดนี้ นักพัฒนาสามารถเข้าถึงบริบทกราฟิก CGContext และใช้ API ของ Core Graphics เพื่อวาดเส้น การเติมสี ข้อความ และรูปภาพ drawRect: ใน Objective-C ทำหน้าที่เดียวกันแต่มีไวยากรณ์ต่างกัน พารามิเตอร์เดียวคือ CGRect ที่ระบุพื้นที่ที่ต้องวาดใหม่

ตาม Apple Engineering (2024) draw(_:) ทำงานผ่านการเรนเดอร์ Bitmap Graphics Context บน CPU ซึ่งให้ความยืดหยุ่นสูงสุดแต่ต้องการทรัพยากรมากกว่าเมื่อเทียบกับ CALayer การตัดสินใจใช้ draw(_:) ขึ้นอยู่กับความซับซ้อนของกราฟิกและข้อกำหนดด้านประสิทธิภาพ

ลายเซ็นเมธอดใน Swift และ Objective-C

ใน Swift เมธอดถูกประกาศเป็น override func draw(_ rect: CGRect) โดยที่ rect คือสี่เหลี่ยมผืนผ้าที่ต้องวาดใหม่ ใน Objective-C ลายเซ็นคือ - (void)drawRect:(CGRect)rect พารามิเตอร์ rect อาจเล็กกว่า bounds ของวิวในระหว่างการวาดใหม่บางส่วนผ่าน setNeedsDisplay(_:)

objective-c
- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
    CGContextFillRect(context, rect);
}

เมื่อไหร่ที่ระบบเรียก draw(_:)

ระบบเรียก draw(_:) โดยอัตโนมัติในสถานการณ์ที่กำหนดไว้อย่างชัดเจน การทำความเข้าใจทริกเกอร์เหล่านี้ช่วยหลีกเลี่ยงการวาดใหม่ที่ไม่จำเป็นและเพิ่มประสิทธิภาพของวิว ด้านล่างคือกรณีทั้งหมดของการเรียกเมธอดโดยอัตโนมัติ

  • การเรนเดอร์ครั้งแรก — เมื่อวิวถูกเพิ่มเข้าไปในลำดับชั้นครั้งแรกและมองเห็นได้บนหน้าจอ
  • setNeedsDisplay() — หลังจากเรียกเมธอดนี้ ระบบจะวาดใหม่ในรอบการวาดถัดไป
  • setNeedsDisplay(_:) — เหมือนข้างต้น แต่ระบุสี่เหลี่ยมผืนผ้าเฉพาะสำหรับการวาดใหม่
  • contentMode — เมื่อ bounds เปลี่ยนแปลง หาก contentMode ต้องการการวาดใหม่ (เช่น .redraw)
  • setNeedsLayout() — ในบางกรณี หลังจากการจัดเรียงวิวย่อยใหม่ อาจจำเป็นต้องวาดใหม่

เอกสารของ Apple เตือน: อย่าเรียก draw(_:) โดยตรง ระบบเป็นผู้ตัดสินใจเองว่าเมื่อใดควรวาด และการเรียกโดยตรงจะทำลายกลไกการแคชภายใน หากต้องการขอให้วาดใหม่ ให้ใช้ setNeedsDisplay() หรือ setNeedsDisplay(_:) เสมอ

วิธีโอเวอร์ไรด์ draw(_:) ใน Swift

การโอเวอร์ไรด์ draw(_:) ใน Swift เริ่มต้นด้วยการรับบริบทกราฟิกและการเรียก Core Graphics ตามมา แนะนำให้สร้างเมธอดแยกต่างหากสำหรับบล็อกการวาดเชิงตรรกะ ซึ่งช่วยปรับปรุงความสามารถในการอ่านและการทดสอบโค้ด

swift
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(_:) ซ้ำหากเนื้อหาของวิวไม่เปลี่ยนแปลง ห้ามใช้ ค่าสุ่ม เวลาระบบ หรือคำขอเครือข่ายภายใน draw(_:)

drawRect: ใน Objective-C และความแตกต่างจาก 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
การเรียก supersuper.draw(rect)[super drawRect:rect]
บริบทUIGraphicsGetCurrentContext()UIGraphicsGetCurrentContext()
พารามิเตอร์ rectrect: CGRectCGRect rect
ประสิทธิภาพเหมือนกันเหมือนกัน

เมื่อโยกย้ายโปรเจกต์จาก Objective-C ไปยัง Swift การเปลี่ยนชื่อเมธอดเป็นหนึ่งในงานแรก Xcode มีตัวแปลงอัตโนมัติ แต่ drawRect: ต้องอัปเดตเป็น draw(_:) ด้วยตนเอง ตาม Apple (2024) เวอร์ชัน Swift draw(_:) เหมาะสำหรับโปรเจกต์ใหม่มากกว่า

การปรับปรุงประสิทธิภาพการวาดใน draw(_:)

draw(_:) ทำงานบน CPU และการใช้งานที่ไม่เหมาะสมอาจทำให้เฟรมดรอปและประสิทธิภาพต่ำ Apple แนะนำวิธีการที่พิสูจน์แล้วหลายวิธีเพื่อเร่งการวาด

ลดจำนวนการดำเนินการวาด

แต่ละการดำเนินการ Core Graphics (move(to:), addLine(to:), strokePath) มีโอเวอร์เฮด จัดกลุ่มการดำเนินการและใช้ CGPath สำหรับรูปทรงที่ซับซ้อน — เส้นทางถูกสร้างครั้งเดียวและนำมาใช้ใหม่ทุกครั้งที่เรียก draw(_:)

ใช้ UIBezierPath สำหรับวัตถุเวกเตอร์

UIBezierPath เป็นตัวหุ้ม Objective-C รอบ CGPath ที่ให้ API ง่ายๆ สำหรับการสร้างรูปทรง สร้าง UIBezierPath ล่วงหน้า (เช่น ในตัวเริ่มต้น) และเรียก fill() หรือ stroke() ภายใน draw(_:)

swift
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(_:)

นักพัฒนามักทำผิดพลาดเหมือนกันเมื่อโอเวอร์ไรด์ draw(_:) การรู้จักรูปแบบเหล่านี้ช่วยหลีกเลี่ยงบั๊กและการลดประสิทธิภาพ มาดูปัญหาที่พบบ่อยที่สุดและวิธีแก้ไขกัน

  • การเรียก draw(_:) โดยตรง — อย่าเรียก draw(_:) โดยตรง ใช้ setNeedsDisplay() เพื่อขอให้วาดใหม่ การเรียกโดยตรงทำลายแคชและอาจทำให้แสดงผลไม่ถูกต้อง
  • การคำนวณหนักภายใน draw(_:) — draw(_:) ควรเบาที่สุดเท่าที่เป็นไปได้ สร้าง UIBezierPath รูปภาพ และวัตถุหนักอื่นๆ ภายนอกเมธอด เริ่มต้นเพียงครั้งเดียว
  • การสร้างวัตถุภายใน draw(_:) — การสร้าง UIColor, UIFont และ UIGraphicsImageRenderer ภายใน draw(_:) สร้างภาระหน่วยความจำที่ไม่จำเป็น ย้ายการสร้างวัตถุไปยังคุณสมบัติของคลาส
  • การละเว้นพารามิเตอร์ rect — rect ระบุพื้นที่ที่ต้องการวาดใหม่ การวาดนอก rect ถูกทิ้งโดยระบบแต่ใช้ทรัพยากร ตรวจสอบการตัดกันกับ rect ก่อนวาด
  • ขาด super.draw(rect) — แม้ว่าการใช้งาน UIView จะว่างเปล่า Apple แนะนำให้เรียก super.draw(rect) เพื่อความเข้ากันได้กับการเปลี่ยนแปลง UIKit ในอนาคต

การปฏิบัติตามกฎเหล่านี้จะช่วยให้คุณวาดวิวแบบกำหนดเองได้อย่างเสถียรและรวดเร็วในทุกโปรเจกต์ iOS โปรไฟล์ draw(_:) ผ่าน Instruments (Core Animation) เพื่อดูเวลาการทำงานจริงและจุดคอขวด

คำถามที่พบบ่อย

สามารถเรียก draw(_:) โดยตรงได้หรือไม่?

ไม่ได้ การเรียก draw(_:) โดยตรงถูกห้ามโดยเอกสารของ Apple ระบบจัดการรอบการวาดด้วยตัวเอง หากต้องการขอให้วาดใหม่ ให้ใช้ setNeedsDisplay() ซึ่งทำเครื่องหมายวิวว่าต้องการอัปเดตในรอบการเรนเดอร์ถัดไปอย่างถูกต้อง

drawRect: แตกต่างจาก draw(_:) อย่างไร?

ในทางหน้าที่เมธอดเหล่านี้เหมือนกัน drawRect: ใช้ใน Objective-C draw(_:) ใช้ใน Swift ทั้งคู่รับ CGRect ของพื้นที่ที่ต้องวาดใหม่และใช้บริบท Core Graphics เดียวกันผ่าน UIGraphicsGetCurrentContext()

ทำไม draw(_:) ไม่ถูกเรียกสำหรับ UIView ที่ว่างเปล่า?

Apple ปรับปรุงประสิทธิภาพการเรนเดอร์: หาก UIView ไม่มี draw(_:) ที่ถูกโอเวอร์ไรด์ ระบบจะไม่สร้างบริบทบิตแมปให้ ซึ่งประหยัดหน่วยความจำ หากมีการโอเวอร์ไรด์แต่เมธอดไม่ถูกเรียก — ตรวจสอบว่าเฟรมของวิวไม่เป็นศูนย์และวิวมองเห็นได้

ระบบเรียก draw(_:) บ่อยแค่ไหน?

เฉพาะเมื่อจำเป็น: การเรนเดอร์ครั้งแรก หลังจาก setNeedsDisplay() เมื่อ bounds เปลี่ยนด้วย contentMode = .redraw ในสถานะคงที่ draw(_:) จะไม่ถูกเรียกซ้ำ ช่วยประหยัดทรัพยากร CPU และแบตเตอรี่

จำเป็นต้องเรียก super.draw(rect) ใน Swift หรือไม่?

Apple แนะนำให้เรียก super.draw(rect) ที่จุดเริ่มต้นของเมธอดที่ถูกโอเวอร์ไรด์ แม้ว่าการใช้งาน UIView ปัจจุบันจะว่างเปล่า การเรียก super ช่วยให้เข้ากันได้กับ UIKit เวอร์ชันอนาคตและเป็นแนวปฏิบัติที่ดี

สรุป

  • draw(_:) / drawRect — เมธอดการวาดหลักของ UIView ใน Swift และ Objective-C ถูกเรียกโดยอัตโนมัติโดยระบบ
  • CGContext — บริบท Core Graphics ที่พร้อมใช้งานภายใน draw(_:) ผ่าน UIGraphicsGetCurrentContext()
  • setNeedsDisplay() — วิธีที่ถูกต้องในการขอให้วาดใหม่ การเรียก draw(_:) โดยตรงถูกห้าม
  • การมีเอกลักษณ์ — draw(_:) ควรให้ผลลัพธ์เดียวกันกับข้อมูลนำเข้าเดียวกันเพื่อการแคชที่เหมาะสม
  • UIBezierPath — สร้างเส้นทางภายนอก draw(_:) เพื่อลดภาระ CPU ในการเรียกแต่ละครั้ง
  • พารามิเตอร์ rect — มีพื้นที่วาดใหม่ ใช้การตัดกันเพื่อปรับปรุงประสิทธิภาพ — อย่าวาดนอกพื้นที่
  • การโปรไฟล์ — ตรวจสอบประสิทธิภาพของ draw(_:) ผ่าน Instruments Core Animation เพื่อระบุการดำเนินการที่ช้า

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม