UIButton inside TableViewCell couldn't be set image after clicked - ios

Button inside UITableViewCell don't respond to setImage method inside its clicked method. Here is the cellForRowAt section for TableView.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell=tableView.dequeueReusableCell(withIdentifier: "BroadcastViewCell", for: indexPath) as!BroadcastViewCell
var show=self.week?[selectedIndex].shows?[indexPath.item]
cell.myLabel.text=show?.programName
if let resim=show?.thumbURL{
cell.myImage.downloadImage(from: resim)
}
cell.notifyButton.tag = indexPath.row
cell.notifyButton.setImage( UIImage(named:"icnAlarm"), for: .normal)
cell.notifyButton.setImage( UIImage(named:"icnAlarmCheck"), for: .selected)
if (self.notifications?.contains(where: {$0.identifier == (show?.airDate)!+(show?.airTime)!}))!
{
cell.notifyButton.isSelected=true
}else
{
cell.notifyButton.isSelected=false
}
cell.notifyButton.addTarget(self, action: #selector(buttonClicked(sender:)), for: .touchUpInside)
return cell;
}
And I just set its selected state inside the buttonClicked implementation.
func buttonClicked(sender:UIButton)
{
sender.isSelected=true
}
But the image doesn't change after it. It only changes after cellForRowAt method invocation for its particular row. I don't understand why it responds differently to the same code part. Thanks for any help.

You have to tell the button what to do:
func buttonClicked(sender:UIButton)
{
if myButton.isSelected {
myButton.isSelected = false
}else{
myButton.isSelected = true
}
}
configure your button to be custom:
let myButton = UIButton(type: .custom)
Or in Attributes Inspector:

Related

How to make only one button clickable in table view cell using swift

I have a table view with buttons in each of the cell. Each of the button playing different song for each of the cell and change image to "play" or "pause". But I have a problem, when I tap on two or three of buttons, they changes photo to "pause". It should change photo only on one of them. Check photo: Buttons in cell
There is my code in view controller:
extension BeatPackViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 12
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: CustomLoopsCell = beatTableView.dequeueReusableCell(withIdentifier: "firstLoopCell", for: indexPath) as! CustomLoopsCell
gettingSongName()
cell.loopNameLabel.text = data[indexPath.row].loop_name
cell.producerLabel.text = data[indexPath.row].producer
cell.instrumentLabel.text = data[indexPath.row].Instrument
cell.delegate = self
cell.selectionStyle = .none
cell.tag = indexPath.row
if let playingCell = currentPlayingIndex {
if playingCell == indexPath.row {
cell.playButtonOutlet.setImage(UIImage(named: "Pause.png"), for:
.normal)
}
} else {
cell.playButtonOutlet.setImage(UIImage(named: "playBtn.png"), for:
.normal)
}
// cell.instrumentLabel.text = data[indexPath.row].loops[indexPath.row].Instrument
// cell.producerLabel.text = data[indexPath.row].loops[indexPath.row].producer
return cell
}
func btnUseTap(cell: CustomLoopsCell) {
let indexPath = self.beatTableView.indexPath(for: cell)
if currentPlayingIndex == cell.tag {
audioPlayer.pause()
currentPlayingIndex = nil
beatTableView.reloadData()
} else { //IF PAUSE BUTTON
playLoop(song_name: songs[cell.tag])
currentPlayingIndex = cell.tag
beatTableView.reloadData()
}
beatTableView.reloadData()
// playSong(index: indexPath!.row)
print("Done")
}
1)Firstly create empty array of your buttons, for example:
let allButtons: [UIButton] = []
2)When you are creating each cell, add button of that cell to array , Example code:
allButtons.append(yourButton)
3)create function that will mute all buttons and also assigning pause image to them, for example:
func muteAllButtons() {
for button in allButtons {
button.muteThisButton()
button.setImageToPlay()
}
}
create function that will handle muting all buttons, and then playing music from selected button, for example:
func userSelectedButton(at yourSelectedCellIndex: Int) {
muteAllButtons()
let currentPlayingButton = allButtons[yourSelectedCellIndex]
currentPlayingButton.playMusic()
currentPlayingButton.setImageToPause()
}
when user clicks on selected cell, call userSelected function. For example:
userSelectedButton(at: yourCellIndex)
Looks like you have problem in your if:
if let playingCell = currentPlayingIndex {
if playingCell == indexPath.row {
cell.playButtonOutlet.setImage(UIImage(named: "Pause.png"), for:
.normal)
}
} else {
cell.playButtonOutlet.setImage(UIImage(named: "playBtn.png"), for:
.normal)
}
When currentPlayingIndex != nil and playingCell != indexPath.row, it doesn't update the image, so it gets random image from dequeued cell. Change it to:
if let playingCell = currentPlayingIndex, playingCell == indexPath.row {
cell.playButtonOutlet.setImage(UIImage(named: "Pause.png"), for:
.normal)
} else {
cell.playButtonOutlet.setImage(UIImage(named: "playBtn.png"), for:
.normal)
}
Also you have redundant reloadDatas here:
if currentPlayingIndex == cell.tag {
audioPlayer.pause()
currentPlayingIndex = nil
beatTableView.reloadData()
} else { //IF PAUSE BUTTON
playLoop(song_name: songs[cell.tag])
currentPlayingIndex = cell.tag
beatTableView.reloadData()
}
beatTableView.reloadData()
Just remove both from if/else and left one after the if/else.

How to save tableviewcell state?

I am successfully saving the state of tableviewcell by saving the state of cell to dictionary of [IndexPath:Bool] , everything is working fine but problem is when I check mark any cell , a cell from bottom also get strokethrough effect . Only selected cell is supposed to be check marked and strikethrough. Check the image in which bottom is not selected but getting strike through effect , How to resolve this ?
TableViewCell code
let checkedImage = (UIImage(named: "success")?.withRenderingMode(UIImage.RenderingMode.alwaysOriginal))! as UIImage
let uncheckedImage = (UIImage(named: "verified")?.withRenderingMode(UIImage.RenderingMode.alwaysOriginal))! as UIImage
var isChecked: Bool = false {
didSet{
if isChecked {
tableRadioButton.setImage(checkedImage, for: .normal)
lblItemName.strikeThrough(true)
lblItemQty.strikeThrough(true)
} else {
tableRadioButton.setImage(uncheckedImage, for: .normal)
lblItemName.strikeThrough(false)
lblItemQty.strikeThrough(false)
}
}
}
#IBAction func RadioButtonTapped(_ sender: UIButton) {
isChecked = !isChecked
radioButtonTapAction?(isChecked)
}
Code of cell for row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomOrderDetailCell
cell.setCellData(itemDetail: arrForProducts[indexPath.section].items![indexPath.row],currency: self.currency)
let selected = selectedButtonIndex[indexPath] ?? false
cell.isChecked = selected
cell.radioButtonTapAction = {
(checked) in
selectedButtonIndex[indexPath] = checked
print(checked)
}

Changing button action on runtime pushing view controller weirdly

