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(_:) از طریق رندرینگ CPU در Bitmap Graphics Context کار می‌کند که حداکثر انعطاف‌پذیری را فراهم می‌کند، اما نسبت به CALayer به منابع بیشتری نیاز دارد. تصمیم به استفاده از draw(_:) بر اساس پیچیدگی گرافیک و الزامات عملکرد گرفته می‌شود.

امضای متد در Swift و Objective-C

در Swift متد به صورت override func draw(_ rect: CGRect) اعلام می‌شود، که در آن rect مستطیلی است که باید دوباره ترسیم شود. در Objective-C امضا - (void)drawRect:(CGRect)rect است. پارامتر rect می‌تواند در ترسیم مجدد جزئی از طریق setNeedsDisplay(_:) کوچک‌تر از bounds ویو باشد.

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 }

    // پارامترهای خط
    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(_:) را دوباره فراخوانی نمی‌کند. درون 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()
    // ساخت شکل ستاره
    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 برای سازگاری با تغییرات آینده UIKit فراخوانی super.draw(rect) را توصیه می‌کند.

با پیروی از این قوانین، ترسیم پایدار و سریع ویوهای سفارشی را در هر پروژه 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 — شامل منطقه ترسیم مجدد است؛ برای بهینه‌سازی از intersection استفاده کنید — خارج از مرزهای آن ترسیم نکنید
  • پروفایل‌ینگ — عملکرد draw(_:) را از طریق Instruments Core Animation برای شناسایی عملیات کند بررسی کنید

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید