setNeedsDisplay(): สาระสำคัญของเมธอดและกลไกการวาดใหม่ใน iOS

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

setNeedsDisplay() เป็นเมธอดอินสแตนซ์ของ UIView ที่ทำเครื่องหมายวิวว่าต้องการให้วาดใหม่ แตกต่างจากการเรียก draw(_:) โดยตรง setNeedsDisplay() จะตั้งค่า dirty flag สำหรับวิวและคืนการควบคุมทันที ระบบประมวลผลคำขอนี้ในรอบการเรนเดอร์ที่ใกล้ที่สุด ทำให้สามารถรวมหลายการเรียกเป็นการดำเนินการวาดเดียว ตามเอกสารของ Apple (2025) การวาดใหม่แบบเลื่อนเวลาผ่าน setNeedsDisplay() เป็นวิธีที่ถูกต้องเพียงวิธีเดียวในการอัปเดตเนื้อหาของ UIView ที่กำหนดเอง

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

  • setNeedsDisplay() — คำขอวาดใหม่แบบเลื่อนเวลาสำหรับ UIView ไม่เรียก draw(_:) แบบซิงโครนัส
  • dirty flag — แฟลกภายในของวิวที่ตั้งค่าโดย setNeedsDisplay() และถูกล้างหลังจาก draw(_:)
  • setNeedsDisplay(_:) — โอเวอร์โหลดกับ CGRect สำหรับการวาดใหม่บางส่วนเฉพาะพื้นที่ที่ระบุ
  • displayIfNeeded() — การวาดใหม่แบบซิงโครนัสบังคับ ใช้สำหรับการอัปเดตทันที
  • รอบการเรนเดอร์ — ระบบประมวลผลวิว dirty ที่ส่วนท้ายของ run loop รวมหลายคำขอเป็นรอบเดียว

setNeedsDisplay() คืออะไร

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

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

การวาดใหม่แบบเลื่อนเวลาทำงานอย่างไร

กลไกการวาดใหม่แบบเลื่อนเวลาขึ้นอยู่กับรอบการเรนเดอร์ของ UIKit เมื่อเรียก setNeedsDisplay() ระบบจะตั้งค่าแฟลกภายใน layer.needsDisplay เป็น true เมื่อสิ้นสุด run loop ปัจจุบัน ระบบจะวนซ้ำเลเยอร์ dirty ทั้งหมดและเรียก draw(_:) สำหรับแต่ละเลเยอร์

Run loop และขั้นตอนการเรนเดอร์

run loop ของ UIKit รวมถึงเฟส อัปเดต ในระหว่างที่ประมวลผลวิว dirty ในเฟสนี้ layoutSubviews() จะถูกเรียกหากจำเป็น จากนั้น draw(_:) สำหรับวิวที่ถูกทำเครื่องหมายทั้งหมด หลังจากวาดเสร็จ ผลลัพธ์จะถูกส่งต่อไปยัง GPU ผ่าน Core Animation สำหรับการประกอบ

swift
class ProgressView: UIView {
    var progress: CGFloat = 0 {
        didSet {
            setNeedsDisplay()
        }
    }

    override func draw(_ rect: CGRect) {
        super.draw(rect)
        guard let ctx = UIGraphicsGetCurrentContext() else { return }

        // พื้นหลัง
        ctx.setFillColor(UIColor.lightGray.cgColor)
        ctx.fill(rect)

        // แถบความคืบหน้า
        let fillRect = CGRect(x: rect.minX, y: rect.minY,
                              width: rect.width * progress, height: rect.height)
        ctx.setFillColor(UIColor.systemGreen.cgColor)
        ctx.fill(fillRect)
    }
}

ในตัวอย่างนี้ progressView อัปเดต progress และแต่ละ setter จะเรียก setNeedsDisplay() โดยอัตโนมัติ ระบบรอจนกระทั่งสิ้นสุด run loop และเรียก draw(_:) ด้วยค่า progress จริง หาก progress เปลี่ยนแปลงสามครั้งต่อเฟรม draw(_:) จะทำงานเพียงครั้งเดียว — ด้วยค่าสุดท้าย

setNeedsDisplay() vs setNeedsDisplay(_:)

เมธอด setNeedsDisplay(_:) รับพารามิเตอร์ rect ชนิด CGRect ซึ่งจำกัดพื้นที่วาดใหม่ ทำให้สามารถวาดใหม่เฉพาะส่วนที่เปลี่ยนแปลงของวิว แทนที่จะวาดทั้งวิว ความแตกต่างด้านประสิทธิภาพจะเห็นได้ชัดเมื่อทำงานกับวิวขนาดใหญ่ที่การเปลี่ยนแปลงส่งผลกระทบต่อพื้นที่เล็ก

ลักษณะsetNeedsDisplay()setNeedsDisplay(_:)
พารามิเตอร์ไม่มีCGRect — พื้นที่วาดใหม่
พื้นที่วาดใหม่bounds ทั้งหมดของวิวเฉพาะสี่เหลี่ยมที่ระบุ
ประสิทธิภาพต่ำกว่าสำหรับวิวขนาดใหญ่สูงกว่าสำหรับการเปลี่ยนแปลงบางส่วน
พารามิเตอร์ rect ใน draw(_:)bounds ทั้งหมดสี่เหลี่ยมที่ระบุ
เมื่อใดควรใช้เปลี่ยนเนื้อหาทั้งหมดการเปลี่ยนแปลงเฉพาะจุด (เคอร์เซอร์, ไฮไลต์)

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

displayIfNeeded() กับ setNeedsDisplay()

displayIfNeeded() เป็นเมธอดซิงโครนัสที่เริ่มการวาดใหม่ของวิว dirty ทั้งหมดในลำดับชั้นทันที โดยไม่รอให้สิ้นสุด run loop แตกต่างจาก setNeedsDisplay() ที่ตั้งค่าแฟลกเท่านั้น displayIfNeeded() บังคับให้ดำเนินการ draw(_:) ทันที

Apple แนะนำให้ใช้ displayIfNeeded() เฉพาะในกรณีที่รุนแรงเมื่อต้องการผลลัพธ์การวาดทันที — ตัวอย่างเช่น ก่อนถ่ายภาพหน้าจอ หรือระหว่างการสร้างภาพแบบซิงโครนัสสำหรับ UIGraphicsImageRenderer ในสถานการณ์ปกติ โมเดลอะซิงโครนัสผ่าน setNeedsDisplay() จะดีกว่าเนื่องจากไม่บล็อกเธรดหลัก

swift
    // แอสังโครนัส — แนะนำ
someView.setNeedsDisplay()

    // ซังโครนัส — เมื่อจำเป็นเท่านั้น
someView.setNeedsDisplay()
someView.displayIfNeeded() // draw(_:) ถูกเรียกทันที

การเรียก displayIfNeeded() โดยไม่มี setNeedsDisplay() ก่อนหน้าจะไม่มีผล — เมธอดประมวลผลเฉพาะวิวที่ทำเครื่องหมายเป็น dirty แล้วเท่านั้น หากไม่มีวิวใดต้องการวาดใหม่ displayIfNeeded() จะเสร็จสมบูรณ์โดยไม่เรียก draw(_:)

การเพิ่มประสิทธิภาพการเรียก setNeedsDisplay()

การเรียก setNeedsDisplay() บ่อยครั้งสามารถลดประสิทธิภาพ โดยเฉพาะระหว่างแอนิเมชัน มาดูกลยุทธ์การเพิ่มประสิทธิภาพหลักกัน

การรวมการเปลี่ยนแปลงผ่านโมเดล

แทนที่จะเรียก setNeedsDisplay() ทุกครั้งที่คุณสมบัติแต่ละรายการเปลี่ยนแปลง ให้สะสมการเปลี่ยนแปลงและเรียกเมธอดครั้งเดียวหลังจากใช้การอัปเดตทั้งหมด ตัวอย่างเช่น เมื่อเปลี่ยนสี ขนาด และตำแหน่งขององค์ประกอบ — เรียก setNeedsDisplay() ครั้งเดียวหลังจากตั้งค่าคุณสมบัติทั้งหมด

การใช้ CALayer สำหรับคุณสมบัติที่ทำแอนิเมชันได้

หากคุณสมบัติสามารถทำแอนิเมชันผ่าน CALayer (backgroundColor, opacity, position, transform) ให้ใช้ Core Animation แทน draw(_:) คุณสมบัติของ CALayer จะอัปเดตบน GPU โดยไม่เรียก draw(_:) ให้ 60 FPS โดยไม่มีภาระบน CPU

การอัปเดตแบบกลุ่มผ่าน CATransaction

เพื่อรวมการเปลี่ยนแปลงหลายรายการเป็นรอบการเรนเดอร์เดียว ให้ใช้ CATransaction สิ่งนี้ช่วยให้รวมการเรียก setNeedsDisplay() หลายครั้งเป็นการดำเนินการ draw(_:) เดียวเมื่อสิ้นสุดธุรกรรม

swift
CATransaction.begin()
CATransaction.setDisableActions(true)

view1.setNeedsDisplay()
view2.setNeedsDisplay()
view3.setNeedsDisplay()

CATransaction.commit()

อย่าใช้ displayIfNeeded() มากเกินไป — การวาดใหม่แบบซิงโครนัสบล็อกเธรดหลักและอาจทำให้เฟรมดรอปได้ ใช้เฉพาะสำหรับการดำเนินการที่ผลลัพธ์การวาดมีความสำคัญก่อนที่ run loop ปัจจุบันจะสิ้นสุด

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

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

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

จะเกิดอะไรขึ้นเมื่อเรียก setNeedsDisplay() หลายครั้ง?

การเรียก setNeedsDisplay() หลายครั้งสำหรับวิวเดียวกันจะถูกรวมโดยระบบเป็นการเรียก draw(_:) ครั้งเดียว สิ่งนี้เกิดขึ้นเพราะเมธอดตั้งค่าเฉพาะ dirty flag ซึ่งจะถูกล้างหลังจากการดำเนินการจริงของ draw(_:) เมื่อสิ้นสุด run loop

setNeedsDisplay() ต่างจาก setNeedsLayout() อย่างไร?

setNeedsDisplay() ขอให้วาดเนื้อหาของวิวใหม่ (เรียก draw(_:)) setNeedsLayout() ขอให้จัดเรียงวิวย่อยใหม่ (เรียก layoutSubviews()) layoutSubviews() สามารถนำไปสู่การเปลี่ยนแปลงขนาด ซึ่งอาจทำให้เกิดการวาดใหม่

เมื่อใดควรใช้ setNeedsDisplay(_:)?

setNeedsDisplay(_:) ควรใช้สำหรับการเปลี่ยนแปลงเฉพาะจุด: การเคลื่อนที่ของเคอร์เซอร์ การเปลี่ยนสีของพื้นที่ที่เลือก การอัปเดตส่วนหนึ่งของกราฟ สิ่งนี้จำกัดพื้นที่วาดใหม่และปรับปรุงประสิทธิภาพเมื่อเทียบกับการวาดวิวทั้งใหม่

การเปลี่ยน frame เรียก setNeedsDisplay() โดยอัตโนมัติหรือไม่?

โดยค่าเริ่มต้น การเปลี่ยน frame หรือ bounds ไม่เรียก setNeedsDisplay() ระบบเพียงแค่ย้ายวิว หากต้องการวาดใหม่เมื่อขนาดเปลี่ยน ให้ตั้งค่าคุณสมบัติ contentMode เป็น .redraw — จากนั้น UIKit จะเรียก setNeedsDisplay() โดยอัตโนมัติทุกครั้งที่ bounds เปลี่ยน

สรุป

  • setNeedsDisplay() — เมธอดอะซิงโครนัสของ UIView สำหรับขอการวาดใหม่แบบเลื่อนเวลาโดยการตั้งค่า dirty flag
  • setNeedsDisplay(_:) — โอเวอร์โหลดกับ CGRect สำหรับการวาดใหม่บางส่วนเฉพาะพื้นที่ที่ระบุ เพิ่มประสิทธิภาพ
  • displayIfNeeded() — เมธอดซิงโครนัสสำหรับการวาดวิว dirty ทันที ใช้เมื่อจำเป็นจริง ๆ เท่านั้น
  • Run loop — ระบบประมวลผลวิว dirty เมื่อสิ้นสุดรอบปัจจุบัน รวมหลายการเรียกเป็นรอบ draw(_:) เดียว
  • CALayer — สำหรับคุณสมบัติที่ทำแอนิเมชันได้ (position, opacity) ให้ใช้ Core Animation แทน draw(_:) — มีประสิทธิภาพมากกว่า
  • CATransaction — รวมการเปลี่ยนแปลงหลายรายการเป็นรอบการเรนเดอร์เดียว ลดจำนวนการเรียก draw(_:)
  • contentMode = .redraw — เปิดใช้งานการเรียก setNeedsDisplay() อัตโนมัติเมื่อ bounds ของวิวเปลี่ยน

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

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

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

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