SwiftUI — المفاهيم الأساسية: View، State و Data Flow

المؤلف: IT Sectr نُشر: 2026-02-21 وقت القراءة: 8 دق

SwiftUI — إطار عمل Apple التصريحي لبناء واجهات المستخدم على جميع منصات النظام البيئي، والذي تم تقديمه في WWDC 2019. على عكس UIKit الحتمي مع viewDidLoad والتحديث اليدوي للشاشة، يصف SwiftUI واجهة المستخدم كمجموعة من الهياكل البسيطة المتوافقة مع بروتوكول View. وفقًا لـ Swift.org (2025)، يُستخدم SwiftUI في 65% من المشاريع الجديدة المنشورة على App Store. يدير الإطار تحديثات الواجهة تلقائيًا عبر آلية State و Data Flow — عندما تتغير البيانات، يتم إعادة رسم View دون استدعاءات يدوية لـ reloadData.

النقاط الرئيسية

  • SwiftUI — إطار عمل Apple التصريحي لواجهة المستخدم (2019)، حيث يتم وصف الواجهة بهياكل متوافقة مع بروتوكول View.
  • View — لبنة البناء الأساسية في SwiftUI؛ تصف كل View جزءها من الشاشة من خلال الخاصية المحسوبة body.
  • @State — غلاف خاصية لتخزين الحالة المحلية، عند تغييرها يتم إعادة رسم View تلقائيًا.
  • @Binding — اتصال ثنائي الاتجاه بين View والبيانات، يسمح لـ View فرعية بتعديل حالة الأصل.
  • @ObservedObject و @StateObject — الاتصال بنماذج البيانات الخارجية عبر فئات متوافقة مع بروتوكول ObservableObject.

ما هو SwiftUI؟

SwiftUI — إطار عمل Apple التصريحي لواجهة المستخدم، المختلف جذريًا عن UIKit. بدلاً من إنشاء وحدات التحكم والعروض وإدارة دورة حياتها يدويًا، يصف المطور الواجهة كتصريحات: ما يجب أن يكون على الشاشة، وليس كيفية بنائه. يعتمد SwiftUI على مبدأ التفاعلية: الواجهة هي دالة للحالة. عندما تتغير الحالة، يعيد SwiftUI حساب body لجميع Views التابعة تلقائيًا ويحدث فقط الأجزاء المتغيرة من الشاشة. SwiftUI متاح على iOS 13+ و iPadOS 13+ و macOS 10.15+ و watchOS 6+ و tvOS 13+ و visionOS 1+. كود SwiftUI متعدد المنصات: ملف واحد يعمل على iPhone و iPad و Mac و Apple Watch مع تعديلات طفيفة حسب المنصة. وفقًا لـ Apple WWDC Session 101 (2024)، يغطي SwiftUI أكثر من 90% من أنماط واجهة المستخدم القياسية في App Store.

SwiftUI مقابل UIKit

UIKit — إطار حتمي (2008): ينشئ المطور UIViewController، ويكون subviews في viewDidLoad، وينفذ delegate/datasource لـ UITableView ويحدث الشاشة عبر reloadData أو setNeedsLayout. يستبدل SwiftUI وحدات التحكم بهياكل View بسيطة، والمندوبين بـ bindings و onChange، و Auto Layout بـ HStack/VStack/ZStack مع معدّلات (padding, frame, offset). يتطلب UIKit إدارة ذاكرة يدوية عبر ARC؛ يستخدم SwiftUI هياكل لا تحتاج إلى حساب المراجع. أداء SwiftUI مشابه لـ UIKit: يستخدم الإطار خوارزمية diffing لمجموعات التغيير الدنيا. في IT Sectr، يُستخدم SwiftUI للمشاريع الجديدة ذات الهدف iOS 17+؛ المشاريع التي تدعم iOS 14–15 تتطلب UIKit بسبب التوافق المحدود لـ SwiftUI.

بناء SwiftUI التصريحي

