باسل العمودي

Members
  • عدد منشوراتي

    12
  • تاريخ الإنضمام

  • تاريخ اخر زياره

السمعه بالموقع

16 Good

عن العضو باسل العمودي

  • الرتبه
    مبدع مثابر

اخر الزوار

429 زياره للملف الشخصي
  1. مرة فترة طويلة على تدوينات المتعلقة بالـ 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() }) } } } النتيجة النهائية :
  2. مرة فترة طويلة على تدوينات المتعلقة بالـ 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() }) } } } النتيجة النهائية :
  3. رغم انه المفترض في هذا الموضوع اتحدث عن طرق الاستخدام الخاطئ للـ 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 استخداماً لكن هناك طرق أخرى أيضا !
  4. رغم انه المفترض في هذا الموضوع اتحدث عن طرق الاستخدام الخاطئ للـ 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 استخداماً لكن هناك طرق أخرى أيضا !
  5. من أكثر الأسباب التي تؤدي الى زيادة معدل تقييم تطبيقك في متجر التطبيقات هو عن طريق سؤال المستخدم من داخل التطبيق ! هناك طرق كثيره ومكتبات متنوعه لفعل ذلك ووظيفتها تكمن في سؤال المستخدم ما اذا اعجبهم التطبيق ويريد كتابة مراجعة له ، فيتم تحويلهم الى متجر التطبيقات ولكن اليوم سوف نتحدث عن API آبل اضافته في تحديث iOS 10.3 يطلق عليه SKStoreReviewController الفكرة في هذا الـ API هو سؤال المستخدم من داخل التطبيق بدون الحاجة الى تحويلهم الى متجر التطبيقات ! لكن العيب الوحيد هو عدم المقدرة على جعلهم يكتبوا مراجعة للتطبيق ، فقط يستطيعوا تقييم التطبيق عن طريق النجوم . وطبيعة الحال هناك عدة شروط : يجب الا يستخدم الـ API بداخل ViewDidLoad أو ViewWillApper او غيرهم بدون وجود Logic ، فانت لا تريد أن يقيم المستخدم التطبيق بمجرد فتحه لأول مره وهو لم يستخدمه بعد ! يجب ان لا يستخدم الـ API بداخل Button او يتطلب أي Action من المستخدم ، قد يسبب ذلك في رفض تطبيقك اذا وضعته بداخل Action ! تذكر بأن كل شيء يتم تلقائيا اعتماداً على النظام ، من حيث ظهور رسالة طلب التقييم من عدمها ! لهذا السبب وجد الشرط السابق أهم شرط هو رسالة التقييم سوف تظهر فقط 3 مرات في السنه لكل مستخدم . اثناء فترة التطوير الرسالة سوف تظهر بشكل دائم عند ارسال تطبيقك كنسخه تجربيه باستخدام TestFlight الرسالة لن تظهر ! ، بمعنى أخرى سوف تظهر فقط عند نشر تطبيقك في متجر التطبيقات . ومن النصائح اذا اردت تقييم إيجابي للتطبيقك ، اطلب ظهور رسالة التقييم اذا فعل المستخدم امراً إيجابياً ! ولا تقاطع المستخدم عند عمل مهمه معينه !! ، ولكن اسأله بعد الانتهاء من مهمة محددة لذا من الأمثله : اذا التطبيق متجر الكتروني اطلب رسالة التقييم بعد إتمام الشراء. اذا كان التطبيق لعبة ، اطلب رسالة التقييم بعد تحقيق سكور عالي في اللعبة أو بعد انتهاء من مرحلة او عدة مراحل في اللعبة . اسأله بعدد عدد مرات معينه من استخدام التطبيق وليس من أول مره ! بحيث تسأل المستخدم لتقييم تطبيقك في وقت يكون في مزاج جيد ! اذا كيف يتم استخدامه ؟ كما ذكرت سابقا الأمر بيد المطور بصورة مختصرة : الذي يتطلبه هو فقط استدعاء import StoreKit في داخل الـ ViewController الذي يريد ظهور الرسالة عليه ومن ثم استخدام السطر التالي ، في أي مكان يرده المطور SKStoreReviewController.requestReview() بصورة افضل هو حساب عدد مرات فتح التطبيق وإظهار الرسالة بعد عدد مرات محدده من دراسة قراءتها بأن افضل وقت هو بعد 5 مرات ويفضل بعد 10 مرات ولكن ليس أقل من ذلك ، بهذا الوقت بعد 5 مرات او 10 مرات تكون متأكد بأن المستخدم يستخدم التطبيق باستمرار . اذا كيف تحسب وقت استخدام المستخدم للتطبيق ؟ عن طريق الاستفادة من UserDefaults اذا لا تعلم ماهو UserDefaults فهو طريقة لحفظ البيانات بشكل دائم ويستخدم مع البيانات البسيطة كحفظ رقم Integer مثلا او حفظ حالة الـ Switch اذا مفعل او مغلق الخ وبالتالي الفكرة هيا بوضع عدد محدد كمتغير وفي هذا المثال سوف نضع المدة 4 لأننا نريد أن تظهر رسالة التقييم بعد خامس مره ، ولأنه العد يبدأ من 0 فالرقم 4 = خامس مره 0 , 1 , 2 , 3 , 4 ونضع متغير اخر يحسب عدد مرات فتح التطبيق (ViewController) ومن ثم نعمل مقارنه اذا وصلت عدد المرات الى 5 مرات تظهر الرسالة اذا لم يوصل نزود قيمة المتغير ونحفظه في UserDefaults الكود يصبح بالشكل التالي : import UIKit import StoreKit class ViewController: UIViewController { let minimumRunCount = 4 let userDefaultsKey = "minimumRunCountUserDefaultsKey" override func viewDidLoad() { super.viewDidLoad() if ShowStoreReview() { SKStoreReviewController.requestReview() } } func ShowStoreReview() -> Bool { let count = UserDefaults.standard.integer(forKey: userDefaultsKey) if count >= minimumRunCount { return true } else { UserDefaults.standard.set((count + 1), forKey: userDefaultsKey) } return false } } اذا كما ذكرنا سابقا في البداية عرفنا الحد الادنى قبل ظهور الرسالة في minimumRunCount عرفنا الـ Key للـ userDefault يمكنك كتابة أي شي تريده بالنسبة الى UserDefaults.standard.integer القيمة سوف تكون 0 بشكل تلقائي عند استخدامه وهذا ما نريده لذا قيمة count سوف تكون 0 في اول مره ومن ثم عملنا مقارنة اذا كانت قيمة count اكبر او يساوي قيمة minimumRunCount تظهر ترجع قيمة true وبالتالي تظهر الرسالة اذا لم تكون تساوي او اكبر من قيمة minimumRunCount نزود واحد على قيمة count ونحفظها في userDefault لذا في المره الثانية يفتح فيها التطبيق تكون قيمة count بـ 1 عوضاً عن 0 وهكذا وعندما توصل لقيمة 4 تظهر الرسالة صورة الرسالة : هذه من الأمثلة التي تستطيع الاستفادة منها تستطيع تحسينها حسب احتياجك اريد أن أوضح نقطة اخيره وهيا الـ SKStoreReviewController فقط يظهر لمستخدم نظام 10.3 فأعلى لذا تستطيع إضافة السطر التالي في ViewDidLoad if #available(iOS 10.3, *) { SKStoreReviewController.requestReview() } else { وهنا ضع الطريقة القديمة لتحويله الى متجر التطبيقات// } فيصبح الكود ViewDidLoad النهائي بالشكل التالي : override func viewDidLoad() { super.viewDidLoad() if ShowStoreReview() { if #available(iOS 10.3, *) { SKStoreReviewController.requestReview() } else { } } } واخيراً اريد أن اذكر بأن هناك مطور جرب استخدام SKStoreReviewController في تطبيقه ، وادى ذلك الي حصوله على 200 تقييم في خلال 8 أيام ! لذا ينصح بشده باستخدام SKStoreReviewController في تطبيقاتكم فهناك عدد كبير من التطبيقات في المتجر لا تملك أي تقييم ! لذلك آبل إضافة هذا الـ API في نظامها
  6. من أكثر الأسباب التي تؤدي الى زيادة معدل تقييم تطبيقك في متجر التطبيقات هو عن طريق سؤال المستخدم من داخل التطبيق ! هناك طرق كثيره ومكتبات متنوعه لفعل ذلك ووظيفتها تكمن في سؤال المستخدم ما اذا اعجبهم التطبيق ويريد كتابة مراجعة له ، فيتم تحويلهم الى متجر التطبيقات ولكن اليوم سوف نتحدث عن API آبل اضافته في تحديث iOS 10.3 يطلق عليه SKStoreReviewController الفكرة في هذا الـ API هو سؤال المستخدم من داخل التطبيق بدون الحاجة الى تحويلهم الى متجر التطبيقات ! لكن العيب الوحيد هو عدم المقدرة على جعلهم يكتبوا مراجعة للتطبيق ، فقط يستطيعوا تقييم التطبيق عن طريق النجوم . وطبيعة الحال هناك عدة شروط : يجب الا يستخدم الـ API بداخل ViewDidLoad أو ViewWillApper او غيرهم بدون وجود Logic ، فانت لا تريد أن يقيم المستخدم التطبيق بمجرد فتحه لأول مره وهو لم يستخدمه بعد ! يجب ان لا يستخدم الـ API بداخل Button او يتطلب أي Action من المستخدم ، قد يسبب ذلك في رفض تطبيقك اذا وضعته بداخل Action ! تذكر بأن كل شيء يتم تلقائيا اعتماداً على النظام ، من حيث ظهور رسالة طلب التقييم من عدمها ! لهذا السبب وجد الشرط السابق أهم شرط هو رسالة التقييم سوف تظهر فقط 3 مرات في السنه لكل مستخدم . اثناء فترة التطوير الرسالة سوف تظهر بشكل دائم عند ارسال تطبيقك كنسخه تجربيه باستخدام TestFlight الرسالة لن تظهر ! ، بمعنى أخرى سوف تظهر فقط عند نشر تطبيقك في متجر التطبيقات . ومن النصائح اذا اردت تقييم إيجابي للتطبيقك ، اطلب ظهور رسالة التقييم اذا فعل المستخدم امراً إيجابياً ! ولا تقاطع المستخدم عند عمل مهمه معينه !! ، ولكن اسأله بعد الانتهاء من مهمة محددة لذا من الأمثله : اذا التطبيق متجر الكتروني اطلب رسالة التقييم بعد إتمام الشراء. اذا كان التطبيق لعبة ، اطلب رسالة التقييم بعد تحقيق سكور عالي في اللعبة أو بعد انتهاء من مرحلة او عدة مراحل في اللعبة . اسأله بعدد عدد مرات معينه من استخدام التطبيق وليس من أول مره ! بحيث تسأل المستخدم لتقييم تطبيقك في وقت يكون في مزاج جيد ! اذا كيف يتم استخدامه ؟ كما ذكرت سابقا الأمر بيد المطور بصورة مختصرة : الذي يتطلبه هو فقط استدعاء import StoreKit في داخل الـ ViewController الذي يريد ظهور الرسالة عليه ومن ثم استخدام السطر التالي ، في أي مكان يرده المطور SKStoreReviewController.requestReview() بصورة افضل هو حساب عدد مرات فتح التطبيق وإظهار الرسالة بعد عدد مرات محدده من دراسة قراءتها بأن افضل وقت هو بعد 5 مرات ويفضل بعد 10 مرات ولكن ليس أقل من ذلك ، بهذا الوقت بعد 5 مرات او 10 مرات تكون متأكد بأن المستخدم يستخدم التطبيق باستمرار . اذا كيف تحسب وقت استخدام المستخدم للتطبيق ؟ عن طريق الاستفادة من UserDefaults اذا لا تعلم ماهو UserDefaults فهو طريقة لحفظ البيانات بشكل دائم ويستخدم مع البيانات البسيطة كحفظ رقم Integer مثلا او حفظ حالة الـ Switch اذا مفعل او مغلق الخ وبالتالي الفكرة هيا بوضع عدد محدد كمتغير وفي هذا المثال سوف نضع المدة 4 لأننا نريد أن تظهر رسالة التقييم بعد خامس مره ، ولأنه العد يبدأ من 0 فالرقم 4 = خامس مره 0 , 1 , 2 , 3 , 4 ونضع متغير اخر يحسب عدد مرات فتح التطبيق (ViewController) ومن ثم نعمل مقارنه اذا وصلت عدد المرات الى 5 مرات تظهر الرسالة اذا لم يوصل نزود قيمة المتغير ونحفظه في UserDefaults الكود يصبح بالشكل التالي : import UIKit import StoreKit class ViewController: UIViewController { let minimumRunCount = 4 let userDefaultsKey = "minimumRunCountUserDefaultsKey" override func viewDidLoad() { super.viewDidLoad() if ShowStoreReview() { SKStoreReviewController.requestReview() } } func ShowStoreReview() -> Bool { let count = UserDefaults.standard.integer(forKey: userDefaultsKey) if count >= minimumRunCount { return true } else { UserDefaults.standard.set((count + 1), forKey: userDefaultsKey) } return false } } اذا كما ذكرنا سابقا في البداية عرفنا الحد الادنى قبل ظهور الرسالة في minimumRunCount عرفنا الـ Key للـ userDefault يمكنك كتابة أي شي تريده بالنسبة الى UserDefaults.standard.integer القيمة سوف تكون 0 بشكل تلقائي عند استخدامه وهذا ما نريده لذا قيمة count سوف تكون 0 في اول مره ومن ثم عملنا مقارنة اذا كانت قيمة count اكبر او يساوي قيمة minimumRunCount تظهر ترجع قيمة true وبالتالي تظهر الرسالة اذا لم تكون تساوي او اكبر من قيمة minimumRunCount نزود واحد على قيمة count ونحفظها في userDefault لذا في المره الثانية يفتح فيها التطبيق تكون قيمة count بـ 1 عوضاً عن 0 وهكذا وعندما توصل لقيمة 4 تظهر الرسالة صورة الرسالة : هذه من الأمثلة التي تستطيع الاستفادة منها تستطيع تحسينها حسب احتياجك اريد أن أوضح نقطة اخيره وهيا الـ SKStoreReviewController فقط يظهر لمستخدم نظام 10.3 فأعلى لذا تستطيع إضافة السطر التالي في ViewDidLoad if #available(iOS 10.3, *) { SKStoreReviewController.requestReview() } else { وهنا ضع الطريقة القديمة لتحويله الى متجر التطبيقات// } فيصبح الكود ViewDidLoad النهائي بالشكل التالي : override func viewDidLoad() { super.viewDidLoad() if ShowStoreReview() { if #available(iOS 10.3, *) { SKStoreReviewController.requestReview() } else { } } } واخيراً اريد أن اذكر بأن هناك مطور جرب استخدام SKStoreReviewController في تطبيقه ، وادى ذلك الي حصوله على 200 تقييم في خلال 8 أيام ! لذا ينصح بشده باستخدام SKStoreReviewController في تطبيقاتكم فهناك عدد كبير من التطبيقات في المتجر لا تملك أي تقييم ! لذلك آبل إضافة هذا الـ API في نظامها
  7. في هذا الجزء سوف ننتقل الي نوع اخر مختلف عنما اعتدنا عليه سابقا سوف ننتقل من Animation الـ View الى Animation الـ Layer بصيغة أخرى هذا الموضوع هو بداية الدخول الى الـ Core Animation هناك فروقات كثيره بينهم ! ابرزها : كل امر فعلته باستخدام الـ UIView Animation تستطيع عمله باستخدام الـ Layer Animation ولا يمكن فعل العكس لماذا ؟ لانه الـ Layer يكون خلف الـ View وبتالي عند تحريك الـ View مثلا انت أيضا تحرك الـ Layer بما يعني أي امر تستطيع عمله بالـ View أيضا تستطيع عمله بالـ Layer لانه طبقة الـ layer موجودة خلف الـ View ولكن لا يمكن عمل العكس . ملكيات الـ Layer التي يمكنك عمل لها Animation أكثر بكثير من ملكيات الـ UIView ، بمعنى اخر تستطيع فعل الكثير باستخدام Layer Animation الـ Layer Animation ينفذ باستخدام المعالج الرسومي GPU في حين الـ UIView باستخدام المعالج المركزي CPU الـ Layer Animation يمكن عمل Animation بشكل 3D و 2D في حين UIView Animation ، يمكن عمل Animation بشكل 2D فقط الـ UIView Animation اسهل استخداما من الـ Layer Animation يمكن دمج الـ Layer Animation مع الـ UIView Animation مختصر الكلام الـ UIView Animation هو نسخه مخففه من الـ Layer Animation بعد معرفة الفروقات لنبدأ الدرس : لأثبات كلامي السابق سوف نحاول عمل Animation يقوم بتحويل المربع الى دائرة باستخدام UIView Animation بكتابة الكود التالي : @IBAction func AnimationButton(_ sender: Any) { UIView.animate(withDuration: 0.3) { self.squareView.layer.cornerRadius = 61 } } ما فعلته هنا هو غيرت زوايا المربع من 0 الى 61 لماذا 61 ؟ لانه الطول والعرض للمربع هو 122 ، وبالتالي لتحويل المربع الى دائره نجعل زواية المربع تساوي أضلاع المربع قسمة 2 بما يعني 122 قسمة 2 = 61 الان نرى النتيجة في الصورة التالية : كما ترى لم يتم حدوث Animation ولكن فقط تم تحويل المربع الى دائره بشكل فجائي والسبب كما ذكرنا سابقا لا يمكنه عملAnimation للـ Layer باستخدام UIView Animationالـ اذا ماهو الـ Layer Animation الذي سوف نتحدث عنه اليوم ؟ كما الامر في UIView Animation هناك أنواع مختلفة أيضا في الـ Layer Animation هناك أنواع مختلفة النوع الذي سوف نتحدث عنه اليوم هو CABasicAnimation سوف نعود الى المربع الأزرق الذي استخدمناه في المواضيع السابقة لذا قم بإضافة "زر" وقم بربطه بملف الاكواد كـ Action باسم AnimationButton وقم بإضافة UIView واعطيه لوناً وقم بربطه بملف الاكواد باسم squareView الان سوف نبدأ في شرح الـ Layer Animation كيف يتم كتابة كود الـ Layer Animation؟ الطريقة مختلفة عن الـ UIView Animation فهيا تعتمد أولا على تعريف خصائص الـ Animation وفي الأخير في سطر واحد يتم تنفيذ الـ Animation قم بكتابة الكود التالي : @IBAction func AnimationButton(_ sender: Any) { let cornerRadiusAnimation = CABasicAnimation(keyPath: "cornerRadius") cornerRadiusAnimation.duration = 0.3 cornerRadiusAnimation.fromValue = 0 cornerRadiusAnimation.toValue = 61 squareView.layer.add(cornerRadiusAnimation, forKey: nil) } في المثال السابق : اول سطر قمنا بتعريف متغير cornerRadiusAnimation من نوع CABasicAnimation لاحظ الـ keypath تقوم بالكتابة فيه نوع الـ Animation الذي تريد عمله هنا نحن نريد عمل Animation للزوايا او بصيغة أخرى الـ cornerRadius من ثم قمنا بكتابة الـ duration الوقت المراد عمل في الـ Animation بالثانية (مدة الـ Animation) ومن ثم نحدد القيمة الاولية و القيمة النهائية بما يعني في اول قيمة fromValue نحدد القيمة الحالية قمنا بكتابة 0 لأننا نعلم بأنه القيمة الاولية هيا 0 وبالتالي يظهر الشكل بشكل مربع ! toValue هيا القيمة التي نريد الوصول اليها نحن نريد ان تكون قيمة الـ cornerRadius تساوي 61 وبالتالي يتغير الشكل من مربع الى دائره الان انتهينا من وضع الخصائص الخاصة بالـ Animation يبقى التنفيذ في اخر سطر نفذنا الـ Animation عن طريق استخدام add squareView.layer.add(cornerRadiusAnimation, forKey: nil) الان قم بتشغيل الكود وسوف تلاحظ الـ Animation عند تغيره من مربع الى دائره كما في الصورة التالية : لكن هناك مشكلة !! عند الانتهاء من الـ Animation تم إعادة الـ View الى الشكل المربع ولم يبقى على شكل الدائره لماذا ؟؟ لتوضيح المشكلة بأبسط صورة ممكنه عند عمل الـ Animation الـ Core Animation ينفذها عن طريق استخدام الـ layer.add وعندما ينتهي الـ Animation يقوم بحذفها بما يعني لا يحدث تغير في القيمة الفعلية للـ Layer فيظل الـ View كما كان بدون تغير ! لحل المشكلة هناك حلين ، وكلاهما صحيحان الاول : نقوم بتغير الـ View الى الوضع الذي نريده عندما ينتهي الـ Animation نحن نريد جعل الحواف بقيمة 61 وذلك لتحويله الى دائرة لذا نقوم بكتابة السطر التالي squareView.layer.cornerRadius = 61 بعد السطر squareView.layer.add(cornerRadiusAnimation, forKey: nil) وبالتالي تحل المشكلة ! الحل الثاني : نقوم بإضافة السطرين التالين : animation.isRemovedOnCompletion = false animation.fillMode = kCAFillModeForwards ضعهم في أي مكان قبل squareView.layer.add وستجده أيضا يعمل ! لكن هذا الحل غير مفضل دائماً ! لماذا ؟ عندما يحدث الـ Animation في الـ Core Animation انت في الحقيقة لا تشاهد الـ Layer يحدث له الـ Animation ولكن الذي تشاهده هو نسخه طبق الأصل منه ، بما يعني الـ Cache الخاص بالـ Layer هو الذي يحدث له Animation ويطلق عليه اسم Presentation Layer وعندما تستخدم الاسطر الماضية فالذي تقوم به هو حفظ الـ Presentation Layer وتمنع ازالته من الشاشة هذا الامر يسبب مشكلة في الأداء (performance) ! وأيضا سوف يسبب مشكلة اذا كان العنصر التي تحركه يتطلب التفاعل معاه كالـ Textfield لن تستطيع الضغط على الحقل بعد انتهاء الـ Animation اذا كان الظاهر هو Presentation Layer وليس الـ Layer الحقيقي الـ Actual Layer لذا يفضل عدم استخدام الحل ذا قدر الإمكان ، ستحتاجه في بعض الحالات ولكن أغلب الحالات يفضل تغير موقع الـ Actual Layer بعد انتهاء الـ Animation كما تم شرحه في الحل الأول وهو الحل الذي سوف نستمر في استخدامه . الان قبل الضغط على الزر سوف يكون على شكل مربع ومن ثم يحدث الـ Animation فيتغير من الشكل المربع الى الدائرة وعندما ينتهي الـ Animation يظل على الشكل النهائي الذي هو الدائرة ! النتيجة : حان الوقت لتوضيح معلومة =) أعتقد بأنك لاحظت امراً وهو خصائص الـ Aniamtion في الـ Layer Animation ليست مرتبطة بـ View معين بما يعني يمكنك إعادة عمل الـ Animation ذاته على كذا View مختلف لذا يمكنك إضافة مربع اخر كما في الصورة التالية : قم بتسميته باسم squareView2 ومن ثم قم بربطة بملف الاكواد واستخدام نفس السطر التالي squareView2.layer.add(cornerRadiusAnimation, forKey: nil) لاحظ التغير الوحيد squareView غيرناه الى squareView2 وهو يساوي اسم الـ view الجديد ولا تنسى في النهاية تضيف السطر التالي squareView2.layer.cornerRadius = 61 قم بتشغيل الكود وشاهد النتيجة : نعود الى الشرح الان وقد فهمت فكرة الـ Layer Animation لنتحدث عن الانواع الأخرى : التلاشي : let animation = CABasicAnimation(keyPath: "opacity") animation.duration = 0.5 animation.fromValue = 1 animation.toValue = 0 squareView.layer.add(animation, forKey: nil) squareView.layer.opacity = 0 بالنسبة للـ Layer فالملكية المسؤولة عن الشفافية هي Opacity عوضاً عن Alpha اخر سطر كما وضحنا سابقا يتوجب علينا تغير القيمة الحقيقية أيضا لكي يظل مخفي الـView بعد انتهاء الـ Animation النتيجة : إضافة ايطار – تغير حجم الايطار : من خواص الـ Layer الـ ايطار يمكن وضع ايطار للـ View بكتابة التالي في داخل الـ AnimationButton: let animation = CABasicAnimation(keyPath: "borderWidth") animation.duration = 0.5 animation.fromValue = 0 animation.toValue = 6 squareView.layer.add(animation, forKey: nil) squareView.layer.borderWidth = 6 بما سبق قمنا بتغير قيمة borderWidth من صفر الى 6 النتيجة : تغير لون الايطار : الان سوف نضيف ايطار مسبقا في الـ Viewdidload() ومن ثم نعمل Aniamtion ونغير لونه عند الضغط على الزر لذا قم بإضافة السطر التالي في Viewdidload() squareView.layer.borderWidth = 6 الان بداخل قواس AnimationButton قم بكتابة التالي : let animation = CABasicAnimation(keyPath: "borderColor") animation.duration = 0.5 animation.fromValue = UIColor.black.cgColor animation.toValue = UIColor.lightGray.cgColor squareView.layer.add(animation, forKey: nil) squareView.layer.borderColor = UIColor.lightGray.cgColor قمنا بتغير لون الحدود (الايطار) من اللون الأسود الى اللون الرصاصي الفاتح ملاحظة مهمه : في الـ Layer في الألوان نستخدم نوع cgColor وليست UIColor لذلك نعمل عملية Casting لتحويل اللون الى نوع cgColor اذا لم تضيف .cgColor سيظهر خطأ ! النتيجة : عمل Animation للظل ! : بالنسبة للظل هناك 4 أمور تؤثر على الظل shadowOpacity : شفافية الظل ، 0 تعني اختفاء الظل و 1 ظهوره shadowOffset : موقع الظل shadowRadius : كثافة الظل ، كل ما زاد الرقم اصبح "عرض" الظل اكبر shadowColor : لون الظل قم بكتابة الاكواد التالية : let animation = CABasicAnimation(keyPath: "shadowOpacity") animation.duration = 0.5 animation.fromValue = 0 animation.toValue = 1 squareView.layer.add(animation, forKey: nil) squareView.layer.shadowOpacity = 1 كما ذكرت سابقا قيمة 1 تعني ظهور الظل لذا وضعنا قيمة 1 النتيجة : الان لغرض استخدام الخصائص الأخرى سوف نقوم بكتابة الكود التالي في viewDidLoad squareView.layer.shadowOpacity = 1 ومن ثم نقوم بكتابة الاكواد التالي لتغير موقع الظل : let animation = CABasicAnimation(keyPath: "shadowOffset") animation.duration = 0.5 animation.fromValue = CGSize.zero animation.toValue = CGSize(width: 5, height: 8) squareView.layer.add(animation, forKey: nil) squareView.layer.shadowOffset = CGSize(width: 5, height: 8) النتيجة : لاحظ بأن shadowOffset يأخد قمتين وهما العرض width والارتفاع height لتسهيل فهمها لاحظ الصورة التالية : اذا اردت الظل يظهر من الجهة اليمين اجعل القيم بالشكل التالي CGSize(width: 8, height: 0) من اليسار CGSize(width: -8, height: 0) من الأعلى CGSize(width: 0, height: -8) من الأسفل CGSize(width: 0, height:8) بما يعني اعتبر القيمتين x و y وتذكر الصورة التالية : لزيادة مساحة الظل : قم بكتابة الاكواد التالية : let animation = CABasicAnimation(keyPath: "shadowRadius") animation.duration = 0.5 animation.fromValue = 0 animation.toValue = 10 squareView.layer.add(animation, forKey: nil) squareView.layer.shadowRadius = 10 النتيجة : لتغير لون الظل : قم بكتابة الاكواد التالية : let animation = CABasicAnimation(keyPath: " shadowColor") animation.duration = 0.5 animation.fromValue = UIColor.black.cgColor animation.toValue = UIColor.gray.cgColor squareView.layer.add(animation, forKey: nil) squareView.layer.shadowColor = UIColor.gray.cgColor النتيجة : وبكذا نكون انتهينا من خصائص الظل الان نعود الى الامثله التي ذكرناها في المواضيع السابقة عندما تحدثنا عن UIView Animation وكيف نفعلها باستخدام الـ Layer Animation تغير موقع الـ View : لتغير الموقع نستخدم ملكية تسمى Position لاجل تغير قيمة y فقط نقوم بكتابة position.y let animation = CABasicAnimation(keyPath: "position.y") animation.duration = 0.5 animation.fromValue = 126 animation.toValue = 400 squareView.layer.add(animation, forKey: nil) squareView.layer.position.y = 400 fromValue هو قيمة y الحالية 126 ونريد تغيرها الى قيمة 400 لجعل المربع ينزل الى الأسفل النتيجة : لاجل تغير قيمة x فقط نقوم بكتابة position.x let animation = CABasicAnimation(keyPath: "position.x") animation.duration = 0.5 animation.fromValue = squareView.center.x animation.toValue = -20 squareView.layer.add(animation, forKey: nil) squareView.layer.position.x = -20 كما تلاحظ في formValue قمنا بكتابة squareView.center.x والسبب لجلب قيمة x لـ View الحالية بدلاً من كتابتها بشكل يدوي النتيجة : التصغير : let animation = CABasicAnimation(keyPath: "transform.scale") animation.isRemovedOnCompletion = false animation.fillMode = kCAFillModeForwards animation.duration = 0.5 animation.fromValue = 1 animation.toValue = 0.8 squareView.layer.add(animation, forKey: nil) النتيجة : التضخيم : let animation = CABasicAnimation(keyPath: "transform.scale") animation.isRemovedOnCompletion = false animation.fillMode = kCAFillModeForwards animation.duration = 0.5 animation.fromValue = 1 animation.toValue = 2 squareView.layer.add(animation, forKey: nil) النتيجة : معلومة : اذا اردت فقط تصغير او تضخيم قيمة X دون Y قم بكتابة let animation = CABasicAnimation(keyPath: "transform.scale.x") الأمر أيضا ينطبق على Y اذا اردت تصغير او تضخيم قيمة Y دون x قم بكتابة let animation = CABasicAnimation(keyPath: "transform.scale.x")
  8. الأفكار كثيره ، وكنصيحه تجنب تكرار الافكار المستهلكه برنامج للمرضى ، لتبرع الدم ، لمتجر الخ تكون هذه البرامج مره مكرره اذا تبغي تبدعي ، اتجهي للمستشعرات ، لانترنت الاشياء ( IOT ) الخ جيبي افكار جديده بحيث تكون تحدي لك مشروع تخرجي كان ترجمة لغة الاشارة بمستشعر Leap Motion يمكن تحميله من هنا https://github.com/X901/Arabic-Sign-Language-Translator-Using-Leap-Motion
  9. في هذا الجزء سوف ننتقل الي نوع اخر مختلف عنما اعتدنا عليه سابقا سوف ننتقل من Animation الـ View الى Animation الـ Layer بصيغة أخرى هذا الموضوع هو بداية الدخول الى الـ Core Animation هناك فروقات كثيره بينهم ! ابرزها : كل امر فعلته باستخدام الـ UIView Animation تستطيع عمله باستخدام الـ Layer Animation ولا يمكن فعل العكس لماذا ؟ لانه الـ Layer يكون خلف الـ View وبتالي عند تحريك الـ View مثلا انت أيضا تحرك الـ Layer بما يعني أي امر تستطيع عمله بالـ View أيضا تستطيع عمله بالـ Layer لانه طبقة الـ layer موجودة خلف الـ View ولكن لا يمكن عمل العكس . ملكيات الـ Layer التي يمكنك عمل لها Animation أكثر بكثير من ملكيات الـ UIView ، بمعنى اخر تستطيع فعل الكثير باستخدام Layer Animation الـ Layer Animation ينفذ باستخدام المعالج الرسومي GPU في حين الـ UIView باستخدام المعالج المركزي CPU الـ Layer Animation يمكن عمل Animation بشكل 3D و 2D في حين UIView Animation ، يمكن عمل Animation بشكل 2D فقط الـ UIView Animation اسهل استخداما من الـ Layer Animation يمكن دمج الـ Layer Animation مع الـ UIView Animation مختصر الكلام الـ UIView Animation هو نسخه مخففه من الـ Layer Animation بعد معرفة الفروقات لنبدأ الدرس : لأثبات كلامي السابق سوف نحاول عمل Animation يقوم بتحويل المربع الى دائرة باستخدام UIView Animation بكتابة الكود التالي : @IBAction func AnimationButton(_ sender: Any) { UIView.animate(withDuration: 0.3) { self.squareView.layer.cornerRadius = 61 } } ما فعلته هنا هو غيرت زوايا المربع من 0 الى 61 لماذا 61 ؟ لانه الطول والعرض للمربع هو 122 ، وبالتالي لتحويل المربع الى دائره نجعل زواية المربع تساوي أضلاع المربع قسمة 2 بما يعني 122 قسمة 2 = 61 الان نرى النتيجة في الصورة التالية : كما ترى لم يتم حدوث Animation ولكن فقط تم تحويل المربع الى دائره بشكل فجائي والسبب كما ذكرنا سابقا لا يمكنه عملAnimation للـ Layer باستخدام UIView Animationالـ اذا ماهو الـ Layer Animation الذي سوف نتحدث عنه اليوم ؟ كما الامر في UIView Animation هناك أنواع مختلفة أيضا في الـ Layer Animation هناك أنواع مختلفة النوع الذي سوف نتحدث عنه اليوم هو CABasicAnimation سوف نعود الى المربع الأزرق الذي استخدمناه في المواضيع السابقة لذا قم بإضافة "زر" وقم بربطه بملف الاكواد كـ Action باسم AnimationButton وقم بإضافة UIView واعطيه لوناً وقم بربطه بملف الاكواد باسم squareView الان سوف نبدأ في شرح الـ Layer Animation كيف يتم كتابة كود الـ Layer Animation؟ الطريقة مختلفة عن الـ UIView Animation فهيا تعتمد أولا على تعريف خصائص الـ Animation وفي الأخير في سطر واحد يتم تنفيذ الـ Animation قم بكتابة الكود التالي : @IBAction func AnimationButton(_ sender: Any) { let cornerRadiusAnimation = CABasicAnimation(keyPath: "cornerRadius") cornerRadiusAnimation.duration = 0.3 cornerRadiusAnimation.fromValue = 0 cornerRadiusAnimation.toValue = 61 squareView.layer.add(cornerRadiusAnimation, forKey: nil) } في المثال السابق : اول سطر قمنا بتعريف متغير cornerRadiusAnimation من نوع CABasicAnimation لاحظ الـ keypath تقوم بالكتابة فيه نوع الـ Animation الذي تريد عمله هنا نحن نريد عمل Animation للزوايا او بصيغة أخرى الـ cornerRadius من ثم قمنا بكتابة الـ duration الوقت المراد عمل في الـ Animation بالثانية (مدة الـ Animation) ومن ثم نحدد القيمة الاولية و القيمة النهائية بما يعني في اول قيمة fromValue نحدد القيمة الحالية قمنا بكتابة 0 لأننا نعلم بأنه القيمة الاولية هيا 0 وبالتالي يظهر الشكل بشكل مربع ! toValue هيا القيمة التي نريد الوصول اليها نحن نريد ان تكون قيمة الـ cornerRadius تساوي 61 وبالتالي يتغير الشكل من مربع الى دائره الان انتهينا من وضع الخصائص الخاصة بالـ Animation يبقى التنفيذ في اخر سطر نفذنا الـ Animation عن طريق استخدام add squareView.layer.add(cornerRadiusAnimation, forKey: nil) الان قم بتشغيل الكود وسوف تلاحظ الـ Animation عند تغيره من مربع الى دائره كما في الصورة التالية : لكن هناك مشكلة !! عند الانتهاء من الـ Animation تم إعادة الـ View الى الشكل المربع ولم يبقى على شكل الدائره لماذا ؟؟ لتوضيح المشكلة بأبسط صورة ممكنه عند عمل الـ Animation الـ Core Animation ينفذها عن طريق استخدام الـ layer.add وعندما ينتهي الـ Animation يقوم بحذفها بما يعني لا يحدث تغير في القيمة الفعلية للـ Layer فيظل الـ View كما كان بدون تغير ! لحل المشكلة هناك حلين ، وكلاهما صحيحان الاول : نقوم بتغير الـ View الى الوضع الذي نريده عندما ينتهي الـ Animation نحن نريد جعل الحواف بقيمة 61 وذلك لتحويله الى دائرة لذا نقوم بكتابة السطر التالي squareView.layer.cornerRadius = 61 بعد السطر squareView.layer.add(cornerRadiusAnimation, forKey: nil) وبالتالي تحل المشكلة ! الحل الثاني : نقوم بإضافة السطرين التالين : animation.isRemovedOnCompletion = false animation.fillMode = kCAFillModeForwards ضعهم في أي مكان قبل squareView.layer.add وستجده أيضا يعمل ! لكن هذا الحل غير مفضل دائماً ! لماذا ؟ عندما يحدث الـ Animation في الـ Core Animation انت في الحقيقة لا تشاهد الـ Layer يحدث له الـ Animation ولكن الذي تشاهده هو نسخه طبق الأصل منه ، بما يعني الـ Cache الخاص بالـ Layer هو الذي يحدث له Animation ويطلق عليه اسم Presentation Layer وعندما تستخدم الاسطر الماضية فالذي تقوم به هو حفظ الـ Presentation Layer وتمنع ازالته من الشاشة هذا الامر يسبب مشكلة في الأداء (performance) ! وأيضا سوف يسبب مشكلة اذا كان العنصر التي تحركه يتطلب التفاعل معاه كالـ Textfield لن تستطيع الضغط على الحقل بعد انتهاء الـ Animation اذا كان الظاهر هو Presentation Layer وليس الـ Layer الحقيقي الـ Actual Layer لذا يفضل عدم استخدام الحل ذا قدر الإمكان ، ستحتاجه في بعض الحالات ولكن أغلب الحالات يفضل تغير موقع الـ Actual Layer بعد انتهاء الـ Animation كما تم شرحه في الحل الأول وهو الحل الذي سوف نستمر في استخدامه . الان قبل الضغط على الزر سوف يكون على شكل مربع ومن ثم يحدث الـ Animation فيتغير من الشكل المربع الى الدائرة وعندما ينتهي الـ Animation يظل على الشكل النهائي الذي هو الدائرة ! النتيجة : حان الوقت لتوضيح معلومة =) أعتقد بأنك لاحظت امراً وهو خصائص الـ Aniamtion في الـ Layer Animation ليست مرتبطة بـ View معين بما يعني يمكنك إعادة عمل الـ Animation ذاته على كذا View مختلف لذا يمكنك إضافة مربع اخر كما في الصورة التالية : قم بتسميته باسم squareView2 ومن ثم قم بربطة بملف الاكواد واستخدام نفس السطر التالي squareView2.layer.add(cornerRadiusAnimation, forKey: nil) لاحظ التغير الوحيد squareView غيرناه الى squareView2 وهو يساوي اسم الـ view الجديد ولا تنسى في النهاية تضيف السطر التالي squareView2.layer.cornerRadius = 61 قم بتشغيل الكود وشاهد النتيجة : نعود الى الشرح الان وقد فهمت فكرة الـ Layer Animation لنتحدث عن الانواع الأخرى : التلاشي : let animation = CABasicAnimation(keyPath: "opacity") animation.duration = 0.5 animation.fromValue = 1 animation.toValue = 0 squareView.layer.add(animation, forKey: nil) squareView.layer.opacity = 0 بالنسبة للـ Layer فالملكية المسؤولة عن الشفافية هي Opacity عوضاً عن Alpha اخر سطر كما وضحنا سابقا يتوجب علينا تغير القيمة الحقيقية أيضا لكي يظل مخفي الـView بعد انتهاء الـ Animation النتيجة : إضافة ايطار – تغير حجم الايطار : من خواص الـ Layer الـ ايطار يمكن وضع ايطار للـ View بكتابة التالي في داخل الـ AnimationButton: let animation = CABasicAnimation(keyPath: "borderWidth") animation.duration = 0.5 animation.fromValue = 0 animation.toValue = 6 squareView.layer.add(animation, forKey: nil) squareView.layer.borderWidth = 6 بما سبق قمنا بتغير قيمة borderWidth من صفر الى 6 النتيجة : تغير لون الايطار : الان سوف نضيف ايطار مسبقا في الـ Viewdidload() ومن ثم نعمل Aniamtion ونغير لونه عند الضغط على الزر لذا قم بإضافة السطر التالي في Viewdidload() squareView.layer.borderWidth = 6 الان بداخل قواس AnimationButton قم بكتابة التالي : let animation = CABasicAnimation(keyPath: "borderColor") animation.duration = 0.5 animation.fromValue = UIColor.black.cgColor animation.toValue = UIColor.lightGray.cgColor squareView.layer.add(animation, forKey: nil) squareView.layer.borderColor = UIColor.lightGray.cgColor قمنا بتغير لون الحدود (الايطار) من اللون الأسود الى اللون الرصاصي الفاتح ملاحظة مهمه : في الـ Layer في الألوان نستخدم نوع cgColor وليست UIColor لذلك نعمل عملية Casting لتحويل اللون الى نوع cgColor اذا لم تضيف .cgColor سيظهر خطأ ! النتيجة : عمل Animation للظل ! : بالنسبة للظل هناك 4 أمور تؤثر على الظل shadowOpacity : شفافية الظل ، 0 تعني اختفاء الظل و 1 ظهوره shadowOffset : موقع الظل shadowRadius : كثافة الظل ، كل ما زاد الرقم اصبح "عرض" الظل اكبر shadowColor : لون الظل قم بكتابة الاكواد التالية : let animation = CABasicAnimation(keyPath: "shadowOpacity") animation.duration = 0.5 animation.fromValue = 0 animation.toValue = 1 squareView.layer.add(animation, forKey: nil) squareView.layer.shadowOpacity = 1 كما ذكرت سابقا قيمة 1 تعني ظهور الظل لذا وضعنا قيمة 1 النتيجة : الان لغرض استخدام الخصائص الأخرى سوف نقوم بكتابة الكود التالي في viewDidLoad squareView.layer.shadowOpacity = 1 ومن ثم نقوم بكتابة الاكواد التالي لتغير موقع الظل : let animation = CABasicAnimation(keyPath: "shadowOffset") animation.duration = 0.5 animation.fromValue = CGSize.zero animation.toValue = CGSize(width: 5, height: 8) squareView.layer.add(animation, forKey: nil) squareView.layer.shadowOffset = CGSize(width: 5, height: 8) النتيجة : لاحظ بأن shadowOffset يأخد قمتين وهما العرض width والارتفاع height لتسهيل فهمها لاحظ الصورة التالية : اذا اردت الظل يظهر من الجهة اليمين اجعل القيم بالشكل التالي CGSize(width: 8, height: 0) من اليسار CGSize(width: -8, height: 0) من الأعلى CGSize(width: 0, height: -8) من الأسفل CGSize(width: 0, height:8) بما يعني اعتبر القيمتين x و y وتذكر الصورة التالية : لزيادة مساحة الظل : قم بكتابة الاكواد التالية : let animation = CABasicAnimation(keyPath: "shadowRadius") animation.duration = 0.5 animation.fromValue = 0 animation.toValue = 10 squareView.layer.add(animation, forKey: nil) squareView.layer.shadowRadius = 10 النتيجة : لتغير لون الظل : قم بكتابة الاكواد التالية : let animation = CABasicAnimation(keyPath: " shadowColor") animation.duration = 0.5 animation.fromValue = UIColor.black.cgColor animation.toValue = UIColor.gray.cgColor squareView.layer.add(animation, forKey: nil) squareView.layer.shadowColor = UIColor.gray.cgColor النتيجة : وبكذا نكون انتهينا من خصائص الظل الان نعود الى الامثله التي ذكرناها في المواضيع السابقة عندما تحدثنا عن UIView Animation وكيف نفعلها باستخدام الـ Layer Animation تغير موقع الـ View : لتغير الموقع نستخدم ملكية تسمى Position لاجل تغير قيمة y فقط نقوم بكتابة position.y let animation = CABasicAnimation(keyPath: "position.y") animation.duration = 0.5 animation.fromValue = 126 animation.toValue = 400 squareView.layer.add(animation, forKey: nil) squareView.layer.position.y = 400 fromValue هو قيمة y الحالية 126 ونريد تغيرها الى قيمة 400 لجعل المربع ينزل الى الأسفل النتيجة : لاجل تغير قيمة x فقط نقوم بكتابة position.x let animation = CABasicAnimation(keyPath: "position.x") animation.duration = 0.5 animation.fromValue = squareView.center.x animation.toValue = -20 squareView.layer.add(animation, forKey: nil) squareView.layer.position.x = -20 كما تلاحظ في formValue قمنا بكتابة squareView.center.x والسبب لجلب قيمة x لـ View الحالية بدلاً من كتابتها بشكل يدوي النتيجة : التصغير : let animation = CABasicAnimation(keyPath: "transform.scale") animation.isRemovedOnCompletion = false animation.fillMode = kCAFillModeForwards animation.duration = 0.5 animation.fromValue = 1 animation.toValue = 0.8 squareView.layer.add(animation, forKey: nil) النتيجة : التضخيم : let animation = CABasicAnimation(keyPath: "transform.scale") animation.isRemovedOnCompletion = false animation.fillMode = kCAFillModeForwards animation.duration = 0.5 animation.fromValue = 1 animation.toValue = 2 squareView.layer.add(animation, forKey: nil) النتيجة : معلومة : اذا اردت فقط تصغير او تضخيم قيمة X دون Y قم بكتابة let animation = CABasicAnimation(keyPath: "transform.scale.x") الأمر أيضا ينطبق على Y اذا اردت تصغير او تضخيم قيمة Y دون x قم بكتابة let animation = CABasicAnimation(keyPath: "transform.scale.x")
  10. تعلمنا في ما سبق عن نوع من الـ Animation وهو الـ UIView.animate والذي يعتبر الأكثر استخداما واليوم سوف نتعلم عن نوع مختلف وهو UIView.transition قبل ان نبدأ في الدرس ، ما الفرق بينهما ؟ باختصار UIView.animate مسؤول عن ملكية الـ UIView من عمل تحريك وتكبير والتفاف وغيرها في حين UIView.transition مسؤول عن إضافة وحذف الـ View تستطيع عند إضافة الـ View كـ Subview انك تعمل Animation في لحظة اضافته او حذفه اعلم الكلام غير مفهوم ! مع التطبيق سوف تتضح الصورة . لتسهيل الامور لن اتطرق الى موضوع إضافة وحذف الـ SubView عن طريق الاكواد ولكن سوف اشرحها بطريقة اكثر بساطة . اذا لنبدأ الدرس . نقوم بإضافة Label ونجعل لون النص باللون الأبيض ولون الخلفية باللون الأسود ومن ثم نضيف زر كما في الصورة التالية : نقوم بإضافتهم الى ملف اكواد التطبيق ونجعل اسم الـ Label بـ Label واسم الـ Function بـ Transition @IBOutlet weak var Label: UILabel! @IBAction func Transition(_ sender: Any) { } الان في viewDidLoad نضيف التالي : Label.isHidden = true لماذا ؟ ذكرنا في السابق بأن الـ UIView.transition يعمل اثناء إضافة وحذف الـ View لذا في الكود السابق ما نقوم به هو حذفه من الـ View بجعله غير ظاهر ! في الدروس السابقة استخدمنا .alpha فما الفرق ؟ الـ .alpha هي الشفافية بمعنى 1 يعتبر لا توجد شفافية في حين 0.5 تعني وجود شفافية و 0 يعني انعدام الشفافية وهي من ملكيات الـ UIView.animate بصورة أخرى من المكيات التي يمكن عمل لها animate لكن الـ isHidden يزيل الـ View وليست من الملكيات التي تستطيع عمل لها animate فاذا استخدمتها بداخل اقواس UIView.animate لن يظهر أي Animation ! بصورة مختصرة : استخدام UIView.transition مع الملكيات التي لا يمكن عمل لها Animation نعود للدرس الان نقوم بعمل التالي بداخل اقواس Transition UIView.transition(with: Label, duration: 0.5, options: .transitionCurlDown, animations: { self.Label.isHidden = false }, completion: nil) كما تلاحظ بصورة عامة فهو مشابه للـ UIView.animate الاختلاف with وتعني ماهو الـ view الذي تريد عمل له الـ transition تكتب نفس اسم الـ View وهنا نحن استخدمنا Label فنقوم بكتابة اسم الـ Label الـ options هناك أنواع مختلف سوف اذكرها بعد قليل ما اريد توضيحه هنا الانواع التي ذكرتها في موضوعي السابق .curveEaseIn واخواتها يمكنك دمجها مع الانواع الخاصة بالـ transition بعمل مربع الاريه [] والفصل بينهم بعلامة فاصلة ماقمنا بفعله داخل الاقواس هو فقط حولنا الحالة من مخفي الى ظاهر بالتغير من true الى false أنواع الـ options : transitionCurlDown : وهو النوع الذي استخدمناه في الكود السابق transitionCurlUp : هيا عملية معاكسه للعملية السابقة لم استطيع عمل صورة متحركة توضحها transitionFlipFromTop: :transitionFlipFromBottom :transitionFlipFromRight :transitionFlipFromLeft transitionCrossDissolve: هناك نوع أخير وهو .showHideTransitionViews سيتضح فائدته في الـ Function الاخر للـ transition من الامور التي اتضحت من أنواع الـ Options السابقة بأنك قد ترغب باستخدام الـ transition لعمل تأثير معين لن تستطيع عمله باستخدام الـ UIView.animate ولكن من الامور الأخرى هو عمل تأثير انتقالي وسيكون المثال التالي توضيحا للطريقة قبل العوده لاستكمال الدرس هنا تلميحه بسيطة وهي هل تريد مشاهدة التأثير ببطيء ؟ عند تشغيل التطبيق على المحاكي اذهب الى خانة Debug واختار خيار Slow Animations ومن ثم اضغط على الزر وسظهر الـ Animation بصورة بطيئة (سلو موشن) كما في الصورة التالية : مثال : نعود للدرس الان سوف ننتقل الى نوع أخرى من أنواع الـ UIView.transition UIView.transition(from: , to: , duration: , options: , completion: nil) كما تلاحظ في الكود السابق بأنه يطلب نوعين من الـ UIView ، وهذا النوع هو نوع انتقالي بحيث يعمل عملية انتقالية بين الـ View الاول الى الـ View الثاني بحيث يخفي الاول ويظهر الثاني ! دعونا نبدأ في المثال أولا : نذهب الى الـ Storyboard ونضيف التالي : نضيف UIView ونجعله بحجم مستطيل صغير ومن ثم بداخله نضيف UIView اخر ونجعله بنفس حجم الـ UIView السابق ونغير لونه الى الاخضر ومن ثم نضيف Label اذا اردت وأيضا نضيف Button ، نحذف النص الذي بداخله ونجعله بحجم الـ UIView كما في الصورة التالية : ومن ثم نقوم بنسخ الـ UIView الذي قمنا بعمله في الخطوة السابقة ونغير النص ولون الـ UIView الى الازرق كما في الصورة التالية : الان اصبح لدينا ثلاثة من الـ UIView اثنين بداخل UIView واحد وفوق كل UIView زر بما يعني ٣ من الـ UIView و ٢ من الـ Button قد تتسأل لماذا اضفنا UIView وبداخله (فوقه) اضفنا اثنين من الـ UIView بدلا من إضافة اثنين من الـ UIView مباشرةً الى الـ ViewController؟ السبب لأنه عند عمل الـ transition سنقلب الـSuperview الذي سيكون الـ View الأساسي بمعنى سوف تنقلب كامل الصفحة !! لكن عند إضافة UIView وجعلنا اثنين من الـ UIView بداخله الذي سوف ينقلب هو الـ UIView الي اضفناه لأنه اصبح هو الـSuperview بالنسبة لهم بما يعني لن تنقلب الصفحة كامله ! بما يعطي ايحاء بتأثير انقلاب البطاقة ! ملاحظة : في الـ StoryBoard من يكون في اخر التسلسل يكون هو الاول (الظاهر) فالان اصبح الـ View 2 هو الاول والـ View 1 هو الثاني يمكنك تغيير المسميات او فقط تقوم بسحب الـ View 2 وتجعله فوق View 1 بالشكل التالي : ملاحظة ٢ : عندما يكون هناك نوعين مختلفة فوق بعض (في المثال هذا هناك اثنين من الـ View) ، سوف يكون من الصعب مشاهدة الـ View الذي في الخلف ، اذا طبقت الملاحظة الاولى بعد عمل القيود سوف تواجه مشاكل مع القيود ! اذا ما الحل ؟ كل ما عليك فعله هو الضغط على الـ View الذي في المقدمة (الذي يكون الأخير في الترتيب) ومن ثم إزالة علامة الصح من Installed وعندها ستخفي وسيظهر الـ View الذي كان في الخلف وسيسهل عليك تعديله بعد الانتهاء من التعديلات قم بتفعيل الصح مره أخرى ! صورة توضيحية : الان نقوم بإضافتهم الى ملف الاكواد سنقوم بإضافة فقط الـ UIView الذي باللون الأزرق والاخضر وأيضا سوف نضيف Action للزر ونربط الزرين بهذا الـ Action بصيغة أخرى سوف يصبح لدينا Function واحد وزرين مرتبطين به ! لأننا نريد جعل البطاقة تنقلب مرتين عند اللمس عوضا عن عمل اثنين من الـ Function سوف نعمل على Function واحد فقط كما في الصورة التالية : وبالتالي ملف الاكواد سوف يصبح بالشكل التالي : import UIKit class ViewController: UIViewController { @IBOutlet weak var View1: UIView! @IBOutlet weak var View2: UIView! override func viewDidLoad() { super.viewDidLoad() } @IBAction func FlipButton(_ sender: UIButton) { } الان بداخل الـ FlipButton نقوم بكتابة التالي : @IBAction func FlipButton(_ sender: UIButton) { UIView.transition(from: self.View1, to: self.View2, duration: 0.5, options: [.transitionFlipFromRight , .showHideTransitionViews], completion: nil) } ما قمنا به هو جعلنا الـ View1 ينقلب الى View2 لاحظ اننا استخدمنا الـ showHideTransitionViews لماذا ؟ لانه بعد عملية الانتقال سوف يتم حذف الـ View1 من الـ Suberview وبالتالي سوف يسبب بخطأ nil والخيار هذا بدل من ازالت الـ View1 سوف يقوم بإخفائه فقط الان عند التشغيل سوف تلاحظ بانه ينقلب من View1 الى View2 ومن ثم لن يعود الى View1 بل سوف يستمر بعرض View2 صورة توضيحية بالنتيجة الحالية : لماذا لم يعود الى الـ View الاول ؟ لأنه الكود الذي كتبناه يقوم على تحويل من View1 الى View2 بما يعني دائما سوف يظهر View2 باستطاعتنا تعديل الخطأ بإضافة Boolean var check = false اسفل @IBOutlet weak var View1: UIView! @IBOutlet weak var View2: UIView! ومن ثم نغير الكود الى هذا الشكل @IBAction func FlipButton(_ sender: UIButton) { check = !check let fromView = check ? View1 : View2 let toView = check ? View2 : View1 UIView.transition(from: fromView!, to: toView!, duration: 0.5, options: [.transitionFlipFromRight , .showHideTransitionViews], completion: nil) } ما الذي قمنا به هنا ؟ قمنا بتغير حالة الـ Boolean اذا كان false سوف يصبح true والعكس صحيح ملاحظة : علامة التعجب هنا معناها اعكس القيمة! ومن ثم اضفنا متغير باسم fromView واضفنا شرط اذا كان true اجعله View1 اذا كان false اجعله View2 واضفنا متغير اخر باسم toView واضفنا شرط اذا كان true اجعله View2 اذا كان false اجعله View1 ومن ثم اضفنا المتغيرات الجديدة الى from و to بداخل Function الـ UIView.transitio اعلم بانه البعض سوف يرى بأن طريقة كتابة الـ IF غريبة وغير منطقية بالنسبة له لذا لتبسيط الامور يمكن أيضا كتابتها بالطريقة التالية : @IBAction func FlipButton(_ sender: UIButton) { check = !check var formView : UIView if check == true { formView = View1 }else { formView = View2 } var toView : UIView if check == true { toView = View2 }else { toView = View1 } UIView.transition(from: formView, to: toView, duration: 0.5, options: [.transitionFlipFromRight , .showHideTransitionViews], completion: nil) } صورة توضيحية بالنتيجة :
  11. مجهود تشكر عليه ، جزاك الله كل خير
  12. البعض اخبرني بأنه يرغب بأمثله في الـ Animation وليس فقط شرح الأساسيات لذلك قررت اشرح مثال لعمل Menu مشابه للمدرج في تطبيق Telegram الفكرة باختصار هي إضافة زر بدلاَ من النص في الـ Navigation bar ومن ثم تغير موقع الـ View عند الضغط على الزر اذا لنبدأ في الشرح أولا نقوم بإضافة الـ Navigation Controller عن الطريق تطبيق اتباع الصورة التالية : ومن ثم نضيف View اسفل الـ Navigation bar بحجم عرض 375 وارتفاع 50 طبعا يعتمد على الابعاد الي انت تبغاها انا استخدمت هذه الابعاد نضيف القيود التالية : ومن ثم نضيف 3 ازرار احجام الايقونات كانت متفاوتة ، لكن اعتمدت على حجم 60 في 60 او اصغر بقليل بالنسبة لأحجام ايقونات الـ x2 الخاصة بحجم iPhone 7 و iPhone SE الان سوف نحتاج نستخدم الـ Stack View لأنه سوف يسهل علينا جعل الايقونات بمسافة متساوية ! قم بتحديد جميع الايقونات في أنٍ واحد ومن ثم ثم باتباع الصورة التالية : سوف تلاحظ تلاصق الايقونات مع بعض قوم بالذهاب الى خصائص الـ Stack View واجعل المسافة Spacing بـ 60 كما في الصورة التالية : واجعل الايقونات في منتصف الـ View الان قم بإضافة قيود الى الـ Stack View: لاحظ باني اضفت القيود بشكل يدوي لجعله الـ Stack View ياخد حجم الـ View الذي خلفه عند التعامل مع الـ Stack View يتوجب عليك استخدام القيود لتغير ابعاده لاحظ الفرق قبل القيود كان بالشكل التالي : بعد إضافة القيود اصبح بالشكل التالي : الان سوف تلاحظ بان الازرار والـ View متناسبة مع جميع احجام الاجهزة أيضا لاحظ بأنه عند استخدام الـ Stack View تكون لست بحاجة الى وضع قيود لكل زر على حدى ! كل ما عليك هو وضع القيود على نفس الـ Stack View فقط هذه هي قوة الـ Stack View سوف تحتاج الى استخدامه بكثره وخاصة عند عمل الـ Animation الان نعود للدرس قبل ان نبدأ تأكد بأن الـ Stack View بداخل الـ View الذي اضفناها لأننا سوف نضيف الـ View فقط الى ملف الاكواد ! يمكن التأكد بمشاهده الترتيب الـ Stack يكون بداخل الـ View الذي اضفناه سابقا كما في الصورة التالية: الان نقوم بإضافة الـ View الى ملف الاكواد ونجعله باسم Menu View وأيضا نضيف قيد الـ top الخاص بـ View ونجعله اسم menuTop يصبح ملف الاكواد بالشكل التالي import UIKit class ViewController: UIViewController { @IBOutlet weak var menuTop: NSLayoutConstraint! @IBOutlet weak var menuView: UIView! override func viewDidLoad() { super.viewDidLoad() } الان نقوم بإضافة الاكواد التالية في الـ ViewDidLoad override func viewDidLoad() { super.viewDidLoad() menuTop.constant = -65 let button = UIButton(type: .custom) button.frame = CGRect(x: 0, y: 0, width: 200, height: 40) button.setTitle("Menu", for: .normal) button.addTarget(self, action: #selector(self.menuButton), for: .touchUpInside) self.navigationItem.titleView = button menuView.layer.borderWidth = 0.5 menuView.layer.borderColor = UIColor.gray.cgColor } في البداية جعلنا الـ View مخفي بجعل القيمة -65 وبالتالي يصبح الـ View في الأعلى خلف الـ Navigation bar ومن ثم انشأنا زر من نوع .custom اعطينا ابعاد له وهيا عرض 200 وارتفاع 40 العرض تستطيع تغيره انا جعلته 200 لأنه في حال وجود زر في اليمين واليسار لن يغطي عليه اما الارتفاع فهو 40 ارتفاع الـ Navigation bar بدون شريط الساعة هو 44 لذلك حجم 40 يعتبر مناسب ومن ثم اضفنا عنوان للزر باسم Menu وبعدها اضفنا Target الي هو جعل الزر يستجيب للضغط طبعا اضفنا اسم للـ Function وهو menuButton سوف نضيفه لاحقا اخيراً قمنا بإضافة الزر بداخل العنوان الـ Navigation bar في الأسفل اضفنا الحدود حولينا الـ view وغيرنا لون الحدود الى اللون الرصاصي الان نضيف Function الزر باضافة التالي : func menuButton(button: UIButton) { } الان سوف نضيف الـ Aniamtion الفكره باختصار عند الضغط على الزر لأول مره نغير القيود الى 0 ليظهر الـ View لماذا 0 ؟ لانه عند اضافة الـ Navigation bar تصبح المسافه اسفله بقيمة 0 بالنسبة الى القيود فين حين بدون الـ Navigation bar تكون المسافة اسفل شريط الساعه هي الـ 0 وعند الضغط على الزر للمره الثانية نغير القيود الى -65 وبالتالي يختفي الـ View كيف نعلم بأمر الـ Menu هو ظاهر او مختفي ؟ الطريقة هي باستخدام متغير من نوع bool يكون true وتعني بانه مخفي ويصبح false اذا كان غير مخفي لذلك نضيف متغير في خارج الـ Function وأيضا بخارج الـ ViewDidLoad باسم var check = true ومن ثم نضيف التالي في الـ menuButton func menuButton(button: UIButton) { if check == true { UIView.animate(withDuration: 0.5, animations: { self.menuTop.constant = 0 self.view.layer.layoutIfNeeded() self.check = false }) }else { UIView.animate(withDuration: 0.5, animations: { self.menuTop.constant = -65 self.view.layer.layoutIfNeeded() self.check = true }) } } الذي قمنا به هو عمل if وقبنا بالتشيك اذا كان القيمة true فهو مخفي لذلك سوف نظهره واذا كان غير مخفي سوف نخفيه صورة بالنتيجة النهائية : اذا كنت تتسأل كيف غيرت لون الـ Navigation Bar فقم باتباع الخطوات التالية : يمكن تحميل الكود من هنا
  13. تحدثنا سابقا عن أنواع مختلفة للـ Animation وفي هذا الموضوع سوف نتحدث أيضا عن نوع اخر النوع هذا مختلف عن الانواع الأخرى فهو يتركز على موضوع التسلسل الزمني ذكرت في موضوع الاول من هذه السلسلة عن كيفية عمل Animation للمربع بحيث يتحرك بشكل حرف L اذا لاحظت في الكود السابق سوف تلاحظ عدة امور UIView.animate(withDuration: 1.0, animations: { self.squareView.transform = CGAffineTransform(translationX:0, y: 300) }) { (true) in UIView.animate(withDuration: 1.0, animations: { self.squareView.transform = CGAffineTransform(translationX:-122, y: 300) }) } الاول : هو كثرة الاقواس وتداخلها ، في حال كان التحرك اكثر تعقيداً سوف يزداد تعقيد الكود وتكثر الاقواس وبالتالي يصعب تعديله الثاني : اذا اردت جعل الحركة متسلسلة بنفس التوقيت سوف ينبغي عليك تعديل توقيت كل Animation على حدى هذه هي فوائد النوع الجديد ازلت الاقواس المتداخله سهولة تعديل الكود او الوقت الكلي للـ Animation قبل أن نبدأ في الشرح العملي ، يتوجب عليا شرحه بصورة نظرية كيف يعمل ؟ عند استخدام UIView.animate فنحن نقوم بإضافة وقت زمني واذا استخدمنا Function معين سوف نستطيع اضافة أيضا زمن تأخير الـ Animation سوف يبدا وينتهي في الزمن المعين بصورة ثابته واذا اردنا ان نحرك في اتجاه مختلف فإننا نقوم بإضافة UIView.animate أخر بداخل اقواس الاستكمال التي نضعها أحيانا بقيمة true بما يعني في كل مره سوف نضع زمن مختلف ونعيد كتابة UIView.animate في هذا النوع الذي سوف نقوم به هو التالي : سوف نعطي زمن كلي محدد ولنقول مدة ثانية واحده ومن ثم ننفذ الـ Aniamtion بالنسبة المئوية ! ما المقصود بالنسبة المئوية؟ افرض بأن مدة الثانية الواحدة كامله منذ بدايتها الى نهاتيها تكون بنسبة 100% اذا سوف نضع Animation يبدا مباشرة وتكون بنسبة 0% الى نسبة 50% يتم التحريك بصورة عامودية ومن النسبة 50% الى 100% يتم التحريك بصورة افقيه صورة توضيحية : هذا هو فكرة النوع هذا باختصار وقت واحد ، يتم تجزئيه بالنسبة وعمل تحريك مختلف لذا ذكرت في بداية الموضوع بأن هذا النوع يعتمد على التسلسل الزمني فهو يعمل كالخط الزمني ! أخيرا يتوجب عليك ان تعلم بأن : النسبة لن تكون 100% ولكن سوف تكون 1 ، بمعنى نقوم بالقسمة على 100 اذا اردنا نسبة 25% سوف تكون 0.25 اذا اردنا نسبة 50 سوف تكون 0.5 وهكذا اذا استوعبت وفهمت الكلام السابق ، تكون فهمت فكرة هذا النوع ! اسم النوع الجديد هو UIView.animateKeyframes الان لجعلك تلاحظ الفرق الجوهري بين استخدام UIView.animate وبين استخدام UIView.animateKeyframes سوف اعطيك نفس المثال مرتين الاولى بالطريقة التقليدية باستخدام UIView.animate الثانية باستخدام UIView.animateKeyframes في هذا الدرس سوف نعود الى استخدام المربع الأزرق التي استخدمناه في الجزء الاول والثاني من هذه السلسلة ما الذي سوف نقوم به ؟ سوف نقوم بتحريك المربع الأزرق بشكل مستطيل يبدا من الزاوية السفلية اليسرى ويتحرك الى الزاوية السفلية اليمنى ومن ثم يتحك الى الزاوية العلوية اليمنى ومن ثم يتحرك الى الزاوية العلوية اليسرى واخيراً يعود الى الزاوية السفلية اليسرى قبل اذهب الى الـ Storyboard واجعله بالشكل التالي : ومن ثم اضيفهم الى ملف الاكواد الان سوف نبدأ الدرس باستخدام الطريقة القديمة الكود سوف يصبح بالشكل التالي : @IBAction func Animation(_ sender: Any) { UIView.animate(withDuration: 0.75, animations: { self.square.transform = CGAffineTransform(translationX: 225, y: 0) }) { (true) in UIView.animate(withDuration: 0.75, animations: { self.square.transform = CGAffineTransform(translationX: 225, y: -500) }, completion: { (true) in UIView.animate(withDuration: 0.75, animations: { self.square.transform = CGAffineTransform(translationX: -0, y: -500) }, completion: { (true) in UIView.animate(withDuration: 0.75, animations: { self.square.transform = .identity }, completion: { (true) in }) }) }) } } صورة توضيحية بالنتيجة : هل لاحظت المشكلة في الكود ؟ كثرة الاقواس وتداخلها يصعب من عملية تعديل الكود ماذا اذا اردت حذف احدى الـ Animation ؟ ستواجه صعوبة في إيجاد كود الـ Animation المراد حذفه ماذا اذا اردت تعديل الوقت الكلي ؟ سوف يتوجب عليك تعديل كود كل Animation على حده في المثال السابق استخدمنا وقت 0.75 لماذا ؟ لنصل الى توقيت كلي 3.0 فكل Animation سوف يستغرق 0.75 ثانية فقط الان للتنقل الى النوع الجديد @IBAction func Animation(_ sender: Any) { UIView.animateKeyframes(withDuration: 3.0, delay: 0.0, options: [], animations: { UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.25, animations: { self.square.transform = CGAffineTransform(translationX: 225, y: 0) }) UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.5, animations: { self.square.transform = CGAffineTransform(translationX: 225, y: -500) }) UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.75, animations: { self.square.transform = CGAffineTransform(translationX: -0, y: -500) }) UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 1.0, animations: { self.square.transform = .identity }) }, completion: nil) } النتيجة الذي وصلنا اليها : ما الجديد ؟ أولا اضفنا animateKeyframes ومن ثم اضفنا التوقيت الكلي وهو 3 ثواني ولم نضيف تأخير لأننا نريد ان يبدا فوراُ فجعلنا القيمة 0.0 في الـ options لم نضيف شيء لذا وضعنا اقواس اريه فاضية مع العلم أنواع الـ options مختلفة تماما عن الانواع السابقة الموجودة في UIView.animate ! (لن نتطرق لها في هذا الموضوع) والان لاحظ كل Animation ينضاف لحاله بداخل اقواس animateKeyframes ويطلق عليه UIView.addKeyframe يتكون من امرين وهي withRelativeStartTime والتي تعبر بداية الـ Animation كما ذكرنا في بداية الموضوع تعتبر نسبة في اول Animation وضعنا قيمة 0.0 ليبدا في البداية بالنسبة الى relativeDuration يعتبر وقت انتهاء الـ Animation في اول Animation وضعنا نسبة 0.25 والتي تساوي نسبة 25% لجعل الامر اكثر وضوحاً اذا نظرنا في animateKeyframes وضعنا وقت كلي هو 3 ثواني فاذا قمنا بعملية حسابية بسيطة 3 ضرب 25 قسمة 100 النتيجة سوف تكون 0.75 ثانية اذاً اول Animation سوف يستغرق 0.75 ثانية فقط والامر ينطبق مع البقية في الـ Animation الثاني وضعنا قيمة withRelativeStartTime بـ 0.25 وقيمة الـ relativeDuration بـ 0.50 اذا نقصنا القيمتين من بعض سوف تكون النسبة 0.25 وذا اتبعنا نفس الحسبة السابقة سوف تكون النتيجة 0.75 ثانية من الشرح السابق اتضح لنا فائدة الـ UIView.animateKeyframes بما يعني : اذا اردنا تغير الوقت الكلي فقط نقوم بتغير القيمة الموجودة في UIView.animateKeyframes اذا اردنا تغير مدة Animation معين ، نقوم بتغير نسبة الـ Animation فقط والاهم من ذا كله الكود اصبح نضيف وسهل القراءة والتعديل !
  14. تحدثنا سابقا عن أنواع مختلفة للـ Animation وفي هذا الموضوع سوف نتحدث أيضا عن نوع اخر النوع هذا مختلف عن الانواع الأخرى فهو يتركز على موضوع التسلسل الزمني ذكرت في موضوع الاول من هذه السلسلة عن كيفية عمل Animation للمربع بحيث يتحرك بشكل حرف L اذا لاحظت في الكود السابق سوف تلاحظ عدة امور UIView.animate(withDuration: 1.0, animations: { self.squareView.transform = CGAffineTransform(translationX:0, y: 300) }) { (true) in UIView.animate(withDuration: 1.0, animations: { self.squareView.transform = CGAffineTransform(translationX:-122, y: 300) }) } الاول : هو كثرة الاقواس وتداخلها ، في حال كان التحرك اكثر تعقيداً سوف يزداد تعقيد الكود وتكثر الاقواس وبالتالي يصعب تعديله الثاني : اذا اردت جعل الحركة متسلسلة بنفس التوقيت سوف ينبغي عليك تعديل توقيت كل Animation على حدى هذه هي فوائد النوع الجديد ازلت الاقواس المتداخله سهولة تعديل الكود او الوقت الكلي للـ Animation قبل أن نبدأ في الشرح العملي ، يتوجب عليا شرحه بصورة نظرية كيف يعمل ؟ عند استخدام UIView.animate فنحن نقوم بإضافة وقت زمني واذا استخدمنا Function معين سوف نستطيع اضافة أيضا زمن تأخير الـ Animation سوف يبدا وينتهي في الزمن المعين بصورة ثابته واذا اردنا ان نحرك في اتجاه مختلف فإننا نقوم بإضافة UIView.animate أخر بداخل اقواس الاستكمال التي نضعها أحيانا بقيمة true بما يعني في كل مره سوف نضع زمن مختلف ونعيد كتابة UIView.animate في هذا النوع الذي سوف نقوم به هو التالي : سوف نعطي زمن كلي محدد ولنقول مدة ثانية واحده ومن ثم ننفذ الـ Aniamtion بالنسبة المئوية ! ما المقصود بالنسبة المئوية؟ افرض بأن مدة الثانية الواحدة كامله منذ بدايتها الى نهاتيها تكون بنسبة 100% اذا سوف نضع Animation يبدا مباشرة وتكون بنسبة 0% الى نسبة 50% يتم التحريك بصورة عامودية ومن النسبة 50% الى 100% يتم التحريك بصورة افقيه صورة توضيحية : هذا هو فكرة النوع هذا باختصار وقت واحد ، يتم تجزئيه بالنسبة وعمل تحريك مختلف لذا ذكرت في بداية الموضوع بأن هذا النوع يعتمد على التسلسل الزمني فهو يعمل كالخط الزمني ! أخيرا يتوجب عليك ان تعلم بأن : النسبة لن تكون 100% ولكن سوف تكون 1 ، بمعنى نقوم بالقسمة على 100 اذا اردنا نسبة 25% سوف تكون 0.25 اذا اردنا نسبة 50 سوف تكون 0.5 وهكذا اذا استوعبت وفهمت الكلام السابق ، تكون فهمت فكرة هذا النوع ! اسم النوع الجديد هو UIView.animateKeyframes الان لجعلك تلاحظ الفرق الجوهري بين استخدام UIView.animate وبين استخدام UIView.animateKeyframes سوف اعطيك نفس المثال مرتين الاولى بالطريقة التقليدية باستخدام UIView.animate الثانية باستخدام UIView.animateKeyframes في هذا الدرس سوف نعود الى استخدام المربع الأزرق التي استخدمناه في الجزء الاول والثاني من هذه السلسلة ما الذي سوف نقوم به ؟ سوف نقوم بتحريك المربع الأزرق بشكل مستطيل يبدا من الزاوية السفلية اليسرى ويتحرك الى الزاوية السفلية اليمنى ومن ثم يتحك الى الزاوية العلوية اليمنى ومن ثم يتحرك الى الزاوية العلوية اليسرى واخيراً يعود الى الزاوية السفلية اليسرى قبل اذهب الى الـ Storyboard واجعله بالشكل التالي : ومن ثم اضيفهم الى ملف الاكواد الان سوف نبدأ الدرس باستخدام الطريقة القديمة الكود سوف يصبح بالشكل التالي : @IBAction func Animation(_ sender: Any) { UIView.animate(withDuration: 0.75, animations: { self.square.transform = CGAffineTransform(translationX: 225, y: 0) }) { (true) in UIView.animate(withDuration: 0.75, animations: { self.square.transform = CGAffineTransform(translationX: 225, y: -500) }, completion: { (true) in UIView.animate(withDuration: 0.75, animations: { self.square.transform = CGAffineTransform(translationX: -0, y: -500) }, completion: { (true) in UIView.animate(withDuration: 0.75, animations: { self.square.transform = .identity }, completion: { (true) in }) }) }) } } صورة توضيحية بالنتيجة : هل لاحظت المشكلة في الكود ؟ كثرة الاقواس وتداخلها يصعب من عملية تعديل الكود ماذا اذا اردت حذف احدى الـ Animation ؟ ستواجه صعوبة في إيجاد كود الـ Animation المراد حذفه ماذا اذا اردت تعديل الوقت الكلي ؟ سوف يتوجب عليك تعديل كود كل Animation على حده في المثال السابق استخدمنا وقت 0.75 لماذا ؟ لنصل الى توقيت كلي 3.0 فكل Animation سوف يستغرق 0.75 ثانية فقط الان للتنقل الى النوع الجديد @IBAction func Animation(_ sender: Any) { UIView.animateKeyframes(withDuration: 3.0, delay: 0.0, options: [], animations: { UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.25, animations: { self.square.transform = CGAffineTransform(translationX: 225, y: 0) }) UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.5, animations: { self.square.transform = CGAffineTransform(translationX: 225, y: -500) }) UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.75, animations: { self.square.transform = CGAffineTransform(translationX: -0, y: -500) }) UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 1.0, animations: { self.square.transform = .identity }) }, completion: nil) } النتيجة الذي وصلنا اليها : ما الجديد ؟ أولا اضفنا animateKeyframes ومن ثم اضفنا التوقيت الكلي وهو 3 ثواني ولم نضيف تأخير لأننا نريد ان يبدا فوراُ فجعلنا القيمة 0.0 في الـ options لم نضيف شيء لذا وضعنا اقواس اريه فاضية مع العلم أنواع الـ options مختلفة تماما عن الانواع السابقة الموجودة في UIView.animate ! (لن نتطرق لها في هذا الموضوع) والان لاحظ كل Animation ينضاف لحاله بداخل اقواس animateKeyframes ويطلق عليه UIView.addKeyframe يتكون من امرين وهي withRelativeStartTime والتي تعبر بداية الـ Animation كما ذكرنا في بداية الموضوع تعتبر نسبة في اول Animation وضعنا قيمة 0.0 ليبدا في البداية بالنسبة الى relativeDuration يعتبر وقت انتهاء الـ Animation في اول Animation وضعنا نسبة 0.25 والتي تساوي نسبة 25% لجعل الامر اكثر وضوحاً اذا نظرنا في animateKeyframes وضعنا وقت كلي هو 3 ثواني فاذا قمنا بعملية حسابية بسيطة 3 ضرب 25 قسمة 100 النتيجة سوف تكون 0.75 ثانية اذاً اول Animation سوف يستغرق 0.75 ثانية فقط والامر ينطبق مع البقية في الـ Animation الثاني وضعنا قيمة withRelativeStartTime بـ 0.25 وقيمة الـ relativeDuration بـ 0.50 اذا نقصنا القيمتين من بعض سوف تكون النسبة 0.25 وذا اتبعنا نفس الحسبة السابقة سوف تكون النتيجة 0.75 ثانية من الشرح السابق اتضح لنا فائدة الـ UIView.animateKeyframes بما يعني : اذا اردنا تغير الوقت الكلي فقط نقوم بتغير القيمة الموجودة في UIView.animateKeyframes اذا اردنا تغير مدة Animation معين ، نقوم بتغير نسبة الـ Animation فقط والاهم من ذا كله الكود اصبح نضيف وسهل القراءة والتعديل !
  15. البعض اخبرني بأنه يرغب بأمثله في الـ Animation وليس فقط شرح الأساسيات لذلك قررت اشرح مثال لعمل Menu مشابه للمدرج في تطبيق Telegram الفكرة باختصار هي إضافة زر بدلاَ من النص في الـ Navigation bar ومن ثم تغير موقع الـ View عند الضغط على الزر اذا لنبدأ في الشرح أولا نقوم بإضافة الـ Navigation Controller عن الطريق تطبيق اتباع الصورة التالية : ومن ثم نضيف View اسفل الـ Navigation bar بحجم عرض 375 وارتفاع 50 طبعا يعتمد على الابعاد الي انت تبغاها انا استخدمت هذه الابعاد نضيف القيود التالية : ومن ثم نضيف 3 ازرار احجام الايقونات كانت متفاوتة ، لكن اعتمدت على حجم 60 في 60 او اصغر بقليل بالنسبة لأحجام ايقونات الـ x2 الخاصة بحجم iPhone 7 و iPhone SE الان سوف نحتاج نستخدم الـ Stack View لأنه سوف يسهل علينا جعل الايقونات بمسافة متساوية ! قم بتحديد جميع الايقونات في أنٍ واحد ومن ثم ثم باتباع الصورة التالية : سوف تلاحظ تلاصق الايقونات مع بعض قوم بالذهاب الى خصائص الـ Stack View واجعل المسافة Spacing بـ 60 كما في الصورة التالية : واجعل الايقونات في منتصف الـ View الان قم بإضافة قيود الى الـ Stack View: لاحظ باني اضفت القيود بشكل يدوي لجعله الـ Stack View ياخد حجم الـ View الذي خلفه عند التعامل مع الـ Stack View يتوجب عليك استخدام القيود لتغير ابعاده لاحظ الفرق قبل القيود كان بالشكل التالي : بعد إضافة القيود اصبح بالشكل التالي : الان سوف تلاحظ بان الازرار والـ View متناسبة مع جميع احجام الاجهزة أيضا لاحظ بأنه عند استخدام الـ Stack View تكون لست بحاجة الى وضع قيود لكل زر على حدى ! كل ما عليك هو وضع القيود على نفس الـ Stack View فقط هذه هي قوة الـ Stack View سوف تحتاج الى استخدامه بكثره وخاصة عند عمل الـ Animation الان نعود للدرس قبل ان نبدأ تأكد بأن الـ Stack View بداخل الـ View الذي اضفناها لأننا سوف نضيف الـ View فقط الى ملف الاكواد ! يمكن التأكد بمشاهده الترتيب الـ Stack يكون بداخل الـ View الذي اضفناه سابقا كما في الصورة التالية: الان نقوم بإضافة الـ View الى ملف الاكواد ونجعله باسم Menu View وأيضا نضيف قيد الـ top الخاص بـ View ونجعله اسم menuTop يصبح ملف الاكواد بالشكل التالي import UIKit class ViewController: UIViewController { @IBOutlet weak var menuTop: NSLayoutConstraint! @IBOutlet weak var menuView: UIView! override func viewDidLoad() { super.viewDidLoad() } الان نقوم بإضافة الاكواد التالية في الـ ViewDidLoad override func viewDidLoad() { super.viewDidLoad() menuTop.constant = -65 let button = UIButton(type: .custom) button.frame = CGRect(x: 0, y: 0, width: 200, height: 40) button.setTitle("Menu", for: .normal) button.addTarget(self, action: #selector(self.menuButton), for: .touchUpInside) self.navigationItem.titleView = button menuView.layer.borderWidth = 0.5 menuView.layer.borderColor = UIColor.gray.cgColor } في البداية جعلنا الـ View مخفي بجعل القيمة -65 وبالتالي يصبح الـ View في الأعلى خلف الـ Navigation bar ومن ثم انشأنا زر من نوع .custom اعطينا ابعاد له وهيا عرض 200 وارتفاع 40 العرض تستطيع تغيره انا جعلته 200 لأنه في حال وجود زر في اليمين واليسار لن يغطي عليه اما الارتفاع فهو 40 ارتفاع الـ Navigation bar بدون شريط الساعة هو 44 لذلك حجم 40 يعتبر مناسب ومن ثم اضفنا عنوان للزر باسم Menu وبعدها اضفنا Target الي هو جعل الزر يستجيب للضغط طبعا اضفنا اسم للـ Function وهو menuButton سوف نضيفه لاحقا اخيراً قمنا بإضافة الزر بداخل العنوان الـ Navigation bar في الأسفل اضفنا الحدود حولينا الـ view وغيرنا لون الحدود الى اللون الرصاصي الان نضيف Function الزر باضافة التالي : func menuButton(button: UIButton) { } الان سوف نضيف الـ Aniamtion الفكره باختصار عند الضغط على الزر لأول مره نغير القيود الى 0 ليظهر الـ View لماذا 0 ؟ لانه عند اضافة الـ Navigation bar تصبح المسافه اسفله بقيمة 0 بالنسبة الى القيود فين حين بدون الـ Navigation bar تكون المسافة اسفل شريط الساعه هي الـ 0 وعند الضغط على الزر للمره الثانية نغير القيود الى -65 وبالتالي يختفي الـ View كيف نعلم بأمر الـ Menu هو ظاهر او مختفي ؟ الطريقة هي باستخدام متغير من نوع bool يكون true وتعني بانه مخفي ويصبح false اذا كان غير مخفي لذلك نضيف متغير في خارج الـ Function وأيضا بخارج الـ ViewDidLoad باسم var check = true ومن ثم نضيف التالي في الـ menuButton func menuButton(button: UIButton) { if check == true { UIView.animate(withDuration: 0.5, animations: { self.menuTop.constant = 0 self.view.layer.layoutIfNeeded() self.check = false }) }else { UIView.animate(withDuration: 0.5, animations: { self.menuTop.constant = -65 self.view.layer.layoutIfNeeded() self.check = true }) } } الذي قمنا به هو عمل if وقبنا بالتشيك اذا كان القيمة true فهو مخفي لذلك سوف نظهره واذا كان غير مخفي سوف نخفيه صورة بالنتيجة النهائية : اذا كنت تتسأل كيف غيرت لون الـ Navigation Bar فقم باتباع الخطوات التالية : يمكن تحميل الكود من هنا

عالم البرمجة

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