swift - How to set UIStepper value from another class? - ios

I have a tableview contains some tableviewcell with UIStepper. This is tableviewcell code:
class ShoppingCartItemCell: UITableViewCell {
static let reuseIdentifier = "ShoppingCartItemCell"
#IBOutlet weak var itemImage: UIImageView!
#IBOutlet weak var itemNameLabel: UILabel!
#IBOutlet weak var itemProviderLabel: UILabel!
#IBOutlet weak var itemPriceLabel: UILabel!
#IBOutlet weak var itemQuantityText: UITextField!
#IBOutlet weak var quantityStepper: UIStepper!
var oldValue = 0.0
var itemName = "" {
didSet {
itemNameLabel.text = itemName
}
}
var itemProvider = "" {
didSet {
itemProviderLabel.text = itemProvider
}
}
var itemPrice = "" {
didSet {
itemPriceLabel.text = itemPrice
}
}
var itemQuantity = "" {
didSet {
itemQuantityText.text = itemQuantity
}
}
}
And this is uistepper code in tableview:
class ShoppingCartViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
#IBAction func quantityStepper(_ sender: UIStepper) {
ShoppingCartItemCell.itemQuantity = "\(Int(sender.value))"// this way is not possible...
// How to update the quantityStepper value in ShoppingCartItemCell class here
}
}
How to update the quantityStepper value in ShoppingCartViewController class? Some people will tell me to create #IBAction of quantityStepper in ShoppingCartItemCell class but my business logic must to do this way. Please help. Thanks all.

I highly recommend to use Key-Value Observation in Swift 4, it's very easy to implement
In the cell create a NSKeyValueObservation property
var observation : NSKeyValueObservation?
In the view controller in cellForRowAt set the values for stepper and label add the observer
let item = dataSource[indexPath.row] // assuming `dataSource` is the data source array
cell.quantityStepper.value = item.quantity
cell.itemQuantityText.text = "\(item.quantity)"
cell.observation = cell.quantityStepper.observe(\.value, options: [.new]) { (stepper, change) in
cell.itemQuantityText.text = "\(change.newValue!)"
// update the data model for example
item.quantity = change.newValue!
}
Finally you have to remove the observer in didEndDisplaying after the cell has left the screen.
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
(cell as! ShoppingCartItemCell).observation = nil
}
No IBAction, no protocol / delegate. It's very simple and efficient.

Related

How to implement event on click in button inside in a tableView to show other element?

I have a tableView with action buttons, one of them are hide until the user click the other button, I was looking how to do that and I found that I have to implement a delegate like the code below:
Class TableViewCell:
import UIKit
import FLAnimatedImage
protocol OnButtonsClickDelegate:AnyObject{
func onBtnDownloadClick(cell: ListadoTableViewCell)
}
class ListadoTableViewCell: UITableViewCell {
#IBOutlet weak var lblAnterior: UILabel!
#IBOutlet weak var lblCompras: UILabel!
#IBOutlet weak var lblDevolucion: UILabel!
#IBOutlet weak var lblSaldo: UILabel!
#IBOutlet weak var lblAbonos: UILabel!
#IBOutlet weak var lblNuevo: UILabel!
#IBOutlet weak var lblDiferido: UILabel!
#IBOutlet weak var lblCliente: UILabel!
#IBOutlet weak var lblNombreCliente: UILabel!
#IBOutlet weak var spinner: FLAnimatedImageView!
#IBOutlet weak var btnDowload: UIButton!
#IBOutlet weak var btnShare: UIButton!
var onButtonsClickDelegate : OnButtonsClickDelegate!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
#IBAction func onBtnDownloadClick(_ sender: AnyObject){
onButtonsClickDelegate.onBtnDownloadClick(cell: self)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Class Controller:
class ListadoController: NavigationViewController, UITableViewDelegate, UITableViewDataSource, RefreshScrollViewDelegate,OnButtonsClickDelegate {
#IBOutlet weak var tableView: RefreshTableView!
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellListado") as! ListadoTableViewCell
let r = data[indexPath.row]
let compras = Functions.stringToFloat(str: r.compras)
let comprasn = Functions.stringToFloat(str: r.comprasn)
let abonos = Functions.stringToFloat(str: r.abonos)
let diferido = Functions.stringToFloat(str: r.diferido)
let saldomov = Functions.stringToFloat(str: r.saldomov)
cell.lblAnterior.text = Functions.moneyFormat(n: saldomov - compras - comprasn)
cell.lblCompras.text = Functions.moneyFormat(n: compras)
cell.lblDevolucion.text = Functions.moneyFormat(n: 0.0)
cell.lblSaldo.text = Functions.moneyFormat(n: saldomov - comprasn)
cell.lblAbonos.text = Functions.moneyFormat(n: abonos) + ""
cell.lblNuevo.text = Functions.moneyFormat(n: saldomov - comprasn - abonos) + ""
cell.lblDiferido.text = Functions.moneyFormat(n: diferido) + ""
cell.lblCliente.text = r.nombre.capitalized
cell.lblNombreCliente.text = r.cvecte
cell.onButtonsClickDelegate = self
if indexPath.row == data.count - 1 {
if (!last && !loading) {
loadData(page: currentPage)
}
}
return cell
}
func onBtnDownloadClick(cell: ListadoTableViewCell) {
cell.btnShare.isHidden = false
}
}
The problem is that it does not work correctly. When the user clicks the button, the other element is displayed but not only in the selected row, but also in other rows as well, how can I solve this problem?
The cell is being re-used and whatever is the state of that cell will still be there, in which case, try adding this to ListadoTableViewCell:
override func prepareForReuse() {
super.prepareForReuse()
btnShare.isHidden = true
}
You should try to save the state of the cell when you press the download button so that when the reloadData of the TableView is performed and the cellForRowAt function is called, the state of the changes in the cells is preserved, in your case the share button is shown if the download button has previously been pressed.
Here there is a project from my Github that make the functionality you need by applying MVVM Pattern https://github.com/JLPenaLopez/MyFiles I hope this helps you
This is a demostration: https://github.com/JLPenaLopez/MyFiles/blob/master/MyFilesGif.gif
I solved using an answer from another post:
Button action in custom UITableViewCell affects other cells
Thank you for your help.

how to pass data from one Tableview to another Tableview when button is pressed in cell?