في SwiftUI، يتم وصف الواجهة عبر ViewBuilder — منشئ نتائج يحول مجموعة من Views إلى tuple أو Group. تنشئ المعدّلات (.padding(), .font(), .foregroundColor()) Views جديدة بإعدادات معدلة بدلاً من تحوير الكائن الأصلي. كل معدّل يُرجع View جديدة، مما يسمح بالتسلسل. يدعم ViewBuilder if/else و switch و ForEach — العرض الشرطي والدوري بدون وحدات تحكم منفصلة. View في SwiftUI هي value type (struct)، مما يضمن سلوكًا متوقعًا ويزيل حالات السباق.

بروتوكول View والخاصية المحسوبة body

View — بروتوكول بمتطلب واحد: الخاصية المحسوبة body من النوع some View. تصف كل هيكل متوافق مع View جزءها من الشاشة في body. النوع some View هو نوع إرجاع مبهم يخفي النوع الملموس لـ View المُرجعة (تجميع VStack و HStack و ZStack و Text و Image وما إلى ذلك). يستنتج مترجم Swift النوع الملموس في وقت الترجمة، مع الحفاظ على أداء الاستدعاءات المباشرة دون مسح النوع.

swift
import SwiftUI

struct GreetingView: View {
    var name: String
    
    var body: some View {
        VStack(spacing: 12) {
            Text("مرحبًا، \(name)!")
                .font(.largeTitle)
                .foregroundColor(.primary)
            
            Text("مرحبًا بك في SwiftUI")
                .font(.body)
                .foregroundColor(.secondary)
        }
        .padding()
        .background(
            RoundedRectangle(cornerRadius: 12)
                .fill(.ultraThinMaterial)
        )
    }
}

تأخذ بنية GreetingView معامل name وتعرض كتلتين نصيتين في كومة عمودية. تهيئ المعدّلات .font و .foregroundColor و .padding و .background المظهر. يستدعي SwiftUI body في كل مرة تتغير فيها معاملات الإدخال (name) — تحدث إعادة الرسم فقط للأجزاء المتغيرة. يستخدم المثال RoundedRectangle مع .ultraThinMaterial — خلفية ضبابية أصلية مدمجة في SwiftUI.

@State: الحالة المحلية في SwiftUI

@State — غلاف خاصية يعلن عن حالة محلية تابعة لـ View واحدة. يدير SwiftUI ذاكرة State تلقائيًا: عندما تتغير القيمة، يُعاد رسم body، ولكن فقط لـ Views التي تستخدم هذا State. State هو مصدر الحقيقة للأنواع البسيطة (String, Int, Bool, enum). لا تستخدم @State لنماذج البيانات المعقدة — استخدم @StateObject و @ObservedObject بدلاً من ذلك. يجب أن يكون State خاصًا ومخزنًا داخل View نفسها، ولا يُمرر بين المكونات.

swift
import SwiftUI

struct CounterView: View {
    @State private var count = 0
    
    var body: some View {
        VStack(spacing: 20) {
            Text("العدد: \(count)")
                .font(.system(size: 48, weight: .bold))
            
            Button(action: { count += 1 }) {
                Label("زيادة", systemImage: "plus.circle")
            }
            .buttonStyle(.borderedProminent)
        }
        .padding()
    }
}

قيمة count الأولية = 0. كل ضغطة زر تزيد count؛ يعيد SwiftUI رسم CounterView بالكامل تلقائيًا (جميع Views). في UIKit، كان سيناريو مشابه سيتطلب IBOutlet و IBAction وتحديثات يدوية لـ label.text. @State يضمن إعادة رسم View فقط عند تغيير State معين — تجد خوارزمية diffing في SwiftUI الحد الأدنى من التغييرات في الشجرة.

@Binding: الاتصال ثنائي الاتجاه بين Views

@Binding — غلاف خاصية ينشئ اتصالًا ثنائي الاتجاه بين View والبيانات التي لا تملكها View. Binding هو مرجع إلى State (أو مصدر حقيقة آخر)، يسمح لـ View فرعية بقراءة وتعديل القيمة المخزنة في الأصل. يُشار إلى Binding بالبادئة $: $count يمرر Binding<Int> إلى View الفرعية. بدون Binding، لا تستطيع View الفرعية تعديل بيانات الأصل — يمكنها فقط قراءتها.

