باستخدام UIViewPropertyAnimator لإنشاء الرسوم المتحركة المخصصة

إنشاء الرسوم المتحركة رائع. إنها جزء مهم من إرشادات واجهة الإنسان لنظام التشغيل iOS . تساعد الرسوم المتحركة في جذب انتباه المستخدم إلى الأشياء المهمة أو ببساطة جعل التطبيق أقل مملة.

هناك عدة طرق لتنفيذ الرسوم المتحركة في نظام iOS. ربما تكون الطريقة الأكثر شيوعًا هي استخدام UIView.animate (withDuration: animations :) . يمكنك تحريك طبقة صورة باستخدام CABasicAnimation . بالإضافة إلى ذلك ، يسمح لك UIKit بتكوين رسوم متحركة مخصصة لعرض وحدة التحكم باستخدام UIViewControllerTransitioningDelegate .

في هذه المقالة ، أريد مناقشة طريقة أخرى مثيرة لتحريك المشاهدات - UIViewPropertyAnimator . توفر هذه الفئة العديد من وظائف الإدارة أكثر من سابقتها ، UIView.animat e. استخدمها لإنشاء رسوم متحركة مؤقتة وتفاعلية ومقاطعة. بالإضافة إلى ذلك ، من الممكن تغيير الرسوم المتحركة بسرعة.

تقديم UIViewPropertyAnimator


تم تقديم UIViewPropertyAnimator في iOS 10 . انها تسمح لك لإنشاء الرسوم المتحركة بطريقة وجوه المنحى. دعونا نلقي نظرة على مثال للرسوم المتحركة التي تم إنشاؤها باستخدام UIViewPropertyAnimator .

صورة

هكذا كان الحال عند استخدام UIView.

UIView.animate(withDuration: 0.3) { view.frame = view.frame.offsetBy(dx: 100, dy: 0) } 

وإليك كيفية القيام بذلك باستخدام UIViewPropertyAnimator :

 let animator = UIViewPropertyAnimator(duration:0.3, curve: .linear) { view.frame = view.frame.offsetBy(dx:100, dy:0) } animator.startAnimation() 

إذا كنت بحاجة إلى التحقق من الرسوم المتحركة ، فما عليك سوى إنشاء ملعب وتشغيل الشفرة كما هو موضح أدناه. كلا شظايا رمز سيؤدي إلى نفس النتيجة.

صورة

قد تعتقد أنه في هذا المثال لا يوجد فرق كبير. فما الفائدة من إضافة طريقة جديدة لإنشاء الرسوم المتحركة؟ تصبح UIViewPropertyAnimator أكثر فائدة عندما تحتاج إلى إنشاء رسوم متحركة تفاعلية.

الرسوم المتحركة التفاعلية والمتقطعة


هل تتذكر لفتة "Finger Swipe to Unlock Device" الكلاسيكية؟ أو الإيماءة "حرك إصبعك على الشاشة من أسفل إلى أعلى" لفتح مركز التحكم؟ هذه أمثلة رائعة للرسوم المتحركة التفاعلية. يمكنك البدء في تحريك الصورة بإصبعك ، ثم حررها وستعود الصورة إلى وضعها الأصلي. بالإضافة إلى ذلك ، يمكنك التقاط الصورة أثناء الرسوم المتحركة ومتابعة تحريكها بإصبعك.

لا توفر الرسوم المتحركة UIView طريقة سهلة للتحكم في النسبة المئوية لإنجاز الرسوم المتحركة. لا يمكنك إيقاف الرسوم المتحركة في منتصف الحلقة ومتابعة تنفيذها بعد المقاطعة.

في هذه الحالة ، سنتحدث عن UIViewPropertyAnimator . بعد ذلك ، سننظر في كيف يمكنك بسهولة إنشاء رسوم متحركة تفاعلية بالكامل ومقاطعة وانعكاس الرسوم المتحركة في خطوات قليلة.

إعداد مشروع الإطلاق


تحتاج أولاً إلى تنزيل مشروع بدء التشغيل . بمجرد فتح الأرشيف ، ستجد تطبيق CityGuide الذي يساعد المستخدمين على التخطيط لقضاء عطلاتهم. يمكن للمستخدم التمرير خلال قائمة المدن ، ثم فتح وصف مفصل مع معلومات مفصلة حول المدينة التي يحبها.

