I couldn't find this exact question, so I am posting it.
I was looking for a way to deselect all the rows or cells that are currently selected in a UITableView
I found that the simpler solution is to extend UITableView
extension UITableView {
func deselectAllRows(animated: Bool) {
guard let selectedRows = indexPathsForSelectedRows else { return }
for indexPath in selectedRows { deselectRow(at: indexPath, animated: animated) }
}
}
Simple one-liner: to deselect all rows, select nil! Like this
tableView.selectRow(at:nil...
(and fill out the rest of the call however you like, depending whether you want animation and scrolling).
you can also do:
tableView.deselectRow(at: indexPath, animated: true)
inside of func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
not sure if this works..
extension UITableView {
func deselectAllRows() {
guard allowsSelection else { return }
let multipleSelect = allowsMultipleSelection
allowsSelection = false
if multipleSelect {
allowsMultipleSelection = true
} else {
allowsSelection = true
}
}
}
Related
I have a FollowVC and FollowCell Setup with collection View. I can display all the datas correctly into my uIcollection view cell using the following code with no problem.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCellWithReuseIdentifier("FollowCell", forIndexPath: indexPath) as? FollowCell {
let post = posts[indexPath.row]
cell.configureCell(post, img: img)
if cell.selected == true {
cell.checkImg.hidden = false
} else {
cell.checkImg.hidden = true
}
return cell
}
}
Note that I could also select and deselect multiple images using the following code
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
if deletePressed == true {
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! FollowCell
cell.checkImg.hidden = false
} else {
let post = posts[indexPath.row]
performSegueWithIdentifier(SEGUE_FOLLOW_TO_COMMENTVC, sender: post)
}
}
func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! FollowCell
cell.checkImg.hidden = true
}
When In "Select" mode, I can perform the selction of each cell and a check mark will be displayed on the cell. However, what I want to do is to have a cancel buttom to disable all the selected cell and removing the checkImg.
I have tried
func clearSelection() {
print("ClearSelection posts.count = \(posts.count)")
for item in 0...posts.count - 1 {
let indexP = NSIndexPath(forItem: item, inSection: 0)
followCollectionView.deselectItemAtIndexPath(indexP, animated: true)
let cell = followCollectionView.cellForItemAtIndexPath(indexP) as! FollowCell
cell.checkImg.hidden = true
}
}
The program crashes here giving me a fatal error: Unexpectedly found nil while unwrapping an optional error at
let cell = followCollectionView.cellForItemAtIndexPath(indexP) as! FollowCell
I dont know why it is having trouble unwrapping the cell to be my FollowCell which contains an instance of the checkImg. I already used it before in a similar situation in didSelectItemAtIndexPath and it seems to work?
Thanks,
Not all of the selected cells may be on screen at the point when you are clearing the selection status, so collectionView.cellForItemAtIndexPath(indexPath) may return nil. Since you have a force downcast you will get an exception in this case.
You need to modify your code to handle the potential nil condition but you can also make your code more efficient by using the indexPathsForSelectedItems property of UICollectionView
let selectedItems = followCollectionView.indexPathsForSelectedItems
for (indexPath in selectedItems) {
followCollectionView.deselectItemAtIndexPath(indexPath, animated:true)
if let cell = followCollectionView.cellForItemAtIndexPath(indexPath) as? FollowCell {
cell.checkImg.hidden = true
}
}
Using Extension in Swift 4
extension UICollectionView {
func deselectAllItems(animated: Bool) {
guard let selectedItems = indexPathsForSelectedItems else { return }
for indexPath in selectedItems { deselectItem(at: indexPath, animated: animated) }
}
}
To simplify further, you could just do
followCollectionView.allowsSelection = false
followCollectionView.allowsSelection = true
This will in fact correctly clear your followCollectionView.indexPathsForSelectedItems even though it feels very wrong.
collectionView.indexPathsForSelectedItems?
.forEach { collectionView.deselectItem(at: $0, animated: false) }
This answer may be useful in swift 4.2
let selectedItems = followCollectionView.indexPathsForSelectedItems
for (value in selectedItems) {
followCollectionView.deselectItemAtIndexPath(value, animated:true)
if let cell = followCollectionView.cellForItemAtIndexPath(value) as? FollowCell {
cell.checkImg.hidden = true
}
}
I got it solved easier by doing this:
tableView.selectRow(at: nil, animated: true, scrollPosition: UITableView.ScrollPosition.top)
I can't seem to figure out what I'm doing wrong.
I have a custom cell which has its own UITableViewCell and nib (3 labels).
If I select the cell I want to show/hide the Resolved Label. I can't seem to change the ResolvedLabel to Hidden or Show. The function gets called as I can see the Print Statement.
Code in didSelectRowAt
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! ResolvedIssueTableViewCell
if cell.resolvedLabel.isHidden == true {
tableView.deselectRow(at: indexPath, animated: true)
print("THIS HAS BEEN SELECTED RESOLVED")
cell.resolvedLabel.isHidden = false
self.resolvedIssueType.append(self.issues[indexPath.row].partType)
self.resolvedIssues.append(self.issues[indexPath.row].documentId)
print(self.resolvedIssues)
} else if cell.resolvedLabel.isHidden == false {
tableView.deselectRow(at: indexPath, animated: true)
print("THIS HAS BEEN SELECTED NOT RESOLVED")
cell.resolvedLabel.isHidden = true
if let index = self.resolvedIssues.firstIndex(of:self.issues[indexPath.row].documentId) {
self.resolvedIssues.remove(at: index)
self.resolvedIssueType.remove(at: index)
}
print(self.resolvedIssues)
print(self.resolvedIssueType)
}
}
You should call tableView.reloadData() in the end of didSelectRowAt function.
I've done some refactor your code
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
guard let cell = tableView.cellForRow(at: indexPath) as? ResolvedIssueTableViewCell else { return }
if cell.resolvedLabel.isHidden {
print("THIS HAS BEEN SELECTED RESOLVED")
cell.resolvedLabel.isHidden = false
self.resolvedIssueType.append(self.issues[indexPath.row].partType)
self.resolvedIssues.append(self.issues[indexPath.row].documentId)
print(self.resolvedIssues)
} else {
print("THIS HAS BEEN SELECTED NOT RESOLVED")
cell.resolvedLabel.isHidden = true
if let index = self.resolvedIssues.firstIndex(of:self.issues[indexPath.row].documentId) {
self.resolvedIssues.remove(at: index)
self.resolvedIssueType.remove(at: index)
}
print(self.resolvedIssues)
print(self.resolvedIssueType)
}
tableView.reloadData() // Here is the magic :)
}
You can learn more from Nicholas Swift's Medium Article
Honestly, it doesn't matter why this doesn't work because the whole approach should be different.
Instead of directly trying to manipulate the cell, something that can be problematic for a lot of reasons (the main one being cell reusability), change the model (as you already do) and refresh the tableView - or better yet refresh just the affected cell.
So your code would be something like this (please note that I have no way to test this, so it's from the top of my head, but the genera idea is valid):
1. Make the change in the model
2. Reflect that change in your view (the cell)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
guard let cell = tableView.cellForRow(at: indexPath) as? ResolvedIssueTableViewCell else {
print("Wrong type of cell. Bailing out...")
return
}
let isResolved = ...// Probably by looking into something like `self.resolvedIssues.firstIndex(of:self.issues[indexPath.row].documentId)` judging from your code
if isResolved {
print("THIS HAS BEEN SELECTED RESOLVED")
self.resolvedIssueType.append(self.issues[indexPath.row].partType)
self.resolvedIssues.append(self.issues[indexPath.row].documentId)
print(self.resolvedIssues)
} else {
print("THIS HAS BEEN SELECTED NOT RESOLVED")
if let index = self.resolvedIssues.firstIndex(of:self.issues[indexPath.row].documentId) {
self.resolvedIssues.remove(at: index)
self.resolvedIssueType.remove(at: index)
}
print(self.resolvedIssues)
print(self.resolvedIssueType)
}
tableView.reloadRows(at: [indexPath], with: .automatic)
}
then make the visual state of your cells reflect your model:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ...
cell.resolvedLabel.isHidden = <whatever_makes_sense>
...
}
After select row. call tableview.reloaddata() function.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! ResolvedIssueTableViewCell
if cell.resolvedLabel.isHidden == true {
tableView.deselectRow(at: indexPath, animated: true)
print("THIS HAS BEEN SELECTED RESOLVED")
cell.resolvedLabel.isHidden = false
self.resolvedIssueType.append(self.issues[indexPath.row].partType)
self.resolvedIssues.append(self.issues[indexPath.row].documentId)
print(self.resolvedIssues)
} else if cell.resolvedLabel.isHidden == false {
tableView.deselectRow(at: indexPath, animated: true)
print("THIS HAS BEEN SELECTED NOT RESOLVED")
cell.resolvedLabel.isHidden = true
if let index = self.resolvedIssues.firstIndex(of:self.issues[indexPath.row].documentId) {
self.resolvedIssues.remove(at: index)
self.resolvedIssueType.remove(at: index)
}
print(self.resolvedIssues)
print(self.resolvedIssueType)
}
tableView.reloadData()
}
As always, keep all information about one row in the data model rather than using separate arrays. In didSelectRowAt modify the model and reload the row. This is the most reliable and efficient way.
In the data model add a property
var isResolved = false
in cellForRow set the hidden property of the text field depending on isResolved.
let issue = self.issues[indexPath.row]
cell.resolvedLabel.isHidden = issue.isResolved
Replace didSelectRowAt with (yes, the 3 lines are sufficient)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
self.issues[indexPath.row].isResolved.toggle()
tableView.reloadRows(at: [indexPath], with: .none)
}
and delete the two extra arrays.
To get all resolved items just filter the array
let resolvedIssues = self.issues.filter{ $0.isResolved }
I have been trying to make an application where when the view loads, the focus automatically goes to first cell of UITableview.
I have tried using the UIFocusGuide and then in prefferedFocusView() method return the tableView but that did not work.
I then wrote this code
var viewToFocus: UIView? = nil {
didSet {
if viewToFocus != nil {
print("called1 ")
self.setNeedsFocusUpdate();
self.updateFocusIfNeeded();
}
}
}
override weak var preferredFocusedView: UIView? {
if viewToFocus != nil {
print("called2 ")
return viewToFocus;
} else {
return super.preferredFocusedView;
}
}
In view did load
self.viewToFocus = myTableView
But that did not work as well.
I was able to get this by setting tableView.remembersLastFocusedIndexPath = true in viewDidLoad, then I implemented the delegate method for preferred focus:
func indexPathForPreferredFocusedView(in tableView: UITableView) -> IndexPath? {
return NSIndexPath(forItem: 0, inSection: 0)
}
When remembersLastFocusedIndexPath - If YES, when focusing on a table view the last focused index path is focused automatically. If the table view has never been focused, then the preferred focused index path is used.
Swift 5
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
myTableView.remembersLastFocusedIndexPath = true
}
and then in your delegate
func indexPathForPreferredFocusedView(in tableView: UITableView) -> IndexPath? {
return IndexPath(row: 0, section: 0)
}
Add the following code snippet to the implementation of the ViewController class with the tableview delegate invoked:
func tableView(tableView: UITableView, canFocusRowAtIndexPath indexPath: NSIndexPath) -> Bool {
if indexPath.row == 0 {
return true
}
return false
}
In tableView:WillDisplayCell method add below code
[cell setSelected:YES animated:NO];
for first cell
You can set the scroll position automatically to go to the top cell.
let topCell:NSIndexPath = NSIndexPath(forRow: 0, inSection: 0)
self.tableView.scrollToRowAtIndexPath(topCell, atScrollPosition: UITableViewScrollPosition.None, animated: true)
I am not sure if this is the same behaviour as preferredFocusedViewthough.
extension UITableView {
func scrollToTop(section: Int) {
let rows = self.numberOfRows(inSection: section)
if rows > 0 {
DispatchQueue.main.async {
let indexPath = IndexPath(row: rows - 1, section: section)
self.scrollToRow(at: indexPath, at: .top, animated: true)
}
}
}
}
if there is only one section replace section with 0
I did implement search page in my app. I put PFObjects in postsArray, when user searching something, searched object goes in filteredArray. that is what I tried to do.
tableView shows result, but when I tap on it, it show postArray indexPath which is indexPath before I search it. For example, original(postsArray) first item is red, and I searched yello, when I tap yellow tableView Cell, it shows red post.
here is my code for this.
override func viewDidLoad() {
super.viewDidLoad()
self.resultSearchController = UISearchController(searchResultsController: nil)
self.resultSearchController.searchResultsUpdater = self
self.resultSearchController.dimsBackgroundDuringPresentation = false
self.resultSearchController.searchBar.sizeToFit()
self.myTable.tableHeaderView = self.resultSearchController.searchBar
self.myTable.reloadData()
self.bringAllDatafromParse()
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
self.filterdArray.removeAllObjects()
let normalizedSearchText =
searchController.searchBar.text!.lowercaseString
for posts in self.postsArray {
var title = ""
var tag = ""
if let titleText = posts["titleText"] as? String {
title = titleText
}
if let tagText = posts["tagText"] as? String {
tag = tagText
}
let results = "\(title) \(tag)"
if results.lowercaseString.rangeOfString(normalizedSearchText) != nil {
self.filterdArray.addObject(posts)
}
}
self.myTable.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if self.resultSearchController.active{
return self.filterdArray.count
}else
{
return self.postsArray.count
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("myCell", forIndexPath: indexPath) as! SearchTVCE
//cell.textLabel!.text = searchResults[indexPath.row]
var postObjects : PFObject!
if self.resultSearchController.active{
postObjects = self.filterdArray.objectAtIndex(indexPath.row) as! PFObject
}else {
postObjects = self.postsArray.objectAtIndex(indexPath.row) as! PFObject
}
//솔드
cell.soldLabel.hidden = true
if (postObjects.objectForKey("sold") as! Bool) == true {
cell.soldLabel.hidden = false
}
// 제목
cell.titleLabel.text = (postObjects.objectForKey("titleText") as! String)
+ " : " + (postObjects.objectForKey("tagText") as! String)
return cell
}
My question is how can I get right indexPath after I get result from search bar.
And after I search and tap on it, it goes another view. but the searchbar doesn't disappear unless I tap on cancel bar button. How do I fix this?
Your code looks good.
However, you are not showing your didSelectRowAtIndexPath function which might give clue for your problem.
My guess is - you are getting index path correct as you are always linking self.filterdArray on tableview. As I understand, everything on UI looks good but only when you tap on first cell, new view controller is loaded with the data that belongs to first cell of non-filtered array. I would advise you to put a check on your didSelectRowAtIndexPath and see if data is being fetched correctly. It should look something like this:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if (self.resultSearchController.active) {
myData = (self.filterdArray[indexPath.row])!
} else {
myData = (self.postsArray[indexPath.row])!
}
self.performSegueWithIdentifier("MySegue", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "MySegue") {
let myViewController = segue.destinationViewController as MyViewController
myViewController.data = self.myData
}
}
And to your other question:
searchbar doesn't disappear unless I tap on cancel bar button. How do
I fix this?
Just call : searchController.active = false
I'm loading a tableView from a plist file. This works with no problems. I just simply want to "tick" the selected rows. At the moment, with my code it didn't work as desired. At the moment, it looks as below:
tap row1 (it will tick row 1 = good)
tap row1 again (nothing happens = bad. I expect here the row to be unticked)
While tapping again on row 1, it unticks then. After the second tap on it.
when I tap row0 at the initial load of the tableview it never ticks me the row
my code:
class portals: UITableViewController {
var lastSelectedIndexPath = NSIndexPath(forRow: -1, inSection: 0)
...
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("myCell", forIndexPath: indexPath) as! UITableViewCell
// Configure the cell...
cell.textLabel!.text = portals[indexPath.row]
return cell
}
// Check which portal is selected
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var whichPortalIsSelected: String = ""
// Get Cell Label
let indexPath = tableView.indexPathForSelectedRow();
// Tick the selected row
if indexPath!.row != lastSelectedIndexPath?.row {
let newCell = tableView.cellForRowAtIndexPath(indexPath!)
newCell?.accessoryType = .Checkmark
lastSelectedIndexPath = indexPath
whichPortalIsSelected = newCell!.textLabel!.text!
println("You selected cell #\(lastSelectedIndexPath.row)!") //PPP
println("You selected portal #\(whichPortalIsSelected)!") //PPP
// Un-Tick unselected row
} else {
let newCell = tableView.cellForRowAtIndexPath(indexPath!)
newCell?.accessoryType = .None
whichPortalIsSelected = newCell!.textLabel!.text!
println("You unselected cell #\(indexPath!.row)!") //PPP
println("You unselected portal #\(whichPortalIsSelected)!") //PPP
}
}
}
Swift 4
First, make your tableView support multiple selection :
self.tableView.allowsMultipleSelection = true
self.tableView.allowsMultipleSelectionDuringEditing = true
Then simply subclass UITableViewCell like this :
class CheckableTableViewCell: UITableViewCell {
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
self.accessoryType = selected ? .checkmark : .none
}
}
Finally, use it in your cellForRowAt indexPath as such :
let cell = tableView.dequeueReusableCell(withIdentifier: "cell",
for: indexPath) as? CheckableTableViewCell
If you have to, don't forget to subclass your prototype cell in your xib/storyboard :
First of all, go to your Storyboard and select you tableview and in the Attributes Inspector, set Selection to Multiple Selection.
Attributes Inspector with multiple selection
Then, override the setSelected(_ selected: Bool, animated: Bool) function in the subclass of UITableViewCell.
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
accessoryType = selected ? UITableViewCellAccessoryType.checkmark : UITableViewCellAccessoryType.none
}
This enable untick.
class TableViewController: UITableViewController
{
var lastSelectedIndexPath = NSIndexPath(forRow: -1, inSection: 0)
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("myCell", forIndexPath: indexPath)
// Configure the cell...
cell.textLabel!.text = "row: \(indexPath.row)"
if cell.selected
{
cell.selected = false
if cell.accessoryType == UITableViewCellAccessoryType.None
{
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
}
else
{
cell.accessoryType = UITableViewCellAccessoryType.None
}
}
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
let cell = tableView.cellForRowAtIndexPath(indexPath)
if cell!.selected
{
cell!.selected = false
if cell!.accessoryType == UITableViewCellAccessoryType.None
{
cell!.accessoryType = UITableViewCellAccessoryType.Checkmark
}
else
{
cell!.accessoryType = UITableViewCellAccessoryType.None
}
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 100
}
}
you have to make a costume class to get the selected state of the cell where you must override a func called
setSelected(_ selected: Bool, animated: Bool)
or the tick will be displayed randomly as you scroll ...
here is an example of what i did:
1- created a class for the cell
2- added an outlet for an image to display the tick (you can escape this if you don't want a costume tick image)
3- overrided the function and used the param selected :D
here is my class:
import UIKit
class AddLocationCell: UITableViewCell {
#IBOutlet weak var check: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected{
check.image = UIImage(named:"check_active")
}else{
check.image = UIImage(named:"check_normal")
}
// Configure the view for the selected state
}
}
There are many solutions to this problem, here's one I came up with. I am using the built in cell "selected" property so the tableview saves it for us. Just make sure in your storyboard or when you setup the tableview in code you use multiple selection.
import UIKit
class TableViewController: UITableViewController
{
var lastSelectedIndexPath = NSIndexPath(forRow: -1, inSection: 0)
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("myCell", forIndexPath: indexPath) as! UITableViewCell
// Configure the cell...
cell.textLabel!.text = "row: \(indexPath.row)"
if cell.selected
{
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
}
else
{
cell.accessoryType = UITableViewCellAccessoryType.None
}
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
let cell = tableView.cellForRowAtIndexPath(indexPath)
if cell!.selected == true
{
cell!.accessoryType = UITableViewCellAccessoryType.Checkmark
}
else
{
cell!.accessoryType = UITableViewCellAccessoryType.None
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 100
}
}
I made a sample project here: https://github.com/brcimo/SwiftTableViewMultipleSelection