لجعل البيتزا من نصفي ، استخدمنا اثنين من UICollectionViewLayout
. أنا أتحدث عن الطريقة التي كتبنا بها مثل هذا التنسيق لنظام iOS ، وما واجهناه ورفضناه.

النموذج
عندما حصلنا على مهمة صنع واجهة للبيتزا من نصفين ، كنا مرتبكين قليلاً. أريدها بشكل جميل وواضح وملائم وكبير وتفاعلي وأكثر من ذلك بكثير. أريد أن أفعل بارد.
جرب المصممون طرقًا مختلفة: شبكة من البيتزا والبطاقات الأفقية والرأسية ، لكنهم استقروا في نصف التمرير السريع. لم نكن نعرف كيف نحقق مثل هذه النتيجة ، لذلك بدأنا بتجربة واستغرقنا أسبوعين للتطبيق الأولي. حتى التصميم الخام كان قادرا على إرضاء الجميع. تم تسجيل التفاعل على الفيديو:
كيف يعمل UICollectionView
UICollectionView
هي فئة فرعية من UICollectionView
، وهي عبارة عن UIView عادية ، مع تغيير bounds
من السحب. .origin
. .origin
، نقوم بتغيير المنطقة المرئية ، وتغيير .size
يؤثر على المقياس.
عندما UICollectionView
الشاشة UICollectionView
تنشئ UICollectionView
(أو تعيد استخدام) الخلايا ، ويتم UICollectionViewLayout
قواعد عرضها في UICollectionViewLayout
. سوف نعمل معه.
إمكانيات UICollectionViewLayout
كبيرة ، يمكنك تحديد أي علاقة بين الخلايا. على سبيل المثال ، يمكنك أن تفعل ما يشبه ما يمكن أن يفعله iCarousel :

النهج الأول
ساهم تغيير مظهر تحريك الشاشة في تسهيل فهم التخطيط.
لقد اعتدنا على حقيقة أن الخلايا تتحرك حول الشاشة (المستطيل الأخضر هو شاشة الهاتف):

لكن العكس ، تتحرك هذه الشاشة بالنسبة للخلايا. لا تزال الأشجار ، يسافر هذا القطار:

في المثال ، لا تتغير إطارات الخلايا ، لكن bounds
المجموعة نفسها تتغير. Origin
هذه bounds
هو contentOffset
المعروف لنا.
لإنشاء تخطيط ، تحتاج إلى المرور على مرحلتين:
- حساب أحجام جميع الخلايا
- عرض مرئي فقط على الشاشة.
تخطيط بسيط كما هو الحال في UITableView
التخطيط لا يعمل مع الخلايا مباشرة. بدلاً من ذلك ، يستخدمون UICollectionViewLayoutAttributes
- هذه هي مجموعة المعلمات التي سيتم تطبيقها على الخلية. Frame
- الرئيسي ، هو المسؤول عن موضع وحجم الخلية. غيرها من المعالم: الشفافية ، تعويض ، الموقف في عمق الشاشة ، الخ

بادئ ذي بدء ، UICollectionViewLayout
نكتب UICollectionViewLayout
بسيط ، والذي يكرر سلوك UITableView
: الخلايا تحتل العرض بأكمله ، انتقل واحدًا تلو الآخر.
4 خطوات للأمام:
- حساب
frame
لجميع الخلايا في طريقة prepare
. - إرجاع الخلايا المرئية في
layoutAttributesForElements(in:)
بطريقة layoutAttributesForElements(in:)
. - إرجاع معلمات الخلية بواسطة الفهرس الخاص بها في
layoutAttributesForItem(at:)
. على سبيل المثال ، يتم استخدام هذه الطريقة عند استدعاء طريقة التجميع scrollToItem (في :). - إرجاع أبعاد المحتوى الناتج في
collectionViewContentSize
. لذلك سوف يكتشف الجامع مكان الحد الذي يمكنك التمرير إليه.
في معظم الأجهزة ، سيكون حجم البيتزا 300 نقطة ، ثم إحداثيات وأحجام جميع الخلايا:

