I want to animate UITableViewCell only in one direction [down direction]
my code:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.alpha = 0
let transform = CATransform3DTranslate(CATransform3DIdentity, 0, 200 , 0)
cell.layer.transform = transform
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
cell.alpha = 1
cell.layer.transform = CATransform3DIdentity
})
}
how can i stop that animation [don't perform animation] while user scroll upside?
thanks!!
If you have just one section, you can simply compare the current indexPath with the one that was presented as last, and animate only when the new indexPath.row is greater:
fileprivate var lastIndexPath: IndexPath = IndexPath(row: -1, section: 0)
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// perform animation only when new indexPath.row is greater then the last presented
if lastIndexPath.row < indexPath.row {
cell.alpha = 0
let transform = CATransform3DTranslate(CATransform3DIdentity, 0, 200 , 0)
cell.layer.transform = transform
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
cell.alpha = 1
cell.layer.transform = CATransform3DIdentity
})
}
lastIndexPath = indexPath
}
In case of multiple sections, the approach is the same, just the comparison would be a bit more complicated, because you will have to take sections into account. So instead of:
if lastIndexPath.row < indexPath.row {
You would have to use something like:
if lastIndexPath.section < indexPath.section ||
(lastIndexPath.section == indexPath.section && lastIndexPath.row < indexPath.row) {
Related
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 am trying to display new tableview cells with a brief pause between each one appearing so the user can distinctly see new rows have been added to an exiting tableview. I have tried the code below with different animation times but it isn't working.
self.tableView.reloadData()
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
UIView.animate(withDuration: 24.8, animations: {
cell.contentView.alpha = 24.0
}
}
Try this:
UIView.animate(withDuration: 0.3,
delay: 0.5,
options: .curveEaseInOut,
animations: {
//Animation code.....
},
completion: nil)
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.
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
}
}