Animate UICollectionViewLayout header size causes unwanted flash cross dissolve - ios

Vis of problem: http://i.imgur.com/TastPR9.gifv
func animateHeaderResize(height: CGFloat) {
let layout = self.collectionView?.collectionViewLayout as! UICollectionViewFlowLayout;
layout.headerReferenceSize = CGSize(width: UIScreen.main.bounds.width, height: height);
UIView.animate(withDuration: 1, delay: 0, options: .curveEaseOut, animations: {
self.collectionView?.layoutIfNeeded();
}) { (finished) in
//self.loadPosts();
}
}
I am attempting to animate a change in the header size of a UICollectionView through it's UICollectionViewLayout. The resulting animation has a weird flash and stretch that might be a cross dissolve transition being triggered somewhere. I have tried many variations of setting the layout of the collection view including setCollectionViewLayout(layout:animated:) and implementing collectionView(_ collectionView: layout: referenceSizeForHeaderInSection section:) but all result in the same animation.
I have noticed that the same flash occurs when changing the item size of the collection view. Maybe this is some default behavior I can modify? Do I have to subclass UICollectionViewFlowLayout? I have tried searching and can't find a solution, would appreciate just a step in the right direction.

This ended up working for me:
func animateHeaderResize(height: CGFloat) {
self.collectionView?.performBatchUpdates({
let layout = self.collectionView?.collectionViewLayout as! UICollectionViewFlowLayout;
layout.headerReferenceSize = CGSize(width: UIScreen.main.bounds.width, height: height);
}, completion: { (fin) in
self.loadPosts();
});
}

Saw the same issue and I could see that animation looks better if i set:
layout.sectionHeadersPinToVisibleBounds = false

Related

Send message animation like iMessage send message using UICollectionView customization

I am working on chat application. I am using UIcollectionview for display message. I am trying to create animation on the send button. When the user taps on send UIButton at that I need animation like iMessage.
For creating such animation I use two different ways but not get proper animation.
I created one demo. In that demo, I implemented different approaches.
Demo link:
https://www.dropbox.com/s/z8rxjkpuxzs95kp/AdvanceCollection.zip?dl=0
Below is animation that actually I need:
https://www.dropbox.com/s/yo35lsm7yrc2oqu/Screen%20Recording.mov?dl=0
I apply 2 approaches. Below is approach description.
First approach:
In the first approach, I customized FlowLayout. I know this is proper way but not getting proper curve animation.
override func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let attributes = super.initialLayoutAttributesForAppearingItem(at: itemIndexPath),
let added = addedItem,
added == itemIndexPath else {
return nil
}
// set new attributes
attributes.frame = CGRect(x: 0, y: 0, width: 500, height: 500)
//attributes.center = CGPoint(x: self.collectionView!.frame.width - 23.5, y: -24.5)
attributes.center = CGPoint(x: 0.0, y: 100.0)
attributes.alpha = 1.0
attributes.transform = CGAffineTransform(scaleX: 0.15, y: 0.15)
attributes.zIndex = 20
return attributes
}
Second approach: In the second approach, I am implementing animation in willdisplaycell method. According to my point of view, I know this is not the proper approach to customize layout but I tried. In second approach I am getting animation from bottom but x, y, width and height. Also whatever animation is visible is also not same as iMessage send message animation
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if !self.collectionData[indexPath.row].isAnimated
{
cell.frame = CGRect(x: 0, y: chatView.frame.origin.y, width: 1000.0, height: chatView.frame.size.height)
UIView.animate(withDuration: 10.0, delay: 0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0.6, options: UIViewAnimationOptions.curveEaseIn, animations: {
cell.frame = CGRect(x:0, y: cell.frame.origin.y, width: cell.frame.size.width, height: cell.frame.size.height)
}, completion: { finished in
self.collectionData[indexPath.row].isAnimated = true
})
}
}
Edit: see tableView demo here animateCell
I think you may try using tableView instead of the collectionView to accomplish that as collectionView layout is awfully silly
I assume you have created a custom xib cell for your collectionView then do this
1- set the top , bottom , leading , trailing constraints properly to the superview of your message label
2- change the top constraint of the message label to be say 400
3- set hook that top constraint as IBOutlet
4- in cellForRowAt set that constraint to say 20
5- then do animation like that
UIView.animateWithDuration(1.0,
{
cell.layoutSubview()
cell.layoutIfNeeded()
}
note: the problem with collectionView is chagning size for item so animation can happen , above solution works perfectly with tableView

Animated table-view cell when the user picks up with his finger (Swift 3)

I'm trying to make this animation
so i want that when the user picks up with his finger the cell get smaller and then when it stops pressing come back on the original size, important is that i'm not using a custom cell but the default UITableViewCell, here is my code with whom i tried to do the animation (after watching old question and tutorial)
var originalCellSize: CGRect!
func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
print("I'm Highlighted at \(indexPath.section)")
guard let cell = tableView.cellForRow(at: indexPath) else {
return
}
originalCellSize = cell.frame
UIView.animate(withDuration: 0.1, animations: {
cell.frame = CGRect(x: cell.frame.origin.x + 20, y: cell.frame.origin.y, width: cell.frame.width - 40, height: cell.frame.height)
})
}
func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) else {
return
}
UIView.animate(withDuration: 0.1, animations: {
cell.frame = CGRect(x: 0, y: self.originalCellSize.origin.y, width: self.view.bounds.width, height: 180)
})
originalCellSize = .zero
}
i don't know why but something is wrong and the animation do not work, can someone help me (it also happens that after pushing on the cell this changes position in a definitive way)
Don't manipulate UITableViewCell's frame directly.
Use it's transform property.
Likewise:
Scale down the cell upon highlight / selection.
UIView.animate(withDuration: 0.3) {
cell.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
cell.layoutIfNeeded()
}
Scale back to normal:
UIView.animate(withDuration: 0.3) {
cell.transform = CGAffineTransform.identity
cell.layoutIfNeeded()
}
Don't forget to call cell.layoutIfNeeded() before the animation block and inside (right after assigning the CGAffineTransform).
You can approach the problem in a different way. You can resize a view inside the Table view cell.
Like you can keep a view with white color and cell background as grey color. In highlight delegate method, you can change the frame of UIView inside the cell and on unhighlight method, you can resize to back to cell size.
WhenEver You want to change layout of cell you must need to call cell.layoutIfNeeded().
Set flag that cell should change size.
Add flag check for heightForRowAtIndexPath
On highlighting use:
table.beginUpdates()
table.reloadRowsAt(...)
table.endUpdates()
Should work just fine.
I'm not sure you can just change frame of the cell. So you can also try try to change cell's content view size. Or your custom view frame itself.

