Here's the dilemma: I want to create a custom editingAccessoryView that contains two buttons for my stock UITableViewCell. I'd like to achieve this using a storyboard. So far I've followed the steps outlined here, here and here. I just can't seem to get it to work. The closest I got was when I created a xib of type UIView, set the class to that of my UIViewController that contains the UITableView and bound it to my IBOutlet, but on cellForRowAtIndexPath it's nil.
Truth is, I think I just need to know how to create the view and then map it to the editAccessoryView; from there I believe I can figure out how to add buttons and map the corresponding IBAction. Can anyone provide some step-by-step instructions or links to tutorials?
I solved this on my own with the following code:
UIView *editingCategoryAccessoryView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 120, 35)];
UIButton *addCategoryButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[addCategoryButton setTitle:#"Add" forState:UIControlStateNormal];
[addCategoryButton setFrame:CGRectMake(0, 0, 50, 35)];
[addCategoryButton addTarget:self action:#selector(addCategoryClicked:withEvent:) forControlEvents:UIControlEventTouchUpInside];
UIButton *removeCategoryButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[removeCategoryButton setTitle:#"Remove" forState:UIControlStateNormal];
[removeCategoryButton setFrame:CGRectMake(55, 0, 65, 35)];
[removeCategoryButton addTarget:self action:#selector(removeCategoryClicked:withEvent:) forControlEvents:UIControlEventTouchUpInside];
[editingCategoryAccessoryView addSubview:addCategoryButton];
[editingCategoryAccessoryView addSubview:removeCategoryButton];
cell.editingAccessoryView = editingCategoryAccessoryView;
As you can see I created a new UIView programmatically and added two buttons via addSubview and then assigned it to the editingAccessoryView.
I know this is probably too late to help, but it is much better than the solution you found. iOS gives you a method named tableView: editActionsForRowAtIndexPath indexPath:. This method basically allows you to add your own UITableViewRowActions, which is much easier (and cleaner) than adding an entire UIView with UIButtons.
Apple says:
Use this method when you want to provide custom actions for one of your table rows. When the user swipes horizontally in a row, the table view moves the row content aside to reveal your actions. Tapping one of the action buttons executes the handler block stored with the action object.
If you do not implement this method, the table view displays the standard accessory buttons when the user swipes the row.
You can look at the Apple Documentation yourself, if you want.
Example (Swift)
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]? {
let customAction = UITableViewRowAction(style: .Normal, title: "Your Custom Action", handler: { (action: UITableViewRowAction!, indexPath: NSIndexPath!) in
println("Do whatever it is you want to do when they press your custom action button")
})
editAction.backgroundColor = UIColor.greenColor()
let deleteAction = UITableViewRowAction(style: .Normal, title: "Delete", handler: { (action: UITableViewRowAction!, indexPath: NSIndexPath!) in
println("You can even implement a deletion action here")
})
deleteAction.backgroundColor = UIColor.redColor()
return [deleteAction, editAction]
}
Related
I've created a custom collectionView cell class that contains a UIButton. So each cell contains it's own button that a user can press. However whenever one cell button is tapped it causes background color changes to buttons in other cells. I want it to where only the selected button's background color is changed instead and to prevent the change to other buttons. Below is my code:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellID", for: indexPath) as! SubjectsCell
cell.subjectButton.setTitle(subjectNames[indexPath.row], for: .normal)
cell.subjectButton.addTarget(self, action: #selector(updateBackgroundColor(_:)), for: .touchUpInside)
if cell.subjectButton.isChosen == selectedState{
cell.subjectButton.backgroundColor = .green }
else if cell.subjectButton.isChosen != selectedState {
cell.subjectButton.backgroundColor = UIColor(red: 34.0/255.0, green: 210.0/255.0, blue: 255.0/255.0, alpha: 1)
}
return cell
I realize that dequeueReusableCell causes some cells to be reused, if this is my problem how I prevent cell changes happening to random cell's and only affect the cell that the tapped button is in.
You're right that dequeueReusableCell(withReuseIdentifier:for:) is the source of the issue here! You just need to have your cell class override UICollectionReusableView's prepareForReuse() method and reset the background to whatever it needs to be.
You may need to change your data model so you have a way of remembering which cells have had the button pressed since the cells can't hold on to that data without causing problems.
Also note that calling addTarget inside that collectionView(_:cellForItemAt:) may cause problems later, targets are typically only added once when the object is set up initially. To avoid issues you should set that up either in the nib / storyboard as an action, or when you setup the view objects if you're doing programmatic view setup.
Here's Apple's documentation on prepareForReuse().
You can do something like this:
class SubjectsCell : UICollectionViewCell
{
#IBAction func buttonAction(_ sender: UIButton) {
updateBackgroundColor()
}
}
so the cell view background update should present in SubjectsCell because its a view update not in job of viewController.
the following code, click event works.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.value1, reuseIdentifier: "SwiftCell")
let button = UIButton()
button.setTitle(hiddenGear ? "+" : "-", for: .normal)
button.setTitleColor(UIColor.lightGray, for: .normal)
button.addTarget(self, action:#selector(self.toggleGear), for: .touchUpInside)
button.frame = CGRect(x: self.view.frame.width - 36, y: 0, width: 30, height: 30)
button.backgroundColor = UIColor.red
cell.addSubview(button)
but what i want just like below:
move UIButton out of the uitableview cell, and the click event does not work.
button.frame = CGRect(x: self.view.frame.width - 36, y: -30, width: 30, height: 30)
try to use cell.bringSubview(toFront: button) , but it does not work too. have any idea?
This is illegal:
cell.addSubview(button)
You may not add a view to the cell. You must add the view only to the cell's contentView.
But the content view does not extend all the way to the right. If you want the view all the way over at the right, you must provide it as an accessory view.
Finally, you cannot easily add a working button outside the cell bounds, because a subview outside its superview's bounds is not touchable. You can make it touchable, however, by munging the hit-testing for the superview. You would need to override this method:
https://developer.apple.com/documentation/uikit/uiview/1622469-hittest
You need to use constraints, relative to the top of that UITableViewCell. Also, I can see you are trying to create a '+' (plus) icon in the corner. Consider using a Navigation Item found in the Object Library.
First it's not preferred to add UI elements inside cellForRow because of reusing , second you can encapsulate the button inside the cell and play with the background color to fake that it's out of cell if the button is supposed to be in every cell , otherwise you have to add it relative to the mainView (self.view)
I have a ViewController who contains a TableView. I want in each cell, a red button who did something (let's say 'hello' for test), or when I touch the cell anywhere but not on the button I perform a segue.
But even when I touch the button, it's the segue who perform. I tried some search on SF but none of the questions help me...
I don't know if that's usual but when I touch the cell, all the row white background become gray, even the background of the red button.
In storyboard I have this cell hierarchy :
The user interaction is enabled on both cell and button.
In the questions I found they said, on the cell set 'accessory' on 'disclosure indicator', I did it, but if it's possible I would like to keep it at 'None'.
Also I set a custom class for the cell, here is the code :
class MyTableViewCell: UITableViewCell {
#IBOutlet weak var rentButton: UIButton? // I use this class for 2 tableview almost similar, but in the other I don't have the redButton, that's why I put an '?'
}
I also set the delegate and datasource of the tableView to my ViewController, here is the code of my view controller :
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! MyTableViewCell
cell.rentButton!.tag = indexPath.row
cell.rentButton!.addTarget(self, action: #selector(MyViewController.rent(_:)), forControlEvents: .TouchUpInside)
return cell
}
// Rent() is never called:
func rent(sender: UIButton) {
print("here \(sender.tag)")
}
EDIT : The solution was to put out the button from the conainer view !
You can stop the segue from actioning when the button is pressed by performing it programatically. You can do the following:
Remove your segue that goes from the cell to another view controller and change it go from the TableViewController to the other view controller
Create a variable in your TableViewController class such as: var buttonPressed = false
In your button action set buttonPressed to true
In didSelectRow, check that buttonPressed is not true then performSegue and change buttonPressed to false, else do nothing
As discussed, move the button from the container to the content view
I am trying to change the background color of the view that appears when you swipe a UITableViewCell row, the background color behind the 'Delete' button.
I tried changing the cell.editingAccessoryView but that didn't do anything.
UIView* myBackgroundView = [[UIView alloc] initWithFrame:CGRectZero];
myBackgroundView.backgroundColor = [UIColor clearColor];
cell.editingAccessoryView = myBackgroundView;
Any ideas?
I'm answering this because it took me a while to find an answer and this is the first entry that comes up in a search. By using the willDisplayCell method you can access the cells background color. Note that [UIColor clearColor]; will return white in so adjust your code accordingly.
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
cell.backgroundColor = [UIColor blackColor];
}
You're almost there. The UITableViewCell class has a backgroundView property, which is nil by default. Just create a new UIView as you've done in your question, then assign that to the backgroundView property of your cell.
cell.backgroundView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
cell.backgroundView.backgroundColor = [UIColor clearColor];
I think it depends on how you are adding content into your cell.
When i added content to the cell directly using [cell addSubView] or [cell.contentView addSubView] i was having the same problem.
My workaround to this was:
Create a view
Add all your content(labels, images etc;) to this view
Finally then add the view to your cell using [cell addSubView:tmpView]
And you do not need to tamper with the backgroundView property anymore. I have tried this and works perfectly!
While these answers are right I feel that for most cases it's easier just to set the cell's background color in interface builder. By that I mean the actual background color property of the cell, not it's content view. There's no reason to do it dynamically if it's always gonna be the color that the content view is anyway.
iOS8 adds new method to UITableViewDelegate:
optional func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]?
You can create customizable UITableViewRowAction which device your row actions.
For example:
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let deleteAction = UITableViewRowAction(style: .destructive, title: "Cancel", handler: { action, indexPath in
// DELETE ROW HERE
})
deleteAction.background = .green // you can change background color
return [deleteAction]
}
For more check here and here
Nice example of usage new API: here
right way, just to let everyone know:
func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
for subview in tableView.subviews {
if NSStringFromClass(type(of: subview)) == "UISwipeActionPullView", let button = subview.subviews.first, NSStringFromClass(type(of: button)) == "UISwipeActionStandardButton"
{
button.backgroundColor = .clear
button.subviews.first?.backgroundColor = .clear
}
}
}
I have created a table view and i am adding my custom uiview into the cell's content view.
The uiview has a uibutton. However i can add the uibutton into the content view when the cell is created.
I want to get the tap event on the tableview to perform some action. I also want tap event on the uibutton to perform a different action.
The problem is when i tap a row the button is also getting pressed and two events are triggered.
How can i get both the events discreetly? i.e tap on the tableview cell when portion the cell outside the uibuttons boundary is clicked.
You can add a Button in cell in as
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake( 13.0f, 13.0f, 90.0f, 90.0f)];
[button addTarget:self action:#selector(BtnPressed) forControlEvents:UIControlEventTouchUpInside];
[cellView.contentView addSubview:button];
then you can get events separately
Are you sure two different events are being triggered by the same tap? My understanding is that UIKit generates only one UIEvent and sends it to the top-most view in the hierarchy that responds to that type of gesture. In this case, if the button is higher in the view hierarchy, as it probably is, it should be receiving the event message. But, I may be wrong.
One solution to definitely avoid the possibility of two events being triggered, though possibly not the ideal, would be to deactivate row selection for the tableView, as follows:
self.tableView.allowsSelection = NO;
Then, add a view covering the remainder of the tableCell & add a gesture recognizer to that view. Since they don't cover the same area, there is no chance of conflicting events. Of course, to know what row was tapped, you'd have to add an instance variable for both the button and the new view to hold the indexPath. You would set the index path when you set up the tableCell in tableView:cellForRowAtIndexPath:
Hope this is helpful or gives you some new ideas.
You could subclass UITableViewCell and add a button, here is an example class:
import UIKit
private let DWWatchlistAddSymbolCellAddSymbolButtonLeftMargin: CGFloat = 15
class DWWatchlistAddSymbolCell: UITableViewCell {
private(set) var addSymbolButton:UIButton
init(reuseIdentifier:String?, primaryTextColor:UIColor) {
self.addSymbolButton = UIButton.buttonWithType(UIButtonType.Custom) as! UIButton
super.init(style:UITableViewCellStyle.Default, reuseIdentifier:reuseIdentifier)
selectionStyle = UITableViewCellSelectionStyle.None
backgroundColor = UIColor.clearColor()
imageView!.image = UIImage(named:"PlusIcon")!
addSymbolButton.setTitle("Add Symbol", forState:UIControlState.Normal)
addSymbolButton.setTitleColor(primaryTextColor, forState:UIControlState.Normal)
addSymbolButton.contentHorizontalAlignment = UIControlContentHorizontalAlignment.Left
addSymbolButton.contentVerticalAlignment = UIControlContentVerticalAlignment.Center
contentView.addSubview(addSymbolButton)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Layout
override func layoutSubviews() {
super.layoutSubviews()
addSymbolButton.frame = contentView.frame
let imageViewFrame = imageView!.frame
let imageViewMaxX = imageViewFrame.origin.x + imageViewFrame.size.width
let addSymbolButtonX = imageViewMaxX + DWWatchlistAddSymbolCellAddSymbolButtonLeftMargin
addSymbolButton.contentEdgeInsets = UIEdgeInsetsMake(0, addSymbolButtonX, 0, 0);
}
}
You can see the "Add Symbol" button at the bottom:
When you're returning the cell for the UITableViewDataSource, make sure to set the target for the button:
private func addButtonCell(tableView: UITableView) -> UITableViewCell {
var cell:DWWatchlistAddSymbolCell? = tableView.dequeueReusableCellWithIdentifier(kDWWatchlistViewControllerAddButtonCellReuseIdentifier) as? DWWatchlistAddSymbolCell
if (cell == nil) {
cell = DWWatchlistAddSymbolCell(reuseIdentifier:kDWWatchlistViewControllerAddButtonCellReuseIdentifier, primaryTextColor:primaryTextColor)
}
cell!.addSymbolButton.addTarget(self,
action:Selector("didPressAddSymbolButton"),
forControlEvents:UIControlEvents.TouchUpInside)
return cell!
}