How to read the changes in UISwitch in a custom UITableViewCell - ios

Although I've found similar questions asked, I'm not able to comprehend the answers for it.
How would we read the changes in UISwitch or for that matter any element while in a UITableViewCell? Tried using a protocol, but the custom cell class complains about no initialisers. Used, a delegate, which seems to not conform to the view controller.
protocol SwitchTableViewCellDelegate {
func didChangeSwitchValue(value: Bool)
}
class SwitchTableViewCell: UITableViewCell {
var delegate: SwitchTableViewCellDelegate
var value: Bool = true
#IBOutlet weak var switchCellLabel: UILabel!
#IBOutlet weak var switchCellSwitch: UISwitch!
#IBAction func changedSwitchValue(sender: UISwitch) {
self.value = sender.on
delegate.didChangeSwitchValue(value)
}
In cellForRowAtIndexPath,
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! SwitchTableViewCell
cell.delegate = self
cell.switchCellLabel?.text = "Show Cloud Music"
cell.switchCellSwitch.on = userDefaults.boolForKey(cloudMusicKey)
Any suggestions, on how to implement this?

I would suggest using a Swift closure for this. Use the following code in your cell class:
class SwitchTableViewCell: UITableViewCell {
var callback: ((switch: UISwitch) -> Void)?
var value: Bool = true
#IBOutlet weak var switchCellLabel: UILabel!
#IBOutlet weak var switchCellSwitch: UISwitch!
#IBAction func changedSwitchValue(sender: UISwitch) {
self.value = sender.on
callback?(switch: sender)
}
Then this code in your cellForRowAtIndexPath:
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! SwitchTableViewCell
cell.callback = { (switch) -> Void in
// DO stuff here.
}
cell.switchCellLabel?.text = "Show Cloud Music"
cell.switchCellSwitch.on = userDefaults.boolForKey(cloudMusicKey)

Firstly, since there can be many cells sharing the same delegate, the delegate should know which cell calls it. Hence, your protocol method should provide the cell itself, not just its switch value. In fact, we can omit the switch value parameter since it can be queried from the cell.
protocol SwitchTableViewCellDelegate {
func switchTableViewCellDidChangeSwitchValue(cell: SwitchTableViewCell)
}
In your delegate's implementation of the protocol method, you can access the switch value like this:
func switchTableViewCellDidChangeSwitchValue(cell: SwitchTableViewCell) {
let switchValue = cell.value
}
Secondly, the delegate property can be nil so its type must be an Optional.
var delegate: SwitchTableViewCellDelegate?
To call the delegate when value changes:
delegate?.switchTableViewCellDidChangeSwitchValue(self)

Related

Updating label in UITableViewCell with UIStepper in Swift

I'm a Swift beginner and I'm trying to make a simple app for ordering food. The user could add a new order by setting food name, price and serving. After adding an order, that order will be shown on the tableView as a FoodTableViewCell, and the user could change the serving with an UIStepper called stepper in each cell. Each order is a FoodItem stored in an array called foodList, and you can see all orders listed in a tableView in ShoppingListVC.
My problem is: When I press "+" or "-" button on stepper, my servingLabel doesn't change to corresponding value. I tried to use NotificationCenter to pass serving value to stepper, and store new value back to food.serving after stepperValueChanged with delegate pattern. However, there still seems to be some bugs. I've been kind of confused after browsing lots of solutions on the Internet. Any help is appreciated.
Update
I removed NotificationCenter and addTarget related methods as #Tarun Tyagi 's suggestion. Now my UIStepper value turns back to 1 whereas the servingLabels are showing different numbers of serving. Since NotificationCenter doesn't help, how can I connect the label and stepper value together? Is it recommended to implement another delegate?
Here are my codes(Updated on July 8):
FoodItem
class FoodItem: Equatable {
static func == (lhs: FoodItem, rhs: FoodItem) -> Bool {
return lhs === rhs
}
var name: String
var price: Int
var serving: Int
var foodID: String
init(name: String, price: Int, serving: Int) {
self.name = name
self.price = price
self.serving = serving
self.foodID = UUID().uuidString
}
}
ViewController
import UIKit
class ShoppingListVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
var foodList = [FoodItem]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
...
for i in 1...5 {
let testItem = FoodItem(name: "Food\(i)", price: Int.random(in: 60...100), serving: Int.random(in: 1...10))
self.foodList.append(testItem)
}
}
// MARK: - Table view data source
...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "foodCell", for: indexPath) as! FoodTableViewCell
let food = foodList[indexPath.row]
cell.nameLabel.text = food.name
cell.priceLabel.text = "$\(String(food.price)) / serving"
cell.servingLabel.text = "\(String(food.serving)) serving"
cell.stepper.tag = indexPath.row
cell.delegate = self
return cell
}
}
// MARK: - FoodTableViewCellDelegate Method.
extension ShoppingListVC: FoodTableViewCellDelegate {
func stepper(_ stepper: UIStepper, at index: Int, didChangeValueTo newValue: Double) {
let indexPath = IndexPath(item: index, section: 0)
guard let cell = tableView.cellForRow(at: indexPath) as? FoodTableViewCell else { return }
let foodToBeUpdated = foodList[indexPath.row]
print("foodToBeUpdated.serving: \(foodToBeUpdated.serving)")
foodToBeUpdated.serving = Int(newValue)
print("Value changed in VC: \(newValue)")
cell.servingLabel.text = "\(String(format: "%.0f", newValue)) serving"
}
}
TableViewCell
import UIKit
protocol FoodTableViewCellDelegate: AnyObject {
func stepper(_ stepper: UIStepper, at index: Int, didChangeValueTo newValue: Double)
}
class FoodTableViewCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var priceLabel: UILabel!
#IBOutlet weak var servingLabel: UILabel!
#IBOutlet weak var stepper: UIStepper!
weak var delegate: FoodTableViewCellDelegate?
#IBAction func stepperValueChanged(_ sender: UIStepper) {
sender.minimumValue = 1
servingLabel.text = "\(String(format: "%.0f", sender.value)) serving"
// Pass the new value to ShoppingListVC and notify which cell to update using tag.
print("sender.value: \(sender.value)")
delegate?.stepper(stepper, at: stepper.tag, didChangeValueTo: sender.value)
}
override func awakeFromNib() {
super.awakeFromNib()
print(stepper.value)
}
}
Initially FoodTableViewCell is the ONLY target for UIStepper value changed (looking at #IBAction inside FoodTableViewCell).
When you dequeue a cell to display on screen, you call -
cell.stepper.addTarget(self, action: #selector(stepperValueChanged(_:)), for: .valueChanged)
which causes your ShoppingListVC instance to be added as an additional target every time a cellForRow call is executed.
Things to fix :
Remove all of your NotificationCenter related code from both classes.
Remove cell.stepper.addTarget() line as well.
This would give you a better idea of why it is happening this way. Update your question with these changes in case you still don't have what you want.
UPDATE
// Inside cellForRow
cell.stepper.value = food.serving
Cell Config:
protocol FoodTableViewCellDelegate: AnyObject {
func stepper(sender: FoodTableViewCell)
}
#IBAction func stepperButtonTapped(sender: UIStepper) {
delegate?.stepperButton(sender: self)
stepperLabel.text = "\(Int(countStepper.value))"
}
Controller Config:
cellForRow:
cell.countStepper.value = Double(foodList[indexPath.row].serving);
cell.stepperLabel.text = "\(Int(cell.countStepper.value))"
Delegate Method:
func stepperButton(sender: FoodTableViewCell) {
if let indexPath = tableView.indexPath(for: sender){
print(indexPath)
foodList[sender.tag].serving = Int(sender.countStepper.value)
}
}
Please check value stepper pod it will help you: Value stepper
Integrate value stepper pod and use below code for basic implementation.
import ValueStepper
let valueStepper: ValueStepper = {
let stepper = ValueStepper()
stepper.tintColor = .whiteColor()
stepper.minimumValue = 0
stepper.maximumValue = 1000
stepper.stepValue = 100
return stepper
}()
override func viewDidLoad() {
super.viewDidLoad()
valueStepper.addTarget(self, action: "valueChanged:", forControlEvents: .ValueChanged)
}
#IBAction func valueChanged1(sender: ValueStepper) {
// Use sender.value to do whatever you want
}
Its simplify custom stepper implantation.Take outlet of value stepper view in table tableview and use it.

Fetching manually entered data from CollectionView cells and storing it in an array of objects, using Swift

I am new to iOS and Swift.
I am not able to get the data which I manually entered in multiple UITextFiled present in a cell in my UICollectionView
I want to get the data from each text field continuously as soon as the user starts editing the text and then push it inside the variable in the object.
Cells Sample - There will be multiple cells
For example, the image provided in the above link is a sample of a cell, this cell holds multiple textfields, labels and buttons
Now I want to get all the data from each cell and store it in an array of object
My ViewController
extension SampleViewController:UICollectionViewDelegate,UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell=collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SampleCollectionViewCell;
return cell;
}
}
My model object class
class Singleton{
static let shared = Singleton()
var list = [CellFields].self
}
class CellFields {
var button1:bool
var button2:bool
var dropdown:String
var field1:String
var field2:String
var field3:String
var label1:String
var label2:String
}
My UICollectionViewCell
class MySampleCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var dropdown: DropDown!
#IBOutlet weak var field1: UITextField!
#IBOutlet weak var field2: UITextField!
#IBOutlet weak var field3: UITextField!
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var label2: UILabel!
#IBOutlet weak var button1: UIButton!
#IBOutlet weak var button2: UIButton!
}
I have tried all the delegate methods yet still I am not able to achieve what I want.
The result I want to achieve looks something similar to this
Singleton.shared.list
[0]
dropdown = "Dropdown"
field1 = "Something"
field2 = "Random"
field3 = "Another"
label1 = "Label"
label2 = "Label2"
button1 = true
button2 = false
[1]
dropdown = "Dropdown1"
field1 = "Something1"
field2 = "Random2"
field3 = "Another3"
label1 = "Label4"
label2 = "Label3"
button1 = false
button2 = true
...
...
...
Create something similar to the following:
// create your own delegate type for the cell
protocol MyCellDelegate {
func myTextFieldChanged(_ tf: UITextField) // call when the textfield changes
func myOtherTextFieldChanged(_ tf: UITextField) // call when the other textfield changes
func myToggleChanged(_ sw: UISwitch) // call when the switch changes
}
class MyCell: UICollectionViewCell {
#IBOutlet private var myTextField: UITextField!
#IBOutlet private var myOtherTextField: UITextField!
#IBOutlet private var myToggle: UISwitch!
private var _delegate: MyCellDelegate? // instance of above protocol type, this will generally be your VC
func initialize(withDelegate delegate: MyCellDelegate) {
myTextField.delegate = self
myOtherTextField.delegate = self
self._delegate = delegate // a textfield uses a delegate pattern
self.myToggle.addTarget(self, action: #selector(toggleValueChanged(_:)), for: .valueChanged) // a switch uses this target/selector pattern
}
}
// I like putting delegate implementations in extensions
extension MyCell: UITextFieldDelegate {
// called when a textfield changes
func textFieldDidEndEditing(_ textField: UITextField) {
if textField == myTextField {
_delegate?.myTextFieldChanged(textField) // call the protocol's method to signal the VC of the change
}
if textField == myOtherTextField {
_delegate?.myOtherTextFieldChanged(textField) // call the protocol's method to signal the VC of the change
}
}
}
extension MyCell {
// #objc is required for the target/selector pattern
#objc func toggleValueChanged(_ toggle: UISwitch) {
if toggle == myToggle {
_delegate?.myToggleChanged(toggle)
}
}
}
Then in your VC's cellForItemAt:
let cell=collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SampleCollectionViewCell;
cell.initialize(withDelegate: self)
return cell;
And also in your VC, make it a MyCellDelegate with:
extension SampleViewController: MyCellDelegate{
func myTextFieldChanged(_ tf: UITextField) {
// save new textfield value
}
func myOtherTextFieldChanged(_ tf: UITextField) {
// save new other textfield value
}
func myToggleChanged(_ sw: UISwitch) {
// save new toggle value
}
}
Ideally, you would create a single method that updates your entire form at once, but that really depends on what kind of data you have and what is optional and whatnot, I'll leave that as a challenge. But at least from this you should be able to get your form working and understand what is going on with all the delegate stuff.
You don't need a singleton to create an array of class objects:
let classFieldSets: [ClassFields] = []
Create an init method in your model class:
Pass them to the cell via cellForItemAt in your SampleViewController extension.

Indexpath.row inside custom cell class

