рд╣рд┐рд╕реНрд╕реЛрдВ рд╕реЗ рдкрд┐рдЬреНрдЬрд╛ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдордиреЗ рджреЛ UICollectionViewLayout
рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ред рдореИрдВ рдЗрд╕ рдмрд╛рд░реЗ рдореЗрдВ рдмрд╛рдд рдХрд░ рд░рд╣рд╛ рд╣реВрдВ рдХрд┐ рд╣рдордиреЗ iOS рдХреЗ рд▓рд┐рдП рдЗрд╕ рддрд░рд╣ рдХрд╛ рдПрдХ рд▓реЗрдЖрдЙрдЯ рдХреИрд╕реЗ рд▓рд┐рдЦрд╛, рд╣рдордиреЗ рдХреНрдпрд╛ рд╕рд╛рдордирд╛ рдХрд┐рдпрд╛ рдФрд░ рдХреНрдпрд╛ рдЗрдирдХрд╛рд░ рдХрд┐рдпрд╛ред

рдкреНрд░реЛрдЯреЛрдЯрд╛рдЗрдк
рдЬрдм рд╣рдореЗрдВ рдЖрдзреЗ рд╣рд┐рд╕реНрд╕реЛрдВ рд╕реЗ рдкрд┐рдЬреНрдЬрд╛ рдХреЗ рд▓рд┐рдП рдПрдХ рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдмрдирд╛рдиреЗ рдХрд╛ рдХрд╛рдо рдорд┐рд▓рд╛, рддреЛ рд╣рдо рдереЛрдбрд╝рд╛ рдЙрд▓рдЭрди рдореЗрдВ рдереЗред рдореИрдВ рдЗрд╕реЗ рдЦреВрдмрд╕реВрд░рддреА рд╕реЗ, рдФрд░ рд╕реНрдкрд╖реНрдЯ рд░реВрдк рд╕реЗ, рдФрд░ рдЖрд╕рд╛рдиреА рд╕реЗ, рдФрд░ рдмрдбрд╝реЗ, рдФрд░ рдЕрдВрддрдГрдХреНрд░рд┐рдпрд╛рддреНрдордХ рдФрд░ рдмрд╣реБрдд рдХреБрдЫ рдЪрд╛рд╣рддрд╛ рд╣реВрдВред рдореИрдВ рдХреВрд▓ рд░рд╣рдирд╛ рдЪрд╛рд╣рддрд╛ рд╣реВрдВред
рдбрд┐рдЬрд╛рдЗрдирд░реЛрдВ рдиреЗ рд╡рд┐рднрд┐рдиреНрди рддрд░реАрдХреЛрдВ рдХреА рдХреЛрд╢рд┐рд╢ рдХреА: рдкрд┐рдЬреНрдЬрд╛, рдХреНрд╖реИрддрд┐рдЬ рдФрд░ рдКрд░реНрдзреНрд╡рд╛рдзрд░ рдХрд╛рд░реНрдб рдХрд╛ рдПрдХ рдЧреНрд░рд┐рдб, рд▓реЗрдХрд┐рди рдЖрдзрд╛ рд╕реНрд╡рд╛рдЗрдк рдкрд░ рдмрд╕рд╛ред рд╣рдореЗрдВ рдирд╣реАрдВ рдкрддрд╛ рдерд╛ рдХрд┐ рдЗрд╕ рддрд░рд╣ рдХреЗ рдкрд░рд┐рдгрд╛рдо рдХреЛ рдХреИрд╕реЗ рдкреНрд░рд╛рдкреНрдд рдХрд┐рдпрд╛ рдЬрд╛рдП, рдЗрд╕рд▓рд┐рдП рд╣рдордиреЗ рдПрдХ рдкреНрд░рдпреЛрдЧ рд╢реБрд░реВ рдХрд┐рдпрд╛ рдФрд░ рдкреНрд░реЛрдЯреЛрдЯрд╛рдЗрдк рдХреЗ рд▓рд┐рдП рджреЛ рд╕рдкреНрддрд╛рд╣ рдХрд╛ рд╕рдордп рд▓рд┐рдпрд╛ред рдпрд╣рд╛рдВ рддрдХ тАЛтАЛрдХрд┐ рдХреНрд░реВрдб рд▓реЗрдЖрдЙрдЯ рднреА рд╕рднреА рдХреЛ рдЦреБрд╢ рдХрд░рдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рдерд╛ред рд╡реАрдбрд┐рдпреЛ рдкрд░ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рджрд░реНрдЬ рдХреА рдЧрдИ рдереА:
рдХреИрд╕реЗ UICollectionView рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ
UICollectionView
UIScrollView
рдХрд╛ рдПрдХ рдЙрдкрд╡рд░реНрдЧ рд╣реИ, рдФрд░ рдпрд╣ рдПрдХ рдирд┐рдпрдорд┐рдд UIView рд╣реИ, рдЬрд┐рд╕рдореЗрдВ рд╕реНрд╡рд╛рдЗрдк рд╕реЗ bounds
рдмрджрд▓ рдЬрд╛рддреА рд╣реИред рдЗрд╕реЗ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░рдХреЗ .origin
, рд╣рдо рджреГрд╢реНрдпрдорд╛рди рдХреНрд╖реЗрддреНрд░ рдХреЛ рдмрджрд▓рддреЗ рд╣реИрдВ, рдФрд░ рдмрджрд▓рддреЗ рд╣реИрдВ .size
рдХреЛ рдкреНрд░рднрд╛рд╡рд┐рдд рдХрд░рддреЗ рд╣реИрдВред
рдЬрдм рд╕реНрдХреНрд░реАрди UICollectionView
рдХреЛрд╢рд┐рдХрд╛рдУрдВ рдХрд╛ рдирд┐рд░реНрдорд╛рдг (рдпрд╛ рдкреБрди: рдЙрдкрдпреЛрдЧ) рдХрд░рддрд╛ рд╣реИ, рдФрд░ рдЙрдиреНрд╣реЗрдВ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдиреЗ рдХреЗ рдирд┐рдпрдо UICollectionViewLayout
рдореЗрдВ рд╡рд░реНрдгрд┐рдд рд╣реИрдВред рд╣рдо рдЙрд╕рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░реЗрдВрдЧреЗред
UICollectionViewLayout
рдХреА рд╕рдВрднрд╛рд╡рдирд╛рдПрдВ рдмрдбрд╝реА рд╣реИрдВ, рдЖрдк рдХреЛрд╢рд┐рдХрд╛рдУрдВ рдХреЗ рдмреАрдЪ рдХрд┐рд╕реА рднреА рд╕рдВрдмрдВрдз рдХреЛ рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдЖрдк iCarousel рдХреНрдпрд╛ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдХреЗ рд╕рдорд╛рди рд╣реИ :

рдкрд╣рд▓рд╛ рддрд░реАрдХрд╛
рд╕реНрдХреНрд░реАрди рдХреЛ рдмрджрд▓рддреЗ рд╣реБрдП рджреЗрдЦрдиреЗ рдХреЛ рдмрджрд▓рдирд╛ рдореЗрд░реЗ рд▓рд┐рдП рд▓реЗрдЖрдЙрдЯ рдХреЗ рд▓реЗрдЖрдЙрдЯ рдХреЛ рд╕рдордЭрдирд╛ рдЖрд╕рд╛рди рд╣реЛ рдЧрдпрд╛ред
рд╣рдо рдЗрд╕ рддрдереНрдп рдХреЗ рд▓рд┐рдП рдЙрдкрдпреЛрдЧ рдХрд┐рдП рдЬрд╛рддреЗ рд╣реИрдВ рдХрд┐ рдХреЛрд╢рд┐рдХрд╛рдПрдВ рд╕реНрдХреНрд░реАрди рдХреЗ рдЪрд╛рд░реЛрдВ рдУрд░ рдШреВрдорддреА рд╣реИрдВ (рд╣рд░реЗ рд░рдВрдЧ рдХреА рдЖрдпрдд рдлреЛрди рдХреА рд╕реНрдХреНрд░реАрди рд╣реИ):

рд▓реЗрдХрд┐рди рдЗрд╕рдХреЗ рд╡рд┐рдкрд░реАрдд, рдпрд╣ рд╕реНрдХреНрд░реАрди рдХреЛрд╢рд┐рдХрд╛рдУрдВ рдХреЗ рд╕рд╛рдкреЗрдХреНрд╖ рдЪрд▓рддреА рд╣реИред рдкреЗрдбрд╝ рдЕрднреА рднреА рд╣реИрдВ, рдпрд╣ рдЯреНрд░реЗрди рдпрд╛рддреНрд░рд╛ рдХрд░рддреА рд╣реИ:

рдЙрджрд╛рд╣рд░рдг рдореЗрдВ, рд╕реЗрд▓ рдлреНрд░реЗрдо рдирд╣реАрдВ рдмрджрд▓рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рд╕рдВрдЧреНрд░рд╣ рдХреА bounds
рд╣реА рдмрджрд▓ рдЬрд╛рддреА рд╣реИрдВред рдЗрд╕ bounds
Origin
рдЬреНрдЮрд╛рдд contentOffset
ред
рдПрдХ рд▓реЗрдЖрдЙрдЯ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдЖрдкрдХреЛ рджреЛ рдЪрд░рдгреЛрдВ рд╕реЗ рдЧреБрдЬрд░рдирд╛ рд╣реЛрдЧрд╛:
- рд╕рднреА рдХреЛрд╢рд┐рдХрд╛рдУрдВ рдХреЗ рдЖрдХрд╛рд░ рдХреА рдЧрдгрдирд╛ рдХрд░реЗрдВ
- рдХреЗрд╡рд▓ рд╕реНрдХреНрд░реАрди рдкрд░ рджрд┐рдЦрд╛рдИ рджреЗ рд░рд╣рд╛ рд╣реИред
UITableView рдХреЗ рд░реВрдк рдореЗрдВ рд╕рд░рд▓ рд▓реЗрдЖрдЙрдЯ
рд▓реЗрдЖрдЙрдЯ рд╕реАрдзреЗ рдХреЛрд╢рд┐рдХрд╛рдУрдВ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИред рдЗрд╕рдХреЗ рдмрдЬрд╛рдп, рд╡реЗ UICollectionViewLayoutAttributes
рдЙрдкрдпреЛрдЧ UICollectionViewLayoutAttributes
- рдпрд╣ рдорд╛рдкрджрдВрдбреЛрдВ рдХрд╛ рд╕реЗрдЯ рд╣реИ рдЬреЛ рд╕реЗрд▓ рдкрд░ рд▓рд╛рдЧреВ рд╣реЛрдЧрд╛ред Frame
- рдореБрдЦреНрдп рдПрдХ, рд╕реЗрд▓ рдХреА рд╕реНрдерд┐рддрд┐ рдФрд░ рдЖрдХрд╛рд░ рдХреЗ рд▓рд┐рдП рдЬрд┐рдореНрдореЗрджрд╛рд░ рд╣реИред рдЕрдиреНрдп рдкреИрд░рд╛рдореАрдЯрд░: рдкрд╛рд░рджрд░реНрд╢рд┐рддрд╛, рдСрдлрд╕реЗрдЯ, рд╕реНрдХреНрд░реАрди рдХреА рдЧрд╣рд░рд╛рдИ рдореЗрдВ рд╕реНрдерд┐рддрд┐, рдЖрджрд┐ред

