Switches in Tableviews - ios

I have the following custom UITableViewCell:
I want my view controller to be notified when somebody flips a cell's switch in order to update my model. I've tried using the table view's delegate methods (didSelect, didFinishEditing, didHighlight, etc.) but none of them are called upon this action. Is there any way to do what I want to do? Somebody please help.

actually your UISwitch has added to accessoryView of UITableViewCell, so do like on cellforRowAtIndex
var switchView = UISwitch(frame: CGRect.zero)
aCell.accessoryView = switchView
lightSwitch.tag = indexPath.row
switchView.setOn(false, animated: false)
switchView.addTarget(self, action: #selector(switchChanged(_:), for: .valueChanged)
and get the action of UISwitch as
func switchChanged(_ sender: UISwitch) {
print("which switch is \(sender.tag)")
print("The switch is \(sender?.on ? "ON" : "OFF")")
}

To update your model when somebody flips a cell's switch you need:
Assign cell's #IBAction func onSwitched(_ sender: UISwitch) as UISwitch Value Changed listener as shown on this screenshot
Attach color model to cell
cell.myColorModel = myColorModels[indexPath.row]
In #IBAction func onSwitched(_ sender: UISwitch) simply change selected property in model
#IBAction func onSwitched(_ sender: UISwitch) {
myColorModel.selected = sender.isOn
}
FULL SOURCE CODE
class MyColorModel {
var title: String!
var color: UIColor!
var selected: Bool = false
init(title: String, color: UIColor) {
self.title = title
self.color = color
}
}
class MyColorCell: UITableViewCell {
#IBOutlet weak var colorTitle: UILabel!
#IBOutlet weak var colorImage: UIImageView!
#IBOutlet weak var colorSwitch: UISwitch!
var myColorModel: MyColorModel! {
didSet {
colorTitle.text = myColorModel.title
colorImage.backgroundColor = myColorModel.color
colorSwitch.isOn = myColorModel.selected
}
}
#IBAction func onSwitched(_ sender: UISwitch) {
myColorModel.selected = sender.isOn
}
}
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
fileprivate var myColorModels = [MyColorModel(title: "Red", color: UIColor.red),
MyColorModel(title: "Green", color: UIColor.green),
MyColorModel(title: "Blue", color: UIColor.blue)]
#IBAction func onColorsCheck(_ sender: AnyObject) {
for myColorModel in myColorModels {
print("color \(myColorModel.title) \((myColorModel.selected) ? "is checked":"is not checked")")
}
}
// MARK: - UITableView datasource & delegate
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myColorModels.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyColorCell") as! MyColorCell
cell.myColorModel = myColorModels[indexPath.row]
return cell
}
}

Related

Is there a simple way to delete specific custom cells from a UITableView?

