التغذية الراجعة الحسية والاهتزازات في SwiftUI: الدليل الكامل لـ iOS 26 (2026)

دليل عملي لمُعدِّل sensoryFeedback في SwiftUI مع iOS 26 وSwift 6.2: أنواع الاهتزازات، الفرق مع Core Haptics، أفضل الممارسات، وأمثلة كود جاهزة للنسخ.

اهتزازات SwiftUI في iOS 26: دليل 2026

آخر تحديث: 1 سبتمبر 2026

مُعدِّل sensoryFeedback في SwiftUI هو الطريقة الرسمية لإضافة الاهتزازات اللمسية (Haptics) في تطبيقات iOS 26: مُعدِّل View واحد يراقب قيمة حالة، وحين تتغيّر يُشغّل النمط الحسي المناسب دون الحاجة لاستدعاء UIImpactFeedbackGenerator يدويًّا. أعمل مصممةَ حركة وواجهات منذ سنوات، وصراحةً، الاهتزاز الجيد هو ما يفصل واجهة تشعر بأنها «رخيصة» عن أخرى تشعر بأنها «مصنوعة بعناية». في مشروعي الأخير أهدرتُ يومًا كاملًا في مطاردة اهتزازة تنطلق مرّتين بعد كل ضغطة قبل أن أدرك أن السبب هو Bool أعيد إسناده لنفس القيمة. هذا الدليل يوفّر عليك ذلك اليوم، ويشرح كل ما تحتاجه لإتقان هذا التفصيل الصغير في iOS 26 وSwift 6.2.

  • مُعدِّل .sensoryFeedback(_:trigger:) متاح منذ iOS 17 ويبقى الطريقة المُوصى بها في iOS 26 لجميع حالات الاستخدام الشائعة.
  • يوجد أكثر من عشرة أنماط جاهزة تشمل .success و.selection و.impact و.increase و.decrease و.levelChange.
  • يعتمد المُعدِّل على قيمة Equatable كمشغّل: كلما تغيّرت القيمة، تشتغل الاهتزازة تلقائيًّا.
  • للأنماط المخصّصة أو المتزامنة مع الصوت (لعبة، أدوات إبداعية) استخدم Core Haptics وملفات .ahap.
  • احترم إعداد «System Haptics» ومفاتيح إمكانية الوصول: يجب ألّا تُفرَض الاهتزازات على المستخدم.
  • الحدّ الأقصى للتردد الموصى به من Apple هو اهتزازة واحدة كل 100 مللي ثانية تقريبًا لتجنّب «التخدير الحسي».

ما هي التغذية الراجعة الحسية في SwiftUI؟

التغذية الراجعة الحسية (Sensory Feedback) هي إشارة لمسية أو صوتية قصيرة تُطلقها منظومة iOS استجابةً لتغيّر في حالة الواجهة. أطلقت Apple مُعدِّل sensoryFeedback في iOS 17 وواصلت توسيعه في iOS 26 عبر تحسينات على المحرّك الأساسي لـTaptic Engine الجديد في أجهزة iPhone 17 Pro، بحيث أصبحت زمن الاستجابة أقل من 8 مللي ثانية بين حدث اللمسة والاهتزاز الفعلي.

الفرق الجوهري بين هذا المُعدِّل وطرق UIKit القديمة هو أنه تصريحي: أنت لا «تُشغّل» الاهتزاز عند حدث، بل تربطه بقيمة حالة، وSwiftUI يتولى بقية القصة. هذا يتماشى مع نموذج البيانات أحادي الاتجاه الذي بُنيت عليه بقية الأطر الحديثة مثل ماكرو @Observable. النتيجة: كود أقل، سلوك أكثر قابلية للاختبار، وضمان أن الاهتزازة لن تُطلق مرتين بالخطأ عند إعادة رسم الواجهة.

على مستوى الأجهزة، تدعم كل أجهزة iPhone منذ iPhone 8 محرّك Taptic Engine كامل الميزات. أما iPad، فحتى iPadOS 26 لا تدعم أجهزة iPad الاهتزازات اللمسية أساسًا (باستثناء iPad Pro مع Magic Keyboard الذي يُصدر نقرات لوحة اللمس)، وعلى watchOS يعمل المُعدِّل عبر محرّك Taptic Engine الخاص بالساعة ويُعطي تجربة ممتازة خصوصًا في تطبيقات اللياقة.

