Call UITableViewCell Method from Method in main VC? - ios

I'm trying to do the opposite of what most people on this topic are asking to do. Most people want a button within a table view cell to call a method in their VC / VC table. I already have a protocol doing that.
Problem / Question
What I am trying to add now is the opposite: I need a button press on my main ViewController (which houses my table) to call a method within my CusomTableViewCell class (note: the button pressed on the main VC is not in the table). I have the protocol class created and the function written, but I don't know how to set the CustomCellViewClass as the delegate. When I did the opposite, I inserted "cell.delegate = self" into the cellForRowAtIndexPath method. I've also used prepareForSegue to assign a delegate. But with no segue and now cell-creation-method, I'm lost!
Example of Desired Function
My end goal is that pressing a button that is in the main VC will change the title of a button within the cells. A simple example would be that I have one view with a single table, on button press the table contents switch between two arrays, cars and motorcycles. When the table is showing cars, the cell button titles should all read "Look inside" but when showing the motorcycle button it should read "Look closer".
Code
I've already written the function that I want the cell to execute:
func cellButton_Title_Switch (currentList: String) {
if vcState == "cars" {
cellButton.setTitle("Look inside", forState: UIControlState.Normal)
}
else {
cellButton.setTitle("Look closer", forState: UIControlState.Normal)
}
}
I created the protocol:
protocol delegateToChangeCellBut {
func cellButton_Title_Switch (currentList: String)
}
I have the self.delegate.cellButton_Title_Switch(currentList) within my VC button and the protocol added to my custom cell class declaration. But how do I do that last missing piece in the custom cell class, where I assign the class to the delegate?

My original problem was that my UITableView's cell has buttons and labels, some of which change to match the state of things outside the table, things handled by the mainViewController.
The custom cell is defined by a customCellviewController. All the custom cell buttons and labels have their IBOutlets connected to the customCellviewController. I couldn't figure out how to make an action/change outside the table (in the mainViewController) immediately cause the cell labels and buttons to change.
Note: Protocols tend to work they other way around (a cell action triggers a function in the mainVC). I couldn't figure out how to use a protocol to solve this. Luckily, the solution was much simpler than a protocol.
The Solution!
I wrote the "updateCell" method that would change the labels and buttons and that code now sits in the customCellviewController. Then I called/triggered the "updateCell" function from the mainViewController simply by adding the call into my cellForRowAtIndexPath function. So it looks something like this:
var stateOfPage = "Green"
//Creates the individual cells. If the above function returns 3, this runs 3 times
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//Setup variables
let cellIdentifier = "BasicCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! customCell
cell.updateCell(stateOfPage)
return cell
}
Now the above code/method runs when the table gets built. So to update the cells, have some button tap or other action reload the table data:
tableView.reloadData()
Good luck.

Related

using a Struct to change tableViewCell from TableViewController

In my app's chat feature, I use a UITableView to present chat history.
Cells are subclasses of UITableViewCell. The class receives a Chat object that contains all the info it needs to build the cell.
I have a Firebase listener that fires when the "last message" is changed. It changes a UILabel text to "new Message!" and then a protocol/delegate reloads the table view. Now I need to change the label back to "".
When the user clicks the cell, I use didSelectRowAt to segue to JSQMessagesViewController after preparing for segue. My idea is to use prepare for segue or even didSelectRowAt itself to change a Boolean variable in the UITableViewCell that corresponds to that chat Object. Navigating back to the tableViewController will trigger reloadData.
Each ChatObject has a unique ID that I can access. After firing the listener, I will set this Boolean to one value. After clicking the cell, I will set it to another. the state of the variable will tell the IBOutlet what text to show. In the UITableViewCell class, I use:
var chat: Chat! {
didSet {
self.updateUI()
}
}
}
So in self.updateUI() I make necessary changes, but can I make properties in the UITableViewCell mutable/visible from the UITableViewController?
How can I make properties in the UITableViewCell that are mutable from the UITableViewController
You don't. You make changes in your model and tell the table view to reload its data.
So in self.updateUI() I make necessary changes, but can I make properties in the UITableViewCell mutable from the UITableViewController?
I don't think you're trying to do what you say you are. In the case that you are trying to access the properties of your custom UITableViewCell subclass, you might want to use cellForRowAtIndexPath(). Then cast the result to whatever class name you have for your UITableViewCell subclass.
Example:
If I want to change the 5th row outside of our table view dataSource, I could do something like this.
let fifthIndexPath = IndexPath(row: 5, section: 0)
let thisCell = myTableView.cellForRowAtIndexPath(at indexPath: fifthIndexPath) as! MyCustomTableViewCell
thisCell.property = value
See:
https://developer.apple.com/documentation/uikit/uitableviewdatasource/1614861-tableview

Create IBAction for CollectionViewCell

I've got a collection view with multiple cells created programically. I want to update a variable to a different value when the user taps a specific cell. So eg: Cell 1 is tapped -> var test = "cell1" , cell2 is tapped var test = "cell2". Usually I'd just create an IBAction by dragging from the storyboard but I'm not sure how to do it in this case.
I'm using Swift 3.1
To add interactivity to UITableViews, UICollectionViews, and other kinds of views which display collections of data, you can't use Storyboard actions, as the content is generated dynamically during runtime, and the Storyboard can only work for static content.
Instead, what you need to do is set your UICollectionView's delegate property to an object that implements the UICollectionViewDelegate protocol. One of the methods defined as part of the protocol is the collectionView(_:didSelectItemAt:) method. This method will get called whenever the user selects (taps) a collection view cell with the IndexPath to that cell as an argument. You can update your variable in that method. Just remember to deselect the cell after handling the tap by using the deselectItem(at:) method on your UICollectionView.
There are UICollectionView delegate that you need to implement. It goes like this
did select item at index path will give index path of the cell that was selected. Using indexpath or any other property of the data source array you are using, you can modify the variable value.
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
if let cell = collectionView.cellForItemAtIndexPath(indexPath) {
//check your condition and modify the variable value depending on index path or any other property you are referring to.
}
}

Change variable based on Table View Cell

I need an event, that changes a variable based on which TableViewCell I click. But unlike an action connected to a button, there is no action indicator for table view cells at all. So my question is:
I want to make a TableView that contains items of an array. Based on which item I click, I want to change my variable so that the result on the next ViewController depends on which button you click.
So to make things easier, here is an example what I want the app to look like:
On the first TableViewController I have a list based on an array and on the second ViewController I have a label that shows text based on the variable.
I have a nameArray = ["Mum", "Brother", "Me"] and a weightArray = [140, 160, 120] and a variable weight = 0. The label on the second ViewController tells the var weight. So when you click on "Mum" in the TableView I want the next ViewController to say 140, when I click on "Brother" then 160 and so on...
Until here everything works just fine and I have no problems with anything but changing the var based on what I click.
Long story, short sense:
I want an Action for the TableViewCell that changes the var like in an Action connected to a Button, but there is no Action outlet for Cells at all.
Use this method. Use indexPath.row to find what row number you selected
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var cell : UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
switch cell.labelSubView.text as! String {
case "Mum":
self.weight = weightArray[0]
case "Brother"
self.weight = weightArray[1]
and so on..
..
default:
statements
}
}
Note A better alternative
I also considered a case where you have too many entries in nameArray and switch statement might not be good. In that case you can get the text inside the selected row by cell.labelSubView.text as! String
next you can check if the nameArray contains the cell text and get the index of the name that matches the cell text. Next you can get the required weight at the same index in weightArray. And then do self.weight = weightArray[requiredIndex]
Hope this helps.
Update : My experienced friend #Duncan mentioned down below that switch statement in this case is a bad coding practice . I am not going to delete it because it is a lesson for me and also my fellow programmers who are relatively new to programming. So i have put it in a yellow box, stating that it is not a good code
A better option for this would be :
As Duncan mentions, creating an array of dictionary is a good option
Second option is the option in my answer after my Note
You need to maintain array of dictionaries , those dictionaries have keys like "person", and "weight", then you can easily get weight value after selecting the cell by using table view delegate method UITableViewDelegate's tableView:didSelectRowAtIndexPath:
Create an instance variable in your view controller (a var at the top level after the class definition) for the selected cell.
class MyTableViewController: UIViewController
var selectedRow: Int
override func tableView(tableView: UITableView,
didSelectRowAtIndexPath indexPath: NSIndexPath)
{
selectedRow = indexPath.row
//invoke a segue if desired
performSegueWithIdentifier("someSegue");
}
override func prepareForSegue(segue: UIStoryboardSegue,
sender: AnyObject?)
{
if segue.identifier == "someSegue"
{
//Cast the destination view controller to the appropriate class
let destVC = DestVCClass(segue.destinationViewController)
destVC.selectedRow = selectedRow
}
}
As Andey says in his answer, it's probably better to create a single array of data objects (dictionaries, structs, or custom data objects). Then when the user taps a cell, instead of passing the index of the selected row to the next view controller, you could pass the whole data object to the destination view controller. Then the destination view controller could extract whatever data it needed. (Weight, in your example.)

Custom swipeable Table View Cell, Close other once new one open

I'm following tutorial from raywenderlich (How To Make A Swipeable Table View Cell With Actions) site, that shows you how to create custom cell with layers and delegate.
Now I got everything working correctly, buy I would like one of my cells to close if other cell open, how can I achieve this? or Like Messenger app, don't allow users to open another cell option unless they close the current one.
I can't wrap my head around this, I see few other people also ask the same question in comments, but no one reply.
Anyone with Objective-C knowledge, it's okay, I can translate it to swift myself.
The reason I'm using this tutorial, is because the Apple API doesn't allow custom button (using PaintCode) to be used as Action button.
I find a very simple solution for anyone else who trying to achieve the same method.
Create a Method - closeOtherCells:
Since we store all cells in NSMutableSet we can see which ones are available for closing.
func closeOtherCells(close close: Bool){
if close{
//Check For Available Cell
for cells in cellsCurrentEditing {
//Get Table Cells at indexPath
var cellToClose: CustomTableViewCell = self.tableView.cellForRowAtIndexPath(cells as! NSIndexPath) as! CustomTableViewCell
//Call Reset Method in our Custom Cell.
cellToClose.resetConstraintContstantsToZero(true, notifyDelegateDidClose: true)
}
}
}
Now Simply in your cellDidOpen: delegate method call closeOtherCells: with true parameter.
func cellDidOpen(cell: UITableViewCell) {
//Close Other Cells
closeOtherCells(close: true)
//Store Open Cell in NSMutableSet
let indexPath: NSIndexPath = self.tableView.indexPathForCell(cell)!
self.cellsCurrentEditing.addObject(indexPath)
}
I hope this help others. :)

TableViewCell button click firing in multiple sections of TableViewControl

Bit of a weird one i can't seem to understand so i have a tableview control in a viewcontroller and within the tableviewcell i have a button which is using a delegate.
So when the button is clicked it will change the tag number of the cell and also change the image of the button based on the tag of the cell in the tableview.
But the problem i'm having is that when you select a button in a row i.e. the first one it seems to be running the function to change the image for each item in a section almost. I.e choosing the 5th item will update every 5th button in a cell to the selected image which is weird. Since it's almost like the tableview is being split up to sections of 5.
Can anyone help prevent this behaviour so it only changes the icon for the button in the cell which has been pressed.
Below are the functions that i've used and a link to a video on dropbox showing the behaviour https://www.dropbox.com/s/a4aasti78872w6j/Help.mov?dl=0
Delegate
protocol StoryTableViewCellDelegate: class {
func didBookmarkItem(cell: StoryTableViewCell, sender: AnyObject)
}
Function for Delegate
#IBAction func bookmarkButtonDidTouch(sender: AnyObject) {
bookmarkButton.animation = "pop"
bookmarkButton.curve = "easeOut"
bookmarkButton.duration = 0.5
bookmarkButton.damping = 0.4
bookmarkButton.velocity = 0.4
bookmarkButton.animate()
// The delegate which will handle bookmarking items
delegate?.didBookmarkItem(self, sender: sender)
}
Function being used in tableview class
// MARK: StoryTableViewDelegate
func didBookmarkItem(cell: StoryTableViewCell, sender: AnyObject) {
// TODO: Implement bookmark functionality
switch cell.bookmarkButton.tag {
case 0:
cell.bookmarkButton.setImage(UIImage(named: "Bookmark-Selected"), forState: UIControlState.Normal)
cell.bookmarkButton.tag = 1
case 1:
cell.bookmarkButton.setImage(UIImage(named: "Bookmark-Not-Selected"), forState: UIControlState.Normal)
cell.bookmarkButton.tag = 0
default:
break
}
}
The reason is, you are probable creating a cell using :
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cellID")
...
like you should do. However dequeueReusableCellWithIdentifier re-uses a cell, so if you have changed anything in a cell (like adding buttons), those buttons will still be in that recycled cell.
So you must set the book mark status in your cellForRowAtIndexPath for each cell.
I once had a similar problem when adding buttons to a cell, and when scrolling the cells automatically had extra buttons. So the first thing I did when re-using a cell was remove all buttons.
Edit sample code
in your
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)
you have to add code for setting the button to the desired state, it will be something similar to the code you're using in didBookMarkItem :
switch myProperty {
case 0:
cell.bookmarkButton.setImage(UIImage(named: "Bookmark-Selected"), forState: UIControlState.Normal)
cell.bookmarkButton.tag = 1
case 1:
cell.bookmarkButton.setImage(UIImage(named: "Bookmark-Not-Selected"), forState: UIControlState.Normal)
cell.bookmarkButton.tag = 0
default:
break
}
You'll need a myProperty variable of course, which you should change in your didBookMarkItem as well
As others have said, it's due to table cell reuse. You want your tableView(_:cellForRowAtIndexPath:) method to always update the cell state from your model. Your bookmark state should be a property of your model. When the bookmark button state for a cell is changed by the user, the new state should be reflected in your model. If you do these things, your table cell state will always be correct. If you don't already, it's nice to always define a custom UITableViewCell class that contains outlets for all of the views and controls within your table view cell (your bookmark button would have an outlet.) Also, within your custom cell class, when you capture the button selection, you can call back to the controller either via a delegate (as you are doing), or a "bookmarkChanged" handler (closure) defined on the cell. I prefer the closure method. If you would like to see an working example of this, let me know.
Also, nix the tag. You don't need the tag to keep track of your bookmark button selection state, unless there's more to it than you have shown/described. If you follow the approach I described, it's taken care of.
Table view cells are reused. Once you select a cell and change its tag number and scroll the selected cell out of the table view, it will be used again to show a row at the index path of the new row that appears. And since its tag represents that of a selected cell it is displayed as highlighted.
Try to look up about cell reuse in table views.
You need to change your approach about determining which cell is to be displayed as selected. Add it as a property in your data source and based on that set the bookmark as selected or not selected.

Resources