Change the color of UIButton in IBOutletCollection - swift

I have an IBOutletCollection of UIButton:
#IBOutlet var buttons: [UIButton]!
and a function for a tap:
#IBAction func button_Tap(_ sender: UIButton) {
for button in buttons {
if button.tag == sender.tag { button.backgroundColor = .blue }
else {button.backgroundColor = .white}
}
}
The outlets are connected in a storyboard.
I need to change the color of the buttons on a button tap. When tap on the first button, it should turn to the blue color and other buttons should be white.
My code does not work correctly. When I tap on the button it turns blue color. But when I tap on another button, the first button does not change the color to white.
Can someone help me to solve this problem? Thanks.
Update:
I also have this code in class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource:
let cellIdentifiers:[String] = ["FirstPercentCell", "SecondPercentCell", "ThirdPercentCell", "FourthPercentCell", "FifthPercentCell"]
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cellIdentifiers.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let collectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifiers[indexPath.item], for: indexPath)
return collectionCell
}
So maybe the problem in this part?

Try this code:
#IBOutlet var buttons: [UIButton]!
var lastTappedButton: UIButton?
#IBAction func button_Tap(_ sender: UIButton) {
for button in buttons {
if let lastTappedButton = lastTappedButton, lastTappedButton != sender {
button.backgroundColor = .white
}
if button.tag == sender.tag {
button.backgroundColor = .blue
lastTappedButton = button
}
}
}

For this to work
1- All buttons should be connected to
#IBOutlet var buttons: [UIButton]!
2- All buttons should be connected to
#IBAction func button_Tap(_ sender: UIButton)
3- Every button should have a different tag for example 0,1,2
4- You can use this
#IBAction func button_Tap(_ sender: UIButton) {
self.buttons.forEach { $0.backgroundColor = $0 === sender ? .blue : .white }
}
Result

Related

Can I pass an image on the SecondViewController to the FirstViewController?

From First ViewController I press a Button and open a SecondViewController that view a collection of Image, when I tap on image the view show a full screen image and return at the collection View. Can I pass the image to the first (starting) viewcontroller to use it as a background?
class SecondViewController: UIViewController {
#IBAction func returnHome(_ sender: UIButton) {
//self.dismiss(animated: true, completion: nil)
self.performSegue(withIdentifier: "backToHome", sender: nil)
}
var images = ["bg13", "bg11", "bg6", "bg10", "bg12", "bg5", "bg3", "bg4", "bg2", "bg7", "bg8", "bg9", "bg1", "bg14"]
// ...
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let imageViewCollection = UIImageView(image:UIImage(named: images [indexPath.item]))
imageViewCollection.frame = self.view.frame
imageViewCollection.backgroundColor = .black
imageViewCollection.contentMode = .top
imageViewCollection.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissFullscreenImage))
imageViewCollection.addGestureRecognizer(tap)
imageViewCollection.addGestureRecognizer(tap)
imageViewCollection.isUserInteractionEnabled = true
self.view.addSubview(imageViewCollection)
}
#objc func dismissFullscreenImage(_ sender: UITapGestureRecognizer) {
sender.view?.removeFromSuperview()
}
}
You can use Delegate to pass the image to the first view controller.
Write this protocol on the top of the Second View Controller.
protocol SecondViewControllerDelegate: NSObject {
func sendToFirstViewController(image: UIImage)
}
Then create a variable inside Second View Controller
class SecondViewController: UIViewController {
....
weak var customDelegate: SecondViewControllerDelegate?
....
}
Call this from collectionView:didSelectItemAt:indexPath method
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let image = UIImage(named: images [indexPath.item])
customDelegate?.sendToFirstViewController(image: image)
...
}
In the First viewController, in where you instantiate the second view controller, put this line
secondVc.customDelegate = self
then create an extension to implement the delegate method
extension FirstViewController: SecondViewControllerDelegate {
func sendToFirstViewController(image: UIImage){
// use this image as your background
}
}

Parsing data from a collectionview Cell to a new viewController using a button in the CollectionViewCell

I have a collectionView that I am populating using an array I created in a class. The collectionView works but the problem is when I want to segue to another viewController using a button in the collectionViewCell as well as parsing specific data from that cell to the viewController.
An example of what I am trying to do is if I press the button in the cell for the question image I want to segue to the next viewController and I want that viewController's image to be the question image, and maybe i the future add some other data corresponding to it.
I have looked at the other answers on this site and other sites, which got me this far, but there is nothing that I can find to fix this problem for a collectionView. I know that it is similar to a tableView, but when I try and change it based on a tableView, but it doesn't seem to work for me.
This is the image of what the first and second views look like
This is the class i created:
import Foundation
import UIKit
class Information {
var image = ""
var button = ""
var infoOne = ""
var infoTwo = ""
var price = ""
init(image: String, button: String, infoOne: String, infoTwo: String, price: String) {
self.image = image
self.button = button
self.infoOne = infoOne
self.infoTwo = infoTwo
self.price = price
}
//MARK: Carousel Data
static func createData() -> [Information] {
return [
Information(image: "question", button: "Buy One", infoOne: "First Data", infoTwo: "Second Data", price: "10"),
Information(image: "exclamation", button: "Buy Two", infoOne: "First Data", infoTwo: "Second Data", price: "20"),
Information(image: "period", button: "Buy Three", infoOne: "First Data", infoTwo: "Second Data", price: "30"),
Information(image: "all", button: "Buy Four", infoOne: "First Data", infoTwo: "Second Data", price: "40")
]
}
}
This array works just fine when it populates the collectionViewCell.
This is the collectionViewCell
import UIKit
class InfoCollectionViewCell: UICollectionViewCell {
var infomation: Information! {
didSet {
updateUI()
}
}
var addBtnAction : (()-> ())?
#IBOutlet weak var infoImage: UIImageView!
#IBOutlet weak var infoLblOne: UILabel!
#IBOutlet weak var infoLblTwo: UILabel!
#IBOutlet weak var priceLbl: UILabel!
#IBOutlet weak var buyBtn: UIButton!
fileprivate func updateUI() {
infoImage.image! = UIImage(named: infomation.image)!
infoLblOne.text = infomation.infoOne
infoLblTwo.text = infomation.infoTwo
priceLbl.text = infomation.price
buyBtn.setTitle(infomation.button, for: .normal)
}
#IBAction func buyBtnPressed(_ sender: Any) {
print("INFORMATION: \(infomation.image)")
addBtnAction?()
}
override func layoutSubviews() {
super.layoutSubviews()
self.layer.cornerRadius = 5.0
self.clipsToBounds = true
}
}
this is the viewController:
import UIKit
class ViewController: UIViewController {
let collectionViewCellId: String = "carouselCollectionCell"
#IBOutlet weak var collectionView: UICollectionView!
fileprivate var information = Information.createData()
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.delegate = self
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return information.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "infoCell", for: indexPath) as! InfoCollectionViewCell
cell.addBtnAction = {
self.performSegue(withIdentifier: "mainSegue", sender: self)
}
cell.infomation = self.information[indexPath.item]
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "mainSegue" {
let nxtVC = segue.destination as? DetailViewController
let cell = sender as! InfoCollectionViewCell
let myIndexPath = self.collectionView.indexPath(for: cell)
nxtVC?.infomation = information[(myIndexPath?.item)!]
}
}
}
So when I run the app everything is fine until I press the button to segue and parse data. The app crashes and this is what it is saying is the cause:
This is what is in the output console:
Could not cast value of type 'carousel.ViewController' (0x107992178) to 'carousel.InfoCollectionViewCell' (0x107991f98).
2018-09-28 21:22:52.116698-0400 carousel[1573:38999] Could not cast value of type 'carousel.ViewController' (0x107992178) to 'carousel.InfoCollectionViewCell' (0x107991f98).
(lldb)
I would prefer to keep the button if at all possible, but if it is not possible I will change that. I am not sure what it is that I am doing wrong. If there is anything else that I can help with please let me know. Thank you very much.
EDIT:
It seems that I forgot to include one of the important views, the one for the other viewController that is segued to.
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
this is the view that i segue to:
import UIKit
class DetailViewController: UIViewController {
#IBOutlet weak var infoImage: UIImageView!
#IBOutlet weak var dataTxtView: UITextView!
var testingImage: UIImage = UIImage()
var infomation: Information! {
didSet {
updateUI()
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
fileprivate func updateUI() {
let images = UIImage(named: infomation.image)
infoImage.image = images
print("Image: \(infomation.image)")
}
}
In the updateUI function I tried to make the infoImage the same as infomation.image but it crashes and states:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
This doesn't make much sense to me because I tried to print what the string of infomation.image is, and it worked, but the moment I try and put it as an image it seems to crash.
Thank you again
EDIT2:
I figured out what I was doing wrong. It seems that I was always trying to use an optional string. How i fixed it is that I changed the class I made and instead of a string and turn that into an image it is already an image. I also tried to create an if let statement to remove the optional.
This is what the prepareForSegue looks like in the main ViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "mainSegue" {
let nxtVC = segue.destination as? DetailViewController
let cell = sender as? InfoCollectionViewCell
let myIndexPath = self.collectionView.indexPath(for: cell!)
if let indexPath = myIndexPath {
nxtVC?.infomation = information[indexPath.row]
}
}
}
this is what the viewDidLoad looks like:
override func viewDidLoad() {
super.viewDidLoad()
print("***********TESTINGIMAGE: \(infomation.image!)***********")
infoImage.image = infomation.image!
}
I know that I am unwrapping the image with an ! but i don't know how to remove the optional otherwise.
Thank you for the Help.
The problem is that you are assuming that the sender in prepare is an instance of the cell but your call to performSegue is passing self as the sender. But self in that case is the view controller, not the cell.
The solution is to pass the cell instead of self.
cell.addBtnAction = {
self.performSegue(withIdentifier: "mainSegue", sender: cell)
}

Change UIButton Color in CollectionViewCell [duplicate]

This question already has answers here:
Change the color of UIButton in IBOutletCollection
(2 answers)
Closed 4 years ago.
I have two classes: ViewController and PercentCollectionViewCell.
When I tap on a button1_Tap the color is changed to blue. When I tap on a button2_Tap, the color of the second button turns blue, but the color of the first button does not turn back to white color.
PercentCollectionViewCell class:
class PercentCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var firstPercent: UIButton!
#IBOutlet weak var secondPercent: UIButton!
}
ViewController class:
let cellIdentifiers:[String] = ["FirstPercentCell", "SecondPercentCell"]
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cellIdentifiers.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let collectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifiers[indexPath.item], for: indexPath) as? PercentCollectionViewCell
collectionCell?.firstPercent?.addTarget(self, action: #selector(button1_Tap(_:)), for: .touchUpInside)
collectionCell?.secondPercent?.addTarget(self, action: #selector(button2_Tap(_:)), for: .touchUpInside)
return collectionCell!
}
#objc func button1_Tap(_ sender: UIButton) {
let firstPercent = sender
firstPercent.isSelected = true;
firstPercent.backgroundColor = UIColor.blue
secondPercent.isSelected = false;
secondPercent.backgroundColor = UIColor.white
percentage = 0.05
}
#objc func button2_Tap(_ sender: UIButton) {
let firstPercent = sender
firstPercent.isSelected = false;
firstPercent.backgroundColor = UIColor.white
secondPercent.isSelected = true;
secondPercent.backgroundColor = UIColor.blue
percentage = 0.1
}
Put your button_Tap function in your UICollectionViewCell subclass.
You can get by with just one button_Tap action. Hook both buttons to this function:
class PercentCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var firstPercent: UIButton!
#IBOutlet weak var secondPercent: UIButton!
#objc func button_Tap(_ sender: UIButton) {
let buttonSelected = sender
let buttonUnselected = (sender === firstPercent) ? secondPercent : firstPercent
buttonSelected.isSelected = true
buttonSelected.backgroundColor = .blue
buttonUnselected.isSelected = false
buttonUnselected.backgroundColor = .white
percentage = (sender === firstPercent) ? 0.05 : 0.1
}
}
Then either wire the buttons up in the Storyboard or set them up like this in cellForItemAt:
collectionCell?.firstPercent?.addTarget(collectionCell!, action: #selector(PercentCollectionViewCell.button_Tap(_:)), for: .touchUpInside)
collectionCell?.secondPercent?.addTarget(collectionCell!, action: #selector(PercentCollectionViewCell.button_Tap(_:)), for: .touchUpInside)
I'm sorry, I forgot to clarify that I have the value of percentage for
each button tap functions. So I think I need a few functions. Also in
the future, there will be more buttons (up to 10).
In that case, assign your buttons to an #IBOutlet collection and assign unique tag values in the range 0...9. Connect each button to the outlet collection in the Storyboard.
#IBOutlet var buttons: [UIButton]!
Then
#objc func button_Tap(_ sender: UIButton) {
buttons.forEach { button in button.backgroundColor = .white }
sender.backgroundColor = .blue
percentage = [0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5][sender.tag]
}
Make sure to assign the button_Tap action to each of your buttons.

Updating button events in Swift?

I am creating a Check List app and trying to update button event which is set images itself after clicked.
Here is my code:
protocol CustomeCellDelegate {
func cell(cell: UITableViewCell, updateTheButton button: UIButton)
}
class Cells: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var checkBox: UIButton!
var buttonPressed = true
#IBAction func checkBoxAction(_ sender: UIButton) {
if buttonPressed {
sender.setImage(UIImage(named: "checkBoxB"), for: UIControlState.normal)
buttonPressed = false
} else {
sender.setImage(UIImage(named:""), for: UIControlState.normal)
buttonPressed = true
}
}
#objc func recordButtonImage(_ button: UIButton) {
cellDelegate?.cell(cell: self, updateTheButton: button) // cell notify the button that touched.
}
override func awakeFromNib() {
checkBox.addTarget(self, action: #selector(recordButtonImage(_:)), for: UIControlEvents.touchUpInside)
}
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate, CustomeCellDelegate {
#IBOutlet weak var tableView: UITableView!
var myImages: [UIImage?]!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
myImages = Array(repeatElement(nil, count: 50))
let nib = UINib(nibName: "Cells", bundle: nil)
tableView.register(nib, forCellReuseIdentifier: "Cells")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 50
}
internal func tableView(_ tableView:UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "theCells") as? Cells
cell?.theTextField.delegate = self
cell?.cellDelegate = self
cell?.checkBox.setImage(myImages[indexPath.row], for: UIControlState.normal)
return cell!
}
// This function from protocol and it helps to update button images.
func cell(cell: UITableViewCell, updateTheButton button: UIButton) {
print("Delegate method Update Button Images.")
if let indexPath = tableView.indexPath(for: cell), let buttonImage = button.image(for: UIControlState.normal) {
myImages[indexPath.row] = buttonImage
}
}
I would like to update both events of the button that set image after checked and unchecked. Therefore, my table view can dequeue Reusable Cell and have those events updated.
But the result is just one event of button which is checked updated but not the unchecked one. The checked button still repeats whatever I do to uncheck it.
I think you don't need recordButtonImage method, you should call the delegate method from the button tapped action i.e. checkBoxAction, just like below
class Cells: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var checkBox: UIButton!
var buttonPressed = true
#IBAction func checkBoxAction(_ sender: UIButton) {
if buttonPressed {
sender.setImage(UIImage(named: "checkBoxB"), for: UIControlState.normal)
buttonPressed = false
} else {
sender.setImage(UIImage(named:""), for: UIControlState.normal)
buttonPressed = true
}
// cell notify the button that touched.
cellDelegate?.cell(cell: self, updateTheButton: button)
}
}
Also your data source method, doesn't tell the cell whether the button was pressed or not. It just sets the image but doesn't set the variable buttonPressed
internal func tableView(_ tableView:UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "theCells") as? Cells
cell?.theTextField.delegate = self
cell?.cellDelegate = self
cell?.checkBox.setImage(myImages[indexPath.row], for: UIControlState.normal)
return cell!
}
The best way to handle UIButton is using its own state.
You need to hold current state value in one array. So you can keep the state in cellForRow and other things. By default set all state to false.
In CellForRow, just put below code:
YOUR_BUTTON.isSelected = aryState[indexPath.row] as! Bool
Set image for your button
YOUR_BUTTON.setImage(UIImage(named: NORMAL_IMAGE), for: .normal)
YOUR_BUTTON.setImage(UIImage(named: SELECTED_IMAGE), for: .selected)
Just change button state when it clicked:
#IBAction func checkBoxAction(_ sender: UIButton) {
let button = sender
button.isSelected = !button.isSelected
}

How to tell if button in collection view header is selected when populating collection view?

How do I tell if I button in the collection view header is selected when populating my collection view? I have 2 tabs in the header which determine which data I populate the collection view with so I want to be able to switch the data and reload the collection view when the user selects a different tab.
some code from header class as requested...I don't see the point though it's just a button. I want to see if this button is selected while populating the cells and cell count etc.
class UserSearchHeader: UICollectionReusableView {
#IBOutlet weak var friendsButton: UIButton!
#IBOutlet weak var peopleButton: UIButton!
#IBOutlet weak var customSlider: UIView!
override func awakeFromNib() {
super.awakeFromNib()
self.friendsButton.selected = true
customSlider.layer.cornerRadius = customSlider.frame.height / 2
}
#IBAction func friendsButtonTapped(sender: AnyObject) {
if self.friendsButton.selected == false {
self.friendsButton.selected = true
self.peopleButton.selected = false
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.customSlider.frame.origin.x = 0
})
}
}
UICollectionviewHeader + UI Button
UIcollectionviewHeader are using dequeueReusableSupplementaryView and for some reason it created a UIView Infront of every Headerview, this would block all gesture that are happening. the solution is to bring the view to front like so:
override func awakeFromNib() {
super.awakeFromNib()
self.bringSubview(toFront: friendsButton) // this depends on you .xib
}
this will solve your gesture issue.
Theres Multiple way to Create a UIButton Action inside a CollectionViewHeader.
AddTarget (as Answered by #derdida)
Drag and Drop Action In CollectionViewHeader Class.
Add Target :-(refer to derdida answer)
inside of viewForSupplementaryElementOfKind// ps: you need to register your CollectionView Class & Nib
Add Target to UIButton like so.
header.friendsButton.tag = 0
header.friendsButton.addTarget(self, action: #selector(self.HeaderClick(_:)), for: .touchUpInside)
Created the Function like so.
#objc func HeaderClick1(_ sender : UIButton){ print("sender.tag \(sender.tag)") }//sender.tag 0
Drag and Drop Action In CollectionViewHeader Class.
for this example i will used #Wayne Filkins Question.
Ctrl + Drag UIButton To Header Class
Select Connection Type to Action create the function like so
#IBAction func friendsButtonTapped(_ sender: Any) { print("something from friendsButtonTapped") }
and thats it.
but the main key here is to get the view to be to front. used debug view hierarchy to know more.
Someone can fixed my code view please!
Update Solution
Using UIButtonSetOnClickListener.Swift and heres the code:
extension UIControl {
func setOnClickListener(for controlEvents: UIControl.Event = .primaryActionTriggered, action: #escaping () -> ()) {
removeTarget(nil, action: nil, for: .allEvents)
let sleeve = ClosureSleeve(attachTo: self, closure: action)
addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for:
controlEvents)
}
}
class ClosureSleeve {
let closure: () -> ()
init(attachTo: AnyObject, closure: #escaping () -> ()) {
self.closure = closure
objc_setAssociatedObject(attachTo, "[\(arc4random())]", self, .OBJC_ASSOCIATION_RETAIN)
}
#objc func invoke() {
closure()
}
}
Here how to used is:
Add Target :-(refer to derdida answer)
inside of viewForSupplementaryElementOfKind// ps: you need to register your CollectionView Class & Nib
header.friendsButton.setOnClickListener {
//put your code here
}
There are 2 delegate methods who are important to decide which items will be shown. For example:
You have 2 different items, they are populated in:
let items1 = [Item]()
let items2 = [Item]()
Then you have a variable, that holds which items should be shown:
let items1Shown:Bool = true
Now implement the delegate methods with something like:
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if(items1Shown == true) {
return items1.count
} else {
return items2.count
}
}
And
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var item:Item!
if(items1Shown == true) {
item = items1[indexPath.row]
} else {
item = items1[indexPath.row]
}
// format your cell
}
And implement any button function
func ChangeItems() {
if(items1Shown == true) {
items1Shown = false
} else {
items1Shown = true
}
// reload your collectionView
self.collectionView.reloadData()
}
Edit:
Why not giving your button a target? (Add this where you dequeue your headerView!)
headerView.friendsButton.addTarget(self, action: #selector(YourViewControllerWithFunction.cellButtonClick(_:)), forControlEvents: .TouchUpInside)
// for example:
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
let headerView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: headerId, forIndexPath: indexPath)
headerView.friendsButton.addTarget(self, action: #selector(YourViewControllerWithFunction.cellButtonClick(_:)), forControlEvents: .TouchUpInside)
return headerView
}
// Class YourViewControllerWithFunction
func cellButtonClick(button: UIButton) {
// you can do anything with that button now
}