I need to do an animation in UITableView to insert sections but i need the sections to animate from the bottom of the screen.
And not like one of the default UITableViewRowAnimation animations.
This is animation i need:
Any suggestions?
Thanks
I figured out how to do this with a simple solution.
public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if shouldAnimate {
cell.alpha = 0
let transform = CATransform3DTranslate(CATransform3DIdentity, 0, UIScreen.main.bounds.height, 0)
cell.layer.transform = transform
UIView.animate(withDuration: 0.5, animations: {
cell.alpha = 1
cell.layer.transform = CATransform3DIdentity
})
}
}
public func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
if shouldAnimate {
view.alpha = 0
let transform = CATransform3DTranslate(CATransform3DIdentity, 0, UIScreen.main.bounds.height, 0)
view.layer.transform = transform
UIView.animate(withDuration: 0.5, animations: {
view.alpha = 1
view.layer.transform = CATransform3DIdentity
})
}
}
public func addSections(sections: IndexSet) {
shouldAnimate = true
CATransaction.begin()
CATransaction.setCompletionBlock({ self.shouldAnimate = false })
tableView.beginUpdates()
tableView.insertSections(sections, with: .top)
tableView.endUpdates()
tableView.scrollToRow(at: IndexPath(row: 0, section: 1), at: .top, animated: true)
CATransaction.commit()
}
So willDisplay cell/header handles the animation from the bottom of the screen.
shouldAnimate cancels the cells animations after finishing the insertSection
How about
create a new table, add it as a subview off the bottom of the screen
and call reloadData to populate it using just the data for the second
section.
Animate this new table into place, below the existing
section.
Once the new section is in place, reload the main table with all the data, then remove the second table from it's superview.
Related
I have created a custom UICollectionViewCell and it contains a subView named animView which I intend to animate. The shape of animView is a regular vertical rectangle.
In the configureCell() method, I use the code below to configure the cells and create an animation, but I do not see the animation.
animView.transform = CGAffineTransform(scaleX: 1.0, y: 0.5)
self.layoutIfNeeded()
UIView.animate(withDuration: 0.7, delay: 0, options: [.curveEaseOut], animations: {
self.animView.transform = CGAffineTransform.identity
self.layoutIfNeeded()
}, completion: nil)
However, when I try to animate whole cell, I see the cell animate successfully.
self.transform = CGAffineTransform(scaleX: 1.0, y: 0.5)
self.layoutIfNeeded()
UIView.animate(withDuration: 0.7, delay: 0, options: [.curveEaseOut], animations: {
self.transform = CGAffineTransform.identity
self.layoutIfNeeded()
}, completion: nil)
I appreciate your time and answer.
EDIT:
As per the suggestions, I changed where I call the animation as shows below. Now, when I try to animate whole cell it is working. However, I want to animate the animView which is a subview of the custom UICollectionViewCell (BarCell). But it is not animating. Any ideas?
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let cells = collectionView.visibleCells
for cell in cells {
let current = cell as! BarCell
let curAnimView = current.animView
curAnimView?.transform = CGAffineTransform(translationX: 0, y: current.animFillHeightCons.constant)
UIView.animate(withDuration: 0.7) {
curAnimView?.transform = CGAffineTransform.identity
}
}
}
You need to start animation for only visible cells.
Here is how you can do that.
add this
UIScrollViewDelegate
It will provide the method
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let cells = colAnim.visibleCells
for cell in cells
{
let current = cell as! testCollectionViewCell
UIView.animate(withDuration: 2.0, animations: {
current.animView.transform = CGAffineTransform(translationX: -400, y: 0)
//Change this animation
})
}
}
Also make sure do this also.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "testCollectionViewCell", for: indexPath) as! testCollectionViewCell
cell.animView.transform = CGAffineTransform(translationX: 0, y: 0)// This one
return cell
}
By doing this you can achieve animation only when cell is visible.
Thanks.
There are 2 conditions to make it happen:
You will need to reset the properties you are animating every cell binding
You will have to start the animation only when the cell is about to be visible
Also, there are exceptions (like what I had), sometimes the cell binding or the layout of your collection is complex or custom thus, in these cases we will have to delay the animation so it will actually work.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// your dequeue code...
// ...
// Reseting your properties as described in 1
cell.myImageView.alpha = 1
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let myCell = cell as! MyCell
// As described in 2
myCell.animate()
}
class MyCell: UICollectionViewCell {
func animate() {
// Optional: As described above, if your scene is custom/complex delaying the animation solves it though it depends on your code
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
// Animation code
UIView.animate(withDuration: 1,
delay: 0,
options: [.autoreverse, .repeat]) {
self.seenNotSeenImageView.alpha = 0.5
}
}
}
}
There's also a third condition:
If your cell will be covered by another VC, you will want to initiate the same animated() method as follow, in your viewWillAppear of the VC that holds the collectionView:
collectionView.visibleCells.forEach{($0 as? MyCell)?.animate()}
I have the same issue as this one.
I have tried to implement a suggested fix, and instead of a table view going the whole way up and then going to the last row again, I got the small jumps up. I need to make the animations smoother. Can it be done with UITableViewAutomaticDimension?
My code is as below:
#objc fileprivate func addNext() {
guard rowsCount.value < cellModels.count else { return }
let lastIndexPath = IndexPath(row: rowsCount.value, section: 0)
rowsCount.value += 1
CATransaction.begin()
CATransaction.setCompletionBlock({ () -> Void in
self.scrollToLastMessage()
})
tableView.update {
tableView.insertRows(at: [lastIndexPath], with: .none)
}
CATransaction.commit()
}
func scrollToLastMessage()
{
let bottomRow = tableView.numberOfRows(inSection: 0) - 1
let bottomMessageIndex = IndexPath(row: bottomRow, section: 0)
guard bottomRow >= 0
else { return }
CATransaction.begin()
CATransaction.setCompletionBlock({ () -> Void in
self.tableView.scrollToRow(at: bottomMessageIndex, at: .bottom, animated: true)
})
let contentOffset = tableView.contentOffset.y
let newContentOffset = CGPoint(x:0, y:contentOffset + 1)
tableView.setContentOffset(newContentOffset, animated: false)
CATransaction.commit()
}
extension UITableView {
typealias Updates = () -> Void
func update(_ updates: Updates) {
beginUpdates()
updates()
endUpdates()
}
}
Thanks in advance.
I resolved this problem when I added estimated row height for my table view:
var cellHeightsDictionary: [Int:CGFloat] = [:]
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeightsDictionary[indexPath.row] = cell.frame.size.height
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeightsDictionary[indexPath.row] ?? 44.0
}
I am trying to create expandable UITableViewCells using the following code:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let cell = tableView.cellForRow(at: indexPath) as! feedBaseCell
if cell.expandButton.isExpanded == true {
return 128
} else {
return 64
}
return 64
}
.isExpanded is a custom property of feedBaseCell. When I run this code, the line let cell = tableView.cellForRow(at: indexPath) as! feedBaseCell gets a EXC_BAD_ACCESS error. How can I check the .isExpanded property and return the height based on whether or not it is true or false? Why is my code not working?
EDIT: Code for both cellForRowAt and numberOfRowsInSection:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "feedBaseCell") as! feedBaseCell
cell.backgroundColor = .clear
cell.selectionStyle = UITableViewCellSelectionStyle.none
cell.contentView.isUserInteractionEnabled = false
cell.expandButton.tag = indexPath.row
cell.expandButton.addTarget(self, action: #selector(self.expandTheCell(_:)), for: .touchUpInside)
cell.contentView.bringSubview(toFront: cell.expandButton)
return cell
}
Here is the expandTheCell method:
func expandTheCell(_ sender: UIButton) {
if sender.isExpanded == false {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
sender.transform = sender.transform.rotated(by: .pi/2)
sender.isExpanded = true
}, completion: nil)
} else {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
sender.transform = sender.transform.rotated(by: -.pi/2)
sender.isExpanded = false
}, completion: nil)
}
tableView.beginUpdates()
tableView.endUpdates()
}
Never call cellForRow(at:) from within heightForRowAt. It causes infinite recursion because the table view tries to get the cell's height when you ask for the cell.
If you really, really need to get a cell to calculate its height, directly call your view controller's dataSource method tableView(_:cellForRowAt:) method instead of the table view's cellForRow(at:) method.
But in this case you don't need the cell. You should be storing the expanded state of each row in your data source data, not in the cell itself. Look at that data, not the cell, to determine the height in your heightForRowAt method. And of course you will need this state information in your cellForRowAt data source method so you can set the cell's state there since cells get reused as you scroll the table view.
I have this code that is setup so that it calls and animates the tableview cells up upon displaying. How could I set this up so it only animates the cells in that appear on the initial loading of the view?
Thanks in advance...
var preventAnimation = Set<NSIndexPath>()
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
//
if !preventAnimation.contains(indexPath) {
preventAnimation.insert(indexPath)
let rotationTransform = CATransform3DTranslate(CATransform3DIdentity, 0, +600, 0)
cell.layer.transform = rotationTransform
UIView.animateWithDuration(0.6, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5, options: .CurveEaseOut, animations: {
cell.layer.transform = CATransform3DIdentity
}, completion: nil)
}
}
You can just use a variable maxCellIndexAppeared to recode the maximum indexPath.row for the cell that have appeared,it better than using a set to contain all the indexPath.
In the initialize method set maxCellIndexAppeared to zero,and then compare it with the indexPath.row when the cell will display,if current cell indexPath greater than the variable then do your animation otherwise return.
Just like this:
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if maxCellIndexAppeared > indexPath.row {
return
}
//here reset the current maximum index
maxCellIndexAppeared = indexPath.row
//And then do your animation
....do your animation.....
}
//Declare Bool array which will keep track of the rows which are animated
var animationShown : [Bool]?
// Initalize animationShown array in your viewDidLoad
self.animationShown = [Bool] (repeating: false, count: YourTableViewRowsCount)
// MARK: - Animate Tableview Cell
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
{
if self.animationShown?[indexPath.row] == false
{
//Transform Animation
let transform = CATransform3DTranslate(CATransform3DIdentity, -200, 0, 0)
cell.layer.transform = transform
UIView.animate(withDuration: 0.5, animations:
{
cell.layer.transform = CATransform3DIdentity
})
self.animationShown?[indexPath.row] = true
}
}
Hope this helps!! Happy Coding...
I am following THIS tutorial and achieve that animation with this code:
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
cell.layer.transform = CATransform3DMakeScale(0.1,0.1,1)
UIView.animateWithDuration(0.25, animations: {
cell.layer.transform = CATransform3DMakeScale(1,1,1)
})
}
But I want to update something in this cell animation like when I scrolling into tableView the cell is small like (0.1,0.1,1) at the start and after that It scales like (1,1,1) but I want to apply like bubble type effect like it is small at start after that it comes at its original Scale like (1,1,1) and one it is zoom and again it comes into its original scale like (1,1,1).
please guide me how can I achieve that animation?
EDIT:
I tried this but its not that kind of smooth and not exact what I want.
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
cell.layer.transform = CATransform3DMakeScale(0.1,0.1,1)
UIView.animateWithDuration(0.3, animations: {
cell.layer.transform = CATransform3DMakeScale(1,1,1)
})
UIView.animateWithDuration(1, animations: {
cell.layer.transform = CATransform3DMakeScale(2,2,2)
})
UIView.animateWithDuration(0.3, animations: {
cell.layer.transform = CATransform3DMakeScale(1,1,1)
})
}
What you need is an ease out back animation. For more information check out http://easings.net
You can make parametric animations using this library https://github.com/jjackson26/JMJParametricAnimation/tree/master/JMJParametricAnimationDemo
For now, the effect you are trying to do can be roughly done using the code below. You have to do the scaling animation one after another. The way you have done makes all animations start together.
Adding the next animation code in the completion block starts it after the animation. You can further tweak the timings to get the rough effect you need.
cell.layer.transform = CATransform3DMakeScale(0.1,0.1,1)
UIView.animateWithDuration(0.3, animations: {
cell.layer.transform = CATransform3DMakeScale(1.05,1.05,1)
},completion: { finished in
UIView.animateWithDuration(0.1, animations: {
cell.layer.transform = CATransform3DMakeScale(1,1,1)
})
})
Swift 4
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
UIView.animate(withDuration: 0.4) {
cell.transform = CGAffineTransform.identity
}
}
Swift 3 version works like charm!
cell.layer.transform = CATransform3DMakeScale(0.1, 0.1, 1)
UIView.animate(withDuration: 0.3, animations: {
cell.layer.transform = CATransform3DMakeScale(1.05, 1.05, 1)
},completion: { finished in
UIView.animate(withDuration: 0.1, animations: {
cell.layer.transform = CATransform3DMakeScale(1, 1, 1)
})
})
Use this code to get spiral cells animation in your tableview
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
//1. Setup the CATransform3D structure
var rotation = CATransform3DMakeRotation( CGFloat((90.0 * M_PI)/180), 0.0, 0.7, 0.4);
rotation.m34 = 1.0 / -600
//2. Define the initial state (Before the animation)
cell.layer.shadowOffset = CGSize(width: 10.0, height: 10.0)
cell.alpha = 0;
cell.layer.transform = rotation;
cell.layer.anchorPoint = CGPoint(x: 0.0, y: 0.5)
//3. Define the final state (After the animation) and commit the animation
cell.layer.transform = rotation
UIView.animate(withDuration: 0.8, animations:{cell.layer.transform = CATransform3DIdentity})
cell.alpha = 1
cell.layer.shadowOffset = CGSize(width: 0, height: 0)
UIView.commitAnimations()
}
Use this code to achieve animation in your tableview
func tableView(_ tableView: UITableView, willDisplay cell:
UITableViewCell, forRowAt indexPath: IndexPath) {
let rotationAngleInRadians = 360.0 * CGFloat(.pi/360.0)
// let rotationTransform = CATransform3DMakeRotation(rotationAngleInRadians, -500, 100, 0)
let rotationTransform = CATransform3DMakeRotation(rotationAngleInRadians, 0, 0, 1)
cell.layer.transform = rotationTransform
UIView.animate(withDuration: 1.0, animations: {cell.layer.transform = CATransform3DIdentity})
}
Use This Code to Animate Tableview cell from left
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let rotationTransform = CATransform3DTranslate(CATransform3DIdentity, -200, 0, 0)
cell.layer.transform = rotationTransform
cell.alpha = 0.5
UIView.animate(withDuration: 0.5){
cell.layer.transform = CATransform3DIdentity
cell.alpha = 1.0
}
}