1. نعود الى اكمال السلسلة بعد انقطاع لفتره طويلة
     
    في هذا الجزء سوف نكمل ما تحدثنا عنه في الجزء السابق وهو الـLayer Animation
    او ان صح التعبير نستكمل شروحات الـ Core Animation
     
    وتحديداً الـ CABasicAnimation
     
    الموضوع الاول كيفية تأخير الـ Animation
     
    عند شرحنا للـ UIView Animation
    ذكرنا بأنه هناك Function
    يحتوي على Parameter
    الـ Delay
    عند إعطائه قيمة (تحسب بالثانية الواحده)
    يقوم على تأخير بدأ الـ Animation
    بعدد الثواني المطلوبة
     
    مثال للكود المستخدم في UIView.animate
     
    UIView.animate(withDuration: 1.0, delay: 0.0, options:[], animations: { }, completion: nil)  
    الطريقة واضحه في الـ UIView Animation
     
    لكن في الـ Core Animation
    الوضع مختلف
     
    لا يوجد ملكية من نوع Delay
    ولكن يتم تنفيذ التأخير بطريقة مختلفة
     
    وهيا باختصار حساب
    إعطاء ملكية beginTime قيمة الوقت الحقيقي + التاخير بالثواني
    لكي يكون الامر واضح
    سوف نستخدم المربع الأزرق
     
    قم بإضافة UIView
    بحجم ١٢٢ في ١٢٢
    لجعله بحجم مربع
     
    ومن ثم اضيف زر أيضا
     
    كما في الصورة التالية :

    ومن ثم اربط الـ UIView وأيضا الزر بملف الاكواد
     
    قمت بإعطاء اسم squareView الى الـ UIView
    وقمت بسمية اسم الـ Function الخاص بالزر باسم AnimationButton()
     
    كما فعلت في الدرس السابق
     
    الان سوف أقوم بكتابة الاكواد التالية بداخل الـ AnimationButton()
     
    let cornerRadiusAnimation = CABasicAnimation(keyPath: "cornerRadius")   cornerRadiusAnimation.duration = 0.5 cornerRadiusAnimation.fromValue = 0 cornerRadiusAnimation.toValue = 61 cornerRadiusAnimation.beginTime = CACurrentMediaTime() + 0.5 squareView.layer.add(cornerRadiusAnimation, forKey: nil)    
    قم بتشغيل الكود وشاهد النتيجة :

     
    الكود السابق وظيفته هو تحول من مربع الى دائرة
     
    ويبدا الـ Animation بعد 0.5 ثانية ولمدة 0.5 ثانية
     
    فكما تلاحظ هو التالي
    لعمل تأخير تحتار الى كتابة الكود
    .beginTime = CACurrentMediaTime() + مدة التأخير حان الوقت لإعطاء ملاحظة =)
     
    ملاحظة :
    ذكرت في الدروس الماضية
    لتحويل من مربع الى دائرة
    فانك تحتاج تعرف قيمة الطول او العرض ومن ثم تقسمه على ٢
    واخيراً تضيف القيمة في cornerRadius
     
    فهنا لكي نقوم بتحويل المربع الى دائره
    قسمنا ١٢٢ على ٢ والقيمة كانت ٦١ لذا اضفناها في خانة toValue
     
    الطريقة الأخرى هيا جلب القيمة الطول او العرض للـ UIView وتقسمها على ٢
     
    بالطريقة التالية
    squareView.frame.width / 2  
    هنا طلبنا قيمة العرض من UIView المربع ومن ثم قسمناها على ٢
     
    لا فرق بين جلب العرض او الطول لانه بطيبيعه الحال المربع يكون متساوي القيمة في العرض وطوال
     
    نعود للدرس
     
    كما لاحظت في الصورة المتحركة السابقة بعد انتهاء الـ Animation
     
    عادت الدائرة الى مربع
     
    ذكرت في الدرس الماضي لجعل الـ UIView
    في حالته النهائية
     
    يتوجب عليك كتابة القيمة مره أخرى بعد كود تنفيذ الـ Animation
    الذي هو بعد سطر
    squareView.layer.add(cornerRadiusAnimation, forKey: nil) بما يعني يتوجب عليك كتابة السطر التالي
     
     squareView.layer.cornerRadius = squareView.frame.width / 2  
     
    فيصبح الكود بالشكل التالي :
     
    let cornerRadiusAnimation = CABasicAnimation(keyPath: "cornerRadius") cornerRadiusAnimation.duration = 0.5 cornerRadiusAnimation.fromValue = 0 cornerRadiusAnimation.toValue = squareView.frame.width / 2 cornerRadiusAnimation.beginTime = CACurrentMediaTime() + 0.5 squareView.layer.add(cornerRadiusAnimation, forKey: nil) squareView.layer.cornerRadius = squareView.frame.width / 2  
    قم بتشغيل التطبيق ولاحظ النتيجة في الصورة التالية :

     
    امراً غريباً الذي حدث
    تم تحويل المربع الى دائرة بشكل مفاجئة و من ثم عاد الى الشكل المربع وحدث الـ Animation
    واخيراً توقف الـ Animation
    على شكل الدائرة
     
    ما الذي حدث ؟
     
    لماذا تحول في البداية الى دائرة بشكل سريع ؟
     
    هذا الحدث الغريب يوجهنا لموضوع جديد وهو الـ FillMode
     
     
    الموضوع الثاني : ماهو الـ FillMode ؟
     
    الـ FillMode يسمح لك بالتحكم بسلوك الـ Animation اثناء بدايته ونهايته
     
    وهو ٤ أنواع :
     
    kCAFillModeRemoved النوع الأول يطلق عليه
    وظيفته يبدا الـ Animation فوراً ومن ثم يحذفه عند اكتماله (انتهائه)
    هذا هو الخيار الافتراضي
     
    النوع الثاني يطلق عليه  kCAFillModeBackwards
    وظيفته يعرض اول Frame للـ Animation قبل بداية الـ Animation
    ومن ثم يبدأ الـ Animation ومن ثم ينتهي
     
    النوع الثالث يطلق عليه  kCAFillModeForwards
    يبدا الـ Animation فوراً ولكن يبقى أخر Frame حتى تقوم بإزالته
     
    النوع الرابع يطلق عليه  kCAFillModeBoth
    وهو دمج النوعين السابقين مع بعض
    بحيث يعرض اول Frame قبل بداية الـ Animation
    ومن ثم يبدأ الـ Animation ومن ثم ينتهي
    واخيراً يبقى أخر Frame بعد انتهاء الـ Animation
     
    نعود للموضوع لحل المشكلة التي واجهتنا
     
    أعتقد بأن الان فهمت الفكرة وكيف تحلها !
     
    من تجربتي وجدت المشكلة تحدث في بعض الحالات
    ومن هذه الحالات عند تأخير بدأ الـ Animation
    فيحدث أمر مختلف عن ما كنت تتوقعه كما حدث معانا
     
    وطريقة حل المشكلة اما بتغيير الـ FillMode الى kCAFillModeBackwards
    وبالتالي سوف يبقي أول Frame في قبل بدأ الـ Animation وبعدها يبدا الـ Animation
    وبالتالي تنحل المشكلة او يمكنك اذا اردت استخدام الـ kCAFillModeBoth
     
    في كلا الحالتين سوف تحل المشكلة التي واجهتنا
     
     
    سوف اقوم باستخدام الـ kCAFillModeBackwards
     
    لذا نقوم بإضافة الكود التالي
    cornerRadiusAnimation.fillMode = kCAFillModeBackwards وبتالي سوف يصبح الكود بالشكل التالي :
    let cornerRadiusAnimation = CABasicAnimation(keyPath: "cornerRadius") cornerRadiusAnimation.duration = 0.5    cornerRadiusAnimation.fromValue = 0 cornerRadiusAnimation.toValue = squareView.frame.width / 2 cornerRadiusAnimation.beginTime = CACurrentMediaTime() + 0.5 cornerRadiusAnimation.fillMode = kCAFillModeBackwards squareView.layer.add(cornerRadiusAnimation, forKey: nil)   squareView.layer.cornerRadius = squareView.frame.width / 2  
    الان قم بتشغيل الكود وسوف تجد النتيجة كما في الصورة التالية :

     
    الموضوع الثالث : خصائص التوقيت Timing Options
     
    كيف تقوم باستخدام  autoreverses
    في الـ CoreAnimation ؟
     
    اذا لا تذكر ماهي وظيفة الـ autoreverses
    فوظيفته هو إعادة الـ Animation بشكل عكسي بعد الانتهاء من تنفيذه
     
    في مثالنا السابق قمنا بتحويل المربع الى دائرة ومن ثم بعد اكتماله قمنا بإبقائه على شكل الدائرة
     
    باستخدام الـ autoreverses
    تستطيع تحويله من مربع الى دائرة ومن ثم من دائرة الى مربع
     
     
    قم بكتابة الكود التالي :
     
    let cornerRadiusAnimation = CABasicAnimation(keyPath: "cornerRadius") cornerRadiusAnimation.duration = 0.5    cornerRadiusAnimation.fromValue = 0 cornerRadiusAnimation.toValue = squareView.frame.width / 2 cornerRadiusAnimation.beginTime = CACurrentMediaTime() + 0.5 cornerRadiusAnimation.fillMode = kCAFillModeBackwards    cornerRadiusAnimation.autoreverses = true      squareView.layer.add(cornerRadiusAnimation, forKey: nil)    
    كل الذي اضفته هو سطر
        cornerRadiusAnimation.autoreverses = true  
    وقمت بحذف سطر
     squareView.layer.cornerRadius = squareView.frame.width / 2  
    لأني لا أريد ابقائه على شكل الدائرة
    في حال عدم حذفي لهذا السطر سوف تلاحظ امرأ غريباً
     
    على أي حال قم بتشغيل الكود وشاهد النتيجة كما في الصورة التالية :

    ماذا اذا اردت تكرار نفس الـ Animation بعدد مرات محدده ؟
     
    هناك امرين تستطيع فعله
     
    الاول هو تكرار الـ Animation بعدد مرات محدده وذلك باستخدام الملكية repeatCount كما في الكود التالي
        cornerRadiusAnimation.repeatCount = 3 او بعدد ثواني محدده باستخدام ملكية repeatDuration كما في الكود التالي
    cornerRadiusAnimation.repeatDuration = 6 لا تستطيع استخدام الاثنين في نفس الوقت لأنه سوف يتم تجاهله احداهما
     
    على أي حال سوف استخدم امر التكرار بعدد مرات محدده
     
    لذا سوف يصبح الكود بالشكل التالي :
     
    let cornerRadiusAnimation = CABasicAnimation(keyPath: "cornerRadius")    cornerRadiusAnimation.duration = 0.5    cornerRadiusAnimation.fromValue = 0 cornerRadiusAnimation.toValue = squareView.frame.width / 2   cornerRadiusAnimation.beginTime = CACurrentMediaTime() + 0.5    cornerRadiusAnimation.fillMode = kCAFillModeBackwards      cornerRadiusAnimation.autoreverses = true          cornerRadiusAnimation.repeatCount = 3       squareView.layer.add(cornerRadiusAnimation, forKey: nil)    
    ملاحظة :
     
    ليس ضروري وضع (عدد صحيح) في التكرار
    بما يعني عند استخدام repeatCount
     
    تستطيع عمل نص تكرار
    مثال
    .repeatCount = 3.5 هنا سوف ينفذ ٣ تكرارات كامله وفي اخر تكرار سوف ينفذ نص الـ Animation
     
    اعلم بأن الامر غريباً ولكنه ممكناً ومفيداً أحياناً !
     
     
    نعود للدرس
     
    تغير سرعة الـ Animation :
    او بعباره أخرى تسريع الـ Animation
     
    لكي تتضح الصورة
    هل لاحظت بأنه عند تشغيل مقطع معين على موقع الـ Youtube
    تستطيع تغير سرعته ؟
     
    مثال طول المقطع هو ١٠ دقائق
    ووضعت السرعة x2
    بمعنى اخر ضعف السرعة
    فالمقطع سوف تشاهده في مدة ٥ دقائق والسبب هو سرعة تشغيله أصبحت الضعف
    وبالتالي مدة مشاهدته أصبحت نص طول المقطع
     
    نفس الامر تستطيع عمله مع الـ Animation
     
    في البداية سوف استخدم الكود السابق
    بدون إضافة زيادة السرعة
     
    لاحظ النتيجة :

    ومن ثم سوف اجعل السرعة الضعف باستخدام الكود التالي :
     
      cornerRadiusAnimation.speed = 2  
    وبالتالي سوف يصبح الكود النهائي بالشكل التالي :
     
    let cornerRadiusAnimation = CABasicAnimation(keyPath: "cornerRadius")   cornerRadiusAnimation.duration = 0.5      cornerRadiusAnimation.fromValue = 0 cornerRadiusAnimation.toValue = squareView.frame.width / 2    cornerRadiusAnimation.fillMode = kCAFillModeBackwards    cornerRadiusAnimation.speed = 2   squareView.layer.add(cornerRadiusAnimation, forKey: nil)    squareView.layer.cornerRadius = squareView.frame.width / 2  
    قم بتشغيل الكود وشاهده النتيجة :

     
    هل لاحظت الفرق ؟
     
    سرعة تشغيل الـ Animation أصبحت الضعف دون الحاجة الى تغير مدة الـ Animation
    (بدون تغيير الـ duration)
     
    الان سوف تستغرب لماذا لم نغير الـ duration
    بدل من تسريع الـ Animation !
     
    الاجابة سوف تتضح في الدرس القادم إن شاء الله
     
    لكن في الوقت الحالي باستطاعتي إيضاح نقطه ما
     
    وهيا باستطاعتك تسريع الـ layer بذاته !
     
    ذكرت في الدرس السابق بأنه من فوائد الـ CoreAnimation
    هيا عدم ارتباطه بأي UIView
    بما يعني نقوم بإضافة خصائص الـAnimation ومن ثم ننفذها على Layer الـ UIView
    المراد
    بكتابة الامر
    squareView.layer.add(cornerRadiusAnimation, forKey: nil)  
    يتضح بما سبق التالي :
     
    نستطيع إضافة UIView أخر
     
    كما فعلنا في الدرس الماضي بإضافة مربع اخضر مع المربع الأزرق
     
    سوف أقوم بتسميته squareView2
     
    ومن ثم سوف اضيف سطر واحد لتنفيذ نفس الـ Animation
    على الـ squareView2
     
    بكتابة الامر التالي :
       squareView2.layer.add(cornerRadiusAnimation, forKey: nil)  
    ومن ثم عند نهاية الكود سوف أقوم بكتابة هذا السطر
    لكي احافظ على شكل الدائرة بعد انتهاء الـ  Animation
    squareView2.layer.cornerRadius = squareView.frame.width / 2  
    الان قم بتشغيل الكود ولاحظ النتيجة :

     
    لاحظت بأني لم أضيف خصائص الـ Animation
     
    مره أخرى ، فقط طلبت تنفيذ نفس الـ Animation
    على الـ squareView2
     
    ونفس الـ Animation تم تنفيذه على كلا المربعين
     
    الان سوف أقوم بتسريع Layer
    الـ squareView2
     
    بكتابة الامر التالي
    squareView2.layer.speed = 2  
    وبالتالي سوف يصبح الكود النهائي بالشكل التالي :
     
    let cornerRadiusAnimation = CABasicAnimation(keyPath: "cornerRadius")   cornerRadiusAnimation.duration = 0.5        cornerRadiusAnimation.fromValue = 0 cornerRadiusAnimation.toValue = squareView.frame.width / 2   cornerRadiusAnimation.fillMode = kCAFillModeBackwards      squareView.layer.add(cornerRadiusAnimation, forKey: nil)    squareView2.layer.speed = 2        squareView2.layer.add(cornerRadiusAnimation, forKey: nil)    squareView.layer.cornerRadius = squareView.frame.width / 2    squareView2.layer.cornerRadius = squareView.frame.width / 2  
    قم بتشغيل الكود ولاحظ النتيجة :
     
     
    هل لاحظت ؟
     
    المربع الأخضر أسرع من المربع الأزرق
    لأننا قمنا بتسريع الـ Animation
    للمربع الأخضر ولم نغير سرعة المربع الأزرق
     
    اعتقد بأنه الان اتضحت الفائدة من تغيير سرعة الـ Animation
     
     
    لنكمل شرح أخر فقره في موضوع اليوم =)
     
    الخصائص الأساسية للتوقيت :
     
    هل تذكر هذه الصورة ؟

    تم شرحها في الدرس الثاني من السلسلة
     
    هناك ٤ انواع رئيسية مسؤوله عن سرعة الـ Animation ، وهيا :
    curveLinear : ويعني يبدا الـ Animation بنفس السرعة وينتهي بنفس السرعة (لا يحدث اختلاف في السرعة)
    curveEaseIn : ويعني يبدا الـ Animation بسرعة بطيئة ومن ثم يزداد سرعته (يبدا بطئي وينتهي بسرعة)
    curveEaseOut : ويعني يبدا الـ Animation بسرعة وينتهي ببطئ (يبدا بسرعة وينتهي ببطئ)
    curveEaseInOut : هذا النوع هو الافتراضي،  ويعني يبدا الـ Animation ببطئ ومن ثم يسرع وينتهي ببطئ (يبدا وينتهي ببطئ)
     
     
    نفس الامر ينطبق على الـ CoreAnimation
     
    الاختلاف في المسميات
     
    الـ curveLinear هو kCAMediaTimingFunctionLinear
    الـ curveEaseIn هو kCAMediaTimingFunctionEaseIn
    الـ curveEaseOut هو kCAMediaTimingFunctionEaseOut
    الـ curveEaseInOut هو kCAMediaTimingFunctionEaseInEaseOut
     
    طريقة استخدام الـ Options
    بالطريقة التالية
     
    بكتابة سطر
        cornerRadiusAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)  
    اذا إضافة الكود السابق لن تلاحظ فرق عند التحويل من مربع الى دائرة
    حتى اذا غيرت النوع الى الانواع الأخرى
    لكن بسهولة سوف تلاحظ عند التحريك من موقع الى اخر
     
    لذا سوف اغير الكود الى هذا الكود
     
        let position = CABasicAnimation(keyPath: "position.y") position.duration = 0.3        position.fromValue = squareView.layer.position.y   position.toValue = 400    position.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) squareView.layer.add(position, forKey: nil) squareView.layer.position.y = 400    
    من النصائح المفيدة انك تسمي المتغير بنفس اسم الـ Animation
    التي تريد عمله ، هذه النصيحة سوف تفييدك في الدرس القادم =)
     
    لذا قمت بتسمة المتغير باسم position
     
    ولاني ارد تحريكه عاموديا كتبت
    position.y  
    ملاحظة :
    في سطر
    position.fromValue كتبت
    squareView.layer.position.y  
    ولم أقوم بإعطائه قيمة كما فعلت في الدرس الماضي
    السبب هو هذه الطريقة افضل لأنك تطلب منه يجلب القيمة الحالية للـ UIView
    -  تستطيع أيضا استخدام الامر
    squareView.center.y  
    الان قم بتشغيل الكود ولاحظ النتيجة :

     
    ومن ثم غير القيمة الى kCAMediaTimingFunctionEaseIn
     
    ولاحظ النتيجة :

    ملاحظة اخيره :
    تستطيع عمل TimingFunction خاص بك باستخدام السطر
    CAMediaTimingFunction(controlPoints: _: _: _:)  
    الصورة هذه توضح بعض الانواع الممكن عملها

     
    وصفحة الـ GitHub هذه سوف نجد بعض الانواع وكيفية كتابتها ككود
     
    مثلا لنفترض تريد عمل نوع
     
    افتح صفحة الـ GitHub
    ابحث عن EaseInOutQuad
    ستجده الكود التالي :
    CAMediaTimingFunction(controlPoints: 0.455, 0.03, 0.515, 0.955) قم باستبداله بالكود الذي كتبناه سابقا
    فيصبح الكود بالشكل التالي :
      position.timingFunction = CAMediaTimingFunction(controlPoints: 0.455, 0.03, 0.515, 0.955) وقم بتشغيل الكود ولاحظ النتيجة
     
    اذا اردت التجربة واكتشاف الفروقات قم بتجربة الانواع الموجودة =)
    فهذا الامر فقط موجود في الـ CoreAnimation
    وليس موجود في UIView Animation
    مستوى المقال: مبتدئ
  2. إصدار البرنامج المستخدم : xcode 9
    السلام عليكم
    في هذا الدرس سنوضح طريقة التعامل الUIPickerView في برنامج الxcode و هي أحد الأدوات المهمة التي تستخدم لتوفير عدد من الخيارات للمستخدم ليختار أحدها. و أحد أهم أنواعها الرئيسية هو الUIDatePicker الذي يستخدم لاختيار التاريخ أو الوقت.
    و حتى تكون طريقة استخدام الpicker view واضحة فإننا سنقوم بعمل تطبيق يقوم بتغيير قيم ال rgb : red - green - blue للشاشة 
    و الrgb هي القيم الأساسية للون الأحمر والأزرق و الأخضر و أي لون هو في الحقيقة حاصل دمج قيم العناصر الأساسية هذه، و بالتالي فأن تغيير قيم أحد العناصر الثلاثة هذه يعني تغيير درجة اللون. 
    إن لم تكن تعلم مسبقا ذلك فريما يبدو الكلام فيه شيء من الغموض، لكن الفكرة ستكون واضحة عند الانتهاء من العمل.
    إذا لنبدأ..
     
    - في البداية قم بإنشاء مشروع xcode جديد..

     
    - قم بإدراج عنصر الUIPickerView من النافذة في الزاوية السفلية على اليمين

     
    - قم بإضافة الconstraints كما هو موضح في الصورة..

     
    - قم بعمل outlet للpickerview في ملف الviewController ليكون بإمكانك التحكم به

     
    - الآن سنقوم بإسناد الdelegate و الdatasource لهذا الinstance من الViewController و لذلك قم بعمل ذلك في الviewDidLoad
    override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. pickerVeiw.delegate = self pickerVeiw.dataSource = self } -- ملاحظة : إذا لم تكن تعلم ما هو الdelegate و الdatasource فبإمكانك مراجعة درس الtableview في الرابط التالي حيث أن العملية مطابقة تماما للعملية في الtableview
     
    - الآن سيظهر البرنامج خطأ لأن الviewController ليس أوبجكت من نوع UIPickerViewDelegate و لا من نوع UIPickerViewDataSource و لذلك قم بإضافة الكود التالي لبداية تعريف الكلاس ليبدو كذلك :
    class ViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate { ...... }  
    - الآن سيـظهر خطأ آخر بسبب عدم تنفيذ الfunctions الخاصة بالdatasource حيث أن هذه الfunctions إجبارية و ليست اختيارية - optional- لذلك قم بإضافة الكود التالي إلى داخل الكلاس : 
    //1 func numberOfComponents(in pickerView: UIPickerView) -> Int { return 3 } //2 func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return 256 } //3 func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return "color" } 1- الدالة الأولى تستخدم لتحديد عدد الأعمدة في الpickerview و لأننا سنحتاج ٣ أعمدة ( للأخضر و الأحمر و الأزرق ) قمنا بإرجاع ٣
    2- الدالة الثانية تستخدم لتحديد عدد الصفوف في كل عمود، و حيث أن كل لون تتفاوت درجاته من 0 - 255 قمنا بإرجاع 256
    3- و أخيرا.. الدالة الأخيرة تستخدم لتحديد العنوان الذي سيظهر لكل صف، و مبدئيا وضعنا color لتكون عنوان لكل الصفوف و سنقوم بتغييرها بعد ذلك..
     
    -الآن قم بعمل run و شاهد النتيجة ليتضح لك الكلام أعلاه.
     
    و الآن يفترض أن تكون عنواين الصفوف هي القيم الخاصة بالألوان من 0-256 و لعمل ذلك قم بإضافة الكود التالي..
    class ViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate { //1 var values=[Int](); .... override func viewDidLoad() { super.viewDidLoad() //2 for i in 0...256 { values.append(i) } // Do any additional setup after loading the view, typically from a nib. pickerVeiw.delegate = self pickerVeiw.dataSource = self } }  
    1- في البداية قمنا بعمل array باسم values و التي ستمثل datasource للpickerview
    2- قمنا بملء هذه الarray في الviewDidLoad بالقيم من 0-256 حيث أنه من الصعب ملؤها يدويا
     
    و الآن قم بتعديل الfunctions الخاصة بالpickerview لتبدو كالتالي : 
    func numberOfComponents(in pickerView: UIPickerView) -> Int { return 3 } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return values.count } func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return String(values[row]); } الآن قمنا بإرجاع عدد الصفوف ليكون مساويا لعدد العناصر في الarray ( بحيث إذا قمت بالتعديل على القيم في الarray فإن عدد الصفوف سيتعد تلقائيا معها )
    و قمنا كذلك بجعل عناوين الصفوف تساوي العناوين الموجودة في الarray و هي القيم من 0-256 و قمنا بتحويلها إلى String
    و الآن قم بتشغيل البرنامج و شاهد النتيجة
     
    - الآن نريد تغيير لون خلفية شاشة التطبيق متى ما تم تغيير قيمة في الpickerview بحيث تقرأ هذه القيمة وتقوم بتعديل إما عنصر الأحمر أو الأخضر أو الأزرق حسب العنصر الذي تم تغييره و هذا يتم عن طريق استخدام الدالة : didSelectRow - inComponent و التي يتم استدعاؤها متى ما تم تغيير أو تحديد القيمة في الpickerView لذا قم بإضافة الكود التالي داخل الكلاس : 
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { //1 let red = pickerView.selectedRow(inComponent: 0) let green = pickerView.selectedRow(inComponent: 1) let blue = pickerView.selectedRow(inComponent: 2) //2 self.view.backgroundColor = UIColor(red: CGFloat(Double(red)/255), green: CGFloat(Double(green)/255), blue:CGFloat(Double(blue)/255), alpha: 1) } 1- في البداية قمنا بتحديد قيمة اللون الأحمر لتكون رقم الصف المحدد في العمود الأول من الpickerview عن طريق استخدام الfunction التي تسمى selectedRow حيث أن القيمة المرجعة هي ترتيب الصف حسب العمود الذي تحدده في البارامتر الخاص بهذه الfunction
    2- قمنا بعمل نفس الشيء للحصول على قيم اللون الأزرق و الأخضر
    3- قمنا بتغيير لون خلفية الview بناء على قيم الpickerview للعناصر الثلاثة ( الأحمر و الأزرق و الأخضر )
     
    -- لاحظ أننا قمنا بتقسيم القيم على 256 و ذلك بسبب أن درجة اللون في الUIColor يجب أن تعرف في القيم ما بين 0-1 و بالتالي فإن القيم التي تكون أكبر من 1 ستعتبر مساوية لل1 و لن تلاحظ وجود أي فرق في هذه الحالة في اللون حيث أن أي تغيير في قيم الpickerview في هذه الحالة هو ليس إلا تغيير للقيمة من ١ إلى ١ و لذلك فإن الحل هو تقسيم هذه القيمة على أقصى قيمة ممكنة في درجات اللون و هي 256 و بالتالي سنحصل على قيم من 0 -1 
     
    -- لاحظ أننا قمنا بتحويل القيم في البداية إلى double و ذلك لأنها في الأصل من نوع Int و عند تقسيم الInt على أي رقم أكبر منه يساوي 0 و لذلك يجب تحويلها إلى double لتفادي ذلك
     
    -- أخيرا.. قيم الألوان يجب أن تكون من نوع CGFloat لذلك قمنا قمنا بتحويل القيمة النهائية إلى هذا النوع و إلا سيظهر البرنامج وجود خطأ لعدم تناسب الأنواع
     
    -الآن قم بتشغيل التطبيق و ابدأ بتغيير القيم و ستلاحظ أن لون الخلفية يتغير حسب تغييرك لها

    إلى هنا نصل إلى نهاية هذا الدرس و إلى لقاء قريب في درس جديد  
    مستوى المقال: مبتدئ
  3. السلام عليكم ورحمة الله وبركاته
     
    في هذه المقالة سنتعلم طريقة تسجيل وتشغيل المقاطع الصوتية باستخدام Swift 4
    تسجيل وتشغيل المقاطع سهل ولكن يحتاج الى الوقت لكتابة الاكواد، لذا لنبدأ
     
    أولا، قم باستدعاء الفريموورك AVFoundation المختصة بمعالجة كل المقاطع المرئية والصوتية على منصات أبل
    import AVFoundation ثم قم بتبني البروتوكولات AVAudioRecorderDelegate و AVAudioPlayerDelegate لاستخدامهما لاحقا
    class ViewController: UIViewController, AVAudioRecorderDelegate, AVAudioPlayerDelegate { ثم قم باضافة:
    زرين لبدء/ايقاف التسجيل/التشغيل 
    recordingSession ليعالج جلسة تسجيل الصوت 
    audioRecorder ليتعامل مع التسجيل نفسه ويحفظه 
    audioPlayer لتشغيل المقاطع الصوتية بعد تسجيلها
    وpath لحفظ مسار ملف الصوت المسجل وتشغيله لاحقا
    @IBOutlet weak var recordButton: UIButton! @IBOutlet weak var playButton: UIButton! var recordingSession: AVAudioSession! var audioRecorder: AVAudioRecorder! var audioPlayer: AVAudioPlayer? var path: URL! (يتم استخدام الrecordingSession من قبل نظام iOS لتنظيم تسجيل وتشغيل المقاطع الصوتية منعا لتداخل الأصوات من أكثر من تطبيق -مثلا لكي لا يستطيع تطبيق تسجيل او تشغيل صوت عند اجراء مكالمة-)
     
    تسجيل الصوت يتطلب اذن المستخدم ليتم، ان تم اعطاء الاذن في هذا الكود سيتم تغيير نص زر التسجيل، اضف هذا الكود في ()viewDidLoad
    playButton.isEnabled = false recordingSession = AVAudioSession.sharedInstance() do { try recordingSession.setCategory(AVAudioSessionCategoryPlayAndRecord) try recordingSession.setActive(true) recordingSession.requestRecordPermission() { [unowned self] allowed in DispatchQueue.main.async { if allowed { self.recordButton.setTitle("تسجيل", for: .normal) } else { // failed to record! } } } } catch { // failed to record! }  
    قبل اضافة الIBAction الخاص بزر التسجيل، سنضيف ميثود startRecording التي (1) ستحدد مكان حفظ ملف الصوت، (2) اعدادات مسجل الصوت ثم (3) بدء التسجيل
    func startRecording() { let audioFilename = getDocumentsDirectory().appendingPathComponent("recording.m4a") //1 path = audioFilename playButton.isEnabled = false let settings = [ //2 AVFormatIDKey: Int(kAudioFormatMPEG4AAC), AVSampleRateKey: 12000, AVNumberOfChannelsKey: 1, AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue ] do { audioRecorder = try AVAudioRecorder(url: audioFilename, settings: settings) //3 audioRecorder.delegate = self audioRecorder.record() //3 recordButton.setTitle("ايقاف التسجيل", for: .normal) } catch { finishRecording(success: false) } }  
    سنحتاج لاضافة الميثود getDocumentsDirectory لتعمل الميثود السابقة
    في هذه الميثود سنعيد المسار المتاح من النظام للتطبيق لحفظ الملفات فيه، سنقوم بحفظ الملف الصوتي في المسار الرئيسي للتطبيق
    func getDocumentsDirectory() -> URL { let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) let documentsDirectory = paths[0] return documentsDirectory }  
    انتهينا من الفنكشن الخاصة ببدء التسجيل، والآن نحتاج لفنكشن تقوم بانهاء التسجيل عند طلب المستخدم
    func finishRecording(success: Bool) { audioRecorder.stop() audioRecorder = nil playButton.isEnabled = true if success { recordButton.setTitle("اعادة التسجيل", for: .normal) } else { recordButton.setTitle("تسجيل", for: .normal) // recording failed :( } }  
    بانهاء الفنكشنين السابقة، نستطيع اخيرا كتابة الIBAction الخاص بزر التسجيل
    @IBAction func recordTapped(_ sender: Any) { if audioRecorder == nil { startRecording() } else { finishRecording(success: true) } }  
    وهكذا نكون قد انتهينا من تسجيل الصوت
    لتشغيل الصوت سنضيف الIBAction التالي الخاص بزر التشغيل
    @IBAction func playTapped(_ sender: Any) { do { audioPlayer = try AVAudioPlayer(contentsOf: path) audioPlayer?.delegate = self audioPlayer?.play() recordButton.isEnabled = false } catch { // couldn't load file :( } }  
    نهاية سنضيف الفنكشين التالية من البروتوكولات التي قمنا بتبنيها في بداية المقالة
    الأولى audioRecorderDidFinishRecording يتم استدعاؤها عن توقف التسجيل
    في بعض الاحيان، قد يقوم النظام بايقاف التسجيل عنوة (عندما ترد مكالمة على سبيل المثال)، في هذه الفنكشن سنتأكد عند عدم نجاح التسجيل لنضبط نص زر التسجيل
    func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) { if !flag { finishRecording(success: false) } } والفنكشن الثانية audioPlayerDidFinishPlaying يتم استدعاؤها عند انتهاء تشغيل المقطع الصوتي
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { recordButton.isEnabled = true }  
    (بالامكان استخدام ()audioPlayer.stop لايقاف التشغيل)
    (تأكد من ربط الOutlets والActions قبل التشغيل)
     
    النتيجة النهائية

    مستوى المقال: متوسط
  4. السلام عليكم ورحمة الله وبركاته
     
    هذه المقالة ستتحدث عن الCoredata، كدليل لمن لم يستخدم ال Coredata ابدا من قبل.
    سننشئ تطبيق to-do بسيط لشرح كيفية استخدام الCoredata
     
    لماذا الCoredata؟
    يتم استخدام ال Coredata لحفظ بيانات الObjects على الجهاز لاستعادتها لاحقا بعد اغلاق التطبيق/الجهاز وهو ما يعرف بـ Persistence Data
    هناك مزايا للCoredata كامكانية التعديل على الrecords والفلترة والPartial Loading عكس الUserDefaults
    وصحيح أن هناك frameworks أخرى تقدم ميزة حفظ البيانات locally (مثل مكتبة Realm على سبيل المثال) لكننا لن نتطرق للفروقات بينهما و سنتحدث فقط عن الCoredata
     
    ماذا ستتعلم هنا؟
    في دليل المبتدئين لن نغوص عميقا في الCoredata، لكنك ستتعلم كيفية حفظ، استعادة، وحذف البيانات.
    سأفترض بأنك على معرفة بالdelegates, optionals وكيفية انشاء UITableView بسيط
    ان لم تكن على علم بها أو ان اردت مراجعة هذه المفاهيم ستجدها في المواضيع التالية:
    الoptional في swift3 مقدمة في الtableview في برنامج الxcode
    الtable view في الxcode ( الجزء الثاني )
     
    واجهة المستخدم
    في هذا الدليل سنقوم بانشاء to-do-app بسيط يتكون من واجهتين، الأولى تتكون من UITableView فقط لعرض المهام المضافة
    والثانية ستسخدم لاضافة المهام تحتوي على UITextField لادخال نص المهمة وUIButton لحفظ المهمة والعودة للواجهة السابقة

     
    بداية
    عندما تقوم بانشاء المشروع لأول مرة، تأكد من اختيار Use Core Data 

     
    عند انشاء المشروع ستلاحظ وجود ملف جديد xcdatamodeld.
    هذا الملف كال Spreadsheet سيستعمل لانشاء مودل للبيانات التي نرغب بتخزينها
    اضغط على الملف الجديد واضف Task كEntity وname كAttribute من نوع String

     
    سنبدأ بحفظ البيانات في الواجهة الثانية  AddTask
    للقيام بذلك يجب أن نحصل على access للCoredata Stack
    هناك كلمتين مخيفتين عليك تذكرهما جيدا، NSPersistentContainer و NSManagedObjectContext
    يمكنك التفكير بال Container كالصندوق الذي سنحتفظ فيه بالبيانات، والContext كفتحة الصندوق التي سنستطيع من خلالها اضافة، استرجاع وحذف البيانات
    بالمناسبة، في الAppDelegate.swift ستجد العديد من الميثودز والخصائص المتعلقة بالCoredata، كل ماعلينا عمله هو استخدامها
     
    في الواجهة الثانية سنضيف الكود التالي

     
    1/ في هذا السطر قمنا بانشاء reference للContext (الصندوق) لنقوم بالتخزين فيه
    2/ قمنا بتحديد نوع البيانات التي نريد تخزينها (من نوع Task Entity) وقمنا بتحديد الname المراد تخزينه
    3/ طلبنا من الContext حفظ البيانات التي تم ادخالها. (بما أن الTask Entity تم ربطها بالContext فسيقوم الContext بحفظها)
    4/ سنعود للواجهة الأولى لعرض المهمات
     
    الآن لعرض المهمات سنضيف الكود التالي للواجهة الأولى
    class TasksTable: UIViewController, UITableViewDelegate, UITableViewDataSource { @IBOutlet weak var tableView: UITableView! let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext //1 var tasks: [Task] = [] //2 override func viewDidLoad() { //3 super.viewDidLoad() tableView.delegate = self tableView.dataSource = self } override func viewWillAppear(_ animated: Bool) { //4 getData() tableView.reloadData() } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { //5 return tasks.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { //5 let cell = UITableViewCell() let task = tasks[indexPath.row] if let myName = task.name { cell.textLabel?.text = myName } return cell } func getData() { //6 do { tasks = try context.fetch(Task.fetchRequest()) } catch { print("Fetching Failed") } } func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { //7 if editingStyle == .delete { let task = tasks[indexPath.row] context.delete(task) (UIApplication.shared.delegate as! AppDelegate).saveContext() do { tasks = try context.fetch(Task.fetchRequest()) } catch { print("Fetching Failed") } } tableView.reloadData() } }  
    سنقوم بشرح الكود السابق بالتفصيل
    1/ قمنا بانشاء reference لل Context في أعلى الكلاس لنستخدمه عند الحاجة في الميثودز المختلفة
    2/ قمنا بانشاء Array مؤقتة لتخزين البيانات المجلوبة من الContext وعرضها في الجدول
    3/ في الViewDidLoad قمنا بتحديد الواجهة الحالية ك Delegate و DataSource للجدول
    4/ في الViewWillAppear قمنا باستدعاء الميثود ()getData لجلب البيانات باستخدام الContext وتحديث الجدول لعرضها
    5/ في الميثودين numberOfRowsInSection و cellForRowAt indexPath قمنا بانشاء الجدول وعرض الخلايا بالبيانات المطلوبة
    6/ في الميثود getData قمنا بطلب fetch من الContext وقمنا بتحديد الTask Entity للحصول على آخر نسخة من بياناتها
        هذا الاستدعاء يجب أن يكون متضمن داخل do-catch block 
    7/ في الميثود editingStyle الخاصة بالجدول قمنا باضافة ميزة السحب للحذف (كالتالي)

         واستخدمنا الميثود ()context.delete لحذف الtask التي تم اختيارها ثم قمنا باعادة استدعاء البيانات من جديد وتحديث الجدول لعرضها
     
    النتيجة النهائية للتطبيق

     
    ونصل هنا الى ختام مقالتنا
    أستودعكم الله الذي لا تضيع ودائعه
    مستوى المقال: متوسط
  5. بسم الله الرحمن الرحيم 
    في هذا الدرس سأقوم بشرح كيفية عمل الخرائط بإستخدام MapKit.
     
    أولا : إفتح xcode و قم بإختيار Single View Application .

    وأدخل المعلومات كما في الصوره التالية :

    ثانيا : إذهب الى storyboard . من صندوق الأدوات إختر MapKit View و قم بسحبها الى main View .

    الأن نضع القياسات لـ MapKit View بحيث تكون بنفس حجم الـmain View كالتالي:
    إضغط على زر Pin في أسفل يمين الـ storyboard ثم إضغط الخطوط الحمراء المتقطعة لتصبح متصلة كما في الصورة التالية :

     
    ثالثا: أنشئ IBOutlet من MapKit View كالتالي : 
    أختر Assistant Editor وتأكد أن ملف ViewController.swift ظاهر أمامك . حدد MapKit View و قم بالضغط على زر الفأره الأيمن + زر Ctrl ثم قم بالسحب إلى الكود البرمجي داخل ViewController class .. قم بتسمية ال IBOutlet بالإسم التالي  mapview كما في الصورة التالية : 
     

     
    رابعا : إذهب إلى ملف ViewController.swift وقم بتضمين مكتبة MapKit في بداية الكود البرمجي .
    import MapKit  
    خامسا : في دالة ViewDidLoad أضف الكود البرمجي التالي : 
    override func viewDidLoad() {         super.viewDidLoad()          // 1         mapview.mapType = MKMapType.standard                 // 2                let location = CLLocationCoordinate2D(latitude: 51.50007773, longitude: -0.1246402)          // 3                let span = MKCoordinateSpanMake(0.05, 0.05)         let region = MKCoordinateRegion(center: location, span: span)         mapview.setRegion(region, animated: true)         4//         let annotation = MKPointAnnotation()         annotation.coordinate = location         annotation.title = "Big Ben"         annotation.subtitle = "London"         mapview.addAnnotation(annotation)     }  
     
    ١- نختار نوع الخريطة بإستخدام خاصية MKMapType , وإخترنا نوعها Standard. 
    ٢- أنشأنا ثابت إسمه location وأسندنا إليه دالة تسمى CLLocationCoordinate2d و وضعنا فيها خطوط الطول والعرض لمدينة لندن .
    ٣- قيمة span تمثل خطوط الطول والعرض لجزء الخريطة المراد عرضه (أي اتساع الخريطة من الشمال إلى الجنوب و من الشرق إلى الغرب ), كلما كانت قيمته صغيره كان الجزء المعروض من الخريطة كبير ,أيضا يحدد مستوى التكبير الحالي في الخريطة .
    - دالة الـ MKCoordinateRegion تحدد المنطقة التي حددنا إحداثياتها في الخطوة الثانية .
    ٤- أنشأنا كائن ثابت من كلاس MKPointAnnotation و أسميناه annotation ( annotation هي العلامة الحمراء التي تظهر عادة في خرائط أوبر) , 
    -أسندنا إلى الإحداثيات coordinate قيم الثابت location الذي أنشأناه في الخطوة الثانية و وضغنا في الـ Title إسم المكان و الـ SubTitle إسم العاصمة. 
     
     
     الأن قم بتشغيل المشروع : 

    مستوى المقال: مبتدئ
  6. بسم الله الرحمن الرحيم
     
    تكلمنا في المقالة السابقة (القواميس Dictionaries في السويفت - الجزء الأول) عن عدّة نقاط وهي :
    ماهي القواميس Dictionaries ؟ كيفية تعريف وإنشاء القواميس Dictionaries :     1    إنشاء قواميس فارغة .
        2    إنشاء قواميس بقيم إبتدائية .
       إضافة عناصر جديدة للقواميس .   إزالة قيم القواميس .   وفي هذه المقالة سنكمل ماتعلمناه عن القواميس وسنتحدث عن :
     
    التعديل على قيم القواميس . خاصية count في القواميس. استخدام التكرار مع القواميس .  
    التعديل علي قيم القواميس : ذكرنا سابقاً أننا عندما نريد التعامل مع قيم القواميس والوصول لها فإن ذلك سيكون عن طريق المفتاح الخاص بالقيمة، وبناءاً على ذلك نستطيع الوصول والتعديل على قيمة معينة من خلال المفتاح الخاص بها .
     
    * ملاحظة : حتى تتمكن من التعديل على قيم القواميس من المهم أن تكون من نوع var (متغير) وليس let (ثابت) .
     
    وللتعديل علي أي قيمة سيكون ذلك كالتالي  :

     
    مثال (١) :
    var  Jobs : [Int : String] = [1:"Programmer" , 2:"Designer" , 3:"Writer"] Jobs [3] = "Secretary"  
    في هذا المثال تم تغيير قيمة المفتاح (3) في المتغير Jobs من (Writer) إلى (Secretary) .
     
    أيضاً للتعديل على القيم نستطيع استخدام الدالة updateValue (ForKey:) وسنستخدمها كالتالي :

     
    سنقوم بتغيير قيمة المفتاح (3) في المتغير Jobs في المثال السابق من (Writer) إلى (Secretary) ولكن باستخدام الدالة :
    Jobs.updateValue("Secretary", forKey: 3)  
    خاصية Count في القواميس :  
    في القواميس نستطيع استخدام الخاصية count لمعرفة عدد العناصر الموجودة .
     
    مثال (١) :
    var  Jobs : [Int : String] = [1:"Programmer" , 2:"Designer" , 3:"Writer"] var  NewJobs : [Int : String] = [1:"Doctor" , 2:"Engineer"] print(Jobs.count) print(NewJobs.count) 
    في هذا المثال لدينا متغيرين من نوع Dictionary وهم Jobs و NewJobs الأول يحتوي على (٣عناصر)  ،بينما المتغير الثاني يحتوي على (عنصرين فقط) .
    بعد ذلك قمنا باستخدام الخاصية count مع كلا المتغيرين لطباعة عدد العناصر الموجودة في كل متغير وعلى ذلك سيتم طباعة رقم 3 ومن ثم رقم 2 والتي تدل كما قلنا على عدد عناصر كل متغير .
     
    تستطيع الآن تجربة ذلك في ال xCode لترى النتيجة  
     
     
    استخدام التكرار مع القواميس :  
    لابد وأنك لاحظت في الأمثلة السابقة جميع المخرجات تكون للقيم بدون المفتاح ولقيمة واحدة فقط ، فلنفترض الآن بأنك أردت طباعة كل المفاتيح الموجودة مع قيمهم ، ولعمل ذلك فإننا سنستخدم for-In loop (يُفترض بأنك قد تعرفت على for-In loop كيفية كتابتها وفكرتها قبل التعرف على القواميس ) .

    مثال (١) :
     
    var  Jobs : [Int : String] = [1:"Programmer" , 2:"Designer " , 3:"Writer"] for (x , y)  in Jobs {          print ( " Key =   \(Key)  and the value is =  \(Value) ") }  
    بالمثال السابق أردنا طباعة جميع المفاتيح مع قيمها للقاموس Jobs ،واستخدمنا الـ for - In loop لطباعتها .
    في for- in loop سيصبح للمفاتيح ثابت وأسميناه x وللقيم أيضاً ثابت واسميناه y 
    والمخرجات ستكون كالتالي :

     
    بإمكانك أيضاً باستخدام الـ for-In loop مع القواميس أن تقوم بطباعة جميع المفاتيح فقط أو جميع القيم فقط كالتالي :
     
    for x in Jobs.keys {     print ( " Key =   \(x)  ") } استخدمنا الخاصية keys بعد اسم القاموس لنتمكن من طباعة جميع المفاتيح في القاموس Jobs.
    والمخرجات ستكون كالتالي :

     
    * لاحظ بأن المخرجات ليست بالترتيب لأن كما ذكرنا سابقاً (في مقالة القواميس Dictionaries في السويفت - الجزء الأول) بأن القواميس تعتبر غير مرتبة unorderd .

    ولطباعة القيم فقط سنستخدم الخاصية values :
    for x in Jobs.values {          print ( " Key =   \(x)  ") }  والمخرجات كالتالي :

     
     
    وبذلك نكون قد انتهينا بحمد الله من موضوع القواميس Dictionaries بجزئيه الأول والثاني ،أتمنى أكون قد وفقت في الشرح ولا تتردد في سؤالي عن أي نقطة قد تصعب عليك
     
    وصلى الله وسلم على نبينا محمد وعلى آله وصحبه أجمعين ..
    مستوى المقال: مبتدئ
  7. بسم الله الرحمن الرحيم
     
    مقدمة
    استعرضنا في الجزء الأول طريقة ارسال البيانات بين الViews عن طريق انشاء Reference للView المستقبلة للبيانات وتغيير قيم متغيراتها.
    في هذه المقالة، سنتعرف على ماهية البروتوكولات في سويفت وطريقة استخدامها لنقل البينات من أي View الى View اخر.
     
    أولا: ماهو البروتوكول
    لتفهم بالبروتوكول بشكل صحيح، فكر به على أنه طريقة لتعريف ما يمكن للمتغير أن يفعله، عكس الكلاسات التي تعرف ماهية المتغير.
     
    ثانيا: نقل البيانات بواسطة البروتوكول
    في الجزء الأول رأينا كيف نقوم بنقل البيانات بين الView Controllers الى الأمام، والآن سنستعمل البروتوكول لنقل البيانات للView السابقة
    سنقوم أولا بعمل السيت اب في الStoryboard كالتالي (سنسمي الView الأولى InitialVC والثانية FinalVC):

     
    والآن ضمًن الView الاولى بداخل Navigation Controller لتصبح كالتالي:

     
    سنقوم بانشاء Segue بين الView الأولى والثانية:

     
    قم باضافة زر لكل View كالتالي: لا تنسى اضافة الSegue identifier لأننا سنستعمله لاحقا

     
    والآن لننهي السيت أب، أضف الIBActions في الViewControllers لتصبح كالتالي:

     
    كل ماسبق كان مجرد تجهيز للتالي
    سنقوم الآن بتعريف البروتوكول والمتطلبات
     
    وانت في كلاس الFinalVC:
    قم باضافة تعريف للبروتوكول باعلى الصفحة يحتوي على ميثود finishPassing بباراميتر String قم بتعريف متغير delegate من نفس نوع البروتوكول في ميثود الIBAction، قم باستخدام المتغير لنقل البيانات التي تريد نقلها الى الView الأولى (في هذا المثال نقلت سترينق) سيصبح كلاس الFinalVC يبدو كالتالي:

    1) أولا قمنا بانشاء بروتوكول (أو blueprint)، يحتوي على الميثود التي سنستعملها لاستقبال البيانات مستقبلا في الكلاس الأول
    2) قمنا بانشاء متغير من نفس نوع البروتوكول ليحمل البيانات التي نريد ارسالها
    3) عند الضغط على زر العودة، سنزود المتغير بالبيانات التي نريد نقلها الى الView الأولى
     
    الى الآن، المتغير delegate لا يعلم الى اي كلاس سيقوم بنقل البيانات التي يحملها معه
    سنقوم الآن بتبني البروتوكول في الكلاس الأول وتحديده كنقطة الوصول بالنسبة للمتغير
     
    وانت في كلاس الInitialVC:
    اضف البروتوكول الى الكلاس الأول لتبنيه اضف الميثود prepareForSegue واستخدمها لتحديد الكلاس الحالي كنقطة الوصول للمتغير delegate من الكلاس الأخير وأخيرا لتبني البروتوكول بشكل كامل وصحيح، أضف الميثود finishPassing واستخدمها للتعامل مع البيانات المنقولة من المتغير delegate سيصبح كلاس InitialVC يبدو كالتالي:

    1) قمنا بتبني البروتوكول مبدئيا في الكلاس الأول
    2) في الميثود prepareForSegue (التي سيتم استدعاءها قبل الانتقال الى الView الأخيرة)، قمنا باخبار المتغير delegate بان نقطة الوصول له هو الكلاس الحالي self
    3) الى الآن ستلاحظ وجود error على الكلاس، لأنه يتوقع منك اضافة الميثود finishPassing التي ستتعامل مع البيانات التي ستصل من المتغير delegate، أضفناها وبداخلها طريقة للتعامل مع البيانات المنقولة عن طريق الparameter
     
    والآن أصبحنا جاهزين للتجربة

    لاحظ طباعة السترينق من الكلاس الأول كما حددنا في الميثود finishPassing، يمكنك أنت أن تقوم بالتعامل مع البيانات كما تشاء.
     
    وصلنا الى نهاية موضوعنا اليوم، أترككم في حفظ الله ورعايته.
    مستوى المقال: متوسط
  8.  
    مرة فترة طويلة على تدوينات المتعلقة بالـ Animation !
    اليوم اقدم لكم مفهوم جديد في تجربة المستخدم وواجهة المستخدم وكيفية عمله =)

    شرح التدوينة في سطر واحد سوف يكون The Power of StackView !
    كيف الاستفادة من الـ StackView في عمل Animation
    في اقل عدد من الاسطر وبنتيجة رائعة =)
     
    لأول مره سوف اعمل الـ Animation مع الـ TablwView
    في هذا الـ Animation سوف نستفيد من ميزة السحب للأعلى وللأسفل
    الموجوده في الـ TableView  ، (أيضا يمكنك عملها مع الـ ScrollingView)
     
    النتيجة النهائية سوف تكون بالشكل التالي :
     

     
    ماذا سوف تحتاج ؟
    أولا : سوف نحتاج الى إضافة UIView
    في الجزء العلوي
     
    (سبب إضافة هذا الـ UIView
    لان الـ StackView لا تستطيع إضافة لون خلفية له
    واسهل حل يكون إضافة UIView خلفه واضافة اللون الى UIView)
     
    ثانيا : نقوم بإضافة الاوبجكت بنفس ترتيب الموجود في هذه الصورة
    ثالثا : نقوم بتحديد كل من الصورة + النص واضافاتهم معا كـ StackView
    سوف نطلق عليه (StackView)
    رابعا : نحدد الـ StackView السابق + الزر وندمجهم كـ StackView اخر
    سوف نطلق عليه (BigStackView)
     
    هيكل الواجهة سوف يكون بهذا الشكل :

     
    لاحظ بأني جعلت الـ View الذي باسم Color
    غير مرتبط بأي View اخر
     
    السبب الفعلي هو لاني اضفته بعد ما انتهيت من تنفيذ المشروع !
     
    وأيضا سابقا واجهة مشكله عند جعل الـ StackView بداخل الـ UIView
    الازار لم استطع الضغط عليها ، لذا يفضل جعله بالشكل السابق
     
    شكل الـ Storyboard سوف يكونب الشكل التالي :
     

     
    الان سوف نضيف القيود بالشكل التالي :
     
    BigStackView:

     
    profileImage: (الصورة)

    NameLable: (النص)

    FollowImage: (الزر)

     (الفيو سوف نضيف قيود وأيضا لا تنسى إعطائه لوناً)UIView:

    TableView: (الجدول)

     
    لا نحتاج الى إضافة قيود الى الـ StackView الصغير (الذي يحتوي على الصورة والنص)
     
    بداية سوف تقوم بإضافة بيانات الجدول كما تريدها
     
    ومن ثم سوف تقوم بإضافة كل من StackView
    و bigStackView الى ملف الاكواد
     
    وأيضا سوف نقوم بإضافة قيود الـ profileImage التي وضعناها سابقا
    Height و Width
     
    اذا لا تعلم كيف تضيفها فشاهد هذه الصورة لكيفية إضافة قيد الـ Height :

    وطبق نفس الامر لإضافة قد الـ Width
     
    أيضا سوف نضيف قيد الـ Height الخاص بـ bigStackView الى ملف الاكواد لكي نستطيع تصغير حجمه عند السحب الى اعلى وأيضا ارجاعه الى حجمه الساسي عند السخب الى أسفل
     
    ومن ثم سوف نضيف الاكواد التالية :
     
    نضيف المتغير في بداية ملف الاكواد

     
        var lastContentOffset: CGFloat = 0 وضيفة هذا المتغير تحديد ما اذا كان المستخدم يسحب الجدول للاعلى او الأسفل (عندما يكون بقيمة 0 هذا يعني بانه لم تتم أي عملية سحب سواء للاعلى او الأسفل )
     
    ومن ثم نضيف الـ Function التالي :

     
      func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {         self.lastContentOffset = scrollView.contentOffset.y     }  
    هذا الـ Function
    وظيفته ما اذا تمت عملية سحب سوا للأعلى او للأسفل ويتم تخزين القينة في متغير lastContentOffset
     
    ومن ثم نستخدم هذا الـ Function
    لتحديد اذا كان القيمة اعلى من القيمة المخزنة في متغير lastContentOffset  معناه تم السحب الى الأعلى
    ولكن اذا كان القيمة اصغر من القيمة المخزنة في متغير lastContentOffset معناه السحب الى الأسفل

     
     func scrollViewDidScroll(_ scrollView: UIScrollView) {   if (self.lastContentOffset < scrollView.contentOffset.y) { // السحب الى اعلى ، افعل امراً ما      } else if (self.lastContentOffset > scrollView.contentOffset.y) { //  السحب الى اسفل ، افعل امراً ما} }  
    الان نبدا في المهم
     
    نضيف التالي :
    اسفل اقواس الـ // السحب الى اعلى ، افعل امراً ما

     
    UIView.animate(withDuration: 0.5,animations: { // صغرنا الصورة بتغير قيودها self.progileHight.constant = 45 self.profileWidth.constant = 45 // حولنا الى وضع افقي  bigStackView self.bigStackView.axis = .horizontal   // حولنا الى وضع افقي StackView self.StackView.axis = .horizontal     // غيرنا المسافة بين الصورة والنص الى ١٥   self.StackView.spacing = 15             // صغرنا ارتفاع الـ bigstackview                                 self.StackViewHight.constant = 60                           // طلبنا بتحديث القيود   self.view.layoutIfNeeded()   })  
    ومن ثم سوف نضيف الاكواد التالية اسفل سطر
    //  السحب الى اسفل ، افعل امراً ما
    UIView.animate(withDuration: 0.5,animations: { // ارجاع حجم الصورة الى حجمها الاصلي   self.progileHight.constant = 100     self.profileWidth.constant = 100    // ارجاع وضعهم الى الوضع العامودي  self.bigStackView.axis = .vertical   self.StackView.axis = .vertical      // ارجاع المسافة المسافة الاصلية                           self.StackView.spacing = 5                           // ارجاع ارتفاع الـ bigStackView    self.StackViewHight.constant = 160 // طلب تحديث القيود        self.view.layoutIfNeeded()      })  
    ملف الاكواد النهائي سوف يكون بالشكل التالي التالي :
     
     
    import UIKit class ViewController: UIViewController , UITableViewDelegate , UITableViewDataSource {          @IBOutlet weak var bigStackView: UIStackView!          @IBOutlet weak var StackView: UIStackView!          @IBOutlet weak var StackViewHight: NSLayoutConstraint!         @IBOutlet weak var progileHight: NSLayoutConstraint!               @IBOutlet weak var profileWidth: NSLayoutConstraint!                    var rowsNames = ["Row 0", "Row 1", "Row 2", "Row 3", "Row 4", "Row 5",                   "Row 6", "Row 7", "Row 8", "Row 9", "Row 10", "Row 11",                   "Row 12", "Row 13", "Row 14", "Row 15", "Row 16", "Row 17",                   "Row 18", "Row 19", "Row 20", "Row 21", "Row 22", "Row 23",                   "Row 24", "Row 25", "Row 26", "Row 27", "Row 28", "Row 29", "Row 20"]               var lastContentOffset: CGFloat = 0               override func viewDidLoad() {         super.viewDidLoad()          }          // MARK: - UITableViewDataSource               func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {         return rowsNames.count     }      func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {         let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)                  cell.textLabel?.text = rowsNames[indexPath.row]                  return cell     }                func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {         self.lastContentOffset = scrollView.contentOffset.y     }         func scrollViewDidScroll(_ scrollView: UIScrollView) {         if (self.lastContentOffset < scrollView.contentOffset.y) {                                             UIView.animate(withDuration: 0.5,                                    animations: {                                                                                                                                                   self.progileHight.constant = 45                                     self.profileWidth.constant = 45                 self.bigStackView.axis = .horizontal                                                                          self.StackView.axis = .horizontal                                                                    self.StackView.spacing = 15                                                                         self.StackViewHight.constant = 60                                                                          self.view.layoutIfNeeded()                                  })                                                    } else if (self.lastContentOffset > scrollView.contentOffset.y) {                                      UIView.animate(withDuration: 0.5,                            animations: {                                                     self.progileHight.constant = 100                             self.profileWidth.constant = 100                                                          self.bigStackView.axis = .vertical                             self.StackView.axis = .vertical                                                         self.StackView.spacing = 5                             self.StackViewHight.constant = 160            self.view.layoutIfNeeded()                                                           })         }     } }  
    النتيجة النهائية :

     
    مستوى المقال: متوسط
  9. بسم الله الرحمن الرحيم
    السلام عليكم ورحمة الله وبركاته
     
    في البداية ماهو Timer؟
    "هو كلاس مؤقت يقوم باطلاق تنبيه بعد أن فترة زمنية يحددها المستخدم، ليرسل رسالة محددة الى Object معين"
     
    في هذا الموضوع سنتعلم كيفية انشاء تطبيق مؤقت بسيط باستخدام كلاس Timer 
    - قم بانشاء مشروع جديد على الxCode من نوع Single View Application

     
    - سم البروجكت كما تحب وتأكد من اختيار Swift كلغة المشروع

     
    - اضف UILabel و UIButton ثلاث مرات بالتصميم المناسب لك، قمت انا بتصميم الواجهة كالتالي

     
    - الآن اضف الOutlets والActions والFunctions والVariables كالتالي

    (قمنا بتعريف variable من كلاس Timer في الاعلى لنستطيع الوصول اليه من كل الميثودز)
     
    - اضف الكود التالي لميثودي updateTimer() و startBtnTapped() 
    @IBAction func startBtnTapped(_ sender: Any) { timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true) } func updateTimer() { intCounter += 1 //Set counter in UILabel timeLbl.text! = String(format: "%02d:%02d:%02d", intCounter / 3600, (intCounter % 3600) / 60, (intCounter % 3600) % 60) } في الكود السابق قمنا باستدعاء الميثود scheduleTimer التي ستقوم بانشاء لوب يتكرر بحسب الوقت الذي نحدده ليقوم بالأكشن الذي نحدده أيضا 
    timeInterval: هو الوقت الذي نريد انتظاره قبل تكرار الأكشن مرة أخرى، قمنا بتحديده 1 لتتكرر الميثود ()updateCounter كل ثانية target: هو self حيث قمنا باختيار الView controller الحالية ViewController.swift selector: هو الأكشن الذي سنقوم به بعد كل timeInterval userinfo: اذا اردت ارسال أي data الى الميثود المستدعاة يمكنك ارسالها من هنا، لكنا قمنا بوضعها nil حاليا repeats: هل تريد تكرار الأكشن كل timeInterval أم اطلاق الأكشن مرة واحدة فقط؟ (في حالة اختيار repeats: true سيقوم الtimer بتكرار تطبيق الأكشن كل timeInterval الى مالا نهاية، أو إلى أن نوقفه يدويا) في الميثود ()updateTimer قمنا بزيادة الكاونتر قيمة 1، ثم قمنا بحساب عدد الساعات والدقائق والثواني التي لعرضها في الtimeLbl
     
    - قم باضافة الكود التالي لبقية ميثودز الأكشن:
    @IBAction func stopBtnTapped(_ sender: Any) { //Invalidate timer timer.invalidate() } @IBAction func resetBtnTapped(_ sender: Any) { intCounter = 0 stopBtnTapped(sender) timeLbl.text = "00:00:00" } في الميثود الأولى:
    ()invalidate: تستعمل لايقاف الtimer (في حالة قمت باختيار repeats: true)
    في الميثود الثانية:
    قمنا باعادة ضبط الكاونتر وايقاف المؤقت تماما، واعادة ضبط الtimeLbl
     
    - أخيرا أضفت بعد الأكواد لايقاف الأزرار عن العمل عند الضغط عليها (لكي لا يضغط المستخدم على زر Start مرتين متتاليتين)

     
    النتيجة النهائية للمشروع:

     
    الى هنا نصل الى ختام موضوعنا
    استودعكم الله الذي لا تضيع ودائعه
    مستوى المقال: مبتدئ
  10. رغم انه المفترض في هذا الموضوع اتحدث عن طرق الاستخدام الخاطئ للـ Segue وتركيزه ينصب في شرح الـ Unwind Segue الا اني سوف أقوم بشرح الامرين لجعل الموضوع اشمل
     
     
    ما هو الـ Segue ؟
    الـ Segue هو عملية الانتقال بين صفحة (View Controller) الى اخر ويتم عن طريق ربط زر او Action موجود في الصفحة الاولى الى الصفحة الأخرى
     
    اهم قاعده هنا الانتقال يكون من صفحة 1 الى صفحة 2 ولا يحصل انتقال بشكل عكسي أيضا !
    هذا من اكثر الاخطاء شيوعاً عند المطورين الجدد !
    لفهم الـ Segue بشكل صحيح سوف اشرحه على مراحل او طرق بالتدرج
     
    الطريقة الاولى :
    في هذه الطريقة فقط نقوم بالربط بين صفحة وأخرى وتستخدم الطريقة هذه بشكل نادر فقط لغرض عرض صفحة أخرى دون الحاجة لاستخدام اكواد او نقل معلومات من صفحة الاولى الى الثانية
     
    والطريقة تكمن في التالي :

     
    أولا : نقوم اول بإضافة 2 من View Controller
     
    ثانيا : نقوم بإضافة Button في صفحة 1
     
    ثالثا : نقوم بالضغط الـ Button + زر control ونسحب الـ Button الى الـ View Controller الاخر
     
    رابعا : نختار Show
     
    من امثله استخدام الطريقة هذه اذا اردت أن تعرض صفحة اتصل بنا او عن التطبيق, في هذه الصفحات فقط تعرض معلومات ولا تحتاج الا نقل معلومة من صفحة الى أخرى او تحتاج أن تقوم ببعض الامور قبل عملية الانتقال, لذا استخدام الطريقة هذه طريقة نادره
     
    الطريقة الثانية :
     
    الطريقة هذه تعتمد على موضوع الانتقال للصفحة الثانية بعد تنفيذ امر معين وبدون الحاجة الى نقل معلومات من الصفحه 1  الى الصفحة 2
     
    لكي تفهم الفكرة بشكل صحيح ، لنفترض بأنك تعمل على صفحة "التسجيل حساب جديد" راح تحتاج تكتب التالي في Function الـ Button
    تتأكد بأن المستخدم كتب جميع الحقول من اسم المستخدم والايميل والباسورد وإعادة كتابة الباسورد تتأكد بأن هناك تطابق في حقل باسورد مع حقل إعادة كتابة الباسورد تتأكد من تلبية شروط كتابة الباسورد على سبيل المثال بأن تم كتابة ٨ احرف تتصل بالسيرفر في حال تم الاتصال وحفظ بيانات المستخدم بنجاح هنا يتم الانتقال الي الصفحة الثانية !  
    هل لاحظت الفرق ؟
     
    في الطريقة الاولى الانتقال مباشرة الى الصفحة الثانية ولكن في هذه الطريقة هناك أمور كثيره تحدث بمجرد ضغط المستخدم على الـ Button وتتم الانتقال اثناء حدوث امراُ معيناً وليس بشكل مباشر !
    والطريقة تكمن في التالي :
     
    نفس خطوات الطريقة الاولى بالضبط ولكن هنا يتم إضافة مُعرف (Identifier)
     
    لاحظ الصورة التالية :

     
    وفي داخل Function الـ Button
    نكتب التالي :

     
            performSegue(withIdentifier: "toView2", sender: nil)  
    لاحظ في السطر السابق كتبنا نفس المُعرف بالضبط الذي كتبناه في حقل الـ Identifier في الـ Storyboard
     
    وأقصد هنا بالضبط  ، المسافة تفرق ، الحرف الكبتل والسمول أيضا يفرق ! بما يعني الأفضل أن تقوم بنسخه ومن ثم لصقه بدلاً من كتابته .
     
    قد تتسأل ما الفائده من المُعرف ؟
     
    المُعرف هو المسؤول عن توجهيه الـ View Controller الى  الـ View Controller  الصحيح.
     
    في تطوير التطبيقات سوف تحتاج الي عملية انتقال مختلفة من نفس الـ View Controller قد يحتوي تطبيقك مثلا على زرين كل زر يوجه الى صفحه مختلفة فهنا المُعرف يجعل الـ Xcode يوجهك الى الصفحة الصحيحة.
     
    الطريقة الثالثة :
    في هذه الطريقة اذا اردت بأن تقوم بنقل معلومات من الصفحة 1 الى صفحة 2
    الطريقة هذه أيضا نفس الطريقة السابقة من حيث انك تعطي مُعرف (Identifier) الفرق بأنه سوف تحتاج الى استخدام Function معين يقوم بوظيفة نقل المعلومات اثناء عملية الـ Segue.
    هذا هو الـ Functionn الذي سوف تحتاج الى استخدامه:
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {         }  
    اين تضعه ؟
    تضعه في الصفحة الحالية, فانت تريد نقل معلومة من صفحة 1 الى صفحة 2 فتضعه في صفحة 1
    هنا تحتاج تعرف معلومة :
     
    لنقل بيانات من صفحة الى اخرى سوف تحتاج الي متغير في الصفحة الأخرى لكن تحفظها به ولكي تتضح الصورة الان في صفحة 1 تريد نقل معلومة معينه الى صفحة 2.
     
    المعلومة هذه عباره عن String اذا سوف تقوم بإنشاء متغير من نوع String في صفحة 2 ومن ثم في داخل الـ Function السابق تستدعي المتغير وتحفظ القيمة الموجودة في صفحة1  الي المتغير في صفحة 2.
     
    نعود للشرح
     
    الان في الـ View Controller الاول نضيف Textfield فوق الـ Button ونربطه بملف الأكواد.
     
     
    ومن ثم في الـ View Controller الثاني نضيف Lable لكن قبل ذلك نضيف Class من نوع View Controller ومن ثم نحدد الـ View controller  الثاني في الـ Stroryboard
    ونختار الـ Class الذي أنشأناه بعدها نربط الـ Label بملف الاكواد
     
     
    اصبح لدينا في الـ View Controller الاول فقط Button و Textfield وفي الـ View Controller الثاني لدينا Label
     
    قبل العودة الى Function الذي اضفناه في ملف اكواد الـ View Controller الاول يتوجب علينا إضافة متغير من نوع String لذا نقوم باضافته
     
    فيصبح شكل الـ StroyBoard بالشكل التالي :

     
    ملف اكواد الـ View Controller الاول بالشكل التالي :

     
    import UIKit class ViewController: UIViewController {   @IBOutlet weak var textField: UITextField!    override func viewDidLoad() {     }             override func prepare(for segue: UIStoryboardSegue, sender: Any?) {       } }  
    ملف اكواد الـ View Controller الثاني بالشكل التالي :
     

     
    import UIKit class ViewController2: UIViewController { var  text:String?   @IBOutlet weak var label: UILabel!   override func viewDidLoad() { super.viewDidLoad()     label.text = text            } }  
    كما تلاحظ في الـ viewDidLoad اضفت السطر التالي
    label.text = text لجعل النص الذي سوف نجلبه من View Controller الاول يتم طباعته في View Controller الثاني
    نعودة الان الى Function
     
    الان نعود لملف اكواد الـ View Controller الاول
     
    ونقوم بكتابة التالي
        override func prepare(for segue: UIStoryboardSegue, sender: Any?){ if segue.identifier == "toView2" { if let vc = segue.destination as? ViewController2 {   vc.text = textField.text!      }       }   }  
    لاحظ التالي اول سطر
    segue.identifier  
    نكتب فيه مُعرف الـ segue كما قمنا بكتابته في الـ Stroyboard
     
    السطر الثاني نقوم بكتابة اسمك الكلاس الـ View Controller الثاني في حالتي اسمه ViewController2
    السطر الثالث نستدعي المتغير الذي أنشأناه في الـ View Controller الثاني ونربطه بمحتوى الـ Textfield لذا اثناء عملية الـ Segue سوف ينتقل النص الذي سوف نكتبه في حقل الـ Textfield الى الـ String الموجود في الصفحة الثانية ومن ثم في الـ View Controller الثاني سوف يتم طباعة الـ String في الـ Label
     
    نقوم بتشغيل التطبيق ونرى النتيجة :

     
    هذه الطريقة الصحيحة لنقل البيانات من View Controller الى اخر وهيا الطريقة الأكثر استخداماً
     
    الطريقة الرابعة :
     
    الطريقة هذه هيا عباره عن دمج طريقة الثانية مع الثالثة
    هناك أوقات تحتاج الى الانتقال الي View Controller اخر بعد حدث معين وفي نفس الوقت تريد فيها نقل البيانات الى الـ View Controller اخر
     
    في هذه اللحظة سوف تحتاج الى الطرقتين بحيث تستخدم
            performSegue(withIdentifier: "", sender: nil)  
    وفي نفس الوقت تستخدم
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {    }  
    الـ Xcode ذكي بما فيه الكفايه
     
    اثناء تنفيذ سطر
            performSegue(withIdentifier: "", sender: nil)  
    سوف يلاحظ وجود Function
    override func prepare(for segue: UIStoryboardSegue, sender: Any?)  
    وبالتالي سوف ينتقل اليه أولا قبل تنفيذ الـ Segue وفقط للمعلومية prepare تعني تجهيز وبالتالي الـ Function يستعد لعملية الـ segue فيتم تنفيذ ما بداخله أولا !
    الان انتهينا من جزء الـ Segue

    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------
     
     
    حان الوقت الانتقال الي موضوع الـ Unwind Segue وهو الموضوع الذي اردت التحدث عنه !
    كل شخص جديد يتعلم برمجة تطبيقات الـ iOS وتحديدا عند تعلمه الـ Segue وطريقة نقل البيانات من صفحة الى أخرى يخطئه هذا الخطأ !!
    اغلبية المبرمجين الجدد يخطئوا نفس الخطأ !
     
    ماهو الخطأ الذي يقع به الكثيرون ؟
     
    استخدام الـ Segue عند الرغبة في الرجوع الى الصفحة السابقة !
    في هذه النقطة سوف أوضح لك الخطأ ولماذا يعتبر مصيبه عند عمله !!
    افتراضاً نملك 4 صفحات و ٣ Button كل Button متصل بـ Segue ينقلك الى الصفحه الأخرى
     
    كما في الصورة التالية :
     

     
    قد تتسأل ماهي المشكلة في الصورة السابقة ؟
     
    الحقيقة هيا لحد الان لا توجد مشكلة !
    لكن في الصورة التالية تكمن المشكلة !
    المشكلة تكمن هنا عندما ينشأ المبرمج زر للرجوع الى الصفحة الرئيسية
     
    كما في الصورة التالية :
     

     
     
    ماذا فعل هنا ؟
     
    قام بربط زر الرجوع الى صفحة رقم 0 عن طريق الـ Segue !!
    هذا هو الخطأ الذي اردت التحدث عنه !
     
    هناك 3 مشاكل تسببه هذه المشكلة!
     
    أولا : كثرة الـ Segue ذهابا وعوده تسبب مشكله لذا المطور في انه لا يعلم هذا الـ Segue مرتبط بأي View Controller ؟
    ثانيا : مشاكل في الأداء واستهلاك موارد الجهاز بما يسبب بطئه عند استخدام التطبيق !!
    ثالثا : مشاكل في Auto Layout ! ، من ضمن المشاكل التي تسببها ، مشاكل في الـ Auto Layout من الامور التي سوف تلاحظها رغم وضعك القيود بشكل صحيح الا انه لايزال يظهر خطأ في القيود !
     
     
    لستُ مقتنعاً ؟
     
    سوف أقوم الان بتشغيل التطبيق واستخدامه ذهاباً وعودة
     
    شاهد الصورة التالية :

     
    الان قم بالتالي ، لا تقوم بإيقاف تشغيل المشروع
     
    واذهب لهذه الخانة :

     
    وحرك باستخدام الماوس لملاحظة هيكل المشروع
     
    لاحظ الصورة التالية :

     
    نقوم الان بعمل Debug لهيكل المشروع من ناحية واجهة المستخدم
     
    هل لاحظت كثرت الطبقات ؟
     
    المفترض أن تظهر فقط 5 صفحات 4 صفحات خاصة بالـ View Controller والصفحه 5 للزر الموجود في اخر صفحة بحكم اني قمت بالتوقف عندها ! ولكن الذي يظهر هو 13 طبقة !
     
    ما السبب ؟؟
     
    السبب هنا بسبب طريقة ربط الـ Segue
     
    ذكرت في بداية الموضوع الـ Segue يذهب من الصفحة 1 الى الصفحة 2 وليس العكس !
    عند عمل العكس هذا ما سوف يحدث !
     
    السبب عند عمل هذا الخطأ يقوم النظام على انشاء الصفحة مره أخرى وأخرى وأخرى كل مره يتم الضغط على زر الرجوع يتم انشاء الصفحة مره أخرى والنتيجة هي استهلاك موارد الجهاز !!
    الان قبل أن اشرح الطريقة سوف انفذ الطريقة الصحيحة ومن ثم سوف اشرحها بعد تغير الطريقة الى الطريقة الصحيحة
     
    لاحظ الصورة التالية في الـ Stroyboard:

     
    هل لاحظت ؟
    لايوجد Segue ، للعودة لصفحة الرئيسية !
     
    الان لاحظ الصورة التالية :

     
    ومن ثم لاحظ هيكل المشروع :
     

     
    اذا عملت مقارنه بين الصورتين هذه والصورتين السابقة
     
    سوف تلاحظ امرين
     
    أولاً : عند الضغط على زر العودة الصفحة الحالية تنزل الى الأسفل وتظهر الصفحة الرئيسية ! ، في حين عند عمل الطريقة الخاطئة تظهر الصفحة الرئيسية من الأسفل الى الأعلى !!
     
    ثانياً : هيكل المشروع اصبح 5 فقط ! لم يتغير ولم يزداد عدده !
     
    لماذا ؟
     
    عند عمل الطريقة الخاطئة ، تقوم على انشاء الصفحة من جديد في كل مره فكل ما زاد وقت استخدامك التطبيق زادت عدد الصفحات وزاد استهلاك موارد الجهاز في حين عند عمل الطريقة الصحيحة ، تقوم على اغلاق الصفحة الحالية والعودة للصفحة التي تريدها ، وبالتالي لا يوجد أي زياده !
     
    بعد فهم الفكرة واتضاح الفرق
     
    نعود الى شرح الطريقة الصحيحة :
     
    لكن قبلها اريد توضيح نقطه معينه اذا اردا الرجوع الصفحة السابقة السابقة هناك 3 طرق
     
    الطريقة الاولى : استخدم هذا السطر بداخل اقواس الـ Button
    dismiss(animated: true, completion: nil)  
    الزر ذا سوف يرجعك الى الصفحة السابقة فقط بما يعني اذا كنت في صفحة 3 سوف يرجعك الى صفحة 2 لا يمكنك العودة الى صفحة 0 او أي صفحة أخرى
     
    الطريقة الثانية : عند استخدام الـ Navigation Bar
    سوف يظهر زر العودة للصفحة السابقة بشكل تلقائي وأيضا مثل الطريقة الاولى سوف تعود فقط الى صفحة السابقة يمكن ملاحظتها في جميع تطبيقات النظام مثال تطبيق الاعدادات عندما تذهب لقسم عام او General سوف تلاحظ وجود زر في الأعلى يرجعك الى صفحة الرئيسية للإعدادات ، هذه تتم بشكل تلقائي بدون تدخل منك
     
    معلومة :
     
    لاحظ الصورة التالي :
     

     
     
    سوف تلاحظ عند استخدام الـ Navigation Bar واختيار نوع Show سوف تكون عملية الانتقال الـ Segue من اليمين الى اليسار والعوده سوف تكون العكس في حين اذا اخترت Present Modally سوف يظهر من الأسفل الى الأعلى والعكس عندها يعود للصفحة السابقة ! كما الحال في تطبيقنا
     
    لكن بدون استخدام الـ Navigation Bar الـ Show يظهر كالـ Present Modally
     
    الطريقة الثالثة :
     
    الطريقة هذه تدعى Unwind Segue
     
    ما الذي يميزها ؟
    الذي يميزها هو التالي :
     
    أولا : يمكنك العودة الى أي صفحة تريدها وليس ملزماً بالعودة الى الصفحة السابقة فقط !
    بما يعني كما هو حال مثالنا سوف يمكنك العودة من صفحة 3 الى صفحة 0 بشكل مباشر
     
    ثانيا:
    يمكنك ارجاع بيانات من الصفحة الحالي الى الصفحة السابقة !
    بما يعني سوف تستطيع ارجاع بيانات موجوده في صفحة 3 الى صفحة 0 او أي صفحة تريدها !!
    معلومة :

    الـ Unwind Segue تعني فك الـ Segue وبالتالي يجب أن يكون هناك Segue لتستخدم هذه الطريقة !
    اذا استخدمتها بدون عمل Segue مسبقاً بين 2 من الـ View Controller او اكثر ، سوف يسبب Crash للتطبيق
     
    الان نبدأ في شرح الطريقة:
     
    كما شرحت الـ Segue سوف اقسم الـ Unwind Segue الى عدة طرق
     
    طريقة الأولى :
    ترغب فقط بالعوده الى صفحة معينه بدون ارجاع أي بيانات
    وبالتالي تحتاج تفهم هذه النقطة :
    اذا رغبت بالرجوع الى صفحة معينه سوف تحتاج الى كتابة كود في صفحة التي تريد الرجوع لها هنا نحن نريد العوده من أي صفحة الى الصفحة الرئيسية View Controller 0
    فسوف نقوم بكتابة الكود التالي :
    @IBAction func unwindToHome(segue:UIStoryboardSegue) {     }  
    في ملف اكواد  View Controller 0
     
    ملاحظة :
    -لا تحتاج الى كتابة أي اكواد في الداخل الاقواس ! على الأقل في الوقت الحالي !
    - تستطيع تسمية الـFunction بأي اسم تريده ، انا قمت بتسميته unwindToHome
     
    ومن ثم ننتقل الى الـ Storyboard ونفعل التالي مع صفحات 1 و  2و 3
    شاهد الصورة :

     
    فقط هذا كل ما نحتاج الى فعله !
    قم بتشغيل التطبيق وسوف تجده يعمل بمجرد الضغط على زر Return to Home سوف تجده يعود الى الصفحة الرئيسية وهيا View Controller 0
     
    الطريقة الثانية :
    ماذا اذا اردت تنفيذ امراً معيناً قبل ان يحدث الـ Unwind Segue ؟
    تحتاج الطريقة دي في بعض الحالات ، مثلا صفحة Login المستخدم بعد ما يكتب اسم المستخدم وكلمة السر ويضغط زر Login سوف تحتاج الى الاتصال بالسيرفر وتتأكد انه المستخدم موجود في قاعدة بياناته وبعد التأكد ، تغلق الصفحة باستخدام  Unwind Segue وتحوله الى الصفحة الرئيسية
    معلومة :
    ما سبق ذكره مجرد مثال ، لأنه تستطيع أيضا استخدام سطر
    dismiss(animated: true, completion: nil)  
    لأغلاق الصفحة فالمثال السابق يعتبر كطريقة أخرى لتنفيذ نفس الأمر, على أي حال الطريقة مشابه لطريقة الـ Segue من حيث تحتاج الى إعطاء مُعرف.
    نعود للشرح :
     
    اهم نقطه هنا هو فصل الربط الذي عملته في الطريقة الاولى بين الـ Button و Exit ولكن لا تحذف الـ  Function الذي كتبناه في View Controller 0
     
    @IBAction func unwindToHome(segue:UIStoryboardSegue) {}  
    في هذه الطريقة سوف نربط الـ Viewcontroller نفسه مع الـ Exit
     
    شاهد الصورة التالية :
     

     
     
    ومن ثم سوف نعطي للـ Unwind Segue مُعرف
    شاهد الصورة التالية :
     

     
    قمت باعطاء مُعرف home الان قم بربط الـ button مع ملف الاكواد ومن ثم استخدم نفس السطر الذي استخدمناه في الـ Segue
    performSegue(withIdentifier: "home", sender: nil)  
    بالطريقة هذه تستطيع عمل أي امر تريده قبل حدوث الـ Unwind Segue
     
    الطريقة الثالثة :
     
    في هذه الطريقة الامر عائد اليك ، يمكنك عمل الطريقة الاولى وتنفذ الطريقة هذه معها او استخدام الطريقة الثانية مع هذه الطريقة.
    فالطريقة الثالثة تشرح طريقة نقل البيانات من الصفحة 3 الى صفحة 1
     
    بنفس درجة حاجتك الى استخدام الى الـ Segue لنقل البيانات أيضا سوف تحتاج استخدام Unwind Segue لنقل البيانات
     
    من امثله استخدام الـ Unwind Segue لنقل البيانات, تطبيقات المحادثه مثل الـ Whatsapp عند استخدامك للتطبيق لأول مره يطلب منك اختيار رمز دولتك فعند الضغط عليه يوجهك لصفحة تختار فيها دولتك ومن ثم عند اختيار دولتك يعود الى صفحة كتابة رقم جوالك هنا حدث Unwind Segue بحيث تم نقل رمز الدولة من صفحة أخرى الى صفحة السابقة
     
    نعود للشرح :
     
    هل تذكر ماذا فعلنا عند نقل معلومات او بيانات من صفحة 1 الى الصفحة 2 باستخدام الـ Segue ؟ الطريقة مشابهه لحد ما ! نحن نريد نقل بيانات من صفحة 3 الى صفحة 0 لذا قبل أنا نبدأ سوف نفعل التالي:
    سوف نضيف Textfield في الصفحة 3 ونضيف Label في صفحة 0 الي هيا الصفحة الرئيسية الذي نريد الرجوع اليها ونقوم بربطهم بملف الاكواد
    شاهد الصورة التالية :

     
    الان حان الوقت للكتابة في Function
    @IBAction func unwindToHome(segue:UIStoryboardSegue) { }  
    الان كما قلنا سابقا نريد نقل النص من الـ Textfield الموجود في View Controller 3 الى الـ Label الموجود في View Controller 0 لذا نقوم بكتابة التالي :
     
     @IBAction func unwindToHome(segue:UIStoryboardSegue) { if segue.identifier == "home" {       let vc = segue.source as! ViewController3 label1.text = vc.text1.text       }      }  
    اول شيء نقوم بالتأكد من اسم المُعرف في حال كنت تتبع الطريقة الاولى لن يكون هناك اسم معرف الا اذا وضعته باختيارك لذا يمكنك حذف هذا السطر
    if segue.identifier == "home" {}  
    وحتى أيضا اذا اتبعت الطريقة الثانية لن تحتاج هذا السطر! الا اذا كنت تريد ارجاع بيانات من صفحتين فأكثر فهنا يتوجب عليك التمييز بينهم عن طريق التأكد من اسم المُعرف على أي حال اذا لاحظت فالكود مشابه جدا من طريقة الـ Segue .
    في الـ Segue نقوم بكتابته بداخل Function يسمى prepare وهنا بداخل Function الـ Unwind وأيضا في الـ Segue نكتب
    segue.destination as اسم الكلاس الذي نريد الانتقال اليه وفي الـ Unwind Segue نكتب
    segue.source as اسم الكلاس الذي سوف نعود منه لذا تقريبا نفس الفكرة الاختلاف في كلمة destination و source
    اخيراً قم بتشغيل التطبيق
     
    وشاهد النتيجة في الصورة التالية :

     
    وبكذا انتهينا من هذا الموضوع
     
    في هذا الموضوع فهمت الطريقة الصحيحة لنقل البيانات بين الـ View Controllers وماهي الاستخدامات الخاطئة والفريق بين Segue و Unwind Segue.
    قد تتسأل هل هذه هيا الطرق الوحيدة لنقل البيانات ؟
    الاجابه هيا لا ، ما تم شرحه في الموضوع هو طرقتين لنقل البيانات Segue و Unwind Segue وهما اشهر واكثر طرقتين لنقل البيانات بين الـ View Controllers استخداماً
    لكن هناك طرق أخرى أيضا !
     
    مستوى المقال: متوسط
  11. بسم الله الرحمن الرحيم
     
    سنتعرف في هذا الدرس على طريقة انشاء تطبيق يدعم تعدد اللغات بحسب لغة الجهاز، سنعتمد اللغتين العربية والانجليزية في هذا التطبيق.
     
    سنبدأ أولا باضافة جميع اللغات التي نريد دعمها في تطبيقنا
    نذهب الى Project Navigator

    ثم نقوم باضافة اللغات المطلوبة من الأسفل

    نلاحظ تقسيم الملفات الى لغتين كما في الصورة

    االآن سنقوم بانشاء ملف من نوع String لنخزن فيه جميع الStrings باللغتين العربية والانجليزية
    اضغط على File > New > File واختر النوع String كما في الصورة

    تأكد من تسمية الملف باسم "Localizable" تماما كما هو ليتعرف عليه الxCode بشكل صحيح
    الآن نختار الملف الجديد من اليسار، ومن قائمة الFile Inspector نضغط على Localize ونختار اللغة الانجليزية مبدئيا

    الآن نختار اللغة العربية أيضا لنقوم بتقسيم ملف الLocalizable.strings الى لغتين

    قم باضافة الStrings المطلوبة الى الملفين بالطريقة التالية

    ليقوم الxCode بعمل Build تأكد من:
    كتابة الkey بشكل موحد بين اللغتين العربية والانجليزية وضع علامة ال= بين الkey والvalue كتابة الvalue بين علامتي تنصيص "" انهاء السطر بSemi-Colon ;  
     
    لنرى طريقة استدعاء الStrings  من ملف Localizable قمت بعمل الواجهة البسيطة التالية:

    وقمت بربط الOutlets والActions كالتالي:

     
    الآن اضف الكود التالي في زر اللغة الانجليزية:
    let path = Bundle.main.path(forResource: "en", ofType: "lproj") let bundle = Bundle.init(path: path!)! as Bundle nameLbl.text = bundle.localizedString(forKey: "WebsiteName", value: nil, table: nil) والتالي لزر اللغة العربية:
    let path = Bundle.main.path(forResource: "ar", ofType: "lproj") let bundle = Bundle.init(path: path!)! as Bundle nameLbl.text = bundle.localizedString(forKey: "WebsiteName", value: nil, table: nil)  
    في السطر الأول: قمنا بتعريف الpath الذي سيوصلنا لكل من ملفات اللغة العربية والانجليزية
    (يمكنك معرفة الResource والType من قائمة الFile Inspector عند الضغط على ملفي العربية والانجليزية)

    في السطر الثاني قمنا بتعريف كل من ملفات اللغتين كVariable يحمل الاسم bundle
    في السطر الثالث قمنا باستدعاء الString المطلوب باستخدام الميثود localizedString(_:String,_:String,_:Strint( 
     
    والآن عند تجربة البرنامج نرى النتيجة التالية (صورة GIF):
     

     
    حتى الآن كنا نحدد ملف اللغة الذي نريد استدعاء الString منه
    والآن سنقوم بتغيير النص اعتمادا على لغة الجهاز باستخدام زر واحد فقط
    لنرى ذلك قمت بتغيير الStoryboard ليحتوي على زر واحد فقط، وقمت باضافة الoutlets والactions كالتالي:


     
    والآن قم باضافة السطرين التاليين للميثود viewDidLoad()
    let btnTitle: String = NSLocalizedString("ButtonText", comment: "") languageBtn.setTitle(btnTitle, for: .normal) والسطر التالي للميثود الخاصة بالbutton action
    nameLbl.text = NSLocalizedString("WebsiteName", comment: "") في هذه الاكواد، قمنا باستعمال الميثود NSLocalizedString لاستدعاء الString المناسب بحسب لغة الجهاز
    لتجربة البرنامج سنقوم بتشغيله على جهاز بلغة انجليزية، ثم سنحولها للعربية ونعيد التجربة
    (صورة GIF):


     
     
    وصلنا الى ختام الدرس، أستودعكم الله الذي لا تضيع ودائعه.
    مستوى المقال: متوسط
  12. ماهو ال Model View Controller (MVC)؟
     
    MVC هو مبدأ او نموذج معماري architectural pattern يستخدم للتعامل مع واجهات المستخدم في تطبيقات iOS.
    هذا المبدا مهم ان تحاول فهمه لانه اساس برمجة تطبيقات ال iOS . فعندما تبدأ برمجه مشروعك عليك تقسيمه الى ثلاثة اقسام كالتالي
    Model : عباره عن مجموعة البيانات أو data في تطبيقك . مثلا لو لدينا تطبيق لعرض موديلات السيارات.  كل المعلومات عن السياره مثل الماركه, اللون وغيرها تعتبر بيانات ويتم تخزينها في كلاس.
    View : عباره عن الواجهه الظاهره لمستخدم تطبيقك.
    في xcode تعتبر  ال view هي العناصر المستخدمه في  storyboard  واللتي نقوم بربطها بالكود مثل UILabel, UIView and UIImage.
    Controller : هو الرابط  او حلقة الوصل بين ال model & view اي بين البيانات والواجهات . فهو يقوم بتزويد ال  view  بالبيانات اللتي تحتاجها من model. ويقوم بتحديث ال model حين يدخل  المستخدم بيانات جديده الى ال view
    هذا الجزء يعتبر الدوال او method او ال action المستخدمه في برمجه العناصر كالازرار مثلا.
     
    الان نستعرض مثال بسيط لشرح الفكره وتعميق فهمها.( من هنا تستطيع البدء والبحث عن المزيد عن هذا المفهوم وتطبيقه).
    لنفرض ان لدينا مشروع  يستعرض ماركة و لون السياره . الان وفقا لهذا المفهوم سنقسم كالتالي
      Model: ننشئ class  نسميه car ونضع فيه بيانات السياره (ماركه brand, لون  color).

    View: هي واجهة المستخدم سننشئها كالتالي:

     
    وننشئ outlet لكل من ال labels كالتالي

     
    Controller: وهي الاوامر المستخدمه لربط عناصر الواجهه بالبيانات.
    - انشأنا object  اسمه car1 من كلاس car حتى نتمكن من الوصول الى خصائص الكلاس (الماركه و اللون ).
    - باستخدام ال object المسمى car1 وصلنا الى خاصية brand ووضعنا فيها قيمه lexus . وخاصية color وضعنا فيها قيمه Red
    - الان مرحله الربط بين  عناصر الواجهه والبيانات فالامر Brand.text يشير الى ان نضع في ال label الموجود في الواجهه  النص الموجود في خاصية Car1.Brand وهو في هذه الحاله lexus. نفس الامر لعنصر اللون.  
     

     
    الشكل النهائي للكود في الصوره التاليه لكن هناك ملاحظه يفضل أن تنشئ ملف سويفت منفصل لتضع فيه كلاسات ال model .

     
    الان نقوم بتشغيل التطبيق لرؤيه النتيجه
     

     
    اتمنى أنني وفقت في شرح هذا المفهوم المهم بطريقه سهله وبسيطه ـ شكرا لكم ولعالم البرمجه
     
     
    مستوى المقال: متوسط

عالم البرمجة

عالم البرمجة مقالات برمجة و دورات مجانية لإحتراف البرمجة هدفنا تبسيط البرمجة ونشرها بيد الكل بشكل ممتع ومتطور ومحدث بإستمرار لمواكبة جديد تطورات البرمجة الحديثة و المتقدمة بدون مقابل