问题描述
我正在尝试在水平胶片布局和垂直堆栈布局之间转换我的收藏视图.
水平胶片条 | 垂直/扩展堆栈 |
---|---|
这是我目前正在做的:
- 使用):
class ViewController: UIViewController {var isExpanded = falsevar verticalFlowLayout = UICollectionViewFlowLayout()var horizontalFlowLayout = UICollectionViewFlowLayout()@IBOutlet 弱变量 collectionView:UICollectionView!@IBOutlet 弱变量 collectionViewHeightConstraint:NSLayoutConstraint!@IBAction func toggleExpandPressed(_ sender: Any) {isExpanded.toggle()如果是扩展{collectionView.setCollectionViewLayout(verticalFlowLayout, animation: true)///设置垂直滚动collectionViewHeightConstraint.constant = 300///使集合视图高度更高} 别的 {collectionView.setCollectionViewLayout(horizontalFlowLayout, animation: true)///设置水平滚动collectionViewHeightConstraint.constant = 50///使集合视图高度更短}///动画集合视图的高度UIView.animate(withDuration: 1) {self.view.layoutIfNeeded()}///奖励积分:///这使动画变得更糟,但我希望能够在过渡期间滚动到特定的 IndexPath.//collectionView.scrollToItem(at: IndexPath(item: 9, section: 0), at: .bottom, animation: true)}覆盖 func viewDidLoad() {super.viewDidLoad()VerticalFlowLayout.scrollDirection = .vertical水平流布局.scrollDirection = .horizontalcollectionView.collectionViewLayout = horizontalFlowLayoutcollectionView.dataSource = selfcollectionView.delegate = self}}扩展视图控制器:UICollectionViewDelegateFlowLayout {func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) ->CG尺寸{///如果展开,单元格应该是全角的///如果没有展开,单元格的宽度应该是 50return isExpanded ?CGSize(width: collectionView.frame.width, height: 50) : CGSize(width: 100, height: 50)}func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) ->CGFloat {return 0///现在不需要间距}}///示例数据源扩展视图控制器:UICollectionViewDataSource {func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection 部分: Int) ->整数{返回 10}func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) ->UICollectionViewCell {let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ID", for: indexPath)cell.contentView.layer.borderWidth = 5cell.contentView.layer.borderColor = UIColor.red.cgColor返回单元格}}
解决方案我通过使用自定义的
UICollectionViewFlowLayout
类 (这是我的视图控制器.我现在使用自定义闭包
sizeForListItemAt
和sizeForStripItemAt
,而不是遵循UICollectionViewDelegateFlowLayout
.class ViewController: UIViewController {var isExpanded = false懒惰 var listLayout = FlowLayout(layoutType: .list)懒惰 var stripLayout = FlowLayout(layoutType: .strip)@IBOutlet 弱变量 collectionView:UICollectionView!@IBOutlet 弱变量 collectionViewHeightConstraint:NSLayoutConstraint!@IBAction func toggleExpandPressed(_ sender: Any) {isExpanded.toggle()如果是扩展{collectionView.setCollectionViewLayout(listLayout,动画:真)collectionViewHeightConstraint.constant = 300} 别的 {collectionView.setCollectionViewLayout(stripLayout,动画:真)collectionViewHeightConstraint.constant = 60}UIView.animate(withDuration: 0.6) {self.view.layoutIfNeeded()}}覆盖 func viewDidLoad() {super.viewDidLoad()collectionView.collectionViewLayout = stripLayoutcollectionView.dataSource = self///使用这些代替`UICollectionViewDelegateFlowLayout`listLayout.sizeForListItemAt = { [weak self] indexPath in返回 CGSize(width: self?.collectionView.frame.width ?? 100, height: 50)}stripLayout.sizeForStripItemAt = { indexPath in返回 CGSize(宽:100,高:50)}}}///示例数据源扩展视图控制器:UICollectionViewDataSource {func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection 部分: Int) ->整数{返回 10}func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) ->UICollectionViewCell {let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ID", for: indexPath)cell.contentView.layer.borderWidth = 5cell.contentView.layer.borderColor = UIColor.red.cgColor返回单元格}}
然后,这是我的自定义
UICollectionViewFlowLayout
类.在prepare
中,我手动计算和设置每个单元格的帧.我不确定为什么会这样,但是系统现在能够确定哪些单元格等于哪些单元格,甚至跨多个FlowLayout
s(listLayout
和>stripLayout
.).enum LayoutType {案例清单箱条}类 FlowLayout:UICollectionViewFlowLayout {var layoutType:布局类型var sizeForListItemAt: ((IndexPath) -> CGSize)?///获取列表项的大小var sizeForStripItemAt: ((IndexPath) -> CGSize)?///获取条带项目的大小var layoutAttributes = [UICollectionViewLayoutAttributes]()///存放每个item的framevar contentSize = CGSize.zero///集合视图的可滚动内容大小override var collectionViewContentSize: CGSize { return contentSize }///将可滚动内容的大小传回集合视图override func prepare() {///配置单元格的框架超级准备()守卫让 collectionView = collectionView else { return }let itemCount = collectionView.numberOfItems(inSection: 0)///我只有 1 个部分如果 layoutType == .list {var y: CGFloat = 0///每个单元格的y位置,从0开始对于 0..UICollectionViewLayoutAttributes?{返回 layoutAttributes[indexPath.item]}覆盖 func layoutAttributesForElements(in rect: CGRect) ->[UICollectionViewLayoutAttributes]?{返回 layoutAttributes.filter { rect.intersects($0.frame) }}///用布局初始化初始化(布局类型:布局类型){self.layoutType = layoutType超级初始化()}///样板代码需要 init?(coder aDecoder: NSCoder) { fatalError(init(coder:) 尚未实现") }覆盖 func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) ->布尔 { 返回真 }覆盖 func invalidationContext(forBoundsChange newBounds: CGRect) ->UICollectionViewLayoutInvalidationContext {let context = super.invalidationContext(forBoundsChange: newBounds) as!UICollectionViewFlowLayoutInvalidationContextcontext.invalidateFlowLayoutDelegateMetrics = newBounds.size != collectionView?.bounds.size返回上下文}}
感谢这篇精彩的文章提供的巨大帮助.
I'm trying to transition my collection view between a horizontal film-strip layout and a vertical stack layout.
This is what I'm currently doing:
- Using
setCollectionViewLayout(_:animated:completion:)
to transition between horizontal and vertical scroll direction - Changing the height constraint of the collection view, along with
UIView.animate
andself.view.layoutIfNeeded()
The animation from film-strip to vertical stack is fine. However, the animation from vertical stack to film-strip is broken. Here's what it looks like:
If I make
collectionViewHeightConstraint
300 by default, and remove these lines:collectionViewHeightConstraint.constant = 300 collectionViewHeightConstraint.constant = 50
... the transition animation is fine both ways. However, there is excess spacing, and I want the film-strip layout to only be in 1 row.
How can I make the animation be smooth both ways? Here's my code (link to the demo project):
class ViewController: UIViewController { var isExpanded = false var verticalFlowLayout = UICollectionViewFlowLayout() var horizontalFlowLayout = UICollectionViewFlowLayout() @IBOutlet weak var collectionView: UICollectionView! @IBOutlet weak var collectionViewHeightConstraint: NSLayoutConstraint! @IBAction func toggleExpandPressed(_ sender: Any) { isExpanded.toggle() if isExpanded { collectionView.setCollectionViewLayout(verticalFlowLayout, animated: true) /// set vertical scroll collectionViewHeightConstraint.constant = 300 /// make collection view height taller } else { collectionView.setCollectionViewLayout(horizontalFlowLayout, animated: true) /// set horizontal scroll collectionViewHeightConstraint.constant = 50 /// make collection view height shorter } /// animate the collection view's height UIView.animate(withDuration: 1) { self.view.layoutIfNeeded() } /// Bonus points: /// This makes the animation way more worse, but I would like to be able to scroll to a specific IndexPath during the transition. // collectionView.scrollToItem(at: IndexPath(item: 9, section: 0), at: .bottom, animated: true) } override func viewDidLoad() { super.viewDidLoad() verticalFlowLayout.scrollDirection = .vertical horizontalFlowLayout.scrollDirection = .horizontal collectionView.collectionViewLayout = horizontalFlowLayout collectionView.dataSource = self collectionView.delegate = self } } extension ViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { /// if expanded, cells should be full-width /// if not expanded, cells should have a width of 50 return isExpanded ? CGSize(width: collectionView.frame.width, height: 50) : CGSize(width: 100, height: 50) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { return 0 /// no spacing needed for now } } /// sample data source extension ViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 10 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ID", for: indexPath) cell.contentView.layer.borderWidth = 5 cell.contentView.layer.borderColor = UIColor.red.cgColor return cell } }
解决方案I got it working by using a custom
UICollectionViewFlowLayout
class (link to demo repo). The center cell even stays centered across both layouts (which was actually the exact purpose of the "Bonus points" part in my question)!Here's my view controller. Instead of conforming to
UICollectionViewDelegateFlowLayout
, I now use the custom closuressizeForListItemAt
andsizeForStripItemAt
.class ViewController: UIViewController { var isExpanded = false lazy var listLayout = FlowLayout(layoutType: .list) lazy var stripLayout = FlowLayout(layoutType: .strip) @IBOutlet weak var collectionView: UICollectionView! @IBOutlet weak var collectionViewHeightConstraint: NSLayoutConstraint! @IBAction func toggleExpandPressed(_ sender: Any) { isExpanded.toggle() if isExpanded { collectionView.setCollectionViewLayout(listLayout, animated: true) collectionViewHeightConstraint.constant = 300 } else { collectionView.setCollectionViewLayout(stripLayout, animated: true) collectionViewHeightConstraint.constant = 60 } UIView.animate(withDuration: 0.6) { self.view.layoutIfNeeded() } } override func viewDidLoad() { super.viewDidLoad() collectionView.collectionViewLayout = stripLayout collectionView.dataSource = self /// use these instead of `UICollectionViewDelegateFlowLayout` listLayout.sizeForListItemAt = { [weak self] indexPath in return CGSize(width: self?.collectionView.frame.width ?? 100, height: 50) } stripLayout.sizeForStripItemAt = { indexPath in return CGSize(width: 100, height: 50) } } } /// sample data source extension ViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 10 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ID", for: indexPath) cell.contentView.layer.borderWidth = 5 cell.contentView.layer.borderColor = UIColor.red.cgColor return cell } }
Then, here's my custom
UICollectionViewFlowLayout
class. Insideprepare
, I manually calculate and set the frames of each cell. I'm not exactly sure why this works, but the system is now able to figure out which cells are equal to which, even across multipleFlowLayout
s (listLayout
andstripLayout
.).enum LayoutType { case list case strip } class FlowLayout: UICollectionViewFlowLayout { var layoutType: LayoutType var sizeForListItemAt: ((IndexPath) -> CGSize)? /// get size for list item var sizeForStripItemAt: ((IndexPath) -> CGSize)? /// get size for strip item var layoutAttributes = [UICollectionViewLayoutAttributes]() /// store the frame of each item var contentSize = CGSize.zero /// the scrollable content size of the collection view override var collectionViewContentSize: CGSize { return contentSize } /// pass scrollable content size back to the collection view override func prepare() { /// configure the cells' frames super.prepare() guard let collectionView = collectionView else { return } let itemCount = collectionView.numberOfItems(inSection: 0) /// I only have 1 section if layoutType == .list { var y: CGFloat = 0 /// y position of each cell, start at 0 for itemIndex in 0..<itemCount { let indexPath = IndexPath(item: itemIndex, section: 0) let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) attributes.frame = CGRect( x: 0, y: y, width: sizeForListItemAt?(indexPath).width ?? 0, height: sizeForListItemAt?(indexPath).height ?? 0 ) layoutAttributes.append(attributes) y += attributes.frame.height /// add height to y position, so next cell becomes offset } /// use first item's width contentSize = CGSize(width: sizeForStripItemAt?(IndexPath(item: 0, section: 0)).width ?? 0, height: y) } else { var x: CGFloat = 0 /// z position of each cell, start at 0 for itemIndex in 0..<itemCount { let indexPath = IndexPath(item: itemIndex, section: 0) let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) attributes.frame = CGRect( x: x, y: 0, width: sizeForStripItemAt?(indexPath).width ?? 0, height: sizeForStripItemAt?(indexPath).height ?? 0 ) layoutAttributes.append(attributes) x += attributes.frame.width /// add width to z position, so next cell becomes offset } /// use first item's height contentSize = CGSize(width: x, height: sizeForStripItemAt?(IndexPath(item: 0, section: 0)).height ?? 0) } } /// pass attributes to the collection view flow layout override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { return layoutAttributes[indexPath.item] } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { return layoutAttributes.filter { rect.intersects($0.frame) } } /// initialize with a layout init(layoutType: LayoutType) { self.layoutType = layoutType super.init() } /// boilerplate code required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { return true } override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext { let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext context.invalidateFlowLayoutDelegateMetrics = newBounds.size != collectionView?.bounds.size return context } }
Thanks to this amazing article for the huge help.
这篇关于`setCollectionViewLayout` 动画在更改集合视图框架时被破坏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!
- Using