I am very new to Swift and Xcode. I am trying to make a UIButton appear at the right side of a tableView cell once the user presses the return key. Right now I am just having problems having a button appear in general.
From my understanding here is my custom class TextInputTableViewCell which I believe should create a button:
class TextInputTableViewCell: UITableViewCell {
#IBOutlet weak var textField: UITextField!
var cellButton: UIButton!
func createCellButton()
{
let image = UIImage(named: "Completed Circle.png")
cellButton = UIButton(frame: CGRect(x: 340, y: 56, width: 30, height: 30));
cellButton.setBackgroundImage(image, for: UIControlState.normal)
addSubview(cellButton)
}
And then here is where I configure the properties of the cell in my ViewController class:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell")! as! TextInputTableViewCell
cell.textField.delegate = self
cell.backgroundColor = UIColor.clear //to get background image loaded
cell.createCellButton()
return cell
}
I know I am probably messing something up. Also, any tips on how to make the appear after the user presses enter would be appreciated. I have the textFieldShouldReturn function set up.
why do not you use storyboard to make a UIButton and make an Outlet. Then in cellForRowAt do cell.button.isHidden = true cell.button.isUserInteractionEnabled = false (or something similar, not sure about actual spelling). Then make your TextInputTableViewCell conform UITextFieldDelegate
class TextInputTableViewCell: UITableViewCell, UITextFieldDelegate { ..
inside cell:
func textFieldShouldReturn(textField: UITextField!) -> Bool { //delegate method
self.button.isUserInteractionEnabled = true
self.button.isHidden = false
return true
}
you might face some problems with reusability of cells, so better make some local variable like var userDidEnterText = false to check if user has changed something
Looking on the createCellButton() body I can tell that the value of x & y could be the source of the problem. I think that the button is created out of the cell frame. Try to use the following parameters:
cellButton = UIButton(frame: CGRect(x: 0, y: 0, width: 30, height: 30));
Related
I have a tableview in my view controller with a custom cell. in the cell I have a function that is suppose to do something. I have a print statement that is getting executed.
#objc func cellButtonTapped() {
cellText.backgroundColor = .red
print("cell button tapped")
}
when I tap the button I do see in my console the print statement, but its not changing the cell background color like I want it to.
this is the cellText in the tableViewCell
let cellText : UITextField = {
let cellText = UITextField()
cellText.isUserInteractionEnabled = false
return cellText
}()
this is how its layout is in the cell. I don't think its layout is important
override func layoutSubviews() {
super.layoutSubviews()
contentView.addSubview(cellText)
contentView.addSubview(cellButton)
cellText.frame = CGRect(x: 0,
y: 0,
width: contentView.width - 30,
height: contentView.height)
cellButton.frame = CGRect(x: cellText.right, y: 0, width: 30, height: contentView.height)
}
what I had to do is create a variable in the viewController with the value that the cell needs. in my case I wanted a bool value to be carried over.
var isBool : Bool = false
then I let the function for the button to change the variable value:
#objc func sendButtonTapped() {
isBool = !isBool
tableView.reloadData()
print("send button tapped")
}
the function here changes the value of my bool every time I click the button.
I transferred the value to the cell with the cellForRowAt in the tableview.
When the app first build, it seem like working just fine, please see pic where each pics is one cell in side the table view . (I am working to place them properly)
The problem that I am facing is that: once I scroll down and scroll up again. Each cell data got massed up. Please see pics. Which I still have no idea what is wrong in my coder after hours searching online and decoding. please point out what I am missing. I have attached my code.
I am using the tutorial code from Ray Wenderlich and adding the codes below in the func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell method Yes I am doing it each word as UIBotton to trigger an action.
Adding
"cell.contentView.subviews.filter { $0 is UILabel }.forEach { $0.removeFromSuperview() }"
didn't solve the issue. please give me a completed example?(working one) Thank you.
let wholeSentense = artist.bio
let whoeSentenseArray = wholeSentense.components(separatedBy: " ")
var xPos = CGFloat(0)
for element in whoeSentenseArray {
var button = UIButton()
button.setTitle(element, for: UIControlState.normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 15)
let buttonTitleSize = (element as NSString).size(attributes: [NSFontAttributeName : UIFont.boldSystemFont(ofSize: 15 + 1)])
button.frame.size.height = buttonTitleSize.height * 1.5
button.frame.size.width = buttonTitleSize.width
button.frame.origin.x += xPos
xPos = xPos + buttonTitleSize.width + 10
button.setTitleColor(UIColor.black, for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: UIControlEvents.touchDown)
cell.addSubview(button)
}
return cell
}
func buttonAction(sender: UIButton!) {
print("\n\n Title \(String(describing: sender.titleLabel?.text)) TagNum \(sender.tag)")
// print("Button tapped")
}
I think you better move this complicated logic into a UITableViewCell subclass. Here's a rough skeleton:
class MyCustomTableViewCell : UITableViewCell {
#IBOutlet var imageView: UIImageView!
var sentence: String! {
didSet { <add your logic here> }
}
}
There are lots of resources on SO and other websites about how to create custom table view cells, such as this.
Anyway, whichever way you choose to do it, you need to be aware that table view cells are reused. You need to first remove all labels before adding them:
cell.contentView.subviews.filter { $0 is UILabel }.forEach { $0.removeFromSuperview() }
Each row of a table has a position control implemented as a segmented control. When the table is initialized, the selectedSegment is unknown. I want to update the selectedSegment from a different function.
Currently the code creates an array of segmentControls which can be accessed by a function. The program runs OK, but the segmentControl does not updated.
Here is the code to create the table cells:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath) as UITableViewCell
cell.textLabel?.text = self.shades[indexPath.row]
let shadePositionControl = UISegmentedControl (items: [#imageLiteral(resourceName: "Icon-Closed").withRenderingMode(.alwaysOriginal),
#imageLiteral(resourceName: "Icon-Half").withRenderingMode(.alwaysOriginal),
#imageLiteral(resourceName: "Icon-Open").withRenderingMode(.alwaysOriginal)])
shadePositionControl.frame = CGRect(x: cell.bounds.origin.x + 200, y: cell.bounds.origin.y + 6, width: 90, height: 30)
shadePositionControl.tag = indexPath.row
shadePositionControl.addTarget(self, action: #selector(self.shadePositionControlAction), for: .valueChanged)
shadePositionControls.append(shadePositionControl)
cell.contentView.addSubview(shadePositionControl)
return cell
}
and here is the function to update the selectedSegment:
func statusPoll() {
myShade.getShadow() //gets the state from AWS-IOT
if myShade.gotState() {
let shadeIndex = 0 //hard-coded during proof-of-concept testing
let position = myShade.getDesiredState(key: "position") as! String
switch position {
case "closed": shadePositionControls[shadeIndex].selectedSegmentIndex = 0
case "half": shadePositionControls[shadeIndex].selectedSegmentIndex = 1
case "open": shadePositionControls[shadeIndex].selectedSegmentIndex = 2
default: shadePositionControls[shadeIndex].selectedSegmentIndex = -1
}
}
}
What am I missing to have the segmentedControl get updated, and have the view refreshed to reflect the update? Is there a better way to update the per-cell segmentControl than creating an array of them? I tried using shadeTableView.reloadData() to refresh the display of the segmentedControl, but it had no effect.
This method of updating controls in a table cell works.
The reason it didn't appear to work was there was a bug in instantiating the shadePositionControls array. It was instantiated as follows, which added an unwanted segmentedControl at the zero position of the array.
var shadePositionControls = [UISegmentedControl()]
Hence, the code in statusPoll was updating the bogus segmentedControl, not the intended one.
The fix was change the array creation to:
var shadePositionControls:[UISegmentedControl] = []
This seems to have been asked a few times in swift and objc, but I can't see a correct answer for swift so hopefully someone can help me this time. I have created a custom accessory view button, but need the correct button action: as "accessoryButtonTapped" is an unrecognised selector.
What is the selector needed to call the tableViewCellDelegate method willSelectRowAtIndexPath?
The code I have is:
let cellAudioButton = UIButton(type: .Custom)
cellAudioButton.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
cellAudioButton.addTarget(self, action: "accessoryButtonTapped", forControlEvents: .TouchUpInside) //INCORRECT ACTION:
cellAudioButton.setImage(UIImage(named: "blueSpeaker.png"), forState: .Normal)
cellAudioButton.contentMode = .ScaleAspectFit
cell.accessoryView = cellAudioButton as UIView
Thanks in advance to anyone who can help.
Why do you need to call willSelectRowAtIndexPath? You have done everything right and to solve your unrecognized selector error just make a function that will be called when you tap on the cell.accessoryView. In your case:
func accessoryButtonTapped(){
print("Tapped")
}
Update
If you want to get the indexPath you could just
Add a tag to your cellAudioButton:
cellAudioButton.tag = indexPath.row
In your addTarget add a : to pass a parameter
And in your function
func accessoryButtonTapped(sender : AnyObject){
print(sender.tag)
print("Tapped")
}
So the whole code:
let cellAudioButton = UIButton(type: .Custom)
cellAudioButton.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
cellAudioButton.addTarget(self, action: "accessoryButtonTapped:", forControlEvents: .TouchUpInside)
cellAudioButton.setImage(UIImage(named: "blueSpeaker.png"), forState: .Normal)
cellAudioButton.contentMode = .ScaleAspectFit
cellAudioButton.tag = indexPath.row
cell.accessoryView = cellAudioButton as UIView
func accessoryButtonTapped(sender : AnyObject){
print(sender.tag)
print("Tapped")
}
Swift 3.1 Updated Solution of Rashwan
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
//add button as accessory view
let cellAudioButton = UIButton(type: .custom)
cellAudioButton.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
cellAudioButton.addTarget(self, action: #selector(ViewController.accessoryButtonTapped(sender:)), for: .touchUpInside)
cellAudioButton.setImage(UIImage(named: "closeRed"), for: .normal)
cellAudioButton.contentMode = .scaleAspectFit
cellAudioButton.tag = indexPath.row
cell.accessoryView = cellAudioButton as UIView
return cell
}
func accessoryButtonTapped(sender : UIButton){
print(sender.tag)
print("Tapped")
}
Note:
Here ViewController is the name of Class like LoginViewController
Why don't you use the UITableViewDelegate?
#available(iOS 2.0, *)
optional func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath)
To clarify that, yet it is possible. You can have custom UI elements act as accessory button.
Here is an example – assuming you're using Interface Builder. Add a UISwitch to the custom cell. Now right-click drag-n-drop from the UITableViewCell to the UISwitch. Select accessoryView as the Outlet connection. Done.
Every time you press the switch button the delegate method will be fired. This should also work for other action elements, e.g., UIButtons. And that is exactly the use case OP asked for.
My tableview displays podcast data. All cells are reused fine and display podcasts in the right order. I add download and play buttons as subviews of each cell. When I scroll down the list, the play button which is meant to be hidden for non-downloaded episodes will be shown and will hold the data of the cell which previously initiated a download. For example if I tap download button in cell 1, it will download the right episode and the play button will play the episode perfectly. If I scroll down to cell 10, the same play button will appear and will play episode from button 1. What's wrong with my code?
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
let cell: PFTableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! PFTableViewCell
if let title = object?["title"] as? String {
cell.textLabel?.text = title
let downloadButton = UIButton(type: UIButtonType.Custom)
downloadButton.frame = CGRectMake(cell.contentView.bounds.width - 100, cell.contentView.bounds.height / 2, 100, 35)
downloadButton.setTitle("Download", forState: .Normal)
downloadButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
downloadButton.tag = indexPath.row
downloadButton.addTarget(self, action: "downloadEpisode:", forControlEvents: .TouchUpInside)
cell.addSubview(downloadButton)
let playButton = UIButton(type: UIButtonType.Custom)
playButton.frame = CGRectMake(cell.contentView.bounds.width - 100, cell.contentView.bounds.height - 89, 100, 35)
playButton.setTitle("Play", forState: .Normal)
playButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
playButton.tag = indexPath.row
playButton.addTarget(self, action: "playEpisode:", forControlEvents: .TouchUpInside)
if let isDownloaded = object?["isDownloaded"] as? String {
if isDownloaded == "yes" {
playButton.hidden = false
} else {
playButton.hidden = true
}
}
cell.addSubview(playButton)
}
return cell
}
Edit:
I've also tried this but it doesn't work and still creates 1 more button per cell:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
let cell: PFTableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! PFTableViewCell
if let title = object?["title"] as? String {
cell.textLabel?.text = title
}
if let button = cell.viewWithTag(indexPath.row) {
print(button)
} else {
if let isDownloaded = object?["isDownloaded"] as? String {
if isDownloaded == "yes" {
let downloadButton = UIButton(type: UIButtonType.Custom)
downloadButton.frame = CGRectMake(cell.contentView.bounds.width - 100, cell.contentView.bounds.height / 2, 100, 35)
downloadButton.setTitle("Play", forState: .Normal)
downloadButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
downloadButton.tag = indexPath.row
downloadButton.addTarget(self, action: "playEpisode:", forControlEvents: .TouchUpInside)
cell.addSubview(downloadButton)
} else {
let downloadButton = UIButton(type: UIButtonType.Custom)
downloadButton.frame = CGRectMake(cell.contentView.bounds.width - 100, cell.contentView.bounds.height / 2, 100, 35)
downloadButton.setTitle("Download", forState: .Normal)
downloadButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
downloadButton.tag = indexPath.row
downloadButton.addTarget(self, action: "downloadEpisode:", forControlEvents: .TouchUpInside)
cell.addSubview(downloadButton)
}
}
}
return cell
}
Edit2:
Added download method:
func downloadEpisode(sender: UIButton) {
print("Downloading..")
let indexPath = NSIndexPath(forRow: sender.tag, inSection: 0)
let object = self.objectAtIndexPath(indexPath)
if let result = object {
let urlstring = result["downloadURL"] as? String
if urlstring != nil {
let episodeURL = NSURL(string: urlstring!)
downloader.downloadPodcastEpisode(episodeURL!, podcast: result)
}
}
}
Answer:
Although all answers were right, I have decided to remove the button download/play on cell taps in the end. Many thanks!
The problem is that you're adding a new button every time. When your cell is reused, it will already have a button. You need to write code that guarantees a button will only be created once, and after that, modify the existing button instead of creating a new one.
There are two common approaches to this:
Use viewWithTag to check if a button exists (create a button and set its tag if it doesn't)
Subclass UITableViewCell, create the button in the initializer, and configure the button each time the cell is used.
Searching for "uitableviewcell viewwithtag" or "subclass uitableviewcell" should give you plenty of sample code, so I won't rehash that here.
This happens because placement of a button in a cell in your code is a one-way street: once a button is added to the cell, it is never removed or made invisible.
You need to add an else to your if statement to remove the button from the recycled cell, if that button exists.
Better yet, make the button a permanent part of the "Cell", and control its visibility in the same if statement: instead of adding the button, make it visible; instead of removing the button, make it invisible. As an added bonus, this would let you set up the visual appearance for your button in interface builder, and set some constraints, which would help avoiding "magic numbers" such as 100, 89, and 35 in the body of your code.
Looking at your code its clear you are already subclassing UITableViewCell and have custom UI, then its recommended add button in side UITableViewCell either in initialization or if you have xib for cell then add UIButton in xib (via interface builder) and create an IBOutlet for newly added button .
Now when a cell will be reused you will have that button as well, when cell is being reused its your responsibility to keep its state right (means updating its UI), for that override prepareForReuse method inside custom tableViewCell class and reset UI of cell, and in cellForRowAtIndexPath you can update UI as per new data object.