النظر في شفرة المصدر للمشروع قبل أن نبدأ خلق الرسوم المتحركة الجميلة. إليك ما يمكنك العثور عليه في مشروع من خلال فتحه في Xcode :

  1. ViewController.swift : وحدة تحكم التطبيق الرئيسية مع UICollectionView يعرض مجموعة من كائنات المدينة .
  2. CityCollectionViewCell.swift: خلية لعرض المدينة . في الواقع ، في هذه المقالة ، سيتم تطبيق معظم التغييرات على هذه الفئة. قد تلاحظ أن descriptionLabel و closeButton تم تعريفهما بالفعل في الفصل. ومع ذلك ، بعد بدء التطبيق ، سيتم إخفاء هذه الكائنات. لا تقلق ، سوف تكون مرئية في وقت لاحق قليلا. هذه الفئة لديها أيضا خصائص الجمع و الفهرس . في وقت لاحق سيتم استخدامها للرسوم المتحركة.
  3. CityCollectionViewFlowLayout.swift: هذه الفئة مسؤولة عن التمرير الأفقي. لن نغيرها بعد.
  4. City.swift : يحتوي طراز التطبيق الرئيسي على طريقة تم استخدامها في ViewController.
  5. Main.storyboard: هناك يمكنك العثور على واجهة المستخدم ل ViewController و CityCollectionViewCell .

دعنا نحاول بناء وتشغيل نموذج التطبيق. نتيجة لذلك ، نحصل على ما يلي.

cityguideapp-iphone8

تنفيذ نشر وانهيار الرسوم المتحركة


بعد بدء التطبيق ، يتم عرض قائمة المدن. لكن لا يمكن للمستخدم التفاعل مع الكائنات في شكل خلايا. تحتاج الآن إلى عرض معلومات لكل مدينة عندما ينقر المستخدم على إحدى الخلايا. ألقِ نظرة على الإصدار النهائي للتطبيق. إليك ما يلزم فعلاً لتطويره:

صورة

الرسوم المتحركة تبدو جيدة ، أليس كذلك؟ ولكن لا يوجد شيء خاص هنا ، إنه مجرد المنطق الأساسي لـ UIViewPropertyAnimator . دعونا نرى كيفية تنفيذ هذا النوع من الرسوم المتحركة. قم بإنشاء طريقة collectionView (_: didSelectItemAt) ، أضف جزء التعليمات البرمجية التالي إلى نهاية ملف ViewController :

 func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { let selectedCell = collectionView.cellForItem(at: indexPath)! as! CityCollectionViewCell selectedCell.toggle() } 

نحن الآن بحاجة إلى تنفيذ طريقة التبديل . دعنا ننتقل إلى CityCollectionViewCell.swift وتنفيذ هذه الطريقة.

أولاً ، أضف تعداد الحالة إلى أعلى الملف ، مباشرةً قبل إعلان فئة CityCollectionViewCell . تسمح لك هذه القائمة بتتبع حالة الخلية:

 private enum State { case expanded case collapsed var change: State { switch self { case .expanded: return .collapsed case .collapsed: return .expanded } } } 

أضف بعض الخصائص للتحكم في الرسوم المتحركة في فئة CityCollectionViewCell :

 private var initialFrame: CGRect? private var state: State = .collapsed private lazy var animator: UIViewPropertyAnimator = { return UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut) }() 

يتم استخدام متغير initialFrame لتخزين إطار الخلية حتى يتم تشغيل الرسوم المتحركة. يتم استخدام الحالة لتتبع ما إذا كانت الخلية موسعة أو مطوية. ويستخدم متغير الرسوم المتحركة للتحكم في الرسوم المتحركة.

أضف الآن طريقة التبديل واستدعيها من طريقة الإغلاق ، على سبيل المثال:

 @IBAction func close(_ sender: Any) { toggle() } func toggle() { switch state { case .expanded: collapse() case .collapsed: expand() } } 

ثم نضيف طريقتين أخريين: expand () و طي () . سنواصل تنفيذها. أولاً ، نبدأ باستخدام طريقة expandd () :

 private func expand() { guard let collectionView = self.collectionView, let index = self.index else { return } animator.addAnimations { self.initialFrame = self.frame self.descriptionLabel.alpha = 1 self.closeButton.alpha = 1 self.layer.cornerRadius = 0 self.frame = CGRect(x: collectionView.contentOffset.x, y:0, width: collectionView.frame.width, height: collectionView.frame.height) if let leftCell = collectionView.cellForItem(at: IndexPath(row: index - 1, section: 0)) { leftCell.center.x -= 50 } if let rightCell = collectionView.cellForItem(at: IndexPath(row: index + 1, section: 0)) { rightCell.center.x += 50 } self.layoutIfNeeded() } animator.addCompletion { position in switch position { case .end: self.state = self.state.change collectionView.isScrollEnabled = false collectionView.allowsSelection = false default: () } } animator.startAnimation() } 

