Is it possible to animate the frame change of a UICollectionView header (also known as UICollectionElementKindSectionHeader) size change?
In other words, I would like my UICollectionView header to have two different possible frames, an expanded (larger height) and collapsed mode (smaller height). By tapping a button somewhere, I would like the header to switch between the expanded and collapsed mode with a custom animation.
Current I have it set to this:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: 0, height: HEADER_HEIGHT)
}
where I am changing the collection view height by calling this function upon a button tap
HEADER_HEIGHT = HEADER_HEIGHT == 100 ? 200 : 100
self.collectionView?.collectionViewLayout.invalidateLayout()
This works but I have yet to find documentation about animating the frame change. Is this even possible?
You can animate any animatable view changes by calling them in the UIView's animation block:
UIView.animate(withDuration: 0.5) {
self.collectionView?.collectionViewLayout.invalidateLayout()
}
Related
The cells with the blue bar are the cell I want to change the height for based on how many green cells there are.
Right now I can make the cell change size but looks really choppy I do this by using,
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 400, height: (50 * actionSteps[indexPath.row].SubActionSteps.count) + 100)
}
Is there any way to animate the change in height?
Try this:
actionSteps[indexPath.row].SubActionSteps.append("") /// or whatever you're currently doing
collectionView.performBatchUpdates(nil, completion: nil)
I have a UICollectionView which is exactly 7 cells wide, with no padding in between. I have done this by adding a flow layout with no interim or line spacing and calculating the width as follows:
public func collectionView(
_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath
) -> CGSize {
return CGSize(
width: self.frame.width / CGFloat(7),
height: self.frame.height - headerHeight
)
}
This works fantastically within my iPhone app
where rotation isn't enabled. On iPad, when in portrait mode this works fine.
When rotating, firstly the app doesn't redraw, I've attempted to invalidateLayout however this didn't solve the issue. I have to scroll down and I get the following layout
When I tap a cell it does however redraw, but with padding added pushing the 7th cell down onto another row.
I am looking to redraw the cells so all 7 fit on a single row when the app is either rotated or grown and shrunk in split view. How should I handle this correctly?
Add this in your UIViewController class
It will adjust your layout automatically while you change device orientation.
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
guard let columnLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else {
return
}
columnLayout.invalidateLayout()
}
I want to display list of buttons in scrollview with dynamic height based on screen size. I can able to do this in UIView, but when I applied same auto layout approach in UIScrollView, it's not increasing the size of buttons. So please guide me How to increase the button size in UIScrollView based on screen size.
if button is dynamic and may be increase and decrease in future than you Should use CollectionView not ScrollView.
Drag CollectionView into ViewController.
Drag Cell in CollectionView.
Add button(or label based on your requirement) In Cell.set button(leading,trailing,top,bottom) == Cell(leading,trailing,top,bottom)
Write dataSource and delegate methods and UICollectionViewDelegateFlowLayout methods.
and change Following Methods to increase button height According To Screen
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.view.bounds.width/2, height: self.view.bounds.height*0.15)
}
I have a custom CollectionViewFlowLayout in which I override layoutAttributesForElements(in rect: CGRect) in order to get the elements positioned like in the design specs.
Everything is fine, but in some cases, to make a "selection mode" animation, I need to update the y position of the frames, but when I do that, my animation suffers a small delay.
If I maintain the y position and update the x, the width and the height of the attribute, the animation is ok.
Why does that happen? How could I avoid it?
Here's a mockup of my layout:
And here's the selected mode:
Basically my animation consists in changing the transform for the visible cells, in a way to make them smaller but centered, and to maintain the alignments and correct spacing I manipulate the frames in layoutAttributesForElements and invalidate the layout.
edit:
other solutions like changing the cellSize and minimumInteritemSpacing doesn't work for me because UICollectionView animation for cellSize change is extremely wonky
As I mentioned up in the comments, there seems to be an easier approach: I was able to get a pretty smooth implementation by just altering the values returned from collectionView(_:layout:sizeForItemAt:), collectionView(_:layout:minimumLineSpacingForSectionAt:) and collectionView(_:minimumInteritemSpacingForSectionAt:) as appropriate, and calling invalidateLayout() and layoutIfNeeded() inside an animation block at the right moment.
The return values for a really basic layout of items might look like this (where separatorSize(isInSelectionMode:) returns larger or smaller values depending on the selection state):
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let totalRowHeight: CGFloat = 100
let widthRatio: CGFloat
switch indexPath.item % 4 {
case 0, 3: widthRatio = (2.0 / 3.0)
case 1, 2: widthRatio = (1.0 / 3.0)
default: widthRatio = 1.0
}
return CGSize(width: (collectionView.bounds.width - separatorSize(isInSelectionMode: selectionMode).width) * widthRatio,
height: totalRowHeight - separatorSize(isInSelectionMode: selectionMode).height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return separatorSize(isInSelectionMode: selectionMode).height
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return separatorSize(isInSelectionMode: selectionMode).width
}
Then you just toggle selectionMode inside an animation block while invalidating the layout.
I made a demo project here and a demo video here.
I want to set custom horizontal scroll for my CollectionView, that it will be scrolling by 1 cell (not by the whole width of my screen).
I could set HORIZONTAL scroll, but not custom. (See screens).
1 screen: my extension of my collectionView for UIScrollViewDelegate.
*I saw, that in console (see too) "x" - my offset = 290 - it's true! But on fact it is not 290. Paging was marked in "true" 2 screen: delegate and dataSource.
Help, please!
First you need to set your collectionView scroll direction horizontal (UICollectionViewScrollDirectionHorizontal) and set pagingEnabled to YES
It means your collection view will be scroll horizontally with one by one cell.
Set horizontal direction (Select collection view from XIB or Storyboard)
And for set pagination enable
myCollectionView.pagingEnabled = true
Updated :
You should use of UICollectionViewDelegateFlowLayout delegate
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
let width = UIScreen.main.bounds.size.width
var height = 101.7 // set height as you want
return CGSize(width: width, height: height)
}
If using storyBoard the go to collection view and select the right handed tab "show the size inspector" and change
minimum spacing for cells = 0
minimum spacing for lines = 0
if you want to change directly from code then implement these UICollectionViewDelegateFlowLayout methods
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
must be remember scrolling direction set horizontal
and pagination enabled
and implement this delegate methods into code
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath:
IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.size.width, height:
collectionView.frame.size.height)
}
}
You could create a custom UICollectionViewFlowLayout that will center the cells in the screen and then page one at a time. Karmadust have written a good blog post that I have successfully used in the past to do the same thing
http://blog.karmadust.com/centered-paging-with-preview-cells-on-uicollectionview/