SwiftUI هو إطار عمل تصريحي من Apple لبناء واجهات المستخدم عبر جميع منصات النظام البيئي. بدلاً من وصف الخطوات بشكل أمري، يصرّح المطور كيف يجب أن تبدو الواجهة، ويتولى SwiftUI عرضها وتحديثها. وفقاً لـ Apple Developer Documentation (2025)، يدعم SwiftUI iOS 15+ و iPadOS 15+ و macOS 12+ و watchOS 8+ و tvOS 15+ ويستخدم View Protocol ككتلة بناء أساسية لجميع مكونات الواجهة.
الخلاصة
body هو أساس أي مكون واجهة في SwiftUI، يعيد وصف الشاشة من خلال تركيب العروض.SwiftUI هو إطار عمل تصريحي قدمته Apple في 2019 ليحل محل UIKit في المشاريع الجديدة. بدلاً من إنشاء مثيلات UIView يدوياً وإضافتها إلى التسلسل الهرمي، يصف المطور الواجهة من خلال هياكل تتوافق مع بروتوكول View. يحسب SwiftUI تلقائياً الفرق بين الحالة الحالية والجديدة ويعيد رسم الأجزاء المتغيرة فقط باستخدام محرك العرض الخاص به.
الإطار مكتوب بلغة Swift باستخدام دلالات القيمة (هياكل وليست فئات)، مما يجعل مكونات الواجهة خفيفة الوزن وآمنة للخيوط. على عكس UIKit، حيث يمكن أن يزن UIViewController 200+ بايت بسبب وقت تشغيل Objective-C، فإن SwiftUI View هي مجرد هيكل بحجم بضعة بايتات. هذا مهم بشكل خاص لـ watchOS بذاكرتها المحدودة.
نفس وصف View يعمل على iPhone و iPad و Mac و Apple Watch و Apple TV و Apple Vision Pro. SwiftUI يكيّف الواجهة مع المنصة: إيماءات اللمس على iOS، اختصارات لوحة المفاتيح على macOS، التمرير بـ Digital Crown على watchOS. هذا يقلل وقت التطوير للشركات التي تطلق تطبيقات على منصات Apple متعددة، لكنه يتطلب تهيئة إضافية للعناصر الخاصة بكل منصة.
في SwiftUI، كل شاشة هي هيكل يطبق بروتوكول View بمتطلب واحد: خاصية محوسبة body من النوع some View. الكلمة المفتاحية some (نوع غير شفاف) تخفي النوع الملموس للعرض، مما يسمح لـ SwiftUI بتحسين العرض. داخل body، يدمج المطور المكونات الجاهزة — Text، Image، Button، List — باستخدام ViewBuilder، الذي يجمع عدة عروض في واحد.
struct GreetingView: View {
let name: String
var var body: some View {
VStack {
Text("مرحبا، \(name)!")
.font(.title)
.foregroundColor(.blue)
Image(systemName: "hand.wave")
.imageScale(.large)
}
.padding()
}
}
في المثال، VStack (المكدس العمودي) يحتوي على Text و Image. قيمة name تُمرر عبر مُهيئ الهيكل — هكذا يعمل DI (حقن التبعيات) في SwiftUI بدون حاويات DI خارجية. كل معدّل يعيد عرضاً جديداً مع التغيير المطبق، دون تحوير الأصل. هذا ممكن بفضل ثبات أنواع القيم.
ViewBuilder هو منشئ نتائج معلّق بـ @resultBuilder يجمع حتى 10 عروض في واحد. داخل body يمكن استخدام if/else و switch و ForEach بدون أغلفة إضافية. ForEach يعمل مع عناصر Identifiable — يُعطى كل عرض معرفاً فريداً للرسوم المتحركة الصحيحة عند الإدراج/الحذف.
في SwiftUI، تحدد الحالة المحتوى المعروض على الشاشة. عندما تتغير الحالة، يعيد SwiftUI إنشاء body للعرض المعتمد ويقارن النتيجة بالسابقة باستخدام خوارزمية الفرق. لتخزين الحالة تُستخدم property wrappers — كل منها يحل مهمته الخاصة: حالة محلية، اتصال مع عرض فرعي، أو نموذج بيانات خارجي.
struct CounterView: View {
@State private var count = 0
var var body: some View {
VStack {
Text("العداد: \(count)")
Button("زيادة") {
count += 1
}
}
}
}
class UserViewModel: ObservableObject {
@Published var name = ""
@Published var age = 0
}
@State يخزّن قيمة محلية بسيطة (Int، String، Bool) داخل هيكل View. ينقل SwiftUI الذاكرة من الهيكل إلى تخزين منفصل — لذلك يمكن تحوير الخاصية مع @State حتى لو كانت View من نوع القيمة. @ObservableObject مخصص للفئات التي تحتوي على خصائص @Published، والتي تُعلم تغييراتها SwiftUI تلقائياً بضرورة إعادة الرسم.
@Binding ينشئ اتصالاً ثنائي الاتجاه مع مصدر البيانات الموجود في العرض الأب. الأب يمرّر $variable (القيمة المسقطة)، والابن يقرأ ويكتب القيمة من خلال binding. وهذا يسمح بنقل إدخال النص أو المفتاح إلى مكون منفصل مع الحفاظ على الحالة في الأب. بدون @Binding، كان كل تغيير يتطلب إغلاق استدعاء لتمرير القيمة الجديدة إلى الأعلى.
قبل iOS 16، كان التنقل في SwiftUI يُبنى على NavigationView — واجهة برمجة قديمة بسلوك معقد على iPad (عرض مقسم، عمود مزدوج). بدءاً من iOS 16، توصي Apple بـ NavigationStack — بديل مبسط بمسارات آمنة النوع. يحدد المطور تعداداً (enum) للمسارات الممكنة، ويدير NavigationStack تلقائياً مكدس الشاشات مع دعم الروابط العميقة والعودة إلى الجذر.
enum Route: Hashable {
case detail(id: Int)
case settings
}
struct ContentView: View {
var var body: some View {
NavigationStack {
List {
NavigationLink("شاشة التفاصيل",
value: Route.detail(id: 42))
NavigationLink("الإعدادات",
value: Route.settings)
}
.navigationDestination(for: Route.self) { route in
switch route {
case .detail(let id): DetailView(id: id)
case .settings: SettingsView()
}
}
}
}
}
المسارات المتوافقة مع Hashable تسمح باستخدام أي نوع بيانات لتمرير المعاملات. navigationDestination(for:destination:) يربط نوع المسار بالعرض الهدف. الميزة على تنقل UIKit هي عدم الحاجة إلى إعادة الرسم عند إضافة مسار جديد: يكفي إضافة case إلى enum ومعالج في switch. الروابط العميقة تُعالج عبر processDeepLink على NavigationStack.
للتنقل البرمجي (بعد تسجيل الدخول، مؤقت، أو استجابة الخادم)، يُستخدم @State مع مُهيئ NavigationLink: NavigationLink(isActive: $isActive). عند تعيين isActive = true، يتم الانتقال دون لمس المستخدم. بديل هو ربط مصفوفة $path في NavigationStack: $path.append(Route.detail(id: 1)).
Modifier هو طريقة تعيد نسخة معدّلة من العرض. على عكس UIKit، حيث يتم تكوين الخصائص من خلال تحوير عرض موجود، ينشئ SwiftUI قيمة جديدة مع التغيير المطبق. سلسلة المعدّلات تبني الواجهة النهائية من تحويلات متتالية: الخط → الحشو → اللون → الظل → الإيماءة.
توفر Apple أكثر من 200 معدّل مدمج. الأكثر شيوعاً: .font()، .foregroundColor()، .padding()، .background()، .cornerRadius()، .shadow()، .opacity()، .offset(). ترتيب المعدّلات مهم: .padding() قبل .background() يملأ المنطقة بالحشو، بعده — المنطقة الداخلية فقط. المعدّلات المخصصة تُنشأ عبر بروتوكول ViewModifier.
يمكن تطبيق المعدّلات شرطياً عبر العامل الثلاثي: .foregroundColor(isError ? .red : .primary). للرسوم المتحركة يُستخدم .animation(.easeInOut, value: state) — يرتبط معدّل الرسوم المتحركة بخاصية حالة محددة. عندما تتغير هذه الخاصية، يقوم SwiftUI بتحريك الانتقال بين القيمة القديمة والجديدة. الرسوم المتحركة تعمل مع opacity، offset، scale، rotation، الحجم واللون — لكل خاصية AnimatableParameter مطابق.
للرسوم المتحركة المخصصة، تتوفر .transition (الظهور/الاختفاء) و .matchedGeometryEffect (انتقال سلس لعنصر بين حاويتين). يُستخدم الأخير للرسوم المتحركة البطولية في القوائم: أيقونة في خلية قائمة تتحول بسلاسة إلى صورة كبيرة على شاشة التفاصيل.
الاختيار بين SwiftUI و UIKit هو أحد المعضلات الأولى لمطور iOS. كلا الإطارين مدعومان من Apple لكنهما يحلان مشكلة بناء الواجهة بطرق مختلفة جوهرياً: SwiftUI تصريحياً، UIKit أمرياً. يظهر الفرق في إدارة الحالة والتنقل والأداء والتوافق.
| الجانب | SwiftUI | UIKit |
|---|---|---|
| النهج | تصريحي: ماذا نعرض | أمري: كيف نبني |
| الحالة | Property Wrappers، إعادة رسم تلقائية | يدوياً: reloadData، setNeedsLayout |
| كود UI | مدمج، سلاسل معدّلات | مطول، NSCoder/Storyboard/قيد |
| الأداء | عالي على iOS 17+، خوارزمية فرق | ذروة على iOS 12–16، تحكم مباشر |
| النسخة الدنيا | iOS 15+ (دعم كامل) | iOS 2+ (جميع النسخ) |
للمشاريع الجديدة ذات النسخة الدنيا iOS 17، توصي Apple بـ SwiftUI كإطار رئيسي. يبقى UIKit ضرورياً للواجهات التي تتطلب تحكماً دقيقاً في العرض (تخطيط UICollectionViewLayout مخصص، مشاهد CAAnimation معقدة) أو دعم iOS 12–14. العديد من المشاريع تستخدم نهجاً هجيناً: SwiftUI عبر UIHostingController يُدمج في تطبيق UIKit، و UIViewRepresentable يسمح باستخدام مكونات UIKit داخل التسلسل الهرمي لـ SwiftUI.
الأسئلة الشائعة
نعم، عبر UIHostingController (SwiftUI في UIKit) و UIViewRepresentable (UIKit في SwiftUI). هذا نهج هجين، شائع أثناء الترحيل.
iOS 17 يوفر وظائف كاملة: NavigationStack، Observation framework، Swift Charts. iOS 15 هو الحد الأدنى للإنتاج.
السبب الأكثر شيوعاً هو تغيير خاصية @Published في خيط خلفي. يجب أن يرسل ObservableObject التغييرات على main actor: @MainActor class ViewModel.
استخدم .debounce عبر Combine: Button.publisher(for: .tap) .debounce(for: .seconds(0.3), scheduler: RunLoop.main).
نعم، عبر معدّلات Gesture: DragGesture، LongPressGesture، MagnificationGesture، RotationGesture. ادمجها باستخدام .simultaneousGesture() و .sequenced().
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.