Custom Accessory Button in UITableViewCell - ios

I am trying to add a custom button in the Accessory View for a UITableViewCell.
I have added the following code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// ....
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "arrow-right-light.png")?.maskWithColor(color: .white), for: .normal)
button.addTarget(self, action: #selector(buttonTest), for: .touchUpInside)
button.tag = indexPath.row
cell.accessoryView = button
// ...
However, I don't see the button show up on in the Accessory View.

I can think of two possible reasons:
The problem might be that the button has no size, so it's invisible. Add this line, after setting the image:
button.sizeToFit()
Another possibility is that you have no image named "arrow-right-light.png". That wouldn't crash your existing code, but it would prevent the button from having any image so you wouldn't see it. Try saying UIImage(named: "arrow-right-light.png")! instead, just as a test; if you crash, that was indeed the problem, and then you can figure out what the correct name is.

I tried #matt's solution above, but this makes the button as large as the image.
I showed a iOS builtin info image which is smaller than the minimum advised button size of 44x44 points. Although the button works, it's hard(er) to tap; which is a common invisible UX issue seen in many apps (even professional ones.
Therefore instead of calling button.sizeToFit() you must set the buttons height (I did my full cell height of 50) and width to a nice & easy tappable 60.

Related

How do I add an ImageView into a table cell on button click?

I am trying to do something like this:
When the user clicks "Add Page," a new grouping shows up below it. Now, I decided to use Table View cells in order to achieve this. After following various tutorials and looking up similar Q&As, I am able to add cells on button click with UILabel and have the cell height be dynamic depending on the content but now I am trying to figure out how to add ImageViews and place buttons within a cell.
I've created a custom cell class:
class PageCell : UITableViewCell {
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = UITableViewCellSelectionStyle.none
setupViews()
}
...
... // other random code here
let imgView : UIImageView = {
let imgview = UIImageView()
imgview.frame = CGRect(x: 100, y: 150, width: 150, height: 140)
imgview.tintColor = UIColor(red: 0.73, green: 0.2, blue: 0.3, alpha: 1.0)
imgview.translatesAutoresizingMaskIntoConstraints = false
return imgview
}()
func setupViews() {
addSubview(pageLabel) // the label that I got working
addSubview(imgView) // can't get this working
...
// constraint info here
}
}
And back in my TableViewController:
class TakePhotosVC: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(PageCell.self, forCellReuseIdentifier: "cellID")
}
// return the actual view for the cell
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let pagecell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath) as! PageCell
// set more stuff here
}
... // more code
}
My issue is that I am trying to get a box showing where the ImageView is that the user can click on to load in a picture. I am unsure how to do that and place all the relevant buttons as well (Trash, X, etc.)
Any help would be greatly appreciated.
EDIT
Okay, I was trying to follow this tutorial and I can't quite get it to work. In my prototype cell, I see this:
But the result is this:
I made the UIImageView have a background so I can see it. I have two constraints for the UIImageView which are: width = 240, height = 128 and two constraints for the Page Label which are: width = 240, height = 21. Two questions: why are my elements not placed correctly even though I have it correctly placed in the Storyboard? And why is the cell height not dynamically resizing to accommodate the elements?
I have these two lines in my viewDidLoad method of the TakePhotosVC but it doesn't seem to do anything.
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 200
If it's relevant, I get this warning when I run the Simulator.
Warning once only: Detected a case where constraints ambiguously
suggest a height of zero for a tableview cell's content view. We're
considering the collapse unintentional and using standard height
instead.
EDIT 2
I got it to work. For any poor souls reading this after me, you have to click on those dotted pink lines in the Constraints window editor and then click "Add X Constraints" in order to get the ImageView to center and stuff.
My issue is that I am trying to get a box showing where the ImageView
is that the user can click on to load in a picture. I am unsure how to
do that and place all the relevant buttons as well (Trash, X, etc.)
I am not sure if I understand you correctly. You have a cell with an UIImageView. But you want to show a box to visually indicate where the user should touch to add an UIImage (?)
Why not simply add a UIButton on top of the UIImageView with the exact same frame size, and on touch, you fire your action to add the image, and once the image is successfully added, you can set the UIButton to hidden.
If the user deletes the image with the trash button, you simply show the UIButton again by hidden = NO.
Other solution:
Add a border to the UIImageView with custom colors and add a UITapGestureRecognizer to fire an action. (Make sure you set the UIImageView to userInteractionEnabled = YES;
You can allso add a Placeholder image to the UIImageView when there is no image set, with your custom design.
The easiest approach would be to use a xib instead of placing the buttons programmatically. To do this, add a new file and select xib. In a xib, you can pre-make a tableviewcell with the image view, and you buttons placed for you already with constraints. Then, you can subclass this table view cell and connect the image view and buttons with ib outlets and ib actions to access the buttons and image view. Then, in your cellForRow function, load the xib like this:
Bundle.main.loadNibNamed("NameOfNib", owner: self, options: nil).first as! NameOfSubclass
I would advise to read more on xibs and nibs.

Refresh Control image remains the top View

I've set up a refreshControl, and I am attempting to add an image to it. However, the image is in front of every other view. I've tried changing the index number and adding it behind the collectionView. Here's my current code, the image refuses to move behind the other views no matter what syntax I change it to.
func setupRefreshControl() {
//add refrechcontrol to Collectionview
self.collectionViewIBO.addSubview(refreshControl)
self.refreshControl.addTarget(self, action: #selector(ViewController.refresh), for: UIControlEvents.valueChanged)
let rcImageView = UIImageView(image: UIImage(named: "Rozei"))
self.refreshControl.insertSubview(rcImageView, at: 0)
}

How to avoid image size changes in UIImageView of UITableViewController in Swift

I am using a UITableViewController to display rows of playing cards. I've set the Mode to "Aspect Fit" and I've checked the "Clip Subviews" in the Storyboard for the ImageView in each row's cell, its ContentView parent, and the Cell that contains the ContentView.
Everything looks as expected when the table is initially displayed but, when I swipe to scroll through the table, some (not all) of the new rows that scroll into view have images scaled to the wrong size, as shown.
It seems like if I drag quickly, I get more rows that are of the wrong size. If I drag slowly, all the new rows are scaled appropriately. When I use a different emulator, the incorrect scaling would make some of the images too big rather than too small and so the entire row of cards would not fit within the display.
In the code, I've set the contentMode and clipToBounds, but they do not seem to help:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("LineCell", forIndexPath: indexPath)
let index = indexPath.row % 4
let imagename = "cards\(index)"
let im = UIImage(named: imagename)
cell.contentMode = UIViewContentMode.ScaleAspectFit
cell.clipsToBounds = true
cell.contentView.contentMode = UIViewContentMode.ScaleAspectFit
cell.contentView.clipsToBounds = true
cell.imageView?.contentMode = UIViewContentMode.ScaleAspectFit
cell.imageView?.clipsToBounds = true
cell.imageView?.image = im
return cell
}
Did I miss something obvious? Thanks!
I ended up using a programmatic way around the problem.
let cellImage : UIImageView = UIImageView(frame: CGRectMake(0, 0, cell.bounds.size.width, cell.bounds.size.height))
cellImage.contentMode = UIViewContentMode.ScaleAspectFit
cellImage.clipsToBounds = true
cellImage.image = im
cell.addSubview(cellImage)
I would still like to find a way to do it via the Storyboard, which I recall used to work in a straightforward manner...back before Apple started coming out with devices of all shapes and sizes leading to their need to offer increasingly complicated ways to accommodate all the sizes in a semi-automated, constraint-based system that requires quite a bit of overhead even for the simplest tasks. But, for now, this will do.

Can't interact with anything inside a UITableViewCell, I'm being blocked by a bool somewhere, where is it?

I'm constantly running into this issue, I don't know how to treat UITableViewCells as UIViews.
I added a button to my UITableViewCell:
let btnWidth = self.contentView.frame.size.width * 1.1
let btnHeight = self.contentView.frame.size.height * 1.6
btnJoinChannel = UIButton(frame: CGRect(x:0,y:0,width:btnWidth,height:btnHeight))
btnJoinChannel.setTitle("Join Channel", for: .normal)
btnJoinChannel.setTitleColor(.white, for: .normal)
btnJoinChannel.backgroundColor = .clear
btnJoinChannel.addTarget(self, action: #selector(JRegionCell.loadRegion), for: .touchUpInside)
self.contentView.addSubview(btnJoinChannel)
Buttons never work on UITableViewCells by default because some kind of touch gesture value overrides the new button.
What do I configure to prevent this override? I would like users to touch buttons inside UITableViewCells. Basically, my cells need to behave like UIViews.
Your button is bigger than content view. Buttons don't work if they are out of superview.

UIButton not responding used in a custom UITableViewCell

I know this issue is already been asked few times in SO. Despite trying those out, I am still unable to solve my problem.
I am using a UITableView inside a UIViewController. I have a custom UITableViewCell which has couple of buttons in it. However, I am not able to make the Button respond to Click event.
The development environment is iOS 9 and Swift 2
Snippets used :
BranchNearMeTableViewCell.swift contains
#IBOutlet weak var btnDetails: UIButton!
view controller class
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("branchNearTableCell") as! BranchNearMeTableViewCell
cell.btnDetails.tag = indexPath.row
cell.btnDetails.addTarget(self, action: "showDetails:", forControlEvents: .TouchUpInside)
}
func showDetails(sender: UIButton){
print("Button Pressed:")
}
Additional Info:
TableView and TableCellView has User interaction disabled in Interface builder since don't want the entire cell to be clickable.
UIButton inside TableViewCell has User Interaction enabled.
Being an iOS noob, I may be making a silly mistake which I might have overlooked.
Similar questions that I checked include:
SO1
SO2
SO3
I Deeply appreciate any help regarding this question.
I faced a similar issue. I was programmatically adding an UIButton to the UITableViewCell via addSubview. The button would not respond to touch events. Using Debug View Hierarchy, I finally discovered that any subviews added to the UITableViewCell was behind contentView, which was blocking user input from reaching the UIButton. The issue was resolved by adding the UIButton to contentView instead of the UITableViewCell.
I would have userInteractionEnabled set to true on the table view cell as well. I would prevent taps using the UITableView allowsSelection to false
Also remember to remove the target and action in tableView:cellForRowAtIndexPath: since the cells are recycled, the button might already have the target and action, it might add a second.
I found a simple solution:
Inherits UITableViewCell, and override init()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
//init subviews, eg. self.switch = UISwitch()
super.init(style: style, reuseIdentifier: reuseIdentifier)
// add this line magic code
contentView.isUserInteractionEnabled = true
//add subviews, e.g. self.addSubView(self.switch)
}
You only have to do (in ViewDidLoad):
mTableView.delaysContentTouches = false
For programmatically created views, the only thing to remember is to declare buttons using lazy var in UITableViewCell. And also add subviews to contentView instead of the cell itself For example:
class CounterCell: UITableViewCell {
lazy var incrementButton: UIButton = {
let button = UIButton()
button.setTitle("+", for: .normal)
button.addTarget(self, action: #selector(incrementAction), for: .touchUpInside)
return button
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(incrementButton)
// Your constrains here
}
#objc func incrementAction() {
}
}
When using programmatically views, there's no need to add .userInteractionEnabled flags.
Then to take the action out of the cell, just add a delegate and assign it from the UITableViewDataSource.
I came across this issue today, with a button inside a static UITableview cell, that was not responding to user events.
I realised the 'Content View' of the cell also has a 'User Interaction Enabled' tick box. Make sure you select the 'Content View' inside the UITableview cell in your Document Outline menu, then tick the box for 'User Interaction Enabled' in the Attributes Inspector - see attached photo for reference. 'User Interaction Enabled' also needs to be checked for the cell for this to work.
Hope this helps. XCcode screen shot
Also, make sure you are adding target actions to your buttons outside their setup. So instead of
let button: UIButton = {
//addTarget...
}()
you can have a function to set up your buttons after something happens:
func setButtonsUp() {
// myButton.addTarget
}
For anyone else struggling, here's my solution:
sendSubviewToBack(cell.contentView)
The thing that there's now an extra UITableViewCellContentView layer which blocks interaction with views behind it.
Related issue: An extra UITableViewCellContentView overlay appears in a TableView on iOS 14 preventing taps, but works fine on iOS 13
Ad a first sight nothing seems to be wrong with your code.
So I suggest you to add a background color to the superview of the button, why? because if the button is outside the frame of its superview it will never receive touches.
If you see that the button is not inside the background color probably you have an issue positioning the item, check constraints or whatever you are using.
Check also the frame of the button.
You can also do both by inspecting the view at runtime, here a tutorial.
I dont know what wrong in the code but i can suggest which i personally use and it works for me
In BranchNearMeTableViewCell.swift
#IBOutlet var btnDetails: UIButton!
#IBAction func btnDetailsClick(sender: AnyObject) {
tapButton?(self)
}
var tapButton: (UITableViewCell -> Void)?
In Table view controller
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("branchNearTableCell") as! BranchNearMeTableViewCell
cell.tapButton = {(user) in
//User will be tablecell here do whatever you want to do here
}
}
So if you click on button in table cell this cell.tapButton will be called you can do whatever you want to do here
The only things we need to do is in cellForRowAt just put:
cell.selectionStyle = .none
in this way, UITableview will bypass the touch of selecting cells and allow buttons inside our cells to be clickable.
set cell and cell content view isUserInteractionEnabled = true
Add Tapgesture to the button
Add a closure to handle gesture action
Add target for that button.
button.addTarget(self, action: #selector(connected(sender:)), for: .touchUpInside)
Set tag of that button since you are using it.
button.tag = indexPath.row
Achieve this by subclassing UITableViewCell. button on that cell, connect it via outlet.
Make sure button.isUserInteractionEnabled = true
To get the tag in the connected function:
#objc func connected(sender: UIButton){
let buttonTag = sender.tag
}
Make sure that ALL of tableView's superviews do have isUserInteractionEnabled set to true
User interaction was already enabled for my UIButton. The thing that worked for me is
switching the stackView distribution to "Fill".

Resources