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


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

بهذه الطريقة ، يستخدم Snatch نسخته الخاصة من وحدة التحكم في التنقل مثل UITabBarController
مع التحولات التفاعلية المخصصة.
يتضمن UIKit
خيارين لوحدات التحكم في التنقل التي تسمح لك بتخصيص الانتقالات - وهما UINavigationController
و UITabBarController
. كلاهما لديه طرق navigationController(_:interactionControllerFor:)
tabBarController(_:interactionControllerFor:)
navigationController(_:interactionControllerFor:)
و tabBarController(_:interactionControllerFor:)
في مفوضيهما ، على التوالي ، مما يسمح لنا باستخدام الرسوم المتحركة التفاعلية الخاصة بنا للانتقال.
tabBarController (_ :actionControllerFor :)
navigationController (_ :actionControllerFor :)
لكني لا أريد أن أكون مقيّدًا بتنفيذ UITabBarController
أو UINavigationController
، خاصة وأننا لا نستطيع التحكم في منطقهم الداخلي. لذلك ، قررت أن أكتب وحدة تحكم مماثلة ، والآن أريد أن أقول وأظهر ما جاء فيها.
بيان المشكلة
قم UITabBarController
وحدة تحكم الحاوية الخاصة بك ، حيث يمكنك التبديل بين وحدات التحكم الفرعية باستخدام الرسوم المتحركة التفاعلية UITabBarController
، باستخدام الآلية القياسية في كل من UITabBarController
و UINavigationController
. نحن بحاجة إلى هذه الآلية القياسية لاستخدام الرسوم المتحركة الانتقالية الجاهزة من نوع UIViewControllerAnimatedTransitioning
المكتوبة بالفعل.
إعداد المشروع
عادة ما أحاول نقل الوحدات إلى أطر عمل منفصلة ، لذلك أقوم بإنشاء مشروع تطبيق جديد ، وأضيف هدف Cocoa Touch Framework
الإضافي هناك ، ثم نبعثر المصادر في المشروع للأهداف المقابلة. بهذه الطريقة أحصل على إطار عمل منفصل مع تطبيق اختبار لتصحيح الأخطاء.
إنشاء Single View App
.

Product Name
سيكون هدفنا.

انقر فوق +
لإضافة الهدف.

اختر Cocoa Touch Framework
.

نحن نسمي إطار عملنا بالاسم المناسب ، يختار Xcode تلقائيًا المشروع لهدفنا ويقدم عروض لربط الثنائية مباشرة في التطبيق. نوافق.

لن نحتاج إلى Main.storyboard
و Main.storyboard
الافتراضيين ، ViewController.swift
.

ولا تنس أيضًا إزالة القيمة من Main Interface
في هدف التطبيق في علامة التبويب "عام".

ننتقل الآن إلى AppDelegate.swift
ونترك فقط طريقة application
المحتويات التالية:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
هنا نضع وحدة التحكم الخاصة بنا في المكان الرئيسي بحيث تظهر بعد المشغل.
الآن إنشاء هذا MasterViewController
جدا. سيرتبط بالتطبيق ، لذا من المهم اختيار الهدف الصحيح عند إنشاء الملف.