I am trying to instantiate empty Buyer cells (custom cell) in my table view and then have the user populate the buyers' names. When the user presses the delete button for a row/cell, it should delete the corresponding row/cell regardless of whether or not the textfield for that row has been populated or not. Clearly, I am not getting the desired behavior. For example, when I press delete Row0 (whose textfield says "Buyer 0") and the tableview reloads, Buyer 0 is still there, but one of the empty Buyer cells at the end gets deleted instead.
import UIKit
class EntryAlertViewController: UIViewController {
//Fields/Table
#IBOutlet weak var itemField: UITextField!
#IBOutlet weak var priceField: UITextField!
#IBOutlet weak var tableView: UITableView!
//Visual Components
#IBOutlet weak var mainView: UIView!
#IBOutlet weak var titleView: UIView!
#IBOutlet weak var splitItemButton: UIButton!
#IBOutlet weak var cancelButton: UIButton!
#IBOutlet weak var addItemButton: UIButton!
//Commonly Used Objects/Variables
var potentialBuyers: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
potentialBuyers.append("")
tableView.dataSource = self
tableView.register(UINib(nibName: "BuyerCell", bundle: nil), forCellReuseIdentifier: "ReusableCell")
}
override func viewWillAppear(_ animated: Bool) {
}
#IBAction func splitItemPressed(_ sender: UIButton) {
potentialBuyers.append("")
tableView.reloadData()
}
}
Here are the tableview datasource and the delete button delegate.
extension EntryAlertViewController: UITableViewDataSource, DeleteButtonDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return potentialBuyers.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ReusableCell", for: indexPath) as! BuyerCell
cell.deleteButtonDelegate = self
cell.indexPath = indexPath
cell.nameField.text = cell.buyerName
if potentialBuyers.count == 1 {
cell.deleteButton.isHidden = true
} else {
cell.deleteButton.isHidden = false
}
return cell
}
func deletePressed(index: Int) {
potentialBuyers.remove(at: index)
tableView.reloadData()
}
}
And here is my BuyerCell class with the UITextFieldDelegate as an extension.
import UIKit
protocol DeleteButtonDelegate {
func deletePressed(index: Int)
}
class BuyerCell: UITableViewCell {
#IBOutlet weak var deleteButton: UIButton!
#IBOutlet weak var nameField: UITextField!
var deleteButtonDelegate: DeleteButtonDelegate!
var indexPath: IndexPath!
var buyerName: String?
override func awakeFromNib() {
super.awakeFromNib()
self.nameField.delegate = self
}
#IBAction func deletePressed(_ sender: UIButton) {
//print the indexPath.row that this was pressed for
print("delet pressed for \(indexPath.row)")
self.deleteButtonDelegate?.deletePressed(index: indexPath.row)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
extension BuyerCell: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
print("textFieldDidBeginEditing")
buyerName = nameField.text
}
func textFieldDidEndEditing(_ textField: UITextField) {
print("textFieldDidEndEditing")
buyerName = nameField.text
}
}
Your problem is in this line
cell.nameField.text = cell.buyerName
Cells are reused from a reuse pool, so you can't rely on the cell holding any particular state or value.
Your buyer name needs to come from your data model array.
Something like
cell.nameField.text = self.potentialBuyers[indexPath.row]
Reloading the whole tableview is a bit excessive when you have only deleted a single row; Just delete the relevant row.
You can also clean up your delegation protocol so that there is no need for the cell to track its indexPath -
protocol DeleteButtonDelegate {
func deletePressed(in cell: UITableViewCell)
}
In your cell:
#IBAction func deletePressed(_ sender: UIButton) {
self.deleteButtonDelegate?.deletePressed(in: self)
}
In your view controller:
func deletePressed(in cell: UITableViewCell) {
guard let indexPath = tableView.indexPath(for: cell) else {
return
}
potentialBuyers.remove(at: indexPath.row)
tableView.deleteRows(at:[indexPath], with: .automatic)
}
There is a major issue in your code. You are not updating the data model so the changes in the cells are lost when the user scrolls.
Rather then quite objective-c-ish protocol/delegate in Swift callback closures are much more convenient and efficient. You can use one callback for both updating the model and deleting the cell.
Replace the BuyerCell cell with
class BuyerCell: UITableViewCell {
#IBOutlet weak var deleteButton: UIButton!
#IBOutlet weak var nameField: UITextField!
var callback : ((UITableViewCell, String?) -> Void)?
override func awakeFromNib() {
super.awakeFromNib()
self.nameField.delegate = self
}
#IBAction func deletePressed(_ sender: UIButton) {
callback?(self, nil)
}
}
extension BuyerCell: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
print("textFieldDidBeginEditing")
callback?(self, nameField.text)
}
func textFieldDidEndEditing(_ textField: UITextField) {
print("textFieldDidEndEditing")
callback?(self, nameField.text)
}
}
In the controller in cellForRow assign the callback and handle the actions. The actions work also reliably if cells are reordered, inserted or deleted.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ReusableCell", for: indexPath) as! BuyerCell
let buyerName = potentialBuyers[indexPath.row]
cell.nameField.text = buyerName
cell.callback = { [unowned self] cCell, cName in
let currentIndexPath = tableView.indexPath(for: cCell)!
if let name = cName {
self.potentialBuyers[currentIndexPath.row] = name
} else {
self.potentialBuyers.remove(at: currentIndexPath.row)
tableView.deleteRows(at: [currentIndexPath], with: .fade)
}
}
cell.deleteButton.isHidden = potentialBuyers.count == 1
return cell
}

Call function in secondFile.swift/viewController from firstFile.swift/viewController?

I'm trying to call a function() made in my first ViewController from another function() made in the second ViewController.
It's a function to update the title of a button in the firstViewController.
I have searched but I can't find a way.
First ViewController // ViewController.swift
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
weightLabel.delegate = self
}
#IBAction func excerciseChooserButton(_ sender: UIButton) {
}
var weight = 0 {
didSet {
weightLabel.text = "\(weight)"
}
}
// User input WEIGHT
#IBOutlet weak var weightLabel: UITextField!
func textField(_ weightLabel: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let isNumber = CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: string))
let withDecimal = (
string == NumberFormatter().decimalSeparator &&
weightLabel.text?.contains(string) == false
)
return isNumber || withDecimal
}
#IBAction func plusWeight(_ sender: UIButton) {
weight += 5
}
#IBAction func minusWeight(_ sender: UIButton) {
weight -= 5
}
// User input REPS
#IBOutlet weak var repLabel: UILabel!
#IBAction func repSlider(_ sender: UISlider) {
let currentRepValue = Int(sender.value)
repLabel.text = "\(currentRepValue)"
let cm = Calculator(weight: weightLabel.text!, reps: repLabel.text!)
let result = cm.calcRM()
repMax.text = "1RM: \(result)kg"
}
#IBOutlet weak var repMax: UILabel!
#IBOutlet weak var excerciseLabel: UIButton!
func changeText() {
excerciseLabel.setTitle(Excercises.excChosen, for: .normal)
print(excerciseLabel)
}
#IBAction func unwindToViewController(segue:UIStoryboardSegue) {
}
}
// // // //
Second ViewController // ExcerciseChooserViewController.swift
import UIKit
struct Excercises {
static var excChosen:String? = ""
}
class ExcerciseChooserViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
// Data model: These strings will be the data for the table view cells
let excercises: [String] = ["Bench Press", "Squat", "Push Press", "Deadlift"]
// cell reuse id (cells that scroll out of view can be reused)
let cellReuseIdentifier = "cell"
// don't forget to hook this up from the storyboard
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Register the table view cell class and its reuse id
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
// (optional) include this line if you want to remove the extra empty cell divider lines
// self.tableView.tableFooterView = UIView()
// This view controller itself will provide the delegate methods and row data for the table view.
tableView.delegate = self
tableView.dataSource = self
}
// number of rows in table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.excercises.count
}
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// create a new cell if needed or reuse an old one
let cell:UITableViewCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as UITableViewCell!
// set the text from the data model
cell.textLabel?.text = self.excercises[indexPath.row]
return cell
}
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let excerciseChosen = "\(excercises[indexPath.row])"
print("You tapped cell number \(indexPath.row).")
print(excerciseChosen)
goBackToOneButtonTapped((Any).self)
Excercises.excChosen = excerciseChosen
print(Excercises.excChosen!)
// call function to update text
ViewController.changeText()
}
#IBAction func goBackToOneButtonTapped(_ sender: Any) {
performSegue(withIdentifier: "unwindToViewController", sender: self)
}
}
Call it from unwindToViewController instead, no need to call it while the first view controller is not visible
There are many ways to do this, but I'll describe a simple one here.
Because you're going back to 'ViewController' via a segue, a good option for you is to override prepare(for:sender:). This will give you a reference to the destination view controller of that segue, which will then allow you to call functions or set properties in that view controller. You can read more about this method here.
Here are some basic steps:
In ViewController, update your changeText() method to accept a string parameter: changeText(_ text: String?).
Add a property to ExcerciseChooserViewController to hold the text you want to use: private var chosenExercise: String?
In your tableView:DidSelectRowAtIndexPath: method, set your new chosenExercise property to the string you want to pass to ViewController.
In prepare(for:sender:) of ExcerciseChooserViewController, grab a reference to destination view controller, downcast it to your subclass ViewController, and call your new method passing in the exerciseText string.
For example:
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var excerciseLabel: UIButton!
func changeText(_ text: String?) {
guard let text = text else { return }
excerciseLabel.setTitle(text, for: .normal)
print(excerciseLabel)
}
}
And in ExcerciseChooserViewController:
class ExcerciseChooserViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
private var chosenExercise: String?
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let excerciseChosen = "\(excercises[indexPath.row])"
print("You tapped cell number \(indexPath.row).")
print(excerciseChosen)
goBackToOneButtonTapped((Any).self)
Excercises.excChosen = excerciseChosen
print(Excercises.excChosen!)
chosenExercise = excerciseChosen
}
#IBAction func goBackToOneButtonTapped(_ sender: Any) {
performSegue(withIdentifier: "unwindToViewController", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationVC = segue.destination as? ViewController {
destinationVC.changeText(chosenExercise)
}
}
}

Updating Label in Cell

I have a TableView which rows contain label and two buttons. What I wanna do is that when a user clicks the first button "Set Name", a pop up view comes up in which he can input text from keyboard. After hitting "Set", pop up view is dismissed and label inside a row containing the clicked button changes to the input text. I set the delegates but I cannot make label to change.
TableView:
import UIKit
class SetGame: UIViewController, UITableViewDelegate, UITableViewDataSource
{
var numOfPlayers = Int()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return numOfPlayers
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.Name.text = "Player \(indexPath.row + 1)"
cell.btn1.tag = indexPath.row
cell.btn2.tag = indexPath.row
return cell
}
override func viewDidLoad()
{
super.viewDidLoad()
self.tableView.separatorStyle = UITableViewCellSeparatorStyle.none
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
#IBAction func setName(sender: UIButton)
{
let thisVC = storyboard?.instantiateViewController(withIdentifier: "SetName") as! SetName
thisVC.delegate = self
present(thisVC, animated: true, completion: nil)
}
#IBAction func setFingerprint(_ sender: UIButton)
{
}
#IBAction func unwindToSetGame(_ segue: UIStoryboardSegue)
{
print("unwinded to SetGame")
}
#IBOutlet weak var tableView: UITableView!
}
extension SetGame: nameDelegate
{
func named(name: String)
{
let indexP = IndexPath(row: 0, section: 0)
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexP) as! TableViewCell
cell.Name.text = "bkjhvghcjhkv"
//wanted to see if it changes first cell. But doesn't work
}
}
TableViewCell Class:
import UIKit
class TableViewCell: UITableViewCell
{
override func awakeFromNib()
{
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool)
{
super.setSelected(selected, animated: animated)
}
#IBOutlet weak var Name: UILabel!
#IBOutlet weak var btn1: UIButton!
#IBOutlet weak var btn2: UIButton!
}
Pop up View:
import UIKit
protocol nameDelegate
{
func named(name: String)
}
class SetName: UIViewController
{
var delegate: nameDelegate!
override func viewDidLoad()
{
super.viewDidLoad()
window.layer.borderWidth = 1
window.layer.borderColor = UIColor.white.cgColor
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
#IBAction func closePopUp(_ sender: Any)
{
if input.text != ""
{
delegate.named(name: input.text!)
}
dismiss(animated: true, completion: nil)
}
#IBOutlet weak var input: UITextField!
#IBOutlet weak var window: UIView!
}
Replace this
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexP) as! TableViewCell
with
let cell = tableView.cellForRow(at:indexP) as! TableViewCell

Passing data from a child VC back to a Collection View Cell

I have two ViewController: MainVC, and ChildVC. The MainVC includes a CollectionView with 5 cell. Each of these cell segues to the ChildVC. On this ChildVC, you can select different items which increases (or decreases) a counter on the ChildVC (the counter just reads "## selected".)
Basically, I just want this counter data on the ChildVC to be passed back onto a label of the respective MainVC cell that was tapped. For example: If user taps the second cell on the MainVC, selects 13 items on the ChildVC, then returns back to the MainVC, there will be a "13" in a label on the second cell. Then if the user taps the first cell, selects 5 items on the ChildVC, then returns back to the MainVC, there will be a "5" in a label on the first cell along with the "13" on second cell.
My progress:
I have decided that delegation is an appropriate solution for my requirements, as delegation makes it easy to pass data to/from VC's. I need assistance in passing data BACK from a ChildVC TO a CollectionView Cell.
My questions:
Along with the selected counter count (Int), what other information should be passed to and from within the protocol? (I wasn't sure if the indexPath should be passed, so that the data displays on the correct cell on the MainVC?)
On the MainVC, should the data received from the protocol ChildVC be sent to the CollectionViewCell? or the MainVC cellForItemAt method?
Update:
I have some progress below. But it's not working as intended.
In the below code, I have created both the ViewController (MainVC) and ChildVC. In the Child VC, there is a UISlider to emulate the selected counter. I would like this counter data passed back to the respective MainVC CollectionView Cells. What's happening now is the MainVC CollectionView gets a new cell added once I change the value of the slider! The 'Clear All Animals' btn needs to "zero out" the slider data for all the cells, but I haven't gotten that far yet..
View Controller (MainVC in my question above)
class ViewController: UIViewController {
var allAnimals = AnimalData.getAllAnimals()
#IBOutlet weak var mainCV: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
mainCV.dataSource = self
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "AnimalSegue" {
let childVC = segue.destination as! ChildVC
childVC.delegate = self
if let indexPath = self.mainCV.indexPath(for: sender as! AnimalCollectionViewCell) {
let animalData = self.allAnimals[indexPath.item]
childVC.animal = animalData
childVC.indexPath = indexPath
}
childVC.allIndexPaths = getAllIndexPaths()
}
}
func getAllIndexPaths() -> [IndexPath] {
var indexPaths: [IndexPath] = []
for i in 0..<mainCV.numberOfSections {
for j in 0..<mainCV.numberOfItems(inSection: i) {
indexPaths.append(IndexPath(item: j, section: i))
}
}
return indexPaths
}
}
extension ViewController: DataDelegate {
func zeroOut(for animalObject: AnimalModel, at indexPath: [IndexPath]) {
print("ZERO OUT")
self.mainCV.reloadData()
}
func updatedData(for animalObject: AnimalModel, at indexPath: IndexPath ) {
self.allAnimals[indexPath.item] = animalObject
self.mainCV.reloadItems(at: [indexPath])
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return allAnimals.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AnimalCell", for: indexPath as IndexPath) as! AnimalCollectionViewCell
let animal = allAnimals[indexPath.item]
cell.animal = animal
return cell
}
}
ChildVC
class ChildVC: UIViewController {
#IBOutlet weak var animalTitleLabel: UILabel!
#IBOutlet weak var labelCounter: UILabel!
#IBOutlet weak var sliderLabel: UISlider!
var delegate: DataDelegate?
var animal: AnimalModel?
var indexPath: IndexPath?
var allIndexPaths: [IndexPath]?
override func viewDidLoad() {
super.viewDidLoad()
animalTitleLabel.text = animal?.name
animalTitleLabel.textColor = animal?.color ?? .white
sliderLabel.value = Float(animal?.amountCounter ?? 0)
self.labelCounter.text = "\(Int(sliderLabel.value))"
}
#IBAction func closeButtonPressed(_ sender: UIButton) {
if let delegate = self.delegate,
let indexPath = self.indexPath,
let animal = self.animal {
delegate.updatedData(for: animal, at: indexPath)
}
self.dismiss(animated: true, completion: nil)
}
#IBAction func sliderChanged(_ sender: UISlider) {
let newValue = Int(sender.value)
labelCounter.text = "\(newValue)"
self.animal?.amountCounter = newValue
}
#IBAction func clearAllBtnPressed(_ sender: UIButton) {
if let delegate = self.delegate,
let all = self.allIndexPaths,
var animal = self.animal {
animal.amountCounter = 0
delegate.zeroOut(for: animal, at: all)
}
self.dismiss(animated: true, completion: nil)
}
}
Animal Collection View Cell
class AnimalCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var animalLabel: UILabel!
#IBOutlet weak var counterLabel: UILabel!
var animal: AnimalModel! {
didSet {
self.updateUI()
}
}
func updateUI() {
animalLabel.text = animal.name
counterLabel.text = "\(animal.amountCounter)"
self.backgroundColor = animal.color
}
}
Data
struct AnimalData {
static func getAllAnimals() -> [AnimalModel] {
return [
AnimalModel(name: "Cats", amountCounter: 0, color: UIColor.red),
AnimalModel(name: "Dogs", amountCounter: 0, color: UIColor.blue),
AnimalModel(name: "Fish", amountCounter: 0, color: UIColor.green),
AnimalModel(name: "Goats", amountCounter: 0, color: UIColor.yellow),
AnimalModel(name: "Lizards", amountCounter: 0, color: UIColor.cyan),
AnimalModel(name: "Birds", amountCounter: 0, color: UIColor.purple)
]
}
}
Delegate
protocol DataDelegate {
func updatedData(for animalObject: AnimalModel, at: IndexPath)
func zeroOut(for animalObject: AnimalModel, at: [IndexPath])
}
Screenshots below of what is happening. See how Dogs is being added as another cell with the value of 23? What should happen is the 0 should change to a 23 on the second blue Dogs cell. I don't understand updating the data source and reloading the correct cells??
How do i simply pass back the slider data into the cell that was originally tapped?
Any help is appreciated
You have the right idea with your delegation, but you need to be able to provide context back to your delegate; ie. what animal was being updated? To do this, either MainVC needs to keep a property of the item that is being updated, or this information needs to be provided to the ChildVC so that it can provide the information back to the MainVC. I will use the latter approach.
Protocol
protocol DataDelegate {
func updatedData(for animalObject: AnimalModel, at: IndexPath)
func clearAll()
}
MainVC
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "AnimalSegue" {
let childVC = segue.destination as! ChildVC
childVC.delegate = self
if let indexPath = self.mainCV.indexPath(for: sender as! AnimalCollectionViewCell) {
let animalData = self.allAnimals[indexPath.item]
childVC.animal = animalData
childVC.indexPath = indexPath
}
}
}
extension ViewController: DataDelegate {
func updatedData(for animalObject: AnimalModel, at indexPath: IndexPath ) {
self.allAnimals[indexPath.item] = animalObject
self.mainCV.reloadItems(at: [indexPath])
}
func clearAll() {
for index in 0..<self.allAnimals.count {
self.allAnimals[index].count =0
}
self.mainCV.reloadData()
}
ChildVC
class ChildVC: UIViewController {
#IBOutlet weak var animalTitleLabel: UILabel!
#IBOutlet weak var labelCounter: UILabel!
#IBOutlet weak var sliderLabel: UISlider!
var delegate: DataDelegate?
var animal: AnimalModel?
var indexPath: IndexPath?
override func viewDidLoad() {
super.viewDidLoad()
animalTitleLabel.text = animal?.name
animalTitleLabel.textColor = animal?.color ?? .white
sliderLabel.value = animal?.count ?? 0
self.labelCounter.text = "\(Int(sliderLabel.value))"
}
#IBAction func closeButtonPressed(_ sender: UIButton) {
if let delegate = self.delegate,
let indexPath = self.indexPath,
let animal = self.animal {
delegate.updatedData(for: animal, at: indexPath)
}
self.dismiss(animated: true, completion: nil)
}
#IBAction func sliderChanged(_ sender: UISlider) {
let newValue = Int(sender.value)
labelCounter.text = "\(newValue)"
self.animal.count = newValue
}
#IBAction func clearAllBtnPressed(_ sender: UIButton) {
delegate.clearAll()
}
}
Updated
I have updated my answer to show how you could implement the clear all. In this case there is no reason to have the ChildVC update the data model; it simply needs to invoke a delegate method to let the MainVC know that it should update the model and refresh the collection view.
I think that this gives a hint as to why the ChildVC is the wrong place for the "clear all" button; if the code feels a bit clunky then the user experience may be a bit clunky too. The clear all button should just be on your MainVC - it doesn't make sense for a button on a animal-specific view to be affecting other animals. It also isn't "discoverable"; I don't find out about the clear all until I select an animal. I realise that this is just a "learning app" but user experience is an important part of iOS app development and so it is never too early to consider it; it can also impact the way you design your code as you can see here.
So this is a very incomplete display, but i believe this solution is something that you are lookin for
class mainVC {
var counters: [Int] = [0,0,0,0,0]
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(forIndexPath: indexPath) as CustomCell
cell.counterLabel = counters[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let childVC = ChildVC()
childVC.finishedSelecting = { counter in
self.counters.insert(counter, at: indexPath.item)
childVC.dismiss()
self.theCollectionView.reloadItems(at: [indexPath])
//Or
self.theCollectionView.reloadData()
}
present(childVC, animated: true)
}
}
class childVC {
var finishedSelecting: ((Int) -> ())?
var counter = 5
#objc func finishedButtonPressed() {
finishedSelecting?(counter)
}
func count() {
counter+=1
}
}