كم رمز. اسمحوا لي أن أشرح ما يحدث خطوة بخطوة:

  1. أولاً ، تحقق مما إذا كانت مجموعة طريقة العرض والفهرس لا تساوي الصفر. خلاف ذلك ، لن نكون قادرين على بدء الرسوم المتحركة.
  2. بعد ذلك ، ابدأ في إنشاء الرسوم المتحركة عن طريق استدعاء animator.addAnimations .
  3. بعد ذلك ، احفظ الإطار الحالي ، والذي يستخدم لاستعادته في الرسوم المتحركة.
  4. ثم قمنا بتعيين قيمة ألفا لوصف لابيل وإغلاق زر لجعلها مرئية.
  5. بعد ذلك ، قم بإزالة الزاوية الدائرية وتعيين إطار جديد للخلية. سيتم عرض الخلية في وضع ملء الشاشة.
  6. بعد ذلك نقوم بنقل الخلايا المجاورة.
  7. اتصل الآن بالطريقة animator.addComplete () لتعطيل تفاعل صورة المجموعة. هذا يمنع المستخدمين من التمرير أثناء توسيع الخلية. قم أيضًا بتغيير الحالة الحالية للخلية. من المهم تغيير حالة الخلية ، وبعد ذلك تنتهي الرسوم المتحركة.

الآن إضافة الرسوم المتحركة الإلتواء. باختصار ، نحن فقط نعيد الخلية إلى حالتها السابقة:

 private func collapse() { guard let collectionView = self.collectionView, let index = self.index else { return } animator.addAnimations { self.descriptionLabel.alpha = 0 self.closeButton.alpha = 0 self.layer.cornerRadius = self.cornerRadius self.frame = self.initialFrame! if let leftCell = collectionView.cellForItem(at: IndexPath(row: index - 1, section: 0)) { leftCell.center.x += 50 } if let rightCell = collectionView.cellForItem(at: IndexPath(row: index + 1, section: 0)) { rightCell.center.x -= 50 } self.layoutIfNeeded() } animator.addCompletion { position in switch position { case .end: self.state = self.state.change collectionView.isScrollEnabled = true collectionView.allowsSelection = true default: () } } animator.startAnimation() } 

حان الآن وقت ترجمة التطبيق وتشغيله. حاول النقر فوق الخلية وسترى الرسوم المتحركة. لإغلاق الصورة ، انقر على أيقونة الصليب في الزاوية اليمنى العليا.

مضيفا معالجة لفتة


يمكنك المطالبة بتحقيق نفس النتيجة باستخدام UIView.animate . ما الهدف من استخدام UIViewPropertyAnimator ؟

