UITableView - controls / IBActions embedded in custom UITableViewCell class - ios

I want to have a table with custom UITableViewCell class as rows. Within each row (custom UITableViewCell), I have a control which the user can use to change the state of something in that row.
Let's say for sake of an example that each table row (custom UITableViewCell) contains a button and a label, and I want the clicking of the button to change the label text from "activated" to "deactivated" back and forth.
*NOTE** I understand that for the above scenario I can use the didSelectRowAtIndexPath method to implement, but this doesn't work for my actual use scenario. I used the above scenario because its simpler, and includes the core functionality I need.
Normally if this were not in a UITableViewCell, I would have a button with an IBAction to the UIViewController custom class, and an IBOutlet to a label. The IBAction method would change the text of the IBOutlet label. Pretty standard stuff.
But with the control embedded in a table row, I cannot make IBAction connections. I've tried making IBAction connections to the UIViewController as well as the UITableViewCell, but it doesn't seem to allow it. How should I go about setting this up? It seems to me like a pretty useful thing to do in interface design, so I think it should be possible right?

First of all, I can control-drag from the button in a UITableViewCell xib to my header files to create IBAction. You just choose connection from Outlet to Action.
On the other hand, you can specify your Button's action by code.
If you button is a property, let's say it's self.tableCellButton, add the action like:
[self.tableCellButton addTarget:self action:#selector(yourAction:) forControlEvents:UIControlEventTouchUpInside];

You can set button's action in cellForRowAtIndexPath like so :
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! YourCustomCell
cell.button.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)
return cell
}
func buttonClicked(button: UIButton) {
// get row index
let index = self.tableView.indexPathsForRowsInRect(button.superview!.convertRect(button.frame, toView: self.tableView))
}

Related

two UITableView in a UICollectionView using the same reference?

I have a UICollectionView with 4 views in it. Each of these views have a UITableView inside it with custom cells. Each cell of the UITableView has a UIButton inside it and I have 2 cells per UITableView.
Something strange is happening. I have an action function for each button so that when a button is clicked, it becomes purple. The strange thing is this: if I scroll to the 4th view of my collection view and click on a button, it becomes purple as expected but then when I scroll to the 1st view of my collection view, the same button that I clicked in the 4th view (either the first one or second one) is also purple as if the 4th view of my collection view was referencing the items of my 1st view of the collection view.
I don't know at which point the 1st view becomes the same as the 4th view but here is a sample of the code:
// this is the cellForItemAt of my UICollectionView, very basic
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cellView = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! PollCellView
return cellView
}
// THIS IS ANOTHER FILE HERE
// this is part of my view that populate the UICollectionViews
class PollCellView: UICollectionViewCell {
// the table view
let questAndAnswersTableView : UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.separatorStyle = .none
tableView.allowsSelection = false
return tableView
}()
// I add the tableview in the view here
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(questAndAnswersTableView)
// a classic cellForRowAt of my UITableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "AnswerCell2", for: indexPath) as! AnswerCell2
return cell
}
// THIS IS ANOTHER FILE HERE
// this part is my custom cell of the UITableView
class AnswerCell2: UITableViewCell {
let answerTextButton: UIButton = {
let answerButton = UIButton()
answerButton.setTitle("initial text", for: .normal)
return answerButton
}()
// I add the button to the cell here
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String!) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
addSubview(answerTextButton)
// I define the action function
answerTextButton.addTarget(self, action: #selector(answerClicked), for: .touchUpInside)
}
// the action to make the button purple
#objc func answerClicked(sender: UIButton) {
sender.backgroundColor = UIColor(displayP3Red: 0.6902, green: 0.7176, blue: 0.9922, alpha: 1.0)
}
[EDIT FOLLOWING THE ANSWERS RECEIVED]
Dequeuing is definitely not as simple as it first seems... You can't trust that the tableview dequeued in a given collection view is really the one you expect... You need to keep track of the content (model) yourself. One thing that made it easier to fix is to use closures between the cell of the TableView and the cell of the UICollectionViewCell... You can very easily pass data from one to the other (like what indexPath was clicked, etc.).
when you're managing multiple nested tableviews or collectionviews or even when you're managing this yourself, you have to set up a system of arrays that when the button is clicked, you add those indexpaths to an array called "indexPathsThatArePuple" then when you after each button click, inside the button click function, you pass the indexPath and add that to the array. If the button is already inside the array when the button click function is pressed, then you remove that indexPath. in the button click function, after you add or remove indexPaths, you reload the collection view which will reload the tableviews and then inside "cellForItemAtIndexPath" you check the indexPathsThatArePuple array and then write "if indexpath is contained in indexPathsThatArePuple" then set the cell color to purple.
I understand what you're trying to do, but you don't realize or understand just how complicated the cell reuse system is in iOS. The first thing you need to do is wrap your mind around the idea the that you have to manually manage cell states due to cell resuse. The purple cell showing up in another table is from cell reuse. Apple won't automatically manage this for you, you have to do it yourself. As i described above and even my description above is not that cut and dry since you'll likely struggle with this for weeks until you grasp the concept. Good luck, you'll eventually get it.
It seems to be caused by UICollectionView cell reuse mechanism.
When the cell is created and scroll out of the screen, it will be reused later if a new cell with the same identifier is required. In your case, when you scroll down, the 1st view is reused as the 4th view, And when you scroll to the top, The 4th view is reused as the first view. If the method prepareForReuse happens to be not implemented, the UI status of the cell will not be change, so you will see the button with purple color.

How to make a UITextView detect the click on table cell

I am working on a project and one of the table views the text for it is a UITextView, but on the other one is a UILabel. The UILabel detects the click from the user as a click on the table cell, but the UITextView doesn't. Why is this happening? Is there any way to fix it?
Try that
yourTextView.addTarget(self, action: #selector(myTargetFunction), for: .touchDown)
#objc func myTargetFunction(textField: UITextView) {
print("myTargetFunction")
}
Ensure the isSelectable property of your textView is true.
I figure it out, the cell view and text view are both scrow views and always will have conflict, so I need to uncheck the UserInteractions and Multiple Touch of the text view, booth set to false
I'm not sure from your question whether you're talking about selection on the UITableView row or the UITextView/UILabel itself.
If you're trying to set up a gesture recognizer on the UILabel or UITextView, I'm wondering if that's necessary or if you could just use didSelectRowAtIndexPath in your view controller.
e.g.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
yourMethodHere(for: cell)
}
Then you'd define in yourMethodHere() what behavior you want to happen when the cell is selected.

Find Out Which Section Button Belongs To In UITableView When Clicked Inside Cell

I am trying to change the value of an element of an array depending which section a button is clicked in. For example say I have this array numbers = [0,0,0,0,0] and I want to change the first element to 5. I insert 5 into the cell of the first section and click done in that same section and the array will now read [5,0,0,0,0]. Is there a way to know which section the button belongs to?
Right now I have two separate classes. One for the custom cell and one for the tableview. Both of the have an outlet to the button. When the button is clicked the custom cell class changes a temporary global number to the inserted number. And inside the table class I want the button action to take that global number and insert in into the element that is the same number as the section the button belongs to. Except I don't know how to find out which section it belongs to.
Can anyone help me out with this? I'm writing in Swift btw.
If you only need the section (not the section and row) of the index path of the cell containing the tapped button, you could tag the button with the section number (Int) when you configure the table view cell:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("MyIdentifier", forIndexPath:indexPath) as! MyCustomTableViewCell
cell.myCustomButton.tag = indexPath.section
// Remove all existing targets (in case cell is being recycled)
cell.myCustomButton.removeTarget(nil, action: nil, forControlEvents: .AllEvents)
// Add target
cell.myCustomButton.addTarget(self, action:"buttonAction:", forControlEvents:.TouchUpInside)
return cell
}
func buttonAction(sender:AnyObject)
{
if let button = sender as UIButton{
println("Tapped Button in section: \(button.tag)")
}
}
If you need both the section AND the row, you're better off storing the whole index path. For that, you can either use a custom UIButton subclass with a property of type NSIndexPath, or store it in the table view cell.
But the second solution is a bit messy since you have to access the table cell (and then, the index path) from the button by using superview, etc., and this relies on the table cell's subview structure. I'm not sure if the parent view of your button is the table cell itself, or its "content view" (I'm a bit rusty right now...)
If you want to find which cell has been 'clicked' you could use the UITableViewDelegate method.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}

Swift XCode - Changes in storyboard aren't being reflected in the iPhone simulator

I'm trying to make a simple UITableView that has a button appended to the right side of every cell. I added the button to the prototype cell in my storyboard and set up the constraints so that AutoLayout takes over and puts it in the right spot. When I go to preview the layout in the Assistant Editor, everything looks great. However, once I start the simulator and go to that view, the button is no longer there (the cell is blank).
I ran into a similar problem when I tried to add a Disclosure Indicator and it wasn't showing up - to fix it I had to add that accessory programatically. Isn't the point of storyboard to help make changes without using code? I'm still really new at this and would appreciate some guidance.
EDIT: Some code as requested (sorry):
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let addButton = UIButton()
let menuItem = currentOrder?.getSortedMenuItems()[indexPath.row]
let itemPrice = "\(currentOrder!.menu[menuItem!]!)"
cell.textLabel!.text = menuItem! + " - " + itemPrice
return cell
}
Most of the code in there is pretty irrelevant, but as you can see I tried adding a button and then stopped cause I wasn't sure where to go from there. I was under the impression that all of this could be done using storyboard.
The problem which you have arises from the fact that you are NOT using the cells which you created in your storyboard. When you call
let cell = UITableViewCell()
it creates a cell with default style. What you want to do instead is to use the cells which you created in the storyboard.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cellWithSwitch") as! UITableViewCell
// setup your cells (e.g. update its title)
return cell
}
You should change the "cellWithSwitch" to the identifier which you have set for your cell in storyboard.
You can find the identifier of your cell by selecting it in the storyboard and then checking info in the Attributes inspector:
If you haven't yet set this identifier - set it and then use it. And then changes which you make in storyboard will get reflected on the phone ;)
Extra
It is often necessary to subclass your cell to make it easier to work with. Here is an example:
class MyCell:UITableViewCell {
#IBOutlet var myButton: UIButton!
}
Now, you have to do 3 other things:
1) Select your cell in the storyboard and set its class to MyCell in Identity inspector (that's the third tab in the image which I posted)
2) Open Connections inspector (last tab) and connect the myButton outlet
3) Modify your cellForRowAtIndexPathFunction:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cellWithSwitch") as! MyCell
// setup your cells (e.g. update its title)
// you can now access your button as cell.myButton
return cell
}

Handling button interaction of a dequeueReusableCell in iOS Swift

I am making an iOS app that relies on a table view. In each cell of the table view, there are 4 buttons aligned on the bottom. I have a cell class that is pretty standard and a feedController to handle the table and setting all the items of the cell.
Everything works fine on it but I can not figure out how to handle the button clicks within the cell. I can hard code it into my cell class, but then every 3 cells has the same interaction. Is there a way to pass the button click function from the cell class into the controller? I have tried checking the state from the controller and that has not worked.
Can you add a gesture recognizer as you're doing your cellForItemAtIndexPath? So I had something similar with a collection view, and what I did was as it within:
func collectionView(collectionView: UICollectionView!, cellForItemAtIndexPath indexPath: NSIndexPath!) -> UICollectionViewCell!
{
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as MyCollectionView
...
I would add a gesture recognizer to each cell
i.e.
cell.addGestureRecognizer(UITapGestureRecognizer(target: self, action:Selector("tapAction:")))
And then something like:
func tapAction(recognizer: UITapGestureRecognizer) {
...
}
so recognizer ends up being the specific item tapped, and I could take action accordingly (in my case, I had my datasource of items and I would find the item in an array by casting recognizer to a cell, finding the appropriate subview, and update values on it)
I would add code block properties to your cell class which the table can assign to deal with each button. In your cell, code each button handler to call the appropriate block, or pass an index for the button used in a single block.
See my answer here which has an example, but for a switch.
How can I get index path of cell on switch change event in section based table view
If after a few cells you get the same interaction, it's possibly because you're dequeueing a reusable cell, and you're getting the same cell.
Make sure to set your .setTarget() call for your buttons in your tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) data source every time the cell is dequeued. It would help if you shared how you're handling dequeuing to see if this is your issue.

Resources