I created a table view and each cell contains an image (covers the entire cell) and a title in the center. However, when I highlight one of the cells, nothing happens. It doesn't get darker which I thought was default. I tried using the code below to fix this, but it doesn't work.
func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
let cell = (tableView.cellForRow(at: indexPath) as! MainPackCell)
UIView.animate(withDuration: 0.4) {
cell.packImageView.highlightedImage = cell.packImageView.image?.withRenderingMode(.alwaysTemplate)
if #available(iOS 10.0, *) {
cell.packImageView.tintColor = UIColor(displayP3Red: 0, green: 0, blue: 0, alpha: 0.3)
} else {
// Fallback on earlier versions
}
UIView.animate(withDuration: 0.4, animations: {
tableView.deselectRow(at: indexPath, animated: true)
})
}
}
Populate the TableVIew and refer to the Screenshot below:---
Use this and it is Done:-- Man
implement it after cellForRowAt
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
Related
Why do animations not work when deselecting a UITableViewCell in a UITableView asynchronously?
Below is a simple example. If you tap and release the first cell, it deselects with the animation as you would expect.
If you tap and release the second cell, you may not notice anything happen at all. You may even need to hold the cell to see the selection, and when you release it reverts back to the unselected state immediately.
This is obviously a contrived example but it shows the essence of the issue. If I wanted to delay the deselection until something had happened I would face the same issue.
Why does this happen and is there any way around it?
import UIKit
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = indexPath.row == 0 ? "Regular deselection" : "Async deselection"
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
tableView.deselectRow(at: indexPath, animated: true)
} else {
DispatchQueue.main.async {
tableView.deselectRow(at: indexPath, animated: true)
}
}
}
}
This is a little curious...
There are a number of UI controls that have "internal processes" - that is, stuff goes on that we're not immediately aware of.
One example is a standard UIButton. During the touch-down / touch-up sequence, the button's Title Label does a cross-fade from .normal to .highlighted and back again.
So, my assumption is:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
// 1
tableView.deselectRow(at: indexPath, animated: true)
} else {
DispatchQueue.main.async {
// 2
tableView.deselectRow(at: indexPath, animated: true)
}
}
}
For the first case, UIKit probably "queues up" the highlight/unhighlight sequence.
Whereas in the second case, we are telling the table view to deselect the row on the next run-loop ... which will happen pretty much immediately. At that point, we're (in effect) interrupting the default sequence, and the table view un-highlights the row before it finishes (or, practically, before it starts) highlighting the row.
What's even more curious... if we leave a row selected, and then at a later point (such as a tap elsewhere) and then call tableView.deselectRow(at: indexPath, animated: true), the animation appears to be much shorter, to the point where it doesn't really even look animated.
Here's something to play around with -- maybe you'll find one approach suitable for your goal.
class DeselViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
let noAnimBtn = UIBarButtonItem(title: "No Anim", style: .plain, target: self, action: #selector(noAnim(_:)))
let animBtn = UIBarButtonItem(title: "With Anim", style: .plain, target: self, action: #selector(withAnim(_:)))
navigationItem.rightBarButtonItems = [animBtn, noAnimBtn]
}
#objc func noAnim(_ b: Any?) -> Void {
if let p = tableView.indexPathForSelectedRow {
tableView.deselectRow(at: p, animated: false)
}
}
#objc func withAnim(_ b: Any?) -> Void {
if let p = tableView.indexPathForSelectedRow {
UIView.animate(withDuration: 0.3, animations: {
self.tableView.deselectRow(at: p, animated: false)
})
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 8
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
switch indexPath.row {
case 0:
cell.textLabel?.text = "Regular deselection"
case 1:
cell.textLabel?.text = "Async deselection"
case 2:
cell.textLabel?.text = "Async deselection with Anim Duration"
case 3:
cell.textLabel?.text = "Asnyc Delay deselection"
case 4:
cell.textLabel?.text = "Asnyc Delay Plus Anim Duration deselection"
case 5:
cell.textLabel?.text = "Asnyc Long Delay deselection"
case 6:
cell.textLabel?.text = "Asnyc Long Delay Plus Anim Duration deselection"
default:
cell.textLabel?.text = "Manual deselection"
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch indexPath.row {
case 0:
tableView.deselectRow(at: indexPath, animated: true)
case 1:
DispatchQueue.main.async {
tableView.deselectRow(at: indexPath, animated: true)
}
case 2:
DispatchQueue.main.async {
UIView.animate(withDuration: 0.3, animations: {
tableView.deselectRow(at: indexPath, animated: true)
})
}
case 3:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
tableView.deselectRow(at: indexPath, animated: true)
})
case 4:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
UIView.animate(withDuration: 0.3, animations: {
tableView.deselectRow(at: indexPath, animated: true)
})
})
case 5:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.75, execute: {
tableView.deselectRow(at: indexPath, animated: true)
})
case 6:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.75, execute: {
UIView.animate(withDuration: 0.3, animations: {
tableView.deselectRow(at: indexPath, animated: true)
})
})
default:
()
// leave selected
}
}
}
Set that as the root view of a navigation controller, so it can put right-bar buttons for "Manual" deselection:
I have a drop-down list that I implemented using table view:
When choosing another floor, information in the center should be substituted.
I have extension:
extension DDList: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (Manager.shared()?.location.floor.count)!
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = Manager.shared()?.location.floor[indexPath.row].name
cell.textLabel?.textAlignment = .center
cell.textLabel?.textColor = .white
cell.textLabel?.font = font
cell.backgroundColor = UIColor.black
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let indexPath = Manager.shared()?.location.floor[indexPath.row].name
let currentCell = tableView.cellForRow(at: indexPath) as UITableViewCell
let currentItem = currentCell.textLabel!.text
}
I can’t make the functionality of selecting an item from a list in a didSelectRowAt method, please tell me how to do it? So that the selected floor is saved from the top to the middle where at the moment (1-st floor)
I would prefer to do that:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let nameOfCurrentItem = Manager.shared()?.location.floor[indexPath.row].name
//if you use a navigation bar
self.navigationBar.topItem?.title = nameOfCurrentItem
//if you use a label
myLabel.text = nameOfCurrentItem
}
If you need an animation:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let nameOfCurrentItem = Manager.shared()?.location.floor[indexPath.row].name
if let currentCell = tableView.cellForRow(at: indexPath){
UIView.animate(withDuration: 0.3, animations: {
currentCell.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
//or any other animation
//currentCell.alpha = 0.5
}) { (_) in
UIView.animate(withDuration: 0.3, animations: {
currentCell.transform = CGAffineTransform.identity
//or any other animation
//currentCell.alpha = 1.0
}) { (_) in
//if you use a navigation bar
self.navigationBar.topItem?.title = nameOfCurrentItem
//if you use a label
self.myLabel.text = nameOfCurrentItem
}
}
}
}
Also ensure that your tableViewCell and tableView itself are able to be selected in the attributes inspector:
TableView selection settings
TableViewCell selection settings
I have already read some issues and not properly uses about spring animations in Swift but I am a little bit confused about this case. I have a ViewController which has a UITableView. I would like to add some little spring bouncing animation to its cells. When a cell tapped it should be expanding and running the bouncing animation and it works perfectly for the first time. But after a cell is expanded and tapped again, the animation is ignored, but the code inside animations is perfectly running (e.g. a print command). Do you have any idea to achieve that goal to make the animation work twice or more? I think I theoretically missed something.
My ViewController:
class TestTableViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var selectedIndex: IndexPath = IndexPath(row: 0, section: 0)
var isExpanded = [Bool]()
var currentExpandedIndexPath: IndexPath?
override func viewDidLoad() {
super.viewDidLoad()
isExpanded = Array(repeating: false, count: 15)
tableView.delegate = self
tableView.dataSource = self
}
}
extension TestTableViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 15
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TestTableViewCell", for: indexPath) as! TestTableViewCell
cell.selectionStyle = .none
cell.animate(duration: 0.5, delay: 0.2, damping: 0.5, options: .curveEaseOut)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if isExpanded[indexPath.row] == true { return 300 }
return 150
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// This is just for handling that to be only one cell that is expanded at one time
isExpanded[indexPath.row] = !isExpanded[indexPath.row]
if (currentExpandedIndexPath != nil) {
if (indexPath == currentExpandedIndexPath) {
currentExpandedIndexPath = nil
} else {
isExpanded[currentExpandedIndexPath!.row] = false
currentExpandedIndexPath = indexPath
}
} else {
currentExpandedIndexPath = indexPath
}
tableView.beginUpdates()
tableView.reloadRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
}
}
And this is my TableViewCell class:
class TestTableViewCell: UITableViewCell {
func animate(duration: TimeInterval, delay: TimeInterval, damping: CGFloat, options: UIView.AnimationOptions = []) {
UIView.animate(withDuration: duration, delay: delay, usingSpringWithDamping: damping, initialSpringVelocity: 1, options: options, animations: {
self.contentView.layoutIfNeeded()
print("this command runs everytime")
})
}
}
These links are GIFs that show how it's working now. If I tap another cell after one expanded it has correct animation (first link). But if I tap the expanded one, it is not animated (second link).
Tapping one after one expanded
Tapping the same cell after it is expanded
There are two possibilities here:
1 - Following code possibly overrides your animation:
tableView.reloadRows(at: [indexPath], with: .automatic)
Try passing .none or some other flag.
2 - Following code must be called from main thread a.k.a DispatchQueue.main.
self.contentView.layoutIfNeeded()
There is also a 3rd possibility that is reloadRows does not necessarily call cellForRowAt where you are currently handling animations.
I have implemented swipe to delete on UITableView, it works correctly with default behaviour, i.e. content of cell is moving to the left while swiping like on the image. What I want to achive (customize) is to have this delete button appear over the cell content while swiping i.e. text in the cell isn't moved.
I am using this method
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let delete = UITableViewRowAction(style: .destructive, title: "Delete") { (action, indexPath) in
self.deletionDidStart(of: self.element(for: indexPath)!, at: indexPath)
// use helper function to delete element at given index path
let deleted = self.deleteElement(for: indexPath)
// refresh tableView
self.tableView.beginUpdates()
self.tableView.deleteRows(at: [indexPath], with: .automatic)
if let deletedSection = deleted.section {
self.tableView.deleteSections(IndexSet(integer: indexPath.section), with: .automatic)
self.deletedSection(deletedSection, at: indexPath)
}
self.tableView.endUpdates()
// call deletion event handler
self.deletedElement(deleted.element!, at: indexPath)
}
return [delete]
}
You should override setEditing method of your UITableViewCell,
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
let leadingSpace = 15
let editingOffset = 80
if editing == true {
self.labelLeadingConstraint.constant = CGFloat(leadingSpace + editingOffset)
} else {
self.labelLeadingConstraint.constant = CGFloat(leadingSpace);
}
UIView.animate(withDuration: 0.1) {
self.layoutIfNeeded()
}
}
You need to take IBOutlet of your UILabel's leading Constraint and do it like this way.
This is suggestion only, actual code may be changed depending on your Cell layout.
UPDATE
Use below functions as well.
func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
cell?.setEditing(true, animated: true)
}
func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) {
if let indexPath = indexPath {
let cell = tableView.cellForRow(at: indexPath)
cell?.setEditing(false, animated: true)
}
}
Hope this helps to you.
Here is the problem, when I use a TableViewController and add a behavior on cell been selected. The behavior showed twice
How can I avoid this?
// MARK: - Table Deleget
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
UIView.animate(withDuration: 0.2, animations: {
cell?.viewWithTag(100)?.isHidden = true
(cell?.viewWithTag(200) as! UILabel).textColor = UIColor.red
})
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
UIView.animate( withDuration: 0.3, animations: {
cell?.viewWithTag(100)?.isHidden = false
(cell?.viewWithTag(200) as! UILabel).textColor = UIColor(red: 0, green: 128/255, blue: 128/255, alpha: 1)
})
}
Move the animation from the 'cellForRow' method to 'willDisplayCell' method. I think it can help to you avoid the twice animation.
I have fixed it, add a var to remember the cell which has been taped, and use cellWillDisplay to refresh every cell will displayed, check each cell if it has been selected, if has, show it the selected way.
// MARK: - Table Deleget
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
index = indexPath.row
if let cell = tableView.cellForRow(at: indexPath){
UIView.animate(withDuration: 0.2, animations: {
cell.viewWithTag(100)?.isHidden = true
(cell.viewWithTag(200) as! UILabel).textColor = UIColor.red
})
}
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
UIView.animate( withDuration: 0.3, animations: {
cell.viewWithTag(100)?.isHidden = false
(cell.viewWithTag(200) as! UILabel).textColor = UIColor(red: 0, green: 128/255, blue: 128/255, alpha: 1)
})
}
}
// Added this
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if let index = index, index == indexPath.row {
cell.viewWithTag(100)?.isHidden = true
(cell.viewWithTag(200) as! UILabel).textColor = UIColor.red
} else {
cell.viewWithTag(100)?.isHidden = false
(cell.viewWithTag(200) as! UILabel).textColor = UIColor(red: 0, green: 128/255, blue: 128/255, alpha: 1)
}
}