UIScrollView behaving weird when updating constraints subview

I made an UIScrollView in a XIB for my onboarding. The UIScrollView has 3 onboarding views. Long story short:
This works perfect. However I want the top left and right buttons (Overslaan - Volgende) to animate up / off the screen when the third/last page is on screen. My UIScrollView starts behaving weird when I animate the buttons off:
This is the code im using:
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let pageIndex = Int(targetContentOffset.pointee.x / self.frame.width)
pageControl.currentPage = pageIndex
if stepViews[pageIndex] is OnboardingLoginView {
moveControlConstraintsOffScreen()
} else {
moveControlConstraintsOnScreen()
}
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.layoutIfNeeded()
}, completion: nil)
}
I debugged the code and it turns out that setting a new constant for the constraints causes the issue, regardless of the animation block. How do I make the buttons move up/off the screen without my scrollView behaving weird?
It looks like triggering a layout pass is interfering with your scroll view positioning. You could implement func scrollViewDidScroll(_ scrollView: UIScrollView) and try to update the button view on a per-frame basis, without changing Auto Layout constraints. I usually use the transform property for frame changes outside of Auto Layout, since any changes to frame or bounds are overwritten during the next layout pass.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard mustBeOnLastPage else {
buttonOne.tranform = .identity
buttonTwo.tranform = .identity
return
}
let offset = scrollView.contentOffset.x
buttonOne.tranform = .init(translationX: 0, y: offset)
buttonTwo.tranform = .init(translationX: 0, y: offset)
}
Old answer
This interpretation is a bit of tangent of what you're asking for.
Since it looks like you're using the scroll view in a paging context, I would approach this problem by using UIPageViewController. Since UIPageViewController uses UIScrollView internally, you can observe the contentOffset of the last view in the scroll view to determine how far along the page has scrolled.
Yes, this involves looking inside the view hierarchy, but Apple hasn't changed it for half a decade so you should be safe. Coincidentally, I actually implemented this approach last week and it works like a charm.
If you're interested, I can further expand on this topic. The post that pointed me in the right direction can be found here.
Here is what i do
I can't post image,you can look at this http://i.imgur.com/U7FHoMu.gif
and this is the code
var centerYConstraint: Constraint!
func setupConstraint() {
fadeView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
centerYConstraint = make.centerY.equalToSuperview().constraint
make.size.equalTo(CGSize(width: 50, height: 50))
}
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint,targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let pageIndex = Int(targetContentOffset.pointee.x / self.view.frame.width)
if pageIndex != 1 {
centerYConstraint.update(offset: self.view.frame.height)
} else {
centerYConstraint.update(offset: 0)
}
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
Based on #CloakedEddy's answer I made 2 changes:
1: It seems layoutSubviews is responsible for the weird behaviour. To fix this I prevent the scrollView from calling layoutSubviews all the time:
override func layoutSubviews() {
super.layoutSubviews()
if !didLayoutSubviews {
for index in 0..<stepViews.count {
let page: UIView = stepViews[index]
let xPosition = scrollView.frame.width * CGFloat(index)
page.frame = CGRect(x: xPosition, y: 0, width: scrollView.bounds.width, height: scrollView.frame.height)
scrollView.contentSize.width = scrollView.frame.width * CGFloat(index + 1)
}
didLayoutSubviews = true
}
}
2: If you want to update your views for device orientations:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
onboardingView.didLayoutSubviews = false
onboardingView.setNeedsLayout()
}