Im trying to pass data from the ViewController to my CartViewController. The cells in the ViewController have 3 buttons(optionBtns) that have a price and weight label above each of them.
What Im trying to do is have the optionBtn selected pass the label data above it once the ATC button is pressed
the ATC button in the cell passes the data of image, name, category, and optionBtn data to the CartViewController cells(CartCell)
how would I be able to pass selected data to the CartVC when the ATC is pressed to present selected item Name, Image, and Category in cell with selected optionBtn data(Price & Weight)
I am also using Cloud Firestore to post data to populate my VC cells
class Cell: UITableViewCell {
weak var items: Items!
#IBOutlet weak var name: UILabel!
#IBOutlet weak var category: UILabel!
#IBOutlet weak var productImage: UIImageView!
#IBOutlet weak var weightOne: UILabel!
#IBOutlet weak var weightTwo: UILabel!
#IBOutlet weak var weightThree: UILabel!
#IBOutlet weak var priceOne: UILabel!
#IBOutlet weak var priceTwo: UILabel!
#IBOutlet weak var priceThree: UILabel!
#IBOutlet weak var addToCart: RoundButton!
#IBOutlet weak var optionBtn1: RoundButton!
#IBOutlet weak var optionBtn2: RoundButton!
#IBOutlet weak var optionBtn3: RoundButton!
var addActionHandler: (() -> Void)?
func configure(withItems items: Items) {
name.text = items.name
category.text = items.category
image.sd_setImage(with: URL(string: items.image))
priceOne.text = items.price1
priceTwo.text = items.price2
priceThree.text = items.price3
weightOne.text = items.weight1
weightTwo.text = items.weight2
weightThree.text = items.weight3
self.items = items
}
var lastSelectedButton = UIButton()
#IBAction func cartTypeSelected(_ sender: RoundButton) {
lastSelectedButton.isSelected = false; do {
self.lastSelectedButton.backgroundColor = UIColor.blue
lastSelectedButton = sender
sender.isSelected = true; do {
self.lastSelectedButton.backgroundColor = UIColor.lightGreen
}
}
#IBAction func atcBtn(_ sender: UIButton) {
self.addActionHandler?()
}
}
class CartViewController: UIViewController {
var items: Items!
#IBOutlet weak var cartTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
cartTableView.dataSource = self
cartTableView.delegate = self
}
}
extension CartViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Cart.currentCart.cartItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CartCell", for: indexPath) as! CartCell
let cart = Tray.currentCart.cartItems[indexPath.row]
cell.configure(withItems: cart)
return cell
}
}
class CartCell: UITableViewCell {
var selctedBtn: Cell?
#IBOutlet weak var lblMealName: UILabel!
#IBOutlet weak var imageUrl: UIImageView!
#IBOutlet weak var lblSubTotal: UILabel!
#IBOutlet weak var lblWeight: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func configure(withItems items: Items) {
// lblWeight.text = "\(items.weight1)"
lblMealName.text = "\(items.category): \(items.name)"
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
formatter.numberStyle = .decimal
// lblSubTotal.text = "$\(formatter.string(for: items.price1)!)"
imageUrl.sd_setImage(with: URL(string: items.imageUrl))
if selctedBtn?.optionBtn1.isSelected == true {
lblSubTotal.text = "$\(formatter.string(for: items.price1)!)"
lblWeight.text = "\(items.weight1)"
} else if selctedBtn?.optionBtn2.isSelected == true {
lblSubTotal.text = "$\(formatter.string(for: items.price2)!)"
lblWeight.text = "\(items.weight2)"
} else if selctedBtn?.optionBtn3.isSelected == true {
lblSubTotal.text = "$\(formatter.string(for: items.price3)!)"
lblWeight.text = "\(items.weight3)"
}
}
}
class Cart {
static let currentCart = Cart()
var cartItems = [Items]()
}
If the idea is for firebase to handle all of your data then the data should go through firebase and not through the viewController. ie your Firebase db should have an items collection (or perhaps one per store) and your user should have a cart collection. When the use taps the add to cart button you add the item in question to that users cart collection in firebase then show the cartViewController. The cartViewController should subscribe to the cart collection on the current user and then populate its tableview from that firebase collection.
TLDR the typical design of a firebase app is that the firebase DB is the source of truth for the app, so you should write all changes to firebase and then simply observe these collections elsewhere in the app. This also insures that if the user edits the cart on another device that it will update itself on the iOS device with the new cart items.
You can pass the values in closures.
So, in your Cell class you could change your closure var to:
var addActionHandler: ((Int) -> Void)?
Then, in your addToCart button action, something along these lines:
#IBAction func atcBtn(_ sender: UIButton) {
// pass back the user selected values
var i = 0
switch lastSelectedButton {
case optionBtn1:
i = 1
case optionBtn2:
i = 2
default:
i = 3
}
self.addActionHandler?(i)
}
Create a .selectedOption property of your Items class, you should add one (of type Int). You can use that to track the user's selection.
Modify cellForAt in Cell:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? Cell else { return UITableViewCell() }
// use var to make item mutable
var item = itemSetup[indexPath.row]
cell.configure(withItem: item)
cell.addActionHandler = { (option: Int) in
print("Option selected = \(option)")
item.selectedOption = option
Cart.currentCart.items.append(item)
}
return cell
}
In your CartCell you can make the labels like this:
if items.selectedOption == 1 {
lblSubTotal.text = "$\(formatter.string(for: items.price1)!)"
lblWeight.text = "\(items.weight1)"
} else if items.selectedOption == 2 {
lblSubTotal.text = "$\(formatter.string(for: items.price2)!)"
lblWeight.text = "\(items.weight2)"
} else if items.selectedOption == 3 {
lblSubTotal.text = "$\(formatter.string(for: items.price3)!)"
lblWeight.text = "\(items.weight3)"
}

iOS - should the UIView or the UIViewController handle the state to UI mapping?

I've read a few posts but I could not find a clear answer to this question: in the MVC framework, who should be responsible for the UI set up? I'm balancing between two options:
class ViewController: UITableViewController {
private var users: [User]
...
override func tableView(
_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = dequeueDataCell(id: "myCell") as? CellView else { return UITableViewCell() }
cell.title.text = user?.name
cell.subtitle.text = user?.nickname
cell.action.isHidden = isSelected
// other numerous UI set up on the cell
return cell
}
}
class CellView: UITableViewCell {
#IBOutlet weak var title: UILabel?
#IBOutlet weak var subtitle: UILabel?
#IBOutlet weak var action: UIButton!
}
and
class ViewController: UITableViewController {
private var users: [User]
...
override func tableView(
_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = dequeueDataCell(id: "myCell") as? CellView else { return UITableViewCell() }
cell.user = self.users[indexPath.row]
cell.isSelected = false
cell.refresh()
return cell
}
}
class CellView: UITableViewCell {
#IBOutlet private weak var title: UILabel?
#IBOutlet private weak var subtitle: UILabel?
#IBOutlet private weak var action: UIButton!
weak var user: User?
var isSelected = false
func refresh() {
title.text = user?.name
subtitle.text = user?.nickname
action.isHidden = isSelected
// other numerous UI set up on the cell
}
}
I've so far been using the first approach that is found in most iOS tutorials. But I've found that this makes the UIViewController much longer, while they already have a tendency to become long and obscure, and harder to read since it mixes UI and state logic.

Populate a Prototype Cell by Passing Object

I have a UITableView that is using prototype cells (everything in storyboard). Ideally, I would like to pass these cells an object via the cellForRowAtIndexPath and have the cell figure out how to populate it's labels etc...
setSelected seems to work, but only if the cells are not scrolled off screen. Once they are scrolled off the screen much of the cell's logic is broken. See sample code for the current approach:
class RequirementsAndContentsCell: UITableViewCell {
var represents = String()
var item = PLItem()
var requirement = PLCaseRequirement()
var content = PLCaseContent()
#IBOutlet weak var itemNameLabel: UILabel!
#IBOutlet weak var tagView: UIView!
#IBOutlet weak var tagLabel: UILabel!
#IBOutlet weak var barcodeIcon: UIImageView!
#IBOutlet weak var barcodeLabel: UILabel!
#IBOutlet weak var unitCountLabel: UILabel!
#IBOutlet weak var statusView: UIView!
#IBOutlet weak var mainLabelVerticalAlign: NSLayoutConstraint!
#IBOutlet weak var barcodeIconLeadingSpace: NSLayoutConstraint!
#IBOutlet weak var tagViewLeadingSpace: NSLayoutConstraint!
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
tagView.layer.cornerRadius = 5
if represents == "content" {
itemNameLabel.text = content.name
unitCountLabel.attributedText = createAttributedString("\(content.qty)", "\nUnits", "HelveticaNeue-Light", 12, UIColor.darkTextColor())
barcodeLabel.text = content.barcode
statusView.hidden = true
if content.isParent == true {
tagView.hidden = true
tagViewLeadingSpace.priority = 1
}
} else if represents == "requirement" {
itemNameLabel.text = requirement.name
mainLabelVerticalAlign.constant = 0
tagView.hidden = true
barcodeIcon.hidden = true
barcodeLabel.hidden = true
unitCountLabel.attributedText = createAttributedString("\(requirement.qty)", "\nUnits", "HelveticaNeue-Light", 12, UIColor.darkTextColor())
if item.isContainer == true {
updateStatusView()
} else {
statusView.hidden = true
}
}
}
func updateStatusView () {
if requirement.fulfillmentLevel == 0 {
statusView.layer.backgroundColor = UIColor.redColor().CGColor
} else if requirement.fulfillmentLevel == 1 {
statusView.layer.backgroundColor = GlobalVars.yellowColor.CGColor
} else if requirement.fulfillmentLevel == 2 {
statusView.layer.backgroundColor = GlobalVars.goodColor.CGColor
} else {
statusView.layer.backgroundColor = UIColor.orangeColor().CGColor
}
}
}
What is the proper way of achieving the desired result?
You shouldn't be trying to populate the cell in setSelected. Create a method that takes the object you're tying to pass as an argument, and call that from cellForRowAtIndexPath.
You can implement the UITableViewDelegate's function func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) so that you can update the view of the cell when it's about to be displayed.

I got the UITableViewCell, but cell's elements are nil. Why?

The following code is a cleansed & rehashed version of a previous post.
ref: This class is not key value coding-compliant for the key...why?
import Foundation
import UIKit
var x = 1
struct DiaryItem {
var title:String?
var subTitle:String?
var leftImage:UIImage?
var rightImage:UIImage?
init(title:String, subTitle:String) {
self.title = title
self.subTitle = subTitle
}
}
class DiaryTableViewCell: UITableViewCell {
#IBOutlet weak var TitleLabel: UILabel!
#IBOutlet weak var SubTitleLabel: UILabel!
#IBOutlet weak var leftImageView: UIImageView!
#IBOutlet weak var rightImageView: UIImageView!
}
class DiaryTableViewController: UITableViewController {
let kCellIdentifier = "DiaryCell"
var diaryCell:DiaryTableViewCell?
var objects = NSMutableArray() //...global var.
override func viewDidLoad() {
self.title = "My Diary"
tableView.registerClass(DiaryTableViewCell.self, forCellReuseIdentifier: kCellIdentifier)
}
// -----------------------------------------------------------------------------------------------------
// MARK: - UITableViewDelegate
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let controller = gStoryboard.instantiateViewControllerWithIdentifier("DiaryPlayerVC") as DiaryPlayerViewController
self.navigationController?.pushViewController(controller, animated: true)
}
// -----------------------------------------------------------------------------------------------------
// MARK: - UITableViewDataSource
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
// -----------------------------------------------------------------------------------------------------
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
// -----------------------------------------------------------------------------------------------------
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier) as DiaryTableViewCell?
// cell?.selectionStyle = .None
println("\(x++)) Inside cell")
cell!.TitleLabel?.text = "Hello"
cell!.TitleLabel?.backgroundColor = UIColor.blueColor()
cell!.SubTitleLabel?.text = "World"
cell!.contentView.backgroundColor = UIColor.redColor()
return cell!
}
...
}
I'm getting the cell, but no elements of that cell.
1) Inside cell
(lldb) po cell!.TitleLabel
nil
I cleaned up the code, it compiles & runs okay. The cell is loaded and painted with red so I can see it was loaded. But none of the cell's contents are instantiated.
why?
It's seeing the members of the cell now...
But now I'm getting:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<NSObject 0x7f9691594810> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key leftImageView.'
If I disconnect the outlets, I get the images:
I've added the required init() but still have the same problem:
class DiaryTableViewCell: UITableViewCell {
#IBOutlet weak var leftImageView: UIImageView!
#IBOutlet weak var rightImageView: UIImageView!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var subTitleLabel: UILabel!
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NSLog("init coder")
}
}
You have declared your properties as implicitly unwrapped optionals, signing that your are sure they are not nil. Thus there is no need to use optional chaining.
And please use lower case property names because upper case names are used for class names:
class DiaryTableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var subTitleLabel: UILabel!
#IBOutlet weak var leftImageView: UIImageView!
#IBOutlet weak var rightImageView: UIImageView!
}
Then try this:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier) as? DiaryTableViewCell {
// cell.selectionStyle = .None
println("\(x++)) Inside cell")
cell.titleLabel.text = "Hello"
cell.titleLabel.backgroundColor = UIColor.blueColor()
cell.subTitleLabel.text = "World"
cell.contentView.backgroundColor = UIColor.redColor()
return cell
}
return UITableViewCell()
}
If that crashes there must be something wrong with your outlet bindings.
Did you set your DiaryTableViewCell as the class for the nib in InterfaceBuilder?
Your tableCellClass lacks the awakeFromNib()method for nib:
class DiaryTableViewCell: UITableViewCell {
#IBOutlet weak var TitleLabel: UILabel!
#IBOutlet weak var SubTitleLabel: UILabel!
#IBOutlet weak var leftImageView: UIImageView!
#IBOutlet weak var rightImageView: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
I found the solution:
This class is not key value coding-compliant for the key...why?
Here's the repost:
I had linked the UI elements to the WRONG source:
Here's the result:

Resources