рд╢реБрд░реВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, UICollectionViewLayout
рдПрдХ рд╕рд░рд▓ UICollectionViewLayout
, рдЬреЛ UITableView
рдХреЗ рд╡реНрдпрд╡рд╣рд╛рд░ рдХреЛ рджреЛрд╣рд░рд╛рддрд╛ рд╣реИ: рдХреЛрд╢рд┐рдХрд╛рдПрдВ рдкреВрд░реА рдЪреМрдбрд╝рд╛рдИ рдкрд░ рдХрдмреНрдЬрд╛ рдХрд░ рд▓реЗрддреА рд╣реИрдВ, рдПрдХ рдХреЗ рдмрд╛рдж рдПрдХ UICollectionViewLayout
рд╣реИрдВред
4 рдХрджрдо рдЖрдЧреЗ:
prepare
рд╡рд┐рдзрд┐ рдореЗрдВ рд╕рднреА рдХреЛрд╢рд┐рдХрд╛рдУрдВ рдХреЗ рд▓рд┐рдП frame
рдЧрдгрдирд╛ рдХрд░реЗрдВредlayoutAttributesForElements(in:)
рдореЗрдВ рджрд┐рдЦрд╛рдИ рджреЗрдиреЗ рд╡рд╛рд▓реА рдХреЛрд╢рд┐рдХрд╛рдУрдВ рдХреЛ layoutAttributesForElements(in:)
редlayoutAttributesForItem(at:)
рдореЗрдВ рдЕрдкрдиреЗ рд╕реВрдЪрдХрд╛рдВрдХ рджреНрд╡рд╛рд░рд╛ рд╕реЗрд▓ рдорд╛рдкрджрдВрдбреЛрдВ рдХреЛ layoutAttributesForItem(at:)
ред layoutAttributesForItem(at:)
рд╡рд┐рдзрд┐ рдкрд░ред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдЗрд╕ рд╡рд┐рдзрд┐ рдХрд╛ рдЙрдкрдпреЛрдЧ рд╕рдВрдЧреНрд░рд╣ рд╡рд┐рдзрд┐ рдХреЛ рд╕реНрдХреНрд░реЙрд▓ рдХрд░рддреЗ рд╕рдордп рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ рд╕реНрдХреНрд░реЙрд▓рдСрдЗрдЯрдо (:) рдкрд░ред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
рдПрдХ рдЙрдкрд╡рд░реНрдЧ рдореЗрдВ рдХрд┐рдП 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
рдЧрдгрдирд╛ extension
рдореЗрдВ рджреА рдЧрдИ рд╣реИ:
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
рдкрд░ рд░реБрдХрдиреЗ рд╡рд╛рд▓рд╛ рд╣реИред
рдЧрдгрдирд╛ рд╕рд░рд▓ рд╣реИ: рджреЗрдЦреЗрдВ рдХрд┐ рдХреМрди рд╕рд╛ рдкрд┐рдЬреНрдЬрд╛ 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 }
UIScrollView
рдореЗрдВ рджреЛ рд╕реНрдХреНрд░реЙрд▓ .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
рд╡рд┐рдзрд┐ рдореЗрдВ, 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
рд╣реЛрдиреЗ 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) } }
.transform
рдХреЛрд╢рд┐рдХрд╛рдУрдВ рдХреЗ рдХреЗрдВрджреНрд░ рдХреЗ рд╕рд╛рдкреЗрдХреНрд╖ рдЖрдХрд╛рд░ рдмрджрд▓рддрд╛ рд╣реИред рдХреЗрдВрджреНрд░реАрдп рд╕реЗрд▓ рдореЗрдВ normScale = 0 рд╣реИ, рдЗрд╕рдХрд╛ рдЖрдХрд╛рд░ рдирд╣реАрдВ рдмрджрд▓рддрд╛ рд╣реИ:

рдкрд╛рд░рджрд░реНрд╢рд┐рддрд╛
alpha
рдкреИрд░рд╛рдореАрдЯрд░ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдкрд╛рд░рджрд░реНрд╢рд┐рддрд╛ рдХреЛ рдмрджрд▓рд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред transform
рдореЗрдВ рд╣рдордиреЗ рдЬреЛ scale
рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рд╡рд╣ рднреА рдЙрдкрдпреБрдХреНрдд рд╣реИред
cell.alpha = scale
рдЕрдм рдкрд┐рдЬреНрдЬрд╛ рдЕрдкрдиреЗ рдЖрдХрд╛рд░ рдФрд░ рдкрд╛рд░рджрд░реНрд╢рд┐рддрд╛ рдХреЛ рдмрджрд▓рддрд╛ рд╣реИред рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдирд┐рдпрдорд┐рдд рддрд╛рд▓рд┐рдХрд╛рдУрдВ рдореЗрдВ рдЙрдмрд╛рдК рдирд╣реАрдВ рд╣реИред