كيف تضيف اهتزازًا لمسيًّا في SwiftUI خطوة بخطوة

لإضافة اهتزاز إلى أي View، تحتاج إلى شيئين فقط: نوع الاهتزاز وقيمة مشغّل تتطابق مع بروتوكول Equatable. عندما تتغيّر قيمة المشغّل (سواء كانت Bool أو Int أو حتى مصفوفة)، يُشغّل SwiftUI الاهتزاز تلقائيًّا. المثال التالي هو أبسط تطبيق عملي:

import SwiftUI

struct TaskCompletionView: View {
    // The trigger state; its change is what fires the haptic.
    @State private var isTaskComplete = false

    var body: some View {
        VStack(spacing: 24) {
            Image(systemName: isTaskComplete ? "checkmark.circle.fill" : "circle")
                .font(.system(size: 64))
                .foregroundStyle(isTaskComplete ? .green : .secondary)
                // A gentle spring settles the icon after the haptic.
                .animation(.spring(response: 0.4, dampingFraction: 0.7),
                           value: isTaskComplete)

            Button("Mark as complete") {
                isTaskComplete.toggle()
            }
            .buttonStyle(.borderedProminent)
        }
        // The haptic fires every time isTaskComplete changes.
        .sensoryFeedback(.success, trigger: isTaskComplete)
    }
}

لاحظ كيف اخترتُ spring(response: 0.4, dampingFraction: 0.7) للحركة: قيمة response الأقل من 0.5 تجعل الحركة تشعر بأنها «متزامنة» مع الاهتزاز، أما دامبنغ 0.7 فيمنع الرجرجة الزائدة التي تعطي انطباعًا صبيانيًّا. في تجربتي، هذه القيم تُعطي إحساسًا احترافيًّا في 90٪ من الحالات الشائعة.

إذا كنت تنتقل من مشروع قديم، فقد كنت تكتب شيئًا مثل UIImpactFeedbackGenerator(style: .medium).impactOccurred() داخل معالج الضغط. هذه الطريقة لا تزال تعمل، لكنها تُجبرك على إدارة دورة حياة المُولّد (prepare/impact) بنفسك وتُصعّب اختبار الوحدة. المُعدِّل الجديد يحلّ هاتين المشكلتين مجانًا.

جميع أنواع SensoryFeedback في iOS 26

يوفّر iOS 26 مجموعة موسّعة من الأنماط المُعرَّفة مسبقًا، وكل نمط يحمل «معنى دلاليًّا» يجب أن تحترمه لتجنّب إرباك المستخدم. اختيار النمط الصحيح لا يقلّ أهمية عن اختيار الأيقونة الصحيحة.

النمط الاستخدام المُوصى به الشدة النسبية
.successإتمام مهمة، حفظ ناجح، دفع مُكتملمتوسط + نبضتان
.warningتحذير قابل للتراجع، تأكيد حذفمتوسط + ثلاث نبضات
.errorفشل عملية، خطأ في التحقققوي + أربع نبضات
.selectionتغيير قيمة في Picker أو Segmentedخفيف جدًّا
.impactضغط زر، ارتطام كائن، سحب مكتملقابل للتخصيص
.increase / .decreaseStepper أو Slider للأرقامخفيف + نغمة اتجاهية
.start / .stopبدء تسجيل، إيقاف مؤقّتمتوسط
.alignmentمحاذاة عنصر أثناء السحبخفيف جدًّا
.levelChangeتغيير مستوى/طبقة (مثل Notes)خفيف مع نبرة مختلفة

القاعدة الذهبية التي أتّبعها: استخدم النمط الأقرب معنويًّا للإجراء، ولا تخترع دلالات جديدة. مثلًا، لا تستخدم .error عند حذف عنصر بنجاح لمجرد أنه «تصرّف قوي»، بل استخدم .impact(weight: .heavy). السبب: مستخدمو iOS ذوو الحساسية العالية للاهتزازات (وهم كُثر) يبنون توقّعًا شرطيًّا للأنماط، فتغيير المعنى يكسر هذا التوقّع ويجعل تطبيقك يبدو «غير مصقول».

التحكّم الدقيق: impact والوزن والمرونة والشدة

النمط .impact هو الأكثر مرونة بين الأنماط الجاهزة، ويأتي بمتغيّرين رئيسيين:

// Variant 1: physical weight + intensity (0.0 – 1.0)
.sensoryFeedback(.impact(weight: .heavy, intensity: 1.0),
                 trigger: buttonTapCount)

// Variant 2: material flexibility + intensity
.sensoryFeedback(.impact(flexibility: .soft, intensity: 0.5),
                 trigger: dragEndedAt)

الفرق بين weight وflexibility ليس تجميليًّا: weight يُحاكي كتلة الجسم المرتطم (light / medium / heavy)، بينما flexibility يُحاكي مادّته (rigid / solid / soft). أستخدم flexibility: .soft عند إفلات عنصر قابل للسحب على منطقة صحيحة، لأن الإحساس يشبه سقوط كيس رمل — مُريح ونهائي. أستخدم weight: .heavy للأزرار الأساسية «Confirm order» لإعطاء ثقل نفسي للقرار.

قيمة intensity بين 0.0 و1.0 تُضبط ليست خطّيًّا في الجهاز: القفزة الحقيقية بين 0.3 و0.7 أوسع بكثير مما توحي به الأرقام. توصيتي الشخصية أن تُقيّد نفسك بثلاث قيم فقط عبر التطبيق: 0.35 (خفيف)، 0.65 (متوسط)، 1.0 (تأكيدي). هذا يمنحك اتّساقًا بلا تكرار.

التشغيل الديناميكي باستخدام المُغلقات (Closures)

الشكل الأقوى للمُعدِّل يقبل مُغلقًا يُعيد نوع الاهتزاز بناءً على المقارنة بين القيمة القديمة والجديدة. هذا يُغني عن الحاجة إلى عدة مُعدِّلات مكدّسة ويُحافظ على منطق التغذية الحسية في مكان واحد. المثال التالي يوضّح Stepper يُشغّل .increase أو .decrease حسب الاتجاه:

struct QuantityStepper: View {
    @State private var quantity = 1

    var body: some View {
        Stepper("Quantity: \(quantity)", value: $quantity, in: 1...99)
            // Old and new values decide which haptic to emit.
            .sensoryFeedback(trigger: quantity) { oldValue, newValue in
                guard oldValue != newValue else { return nil }
                return newValue > oldValue ? .increase : .decrease
            }
    }
}

يمكنك أيضًا إعادة nil لتعطيل الاهتزاز بشكل انتقائي، وهو أفضل بكثير من لفّ المُعدِّل داخل if. الحالة التي أراها كثيرًا في مراجعات كودي: مطوّرون يشغّلون .selection عند كل تغيير في TabView، بما في ذلك التغييرات البرمجية الناتجة عن Deep Link. الحلّ:

.sensoryFeedback(.selection, trigger: selectedTab) { oldValue, newValue in
    // Only vibrate when the user physically changed the tab,
    // not when a deep link programmatically switched it.
    UIApplication.shared.applicationState == .active && oldValue != newValue
}

هذا النوع من التحكم الدقيق هو ما يجعل التطبيق يشعر بأنه «يفكّر بك». وهو مماثل بالمبدأ للتحكم الدقيق في التنقل الذي شرحته سابقًا في دليل NavigationStack مع NavigationPath.

Core Haptics مقابل sensoryFeedback: متى تنتقل؟

مُعدِّل sensoryFeedback يُغطّي 80٪ من احتياجات التطبيقات، لكنه يعجز عن ثلاثة أشياء: (1) تسلسلات معقّدة أطول من 300 مللي ثانية، (2) اهتزازات متزامنة مع الصوت، (3) منحنيات شدّة مستمرّة. لهذه الحالات، انتقل إلى إطار Core Haptics الرسمي من Apple.

import CoreHaptics

final class HeartbeatHaptics {
    private var engine: CHHapticEngine?

    init() {
        // Guard against devices without a Taptic Engine.
        guard CHHapticEngine.capabilitiesForHardware().supportsHaptics
        else { return }
        do {
            engine = try CHHapticEngine()
            try engine?.start()
        } catch {
            print("Haptic engine start failed: \(error)")
        }
    }

    func playHeartbeat() throws {
        // Two sharp taps 120 ms apart, a classic heartbeat pattern.
        let thump1 = CHHapticEvent(
            eventType: .hapticTransient,
            parameters: [
                .init(parameterID: .hapticIntensity, value: 1.0),
                .init(parameterID: .hapticSharpness, value: 0.7)
            ],
            relativeTime: 0
        )
        let thump2 = CHHapticEvent(
            eventType: .hapticTransient,
            parameters: [
                .init(parameterID: .hapticIntensity, value: 0.6),
                .init(parameterID: .hapticSharpness, value: 0.4)
            ],
            relativeTime: 0.12
        )

        let pattern = try CHHapticPattern(events: [thump1, thump2], parameters: [])
        let player = try engine?.makePlayer(with: pattern)
        try player?.start(atTime: CHHapticTimeImmediate)
    }
}

البديل الأنيق: عرّف النمط في ملف .ahap منفصل (JSON بامتداد مختلف) وحمّله من الحزمة عبر engine.playPattern(from: url). هذا يفصل «تصميم الاهتزاز» عن كود Swift ويسمح لمصمّم الحركة في فريقك بتعديل الأنماط بدون لمس المصدر.

أفضل ممارسات Apple HIG للاهتزازات

وثيقة Human Interface Guidelines من Apple حول تشغيل الاهتزازات تُلخّص القواعد التي أتعامل معها كخط أحمر في مراجعات الكود. أهمها في ممارستي اليومية:

  • لا تربط الاهتزازة بحدث بصري وحدها. يجب أن يكون هناك دائمًا مرافق مرئي أو صوتي؛ الاهتزاز يُعزّز الإدراك لا يستبدله.
  • لا تُطلق أكثر من اهتزازة كل 100 مللي ثانية. بعدها يبدأ «التخدير الحسي» ويصبح المستخدم غير قادر على تمييز الأنماط.
  • لا تستخدم الاهتزاز لجذب الانتباه لإشعارات ترويجية. هذا يُعتبر إساءة استخدام وقد يُرفض تطبيقك في المراجعة.
  • اختبر على أجهزة حقيقية. المُحاكي لا يُشغّل الاهتزازات؛ الاختبار البصري لا يُغني عن الشعور الفعلي.
  • وحّد لغتك الحسية. إذا استخدمت .success عند حفظ في شاشة، استخدمها نفسها في كل الشاشات المشابهة.

القاعدة الأخيرة هي الأصعب فرضًا في فرق كبيرة. أحتفظ عادةً بملف Swift مركزي يُصدّر ثوابت مثل Haptics.confirmDestructive وHaptics.itemAddedToCart، وأُلزم الفريق باستخدامها بدلاً من كتابة .sensoryFeedback مباشرة في كل View. هذه الطبقة الرقيقة توفّر ساعات في التوحيد لاحقًا.

إمكانية الوصول واحترام إعدادات المستخدم

يمتلك المستخدم ثلاثة مستويات من التحكّم بالاهتزازات على جهازه: مفتاح «Vibration» في «Accessibility → Touch»، ومفتاح «System Haptics» في «Sounds & Haptics»، وإعدادات محدّدة لكل تطبيق تظهر تلقائيًّا في iOS 26 إذا استخدم تطبيقك نمطًا مخصّصًا. أنت لست مطالبًا بإضافة مفتاح تعطيل داخل التطبيق، لكنّي أُوصي بشدّة بذلك للتطبيقات التي تعتمد بكثافة على الاهتزازات (ألعاب، أدوات إبداعية، تطبيقات صحّية).

@AppStorage("hapticsEnabled") private var hapticsEnabled = true

// Wrap your trigger so the user's in-app preference is respected.
private var hapticTrigger: Int {
    hapticsEnabled ? interactionCount : 0
}

// The trigger value stays constant when the user opts out.
.sensoryFeedback(.impact(weight: .medium), trigger: hapticTrigger)

إذا كنت تعرض في واجهتك مفتاح تعطيل، ضعه في نفس المكان الذي يتوقّعه المستخدم: قسم «Sounds & Haptics» داخل شاشة الإعدادات. هذا الاتّساق مع النظام يجعل التطبيق يبدو مدروسًا. لمزيد من الحديث عن تصميم واجهات تُشعر بالتناغم مع النظام، راجع دليل Liquid Glass لتصميم واجهات iOS 26.

ملاحظة تقنية مهمة: إعداد «Reduce Motion» لا يُعطّل الاهتزازات، بل الحركات البصرية فقط. لكن أنصح شخصيًّا بتخفيف الاهتزازات عند تفعيله عبر قراءة UIAccessibility.isReduceMotionEnabled، لأن المستخدمين الذين يُفعّلون Reduce Motion غالبًا ما يكونون حسّاسين للمحفّزات الجسدية عمومًا.

لماذا لا تعمل الاهتزازات في تطبيقي؟

خمسة أسباب تغطي أكثر من 95٪ من حالات «الاهتزاز لا يشتغل» التي واجهتها:

  1. تختبر على المُحاكي. Xcode Simulator لا يدعم الاهتزازات إطلاقًا. استخدم جهازًا حقيقيًّا.
  2. الجهاز على وضع الصامت والاهتزاز في الصامت مُعطَّل. في iOS 26 يُميّز النظام بين اهتزاز التنبيهات (الذي يخضع لمفتاح Ring/Silent) واهتزازات النظام. تحقّق من إعداد «Haptics in Silent Mode».
  3. قيمة المشغّل لا تتغيّر فعلاً. إذا كنت تُسنِد true إلى Bool هو true أصلاً، لن يعتبرها SwiftUI تغييرًا. استخدم counter += 1 بدل Bool ثابت.
  4. iPad. كما ذكرت أعلاه، لا يدعم iPad الاهتزازات؛ لن يحدث شيء ولن يظهر خطأ.
  5. خنق حراري أو بطارية منخفضة. عند تفعيل Low Power Mode أو حين ترتفع حرارة الجهاز، يُقلّل iOS شدّة الاهتزازات تلقائيًّا وقد يُلغي بعضها.

للتشخيص السريع، أضف مُعدِّل .onChange(of: trigger) بجوار .sensoryFeedback واطبع القيمة الجديدة؛ إذا لم تُطبع، فالمشغّل هو المشكلة لا الاهتزاز.

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

هل يعمل sensoryFeedback على iPadOS 26؟

المُعدِّل نفسه يُترجم دون خطأ على iPadOS ولن يُعطي تحذيرًا وقت التشغيل، لكن أجهزة iPad لا تحتوي على Taptic Engine لذا لن تشعر بأي اهتزاز فعلي. الاستثناء الوحيد هو نقرات لوحة اللمس في Magic Keyboard المرتبطة بإجراءات النظام لا بواجهة تطبيقك.

ما الفرق بين sensoryFeedback و UIImpactFeedbackGenerator؟

الأول تصريحي ومربوط بحالة SwiftUI، والثاني إمبراطي ويتطلّب إدارة يدوية لدورة الحياة (prepare/impact). في iOS 17 وما فوق يجب تفضيل sensoryFeedback في أي كود SwiftUI جديد؛ UIImpactFeedbackGenerator لا يزال صالحًا لأكواد UIKit القديمة.

هل يمكنني تشغيل صوت مع الاهتزاز في نفس اللحظة؟

نعم، لكن ليس عبر sensoryFeedback. تحتاج إلى Core Haptics مع أحداث CHHapticEvent من نوع audioCustom، ويجب تمرير نفس AVAudioSession إلى مُنشئ محرّك الاهتزازات لضمان التزامن الدقيق دون انزلاق.

كيف أُعطّل الاهتزاز نهائيًّا في تطبيقي؟

احتفظ بمفتاح @AppStorage بولياني وربطه بقيمة مشغّل ثابتة عند التعطيل (مثل صفر دائم). لا تُزل المُعدِّل بشكل مشروط لأن ذلك يُعيد بناء الشجرة ويسبّب تجاوزات غير متوقّعة في حالات الحركة.

هل تستهلك الاهتزازات الكثير من البطارية؟

الاهتزازات القصيرة عبر sensoryFeedback مُهمَلة من ناحية البطارية. لكن Core Haptics مع أنماط طويلة أو منحنيات مستمرّة يمكن أن يستهلك ما يعادل تشغيل فيديو بجودة منخفضة، لذا استخدمها باعتدال وأوقف المحرّك عبر engine.stop() عند مغادرة الشاشة.

Diana Kowalski
عن الكاتب Diana Kowalski

Mobile UX engineer translating design intent into pixel-perfect SwiftUI. Has strong opinions about haptics.