I have table view. Inside cell I have method that animates view inside cell.
func animateCell() {
UIView.animate(withDuration: 0.4, animations: { [weak self] in
self?.quantityBackround.transform = CGAffineTransform(scaleX: 25, y: 25)
}) { [weak self] _ in
UIView.animate(withDuration: 0.1) {
self?.quantityBackround.transform = CGAffineTransform.identity
}
}
}
Also I have prepareForReuse()
override func prepareForReuse() {
quantityBackround.transform = CGAffineTransform.identity
}
The animation must work only for last cell when array of datasource changes and I do this in property observer like this (fires when something is being added to array)
guard let cell = checkTableView.cellForRow(at: IndexPath(row: viewModel.checkManager.check.count - 1, section: 0)) as? CheckItemTableViewCell else { return }
cell.animateCell()
All of this works fine.
One problem, is that I encounter is that when tableView is reloaded, all background views in all cells expand from zero size to its initial. Last cell animates ok.
I think that i miss something in prepareForReuse and because of this i see this glitch of inreasing from zero to initial size.
How to fix it ?
You need to implement this method of UITableViewDelegate
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
//here check if your this cell is the last one, something like this
if (indexPath.row == yourDataSourceArray.count - 1)
{
if let customCell = cell as? CheckItemTableViewCell{
customCell.animateCell()
}
}
}
Hope this helps
Related
I have a cell class which implements a textfield delegate. In this delegate I am calling a function to tell the tableview to scroll to a specific row based off an indexPath. This works in most cases but not when the row is at the bottom of the table view. The cell class has a table property which is passed in, in my main controllers cellForRow method. Code below:
extension IR_TextCell: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
util_textField.addHightlightedBorder(textField)
if let index = table?.indexPath(for: self)
{
scrollDelegate?.scrollToMe(at: index)
}
}
}
func scrollToMe(at index: IndexPath) {
self.tableV.scrollToRow(at: index, at: .middle, animated: false)
}
I have tried wrapping DispatchQueue.main.async around this and adding a deadline but it didn't make a difference.
Do I need to change my tableview's bottom constraint maybe?
My situation is a little different than yours but I had a same issue scrolling to cells that are near the bottom. It might now work for your exact situation but I hope this helps someone who comes across this posting. I suspected that it might be a timing issue so I ended up doing it like below:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == tableView.indexPathsForVisibleRows?.last?.row {
let scrollIndex = 0//set to your predetermined scrolled to index
let cellRect = tableView.rectForRow(at: indexPath)
let completelyVisible = tableView.bounds.contains(cellRect)
if scrollIndex >= indexPath.row && !completelyVisible {
let maxIndex = 10//number of elements in the array - 1
//in case you want a delay
DispatchQueue.main.asyncAfter(deadline: .now() + 0) {
self.tableView.scrollToRow(at: IndexPath(row: scrollIndex > maxIndex ? maxIndex : scrollIndex, section: 0), at: .middle, animated: false)
}
}
}
}
I have an app that pulls objects from Firebase, then displays them in a table. I've noticed that if I delete 5 entries (this is about when I get to the reused cells that were deleted), I can't delete any more (red delete button is unresponsive) & can't even select the cells. This behavior stops when I comment out override func prepareForReuse() in the TableViewCell.swift controller. Why???
The rest of the app functions normally while the cells are just unresponsive. Weirdly, if I hold one finger on a cell and tap the cell with another finger, I can select the cell. Then, if I hold a finger on the cell and tap the delete button, that cell starts acting normally again. What is happening here??? Here is my code for the table & cells:
In CustomTableViewCell.swift >>
override func prepareForReuse() {
// CELLS STILL FREEZE EVEN WHEN THE FOLLOWING LINE IS COMMENTED OUT?!?!
cellImage.image = nil
}
In ViewController.swift >>
override func viewDidLoad() {
super.viewDidLoad()
loadUserThings()
}
func loadUserThings() {
ref.child("xxx").child(user!.uid).child("yyy").queryOrdered(byChild: "aaa").observe(.value, with: { (snapshot) in
// A CHANGE WAS DETECTED. RELOAD DATA.
self.arr = []
for tempThing in snapshot.children {
let thing = Thing(snapshot: tempThing as! DataSnapshot)
self.arr.append(thing)
}
self.tableView.reloadData()
}) { (error) in
print(error)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
let cellData = arr[indexPath.row]
...
// SET TEXT VALUES OF LABELS IN THE CELL
...
// Setting image to nil in CustomTableViewCell
let imgRef = storageRef.child(cellData.imgPath)
let activityIndicator = MDCActivityIndicator()
// Set up activity indicator
cell.cellImage.sd_setImage(with: imgRef, placeholderImage: nil, completion: { (image, error, cacheType, ref) in
activityIndicator.stopAnimating()
delay(time: 0.2, function: {
UIView.animate(withDuration: 0.3, animations: {
cell.cellImage.alpha = 1
})
})
})
if cell.cellImage.image == nil {
cell.cellImage.alpha = 0
}
// Seems like sd_setImage doesn't always call completion block if the image is loaded quickly, so we need to stop the loader before a bunch of activity indicators build up
delay(time: 0.2) {
if cell.cellImage.image != nil {
activityIndicator.stopAnimating()
cell.cellImage.alpha = 1
}
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// instantly deselect row to allow normal selection of other rows
tableView.deselectRow(at: tableView.indexPathForSelectedRow!, animated: false)
selectedObjectIndex = indexPath.row
self.performSegue(withIdentifier: "customSegue", sender: self)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
print("should delete")
let row = indexPath.row
let objectToDelete = userObjects[row]
userObjects.remove(at: row)
ref.child("users/\(user!.uid)/objects/\(objectToDelete.nickname!)").removeValue()
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
if (self.tableView.isEditing) {
return UITableViewCellEditingStyle.delete
}
return UITableViewCellEditingStyle.none
}
A few things. For performance reasons, you should only use prepareForReuse to reset attributes that are related to the appearance of the cell and not content (like images and text). Set content like text and images in cellForRowAtIndexPath delegate of your tableView and reset cell appearance attributes like alpha, editing, and selection state in prepareForReuse. I am not sure why it continues to behave badly when you comment out that line and leave prepareForReuse empty because so long as you are using a custom table view cell an empty prepareForReuse should not affect performance. I can only assume it has something to do with you not invoking the superclass implementation of prepareForReuse, which is required by Apple according to the docs:
override func prepareForReuse() {
// CELLS STILL FREEZE EVEN WHEN THE FOLLOWING LINE IS COMMENTED OUT?!?!
super.prepareForReuse()
}
The prepareForReuse method is only ever intended to do minor cleanup for your custom cell.
I am trying to make a expandable table view (static cells). A container view is placed inside a cell below the "Lists of options" cell, as shown below:
The problem comes with the animation when expanding / collapsing. The topmost cell of each section will disappear briefly(hidden?), then reappear after the animation ends. I have later experimented and the results are as followed:
This will not happen when using tableView.reloadData().
This will happen when using tableView.beginUpdates() / endUpdates() pairs.
Hence I have come to the conclusion that this has sth to do with the animation. Below is my code and pictures for further explanation for said issue.
Code for hiding / showing
private func hideContainerView(targetView: UIView) {
if targetView == violationOptionsContainerView {
violationOptionContainerViewVisible = false
}
tableView.beginUpdates()
tableView.endUpdates()
UIView.animate(withDuration: 0.1, animations: { targetView.alpha = 0.0 }) { _ in
targetView.isHidden = true
}
}
private func showContainerView(targetView: UIView) {
if targetView == violationOptionsContainerView {
violationOptionContainerViewVisible = true
}
tableView.beginUpdates()
tableView.endUpdates()
targetView.alpha = 0.0
UIView.animate(withDuration: 0.1, animations: { targetView.alpha = 1.0 }) { _ in
targetView.isHidden = false
}
}
Table view
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
switch cell {
case violationTableViewCell:
if violationOptionContainerViewVisible {
hideContainerView(targetView: violationOptionsContainerView)
} else {
showContainerView(targetView: violationOptionsContainerView)
}
default: break
}
// tableView.reloadData()
// if we use this instead of begin/end updates, the cells won't disappear. But then we'll be ditching animation too.
}
tableView.deselectRow(at: indexPath, animated: true)
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 && indexPath.row == 3 {
if !violationOptionContainerViewVisible {
return 0.0
}
}
return super.tableView(tableView, heightForRowAt: indexPath)
}
Images
Note that the first cell in each section ("FFFF" and the "slider bar") disappears and reappears during the animation
After searching for a few days, I have found the cause to this behavior.
I have, for some reason, set the cells layer.zposition below default zero. This will cause the cell to be seen "below" it's background view (hence disappeared) during the animation even though it's a subview of it.
Adjusting the value back to 0 or higher will remove this issue.
I am new to swift, I am trying to have a UITableView and the cells will be animated to appear one by one. How can I do that? Also, if the newly appeared row of cell not on the screen (hiding below the table). How can I move the table up when each cell appear?
var tableData1: [String] = ["1", "2", "3", "4", "5", "6", "7"]
override func viewWillAppear(animated: Bool) {
tableView.scrollEnabled=false
tableView.alpha=0.0
NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(3), target: self, selector: "animateTable", userInfo: nil, repeats: false)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tableData1.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:TblCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! TblCell
cell.lblCarName.textAlignment = NSTextAlignment.Justified;
return cell
}
func animateTable() {
//what should be the code?//
}
Step-1
In your cellForRowAtIndexPath method where initialize your cell, hide it like that;
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: TblCell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TblCell
cell.continueLabel.textAlignment = .justified
cell.contentView.alpha = 0
return cell
}
Step-2
Let's make fade animation. UITableViewDelegate has willDisplayCell method which is able to detect that when you scroll to top or bottom, first cell will display on the window.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
UIView.animate(withDuration: 0.8, animations: {
cell.contentView.alpha = 1
})
}
Your fade animation is on the progress. The most important thing is that you can not setup your cell's alpha directly in runtime because iOS is doing some special internal handling with the cell as part of your UITableView and ignores your setup. So if you setup your cell's contentView, everything's gonna be fine.
In the UITableView, the rows are prepared automatically for you when they get to be in your "range vision". I'm assuming you are, at least not initially, being able to scroll the tableView, so we would scroll it programmatically making the rows appear as it goes. How are we doing that? Actually UITableView has a method that let us scroll it to a specific row:
scrollToRowAtIndexPath(indexPath : NSIndexPath, atScrollPosition : UITableViewScrollPosition, animated : Bool);
I'm too tired to write the code, so I'm going to try to explain. First, for every cell you set alpha to 0, as soon as they get loaded (cellForRowAtIndexPath).
Then let's suppose our screen fits the 5 first rows. You are going to animate their alphas sequentially (using UIView.animateWithDuration ) until the fifth one (index 4), then you are going to scrollToRowAtIndexPath passing NSIndexPath using 5, then 6,... until the rest of them (using scrollPosition = .Bottom). And for each of them, you would animate as soon as they get loaded. Just remember to put some time between this interactions. (animate first, run NSTimer to the second, and it goes on). And the boolean animated should be true of course.
To appear each visible cell one by one, you can do it by playing with alpha and duration value.
extension UITableView {
func fadeVisibleCells() {
var delayDuration: TimeInterval = 0.0
for cell in visibleCells {
cell.alpha = 0.0
UIView.animate(withDuration: delayDuration) {
cell.alpha = 1.0
}
delayCounter += 0.30
}
}
}
Here is some code which can get you started. In my COBezierTableView
I subclassed UITableView and override the layoutSubviewsmethod. In there you can manipulate the cells according to their relative position in the view. In this example I fade them out in the bottom.
import UIKit
public class MyCustomTableView: UITableView {
// MARK: - Layout
public override func layoutSubviews() {
super.layoutSubviews()
let indexpaths = indexPathsForVisibleRows!
let totalVisibleCells = indexpaths.count - 1
if totalVisibleCells <= 0 { return }
for index in 0...totalVisibleCells {
let indexPath = indexpaths[index]
if let cell = cellForRowAtIndexPath(indexPath) {
if let superView = superview {
let point = convertPoint(cell.frame.origin, toView:superView)
let pointScale = point.y / CGFloat(superView.bounds.size.height)
cell.contentView.alpha = 1 - pointScale
}
}
}
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell .alpha = 1.0
let transform = CATransform3DTranslate(CATransform3DIdentity, 0, 3000, 1200)
//let transform = CATransform3DTranslate(CATransform3DIdentity, 250, 0, 1250)
//let transform = CATransform3DTranslate(CATransform3DIdentity, 250, 1250, 0)
// let transform = CATransform3DTranslate(CATransform3DIdentity, -250, 300, 120)
cell.layer.transform = transform
UIView.animate(withDuration: 1.0) {
cell.alpha = 1.0
cell.layer.transform = CATransform3DIdentity
cell.layer.transform = CATransform3DIdentity
}
}
I think this method will be a great solution.
Set all cell alpha to 0
animate them in tableView(_:,willDisplay:,forRowAt:)
set a delay for every indexPath.row
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.alpha = 0
UIView.animate(withDuration: 0.5, delay: 0.05 * Double(indexPath.row), animations: {
cell.alpha = 1
})
}
I am programmatically adding a UITableView as a subview of a view that uses UIView.animateWithDuration to expand the view when a button is clicked from a single point to a full window. Basically, a box that starts as a point and expands to full size with an animation. I am having difficulties getting the table to populate with cells. At first, a cell was being created, but would disappear after quickly after the animation completed, after playing around with it, I have gotten the cell to remain after the animation is complete, but now the cell disappears when I tap on it. I don't understand what is going on here. Can someone please help?
Here is my code. Note, I have removed what I believe to be irrelevant to this problem to make the code easier to read.
class PokerLogSelectionView: UIViewController {
let logSelectionTableViewController = LogSelectionTableViewController()
let logSelectionTableView = UITableView()
// Irrelevant class variables removed
init(btn : PokerLogSelectionButton){
// Irrelevant view initialization code removed
// Display the subviews
self.displayLogListScrollView()
}
func displayLogListScrollView() {
// Frame is set to (0,0,0,0)
let frame = CGRect(x: self.subviewClosed, y: self.subviewClosed, width: self.subviewClosed, height: self.subviewClosed)
logSelectionTableView.delegate = self.logSelectionTableViewController
logSelectionTableView.dataSource = self.logSelectionTableViewController
// Set the frame of the table view
logSelectionTableView.frame = frame
// Give it rounded edges
logSelectionTableView.layer.cornerRadius = 10
// Remove the cell divider lines
logSelectionTableView.separatorStyle = UITableViewCellSeparatorStyle.None
logSelectionTableView.backgroundColor = logSelectionViewContentScrollViewColor
self.view.addSubview(logSelectionTableView)
//self.logSelectionTableView.reloadData()
//self.addChildViewController(logSelectionTableViewController)
}
override func viewDidAppear(animated: Bool) {
// Create animation
let timeInterval : NSTimeInterval = 0.5
let delay : NSTimeInterval = 0
UIView.animateWithDuration(timeInterval, delay: delay, options: UIViewAnimationOptions.CurveEaseOut, animations: {
// Irrelevant code removed
// Set the size and position of the view and subviews after the animation is complete
self.view.frame = CGRect(x: self.frameXopen, y: self.frameYopen, width: self.frameWopen, height: self.frameHopen)
self.logSelectionTableView.frame = CGRect(x: self.subviewXopen, y: self.svYopen, width: self.subviewWopen, height: self.svHopen)
}, completion: { finished in
self.addChildViewController(self.logSelectionTableViewController)
})
}
}
class LogSelectionTableViewController : UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(LogSelectionCell.self, forCellReuseIdentifier: "logCell")
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return pokerLibrary.logNames.count
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 20
}
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("Selected row: \(indexPath.row)")
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell : LogSelectionCell = self.tableView.dequeueReusableCellWithIdentifier("logCell") as? LogSelectionCell {
cell.selectionStyle = UITableViewCellSelectionStyle.None
cell.textLabel!.text = pokerLibrary.logNames[indexPath.row].name
return cell
}
fatalError("Could not dequeue cell of type 'LogSelectionCell'")
}
}
Note: I can see the tableview after the animation is complete. The color is different than the view in the background view and the tableview does not disappear, just the cell. I expect there to be 1 cell, and I have printed out the number of rows in section 0 and it always returns 1.
Thanks for the help!
Edit:
Here is a screenshot of the view hierarchy before the cell disappears.
Here is a screenshot of the view hierarchy after I tap the cell and it disappears.
I overrode the touchesBegan method in my custom cell and did not call its superclass method. This stopped the cell from disappearing when I tap it, but it still disappears when I scroll the tableView.