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(_:) از طریق رندرینگ CPU در Bitmap Graphics Context کار میکند که حداکثر انعطافپذیری را فراهم میکند، اما نسبت به CALayer به منابع بیشتری نیاز دارد. تصمیم به استفاده از draw(_:) بر اساس پیچیدگی گرافیک و الزامات عملکرد گرفته میشود.
در Swift متد به صورت override func draw(_ rect: CGRect) اعلام میشود، که در آن rect مستطیلی است که باید دوباره ترسیم شود. در Objective-C امضا - (void)drawRect:(CGRect)rect است. پارامتر rect میتواند در ترسیم مجدد جزئی از طریق setNeedsDisplay(_:) کوچکتر از bounds ویو باشد.
- (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 }
// پارامترهای خط
context.setStrokeColor(UIColor.darkGray.cgColor)
context.setLineWidth(2.0)
// رسم مثلث
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()
// ساخت شکل ستاره
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 از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید