Updating button events in Swift? - 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
}

Related

How to save array data locally

Here is my code. I want to learn how to save my dataSource for the like button of each cell. So that when the user leaves the app and returns later, the same cells that were liked remain liked.
This is new code from my last post. Since I don't want to keep making new arrays, I can try and locally save my dataSource variable which stores each cells Status of being liked or not.
ViewController -
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
var dataSource: [TableModel] = []
var updatedCell = TableViewCell()
var updatedIndex = Int()
override func viewDidLoad() {
super.viewDidLoad()
overrideUserInterfaceStyle = .light
self.dataSource = Array(repeating: TableModel(isLiked: false), count: 8)
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.showsVerticalScrollIndicator = false
self.tableView.reloadData()
}
#IBAction func buttonSelected(_ sender: Any) {
// Update Cell for which UIButton (Like Button) was tapped.
dataSource[(sender as AnyObject).tag].isLiked = !dataSource[(sender as AnyObject).tag].isLiked
let indexPath = IndexPath(row: (sender as AnyObject).tag, section: 0)
tableView.reloadRows(at: [indexPath], with: .automatic)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
// Saved updatedCell & updatedIndex variables for delegate pattern. To Update this VC's cells data when edited on secondVC (moreInfoViewController) in a way, made them accessible outside this function
// To Get Specific TableView Cell the user is interacting with.
updatedCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
updatedIndex = indexPath.row
// Go to Second VC and Send cell tapped data to next view
let vc = (storyboard?.instantiateViewController(identifier: "secondVC") as? moreInfoViewController)!
vc.delegate = self
// Get Status of Liked Button in the cell the user tapped and display if the user liked it previously in the SecondVC
let isLiked = dataSource[indexPath.row].isLiked
if isLiked {
// print("Liked")
vc.isLiked = true
} else {
// print("Not Liked")
vc.isLiked = false
}
present(vc, animated: true)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 8
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
// cell.moreBtn.isUserInteractionEnabled = false
cell.likeBtn.tag = indexPath.row
// cell.likeBtn.addTarget(self, action: #selector(buttonSelected(_:)), for: .touchUpInside)
// Get Each Cell Liked Button Status and display if the user liked or not Liked each cell
let isLiked = dataSource[indexPath.row].isLiked
if isLiked {
// User liked the post
cell.likeBtn.setImage(UIImage(named: "liked"), for: UIControl.State.normal)
} else {
// User Unliked the post
cell.likeBtn.setImage(UIImage(named: "unLiked"), for: UIControl.State.normal)
}
return cell
}
}
// Conform VC to protocol (VC2Delegate) located in "Structs.swift" File
extension ViewController: VC2Delegate {
func likeStatusDidChange(_ vc2: moreInfoViewController, to title: Bool) {
// set the text of the table cell here...
dataSource[updatedIndex].isLiked = !dataSource[updatedIndex].isLiked
let indexPath = IndexPath(row: updatedIndex, section: 0)
tableView.reloadRows(at: [indexPath], with: .automatic)
}
}
moreInfoViewController -
import UIKit
class moreInfoViewController: UIViewController {
#IBOutlet var backBtn: UIButton!
#IBOutlet var titleLabel: UILabel!
#IBOutlet var locationLabel: UILabel!
#IBOutlet var theImage: UIImageView!
#IBOutlet var mainlikeBtn: UIButton!
#IBOutlet var mainTypeLbl: UILabel!
var currentID:String = ""
var isLiked = Bool()
weak var delegate: VC2Delegate?
override func viewDidLoad() {
super.viewDidLoad()
overrideUserInterfaceStyle = .light
self.navigationController?.setNavigationBarHidden(true, animated: true)
styles()
// "isLiked" variable to display whether or user liked this event
if (isLiked == true) {
// is Liked
mainlikeBtn.setImage(UIImage(named: "liked"), for: UIControl.State.normal)
} else {
// Not Liked
mainlikeBtn.setImage(UIImage(named: "unLiked"), for: UIControl.State.normal)
}
}
// Heart/Like Button Action. User can like event in this VC with this button and it will tell the firstVC (ViewController) to update "Like Status" there also
#IBAction func likeBtnAction(_ sender: Any) {
if (isLiked == true) {
// is Liked
isLiked = false
mainlikeBtn.setImage(UIImage(named: "unLiked"), for: UIControl.State.normal)
} else {
isLiked = true
mainlikeBtn.setImage(UIImage(named: "liked"), for: UIControl.State.normal)
}
// When User interacts with like Button, this function gets called that tells the firstVC (ViewController) to update as well.
// likeStatusDidChange function is located at the bottom of the (ViewController) with extension ViewController.
delegate?.likeStatusDidChange(self, to: true)
}
// Go Back To FirstVC (ViewController)
#IBAction func previousVC(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
func styles() {
titleLabel.numberOfLines = 1
titleLabel.adjustsFontSizeToFitWidth = true
locationLabel.numberOfLines = 1
locationLabel.adjustsFontSizeToFitWidth = true
backBtn.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2)
theImage.layer.borderColor = UIColor.black.cgColor
theImage.layer.borderWidth = 2
theImage.layer.cornerRadius = 10
}
}
When you get array from userdefaults it gives you Any type array so you need to cast it to Int array and check it nil or not before you use. So you can use this code block.
if let savedDataKey = UserDefaults.standard.array(forKey: "savedDataKey") as? [Int] {
print(savedDataKey)
}

Change Button image from view controller inside custom header cell

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)
}
}

How to add an action button to a custom XIB cell?

I want to create a custom XIB Table View Cell with an action button that can segue to a new view controller.
So far, I created a custom XIB Cell (TaskListCell) with a button (timeButton), and registered the TaskListCell to TaskListViewController. For tableView(_:cellForRowAt:), I added tag for timeButton and addTarget(_:action:for:) to respond when timeButton is tapped. However, I get this error message: Thread 1: "-[Sprints.TaskListCell pressedTimeButton:]: unrecognized selector sent to instance 0x105311560"
Here is the custom XIB cell file:
class TaskListCell: UITableViewCell {
#IBOutlet weak var nameField: UITextField!
#IBOutlet weak var timeButton: UIButton!
#IBAction func pressedTimeButton(_ sender: UIButton) {
}
override func awakeFromNib() {
}
override func setSelected(_ selected: Bool, animated: Bool) {
}
}
Here is the ViewController that displays the custom cells in a Table View:
class TaskListViewController: UIViewController {
// MARK: - Outlet Variables
#IBOutlet weak var taskList: SelfSizedTableView!
...
// MARK: - Instance Variables
var taskData = [TaskData]()
var taskCount: Int = 1
...
// MARK: - View Controller Methods
override func viewDidLoad() {
super.viewDidLoad()
// Register TaskListCell.xib file
let taskCellNib = UINib(nibName: "TaskCell", bundle: nil)
taskList.register(taskCellNib, forCellReuseIdentifier: "taskCell")
...
// Connect table view's dataSource and delegate to current view controller
taskList.delegate = self
taskList.dataSource = self
}
// MARK: - Action Methods
// Adds new cell in Table View
#IBAction func pressedAddTask(_ sender: UIButton) {
taskCount += 1
taskList.reloadData()
taskList.scrollToRow(at: IndexPath(row: taskCount-1, section: 0), at: .bottom, animated: true)
}
// MARK: - Methods
// Segues to SelectTime screen when timeButton is pressed
#objc func pressedTimeButton(_ sender: UIButton) {
let destination = SelectTimeViewController()
navigationController?.pushViewController(destination, animated: true)
}
}
extension TaskListViewController: UITableViewDataSource {
// Return the number of rows in table view
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return taskCount
}
// Return the cell to insert in table view
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "taskCell", for: indexPath) as! TaskListCell
// Fatal error for next line: "Unexpectedly found nil while implicitly unwrapping an Optional value"
cell.timeButton.tag = indexPath.row
cell.timeButton.addTarget(self, action: #selector(pressedTimeButton(_:)), for: .touchUpInside)
return cell
}
}
extension TaskListViewController: UITableViewDelegate {
}
Check instance of IBAction from your cell may be it is being called. Remove this function and it will work
#IBAction func pressedTimeButton(_ sender: UIButton) {
}

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.

Update UIButton's image according to Model

I have a UITableView where each cell get's its data from my model class, which is an array in my TableViewController class. The model for each cell is set in cellForRowAtIndexPath. My model has a "isFavorited" property. My custom cell class has a UIButton with an image of a star.
If model.isFavorited == false, the UIButton image is a gray star.
If model.sFavorited == true, the UIButton image should change to a yellow star.
So far, I have tried to approach this like so:
private let normal = UIImage(named: "star")
private let selection = UIImage(named: "star highlighted")
class CustomCell: UITableViewCell {
var model: Model? {
didSet{
favoriteButton.selected = (model?.isFavorite)!
}
}
#IBOutlet var favoriteButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
favoriteButton.adjustsImageWhenHighlighted = true
favoriteButton.setImage(normal, forState: .Normal)
favoriteButton.setImage(selection, forState: .Highlighted)
favoriteButton.setImage(selection, forState: .Selected)
}
#IBAction func favoriteTapped(sender: AnyObject) {
//UPDATE THE MODEL
model?.isFavorite = !(model?.isFavorite)!
}
}
This code successfully changes a star from gray to yellow and vice-versa thanks to didSet being called to update the favoriteButton.selected boolean with the value from (model?.isFavorite)!.
HOWEVER, as I scroll far enough through my tableview, and try to come back to the specific cell I favorited, the image will reappear as unfavorited (gray star instead of yellow star).
If my model is being updated when I tap the button, and the UIButton's image is adjusted during didSet as a result of this change, why does it revert back to the defaulted state when scrolling back to this cell??
EDIT:
My code in cellForRowAtIndexPath
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: CustomCell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath) as! CustomCell
cell.model = modelArray[indexPath.row]
return cell
}
Basically you could subclass the UIButton with custom images set based on the tap. I had the same issue and solved it using below:
class FavioriteButton: UIButton {
//Images
let normalImage = UIImage(named: "star")! as UIImage
let selectedImage = UIImage(named: "star_highlighted")! as UIImage
//Bool property
var isSelected: Bool = false {
didSet {
if isSelected == true {
self.setImage(normalImage, forState: .Normal)
}else{
self.setImage(selectedImage, forState: .Normal)
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
self.addTarget(self, action: #selector(buttonClicked(_:)), forControlEvents: .TouchUpInside)
self.isSelected = false
}
func buttonClicked(sender: UIButton) {
if sender == self {
if isSelected == true {
isSelected = false
}else{
isSelected = true
}
}
}
}
Hope it helps you.