My code is so complex so Im gonna minimalize it a little bit.
I have a tableviewController that has 2 cell and a button in view.(Button not in cell).
I'm changing button action according to selected cell :
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0{
self.botButton.addTarget(self, action: #selector(self.showA), for: .touchUpInside)
else{
self.botButton.addTarget(self, action: #selector(self.showB), for: .touchUpInside)
}
botButton is my button outlet
This is my action buttons :
#objc func showA(){
let showParcelsViewController = self.storyboard?.instantiateViewController(withIdentifier: "showA") as! showAVC
self.navigationController?.pushViewController(showParcelsViewController, animated: true)
}
#objc func showB(){
let decribeland = self.storyboard?.instantiateViewController(withIdentifier: "showB") as! showBVC
self.navigationController?.pushViewController(decribeland, animated: true)
}
When page load, If I select a row and then tap button , Its perfecly worked.But , For example, If I select 1.row then change to selected row to 2.row and tap button , View pushes First row's
viewcontroller (showAVC) and then pushes Second row's viewcontroller (showBVC) quickly.
How can I fix it?
Target keeps on adding-up as you keep selecting the rows you need to remove the previous target when you add a new one:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
botButton.removeTarget(self, action: #selector(self.showB), for: .touchUpInside)
botButton.addTarget(self, action: #selector(self.showA), for: .touchUpInside)
} else {
botButton.removeTarget(self, action: #selector(self.showA), for: .touchUpInside)
botButton.addTarget(self, action: #selector(self.showB), for: .touchUpInside)
}
}
Here is another way.
Set the button tag to indexPath.row in didSelectRowAt
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
self.botButton.tag = indexPath.row
}
Then use the button tag to know which controller to present.
#IBAction func showBtnPressed(sender : UIButton) { //
let index = sender.tag
if index == 0 {
let showParcelsViewController = self.storyboard?.instantiateViewController(withIdentifier: "showA") as! showAVC
self.navigationController?.pushViewController(showParcelsViewController, animated: true)
} else {
let decribeland = self.storyboard?.instantiateViewController(withIdentifier: "showB") as! showBVC
self.navigationController?.pushViewController(decribeland, animated: true)
}
}

How to get the get a index.row and index section from a UITableViewCell with an UIStepper programmatically using Swift 4 [duplicate]

I have table view cells like quiz. And in each cell I have a buttons And how can I identify in which cell button was pressed. Maybe by IndexPath???
This is how I connected button to
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionCell")!
variant1 = cell.contentView.viewWithTag(1) as! UIButton
variant2 = cell.contentView.viewWithTag(2) as! UIButton
variant3 = cell.contentView.viewWithTag(3) as! UIButton
variant4 = cell.contentView.viewWithTag(4) as! UIButton
variant1.addTarget(self, action: #selector(self.variant1ButtonPressed), for: .touchUpInside)
variant2.addTarget(self, action: #selector(self.variant2ButtonPressed), for: .touchUpInside)
variant3.addTarget(self, action: #selector(self.variant3ButtonPressed), for: .touchUpInside)
variant4.addTarget(self, action: #selector(self.variant4ButtonPressed), for: .touchUpInside)
return cell
}
func variant1ButtonPressed() {
print("Variant1")
variant1.backgroundColor = UIColor.green
}
func variant2ButtonPressed() {
print("Variant2")
variant2.backgroundColor = UIColor.green
}
func variant3ButtonPressed() {
print("Variant3")
variant3.backgroundColor = UIColor.green
}
func variant4ButtonPressed() {
print("Variant4")
variant4.backgroundColor = UIColor.green
}
This is how it looks like in Storyboard:
You should use delegate pattern, basic example:
protocol MyCellDelegate {
func didTapButtonInside(cell: MyCell)
}
class MyCell: UITableViewCell {
weak var delegate: MyCellDelegate?
func buttonTapAction() {
delegate?.didTapButtonInside(cell: self)
}
}
class ViewController: MyCellDelegate {
let tableView: UITableView
func didTapButtonInside(cell: MyCell) {
if let indexPath = tableView.indexPath(for: cell) {
print("User did tap cell with index: \(indexPath.row)")
}
}
}
Use this line to get indexPath, Where you have to pass UIButton on target selector
func buttonTapped(_ sender:AnyObject) {
let buttonPosition:CGPoint = sender.convert(CGPointZero, to:self.tableView)
let indexPath = self.tableView.indexPathForRow(at: buttonPosition)
}
Since actions need to be inside the view controller, ctrl + drag from your button to the view controller - this will use the responder chain.
Basically you need to convert the view (button) to the coordinate system of the table view in order to tell what is the IndexPath and if you have the IndexPath you have the object that corresponds to the button inside the cell that was tapped:
#IBAction func buttonTapped(_ sender: Any) {
if let indexPath = indexPath(of: sender) {
// Your implementation...
}
}
private func indexPath(of element:Any) -> IndexPath? {
if let view = element as? UIView {
// Converting to table view coordinate system
let pos = view.convert(CGPoint.zero, to: self.tableView)
// Getting the index path according to the converted position
return tableView.indexPathForRow(at: pos) as? IndexPath
}
return nil
}
It is important to mention that there many solutions for your question. But you should know that in Apple's sample projects they also use this technic.
This is how you add tag to a UIButton inside UITableView, add below lines of code in
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
cell.yourButton.tag = indexPath.row
cell.yourButton.addTarget(self, action:#selector(btnPressed(sender:)), for: .touchUpInside)
Add this function in your ViewController
func btnPressed(sender: UIButton)
{
print("Button tag \(sender.tag)")
}
Hope this helps...
Simple Subclass button just like JSIndexButton
class JSIndexButton : UIButton {
var indexPath : IndexPath!
}
Now at cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ItemCell
let itemCategory = dataList[button.indexPath.section];
let item = itemCategory.items[button.indexPath.row];
cell.imgView.setImageWithURL(item.photoUrl);
cell.btnBuy.indexPath = indexPath;
cell.btnBuy.addTarget(self, action: #selector(JSCollapsableTableView.btnBuyPressed(_:)), for: UIControlEvents.touchUpInside)
return cell;
}
Check Button Action
#IBAction func btnBuyPressed(_ button: JSIndexButton) {
let itemCategory = dataList[button.indexPath.section];
let item = itemCategory.items[button.indexPath.row];
}
#objc func ItemsDescription(_ sender: UIButton?,event: AnyObject?) {
let touches: Set<UITouch>
touches = (event?.allTouches!)!
let touch:UITouch = (touches.first)!
let touchPosition:CGPoint = touch.location(in: self.tableView)
let indexPath:NSIndexPath = self.tableView.indexPathForRow(at: touchPosition)! as NSIndexPath
}
adding target
cell.ItemsDescription.addTarget(self, action: #selector(ItemsDescription(_:event:)), for: UIControlEvents.touchUpInside)

Want accessory checkmark to show only when tapped on the right

I have a TableView with cells that when pressed anywhere in the cell, it adds a checkmark on the right. I only want the checkmark to show up if the cell is tapped on the right side. Here's the pertinent section of code from the TableViewController:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TaskCell", for: indexPath) as! TaskCell
let task = tasks[indexPath.row]
cell.task = task
if task.completed {
cell.accessoryType = UITableViewCellAccessoryType.checkmark;
} else {
cell.accessoryType = UITableViewCellAccessoryType.none;
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
var tappedItem = tasks[indexPath.row] as Task
tappedItem.completed = !tappedItem.completed
tasks[indexPath.row] = tappedItem
tableView.reloadRows(at: [indexPath], with: UITableViewRowAnimation.none)
}
}
Is there a simple way to do that, or to do it using storyboard? My Swift skills leave a LOT to be desired. Any help would be appreciated! Thank you!
Instead of the built-in checkmark accessory type, why not provide, as accessory view, an actual button that the user can tap and that can display the checkmark? The button might, for example, display as an empty circle normally and as a circle with a checkmark in it when the user taps it.
Otherwise, you're expecting the user to guess at an obscure interface, whereas, this way, it's perfectly obvious that you tap here to mark the task as done.
Example:
To accomplish that, I created a button subclass and set the accessoryView of each cell to an instance of it:
class CheckButton : UIButton {
convenience init() {
self.init(frame:CGRect.init(x: 0, y: 0, width: 20, height: 20))
self.layer.borderWidth = 2
self.layer.cornerRadius = 10
self.titleLabel?.font = UIFont(name:"Georgia", size:10)
self.setTitleColor(.black, for: .normal)
self.check(false)
}
func check(_ yn:Bool) {
self.setTitle(yn ? "✔" : "", for: .normal)
}
override init(frame:CGRect) {
super.init(frame:frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The title of the button can be the empty string or a checkmark character, thus giving the effect you see when the button is tapped. This code comes from cellForRowAt::
if cell.accessoryView == nil {
let cb = CheckButton()
cb.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
cell.accessoryView = cb
}
let cb = cell.accessoryView as! CheckButton
cb.check(self.rowChecked[indexPath.row])
(where rowChecked is an array of Bool).
You will have to define your own accessory button, and handle its own clicks.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TaskCell", for: indexPath) as! TaskCell
let task = tasks[indexPath.row]
cell.task = task
let checkButton = UIButtonSubclass()
...configure button with your circle and check images and a 'selected property'...
checkButton.addTarget(self, action:#selector(buttonTapped(_:forEvent:)), for: .touchUpInside)
cell.accessoryView = checkButton
checkButton.selected = task.completed //... this should toggle its state...
return cell
}
func buttonTapped(_ target:UIButton, forEvent event: UIEvent) {
guard let touch = event.allTouches?.first else { return }
let point = touch.location(in: self.tableview)
let indexPath = self.tableview.indexPathForRow(at: point)
if let task = tasks[indexPath.row] {
task.completed = !task.completed
}
tableView.reloadData() //could also just reload the row you tapped
}
Though, it has been noted that using tags to detect which row was tapped is dangerous if you start to delete rows. You can read more here https://stackoverflow.com/a/9274863/1189470
EDITTED
Removed the reference to tags per #matt

Resources