рдЖрдзреЗ рдореЗрдВ рд╡рд┐рднрд╛рдЬрд┐рдд
рдЗрд╕рд╕реЗ рдкрд╣рд▓реЗ, рд╣рдордиреЗ рдПрдХ рдкрд┐рдЬреНрдЬрд╛ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд┐рдпрд╛: рд╣рдордиреЗ рдХреЗрдВрджреНрд░ рд╕реЗ рд╕рдВрджрд░реНрдн рдкреНрд░рдгрд╛рд▓реА рд╕реЗрдЯ рдХреА, рдЖрдХрд╛рд░ рдФрд░ рдкрд╛рд░рджрд░реНрд╢рд┐рддрд╛ рдХреЛ рдмрджрд▓ рджрд┐рдпрд╛ред рдЕрдм рдЖрдкрдХреЛ рдЖрдзреЗ рдореЗрдВ рд╡рд┐рднрд╛рдЬрд┐рдд рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред
рдЗрд╕рдХреЗ рд▓рд┐рдП рдПрдХ рд╕рдВрдЧреНрд░рд╣ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рдмрд╣реБрдд рдореБрд╢реНрдХрд┐рд▓ рд╣реИ: рдЖрдкрдХреЛ рдкреНрд░рддреНрдпреЗрдХ рдЖрдзреЗ рдХреЗ рд▓рд┐рдП рдЕрдкрдирд╛ рдЦреБрдж рдХрд╛ рдЗрд╢рд╛рд░рд╛ рд╣реИрдВрдбрд▓рд░ рд▓рд┐рдЦрдирд╛ рд╣реЛрдЧрд╛ред рджреЛ рд▓реЗрдЖрдЙрдЯ рдмрдирд╛рдирд╛ рдЖрд╕рд╛рди рд╣реИ, рдкреНрд░рддреНрдпреЗрдХ рдХрд╛ рдЕрдкрдирд╛ рд▓реЗрдЖрдЙрдЯ рд╣реИред рдХреЗрд╡рд▓ рдЕрдм, рдПрдХ рдкреВрд░реЗ рдкрд┐рдЬреНрдЬрд╛ рдХреЗ рдмрдЬрд╛рдп, рдЖрдзрд╛ рд╣реЛ рдЬрд╛рдПрдЧрд╛ред
рджреЛ рдирд┐рдпрдВрддреНрд░рдХ, рдПрдХ рдХрдВрдЯреЗрдирд░
рд▓рдЧрднрдЧ рд╣рдореЗрд╢рд╛, рдореИрдВ рдПрдХ рд╕реНрдХреНрд░реАрди рдХреЛ рдХрдИ UIViewController
рдореЗрдВ рддреЛрдбрд╝рддрд╛ рд╣реВрдВ, рдкреНрд░рддреНрдпреЗрдХ рдЕрдкрдиреЗ рд╕реНрд╡рдпрдВ рдХреЗ рдХрд╛рд░реНрдп рдХреЗ рд╕рд╛рдеред рдЗрд╕ рдмрд╛рд░ рдпрд╣ рдЗрд╕ рддрд░рд╣ рдирд┐рдХрд▓рд╛:

- рдореБрдЦреНрдп рдирд┐рдпрдВрддреНрд░рдХ: рд╕рднреА рднрд╛рдЧреЛрдВ рдореЗрдВ рдЗрд╕реЗ рдЗрдХрдЯреНрдард╛ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ рдФрд░ "рдорд┐рдХреНрд╕" рдмрдЯрдиред
- рдЖрдзрд╛ рдХреЗ рд▓рд┐рдП рджреЛ рдХрдВрдЯреЗрдирд░реЛрдВ рдХреЗ рд╕рд╛рде рдирд┐рдпрдВрддреНрд░рдХ, рдПрдХ рдХреЗрдВрджреНрд░реАрдп рд╣рд╕реНрддрд╛рдХреНрд╖рд░ рдФрд░ рд╕реНрдХреНрд░реЙрд▓ рд╕рдВрдХреЗрддрдХред
- рдПрдХ рдХрд▓реЗрдХреНрдЯрд░ (рд╕рд╣реА рд╕рдлреЗрдж) рдХреЗ рд╕рд╛рде рдирд┐рдпрдВрддреНрд░рдХред
- рдХреАрдордд рдХреЗ рд╕рд╛рде рдирд┐рдЪрд▓рд╛ рдкреИрдирд▓ред
рдмрд╛рдПрдВ рдФрд░ рджрд╛рдПрдВ рдЖрдзреЗ рдХреЗ рдмреАрдЪ рдЕрдВрддрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдореИрдВрдиреЗ рдПрдирдо рд╢реБрд░реВ рдХрд┐рдпрд╛, рдЗрд╕реЗ рд▓реЗрдЖрдЙрдЯ рдореЗрдВ рд╕рдВрдЧреНрд░рд╣рд┐рдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред
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
рдмрджрд▓реЗрдВ:
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(forCellWith:)
рдмрдЬрд╛рдп рдЖрдкрдХреЛ рдХрдВрд╕реНрдЯреНрд░рдХреНрдЯрд░ UICollectionViewLayoutAttributes(forSupplementaryViewOfKind:)
рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ
рдФрд░ layoutAttributesForElements(in:)
рдореЗрдВ рд╕реЗрд▓ рдорд╛рдкрджрдВрдбреЛрдВ рдХреЗ рд╕рд╛рде рдЙрдиреНрд╣реЗрдВ рд╡рд╛рдкрд╕ рдХрд░реЗрдВ
рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рд╣рдо IndexPath
рджреНрд╡рд╛рд░рд╛ рд╣реЗрдбрд░ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рд╡рд┐рдзрд┐ рдХрд╛ рд╡рд░реНрдгрди 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:)
рдореЗрдВ рдЫрд┐рдкрд╛ рд╣реЛрддрд╛ рд╣реИ 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
рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░рддреЗ рд╣реИрдВ:
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
рдорд╛рдзреНрдпрдо рд╕реЗ рд╕реЗрдЯ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рд╢рд┐рд▓рд╛рд▓реЗрдЦ рдХреЛ рдЗрд╕рдХреЗ рдлреНрд░реЗрдо рдХреЗ рд╕рд╛рдкреЗрдХреНрд╖ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛:
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
рд╡рд╛рдкрд╕ рдХрд░ рджрд┐рдпрд╛ред рдЗрд╕рд╕реЗ рд╣реЗрдбрд░ рдХреЛ рдкрджрд╛рдиреБрдХреНрд░рдо рдореЗрдВ рдЬреЛрдбрд╝рд╛ рдЬрд╛рддрд╛ рд╣реИ, рд▓реЗрдЖрдЙрдЯ рдореЗрдВ рднрд╛рдЧ рд▓реЗрддреЗ рд╣реИрдВ, рд▓реЗрдХрд┐рди рдкреНрд░рджрд░реНрд╢рд┐рдд рдирд╣реАрдВ рд╣реЛрддреЗ рд╣реИрдВред рд╣рдордиреЗ рдХреЗрд╡рд▓ рдЙрди рдХреЛрд╢рд┐рдХрд╛рдУрдВ рдХреЛ рджрд┐рдЦрд╛рдпрд╛ рдЬреЛ рд╡рд░реНрддрдорд╛рди bounds
рд╕рд╛рде рдЕрдВрддрд░ рдХрд░рддреА рд╣реИрдВ, рдФрд░ рд╣реЗрдбрд░ рдХреЛ alpha
рджреНрд╡рд╛рд░рд╛ рдлрд╝рд┐рд▓реНрдЯрд░ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрддреА рд╣реИ:
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { тАж let visibleHeaders = headers.filter { $0.alpha > 0 } return cells + visibleHeaders }
рд╣рдо рдХреЗрдВрджреНрд░реАрдп рд╣рд╕реНрддрд╛рдХреНрд╖рд░ (рдореВрд▓ рдиреБрд╕реНрдЦрд╛) рд╕реЗ рд╕рд╣рдордд рд╣реИрдВ
рд╣рдордиреЗ рдмрд╣реБрдд рдЕрдЪреНрдЫрд╛ рдХрд╛рдо рдХрд┐рдпрд╛, рд▓реЗрдХрд┐рди рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдореЗрдВ рдПрдХ рд╡рд┐рд░реЛрдзрд╛рднрд╛рд╕ рдерд╛ - рдпрджрд┐ рдЖрдк рджреЛ рд╕рдорд╛рди рд╣рд┐рд╕реНрд╕реЛрдВ рдХрд╛ рдЪрдпрди рдХрд░рддреЗ рд╣реИрдВ, рддреЛ рд╡реЗ рдПрдХ рдирд┐рдпрдорд┐рдд рдкрд┐рдЬреНрдЬрд╛ рдореЗрдВ рдмрджрд▓ рдЬрд╛рддреЗ рд╣реИрдВред
рд╣рдордиреЗ рдЗрд╕реЗ рдЗрд╕ рддрд░рд╣ рдЫреЛрдбрд╝рдиреЗ рдХрд╛ рдлреИрд╕рд▓рд╛ рдХрд┐рдпрд╛, рд▓реЗрдХрд┐рди рд╣рд╛рд▓рдд рдХреЛ рд╕рд╣реА рдврдВрдЧ рд╕реЗ рд╕рдВрд╕рд╛рдзрд┐рдд рдХрд░рддреЗ рд╣реБрдП, рдпрд╣ рджрд┐рдЦрд╛рддреЗ рд╣реБрдП рдХрд┐ рдпрд╣ рдПрдХ рдирд┐рдпрдорд┐рдд рдкрд┐рдЬреНрдЬрд╛ рдерд╛ред рд╣рдорд╛рд░рд╛ рдирдпрд╛ рдХрд╛рдо рд╕рдорд╛рди рдкрд┐рдЬреНрдЬрд╛ рдХреЗ рд▓рд┐рдП рдХреЗрдВрджреНрд░ рдореЗрдВ рдПрдХ рд▓реЗрдмрд▓ рджрд┐рдЦрд╛рдирд╛, рдФрд░ рдХрд┐рдирд╛рд░реЛрдВ рдХреЗ рд╕рд╛рде рдЫрд┐рдкрд╛рдирд╛ рд╣реИред

