Change Button image from view controller inside custom header cell - swift

I have a VC that is UICollectionView that has header. This header is a custom cell, and inside that cell there is an button with image which I need to change based on info from API or user tap.
In func loadData i get info about icon and then I call function inside headerCell to change the icon. Do I need to do this in other way in order for this to work?
protocol HeaderCellDelegate: class {
func setDropdown()
func tapOnSelection()
}
class HeaderCell: UIGestureRecognizerDelegate {
// delegate
weak var hDelegate: HeaderCellDelegate?
#objc
lazy var selectionBtn: UIButton = {
let button = UIButton(type: .system)
button.setTitle("One", for: .normal)
button.addTarget(self, action: #selector(changeSelection), for: .touchUpInside)
return button
}()
#objc
lazy var oneOrTwo: UIButton = {
let button = UIButton()
button.setImage(UIImage(named: "oneOff")?.withRenderingMode(.alwaysTemplate), for: .normal)
button.addTarget(self, action: #selector(toggleOneOrTwo), for: .touchUpInside)
return button
}()
#objc
lazy var iconBtn: UIButton = {
let button = UIButton()
button.setImage(UIImage(named: "iconGreen")?.withRenderingMode(.alwaysTemplate), for: .normal)
button.addTarget(self, action: #selector(setIcon), for: .touchUpInside)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
// I setup view helper
// Just a button and icon on the right
// button opens DropDown list
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Delegate Functions
#objc
func toggleOneOrTwo(_ sender: Any) {
self.hDelegate?.setDropdown()
}
#objc
func changeSelection(sender: UIButton) {
self.hDelegate?.tapOnSelection()
}
#objc
func setIcon() {
isIconGreen
? iconColor.setImage(UIImage(named: "iconGreen")?.withRenderingMode(.alwaysTemplate), for: .normal)
: iconColor.setImage(UIImage(named: "iconRed")?.withRenderingMode(.alwaysTemplate), for: .normal)
}
}
ViewController
class ViewController: UICollectionViewController, UIGestureRecognizerDelegate {
fileprivate let headerCellId = "headerCellId"
lazy var headerCell = HeaderCell()
override func viewDidLoad() {
super.viewDidLoad()
// Register Cell Class
self.collectionView.register(HeaderCell.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerCellId)
loadData()
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionView.elementKindSectionHeader:
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerCellId, for: indexPath) as! HeaderCell
header.backgroundColor = .groupTableViewBackground
header.hDelegate = self
return header
default:
fatalError("This should never happen!!")
}
}
func loadData() {
// get data from API and isIconGreen value
let isIconGreen = true
headerCell.setIcon(isIconGreen: isIconGreen)
}
}

Related

Button not enabled when added programmatically to collectionviewcell

When I add a button programmatically to the custom UICollectionViewCell class, it is not clickable and will not run the function I attached to it.
I have tried these so far:
Button inside CollectionView not clickable
Swift - UIButton in UICollectionViewCell not clickable
but did not get any luck.
In my cell Class, I have:
let toggleButton: UIButton = {
let tb = UIButton(type: .custom) as UIButton
tb.setTitleColor(.white, for: .normal)
tb.setTitle("Example", for: .normal)
tb.titleLabel?.font = UIFont.customFont(name: "InterUI-Medium", size: 18)
tb.backgroundColor = UIColor(red:0.36, green:0.69, blue:0.55, alpha:1.0)
tb.layer.cornerRadius = 5
tb.titleLabel?.adjustsFontSizeToFitWidth = true
tb.titleLabel?.minimumScaleFactor = 0.4
tb.translatesAutoresizingMaskIntoConstraints = false
tb.addTarget(self, action: #selector(topButtonTouched), for: .touchUpInside)
return tb
}()
and
func setupView() {
self.addSubview(toggleButton)
let menuAndToggleConstraints = [
toggleButton.centerYAnchor.constraint(equalTo: self.centerYAnchor),
toggleButton.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -100,
toggleButton.widthAnchor.constraint(equalToConstant: 150)
]
NSLayoutConstraint.activate(menuAndToggleConstraints)
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
to setup the button in the cell. I have tried self.isUserInteractionEnabled to see if there are any conflicts but it does not resolve anything. I have one other gesture recognizer in the cell and have tried without it and the button is still not clickable.
Edit:
Here is my delegate and my function that calls the collectionView to change the size at indexPath:
weak var delegate: ExpandedCellDelegate?
public var indexPath: IndexPath!
#objc func topButtonTouched(_ sender: UIButton) {
print("hello")
if let delegate = self.delegate{
delegate.topButtonTouched(indexPath: indexPath)
}
}
I made sure to set the delegate and indexPath as self in cellForRow function in CollectionView class as demonstrated in this answer.
I was able to make the button work by putting this:
tb.addTarget(self, action: #selector(topButtonTouched), for: .touchUpInside)
into the setupView() method like so:
func setupView() {
self.addSubview(toggleButton)
toggleButton.addTarget(self, action: #selector(topButtonTouched), for: .touchUpInside)
let menuAndToggleConstraints = [
toggleButton.centerYAnchor.constraint(equalTo: self.centerYAnchor),
toggleButton.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -100,
toggleButton.widthAnchor.constraint(equalToConstant: 150)
]
NSLayoutConstraint.activate(menuAndToggleConstraints)
}
Not sure if it's correct but it works.

Call function from UICollectionViewCell to use in UICollectionView

I have a UICollectionViewCell that creates a bunch of text fields and a function (handleNewJob) that looks at all the textfields created in the cell and writes them to firebase.
However, what I want is for a button that I have created in my UICollectionView to call the function (handleNewJob) from the UICollectionViewCell.
Is this possible? I have tried having the function inside the UICollectionView but I can't seem to reference all the textfields.text?
Here is my function inside UICollectionViewCell (I haven't included all the textfield generation as it's quite long):
// HANDLE NEW JOB
func handleNewJob(){
let newJobBrand = jobBrand.text!
let newJobName = jobName.text!
let newDirectorName = directorName.text!
let newAgencyName = agencyName.text!
let newProdCoName = prodCoName.text!
// WHERE TO PUT IN DATABASE
let reference = Database.database().reference().child("jobInfo")
let childRef = reference.childByAutoId()
// REFERENCING DICTIONARY
let jobBrandValue = ["jobBrand": newJobBrand]
let jobNameValue = ["jobName": newJobName]
let jobDirectorValue = ["directorName": newDirectorName]
let jobAgencyNameValue = ["agencyName": newAgencyName]
let jobProdCoValue = ["prodCoName": newProdCoName]
// WRITE TO DATABASE
childRef.updateChildValues(jobBrandValue)
childRef.updateChildValues(jobNameValue)
childRef.updateChildValues(jobDirectorValue)
childRef.updateChildValues(jobAgencyNameValue)
childRef.updateChildValues(jobProdCoValue)
}
Here is my code for the UICollectionView - I want to call the function under the handleNext button:
import UIKit
import Firebase
class page_newJobSwipingController : UICollectionViewController, UICollectionViewDelegateFlowLayout, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
// VARIABLES
var ref:DatabaseReference?
// BOTTOM BUTTONS
private let previousButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Previous", for: .normal)
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
button.setTitleColor(.gray, for: .normal)
button.addTarget(self, action: #selector(handlePrev), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
private let nextButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Next", for: .normal)
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
let pinkColour = UIColor(red: 232/255, green: 68/266, blue: 133/255, alpha: 1)
button.setTitleColor(.mainPink, for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(handleNext), for: .touchUpInside)
return button
}()
// SET UP NEXT AND PREVIOUS BUTTONS TO HAVE A FUNCTION
#IBAction func handlePrev(sender : UIButton) {
let prevIndex = max(pageControl.currentPage - 1, 0)
pageControl.currentPage = prevIndex
let indexPath = IndexPath(item: prevIndex, section: 0)
collectionView?.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}
#IBAction func handleNext(sender : UIButton) {
let nextIndex = pageControl.currentPage + 1
pageControl.currentPage = nextIndex
if nextIndex == 1 {
print ("move to page 2")
} else {
print ("send alert message")
newJobCellGeneral.handleNewJob()
storyboardAlert()
}
let indexPath = IndexPath(item: 1, section: 0)
collectionView?.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}
// HANDLE UPLOAD STORYBOARD OPTIONS
#IBAction func storyboardAlert() {
let imagePickerController = UIImagePickerController()
imagePickerController.delegate = self
// ACTION SHEET FOR ADDING NEW ATTACHMENT
let alert = UIAlertController(title: "Job Created", message: "Do you want to upload storyboard cells now?", preferredStyle: .actionSheet)
alert.addAction(UIAlertAction(title: "Now", style: .default, handler: { (action:UIAlertAction) in
let storyboardUpload = page_newJobStoryboardUpload()
self.show(storyboardUpload, sender: self)
}))
alert.addAction(UIAlertAction(title: "Later", style: .default, handler: { (action:UIAlertAction) in
let jobList = page_jobList()
self.show(jobList, sender: self)
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
}
// PAGE CONTROL
private let pageControl: UIPageControl = {
let pc = UIPageControl()
pc.numberOfPages = 2
pc.currentPageIndicatorTintColor = .mainPink
pc.pageIndicatorTintColor = UIColor(red: 249/255, green: 207/266, blue: 224/255, alpha: 1)
return pc
}()
override func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let x = targetContentOffset.pointee.x
pageControl.currentPage = Int(x / view.frame.width)
}
// CONSTRAINTS OF BOTTOM CONTROLS
fileprivate func setupBottomControls(){
let bottomControlsStackView = UIStackView(arrangedSubviews: [previousButton, pageControl, nextButton])
bottomControlsStackView.translatesAutoresizingMaskIntoConstraints = false
bottomControlsStackView.distribution = .fillEqually
view.addSubview(bottomControlsStackView)
NSLayoutConstraint.activate([
bottomControlsStackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
bottomControlsStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
bottomControlsStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
bottomControlsStackView.heightAnchor.constraint(equalToConstant: 50)
])
}
// SUPER VIEW DID LOAD
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = .white
collectionView?.register(newJobCellGeneral.self, forCellWithReuseIdentifier: "newJobCellGeneral")
collectionView?.register(newJobCellTechnical.self, forCellWithReuseIdentifier: "newJobCellTechnical")
collectionView?.isPagingEnabled = true
setupBottomControls()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "newJobCellGeneral", for: indexPath) as! newJobCellGeneral
navigationItem.title = "General Info"
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "newJobCellTechnical", for: indexPath) as! newJobCellTechnical
navigationItem.title = "Technical Specs"
return cell
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: view.frame.height)
}
}
The flow is inversed but yes, the easiest way is via notifications
in you collection view
Put this in your collectionView button pressed method
NotificationCenter.default.post(name: Notification.Name("handleNewJob"), object: nil)
Add observer in you collection view cell (init or awakeFromNib depends)
NotificationCenter.default.addObserver(self, selector: #selector(handleNewJob, name: NSNotification.Name(rawValue: "handleNewJob"), object: nil)
Notification Center is one solution, but the easier way is to be able to call a function on your collection view controller directly from the cell.
To do that, you need to be able to reference your parent view controller. so add this UIView extension:
Create a swift file named UIView.swift and paste this:
import UIKit
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if parentResponder is UIViewController {
return parentResponder as! UIViewController!
}
}
return nil
}
}
then from your cell, for example when a button is clicked:
#IBAction func someButtonAction(sender : UIButton) {
let parent = self.parentViewController as! page_newJobSwipingController
parent.whateverFunction()
}

How to activate share extension (UIActivityViewController) when a button tapped (which is in UICollectionViewController)?

I'm trying to add some buttons or labels to one of UICollectionViewCell, but it seems I'm not be able to activate UIActivityViewController when the share button tapped.
Here are my codes:
class VerticalCollectionViewCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
containerView.addSubview(shareButton)
shareButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -16).isActive = true
shareButton.rightAnchor.constraint(equalTo: favoriteButton.leftAnchor, constant: -16).isActive = true
shareButton.widthAnchor.constraint(equalToConstant: 24).isActive = true
shareButton.heightAnchor.constraint(equalToConstant: 24).isActive = true
shareButton.addTarget(self, action: #selector(handleShareButton), for: .touchUpInside)
}
#objc func handleShareButton(sender: UIButton) {
let shareText = NSLocalizedString("I found something interesting you may want to take a look at.", comment: "share")
let shareImage = #imageLiteral(resourceName: "Bear")
let activityViewController : UIActivityViewController = UIActivityViewController(activityItems: [shareText, shareImage as Any], applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true, completion: nil)
}
}
The errors are:
Value of type 'VerticalCollectionViewCell' has no member 'view'
and
Value of type 'VerticalCollectionViewCell' has no member 'present'.
I know presentViewController is a UIViewController method, UITableViewCell does not has a method called presentViewController, and UITableViewCell should never handle any business logic. It should be implemented in a view controller.
But I have no idea how to fix this.
you call do in this way
class Cell: UITableViewCell {
#IBOutlet let shareButton: UIButton!
}
class VC: UIViewController, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? Cell {
cell?.shareButton.addTarget(self, action: #selector(VC.shareButtonPressed(_:)), for: .touchUpInside)
}
}
func shareButtonPressed(_ button: UIButton) {
//code here
}
}
What you can do, is define a protocol of the cell, which your tableview can conform to. When the share button on a collectionView is tapped, call this delegate method, passing in relevant information like the cell tag or the model for that collectionView cell. Using that information you can then present the ActivityController from your viewController.

Creating a checkbox programmatically using images as the UIButtons background

I have seen multiple questions on how to implement a checkbox by just changing the background image when clicked, but I don't understand why the checkbox only shows when I add it to my ViewController setupViews.
It simply will not show up or change when I have all the functionality in my actionButton function. Should i be using a protocol and delegate set up to get my button showing changing when clicked? Below is my code, I am hoping someone can shed some light as to what I am missing here?
class MainMenuViewController: UIViewController {
let clickingCheckbox = ClickingCheckbox()
var checkbox = UIImage(named: "Checked_Checkbox")
var empty_checkbox = UIImage(named:"Empty_Checkbox")
var isBoxClicked: Bool!
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
func setupViews() {
self.backgroundImage.addSubview(contentView)
self.contentView.addSubview(clickingCheckbox)
clickingCheckbox.snp.makeConstraints { (make) in
make.top.equalTo(signInButton.snp.bottom).offset(-MainMenuViewController.padding)
make.leading.equalTo(buttonView)
make.width.equalTo(signInButton).multipliedBy(0.2)
make.height.equalTo(clickingCheckbox.snp.width)
}
clickingCheckbox.addTarget(self, action: #selector(buttonAction(_:)), for: .touchUpInside)
clickingCheckbox.setImage(empty_checkbox, for: UIControlState.normal) #The checkbox only shows on screen if I put it here, however it does nothing when clicked!
}
#objc func buttonAction(_ sender: ClickingCheckbox) {
if isBoxClicked == true {
isBoxClicked = false
}else{
isBoxClicked = true
}
if isBoxClicked == true {
sender.setImage(checkbox, for: UIControlState.selected)
}else{
sender.setImage(empty_checkbox, for: UIControlState.normal)
}
print("test")
}
In my Class I have.....
class ClickingCheckbox: UIButton {
override func draw(_ rect: CGRect) {
super.draw(rect)
}
}
I have tried keeping the buttonAction functionality in the class, but that didn't work, I have tried a multiple different ways but can't get my head around how to show it working. All other advice is given to implement using IBOutlets so this would be really helpful for me to understand. Thanks
Try something like this:
class ClickingCheckbox: UIButton {
convenience init() {
self.init(frame: .zero)
self.setImage(UIImage(named: "Empty_Checkbox"), for: .normal)
self.setImage(UIImage(named: "Checked_Checkbox"), for: .selected)
self.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
}
#objc private func buttonTapped() {
self.isSelected = !self.isSelected
}
}

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
}