swift
import SwiftUI

struct StepperControl: View {
    @Binding var value: Int
    let range: ClosedRange<Int>
    
    var body: some View {
        HStack {
            Button(action: { if value > range.lowerBound { value -= 1 } }) {
                Image(systemName: "minus.circle")
            }
            Text("\(value)")
                .frame(minWidth: 40)
            Button(action: { if value < range.upperBound { value += 1 } }) {
                Image(systemName: "plus.circle")
            }
        }
    }
}

struct ParentView: View {
    @State private var quantity = 5
    
    var body: some View {
        StepperControl(value: $quantity, range: 1...10)
    }
}

ParentView يمتلك State quantity ويمرر Binding عبر $quantity. يمكن لـ StepperControl تغيير القيمة، وتتزامن quantity تلقائيًا في الأصل. Binding ليس نسخة من البيانات، بل جسر إلى مصدر الحقيقة. استخدم @Binding لعناصر التحكم المخصصة والمحررات والمكونات القابلة لإعادة الاستخدام التي تحتاج إلى تعديل بيانات الأصل.

@ObservedObject و @StateObject: نماذج البيانات الخارجية

@StateObject — غلاف خاصية لإنشاء وامتلاك مثيل لفئة متوافقة مع ObservableObject. تنشئ View الكائن مرة واحدة لكل دورة حياة وتُعاد رسمه عند تغيير خصائصه @Published. @ObservedObject — غلاف مشابه، لكن View لا تملك الكائن — يتم إنشاء الكائن وتخزينه خارج View (يُمرر عبر المُهيئ). توصي Apple بـ @StateObject لمصدر الحقيقة في تسلسل View هرمي و @ObservedObject لحقن التبعيات.

swift
import SwiftUI
import Combine

class UserSettings: ObservableObject {
    @Published var username: String = "Guest"
    @Published var isLoggedIn = false
}

struct ProfileView: View {
    @StateObject private var settings = UserSettings()
    
    var body: some View {
        VStack {
            TextField("Username", text: $settings.username)
                .textFieldStyle(.roundedBorder)
            
            Toggle("Logged In", isOn: $settings.isLoggedIn)
            
            if settings.isLoggedIn {
                Text("مرحبًا، \(settings.username)!")
                    .font(.headline)
            }
        }
        .padding()
    }
}

UserSettings — ObservableObject بخاصيتين @Published. يمتلك ProfileView الكائن عبر @StateObject. التغييرات في username أو isLoggedIn تعيد رسم ProfileView تلقائيًا. يستخدم @Published مُنشر Combine لإعلام SwiftUI بالتغييرات. لتمرير settings إلى Views فرعية، استخدم @ObservedObject:

Data Flow في SwiftUI: الصورة الكاملة

تحدد Apple أربعة مستويات لـ Data Flow في SwiftUI: @State (محلي، value type)، @Binding (ثنائي الاتجاه)، @StateObject/@ObservedObject (reference type مع ObservableObject)، @EnvironmentObject (عام، يتم حقنه عبر البيئة). يسمح EnvironmentObject بتمرير البيانات عبر التسلسل الهرمي View بالكامل دون تمرير صريح في المُهيئ. بالإضافة إلى ذلك، يعمل @AppStorage مع UserDefaults، و @SceneStorage مع حالة المشهد، و @FetchRequest مع Core Data. يحدد اختيار مستوى Data Flow بنية التطبيق: تستخدم الشاشات البسيطة State/Binding، والشاشات المعيارية تستخدم ObservedObject، والتطبيقات واسعة النطاق تستخدم EnvironmentObject + حلول شبيهة بـ Redux (TCA, Composable Architecture).

