I am trying to add an UIButton to every cell in my UICollectionView when a button (outside of the UICollectionView) is pressed and then remove them when it's not.
Basically, a boolean if true - show/add, else hide/remove. This is my cellForItemAt. I also tried adding it to willDisplay cell.
let btnItemDelete = UIButton()
btnItemDelete.tag = indexPath.row
btnItemDelete.addTarget(self, action: #selector(self.btnItemDeleteClick), for: .touchUpInside) //Selector works
btnItemDelete.frame = CGRect(x: cell.bounds.width-22, y: 2, width: 20, height: 20) //Creation works
btnItemDelete.setImage(deleteImage, for: .normal) //Image works
if (isEdit) {
//Add or Show
cell.addSubview(btnItemDelete)
}
else {
//Delete or Hide
btnItemDelete.removeFromSuperview()
}
When running this, isEdit is initially set to false and the buttons do not show up. After clicking the button to change the boolean, the buttons appear. When clicking the button to set the boolean back to false, the buttons stay. I am figuring it's something with btnItemDelete.removeFromSuperview() - is there a different approach to do this? I figured I can't hide/show them because it's just going to keep adding a new button to the cell on every reload.
First you need to add to
cell.contentView not the cell
By this
btnItemDelete.removeFromSuperview()
you remove a button on the fly that's not added , instead you need
cell.contentView.subviews.forEach {
if $0.tag == 12 {
$0.removeFromSuperview()
}
}
I think the best approach is to add the button previously on the cell layout then manage it's appearance like
cell.myButton.isHidden = !isEdit
Related
I am trying to implement favorite and unfavorite functionality into my cell. Here, Initially I am showing in collection view cell button icon favorite (gray), its mean unfavorite but after clicked the button it will show green, its mean favorite. From JSON I am getting response which cells are already favorite and others unfavorite icon. Its working fine but when I click JSON favorite enabled green button it’s not changing gray by single click, its working fine double click but every click I am calling JSON so 3rd click it will be add again favorite.
Here, below my code -
var checked = false
#IBAction func favoritesTapped(_ sender: Any) {
if checked {
favbutton.setImage(UIImage(named: "item_grayfav.png"), for: .normal)
checked = false
delegate?.unfavoriteButtonPressed(cell: self)
} else {
favbutton.setImage(UIImage(named: "item_greenfav.png"), for: .normal)
checked = true
delegate?.favoriteButtonPressed(cell: self)
}
}
Above code I am using for button click to change image and also initially I fixed gray checked = false
Into collection view cell at item
if indexPath.row < cfavoritesIDData.count {
if let i = cfavoritesIDData[indexPath.item] {
cell.favbutton.setImage(UIImage(named: "item_greenfav.png"), for: .normal)
}
else {
print("id is nil")
cell.favbutton.setImage(UIImage(named: "item_grayfav.png"), for: .normal)
}
}
Issues: If I click JSON enabled green button (favorite), its calling to unfavorite well but button icon not changing green after 3’rd click only its changing gray.
You need to store your cell model in your View Controller, the cell should not have a checked boolean, when you are configuring your cell in cellForRow: you should tell it there wether it is checked or not.
When pressing the button you should have a delegate callback or closure to tell the View Controller that the cell at IndexPath 'x' has pressed its favourite button and check the model as to wether or not the reaction would be checked or not, then reconfigure the cell again.
You can use the UIButton's selected property instead of checked variable:
if indexPath.row < favoritesIDData.count {
if let i = cfavoritesIDData[indexPath.item] {
cell.favbutton.setImage(UIImage(named: "item_greenfav.png"), for: .normal)
cell.favbutton.setSelected = true
}
else {
print("id is nil")
cell.favbutton.setImage(UIImage(named: "item_grayfav.png"), for: .normal)
cell.favbutton.setSelected = false
}
}
#IBAction func favoritesTapped(_ UIButton: favButton) {
if favButton.isSelected {
favbutton.setImage(UIImage(named: "item_grayfav.png"), for: .normal)
checked = false
delegate?.unfavoriteButtonPressed(cell: self)
} else {
favbutton.setImage(UIImage(named: "item_greenfav.png"), for: .normal)
checked = true
delegate?.favoriteButtonPressed(cell: self)
}
}
Note: Don't copy paste change according to correct syntax.
I'd like to show a loading indicator when the user swipes the cell to the right, which triggers an asynchronous action. While this action is ongoing, I'd like to hide the label inside, keep the cell "inlined", not "re-swipeable" and show the already mentioned indicator.
This is what I got so far, and it is already partially working:
override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let action = UIContextualAction(style: .normal, title: "My Action") { (action, view, handler: #escaping (Bool) -> Void) in
// Getting UIButtonLabel, which is a subclass of UILabel
guard let label = view as? UILabel else {
return
}
// Not used right now
guard let superView = label.superview else {
return
}
// Try to hide the existing label
label.text = "" // Doesn't work
label.attributedText = nil // Doesn't work
label.textColor = UIColor.clear // Doesn't work
// Add a loading indicator at the position of the label, works!
let loadingIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.white)
loadingIndicator.translatesAutoresizingMaskIntoConstraints = false
loadingIndicator.startAnimating()
label.addSubview(loadingIndicator)
NSLayoutConstraint.activate([
loadingIndicator.centerXAnchor.constraint(equalTo: label.centerXAnchor),
loadingIndicator.centerYAnchor.constraint(equalTo: label.centerYAnchor),
loadingIndicator.heightAnchor.constraint(equalTo: label.heightAnchor, multiplier: 0.8),
loadingIndicator.widthAnchor.constraint(equalTo: loadingIndicator.heightAnchor)
])
self.viewModel.doAsyncAction(completionHandler: { (success) in
loadingIndicator.stopAnimating()
loadingIndicator.removeFromSuperview()
handler(true)
})))
}
action.backgroundColor = .darkGray
return UISwipeActionsConfiguration(actions:[action])
}
I'm able to add a loading indicator at the right position. I'm still not able to hide the label. Removing it or settings it's alpha or hidden state will not work, as the loading indicator will have to be a subview of it (for UITableView's internal positioning).
Also, I'm able to stop the user from pulling the action again by calling the callback handler late, but I'm not able to make the cell persist in that "one-level inline" swipe position. This is especially ugly when the user does not swipe the cell and click the button, but decides to swipe the cell all over to trigger the action. In this case I'd like the cell to automatically move to the "one level inline" position.
Does someone know a trick for this? I've seen this in some apps already. Is there a common UIKit addition for this kind of stuff?
One way I to go for I found out is disabling the superview, which is a subclass of a UIButton.
You can get it by
guard let superView = label.superview as? UIButton else {
return
}
and then disable it by doing
superView.isEnabled = false
label.isEnabled = false
label.isUserInteractionEnabled = false // Probably not required
This way, the label inside the button appears slightly faded. Also, it prevents the user from clicking the button multiple times.
It will not look so nice when the user "swipes to action", as the action button will remain at the slided position. But maybe this is something that can be tweaked easily, too?!
I'm new to iOS dev and I've been trying to toggle a button/checkbox in my tableviewcell by tapping the cell. My code is pretty naive, switching to the 'check' image works fine, but the issue is switching back to un-checked image when double tapping the cell.
What's not working in my code? Also is there a better way to do this? (perhaps via delegate funcs from the cell itself?)
Code examples would be very helpful! Thanks.
var isChecked:Bool = true
This following func is called in didSelectRowAtIndexPath func in my Tableview class.
let btnCheckBox:UIButton = cell.contactCheckbox
setState(button: btnCheckBox)
func setState(button: UIButton){
let check = UIImage(named: "check_green")
let unCheck = UIImage(named: "un_check_green")
if isChecked {
button.setImage(check, for: .normal)
isChecked = false
print("First Tap - Bool is \(isChecked)")
} else {
button.setImage(unCheck, for: .normal)
isChecked = true
print("Double Tap - Bool is \(isChecked)")
}
}
You should use UIButton , and set image for state of button, sample
check_green for selected
check_red for none
And each UITableviewcell must have an isSelected
I am programmatically creating cells and adding a delete button to each one of them. The problem is that I'd like to toggle their .hidden state. The idea is to have an edit button that toggles all of the button's state at the same time. Maybe I am going about this the wrong way?
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("verticalCell", forIndexPath: indexPath) as! RACollectionViewCell
let slide = panelSlides[indexPath.row]
cell.slideData = slide
cell.slideImageView.setImageWithUrl(NSURL(string: IMAGE_URL + slide.imageName + ".jpg")!)
cell.setNeedsLayout()
let image = UIImage(named: "ic_close") as UIImage?
var deleteButton = UIButton(type: UIButtonType.Custom) as UIButton
deleteButton.frame = CGRectMake(-25, -25, 100, 100)
deleteButton.setImage(image, forState: .Normal)
deleteButton.addTarget(self,action:#selector(deleteCell), forControlEvents:.TouchUpInside)
deleteButton.hidden = editOn
cell.addSubview(deleteButton)
return cell
}
#IBAction func EditButtonTap(sender: AnyObject) {
editOn = !editOn
sidePanelCollectionView.reloadData()
}
I think what you want to do is iterate over all of your data by index and then call cellForItemAtIndexPath: on your UICollectionView for each index. Then you can take that existing cell, cast it to your specific type as? RACollectionViewCell an then set the button hidden values this way.
Example (apologies i'm not in xcode to verify this precisely right now but this is the gist):
for (index, data) in myDataArray.enumerated() {
let cell = collectionView.cellForRowAtIndexPath(NSIndexPath(row: index, section: 0)) as? RACollectionViewCell
cell?.deleteButton.hidden = false
}
You probably also need some sort of isEditing Boolean variable in your view controller that keeps track of the fact that you are in an editing state so that as you scroll, newly configured cells continue to display with/without the button. You are going to need your existing code above as well to make sure it continues to work as scrolling occurs. Instead of creating a new delete button every time, you should put the button in your storyboard and set up a reference too and then you can just use something like cell.deleteButton.hidden = !isEditing
I need to add clear button in table view section header. It is exactly same like the one in notification center. So when user clicks it, 'x' will change to 'Clear'. If s/he clicks it again, app will perform action. But any click outside this button, 'Clear' will be back to 'x'
How can I make this? Thanks in advance
Unfortunately this does not come out of the box. You have to create your own custom section header view. That custom header view contains a UILabel on the left side. On the right side you put a UIButton that has the title "X". When the user taps on the button, change its title to "Clear" and its state to .Selected. Only clear your table view section when the button is tapped and its state is .Selected:
func setupButton() {
let button = UIButton()
button.setTitle("X", forState: .Normal)
button.addTarget(self, action: Selector("didPressButton:"), forControlEvents: .TouchUpInside)
addSubview(button)
}
func didPressButton(button: UIButton) {
if button.selected {
// clear your table view section
}
button.setTitle("Clear", forState: .Normal)
button.selected = true
}
To put the button back in its unselected state add a background UIView to your section header view and add a UITapGestureRecognizer to it:
func setupBackground() {
let background = UIView()
let recognizer = UITapGestureRecognizer(target: self, action: Selector("didTapOutside:"))
background.addGestureRecognizer(recognizer)
insertSubview(background, belowSubview: button)
}
func didTapOutside(recognizer: UITapGestureRecognizer) {
button.selected = false
button.setTitle("X", forState: .Normal)
}
To use your custom section header in your table view you implement the following UITableViewDelegate method:
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
// create your custom section header and return it
}