How can I figure out the indexpath.row of an active cell in a function that's inside the custom cell class?
I use this:
protocol ItemTableViewCellDelegate: NSObjectProtocol {
func textFieldDidEndEditing(text: String, cell: ItemTableViewCell)
}
class ItemTableViewCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var itemTitle: UITextField!
#IBOutlet weak var date: UILabel!
var delegate: ItemTableViewCellDelegate?
override func awakeFromNib() {
itemTitle.delegate = self
}
func textFieldDidEndEditing(_ textField: UITextField) {
if let text = textField.text {
delegate?.textFieldDidEndEditing(text: text, cell: self)
}
//BTW: everything below this comment runs every time I want it to, so no problem about that
items.insert(itemTitle.text!, at: //(Here I want the indexpath))
}
}
So, I want to update my array as the textfield's change. In order to do that, I need to figure out the index path.row. I tried putting it in as so:
items.insert(itemTitle.text!, at: indexPath.row)
But it doesn't let me do that.
If this isn't possible to do in the cell class, I'm open to ideas how it could be done inside the main class, too.
a screenshot of my view:
Add an IndexPath variable in your ItemTableViewCell class
var indexPathForCell: IndexPath?
And in your parent view controller class cellForRowAtIndexPath:-
let cell = tableView.dequeueReusableCell(withIdentifier: "yourIdentifier", for: indexPath) as! ItemTableViewCell
cell.indexPathForCell = indexPath
return cell

Swift: I want to know what is the index path row of the button that i clicked?

I have a custom cell class given below:
class SizeAndQuantityCellView:UITableViewCell
{
#IBOutlet weak var imageview: UIImageView!
#IBOutlet weak var plusButton4x4: UIButton!
#IBOutlet weak var plusButton4x6: UIButton!
#IBOutlet weak var plusButton5x7: UIButton!
#IBOutlet weak var plusButton8x10: UIButton!
#IBOutlet weak var minusButton4x4: UIButton!
#IBOutlet weak var minusButton4x6: UIButton!
#IBOutlet weak var minusButton5x7: UIButton!
#IBOutlet weak var minusButton8x10: UIButton!
#IBOutlet weak var quantity4x4: UILabel!
#IBOutlet weak var quantity4x6: UILabel!
#IBOutlet weak var quantity5x7: UILabel!
#IBOutlet weak var quantity8x10: UILabel!
let sizeAndQuantityController = SizeAndQuantityController()
#IBAction func plusButtonClick(sender: UIButton)
{
let btnTag:Int = sender.tag
let tableView = sender.superview!.superview?.superview as! UITableView
let cellRow = tableView.indexPathForCell(self)?.row
sizeAndQuantityController.plusButtonClick(btnTag,cellRow: cellRow!)
}
#IBAction func minusButtonClick(sender: UIButton)
{
let btnTag:Int = sender.tag
let tableView = sender.superview!.superview?.superview as! UITableView
let cellRow = tableView.indexPathForCell(self)?.row
sizeAndQuantityController.plusButtonClick(btnTag,cellRow: cellRow!)
}
}
What i want to do is when i click the plus button the quantity should increase by one and when i click the minus button it should decrease by one.
Here's my controller class for that:
class SizeAndQuantityController
{
func plusButtonClick(tag:Int,cellRow:Int)
{
switch tag
{
case 13:
let quant = quantity4x4[cellRow]
quantity4x4[cellRow] = quant+1
break;
case 14:
let quant = quantity4x6[cellRow]
quantity4x6[cellRow] = quant+1
break;
case 15:
let quant = quantity5x7[cellRow]
quantity5x7[cellRow] = quant+1
break;
case 16:
let quant = quantity8x10[cellRow]
quantity8x10[cellRow] = quant+1
break;
default:
break
}
}
func minusButtonClick(tag:Int,cellRow:Int)
{
switch tag
{
case 17:
let quant = quantity4x4[cellRow]
quantity4x4[cellRow] = quant-1
break;
case 18:
let quant = quantity4x6[cellRow]
quantity4x6[cellRow] = quant-1
break;
case 19:
let quant = quantity5x7[cellRow]
quantity5x7[cellRow] = quant-1
break;
case 20:
let quant = quantity8x10[cellRow]
quantity8x10[cellRow] = quant-1
break;
default:
break
}
}
i have given different tags to all the buttons.
when i run the app it gives me the following error: "Could not cast value of type UITableViewWrapperView to UITableView" at the line where i set my tableview.
Doing sender.superview!.superview?.superview as! UITableView is very dangerous. In the transition between iOS6 and iOS7, an extra layer was actually introduced and that kind of call failed.
Rather just have a property rowIndex in cell, which you set in your cellForRowAtIndexPath. For Example:
class SizeAndQuantityCellView:UITableViewCell
{
var rowIndex: Int = 0
...
}
In your TableViewController
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myTableViewCell", for: indexPath) as! SizeAndQuantityCellView
cell.rowIndex = indexPath.row
...
return cell
}
From your code, it is not clear where quantity4x4[cellRow], for example, fits in but it seems to me that a Delegation Pattern might also be handy. I.o.w. Create a delegate protocol for SizeAndQuantityCellView and let your ViewController be the delegate of SizeAndQuantityCellView. When the buttons is tapped, fire an event to the delegate. That way your ViewController can handle the logic upon the pressing of the buttons.
A more sofisticated approach, involves the use of extensions and bitwise operator. Simplifying, you can use the tag property built-in with every UIButton, to store the whole value of of and IndexPath (that is identified by a row and a section) by packing it using bitwise operators and shifting.
Once the value is stored, you can use the computed property technique by extending your UIButton class and returning a new IndexPath that is created by unpacking the original values.
Below there's a simple extension that do the job:
extension UIButton {
func packing(low:Int, high:Int) -> Int {
//With the packing function we force our Packed number to be a 64 bit one
//we shift the high part 32bits to the left and OR the resulting value with the low part
return ((high << 32) | low)
}
func unpackHigh(packed:Int) -> Int {
//Unpacking the high part involve swifting to right the
//number in order to zero'ing all the non relevant bits.
return packed >> 32
}
func unpackLow(packed:Int) -> Int {
//Unpacking the low part involve masking the whole packed number with the max value allowed for an Int.
//note that using the Int.max function does not work as expected, returning a compile error.
//Maybe, it's a bug of compiler.
let mask = 2147483647
return mask & packed
}
//since we cannot use stored property on extensions, we need to compute realtime, every time the
//right value of our indexPath.
var indexPath:IndexPath {
get {
return IndexPath(row: unpackLow(packed: self.tag), section: unpackHigh(packed: self.tag))
}
set {
self.tag = packing(low: newValue.row, high: newValue.section)
}
}
}
and here you can find a simple application on a prototype cellForRowAtIndexPath:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let aCell = tableView.dequeueReusableCell(withIdentifier: "reuseCell") as! CustomTableViewCell
...
aCell.aButton.indexPath = indexPath
...
return aCell
}
note that you need to pass, after the dequeue, the right indexPath to the cell, in order to trigger the extension methods.

is there a way of refreshing the whole UITableView through a button that is in one of the cells?

I have a dynamically generated UITableView with many dynamic UITableViewCells and one static UITableViewCell.
The static one has a button and I want to refresh the whole table view when user presses it.
My code attached to the cell is simple:
class MyStaticCell: UITableViewCell {
#IBOutlet weak var sendCommentButton: UIButton!
#IBAction func sendCommentButtonAction(sender: AnyObject) {
//from here I want to refresh the table
}
}
How can I refresh the parent table from that button? In the class MyStaticCell I don't have any instance of the table, so that's my problem for now :|
The cleanest way to do this is through delegation. This ensures that the cell class doesn't need to know what should happen when the button is pressed; that logic can remain in your view controller where it belongs.
protocol CommentButtonProtocol {
func commentButtonTapped(sender: MyStaticCell)
}
class MyStaticCell: UITableViewCell {
#IBOutlet weak var sendCommentButton: UIButton!
var delegate: CommentButtonProtocol?
#IBAction func sendCommentButtonAction(sender: AnyObject) {
self.delegate?.commentButtonTapped(self)
}
}
Then in your view controller you can set it as the delegate in cellForRowAtIndexPath and comply with the protocol in order to handle the event:
class ViewController: UIViewController, CommentButtonProtocol {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("staticCell", forIndexPath: indexPath) as! MyStaticCell
cell.delegate = self
return cell
}
func commentButtonTapped(sender: MyStaticCell) {
// Do whatever you need to do when the button is tapped
}
}
You could access the tableView using superview.
class MyStaticCell: UITableViewCell {
#IBOutlet weak var sendCommentButton: UIButton!
#IBAction func sendCommentButtonAction(sender: AnyObject) {
(superview as? UITableView)?.reloadData()
}
}
This isn't as stable as it could be so maybe consider this extension:
extension UIResponder {
func nextResponder<T: UIResponder>(ofType type: T.Type) -> T? {
switch nextResponder() {
case let responder as T:
return responder
case let .Some(responder):
return responder.nextResponder(ofType: type)
default:
return nil
}
}
}
It allows you to find the next parent of a particular type, in the cells case, a UITableView.
class MyStaticCell: UITableViewCell {
#IBOutlet weak var sendCommentButton: UIButton!
#IBAction func sendCommentButtonAction(sender: AnyObject) {
nextResponder(ofType: UITableView.self)?.reloadData()
}
}

Resources