Property Wrapperالملكيةالنوعمتى تستخدم
@StateمحليValue (struct, enum)حالة بسيطة لـ View واحدة (عداد، مبدل، حقل نص)
@Bindingخارجيمرجع إلى StateView فرعية تعدل بيانات الأصل
@StateObjectملكية ViewReference (class)مصدر الحقيقة لنموذج بيانات معقد
@ObservedObjectحقنReference (class)نموذج تم إنشاؤه خارج View (مُمرر عبر init)
@EnvironmentObjectعامReference (class)بيانات متاحة للتسلسل الهرمي بأكمله (auth, theme)

الأسئلة الشائعة

كيف يختلف @State عن @StateObject؟

@State — للأنواع ذات القيمة (struct, enum, String, Int) والحالة المحلية لـ View واحدة. يدير SwiftUI ذاكرة State تلقائيًا. @StateObject — للأنواع المرجعية (class) المتوافقة مع ObservableObject. يمتلك @StateObject الكائن ويعيد رسم View عند تغيير خصائص @Published. للعدادات البسيطة استخدم @State؛ للنماذج ذات المنطق التجاري استخدم @StateObject.

هل يمكن استخدام SwiftUI مع UIKit؟

نعم، يتكامل SwiftUI مع UIKit عبر UIHostingController (SwiftUI داخل UIKit) و UIViewRepresentable (UIKit داخل SwiftUI). يغلف UIHostingController SwiftUI View في UIViewController. يسمح UIViewRepresentable باستخدام مكونات UIKit (MKMapView, WKWebView) في SwiftUI. هذا هو النهج القياسي لترحيل المشاريع من UIKit إلى SwiftUI.

ما هو ViewBuilder في SwiftUI؟

ViewBuilder — منشئ نتائج (Swift 5.1) يحول مجموعة من Views إلى قيمة واحدة من نوع TupleView أو Group أو ConditionalContent. يسمح ViewBuilder بكتابة if/else و switch حتمية داخل body تصريحي. بدون ViewBuilder كان عليك إرجاع AnyView أو Group لكل كتلة شرطية. ViewBuilder هو السبب في أن body لا يحتاج إلى فواصل بين Views.

هل يعمل SwiftUI على جميع أجهزة Apple؟

نعم، يدعم SwiftUI iOS 13+ و iPadOS 13+ و macOS 10.15+ و watchOS 6+ و tvOS 13+ و visionOS 1+. ومع ذلك، بعض واجهات API متاحة فقط على الإصدارات الأحدث: على سبيل المثال، navigationStack (iOS 16+)، Observable macro (iOS 17+). للتوافق مع الإصدارات السابقة، استخدم #available وتكييفات UIKit.

كيف تصحيح تطبيقات SwiftUI؟

Xcode Debug View Hierarchy يعرض شجرة Views SwiftUI مع المعدّلات والإطارات. أداة SwiftUI Inspector (اللوحة اليمنى في Xcode) تسمح بتعديل المعدّلات في الوقت الفعلي. self._printChanges() في body يسجل أسباب إعادة الرسم. Instruments مع قالب SwiftUI يتتبع أداء View ويحدد عمليات إعادة الرسم المفرطة.

الخلاصة

  • SwiftUI — إطار عمل Apple التصريحي لواجهة المستخدم، حيث يتم وصف الواجهة بهياكل View مع خاصية body محسوبة (2019).
  • View — value type (struct) متوافق مع بروتوكول View؛ body يُرجع some View عبر ViewBuilder.
  • @State — حالة محلية للأنواع ذات القيمة؛ عند التغيير، تُعاد رسم View تلقائيًا.
  • @Binding — اتصال ثنائي الاتجاه عبر البادئة $؛ View فرعية تعدل بيانات الأصل.
  • @StateObject / @ObservedObject — أنواع مرجعية مع ObservableObject وخصائص @Published؛ StateObject يمتلك الكائن، ObservedObject يستقبله من الخارج.
  • @EnvironmentObject — حالة عامة للتسلسل الهرمي View بأكمله؛ يُحقن عبر .environmentObject().
  • Data Flow في SwiftUI — من State (محلي) عبر Binding (ثنائي الاتجاه) إلى ObservedObject (معياري) و EnvironmentObject (عام).

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا