Interface Builder هو محرر واجهات مرئي مدمج في Xcode لتطوير iOS و macOS. يتيح إنشاء واجهات المستخدم عبر السحب والإفلات، وضبط Auto Layout، وربط الكود عبر IBOutlet و IBAction. سنشرح كيف يعمل IB، وما الفرق بين Storyboard و XIB، ولماذا نحتاج @IBDesignable.
أهم النقاط
Interface Builder هو مكون من Xcode مصمم للتصميم المرئي لواجهات المستخدم. بدأ تاريخ IB في عام 1988 في NeXT، قبل وقت طويل من ظهور iOS. طور ستيفان بوب النسخة الأولى لـ NeXTSTEP — نظام التشغيل الذي أصبح أساس macOS و iOS. في عام 1996، استحوذت Apple على NeXT ودمجت Interface Builder في Xcode.
في Xcode الحديث، يدعم Interface Builder ثلاثة تنسيقات ملفات: Storyboard و XIB (Xcode Interface Builder) و ملفات XIB لخلايا الجدول وطرق العرض المخصصة. يخزن كل من هذه التنسيقات وصف XML لتسلسل عناصر واجهة المستخدم وخصائصها وقيودها وارتباطاتها بالكود.
يعمل IB على مستوى UIKit: الأزرار والتسميات وحقول النص والجداول والمجموعات والقيود تُسحب بالماوس إلى اللوحة. يقوم Xcode بتجميع ملفات .storyboard و .xib إلى أرشيفات nib (Interface Builder مُجمَّع) في وقت البناء، مما يقلل حجم الحزمة ويسرع التحميل.
وفقًا لـ Apple، أكثر من 70% من مشاريع iOS على UIKit تستخدم Interface Builder في مراحل مختلفة من التطوير. على الرغم من نمو SwiftUI، يظل IB هو المعيار للتطبيقات التجارية التي تدعم iOS 12 وما دونه، وكذلك للواجهات المخصصة المعقدة التي تتطلب ضبطًا دقيقًا لـ Auto Layout.
قبل Xcode 4، كان Interface Builder تطبيقًا منفصلاً يُشغل بالتوازي مع محرر الكود. في Xcode 4 (2011)، دمجت Apple IB ومحرر الكود في IDE واحد. هذا سمح بالتبديل بين الكود والتخطيط دون تبديل النوافذ، ورؤية تغييرات الخصائص في الوقت الفعلي عبر لوحة Attributes Inspector.
| إصدار Xcode | السنة | التغييرات في Interface Builder |
|---|---|---|
| Xcode 3 | 2008 | IB — تطبيق منفصل، دعم iOS 2.0 |
| Xcode 4 | 2011 | IB مدمج في IDE، ظهور Storyboard |
| Xcode 5 | 2013 | Auto Layout مع قائمة القيود، معاينة الشاشات |
| Xcode 6 | 2014 | Size Classes، @IBDesignable، Preview Assistant |
| Xcode 11 | 2019 | SwiftUI Canvas، IB يبقى لـ UIKit |
| Xcode 15 | 2023 | SwiftUI Preview كأداة رئيسية، وضع التوافق لـ IB |
مع ظهور SwiftUI في 2019، حولت Apple التركيز إلى التطوير التصريحي، ومع ذلك يظل Interface Builder مدمجًا في Xcode لدعم مشاريع UIKit. آلاف التطبيقات الحالية تستمر في استخدام IB، ولم تعلن Apple عن إزالته.
يدعم Interface Builder تنسيقين رئيسيين: Storyboard (.storyboard) و XIB (.xib). الفرق بينهما يكمن في النطاق وحالة الاستخدام.
Storyboard هو ملف يحتوي على مشهد التطبيق بالكامل: شاشات متعددة (UIViewController)، انتقالات بينها (segues)، وحدات تحكم التنقل، أشرطة التبويب، وجميع عناصر واجهة المستخدم. يتم تحميل Storyboard مرة واحدة عند بدء التشغيل من Info.plist عبر المفتاح UIMainStoryboardFile (k). هذا مناسب لتصور تدفق الشاشات، لكنه يخلق مشاكل مع تضاربات الدمج في git، لأن وصف XML للتطبيق بالكامل مخزن في ملف واحد.
XIB (اختصار لـ Xcode Interface Builder) هو ملف لمكون واحد: UIView فردية، UITableViewCell، UICollectionViewCell، أو ViewController واحد. يتم تحميل XIB عند الطلب عبر UINib(nibName:bundle:) (k) أو طريقة Bundle.loadNibNamed (k). ملفات XIB أسهل في الدمج، وأكثر إحكامًا، وتُحمل أسرع لأنها لا تحتوي على وصف التطبيق بالكامل.
| المعيار | Storyboard | XIB |
|---|---|---|
| النطاق | شاشات متعددة + انتقالات | شاشة أو مكون واحد |
| Segues | يدعم (push, modal, unwind) | لا يدعم |
| الدمج في git | صعب (ملف XML واحد كبير) | سهل (ملفات صغيرة متعددة) |
| التحميل | عند بدء التطبيق | عند الطلب (كسول) |
| إعادة الاستخدام | فقط عبر مراجع storyboard | عالية (خلايا، رؤوس، طرق عرض) |
| توصية Apple | غير موصى به للمشاريع الكبيرة | موصى به للمكونات |
منذ Xcode 11، توصي Apple باستخدام XIB للمكونات الفردية وتجنب Storyboards الأحادية. للتنقل بين الشاشات، يُفضل التنقل القائم على الكود عبر UIStoryboardSegue (k) يدويًا أو المنسقين.
تخزن ملفات .storyboard و .xib XML بتنسيق Interface Builder Cocoa Touch XIB (dt). مثال على هيكل مبسط:
<!-- XIB file with UIView and UILabel -->
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB"
version="3.0">
<objects>
<view id="abc-123"
userLabel="CustomHeaderView"
contentMode="scaleToFill">
<subviews>
<label id="def-456"
text="Title"
textColor="darkTextColor"
fontDescription="title1"/>
</subviews>
</view>
</objects>
</document>كل عنصر له id (an) فريد، يربط IB من خلاله عقدة XML بالكائن في وقت التشغيل. أثناء التجميع، يحول Xcode XML إلى تنسيق nib ثنائي (.nib)، مما يقلل حجم الملف بحوالي 40%.
Auto Layout هو نظام لتحديد موضع العناصر على الشاشة عبر علاقات رياضية (قيود). يوفر Interface Builder واجهة مرئية لإنشاء وتحرير وتصحيح القيود دون كتابة كود. يصف كل قيد اعتمادًا: view.leading = superview.leading + 16 (k) أو view.width = 2 * otherView.height (k).
في IB، تُنشأ القيود عبر قائمة Pin (تثبيت الهوامش والعرض والارتفاع) وقائمة Align (المحاذاة للوسط والحواف وخط الأساس). تعرض لوحة Size Inspector جميع قيود العنصر المحدد وأولوياتها (required/high/low) وتسمح بتحرير المضاعفات والثوابت.
يدعم IB أيضًا UIStackView — حاوية تدير تلقائيًا تخطيط طرق العرض التابعة. ببساطة ضع العناصر في stack view على اللوحة، وسيولد IB القيود الضرورية تلقائيًا. هذا يسرع التخطيط بشكل كبير مقارنة بالوضع اليدوي للقيود.
Size Classes هي تجريد يجمع الأجهزة حسب عرض وارتفاع الشاشة: Compact و Regular. تسمح التركيبات (wC hR لـ iPhone عمودي، wR hR لـ iPad) بتحديد قيود وتخطيطات عناصر مختلفة لسيناريوهات مختلفة. في Interface Builder، التبديل بين size classes يغير مجموعة القيود النشطة على اللوحة.
| الجهاز | الاتجاه | Width Class | Height Class |
|---|---|---|---|
| iPhone (باستثناء Max/Plus) | عمودي | Compact | Regular |
| iPhone (باستثناء Max/Plus) | أفقي | Compact | Compact |
| iPhone Plus/Max | أفقي | Regular | Compact |
| iPad | أي | Regular | Regular |
| iPad Split View | 1/3 شاشة | Compact | Regular |
مثال على قيد مع تغيير size class:
import UIKit
class AdaptiveViewController: UIViewController {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var leadingConstraint: NSLayoutConstraint!
private func updateConstraints() {
let isRegular = traitCollection.horizontalSizeClass == .regular
leadingConstraint.constant = isRegular ? 40 : 16
titleLabel.font = isRegular
? UIFont.preferredFont(forTextStyle: .largeTitle)
: UIFont.preferredFont(forTextStyle: .title1)
}
override func traitCollectionDidChange(
_ previousTraitCollection: UITraitCollection?
) {
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.horizontalSizeClass != previousTraitCollection?.horizontalSizeClass {
updateConstraints()
}
}
}في الكود أعلاه، traitCollectionDidChange يستجيب لتغيير size class، محدثًا القيد والخط. يتيح Interface Builder تعيين قيم افتراضية لكل size class عبر المفتش، بينما يُستخدم الكود للسيناريوهات الديناميكية التي لا يمكن وصفها بشكل ثابت.
يتم الاتصال بين الواجهة المرئية في Interface Builder وكود Swift/Objective-C عبر آليتين: IBOutlet (Interface Builder Outlet) و IBAction (Interface Builder Action). يتم إنشاء كلاهما عن طريق السحب مع الضغط على Ctrl من لوحة IB إلى ملف وحدة التحكم.
IBOutlet هو تعليق يعلن مرجعًا لعنصر واجهة المستخدم. يقوم Xcode تلقائيًا بربطه بالكائن المقابل في أرشيف nib عند التحميل. إذا انقطع الاتصال (مثل إعادة تسمية عنصر)، يتعطل التطبيق مع خطأ NSUnknownKeyException (k). يتم وضع علامة IBOutlet كـ weak (k)، لأن nib يملك الكائن ووحدة التحكم مجرد مراقب.
IBAction هو طريقة تُستدعى عند حدث عنصر واجهة المستخدم: الضغط على زر، تغيير نص، تبديل مفتاح. يربط IB UIControlEvent (k) بالطريقة عبر addTarget:action:forControlEvents: (k). في الكود، يبدو IBAction كطريقة عادية بنوع إرجاع IBAction (dt).
import UIKit
final class LoginViewController: UIViewController {
@IBOutlet weak var emailTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
@IBOutlet weak var loginButton: UIButton!
@IBOutlet weak var spinner: UIActivityIndicatorView!
@IBAction private func loginButtonTapped(_ sender: UIButton) {
guard let email = emailTextField.text, !email.isEmpty,
let password = passwordTextField.text, !password.isEmpty
else {
showAlert(message: "Fill in all fields")
return
}
loginButton.isEnabled = false
spinner.startAnimating()
performLogin(email: email, password: password)
}
private func performLogin(email: String, password: String) {
/// API call via URLSession
let request = LoginRequest(email: email, password: password)
APIClient.shared.login(request) { [weak self] result in
DispatchQueue.main.async {
guard let self else { return }
self.spinner.stopAnimating()
self.loginButton.isEnabled = true
switch result {
case .success:
self.navigateToMainScreen()
case .failure(let error):
self.showAlert(message: error.localizedDescription)
}
}
}
}
private func showAlert(message: String) {
let alert = UIAlertController(
title: "Error",
message: message,
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
}
}يظهر المثال إعدادًا قياسيًا: IBOutlet لحقول النص والزر والدوار، IBAction لمعالجة الضغط. كل هذه الاتصالات تُعد في Interface Builder عبر Ctrl+سحب. إذا لم يتم تكوين الاتصال، سيكون IBOutlet nil (v) في وقت التشغيل، مما يسبب تعطلًا عند الوصول — لذلك يُعلن IBOutlet كـ weak var (k s) مع فتح ضمني.
@IBDesignable هو تعليق Swift يسمح بعرض UIView مخصصة مباشرة على لوحة Interface Builder في الوقت الفعلي. يرى المطور تغييرات الكود دون تشغيل التطبيق. @IBInspectable هو تعليق للخصائص يضيفها إلى لوحة Attributes Inspector في IB، حيث يمكن تغيير القيم بشكل تفاعلي.
هذه التعليقات مفيدة بشكل خاص عند إنشاء مكتبات مكونات واجهة المستخدم: أزرار مخصصة، حقول إدخال مقنعة، مؤشرات متحركة. يستخدم IBDesignable prepareForInterfaceBuilder() (fn) لتجميع منفصل لكود البناء، دون التأثير على الملف الثنائي الرئيسي للتطبيق.
import UIKit
@IBDesignable
final class GradientButton: UIButton {
@IBInspectable var startColor: UIColor = .systemBlue {
didSet { updateGradient() }
}
@IBInspectable var endColor: UIColor = .systemPurple {
didSet { updateGradient() }
}
@IBInspectable var cornerRadius: CGFloat = 12 {
didSet {
layer.cornerRadius = cornerRadius
layer.masksToBounds = true
}
}
private let gradientLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
setupGradient()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupGradient()
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
private func setupGradient() {
layer.insertSublayer(gradientLayer, at: 0)
updateGradient()
}
private func updateGradient() {
gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setupGradient()
}
}في الكود أعلاه، GradientButton هو مكون IBDesignable مع خصائص IBInspectable startColor (v) و endColor (v) و cornerRadius (v). عند سحب UIView إلى لوحة IB وتغيير الفئة إلى GradientButton في Identity Inspector، سيظهر زر متدرج على اللوحة في الوقت الفعلي. ستظهر جميع خصائص IBInspectable في لوحة Attributes Inspector على اليمين.
هام: @IBDesignable يجمع الكود بالكامل للعرض في IB، لذلك لا ينبغي تنفيذ طلبات الشبكة أو العمليات الطويلة داخله. للفصل، يُستخدم #if TARGET_INTERFACE_BUILDER (k) — تجميع شرطي يستبعد الكود غير المخصص لـ IB.
عملية تحويل ملفات Interface Builder من إنشاء nib إلى العرض على الشاشة تشمل عدة مراحل. فهم هذه الدورة يساعد في تشخيص المشكلات المتعلقة بـ IB.
في مرحلة البناء، يشغل Xcode أداة ibtool (k، fn) — أداة سطر أوامر لتجميع ملفات .storyboard و .xib إلى تنسيق nib ثنائي. يقوم ibtool أيضًا بالتحقق من الصحة: التأكد من صحة القيود، وجود جميع الفئات، أنواع اتصالات IBOutlet/IBAction. تظهر أخطاء التحقق في Issue Navigator في Xcode.
يتم وضع أرشيف .nib النهائي في حزمة التطبيق في مجلد .nib (s). حجم ملف nib أصغر بكثير من XML الأصلي: يستخدم التنسيق الثنائي تمثيلًا محسنًا مع استبدال السلاسل برموز وضغط القيم الرقمية. الضغط النموذجي هو 50–60% من حجم XML الأصلي.
في وقت التشغيل، يُحمل nib عبر UINib(nibName:bundle:) (k) أو تلقائيًا عبر UIStoryboard.instantiateViewController(withIdentifier:) (k). تتضمن عملية التحميل:
awakeFromNib() (fn) لكل كائن — نقطة دخول للتكوين بعد التحميليُستدعى الأسلوب awakeFromNib() (fn) بعد تعيين جميع IBOutlets ولكن قبل أول layoutSubviews. هذا مناسب للتكوين الأولي: تدوير الزوايا، إضافة الظلال، ترجمة النصوص. ومع ذلك، جميع IBOutlets مضمونة ألا تكون nil في awakeFromNib.
مع إصدار SwiftUI في 2019، حصل مطورو iOS على بديل لـ Interface Builder — إطار تصريحي مع Canvas Preview في الوقت الفعلي. دعنا نستعرض الاختلافات الرئيسية بين النهجين.
Interface Builder يولد وصف XML يُجمّع إلى nib. يتم إنشاء الواجهة بصريًا؛ الكود يتعامل مع المنطق فقط. يتطلب IB عتبة دخول أقل للمصممين دون مهارات برمجة، لكنه صعب لمراجعة الكود (تغييرات XML غير مرئية في الفرق).
SwiftUI Preview هو تطوير قائم على الكود بالكامل. تُوصف الواجهة في Swift، وتتحديث المعاينة عند كل حفظ. لا XML، لا nib، لا خطر انقطاع اتصالات IBOutlet. يعمل SwiftUI Preview أسرع من IB لأنه لا يتطلب تجميع ملف منفصل.
| المعيار | Interface Builder (UIKit) | SwiftUI Preview |
|---|---|---|
| تنسيق الملف | XML (.storyboard / .xib) → nib ثنائي | كود Swift (بدون ملف وسيط) |
| المعاينة | لوحة IB مع تأخير للطرق العرض المعقدة | Canvas Preview في الوقت الفعلي |
| دعم إصدارات iOS | iOS 2.0+ (جميع الإصدارات) | iOS 13+ |
| الدمج في git | إشكالي (ملف XML واحد) | سهل (كود Swift عادي) |
| البيانات الديناميكية | عبر IBOutlet + كود | @State (k)، @Observable (k) |
| طرق العرض المخصصة | @IBDesignable (تجميع) | SwiftUI View مع PreviewProvider |
| الأداء | تحميل nib سريع | تجميع Swift فوري |
عمليًا، يعتمد الاختيار بين IB و SwiftUI Preview على متطلبات المشروع. Interface Builder لا غنى عنه لتطبيقات UIKit التي تدعم إصدارات iOS القديمة، وكذلك للمشاريع التجارية حيث يعمل المصممون في Xcode دون مهارات Swift. يُفضل SwiftUI للمشاريع الجديدة الموجهة لـ iOS 17+، حيث سرعة التطوير والتفاعل مهمة.
لا تخطط Apple لإزالة Interface Builder من Xcode. علاوة على ذلك، في Xcode 16، حسنت الشركة أداء لوحة IB وأضافت دعمًا لمكونات SwiftUI عبر UIViewRepresentable Bridge. من المتوقع أن يستمر دعم IB حتى 2030 على الأقل.
شكلت سنوات من الخبرة في تطوير iOS مجموعة من التوصيات التي تقلل عدد المشكلات عند استخدام Interface Builder في المشاريع التجارية.
استخدم XIB بدلاً من Storyboard للمكونات القابلة لإعادة الاستخدام. يجب أن تكون كل خلية جدول مخصصة ورأس وتذييل في XIB منفصل. هذا يسهل الدمج، ويسرع التحميل، ويسمح بإعادة استخدام المكونات بين المشاريع عبر Swift Package Manager أو CocoaPods.
قم بتكوين Storyboard References لتقسيم storyboards الكبيرة إلى وحدات. بدلاً من Main.storyboard واحد بـ 100 شاشة، أنشئ storyboard لكل وحدة (Auth, Profile, Feed) واربطها عبر Storyboard Reference. هذا سيقلل وقت تجميع ibtool ويبسط العمل الجماعي.
تجنب اتصالات IBOutlet إلى File's Owner (k) دون تحقق. يجب أن يكون كل اتصال weak (k) واختياريًا (الاختياري المفتوح ضمنيًا رائع فقط في playgrounds). عند إعادة تسمية IBOutlet في طريقة عرض، يقوم Xcode تلقائيًا بتحديث الاتصال، لكن التحرير اليدوي لـ XML يمكن أن يُدخل أخطاء بسهولة.
Show Connection Panel (k) بعد تحرير ملف IB — المؤشرات الحمراء تشير إلى اتصالات مقطوعةUser Defined Runtime Attributes (k) لتعيين الخصائص دون كود: layer.cornerRadius، layer.borderWidth، tintColorIdentifier (k) لكل قيد في Size Inspector — هذا يساعد في تصحيح النزاعاتimport UIKit
final class ProfileHeaderView: UIView {
@IBOutlet weak var avatarImageView: UIImageView!
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var bioLabel: UILabel!
@IBOutlet weak var editButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
avatarImageView.layer.cornerRadius = avatarImageView.bounds.width / 2
avatarImageView.layer.masksToBounds = true
nameLabel.font = UIFont.preferredFont(forTextStyle: .headline)
bioLabel.font = UIFont.preferredFont(forTextStyle: .subheadline)
}
func configure(with profile: UserProfile) {
nameLabel.text = profile.fullName
bioLabel.text = profile.bio
/// Loading avatar via SDWebImage or Kingfisher
}
static func instantiateFromNib() -> ProfileHeaderView {
let nib = UINib(nibName: String(describing: self), bundle: nil)
return nib.instantiate(withOwner: nil).first as! ProfileHeaderView
}
}يظهر المثال أفضل ممارسة لطرق عرض XIB: طريقة ثابتة instantiateFromNib (fn) تحمّل طريقة العرض من XIB بنفس اسم الفئة. طريقة awakeFromNib (fn) تهيئ واجهة المستخدم (زوايا مدورة، خطوط)، وطريقة configure(with:) (fn) تستقبل نموذج بيانات للتعبئة. فصل المسؤوليات يبسط الاختبار وإعادة الاستخدام.
الأسئلة الشائعة
Interface Builder هو محرر مرئي لـ UIKit بتنسيق Storyboard/XIB، يعمل عبر السحب والإفلات. SwiftUI Preview هو معاينة تصريحية في الوقت الفعلي حيث تُوصف الواجهة بكود Swift. كلتا الأداتين مدمجتان في Xcode، لكن IB يولد XML بينما يجمّع SwiftUI Swift مباشرة. IB يدعم iOS 2.0+، SwiftUI يدعم iOS 13+.
لا، Interface Builder غير متوافق مباشرة مع SwiftUI. يستخدم SwiftUI بناء الجملة التصريحي الخاص به و Canvas Preview. ومع ذلك، يمكن دمج مشاريع UIKit المنشأة عبر IB في SwiftUI من خلال UIViewRepresentable، ودمج طرق عرض SwiftUI في UIKit عبر UIHostingController. هذا يسمح بالترحيل التدريجي من IB إلى SwiftUI.
@IBDesignable هو تعليق Swift يعرض UIView مخصصة مباشرة في Interface Builder في الوقت الفعلي دون تشغيل التطبيق. @IBInspectable هو تعليق للخصائص يضيفها إلى لوحة Attributes Inspector في IB. كلا التعليقين يسرعان تطوير مكونات واجهة المستخدم المخصصة: ببساطة غيّر خاصية في المفتش ويظهر التغيير فورًا على اللوحة.
Auto Layout في Interface Builder يحدد القيود عبر قائمة Pin (الهوامش، العرض، الارتفاع) وقائمة Align (المحاذاة للوسط، خط الأساس). كل قيد هو علاقة رياضية بين طرق العرض. يعرض IB الأخطاء بخطوط حمراء والنزاعات بتحذيرات صفراء. Size Classes في IB تسمح بتعيين قيود مختلفة لأجهزة واتجاهات مختلفة دون كتابة كود.
IBOutlet هو تعليق لمرجع إلى عنصر واجهة المستخدم من الكود (مثل @IBOutlet weak var label: UILabel!). IBAction هو تعليق لطريقة تُستدعى عند حدث (مثل @IBAction func buttonTapped(_ sender: UIButton)). يتم إنشاء الاتصال عبر Ctrl+سحب من لوحة IB إلى ملف وحدة التحكم. يولد Xcode تلقائيًا كود الاتصال عند تحرير الماوس.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.