لقد أجريت الحسابات في فصل منفصل. يتكون من جزأين: يحسب جميع الإطارات في المنشئ ، ثم يمنح فقط الوصول إلى النتائج النهائية:
class TableLayoutCache {
ثم في فئة التخطيط ، تحتاج فقط إلى تمرير المعلمات من ذاكرة التخزين المؤقت.
- تستدعي طريقة
prepare
حساب جميع الإطارات. - يقوم layoutAttributesForElements (في :) بتصفية الإطارات. إذا كان الإطار يتقاطع مع المنطقة المرئية ، فيجب عرض الخلية: حساب جميع السمات وإعادتها إلى صفيف الخلايا المرئية.
- layoutAttributesForItem (في :) - يحسب السمات لخلية واحدة.
class TableLayout: UICollectionViewLayout { override var collectionViewContentSize: CGSize { return cache.contentSize } override func prepare() { super.prepare() let numberOfItems = collectionView!.numberOfItems(inSection: section) cache = TableLayoutCache(itemSize: itemSize, collectionWidth: collectionView!.bounds.width) cache.recalculateDefaultFrames(numberOfItems: numberOfItems) } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let indexes = cache.visibleRows(in: rect) let cells = indexes.map { (row) -> UICollectionViewLayoutAttributes? in let path = IndexPath(row: row, section: section) let attributes = layoutAttributesForItem(at: path) return attributes }.compactMap { $0 } return cells } override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) attributes.frame = cache.defaultCellFrame(atRow: indexPath.row) return attributes } var itemSize: CGSize = .zero { didSet { invalidateLayout() } } private let section = 0 var cache = TableLayoutCache.zero }
نحن نغير وفقا لاحتياجاتك
قمنا بفرز طريقة عرض الجدول ، لكننا نحتاج الآن إلى تصميم ديناميكي. في كل وردية إصبع ، سنقوم بإعادة حساب سمات الخلايا: أخذ الإطارات التي تم حسابها بالفعل ، وتغييرها باستخدام .transform
. سيتم إجراء جميع التغييرات في فئة فرعية من PizzaHalfSelectorLayout
.
نقرأ مؤشر البيتزا الحالي
للراحة ، يمكنك نسيان contentOffset
واستبداله بعدد البيتزا الحالية. لن تحتاج بعد ذلك إلى التفكير في الإحداثيات ، فكل القرارات ستكون حول رقم البيتزا ودرجة نزوحها من وسط الشاشة.
هناك حاجة إلى طريقتين: واحد يحول contentOffset
إلى رقم بيتزا ، والعكس بالعكس:
extension PizzaHalfSelectorLayout { func contentOffset(for pizzaIndex: Int) -> CGPoint { let cellHeight = itemSize.height let screenHalf = collectionView!.bounds.height / 2 let midY = cellHeight * CGFloat(pizzaIndex) + cellHeight / 2 let newY = midY - screenHalf return CGPoint(x: 0, y: newY) } func pizzaIndex(offset: CGPoint) -> CGFloat { let cellHeight = itemSize.height let proposedCenterY = collectionView!.screenCenterYOffset(for: offset) let pizzaIndex = proposedCenterY / cellHeight return pizzaIndex } }
يتم contentOffset
حساب contentOffset
لمركز الشاشة contentOffset
:
extension UIScrollView { func screenCenterYOffset(for offset: CGPoint? = nil) -> CGFloat { let offsetY = offset?.y ?? contentOffset.y let contentOffsetY = offsetY + bounds.height / 2 return contentOffsetY } }
نتوقف عند البيتزا في المركز
أول شيء يتعين علينا القيام به هو إيقاف البيتزا في وسط الشاشة. يسأل الأسلوب targetContentOffset(forProposedContentOffset:)
أين تتوقف إذا كانت السرعة الحالية ستتوقف عند proposedContentOffset
targetContentOffset(forProposedContentOffset:)
.
الحساب بسيط: انظر إلى البيتزا التي ستسقط عليها مجموعة ContentOffset proposedContentOffset
، ثم قم بالتمرير بحيث يكون في الوسط:
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { let pizzaIndex = Int(self.pizzaIndex(offset: proposedContentOffset)) let projectedOffset = contentOffset(for: pizzaIndex) return projectedOffset }
.normal
التمرير .normal
و. .fast
. .fast
أكثر ملاءمة بالنسبة .fast
:
collectionView!.decelerationRate = .fast
ولكن هناك مشكلة واحدة: إذا مررنا قليلاً ، فسنحتاج إلى البقاء على البيتزا ، وعدم الانتقال إلى التالي. لا توجد طريقة لتغيير السرعة ، وبالتالي فإن الارتداد العكسي ، وإن كان على مسافة صغيرة ، ولكن بسرعة عالية للغاية:

الحذر ، الاختراق!
إذا لم تتغير الخلية ، فسنقوم بإرجاع contentOffset
الحالي ، ثم يتوقف التمرير. بعد ذلك ، سننتقل نحن أنفسنا إلى المكان السابق باستخدام scrollToItem
القياسي. للأسف ، عليك أيضًا التمرير بشكل غير متزامن ، بحيث يتم استدعاء الرمز بعد return
، ثم سيكون هناك القليل من التلاشي أثناء الحركة.
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { let pizzaIndex = Int(self.pizzaIndex(offset: proposedContentOffset)) let projectedOffset = contentOffset(for: pizzaIndex) let sameCell = pizzaIndex == currentPizzaIndexInt if sameCell { animateBackwardScroll(to: pizzaIndex) return collectionView!.contentOffset
انتهت المشكلة ، والآن تعود البيتزا بسلاسة:

زيادة البيتزا الوسطى
نحن إعادة فرز التخطيط عند التحرك
من الضروري جعل البيتزا المركزية تزداد تدريجياً مع اقترابها من المركز. للقيام بذلك ، تحتاج إلى حساب المعلمات ليس مرة واحدة في البداية ، ولكن في كل مرة عند الإزاحة. يعمل ببساطة:
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { return true }
الآن ، مع كل إزاحة ، سيتم استدعاء أساليب prepare
و layoutAttributesForElements(in:)
. حتى نتمكن من تحديث UICollectionViewLayoutAttributes
عدة مرات متتالية ، وتغيير الموقف والشفافية بسلاسة.
في تخطيط الجدول ، كانت الخلايا مستلقية تحت بعضها البعض وتم حساب إحداثياتها مرة واحدة. سنقوم الآن بتغييرها اعتمادًا على الموضع بالنسبة إلى مركز الشاشة. أضف طريقة ستغيرها أثناء الطيران.
في طريقة layoutAttributesForElements
، تحتاج إلى الحصول على السمات من الطبقة الفائقة ، وتصفية سمات الخلايا وتمريرها إلى طريقة updateCells
:
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let elements = super.layoutAttributesForElements(in: rect) else { return nil } let cells = elements.filter { $0.representedElementCategory == .cell } self.updateCells(cells) }
الآن سنقوم بتغيير سمات الخلية في وظيفة واحدة:
private func updateCells(_ cells: [UICollectionViewLayoutAttributes])
أثناء الحركة ، نحتاج إلى تغيير الشفافية والحجم والحفاظ على البيتزا على طول الوسط.
يتم عرض موضع الخلية بالنسبة إلى مركز الشاشة بشكل مريح في شكل طبيعي. إذا كانت الخلية في الوسط ، فإن المعلمة هي 0 ، وإذا تم إزاحتها ، فإن المعلمة تتغير من -1 عند الانتقال لأعلى ، إلى 1 عند التحرك. إذا كانت القيم أبعد من الصفر عن 1/1 ، فهذا يعني أن الخلية لم تعد مركزية وتوقفت عن التغيير. اتصلت هذه المعلمة مقياس:

تحتاج إلى حساب الفرق بين مركز الإطار ووسط الشاشة. بقسمة الفرق على ثابت ، نقوم بتطبيع القيمة ، وسيؤدي الحد الأدنى والحد الأقصى إلى مجموعة من -1 إلى +1:
extension PizzaHalfSelectorLayout { func scale(for row: Int) -> CGFloat { let frame = cache.defaultCellFrame(atRow: row) let scale = self.scale(for: frame) return scale.normalized } func scale(for frame: CGRect) -> CGFloat { let criticalOffset = PizzaHalfSelectorLayout.criticalOffsetFromCenter
حجم
وجود scale
طبيعي ، يمكنك أن تفعل أي شيء. التغييرات من -1 إلى +1 قوية جدًا ، يجب تحويلها للحجم. على سبيل المثال ، نريد تقليل الحجم إلى حد أقصى قدره 0.6 من حجم البيتزا المركزية:
private func updateCells(_ cells: [UICollectionViewLayoutAttributes]) { for cell in cells { let normScale = scale(for: cell.indexPath.row) let scale = 1 - PizzaHalfSelectorLayout.scaleFactor * abs(normScale) cell.transform = CGAffineTransform(scaleX: scale, y: scale) } }
تغيير حجم. نسبة إلى مركز الخلايا. تحتوي الخلية المركزية على normScale = 0 ، لا يتغير حجمها:

شفافية
يمكن تغيير الشفافية من خلال المعلمة alpha
. قيمة scale
التي استخدمناها في transform
مناسبة أيضًا.
cell.alpha = scale
الآن تغير البيتزا حجمها وشفافيتها. بالفعل ليست مملة كما هو الحال في الجداول العادية.

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

- وحدة التحكم الرئيسية: يتم تجميع جميع الأجزاء فيه وزر "mix".
- جهاز التحكم مع حاويتين للنصفين ، والتوقيع المركزي ومؤشرات التمرير.
- وحدة تحكم مع جامع (أبيض اليمين).
- لوحة أسفل مع السعر.
للتمييز بين اليسار والنصف الأيمن ، بدأت enum
، يتم تخزينه في التخطيط في .orientation
اتجاه.:
enum PizzaHalfOrientation { case left case right func opposite() -> PizzaHalfOrientation { switch self { case .left: return .right case .right: return .left } } }
نحول نصفي إلى المركز
توقف التخطيط السابق عن فعل ما نتوقعه: بدأ النصفان في التحول إلى مركز مجموعاتهم ، وليس إلى وسط الشاشة:

الإصلاح بسيط: تحتاج إلى تحويل الخلايا أفقياً إلى منتصف الشاشة إلى منتصف الشاشة:
func centerAlignedFrame(for element: UICollectionViewLayoutAttributes, scale: CGFloat) -> CGRect { let hOffset = horizontalOffset(for: element, scale: scale) switch orientation { case .left:
يتم التحكم على الفور بين نصفي على الفور.

إزاحة الخلية
كان من السهل وضع البيتزا المستديرة في مربع ، ولأنك تحتاج إلى نصف مربع:

يمكنك إعادة كتابة حساب الإطارات: نصف العرض ، محاذاة الإطارات إلى المركز بشكل مختلف لكل نصف. من أجل البساطة ، ما contentMode
سوى تغيير contentMode
صورة الصورة داخل الخلية:
class PizzaHalfCell: UICollectionViewCell { var halfOrientation: PizzaHalfOrientation = .left { didSet { imageView?.contentMode = halfOrientation == .left ? .topRight : .topLeft self.setNeedsLayout() } } }

اضغط على البيتزا عموديا
انخفضت البيتزا ، لكن المسافة بين مراكزها لم تتغير ، ظهرت فجوات كبيرة. يمكنك تعويضهم بنفس طريقة محاذاة نصفي المركز.
private func verticalOffset(for element: UICollectionViewLayoutAttributes, scale: CGFloat) -> CGFloat { let offsetFromCenter = offsetFromScreenCenter(element.frame) let vOffset: CGFloat = PizzaHalfSelectorLayout.verticalOffset( offsetFromCenter: offsetFromCenter, scale: scale) return vOffset } static func verticalOffset(offsetFromCenter: CGFloat, scale: CGFloat) -> CGFloat { return -offsetFromCenter / 4 * scale }
نتيجة لذلك ، تبدو جميع التعويضات كما يلي:
func centerAlignedFrame(for element: UICollectionViewLayoutAttributes, scale: CGFloat) -> CGRect { let hOffset = horizontalOffset(for: element, scale: scale) let vOffset = verticalOffset (for: element, scale: scale) switch orientation { case .left:
وإعداد الخلية يشبه هذا:
private func updateCells(_ cells: [UICollectionViewLayoutAttributes]) { for cell in cells { let normScale = scale(for: cell.indexPath.row) let scale = 1 - PizzaHalfSelectorLayout.scaleFactor * abs(normScale) cell.alpha = scale cell.frame = centerAlignedFrame(for: cell, scale: scale) cell.transform = CGAffineTransform(scaleX: scale, y: scale) cell.zIndex = cellZLevel } }
لا تخلط بين: يجب أن يكون إعداد الإطار قبل التحويل. إذا قمت بتغيير الترتيب ، فستكون نتيجة الحسابات مختلفة تمامًا.
القيام به! لقد قطعنا نصفيها وربطناها بالمركز:

إضافة تعليق
يتم إنشاء الرؤوس بنفس طريقة الخلايا ، فقط بدلاً من UICollectionViewLayoutAttributes(forCellWith:)
تحتاج إلى استخدام المُنشئ UICollectionViewLayoutAttributes(forSupplementaryViewOfKind:)
وإعادتها مع معلمات الخلية في layoutAttributesForElements(in:)
أولاً ، نحن نصف طريقة للحصول على رأس بواسطة IndexPath
:
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { let attributes = UICollectionViewLayoutAttributes( forSupplementaryViewOfKind: elementKind, with: indexPath) attributes.frame = defaultFrameForHeader(at: indexPath) attributes.zIndex = headerZLevel return attributes }
يتم إخفاء حساب الإطار في طريقة defaultFrameForHeader
( defaultFrameForHeader
لاحقًا).
يمكنك الآن الحصول على IndexPath
للخلايا المرئية وإظهار IndexPath
لها:
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { … let visiblePaths = cells.map { $0.indexPath } let headers = self.headers(for: visiblePaths) updateHeaders(headers) return cells + headers }
يتم استدعاء استدعاء دالة طويلة بشكل رهيب في headers(for:)
الطريقة:
func headers(for paths: [IndexPath]) -> [UICollectionViewLayoutAttributes] { let headers: [UICollectionViewLayoutAttributes] = paths.map { layoutAttributesForSupplementaryView( ofKind: UICollectionView.elementKindSectionHeader, at: $0) }.compactMap { $0 } return headers }
zIndex
الآن أصبحت الخلايا والتوقيعات على نفس المستوى من "الارتفاع" ، بحيث يمكن طبقاتها فوق بعضها البعض. للحفاظ على الرؤوس أعلى دائمًا ، zIndex
أكبر من الصفر. على سبيل المثال ، 100.
نصلح موقف (في الواقع لا)
التواقيع المثبتة على الشاشة محيرة قليلاً. تريد الإصلاح ، ولكن بالعكس ، يمكنك التحرك باستمرار مع bounds
:

كل شيء بسيط في الكود: نحصل على موضع التوقيع على الشاشة contentOffset
إلى contentOffset
:
func defaultFrameForHeader(at indexPath: IndexPath) -> CGRect { let inset = max(collectionView!.layoutMargins.left, collectionView!.layoutMargins.right) let y = collectionView!.bounds.minY let height = collectionView!.bounds.height let width = collectionView!.bounds.width let headerWidth = width - inset * 2 let headerHeight: CGFloat = 60 let vOffset: CGFloat = 30 let screenY = (height - itemSize.height) / 2 - headerHeight / 2 - vOffset return CGRect(x: inset, y: y + screenY, width: headerWidth, height: headerHeight) }
يمكن أن يكون ارتفاع التواقيع مختلفًا ، فمن الأفضل مراعاة ذلك في المفوض (وذاكرة التخزين المؤقت هناك).
توقيعات موحية
كل شيء مشابه جدا للخلايا. بناءً على scale
الحالي ، يمكنك حساب شفافية الخلية. يمكن ضبط الإزاحة عبر .transform
، وبالتالي سيتم نقل النقش بالنسبة .transform
:
func updateHeaders(_ headers: [UICollectionViewLayoutAttributes]) { for header in headers { let scale = self.scale(for: header.indexPath.row) let alpha = 1 - abs(scale) header.alpha = alpha let translation = 20 * scale header.transform = CGAffineTransform(translationX: 0, y: translation) } }

الأمثل
بعد إضافة الرؤوس ، انخفض الأداء بشكل كبير. لقد حدث ذلك لأننا UICollectionViewLayoutAttributes
التواقيع ، لكن مازلنا UICollectionViewLayoutAttributes
إلى UICollectionViewLayoutAttributes
. من هذا ، يتم إضافة الرؤوس إلى التسلسل الهرمي ، والمشاركة في التخطيط ، ولكن لا يتم عرضها. أظهرنا فقط الخلايا التي تتقاطع مع bounds
الحالية ، والرؤوس تحتاج إلى تصفية بواسطة alpha
:
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { … let visibleHeaders = headers.filter { $0.alpha > 0 } return cells + visibleHeaders }
نحن نتفق مع التوقيع المركزي (الوصفة الأصلية)
لقد قمنا بعمل رائع ، ولكن كان هناك تناقض في الواجهة - إذا اخترت نصفين متطابقين ، فسيتحولان إلى بيتزا منتظمة.
قررنا أن نترك الأمر بهذه الطريقة ، لكننا نعالج الحالة بشكل صحيح ، ونظهر أنها بيتزا عادية. مهمتنا الجديدة هي إظهار تسمية واحدة في الوسط للبيتزا متطابقة ، والاختباء على طول الحواف.

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

الفيديو يتحول مثل هذا:

استغرق الأمر ثلاثة تغييرات:
- بدلا من الإطارات من ذاكرة التخزين المؤقت سنتخذ
centerPizzaFrame
. - باستخدام
scale
اقرأ الإزاحة من هذا الإطار. إعادة حساب zIndex
.
func centerAlignedFrame(for element: UICollectionViewLayoutAttributes, scale: CGFloat) -> CGRect { let hOffset = self.horizontalOffset(for: element, scale: scale) let vOffset = self.verticalOffset (for: element, scale: scale) switch self.pizzaHalf { case .left:
, zIndex
. : , , zIndex
.
private func updateCells(_ cells: [UICollectionViewLayoutAttributes]) { for cell in cells { let normScale = self.scale(for: cell.indexPath.row) let scale = 1 - PizzaHalfSelectorLayout.scaleFactor * abs(normScale) cell.alpha = 1
, , :
row: zIndex` 0: 0 1: 1 2: 2 3: 10 — 4: 5 5: 4 6: 3 7: 2 8: 1 9: 0
, .
, : , . : , .
, :
- ,
- , : , ,
- ,
- ,
- -,
- «»,
- Voice Over.
:
github , .
UICollectionViewLayout
, A Tour of UICollectionView
, .