рдЕрдХреЗрд▓реЗ рд▓реЗрдЖрдЙрдЯ рдХреЛ рд╣рд▓ рдХрд░рдирд╛ рдмрд╣реБрдд рдореБрд╢реНрдХрд┐рд▓ рд╣реИ, рдХреНрдпреЛрдВрдХрд┐ рд╢рд┐рд▓рд╛рд▓реЗрдЦ рджреЛ рдХрд▓реЗрдХреНрдЯрд░реЛрдВ рдХреЗ рдЬрдВрдХреНрд╢рди рдкрд░ рд╣реИред рдпрд╣ рдЖрд╕рд╛рди рд╣реЛрдЧрд╛ рдпрджрд┐ рдирд┐рдпрдВрддреНрд░рдХ, рдЬрд┐рд╕рдореЗрдВ рджреЛрдиреЛрдВ рд╕рдВрдЧреНрд░рд╣ рд╢рд╛рдорд┐рд▓ рд╣реИрдВ, рд╕рднреА рд╣рд╕реНрддрд╛рдХреНрд╖рд░ рдХреЗ рдЖрдВрджреЛрд▓рди рдХрд╛ рд╕рдордиреНрд╡рдп рдХрд░рддрд╛ рд╣реИред
рд╕реНрдХреНрд░реЙрд▓ рдХрд░рддреЗ рд╕рдордп, рд╣рдо рдирд┐рдпрдВрддреНрд░рдХ рдХреЛ рд╡рд░реНрддрдорд╛рди рд╕реВрдЪрдХрд╛рдВрдХ рдкрд╛рд╕ рдХрд░рддреЗ рд╣реИрдВ, рдпрд╣ рд╕реВрдЪрдХрд╛рдВрдХ рдХреЛ рд╡рд┐рдкрд░реАрдд рдЖрдзреЗ рдкрд░ рднреЗрдЬрддрд╛ рд╣реИред рдпрджрд┐ рд╕реВрдЪрдХрд╛рдВрдХ рдореЗрд▓ рдЦрд╛рддреЗ рд╣реИрдВ, рддреЛ рдпрд╣ рдореВрд▓ рдкрд┐рдЬреНрдЬрд╛ рдХрд╛ рд╢реАрд░реНрд╖рдХ рджрд┐рдЦрд╛рддрд╛ рд╣реИ, рдФрд░ рдпрджрд┐ рдЕрд▓рдЧ рд╣реИ, рддреЛ рдкреНрд░рддреНрдпреЗрдХ рдЖрдзреЗ рдХреЗ рд▓рд┐рдП рд╣рд╕реНрддрд╛рдХреНрд╖рд░ рджрд┐рдЦрд╛рдИ рджреЗрддреЗ рд╣реИрдВред
рдЕрдкрдиреЗ рд▓реЗрдЖрдЙрдЯ рдХрд╛ рдЖрд╡рд┐рд╖реНрдХрд╛рд░ рдХреИрд╕реЗ рдХрд░реЗрдВ
рд╕рдмрд╕реЗ рдХрдард┐рди рд╣рд┐рд╕реНрд╕рд╛ рдпрд╣ рдкрддрд╛ рд▓рдЧрд╛рдирд╛ рдерд╛ рдХрд┐ рдЕрдкрдиреЗ рд╡рд┐рдЪрд╛рд░ рдХреЛ рдХрдВрдкреНрдпреВрдЯрд┐рдВрдЧ рдореЗрдВ рдХреИрд╕реЗ рд░рдЦрд╛ рдЬрд╛рдПред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдореИрдВ рдкрд┐рдЬреНрдЬрд╛ рдХреЛ рдбреНрд░рдо рдХреА рддрд░рд╣ рд░реЛрд▓ рдХрд░рдирд╛ рдЪрд╛рд╣рддрд╛ рд╣реВрдВред рд╕рдорд╕реНрдпрд╛ рдХреЛ рд╕рдордЭрдиреЗ рдХреЗ рд▓рд┐рдП, рдореИрдВ 4 рд╕рд╛рдорд╛рдиреНрдп рдЪрд░рдгреЛрдВ рд╕реЗ рдЧреБрдЬрд░рд╛:
- рдореИрдВрдиреЗ рдХреБрдЫ рд░рд╛рдЬреНрдпреЛрдВ рдХреЛ рдЖрдХрд░реНрд╖рд┐рдд рдХрд┐рдпрд╛ред
- рдореИрдВ рд╕рдордЭ рдЧрдпрд╛ рдХрд┐ рддрддреНрд╡ рд╕реНрдХреНрд░реАрди рдХреА рд╕реНрдерд┐рддрд┐ рд╕реЗ рдХреИрд╕реЗ рд╕рдВрдмрдВрдзрд┐рдд рд╣реИрдВ (рддрддреНрд╡ рд╕реНрдХреНрд░реАрди рдХреЗ рдХреЗрдВрджреНрд░ рдХреЗ рд╕рд╛рдкреЗрдХреНрд╖ рдЪрд▓рддреЗ рд╣реИрдВ)ред
- рдРрд╕реЗ рд╡реИрд░рд┐рдПрдмрд▓ рдмрдирд╛рдП рдЧрдП рд╣реИрдВ рдЬреЛ рд╕реНрдХреНрд░реАрди рдХреЗ рдордзреНрдп (рд╕реЗрдВрдЯреНрд░рд▓ рдкрд┐рдЬреНрдЬрд╛ рдлреНрд░реЗрдо, рд╕реНрдХреЗрд▓) рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕реБрд╡рд┐рдзрд╛рдЬрдирдХ рд╣реИрдВред
- рдореИрдВ рд╕рд░рд▓ рдЪрд░рдгреЛрдВ рдХреЗ рд╕рд╛рде рдЖрдпрд╛, рдЬрд┐рдирдореЗрдВ рд╕реЗ рдкреНрд░рддреНрдпреЗрдХ рдХреЛ рд╕рддреНрдпрд╛рдкрд┐рдд рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред
рд░рд╛рдЬреНрдпреЛрдВ рдФрд░ рдПрдирд┐рдореЗрд╢рди рдХреАрдиреЛрдЯ рдореЗрдВ рдЦреАрдВрдЪрдирд╛ рдЖрд╕рд╛рди рд╣реИред рдореИрдВрдиреЗ рдорд╛рдирдХ рд▓реЗрдЖрдЙрдЯ рд▓рд┐рдпрд╛ рдФрд░ рдкрд╣рд▓реЗ рджреЛ рдЪрд░рдгреЛрдВ рдХреЛ рдЖрдХрд░реНрд╖рд┐рдд рдХрд┐рдпрд╛:

рд╡реАрдбрд┐рдпреЛ рдЗрд╕ рдкреНрд░рдХрд╛рд░ рд╣реИ:

рдЗрд╕рдореЗрдВ рддреАрди рдмрджрд▓рд╛рд╡ рд╣реБрдП:
- рдХреИрд╢ рд╕реЗ рдлреНрд░реЗрдо рдХреЗ рдмрдЬрд╛рдп рд╣рдо
centerPizzaFrame
ред scale
рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реБрдП 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
, .