حسنًا ، لقد حان الوقت لجعل الرسوم المتحركة تفاعلية. أضف UIPanGestureRecognizer وخاصية جديدة تسمى popupOffset لتتبع مقدار نقل الخلية. دعنا نعلن هذه المتغيرات في فئة CityCollectionViewCell :

 private let popupOffset: CGFloat = (UIScreen.main.bounds.height - cellSize.height)/2.0 private lazy var panRecognizer: UIPanGestureRecognizer = { let recognizer = UIPanGestureRecognizer() recognizer.addTarget(self, action: #selector(popupViewPanned(recognizer:))) return recognizer }() 

ثم أضف الطريقة التالية لتسجيل تعريف التمرير السريع:

 override func awakeFromNib() { self.addGestureRecognizer(panRecognizer) } 

أنت الآن بحاجة إلى إضافة طريقة popupViewPanned لتتبع لفتة التمرير السريع. الصق التعليمة البرمجية التالية في CityCollectionViewCell :

 @objc func popupViewPanned(recognizer: UIPanGestureRecognizer) { switch recognizer.state { case .began: toggle() animator.pauseAnimation() case .changed: let translation = recognizer.translation(in: collectionView) var fraction = -translation.y / popupOffset if state == .expanded { fraction *= -1 } animator.fractionComplete = fraction case .ended: animator.continueAnimation(withTimingParameters: nil, durationFactor: 0) default: () } } 

هناك ثلاث ولايات. في بداية الإيماءة ، نقوم بتهيئة الرسوم المتحركة باستخدام طريقة التبديل وإيقافها على الفور. بينما يقوم المستخدم بسحب الخلية ، نقوم بتحديث الرسوم المتحركة عن طريق تعيين خصائص مضاعف الكسر . هذا هو السحر الرئيسي للرسوم المتحركة ، والذي يسمح لهم بالتحكم. أخيرًا ، عندما يقوم المستخدم بإصدار إصبعه ، يتم استدعاء طريقة الرسوم المتحركة ContinueAnimation لمتابعة الرسوم المتحركة. ثم ستنتقل الخلية إلى الموضع المستهدف.

بعد بدء التطبيق ، يمكنك سحب الخلية لأعلى لتوسيعها. ثم اسحب الخلية الموسعة لأسفل لطيها.

تبدو الرسوم المتحركة الآن جيدة ، لكن مقاطعة الرسوم المتحركة في الوسط غير ممكنة. لذلك ، لجعل الرسوم المتحركة تفاعلية بالكامل ، تحتاج إلى إضافة وظيفة أخرى - المقاطعة. يمكن للمستخدم بدء الرسوم المتحركة للتمديد / الانهيار كالمعتاد ، ولكن يجب إيقاف الرسوم المتحركة مباشرةً بعد أن ينقر المستخدم على الخلية أثناء دورة الرسوم المتحركة.

للقيام بذلك ، تحتاج إلى حفظ تقدم الرسوم المتحركة ثم أخذ هذه القيمة في الاعتبار من أجل حساب النسبة المئوية لإكمال الرسوم المتحركة.

أولاً ، أعلن خاصية جديدة في CityCollectionViewCell :

 private var animationProgress: CGFloat = 0 

ثم قم بتحديث الكتلة .began للأسلوب popupViewPanned بسطر التعليمات البرمجية التالي لتذكر التقدم:

 animationProgress = animator.fractionComplete 

في الكتلة .changed ، تحتاج إلى تحديث السطر التالي من التعليمات البرمجية لحساب نسبة الإكمال بشكل صحيح:

 animator.fractionComplete = fraction + animationProgress 

الآن التطبيق جاهز للاختبار. قم بتشغيل المشروع وشاهد ما يحدث. إذا تم تنفيذ جميع الإجراءات بشكل صحيح باتباع إرشاداتي ، فيجب أن تظهر الرسوم المتحركة بالشكل التالي:

صورة

عكس الرسوم المتحركة


يمكنك العثور على خطأ للتنفيذ الحالي. إذا قمت بسحب الخلية قليلاً ثم إعادتها إلى موضعها الأصلي ، فستستمر الخلية في التوسع عند تحرير إصبعك. دعونا إصلاح هذه المشكلة لجعل الرسوم المتحركة التفاعلية أفضل.
لنقم بتحديث .end بلوك طريقة popupViewPanned ، كما هو موضح أدناه:

 let velocity = recognizer.velocity(in: self) let shouldComplete = velocity.y > 0 if velocity.y == 0 { animator.continueAnimation(withTimingParameters: nil, durationFactor: 0) break } switch state { case .expanded: if !shouldComplete && !animator.isReversed { animator.isReversed = !animator.isReversed } if shouldComplete && animator.isReversed { animator.isReversed = !animator.isReversed } case .collapsed: if shouldComplete && !animator.isReversed { animator.isReversed = !animator.isReversed } if !shouldComplete && animator.isReversed { animator.isReversed = !animator.isReversed } } animator.continueAnimation(withTimingParameters: nil, durationFactor: 0) 

الآن نأخذ في الاعتبار سرعة الإيماءة لتحديد ما إذا كان يجب عكس الحركة.

وأخيرًا ، أدخل سطرًا آخر من التعليمات البرمجية في الكتلة .changed . ضع هذا الرمز على يمين حساب animator.fractionComplete .

 if animator.isReversed { fraction *= -1 } 

لنقم بتشغيل التطبيق مرة أخرى. الآن يجب أن تعمل كل شيء دون فشل.

صورة

إصلاح لفتة عموم


لذلك ، أكملنا تنفيذ الرسوم المتحركة باستخدام UIViewPropertyAnimator . ومع ذلك ، هناك خطأ واحد غير سارة. قد تكون قابلتها أثناء اختبار التطبيق. المشكلة هي أن التمرير للخلية أفقيا غير ممكن. دعونا نحاول التمرير يسارًا / يمينًا عبر الخلايا ، ونحن نواجه المشكلة.

السبب الرئيسي يتعلق بـ UIPanGestureRecognizer الذي أنشأناه . كما أنه يمسك بالإيماءة السريعة ، ويتعارض مع أداة التعرف على الإيماءات المدمجة UICollectionView .

على الرغم من أنه لا يزال بإمكان المستخدم التمرير عبر أعلى / أسفل الخلايا أو المسافة بين الخلايا للتنقل عبر المدن ، إلا أنني لا أزال أحب واجهة المستخدم السيئة هذه. دعونا إصلاحه.

لحل التعارضات ، نحتاج إلى تطبيق طريقة تفويض تسمى gestRecognizerShouldBegin (_ :) . تتحكم هذه الطريقة فيما إذا كان يجب على أداة التعرف على الإيماءات الاستمرار في تفسير اللمسات. إذا رجعت خطأ في الطريقة ، فسيتجاهل أداة التعرف على الإيماءات اللمسات. إذن ما سنفعله هو إعطاء أداة التعرف على بانوراما الخاصة بنا القدرة على تجاهل الحركات الأفقية.

للقيام بذلك ، دعنا نعيّن مفوض أداة التعرف على عمومنا. أدخل سطر الكود التالي في تهيئة panRecognizer (يمكنك وضع الكود مباشرةً قبل أداة التعرف على الإرجاع :

 recognizer.delegate = self 

بعد ذلك ، نطبق طريقة gestRecognizerShouldBegin (_ :) كما يلي:

 override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { return abs((panRecognizer.velocity(in: panRecognizer.view)).y) > abs((panRecognizer.velocity(in: panRecognizer.view)).x) } 

سنفتح / نغلق إذا كانت سرعته الرأسية أكبر من السرعة الأفقية.

عظيم! دعونا اختبار التطبيق مرة أخرى. يمكنك الآن التنقل حول قائمة المدن عن طريق تمرير اليسار / اليمين عبر الخلايا.

صورة

المكافأة: ميزات مزامنة مخصصة


قبل الانتهاء من هذا البرنامج التعليمي ، دعونا نتحدث عن وظائف التوقيت. هل ما زلت تتذكر الحالة عندما طلب منك المطور تنفيذ وظيفة مزامنة مخصصة للرسوم المتحركة التي تنشئها؟

عادة ، يجب عليك تغيير UIView.animation إلى CABasicAnimation أو لفه في CATransaction . مع UIViewPropertyAnimator ، يمكنك بسهولة تنفيذ وظائف توقيت مخصص.

تُفهم وظائف التوقيت (أو وظائف التخفيف) على أنها وظائف سرعة الحركة التي تؤثر على معدل تغيير خاصية متحركة أو أخرى. هناك أربعة أنواع مدعومة حاليًا: easInOut و easin و easOut وخطي.

استبدال التهيئة animator مع هذه وظائف توقيت (حاول رسم منحنى Bezier مكعب الخاص بك) كما يلي:

 private lazy var animator: UIViewPropertyAnimator = { let cubicTiming = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.17, y: 0.67), controlPoint2: CGPoint(x: 0.76, y: 1.0)) return UIViewPropertyAnimator(duration: 0.3, timingParameters: cubicTiming) }() 

بدلاً من ذلك ، بدلاً من استخدام معلمات التزامن المكعب ، يمكنك أيضًا استخدام التزامن النابض ، على سبيل المثال:

  let springTiming = UISpringTimingParameters(mass: 1.0, stiffness: 2.0, damping: 0.2, initialVelocity: .zero) 

حاول أن تبدأ المشروع مرة أخرى وشاهد ما يحدث.

استنتاج


من خلال UIViewPropertyAnimator ، يمكنك تحسين الشاشات الثابتة وتفاعل المستخدم من خلال الرسوم المتحركة التفاعلية.

أعلم أنه لا يمكنك الانتظار حتى تدرك ما تعلمته في مشروعك. إذا قمت بتطبيق هذا النهج في مشروعك ، فسيكون رائعًا ، اسمحوا لي أن أعرف هذا عن طريق ترك تعليق أدناه.

كمرجع ، يمكنك هنا تنزيل المسودة النهائية .

روابط أخرى


الرسوم المتحركة الاحترافية باستخدام UIKit - https://developer.apple.com/videos/play/wwdc2017/230/

وثائق UIViewPropertyAnimator لمطوري Apple - https://developer.apple.com/documentation/uikit/uiviewpropertyanimator

Source: https://habr.com/ru/post/ar458970/


All Articles