setNeedsDisplay() — این یک روش نمونه UIView است که نما را به عنوان نیازمند ترسیم مجدد علامتگذاری میکند. بر خلاف فراخوانی مستقیم draw(_:)، setNeedsDisplay() فقط پرچم dirty را برای نما تنظیم میکند و کنترل را بلافاصله برمیگرداند. سیستم این درخواست را در نزدیکترین چرخه رندر پردازش میکند که امکان ترکیب چندین فراخوانی در یک عملیات ترسیم را فراهم میکند. طبق Apple Documentation (2025)، ترسیم مجدد با تأخیر از طریق setNeedsDisplay() تنها روش صحیح بهروزرسانی محتوای UIView سفارشی است.
نکات اصلی
setNeedsDisplay() — یک روش UIView است که به سیستم سیگنال میدهد محتوای نما قدیمی شده و نیاز به رندر مجدد دارد. این روش پارامتری نمیپذیرد و void برمیگرداند. پس از فراخوانی، نما به عنوان dirty علامتگذاری میشود و در نزدیکترین چرخه ترسیم، سیستم draw(_:) را برای این نما فراخوانی میکند.
ویژگی کلیدی ناهمزمانی است. چندین فراخوانی متوالی setNeedsDisplay() برای یک نما منجر به فراخوانیهای متعدد draw(_:) نمیشود — سیستم آنها را در یک عملیات ترکیب میکند. این برای عملکرد حیاتی است: اگر ویژگی نما چندین بار در یک فریم تغییر کند، draw(_:) فقط یک بار فراخوانی میشود.
مکانیسم ترسیم مجدد با تأخیر بر اساس چرخه رندر UIKit است. هنگامی که setNeedsDisplay() فراخوانی میشود، سیستم پرچم داخلی layer.needsDisplay را به true تنظیم میکند. در پایان run loop جاری، سیستم از همه لایههای dirty عبور کرده و برای هر کدام draw(_:) فراخوانی میکند.
UIKit run loop شامل فاز update است که در آن نماهای dirty پردازش میشوند. در این فاز، در صورت لزوم layoutSubviews() فراخوانی میشود، سپس draw(_:) برای همه نماهای علامتگذاری شده اجرا میشود. پس از اتمام ترسیم، نتیجه برای ترکیب از طریق Core Animation به GPU منتقل میشود.
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(_:) پارامتر rect از نوع CGRect را میپذیرد که ناحیه ترسیم مجدد را محدود میکند. این امکان ترسیم مجدد فقط بخش تغییر یافته نما را فراهم میکند، نه کل آن. تفاوت عملکرد هنگام کار با نماهای بزرگ که تغییرات ناحیه کوچکی را تحت تأثیر قرار میدهند قابل توجه میشود.
| ویژگی | setNeedsDisplay() | setNeedsDisplay(_:) |
|---|---|---|
| پارامتر | ندارد | CGRect — ناحیه ترسیم مجدد |
| ناحیه ترسیم مجدد | کل bounds نما | فقط مستطیل مشخص شده |
| عملکرد | پایینتر در نماهای بزرگ | بالاتر در تغییرات جزئی |
| پارامتر rect در draw(_:) | کل bounds | مستطیل مشخص شده |
| زمان استفاده | تغییر کل محتوا | تغییرات نقطهای (مکاننما، highlight) |
برای گرافیک پیچیده، توصیه میشود ابتدا کل صحنه را در زمینه offscreen رسم کنید، سپس setNeedsDisplay(_:) را فقط برای ناحیه تغییر یافته فراخوانی کنید. این یک تکنیک استاندارد در برنامههای طراحی و ویرایشگرهای گرافیکی در iOS است.
displayIfNeeded() — یک روش همزمان است که بلافاصله ترسیم مجدد همه نماهای dirty در سلسلهمراتب را بدون منتظر ماندن برای پایان run loop شروع میکند. بر خلاف setNeedsDisplay() که فقط پرچم را تنظیم میکند، displayIfNeeded() اجرای فوری draw(_:) را مجبور میکند.
Apple توصیه میکند از displayIfNeeded() فقط در موارد ضروری استفاده کنید — مثلاً قبل از ایجاد اسکرینشات یا هنگام تولید همزمان تصویر برای UIGraphicsImageRenderer. در سناریوهای معمولی، مدل ناهمزمان از طریق setNeedsDisplay() ترجیح داده میشود زیرا thread اصلی را مسدود نمیکند.
// ناهمزمان — توصیه شده
someView.setNeedsDisplay()
// همزمان — فقط در صورت لزوم
someView.setNeedsDisplay()
someView.displayIfNeeded() // draw(_:) بلافاصله فراخوانی شد
فراخوانی displayIfNeeded() بدون setNeedsDisplay() قبلی اثری نخواهد داشت — این روش فقط نماهایی را پردازش میکند که قبلاً به عنوان dirty علامتگذاری شدهاند. اگر هیچ نمایی نیاز به ترسیم مجدد نداشته باشد، displayIfNeeded() بدون فراخوانی draw(_:) پایان مییابد.
فراخوانیهای مکرر setNeedsDisplay() میتوانند عملکرد را کاهش دهند، به ویژه در هنگام انیمیشن. استراتژیهای اصلی بهینهسازی را بررسی میکنیم.
به جای فراخوانی setNeedsDisplay() در هر تغییر یک ویژگی جداگانه، تغییرات را جمع کنید و پس از اعمال همه بهروزرسانیها یک بار روش را فراخوانی کنید. مثلاً هنگام تغییر رنگ، اندازه و موقعیت عنصر — پس از تنظیم همه ویژگیها یک بار setNeedsDisplay() را فراخوانی کنید.
اگر ویژگی میتواند از طریق CALayer انیمیشن داده شود (backgroundColor, opacity, position, transform)، به جای draw(_:) از Core Animation استفاده کنید. ویژگیهای CALayer بدون فراخوانی draw(_:) روی GPU بهروزرسانی میشوند که 60 FPS بدون بار CPU فراهم میکند.
برای گروهبندی چندین تغییر در یک مرحله رندر از CATransaction استفاده کنید. این امکان ترکیب چندین setNeedsDisplay() را در یک عملیات draw(_:) در پایان تراکنش فراهم میکند.
CATransaction.begin()
CATransaction.setDisableActions(true)
view1.setNeedsDisplay()
view2.setNeedsDisplay()
view3.setNeedsDisplay()
CATransaction.commit()
از displayIfNeeded() سوءاستفاده نکنید — ترسیم مجدد همزمان thread اصلی را مسدود کرده و میتواند باعث افت فریم شود. فقط برای عملیاتی استفاده کنید که نتیجه ترسیم قبل از پایان run loop جاری حیاتی است.
سؤالات متداول
خیر، فراخوانی مستقیم draw(_:) ممنوع است. setNeedsDisplay() به درستی نما را به عنوان dirty علامتگذاری میکند و سیستم draw(_:) را در لحظه مناسب چرخه رندر فراخوانی میکند. فراخوانی مستقیم draw(_:) مکانیسم کش را نادیده میگیرد و میتواند به حالت ناهماهنگ منجر شود.
چندین فراخوانی setNeedsDisplay() برای یک نما توسط سیستم در یک فراخوانی draw(_:) ترکیب میشوند. این به این دلیل رخ میدهد که روش فقط dirty flag را تنظیم میکند که تنها پس از اجرای واقعی draw(_:) در پایان run loop بازنشانی میشود.
setNeedsDisplay() ترسیم مجدد محتوای نما را درخواست میکند (فراخوانی draw(_:)). setNeedsLayout() چیدمان مجدد subviewها را درخواست میکند (فراخوانی layoutSubviews()). layoutSubviews() میتواند منجر به تغییر اندازه شود که به نوبه خود ممکن است باعث ترسیم مجدد شود.
setNeedsDisplay(_:) باید در تغییرات نقطهای استفاده شود: حرکت مکاننما، تغییر رنگ ناحیه انتخاب شده، بهروزرسانی بخشی از نمودار. این کار ناحیه ترسیم مجدد را محدود کرده و عملکرد را در مقایسه با ترسیم مجدد کامل کل نما افزایش میدهد.
به طور پیشفرض، تغییر frame یا bounds setNeedsDisplay() را فراخوانی نمیکند. سیستم فقط نما را جابجا میکند. اگر در هنگام تغییر اندازه نیاز به ترسیم مجدد است، ویژگی contentMode را به .redraw تنظیم کنید — در این صورت UIKit در هر تغییر bounds به طور خودکار setNeedsDisplay() را فراخوانی میکند.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید