cell selection of UICollectionView does not work in swift - ios

I have written sample code here - when i tap button "Select" it should select all the cells of UICollectionView. But is does not work and no error is reported. Below is code
// collectionView is name of UICollectionView
let collectonViewCount = collectionView.numberOfItemsInSection(0);
for i in 0...collectonViewCount {
let indexPath = NSIndexPath(forRow: i, inSection: 0)
collectionView.selectItemAtIndexPath(indexPath, animated: true, scrollPosition: .None)
}
I'm not sure what could be wrong in the above code.

cybergeeeek,
you can try
for i in 0...collectonViewCount {
let indexPath = NSIndexPath(forRow: i, inSection: 0)
collectionView.delegate?.collectionView!(collectionView, didSelectItemAtIndexPath: indexPath)
}
This answer ofcourse assumes that you have set the collectionView delegate and have implemented didSelectItemAtIndexPath this will select the collectionView cell and will trigger didSelectItemAtIndexPath :)

Related

Iterating over UITableViewCell for AVSpeechSynthesizer

I've seen everywhere that it's bad practice to iterate through a tableView but I'm afraid I have no other alternative. I want to have the AVSpeechSynthesizer read the content of the labels that are inside my custom UITableViewCells.
Of course, this could be done directly using the source array of the tableView but the problem is that I want to have the synthesiser read the labels and at the same time highlight the words that are being spoken.
I have done some tests but the problem is that as the cells are being reused by the table view, I end up with empty cells...
What is the right way of ensuring my cells are not empty ?
I've tried scrolling the tableView to ensure the cells aren't empty but it doesn't work either.
Here is my code so far ...
func scrollToRow(rowToGo: Int) {
self.collectionView.scrollToItem(at: self.indexPath as IndexPath, at: .centeredVertically, animated: true)
let colorCell = self.collectionView.dequeueReusableCell(withReuseIdentifier: "ColorCell", for:indexPath as IndexPath ) as! ColorCell
let myString = colorCell.colorLabel.text!
print(myString)
self.speakString(sender: self, str:(myString))
}
#IBAction func playPauseButton(_ sender: UIButton) {
if !self.speechSynthesizer.isSpeaking{
let btnImage = UIImage(named: "pausedButton.png")
self.playPauseButton.setImage(btnImage , for: UIControlState.normal)
self.isPaused = false
// var indexPath:Double = 0
self.indexPath = NSIndexPath(row: 0, section: 0)
for i in 0...36 {
print(i)
self.indexPath = NSIndexPath(row: i, section:0)
self.scrollToRow(rowToGo:i)
// self.indexPath = NSIndexPath(index:i)
// self.collectionView.selectItem(at: indexPath as IndexPath, animated: true, scrollPosition: .centeredVertically)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt
indexPath: IndexPath) -> UICollectionViewCell {
let myCell = collectionView.dequeueReusableCell(withReuseIdentifier:
"ColorCell", for: indexPath) as! ColorCell
myCell.colorLabel.text = self.wordList[indexPath.row]
myCell.colorImage.image = self.images[indexPath.row]
return myCell }
My collectionView is displayed correctly and all my labels inside the UICollectionViewCells contain text. However, when I press the play button, the first time, nothing gets spoken. If I scroll back and press again, only 3 or 4 cells actually exist and get their label.text printed and spoken while all the other indexes are empty.
Any suggestion would be welcome

How to Translate a Delegated Function to a IBAction Function

I have a delegate function that changes a UITableViewCell's section location on tap:
//On cell tap, expand
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.section == 0
{
let data = firstDataSource[indexPath.row]
tableView.beginUpdates()
secondDataSource.append(data)
firstDataSource.removeAtIndex(indexPath.row)
let newIndexPath = NSIndexPath(forRow: find(secondDataSource, data)!, inSection: 1)
tableView.moveRowAtIndexPath(indexPath, toIndexPath: newIndexPath)
tableView.endUpdates()
} else if indexPath.section == 1 {
let data = secondDataSource[indexPath.row]
tableView.beginUpdates()
firstDataSource.append(data)
secondDataSource.removeAtIndex(indexPath.row)
let newIndexPath = NSIndexPath(forRow: find(firstDataSource, data)!, inSection: 0)
tableView.moveRowAtIndexPath(indexPath, toIndexPath: newIndexPath)
tableView.endUpdates()
}
}
I would like for this action to occur when I fire an IBAction from a button tap, but I'm not sure how I can access the indexPath argument as is given in the delegate function. Here is my current code for the IBAction button:
#IBAction func checkOffTask(sender: UIButton) {
var checkedOff = false
let indexOfCell = sender.tag
sender.setImage(UIImage(named: "checkbox-checked"), forState: UIControlState.Normal)
self.tableView.beginUpdates()
self.tableView.reloadRowsAtIndexPaths([NSIndexPath(forItem: indexOfCell, inSection: 1)], withRowAnimation: UITableViewRowAnimation.Automatic)
self.tableView.endUpdates()
}
Any idea on how to get the delegate function to work for the IBAction function?
Okay, so your action is coming from a button, that button is within a table view cell, the question is: what's the indexPath of that table view cell.
The easiest way is:
get the location of the button within the table view;
ask for the index path at that position.
E.g.
#IBAction func checkOffTask(sender: UIButton!) {
let originInTableView = self.tableView.convertPoint(CGPointZero, fromView: sender)
let indexPath = self.tableView.indexPathForRowAtPoint(originInTableView)
}
Okay, this is a workaround I have used before.
Doesn't look pretty but does the job:
UIButton *likeButton = (UIButton *)sender;
UITableViewCell *cell = (UITableViewCell *)likeButton.superview.superview;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
Just find the correct superview for the button which is of UICollectionView Class type.
P.S. - My Swift is not the best. Hoping you can convert this.

UITableView indexPath of last row

I am attempting to make the last row in a UITableView visible, after it has been added. Right now, when I add a row and call reloadData, the table goes to the top.
I figure if I get the indexPath for the last row, that I can select that row and it should appear in the list. I am unsure of how to get that value, or even if I am approaching this correctly.
How do I get an indexPath for a specific row?
Please note that, you don't need to call the reloadData to make the last row visible. You can make use of scrollToRowAtIndexPath method.
You can use the below code to achieve your goal.
// First figure out how many sections there are
let lastSectionIndex = self.tblTableView!.numberOfSections() - 1
// Then grab the number of rows in the last section
let lastRowIndex = self.tblTableView!.numberOfRowsInSection(lastSectionIndex) - 1
// Now just construct the index path
let pathToLastRow = NSIndexPath(forRow: lastRowIndex, inSection: lastSectionIndex)
// Make the last row visible
self.tblTableView?.scrollToRowAtIndexPath(pathToLastRow, atScrollPosition: UITableViewScrollPosition.None, animated: true)
Swift 4.0:
tableView.scrollToRow(at: indexPath, at: UITableViewScrollPosition.none, animated: true)
You can use scrollToRowAtIndexPath with extension:
In Swift 3:
extension UITableView {
func scrollToLastCell(animated : Bool) {
let lastSectionIndex = self.numberOfSections - 1 // last section
let lastRowIndex = self.numberOfRows(inSection: lastSectionIndex) - 1 // last row
self.scrollToRow(at: IndexPath(row: lastRowIndex, section: lastSectionIndex), at: .Bottom, animated: animated)
}
}
Shamsudheen TK's answer will crash
if there is no rows/sections in tableview.
The following solution to avoid crash at run time
extension UITableView {
func scrollToBottom() {
let lastSectionIndex = self.numberOfSections - 1
if lastSectionIndex < 0 { //if invalid section
return
}
let lastRowIndex = self.numberOfRows(inSection: lastSectionIndex) - 1
if lastRowIndex < 0 { //if invalid row
return
}
let pathToLastRow = IndexPath(row: lastRowIndex, section: lastSectionIndex)
self.scrollToRow(at: pathToLastRow, at: .bottom, animated: true)
}
}
Note: If you are trying to scroll to bottom in block/clousure then you need to call this on main thread.
DispatchQueue.main.async {
self.tableView.scrollToBottom()
}
Hope this will helps other
As suggested by others get indexPath for perticular sections like section 0.
After that call...add this methos in cellFOrROwAtIndex
[tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; ..to scroll to specific indexPath in TableView.
Note:-But it still need scrolling of tableview in Downward direction.
You shouldn't be using -reloadData for this use case. What you're looking for is -insertRowsAtIndexPaths:withRowAnimation:.
Feel free to ask if you want some usage examples or a more detailed explanation as to why using -reloadData send you to the top of the UITableView.
Not mandatorily required to get the indexpath of last row.
You can set the CGPoint of UITableview to show a last row you added.
I always use this code in my chat application to show a last added message.
//Declaration
#IBOutlet weak var tableview: UITableView!
//Add this executable code after you add this message.
var tblframe: CGRect = tableview.frame
tblframe.size.height = self.view.frame.origin.y
tableview.frame = tblframe
var bottomoffset: CGPoint = CGPointMake(0, tableview.contentSize.height - tableview.bounds.size.height)
if bottomoffset.y > 0 {
tableview.contentOffset = bottomoffset;
}
I hope it will work for you.
Thanks.
Swift 5.0 +
extension UITableView {
func isLastVisibleCell(at indexPath: IndexPath) -> Bool {
guard let lastIndexPath = indexPathsForVisibleRows?.last else {
return false
}
return lastIndexPath == indexPath
}
}

How to programmatically select a row in UITableView in Swift

I need to select a row in a UITableView programmatically using Swift 1.2.
This is the simple code:
var index = NSIndexPath(forRow: 0, inSection: 0)
self.tableView.selectRowAtIndexPath(index, animated: true, scrollPosition: UITableViewScrollPosition.Middle)
self.tableView(self.tableView, didSelectRowAtIndexPath: index)
The above gives me the following error:
Cannot invoke 'selectRowAtIndexPath' with an argument list of type '(NSIndexPath!, animated: Bool, scrollPosition: UITableViewScrollPosition)'
What is wrong with my Swift 1.2 code?
My UItableView has been created in IB in the UIViewController that I am trying to call the code above.
When I put the code in a UITableViewController the compiler does not give any errors.
Do I need to embed a UITableViewController in a container or is there another way?
Swift 3 to Swift 5 Solution
Selecting a Row
let indexPath = IndexPath(row: 0, section: 0)
myTableView.selectRow(at: indexPath, animated: true, scrollPosition: .bottom)
myTableView.delegate?.tableView!(myTableView, didSelectRowAt: indexPath)
DeSelecting a Row
let deselectIndexPath = IndexPath(row: 7, section: 0)
myTableView.deselectRow(at: deselectIndexPath, animated: true)
myTableView.delegate?.tableView!(myTableView, didDeselectRowAt: indexPath)
The statement
self.tableView.selectRowAtIndexPath(index, animated: true, scrollPosition: UITableViewScrollPosition.Middle)
assumes that tableView is a property of the view controller, connected
to a table view in the Storyboard. A UITableViewController, for example, already has this
property.
In your case, the view controller is a not a table view controller
but a subclass of a UIViewController. It also has an outlet that is
connected to the table view, but it is not called
tableView but menuTable. Then of course you have to call
self.menuTable.selectRowAtIndexPath(index, animated: true, scrollPosition: UITableViewScrollPosition.Middle)
to select a row of that table view.
The strange error messages are caused by the fact that
self.tableView can also be understood by the compiler as a
"curried function" (compare http://oleb.net/blog/2014/07/swift-instance-methods-curried-functions/).
Use below code,after loading your table view with data:
let rowToSelect:NSIndexPath = NSIndexPath(forRow: 0, inSection: 0); //slecting 0th row with 0th section
self.tableView.selectRowAtIndexPath(rowToSelect, animated: true, scrollPosition: UITableViewScrollPosition.None);
now,you have to manually call didSelectRowAtIndexPath: delegate method using below code:
self.tableView(self.tableView, didSelectRowAtIndexPath: rowToSelect); //Manually trigger the row to select
Thanks.
Swift 3.x
if you want to do it at the 'cell-creation', you can do it like this
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = TableViewCell()
let item = items[indexPath.row]
cell.textLabel?.text = item.title
if (item.checked) {
tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
}
return cell
}
Using Swift 2.x, as described by Pankaj purohit answers the correct method is:
func tapRowAtIndex(index:Int) {
let rowToSelect:NSIndexPath = NSIndexPath(forRow: index, inSection: 0)
self.tableView.selectRowAtIndexPath(rowToSelect, animated: true, scrollPosition: UITableViewScrollPosition.None)
self.tableView(self.tableView, didSelectRowAtIndexPath: rowToSelect)
}
Keep in mind that if you call this method from an external class for example, you dont know when tableView has finished its loading, so what's the possibilities?, how to workaround this problem? :
Step one: create a class boolean var
var automatingTap: Bool = false
Step two: check when the table finish its loading and launch an "end operations" method:
func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath)
{
let lastRowIndex = tableView.numberOfRowsInSection(0)
if indexPath.row == lastRowIndex - 1 {
endOperations()
}
}
func endOperations()
{
print("finished loading")
if automatingTap {
tapRowAtIndex(0)
automatingTap = false
}
}
Step three: call my tableView class from another class
for example:
override func prepareForSegue(segue: UIStoryboardSegue?, sender: AnyObject?) {
if segue!.identifier == "DetailsTableView" {
let viewController:ViewController = segue!.destinationViewController as ViewController
viewController.automatingTap = true
}
}
Reusable function with validation of table size
Swift 4 and 5
This reusable function works and validate the size of table.
func selectRow(tableView: UITableView, position: Int) {
let sizeTable = tableView.numberOfRows(inSection: 0)
guard position >= 0 && position < sizeTable else { return }
let indexPath = IndexPath(row: position, section: 0)
tableView.selectRow(at: indexPath, animated: true, scrollPosition: .middle)
}
you can use it in this way
selectRow(tableView: myTableView, position: pos)
or you can implement this extension:
extension UITableView {
func selectRow(row: Int, section: Int = 0) {
let sizeTable = self.numberOfRows(inSection: section)
guard row >= 0 && row < sizeTable else { return }
let indexPath = IndexPath(row: row, section: section)
self.selectRow(at: indexPath, animated: true, scrollPosition: .middle)
}
}
and you can use it in this way:
mytableView.selectRow(row: pos, section: 0)
or
mytableView.selectRow(row: pos)
Swift 4.2:
Select one or more Rows
let ndx:IndexSet = [1]
// or: let ndx:IndexSet = [1, 2, 3]; // etc
tableview?.selectRowIndexes(ndx, byExtendingSelection: false);
Deselect a Single Row
tableview?.deselectRow(current)
Note that if you have (func tableViewSelectionDidChange(...)) implemented, that will be triggered by the above.
Also see Charlton Provatas' answer at https://stackoverflow.com/a/48696458/352920 for an extension to NSTableView, that provides a simple
tableview?.selectRow(at:)

UITableView didDeselectRowAtIndexPath delegate method not called after programmatically selecting row

I have a custom class (superclass UITableViewController).
I have an add button that:
Begins table updates.
Inserts row in table.
Ends table updates.
Calls selectRowAtIndexPath.
Calls didSelectRowAtIndexPath.
Unfortunately, didDeselectRowAtIndexPath is not being called after programatically selecting a row.
Note: Using Swift (not Objective-C).
#IBAction func addButton(sender: UIBarButtonItem) {
let indexPathZero: NSIndexPath = NSIndexPath(forRow: 0, inSection: 0)
todoController.addNewTodoItem(nameOfItem: "test5")
self.tableView.beginUpdates()
self.tableView.insertRowsAtIndexPaths([indexPathZero], withRowAnimation: UITableViewRowAnimation.Top)
self.tableView.contentOffset.y = 64
self.tableView.endUpdates()
// Ask user for input
editCell(indexPathZero)
}
func editCell(indexPath: NSIndexPath) {
self.tableView.selectRowAtIndexPath(indexPath, animated: true, scrollPosition: UITableViewScrollPosition.None)
self.tableView(self.tableView, didSelectRowAtIndexPath: indexPath)
}
Solution
let indexPathZero: NSIndexPath = NSIndexPath(forRow: 0, inSection: 0)
todoController.addNewTodoItem(nameOfItem: "test5")
let currSelectedIndexPath = self.tableView.indexPathForSelectedRow()
if currSelectedIndexPath != nil {
self.tableView.deselectRowAtIndexPath(currSelectedIndexPath!, animated: true)
self.tableView(self.tableView, didDeselectRowAtIndexPath: currSelectedIndexPath!)
}
self.tableView.beginUpdates()
self.tableView.insertRowsAtIndexPaths([indexPathZero], withRowAnimation: UITableViewRowAnimation.Top)
self.tableView.contentOffset.y = 64
self.tableView.endUpdates()
// Ask user for input
editCell(indexPathZero)
See the apple docs. When you call selectRowAtIndexPath, it will not call didSelectRowAtIndexPath.
"Calling this method does not cause the delegate to receive a tableView:willSelectRowAtIndexPath: or tableView:didSelectRowAtIndexPath: message, nor does it send UITableViewSelectionDidChangeNotification notifications to observers."

Resources