سوف نرث MasterViewController
من MasterViewController
، والذي سنقوم بتطبيقه لاحقًا في إطار العمل. لا تنس تحديد import
إطار عملنا. أنا لا أقدم رمز وحدة التحكم الكامل هنا ، وتظهر الحذف من خلال علامات الحذف ...
، لقد وضعت التطبيق على GitHub ، حيث يمكنك رؤية جميع التفاصيل. في وحدة التحكم هذه ، نحن مهتمون فقط viewDidLoad()
، التي viewDidLoad()
وحدة تحكم الخلفية بالكاميرا + وحدة تحكم شفافة واحدة (النافذة الرئيسية) + وحدة التحكم التي تحتوي على بطاقة المغادرة.
import MakingSnapchatNavigation class MasterViewController: SnapchatNavigationController { override func viewDidLoad() { super.viewDidLoad()
ما الذي يحدث هنا؟ نقوم بإنشاء وحدة تحكم بكاميرا وضبطها في الخلفية باستخدام طريقة setBackground
من SnapchatNavigationController
. تحتوي وحدة التحكم هذه على صورة ممتدة للعرض بالكامل من الكاميرا. ثم نقوم بإنشاء وحدة تحكم شفافة فارغة وإضافتها إلى المصفوفة ، فهي ببساطة تمرر الصورة من الكاميرا من خلالها ، ويمكننا وضع عناصر التحكم عليها ، وإنشاء وحدة تحكم شفافة أخرى ، وإضافة التمرير إليها ، وإضافة عرض بالمحتوى داخل التمرير ، وإضافة وحدة تحكم ثانية إلى الصفيف وتعيين هذا الصفيف باستخدام طريقة setViewControllers
خاصة من الأصل SnapchatNavigationController
.
لا تنس أن تضيف طلب لاستخدام الكاميرا في Info.plist
<key>NSCameraUsageDescription</key> <string>Need camera for background</string>
في هذا الصدد ، نعتبر تطبيق الاختبار جاهزًا ، وننتقل إلى الجزء الأكثر إثارة للاهتمام - تنفيذ الإطار.
هيكل تحكم الوالدين
أولاً ، قم بإنشاء SnapchatNavigationController
فارغة ، من المهم اختيار الهدف المناسب لها. إذا تم كل شيء بشكل صحيح ، فيجب بناء التطبيق. يمكن تفريغ هذا الوضع من المشروع بالرجوع إليه .
open class SnapchatNavigationController: UIViewController { override open func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } // MARK: - Public interface /// Sets view controllers. public func setViewControllers(vcs: [UIViewController]) { } /// Sets background view. public func setBackground(vc: UIViewController) { } }
أضف الآن المكونات الداخلية التي ستتألف منها وحدة التحكم. لا أحمل جميع التعليمات البرمجية هنا ، فأنا أركز فقط على النقاط المهمة.
نقوم بتعيين المتغيرات لتخزين مجموعة وحدات التحكم التابعة. الآن قمنا بتعيين الكمية المطلوبة بدقة - 2 قطعة. في المستقبل ، سيكون من الممكن توسيع منطق وحدة التحكم للاستخدام مع أي عدد من وحدات التحكم. قمنا أيضًا بتعيين متغير لتخزين وحدة التحكم الحالية المعروضة.
private let requiredChildrenAmount = 2 // MARK: - View controllers /// top child view controller private var topViewController: UIViewController? /// all children view controllers private var children: [UIViewController] = []
إنشاء طرق العرض. نحتاج إلى عرض واحد للخلفية ، وعرض واحد للتأثير الذي نريد تطبيقه على الخلفية عند تغيير وحدة التحكم. لدينا أيضًا حاوية عرض لوحدة تحكم الطفل الحالية ومؤشر عرض يخبر المستخدم بكيفية العمل مع التنقل.
// MARK: - Views private let backgroundViewContainer = UIView() private let backgroundBlurEffectView: UIVisualEffectView = { let backgroundBlurEffect = UIBlurEffect(style: UIBlurEffectStyle.light) let backgroundBlurEffectView = UIVisualEffectView(effect: backgroundBlurEffect) backgroundBlurEffectView.alpha = 0 return backgroundBlurEffectView }() /// content view for children private let contentViewContainer = UIView() private let swipeIndicatorView = UIView()
في الكتلة التالية ، قمنا بتعيين متغيرين ، swipeAnimator
هو المسؤول عن الرسوم المتحركة ، و swipeInteractor
مسؤول عن التفاعل (القدرة على التحكم في تقدم الرسوم المتحركة) ، ويجب أن نهيئها أثناء تمهيد وحدة التحكم ، لذلك نحن swipeInteractor
.
// MARK: - Animation and transition private let swipeAnimator = AnimatedTransitioning() private var swipeInteractor: CustomSwipeInteractor!
قمنا أيضًا بتعيين التحول للمؤشر. نقوم بتحويل المؤشر بعرض الحاوية + إزاحة مزدوجة من الحافة + عرض المؤشر نفسه بحيث يكون المؤشر في الطرف المقابل للحاوية. سيصبح عرض الحاوية معروفًا أثناء التطبيق ، لذلك يتم حساب المتغير أثناء التنقل.
// MARK: - Animation transforms private var swipeIndicatorViewTransform: CGAffineTransform { get { return CGAffineTransform(translationX: -contentViewContainer.bounds.size.width + (swipeIndicatorViewXShift * 2) + swipeIndicatorViewWidth, y: 0) } }
أثناء تحميل وحدة التحكم ، فإننا نعين self
للرسوم المتحركة (سننفذ البروتوكول المقابل أدناه) ، ونهيئ المتفاعل بناءً على الرسوم المتحركة الخاصة بنا ، والذي سيتحكم في تقدمه. ونعينه أيضًا مندوبًا. سيرد المفوض على بداية إيماءة المستخدم وسيبدأ الرسوم المتحركة أو الإلغاء حسب حالة وحدة التحكم. ثم نضيف جميع طرق العرض إلى العرض الرئيسي واستدعاء setupViews()
، الذي يحدد القيود.
override open func viewDidLoad() { super.viewDidLoad() swipeAnimator.animation = self swipeInteractor = CustomSwipeInteractor(with: swipeAnimator) swipeInteractor.delegate = self view.addSubview(backgroundViewContainer) view.addSubview(backgroundBlurEffectView) view.addSubview(contentViewContainer) view.addSubview(swipeIndicatorView) setupViews() }
بعد ذلك ، ننتقل إلى منطق تثبيت وإزالة وحدات التحكم التابعة في حاوية. كل شيء هنا بسيط كما هو الحال في وثائق Apple. نستخدم الأساليب الموصوفة لهذا النوع من العمليات.
addChildViewController(vc)
- إضافة وحدة تحكم addChildViewController(vc)
إلى وحدة التحكم الحالية.
contentViewContainer.addSubview(vc.view)
- إضافة عرض وحدة التحكم إلى العرض الهرمي.
vc.view.frame = contentViewContainer.bounds
- قم vc.view.frame = contentViewContainer.bounds
العرض إلى الحاوية بأكملها. نظرًا لأننا نستخدم الإطارات هنا بدلاً من التخطيط التلقائي ، فنحن بحاجة إلى تغيير أحجامها في كل مرة يتغير فيها حجم وحدة التحكم ، وسنقوم بحذف هذا المنطق ونفترض أن الحاوية لن تغير حجم التطبيق أثناء تشغيل التطبيق.
vc.didMove(toParentViewController: self)
- وضع حد لعملية إضافة وحدة تحكم تابعة.
swipeInteractor.wireTo
- نقوم بربط وحدة التحكم الحالية بإيماءات المستخدم. في وقت لاحق سنقوم بتحليل هذه الطريقة.
// MARK: - Private methods private func addChild(vc: UIViewController) { addChildViewController(vc) contentViewContainer.addSubview(vc.view) vc.view.frame = contentViewContainer.bounds vc.didMove(toParentViewController: self) topViewController = vc let goingRight = children.index(of: topViewController!) == 0 swipeInteractor.wireTo(viewController: topViewController!, edge: goingRight ? .right : .left) } private func removeChild(vc: UIViewController) { vc.willMove(toParentViewController: nil) vc.view.removeFromSuperview() vc.removeFromParentViewController() topViewController = nil }
هناك طريقتان أخريان لن setViewControllers
كود هنا: setViewControllers
و setBackground
. في طريقة setViewControllers
نقوم ببساطة بتعيين صفيف وحدات التحكم الفرعية في المتغير المقابل addChild
التحكم الخاصة بنا addChild
لعرض أحدهم في العرض. في طريقة setBackground
نقوم بنفس الشيء مثل addChild
، فقط لوحدة التحكم في الخلفية.
منطق متحرك للحاويات
الإجمالي ، أساس وحدة التحكم الرئيسية لدينا هو:
- ينقسم UIView إلى نوعين
- قائمة الطفل UIViewController
- عنصر تحكم للرسوم المتحركة من نوع
swipeAnimator
AnimatedTransitioning
- عنصر يتحكم في المسار التفاعلي للرسوم المتحركة
swipeInteractor
من النوع CustomSwipeInteractor
- تفويض الرسوم المتحركة التفاعلية
- تنفيذ بروتوكول الرسوم المتحركة
الآن سنقوم بتحليل النقطتين الأخيرتين ، ثم ننتقل إلى تنفيذ AnimatedTransitioning
و CustomSwipeInteractor
.
تفويض الرسوم المتحركة التفاعلية
يتكون المندوب من panGestureDidStart(rightToLeftSwipe: Bool) -> Bool
واحد فقط panGestureDidStart(rightToLeftSwipe: Bool) -> Bool
طريقة panGestureDidStart(rightToLeftSwipe: Bool) -> Bool
، التي تخبر وحدة التحكم عن بداية الإيماءة واتجاهها. رداً على ذلك ، ينتظر معلومات حول ما إذا كان يمكن اعتبار الرسوم المتحركة قد بدأت.
كمندوب ، نتحقق من الترتيب الحالي لوحدات التحكم من أجل فهم ما إذا كان بإمكاننا بدء الرسوم المتحركة في الاتجاه المحدد ، وإذا كان كل شيء على ما يرام ، فإننا نبدأ طريقة transition
، مع المعلمات: وحدة التحكم التي نتحرك منها ، وحدة التحكم التي ننتقل إليها ، اتجاه الحركة ، علم التفاعل (في حالة false
، يتم تشغيل حركة انتقال محددة بوقت).
func panGestureDidStart(rightToLeftSwipe: Bool) -> Bool { guard let topViewController = topViewController, let fromIndex = children.index(of: topViewController) else { return false } let newIndex = rightToLeftSwipe ? 1 : 0 // - if newIndex > -1 && newIndex < children.count && newIndex != fromIndex { transition(from: children[fromIndex], to: children[newIndex], goingRight: rightToLeftSwipe, interactive: true) return true } return false }
دعونا نفحص على الفور نص طريقة transition
. بادئ ذي بدء ، نقوم بإنشاء سياق الرسوم المتحركة للرسوم المتحركة CustomControllerContext
. سنقوم أيضًا بتحليل هذه الفئة بعد ذلك بقليل ؛ فهي تنفذ بروتوكول UIViewControllerContextTransitioning
. في حالة UINavigationController
و UITabBarController
مثيل لتطبيق هذا البروتوكول تلقائيًا بواسطة النظام ويتم إخفاء منطقه عنا ، نحتاج إلى إنشاء منطقتنا.
let ctx = CustomControllerContext(fromViewController: from, toViewController: to, containerView: contentViewContainer, goingRight: goingRight) ctx.isAnimated = true ctx.isInteractive = interactive ctx.completionBlock = { (didComplete: Bool) in if didComplete { self.removeChild(vc: from) self.addChild(vc: to) } };
ثم ببساطة نسمي الرسوم المتحركة الثابتة أو التفاعلية. في المستقبل ، سيكون من الممكن تعليق واحدة ثابتة على أزرار علامات التبويب للتنقل بين وحدات التحكم ، في هذا المثال لن نقوم بذلك.
if interactive { // Animate with interaction swipeInteractor.startInteractiveTransition(ctx) } else { // Animate without interaction swipeAnimator.animateTransition(using: ctx) }
بروتوكول الرسوم المتحركة
يتكون بروتوكول الرسوم المتحركة TransitionAnimation
من 4 طرق:
addTo
هي طريقة مصممة لإنشاء البنية الصحيحة addTo
الفرعية في الحاوية ، بحيث يتداخل العرض السابق مع العرض الجديد وفقًا لفكرة الرسوم المتحركة.
prepare
هو الطريقة التي تسمى قبل الرسوم المتحركة لتحضير العرض.
/// Setup the views position prior to the animation start. func prepare(fromView from: UIView?, toView to: UIView?, fromLeft: Bool)
animation
- الرسوم المتحركة نفسها.
finalize
- الإجراءات اللازمة بعد الانتهاء من الرسوم المتحركة.
لن نفكر في التطبيق المستخدم ، كل شيء شفاف تمامًا هناك ، سننتقل مباشرة إلى الفئات الرئيسية الثلاثة ، والتي بفضلها تتم الرسوم المتحركة.
class CustomControllerContext: NSObject, UIViewControllerContextTransitioning
سياق الرسوم المتحركة. لوصف وظيفتها ، نشير إلى مساعدة بروتوكول UIViewControllerContextTransitioning
:
يقوم كائن السياق بتغليف المعلومات حول طرق العرض وطرق التحكم في عملية النقل. يحتوي أيضًا على تفاصيل حول كيفية تنفيذ النقل.
الشيء الأكثر إثارة للاهتمام هو حظر تكييف هذا البروتوكول:
لا تعتمد هذا البروتوكول في فصولك الخاصة ، ولا يجب عليك إنشاء كائنات تعتمد هذا البروتوكول مباشرة.
لكننا نحتاج إليه حقًا لتشغيل محرك الرسوم المتحركة القياسي ، لذلك نتكيف معه على أي حال. ليس لديها أي منطق تقريبًا ؛ فهي تقوم بتخزين الحالة فقط. لذلك ، لن أحضره حتى هنا. يمكنك مشاهدته على جيثب .
يعمل بشكل رائع على الرسوم المتحركة المحددة بوقت. ولكن عند استخدامه للرسوم المتحركة التفاعلية ، تنشأ مشكلة واحدة - UIPercentDrivenInteractiveTransition
تستدعي طريقة غير موثقة في السياق. الحل الصحيح الوحيد في هذه الحالة هو تكييف بروتوكول آخر - UIViewControllerInteractiveTransitioning
لاستخدام السياق الخاص بك.
class PercentDrivenInteractiveTransition: NSObject, UIViewControllerInteractiveTransitioning
ها هو - قلب المشروع ، مما يسمح بوجود رسوم متحركة تفاعلية في وحدات التحكم في الحاويات المخصصة. لنأخذ الأمر بالترتيب.
تم تهيئة الفئة بمعلمة واحدة من النوع UIViewControllerAnimatedTransitioning
، هذا هو البروتوكول القياسي لتحريك الانتقال بين وحدات التحكم. بهذه الطريقة يمكننا استخدام أي من الرسوم المتحركة المكتوبة بالفعل مع فصلنا.
init(with animator: UIViewControllerAnimatedTransitioning) { self.animator = animator }
الواجهة العامة بسيطة للغاية ، أربع طرق ، يجب أن تكون وظيفتها واضحة.
على المرء فقط أن يلاحظ لحظة بدء الرسوم المتحركة ، نأخذ العرض الرئيسي للحاوية ونضبط سرعة الطبقة إلى 0 ، حتى نتمكن من التحكم في تقدم الرسوم المتحركة يدويًا.
// MARK: - Public func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) { self.transitionContext = transitionContext transitionContext.containerView.superview?.layer.speed = 0 animator.animateTransition(using: transitionContext) } func updateInteractiveTransition(percentComplete: CGFloat) { setPercentComplete(percentComplete: (CGFloat(fmaxf(fminf(Float(percentComplete), 1), 0)))) } func cancelInteractiveTransition() { transitionContext?.cancelInteractiveTransition() completeTransition() } func finishInteractiveTransition() { transitionContext?.finishInteractiveTransition() completeTransition() }
ننتقل الآن إلى كتلة المنطق الخاصة لفئتنا.
يعين setPercentComplete
الإزاحة الزمنية لتقدم الرسم المتحرك للطبقة العليا ، بحساب القيمة من النسبة المئوية لإكمال الرسوم المتحركة ومدتها.
private func setPercentComplete(percentComplete: CGFloat) { setTimeOffset(timeOffset: TimeInterval(percentComplete) * duration) transitionContext?.updateInteractiveTransition(percentComplete) } private func setTimeOffset(timeOffset: TimeInterval) { transitionContext?.containerView.superview?.layer.timeOffset = timeOffset }
يتم استدعاء CompleteTransition عندما يتوقف المستخدم عن إيماءة. ننشئ هنا نسخة من فئة CADisplayLink
، والتي ستسمح لنا بإكمال الرسوم المتحركة تلقائيًا بشكل جميل من النقطة التي لم يعد فيها المستخدم يتحكم في تقدمها. نضيف displayLink
بنا إلى run loop
بحيث يقوم النظام باستدعاء displayLink
كلما احتاج إلى عرض إطار جديد على شاشة الجهاز.
private func completeTransition() { displayLink = CADisplayLink(target: self, selector: #selector(tickAnimation)) displayLink!.add(to: .main, forMode: .commonModes) }
في محددنا ، نقوم بحساب وتعيين الإزاحة المؤقتة لتقدم الرسوم المتحركة ، كما فعلنا من قبل أثناء إيماءة المستخدم ، أو نكمل الرسوم المتحركة عندما تصل إلى نقطة البداية أو النهاية.
@objc private func tickAnimation() { var timeOffset = self.timeOffset() let tick = (displayLink?.duration ?? 0) * TimeInterval(completionSpeed) timeOffset += (transitionContext?.transitionWasCancelled ?? false) ? -tick : tick; if (timeOffset < 0 || timeOffset > duration) { transitionFinished() } else { setTimeOffset(timeOffset: timeOffset) } } private func timeOffset() -> TimeInterval { return transitionContext?.containerView.superview?.layer.timeOffset ?? 0 }
عند الانتهاء من الرسوم المتحركة ، نقوم بإيقاف عرضنا displayLink
، displayLink
سرعة الطبقة ، وإذا لم يتم إلغاء الرسوم المتحركة ، أي أنها وصلت إلى إطارها النهائي ، فإننا نحسب الوقت الذي يجب أن تبدأ الرسوم المتحركة للطبقة. يمكنك معرفة المزيد حول هذا الأمر في دليل برمجة الرسوم المتحركة الأساسي ، أو في هذه الإجابة على stackoverflow.
private func transitionFinished() { displayLink?.invalidate() guard let layer = transitionContext?.containerView.superview?.layer else { return } layer.speed = 1; let wasNotCanceled = !(transitionContext?.transitionWasCancelled ?? false) if (wasNotCanceled) { let pausedTime = layer.timeOffset layer.timeOffset = 0.0; let timeSincePause = layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime layer.beginTime = timeSincePause } animator.animationEnded?(wasNotCanceled) }
class AnimatedTransitioning: NSObject, UIViewControllerAnimatedTransitioning
الفئة الأخيرة التي لم نفحصها بعد هي تنفيذ بروتوكول UIViewControllerAnimatedTransitioning
، الذي نتحكم فيه بترتيب تنفيذ طرق بروتوكول إضافة الرسوم المتحركة لدينا ، prepare
، animation
، addTo
. كل شيء هنا مريب للغاية ، تجدر الإشارة فقط إلى استخدام UIViewPropertyAnimator
لأداء الرسوم المتحركة بدلاً من UIView.animate(withDuration:animations:)
الأكثر نموذجية UIView.animate(withDuration:animations:)
. يتم ذلك بحيث كان من الممكن التحكم في تقدم الرسم المتحرك بشكل أكبر ، وإذا تم إلغاؤه ، قم بإعادته إلى موضعه finishAnimation(at: .start)
عن طريق استدعاء finishAnimation(at: .start)
، والذي يتجنب الوميض غير الضروري للإطار النهائي للرسم المتحرك على الشاشة.
الخاتمة
لقد أنشأنا عرضًا توضيحيًا جيدًا لواجهة مشابهة لتلك الموجودة في Snapchat. في إصداري ، قمت بتكوين الثوابت بحيث توجد حقول إلى يمين ويسار البطاقة ، بالإضافة إلى ذلك ، تركت الكاميرا في عرض الخلفية لإنشاء تأثير خلف البطاقة. يتم ذلك فقط لإثبات قدرات هذا النهج ، وكيف سيؤثر على أداء الجهاز ولم أتحقق من شحن البطارية.
— , - , . , - .
GitHub .
, , , !

:
Custom Container View Controller Transitions, Joachim Bondo.
Objective C. Swift.
Interactive Custom Container View Controller Transitions, Alek Åström
, Objective C, Swift.
SwipeableTabBarController
, UITabBarController
. .