How to detect one button in tableview cell

How to detect one button in UITableviewCell, I have 10 UIButton in UITableViewCell, next when I click on UIButton then it detects multiple buttons, (as like odd number list). my UITableView is with paging enabled. Here is my all code.
TableView
class HomeViewController: UIViewController, UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var homeTableView: UITableView!
let mainArray = [["1","2","3","4"],["5","6","7","8"],["9","10","11","12"],["13","14","15","16"]]
override func viewDidLoad() {
super.viewDidLoad()
self.homeTableView.delegate = self
self.homeTableView.dataSource = self
}
func numberOfSections(in tableView: UITableView) -> Int {
return mainArray.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return mainArray[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "HomeTableViewCell", for: indexPath) as! HomeTableViewCell
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return self.view.frame.size.height
}
}
TableViewCell
class HomeTableViewCell: UITableViewCell {
#IBOutlet weak var bookMarkBtn: UIButton!
#IBAction func bookMarkBtnAction(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
if(sender.isSelected == true)
{
sender.setImage(UIImage(named:"favorite_blue"), for: UIControlState.normal)
}
else
{
sender.setImage(UIImage(named:"favorite_white"), for: UIControlState.normal)
}
}
}
To detect a UIButton in a UITableViewCell, you can follow any of the below approaches:
1. Use UIButton IBOutlets
You can create an IBOutlet corresponding to each UIButton in the UITableViewCell and use those outlets to identify which button action is performed.
Example:
class CustomCell: UITableViewCell
{
#IBOutlet weak var button1: UIButton!
#IBOutlet weak var button2: UIButton!
#IBOutlet weak var button3: UIButton!
#IBOutlet weak var button4: UIButton!
#IBOutlet weak var button5: UIButton!
#IBAction func onTapButton(_ sender: UIButton)
{
if sender === button1
{
//button1 specific code here
}
else if sender === button2
{
//button2 specific code here
}
//and so on..
}
}
2. Use UIButton Tag property
You can provide a tag value to each of the UIButton present in the UITableViewCell and then use that tag to identify the specific button.
Example:
class CustomCell: UITableViewCell
{
#IBAction func onTapButton(_ sender: UIButton)
{
if sender.tag == 1
{
//button1 has a tag = 1
//button1 specific code here
}
else if sender.tag == 2
{
//button2 has a tag = 2
//button2 specific code here
}
//and so on..
}
}
Edit:
For setting different images in selected/unselected state of UIButton, you can use storyboard for that:
For Unselected state:
For Selected state:
Let me know if you still face any issues.
In your cellForRowAt method, do add tag number to the buttons
cell.bookMarkBtn.tag = indexPath.row;
Then
#IBAction func bookMarkBtnAction(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
if(sender.tag == 0)
{
...
} else if (sender.tag == 1)
{
...
}
}
Create protocol
protocol HomeTableViewCellDelegate {
func bookMarkBtnTapped(btn: UIButton)
}
class HomeTableViewCell: UITableViewCell {
#IBOutlet weak var bookMarkBtn: UIButton!
//add delegate var for protocol
var delegate: HomeTableViewCellDelegate?
#IBAction func bookMarkBtnAction(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
if(sender.isSelected == true)
{
sender.setImage(UIImage(named:"favorite_blue"), for: UIControlState.normal)
}
else
{
sender.setImage(UIImage(named:"favorite_white"), for: UIControlState.normal)
}
//set this which button is pressed
self.delegate?.bookMarkBtnTapped(btn: sender)
}
}
HomeViewController implement HomeTableViewCellDelegate method
class HomeViewController: HomeTableViewCellDelegate {
func bookMarkBtnTapped(btn: UIButton) {
// here btn is book mark button tapped by user from tableview cell
}
}
Use button tag for this.
In tableViewController
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "HomeTableViewCell", for: indexPath) as! HomeTableViewCell
cell.bookMarkBtn.tag = indexPath.row
cell.bookMarkBtn.addTarget(self, action: #selector(self. bookMarkBtnAction), for: .touchUpInside)
return cell
}
#objc func bookMarkBtnAction(sender: UIButton) {
if sender.tag == 0 { //or which indexpath do you want.
//code
} else if sender.tag == 1 {
//code
}
..
}
remove #IBAction func bookMarkBtnAction(_ sender: UIButton) from tableviewcell class

Resources