iOS UICollectionView cells' transition/slide in animation when they are about to appear

What I already have: a horizontally scrolled UICollectionView, and its cells (just a single line of cells).
What I want to do: animate the cells when they are about to appear -- cells slide in from right (outside the screen) to left, eventually stop at the left border of the UICollectionView.
How could I achieve this? Any help is greatly appreciated!
You have to move the collectionView cell from their position to previous cell position by using following method
[collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
I have implement same animation in collection view as given bellow :
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let myreact = cell.frame
cell.frame = CGRect(x: cell.frame.origin.x+320, y: cell.frame.origin.y, width: cell.frame.size.width, height: cell.frame.size.height)
let value = Double(indexPath.row)*0.1
UIView.animate(withDuration: 0.8, delay:value, options: .curveEaseInOut, animations: {
cell.frame = CGRect(x: myreact.origin.x+100, y: myreact.origin.y, width: myreact.size.width, height: myreact.size.height)
}) { (finish) in
UIView.animate(withDuration: 0.8, animations: {
cell.frame = myreact
})
}
}
}
Try with it in Swift 3.0
Instead of animating the cells/the layout, I animate the UICollectionView, whose background color is [UIColor clearColor]...

Custom UINavigationBar - Animating Intrinsic Height Changes

In this particular scenario, I'm actually very confused. I have subclassed UINavigationBar, noted Apple's outdated sample code, realized that sizeToFit is never called and overrode intrinsicContentSize and layoutSubviews to calculate the necessary height. Everything works well when it comes to calculating the height based on a custom view I provide (I use systemLayoutSizeFittingSize for calculating the height dynamically based on a custom content view/titleView).
The odd part is, when I attempt to animate any changes, I can animate changes in the custom titleView, but when reloading the intrinsicContentSize via invalidateIntrinsicContentSize(), the view frame changes do not animate. It snaps.
Sample code:
let delta = destinationTextFieldContainerView.frame.minX - originTextFieldContainerView.frame.minX + originTextFieldContainerView.frame.height
textContainersSeparatorConstraint.constant = -delta
UIView.animateWithDuration(2, delay: 2, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.customContainerNavigationBar.invalidateIntrinsicContentSize()
self.customContainerNavigationBar.layoutIfNeeded()
}) { (_) in
self.customContainerNavigationBar.invalidateIntrinsicContentSize()
}
The one constraint value change will animate, but unfortunately (because navBars don't rely on auto layout) it seems there's a problem updating the frame. I've attempted updating the frame directly before invalidating the intrinsic size with no success. I've also tried a height constraint, listing it in key frame animations with different timing offsets, invalidating the layout at different points in time, all to no success. Any help would be greatly appreciated.
Resizing code can be found here:
override func intrinsicContentSize() -> CGSize {
var navigationBarSize = super.intrinsicContentSize()
navigationBarSize.width = superview?.frame.width ?? 0
guard let titleView = navigationItem?.titleView else {
return navigationBarSize
}
let fittingSize = CGSize(width: navigationBarSize.width, height: UILayoutFittingCompressedSize.height)
let titleViewSize = titleView.systemLayoutSizeFittingSize(fittingSize)
navigationBarSize.height = max(titleViewSize.height, 44)
navigationBarSize.width = UIViewNoIntrinsicMetric
return navigationBarSize
}
override func layoutSubviews() {
super.layoutSubviews()
guard let navigationItem = navigationItem, titleView = navigationItem.titleView else {
return
}
var navigationBarSize = bounds.size
navigationBarSize.width = superview?.frame.width ?? 0
var fittingSize = CGSize(width: navigationBarSize.width, height: UILayoutFittingCompressedSize.height)
if let leftBarButtonItem = navigationItem.leftBarButtonItem, leftButtonCustomView = leftBarButtonItem.customView {
fittingSize.width -= leftButtonCustomView.frame.width + leftButtonCustomView.frame.origin.x + 22
}
let titleViewSize = titleView.systemLayoutSizeFittingSize(fittingSize, withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityDefaultLow)
titleView.frame = CGRect(origin: CGPoint(x: titleView.frame.origin.x, y: 0), size: titleViewSize)
titleView.setNeedsLayout()
titleView.layoutIfNeeded()
if let leftBarButtonItem = navigationItem.leftBarButtonItem, leftButtonCustomView = leftBarButtonItem.customView {
var customViewFrame = CGRect(origin: CGPoint.zero, size: navigationBarSize)
let customViewSize = leftButtonCustomView.systemLayoutSizeFittingSize(customViewFrame.size, withHorizontalFittingPriority: UILayoutPriorityDefaultLow, verticalFittingPriority: UILayoutPriorityDefaultLow)
customViewFrame.size = customViewSize
leftButtonCustomView.frame = customViewFrame
leftButtonCustomView.setNeedsLayout()
leftButtonCustomView.layoutIfNeeded()
}
invalidateIntrinsicContentSize()
}

Resources