Swift - How to deselect all selected cells [duplicate] - swift

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)

Related

How can I deselect all rows in an UITableView?

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
}
}
}

Swift 5 CollectionView get indexPath by longPress cell

I am looking way to get indexPath or data when i do longPress on cell. Basically i can to delete album from collectionView, to do that i need to get id.
My cellForItem
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AlbumCollectionViewCell", for: indexPath) as! AlbumCollectionViewCell
cell.data = albumsDataOrigin[indexPath.row]
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGetstureDetected))
cell.addGestureRecognizer(longPressGesture)
return cell
}
longPressGetstureDetected
#objc func longPressGetstureDetected(){
self.delegateAlbumView?.longPressGetstureDetected()
}
delete func
func longPressGetstureDetected() {
showAlertWith(question: "You wanna to delete this album?", success: {
self.deleteAlbum() //Here i need to pass ID
}, failed: {
print("Delete cenceled")
})
}
For people who looking complete answer
#objc func longPress(_ longPressGestureRecognizer: UILongPressGestureRecognizer) {
if longPressGestureRecognizer.state == UIGestureRecognizer.State.began {
let touchPoint = longPressGestureRecognizer.location(in: collectionView)
if let index = collectionView.indexPathForItem(at: touchPoint) {
self.delegateAlbumView?.longPressGetstureDetected(id: albumsDataOrigin[index.row].id ?? 0)
}
}
}
Start by getting the coordinates of the press using gesture.location(in:) Ref: https://developer.apple.com/documentation/uikit/uigesturerecognizer/1624219-location
Then use indexPathForItem(at:) to retrieve the IndexPath of the cell touched. Ref: https://developer.apple.com/documentation/uikit/uicollectionview/1618030-indexpathforitem
Based on this you probably do not need a different gesture recognizer for each cell, you can probably register it with the collection view once.
Solution provided by George Heints based on the above:
#objc func longPress(_ longPressGestureRecognizer: UILongPressGestureRecognizer) {
if longPressGestureRecognizer.state == UIGestureRecognizer.State.began {
let touchPoint = longPressGestureRecognizer.location(in: collectionView)
if let index = collectionView.indexPathForItem(at: touchPoint) {
self.delegateAlbumView?.longPressGetstureDetected(id: albumsDataOrigin[index.row].id ?? 0)
}
}
}
I would recommend to use the State.recognized instead of State.began, your mileage may vary!
import UIKit
extension UIResponder {
func next<T: UIResponder>(_ type: T.Type) -> T? {
return next as? T ?? next?.next(type)
}
}
extension UICollectionViewCell {
var collectionView: UICollectionView? {
return next(UICollectionView.self)
}
var indexPath: IndexPath? {
return collectionView?.indexPath(for: self)
}
}
By the help with this extension you can know the indexPath of collection view from collection view cell file. And you can simply find the id of the photo by the indexPath from data array and delete it.

How to Use Table View Cell Outside Table View Function

i need to change my image in cell using view will appear. But i can't use my cell in view will appear here what i've done
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
let cell: HomeCellTableViewCell = self.tableCity.dequeueReusableCell(withIdentifier: "Cell") as! HomeCellTableViewCell
if session == nil {
print("first")
cell.iconForDownload.setImage(UIImage(named: "ic_download"), for: .normal)
} else {
print("second")
cell.iconForDownload.setImage(UIImage(named: "ic_next_green"), for: .normal)
}
}
it print "first" but the image still didn't change
in my cellForRowAt :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: HomeCellTableViewCell = self.tableCity.dequeueReusableCell(withIdentifier: "Cell") as! HomeCellTableViewCell
let city = listData[indexPath.row]
cell.labelNameCity.text = city.region
cell.labelNameCityJpn.text = city.regionJpn
let stringImage = config.BASE_URL+city.imgArea
let url = URL(string: stringImage.replacingOccurrences(of: " ", with: "%20"))
urlDownload = config.BASE_URL+kota.urlDownload
urlDownloadFinal = URL(string: urlDownload.replacingOccurrences(of: " ", with: "%20"))
if session == nil {
cell.imageCity.kf.setImage(with: url)
cell.iconForDownload.setImage(UIImage(named: "ic_download"), for: .normal)
} else {
cell.imageCity.kf.setImage(with: url)
cell.iconForDownload.setImage(UIImage(named: "ic_next_green"), for: .normal)
}
return cell
}
You need to use cellForRow(at:) to get the cell it will return optional UITableViewCell so use if let or guard let to wrapped the optional.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
let indexPath = IndexPath(row: 0, section: 0) //Set your row and section
if let cell = tableView.cellForRow(at: indexPath) as? HomeCellTableViewCell {
//access cell
}
}
Note: The batter approach is to set your datasource array and simply reload the affected table rows using reloadRows(at:with:).
let indexPath = IndexPath(row: 0, section: 0) //Set your row and section
self.tableView.reloadRows(at: [indexPath], with: .automatic)

Swift: Bool if true, doesn't display image

I'm a beginner so please be patient explaining, Thanks.
So, basically I have a bool column in parse and I want to display an image if it's false and don't display anything if its true.
here are my codes:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let myCell = tableView.dequeueReusableCellWithIdentifier("todayCell", forIndexPath: indexPath) as! reqTodaysCell
let cellDataParse: PFObject = self.dataparse.objectAtIndex(indexPath.row) as! PFObject
var newReadQuery = PFQuery(className: "request")
newReadQuery.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if let objects = objects {
for object in objects {
if object["reqRead"] as! Bool == true {
myCell.isRead.image = nil //here is where I say pic to be nil but what happens is that if the first one is true then it will remove the pic for all of them.
// and if its not true it should display the pic
} else {
myCell.isRead.image = UIImage(named: "newReq")
print("user not read")
}
}
}
})
If I don't explain properly please let me know and I will try my best to explain again.
This sounds like an ideal use case for a ternary operator. As per my example below, you use the ? : syntax following a Bool, if the bool is true it will return the first case and if it's false it will return the second case.
newReadQuery.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if let objects = objects {
for object in objects {
let reqRead = object["reqRead"] as! Bool
cell.image.image = reqRead ? nil : UIImage(named: "newReq")
}
}
})
UPDATE
The above may not have worked as the Parse call might not be completed before the cells are loaded.
Create a global variable (outside of any function):
var reqRead = [Bool]()
In ViewDidLoad you can create an array of the bools.
var newReadQuery = PFQuery(className: "request")
newReadQuery.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if let objects = objects {
for object in objects {
reqRead.append(object["reqRead"] as! Bool)
}
tableView.reloadData()
}
})
Then in your CellForRow:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let myCell = tableView.dequeueReusableCellWithIdentifier("todayCell", forIndexPath: indexPath) as! reqTodaysCell
let cellDataParse: PFObject = self.dataparse.objectAtIndex(indexPath.row) as! PFObject
cell.image.image = reqRead[indexPath.row] ? nil : UIImage(named: "newReq")
return cell
}
There's a possibility that it will try to populate the cells before the array is loaded but let me know if this works for you.
if object["reqRead"] as! Bool == false {
myCell.isRead.image = nil
myCell.isRead.hidden = false
} else {
myCell.isRead.hidden = true
}

when I search through search controller, indexPath doesn't change with original array

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