Change Data with Switch on UITableViewCell - ios

i am quite new to iOS and Swift and want to solve a Problem with UITableViewCell
I have a ControllerClass with a UITableView that has a Custom UITableViewCell called ArtistCell as following
public class ArtistCell: UITableViewCell {
var value : Bool = false
#IBOutlet weak var artistSwitch: UISwitch!
#IBOutlet weak var artistTextField: UITextField!
#IBAction func changedBoolValue(sender: UISwitch) {
self.value = sender.on
}
public func configure(text: String, enabledArtist: Bool) -> Bool{
self.artistSwitch.on = enabledArtist
self.value = enabledArtist
self.artistTextField.text = text
return self.value
}
In this class as you can see, there is a textfield and a switch. If this switch is clicked the value of this list item in my ViewController should be changed
import UIKit
class ProfileViewController: UIViewController, WCSessionDelegate, UITableViewDataSource, UITableViewDelegate {
...
#IBOutlet weak var tableView: UITableView!
var profile : UserProfile = UserProfile()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 10
...
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.profile.artists.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//get my own cell
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! ArtistCell
//initialize cell and get back the actual Value of the switch and set it to my object???
let boolValue : Bool = cell.configure( self.profile.artists[indexPath.row].name, enabledArtist: self.profile.artists[indexPath.row].display.boolValue )
//this following value should be set in my object
profile.artists[indexPath.row].display = boolValue
return cell
}
}
now i want to know how i should set the bool value of my switch to my object?

Define protocol before ArtistCell:
protocol ArtistTableViewCellDelegate {
func didChangeSwitchValue(value: Bool, artistName: String)
}
public class ArtistCell: UITableViewCell {
var delegate: ArtistTableViewCellDelegate?
var value: Bool = false
#IBOutlet weak var artistSwitch: UISwitch!
#IBOutlet weak var artistTextField: UITextField!
#IBAction func changedBoolValue(sender: UISwitch) {
self.value = sender.on
delegate?.didChangeSwitchValue(value, artistName: artistTextField.text!)
}
}
And in your ViewController:
class ProfileViewController: UIViewController, WCSessionDelegate, UITableViewDataSource, UITableViewDelegate, ArtistTableViewCellDelegate {
//...
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//get my own cell
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! ArtistCell
//initialize cell and get back the actual Value of the switch and set it to my object???
let boolValue : Bool = cell.configure( self.profile.artists[indexPath.row].name, enabledArtist: self.profile.artists[indexPath.row].display.boolValue )
//this following value should be set in my object
profile.artists[indexPath.row].display = boolValue
cell.delegate = self
return cell
}
//...
func didChangeSwitchValue(value: Bool, artistName: String) {
//do sth with your value
}
}
You can also do some refactor in ArtistCell to achieve:
func didChangeSwitchValue(value: Bool, artistID: Int)
or:
func didChangeSwitchValue(value: Bool, artist: YOUR_ARTIST_TYPE)

Now you have default value from switch when your cell is created. To set new value for dataModel in ViewController when your switch state changed you can use delegate mechanism.
Create protocol for your action:
protocol SwitchChangedDelegate {
func changeStateTo(isOn: Bool, row: Int)
}
Make your ProfileViewController class confirm this protocol:
class ProfileViewController: UIViewController, WCSessionDelegate, UITableViewDataSource, UITableViewDelegate, SwitchChangedDelegate {
...
func changeStateTo(isOn: Bool, row: Int) {
// here update your dataModel
profile.artists[row].display = isOn
}
...
}
Add to your ArtistCell delegate object with type on protocol and row variable:
var delegate: SwitchChangedDelegate?
var row: Int?
Set delegate and row at your func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath):
cell.delegate = self
cell.row = indexPath.row
Call protocol method in your changedBoolValue func:
#IBAction func changedBoolValue(sender: UISwitch) {
...
self.delegate?.changeStateTo(sender.on, row: row)
}

Related

How to pass stepper value to ViewController?

I have a custom cell that has 2 labels, myLabel and numLabel, and a stepper. I have my custom cell in a Swift file and XIB file. I want when I click + or - button on the stepper, my numLabel change with the value of the stepper. I don't know how to pass the stepper value to the viewController where I have my tableView. Later want to save the stepper value to CoreDate how can I do that?. I'm just a beginner. Thank you for helping.
MyCell.swift
import UIKit
class MyCell: UITableViewCell {
static let identifier = "MyCell"
static func nib() -> UINib {
return UINib(nibName: "MyCell", bundle: nil)
}
public func configure(with name: String, number: String) {
myLabel.text = name
numLabel.text = number
}
#IBOutlet var myLabel: UILabel!
#IBOutlet var numLabel: UILabel!
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
}
}
ViewController.swift
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
table.register(MyCell.nib(), forCellReuseIdentifier: MyCell.identifier)
table.delegate = self
table.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MyCell.identifier, for: indexPath) as! MyCell
cell.configure(with: "Item 1", number: "1")
return cell
}
}
My Screen Shot
You can do this easily with a "callback" closure:
class MyCell: UITableViewCell {
static let identifier: String = "MyCell"
#IBOutlet var myStepper: UIStepper!
#IBOutlet var numLabel: UILabel!
#IBOutlet var myLabel: UILabel!
// "callback" closure - set my controller in cellForRowAt
var callback: ((Int) -> ())?
public func configure(with name: String, number: String) {
myLabel.text = name
numLabel.text = number
}
#IBAction func stepperChanged(_ sender: UIStepper) {
let val = Int(sender.value)
numLabel.text = "\(val)"
// send value back to controller via closure
callback?(val)
}
static func nib() -> UINib {
return UINib(nibName: "MyCell", bundle: nil)
}
}
Then, in cellForRowAt:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MyCell.identifier, for: indexPath) as! MyCell
cell.configure(with: "Item 1", number: "1")
// set the "callback' closure
cell.callback = { (val) in
print("Stepper in cell at \(indexPath) changed to: \(val)")
// do what you want when the stepper value was changed
// such as updating your data array
}
return cell
}
Use a delegate for a generic approach. This allows flexibility in how your cell interacts with the tableview, and enables type checking as you would expect from Swift.
Typically, for a UITableView, you would have an array of data that drives the content of the cells. In your case, let's assume that it's MyStruct (inside your view controller):
struct MyStruct {
let name: String
var value: Int
}
var myStructs: [ MyStruct ] = [
MyStruct( name: "Name 1", value: 1 ),
MyStruct( name: "Name 2", value: 2 ),
MyStruct( name: "Name 3", value: 3 ) ]
Create MyCellDelegate, and place in it whatever methods that you require to communicate changes from the cell to the view controller. For example:
protocol MyCellDelegate: class {
func didSet( value: Int, for myStructIndex: Int )
}
class MyCell: UITableViewCell {
weak var delegate: MyCellDelegate!
var myStructIndex: Int!
...
}
For your table view, assign the delegate when dequeuing the cell, and implement the protocol.
class ViewController: MyCellDelegate, UITableViewDelegate, UITableViewDataSource {
func tableView( _ tableView: UITableView, cellForRowAt indexPath: IndexPath ) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MyCell.identifier, for: indexPath) as! MyCell
let myStruct = myStructs[indexPath.row] // You may want to ensure that you are in bounds
cell.delegate = self
cell.myStructIndex = indexPath.row
cell.configure( with: myStruct.name, number: myStruct.value )
return cell
}
func didSet( value: Int, for myStructIndex: Int ) {
// Now MyViewController sees the change.
myStructs[myStructIndex].value = value
}
}
Lastly, in your MyCell, whenever the value changes, for example in your stepper, invoke:
#IBAction func stepperChanged( _ sender: UIStepper ) {
let integerValue = Int( sender.value.round() )
numLabel.text = "\(integerValue)"
// Tell the view controller about the change: what happened, and to what cell.
self.delegate.didSet( value: integerValue, for: self.myStructIndex )
}

How to pass data on button clicked in cell to another tableview?

First: How would I be able to pass data from the ItemVC Cell to the CartVC on button Clicked (Add To Cart Button(ATC)) in the selected cell, since I am no trying to use didSelectRowAt to pass the data to the CartVC. but the ATC btn to pass the cells data to the CartVC
my Segue from the ItemVC to the CartVC is in the BarButtonItem(cartBtn) so I dont want to jump to the CartVc when pressing the ATC button but only pass selected items data to CartVC when the ATC is pressed
Second how would I be able to pass the increment/decrement value in lblQty to to the CartVC when the ATC is pressed as well to be able to present a more accurate Subtotal
import UIKit
import SDWebImage
import Firebase
class ItemCell: UITableViewCell {
weak var items: Items!
#IBOutlet weak var name: UILabel!
#IBOutlet weak var category: UILabel!
#IBOutlet weak var productImage: UIImageView!
#IBOutlet weak var weight: UILabel!
#IBOutlet weak var price: UILabel!
#IBOutlet weak var lblQty: UILabel!
#IBOutlet weak var addToCart: RoundButton!
#IBOutlet weak var plusBtn: RoundButton!
#IBOutlet weak var minusBtn: RoundButton!
func configure(withItems : Items) {
name.text = product.name
category.text = items.category
image.sd_setImage(with: URL(string: items.image))
price.text = items.price
weight.text = items.weight
}
}
import UIKit
import Firebase
import FirebaseFirestore
class ItemViewController: UITableViewController {
#IBOutlet weak var cartBtn: BarButtonItem!!
#IBOutlet weak var tableView: UITableView!
var itemSetup: [Items] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
fetchItems { (items) in
self.itemSetup = items.sorted
self.tableView.reloadData()
}
}
func fetchItems(_ completion: #escaping ([Items]) -> Void) {
// -** FireStore Code **-
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as? CartViewController {
vc.items = self.itemSetup
}
}
#objc func plusItem(_ sender: UIButton) {
// -** increase Qty Code **-
}
//Function to decrement item count
#objc func minusItem(_ sender: UIButton) {
// -** decrease Qty Code **-
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemSetup.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell") as? ItemCell else { return UITableViewCell() }
cell.configure(withItem: itemSetup[indexPath.row])
cell.lblQty.text = "\(self.imageSetup[indexPath.row].count)"
cell.plusBtn.tag = indexPath.row
cell.plusBtn.addTarget(self, action: #selector(self.plusItem(_:)), for: .touchUpInside)
cell.minusBtn.tag = indexPath.row
cell.minusBtn.addTarget(self, action: #selector(self.minusItem(_:)), for: .touchUpInside)
return cell
}
}
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 = Cart.currentCart.CartItems[indexPath.row]
cell.qty.text = "\(cart.qty)"
cell.lblMealName.text = "\(cart.items.category): \(cart.items.name)"
cell.lblSubTotal.text = "$\(cart.items.price1 * Float(cart.qty))"
cell.imageUrl // can't figure out how to pass image
return cell
}
}
import Foundation
class CartItem {
var items: Items
var qty: Int
init(items: Items, qty: Int) {
self.items = items
self.qty = qty
}
}
class Cart {
static let currentCart = Cart()
var cartItems = [CartItem]()
}
Just create a protocol to create a delegate and assign it to your cart class when the cell is initialized
protocol ItemCellDelegate {
func itemCell(didTapButton button: UIButton)
}
Then have a delegate property in the ItemCell
class ItemCell {
weak var delegate: ItemCellDelegate?
...
// Then call the delegate method when the buttons is tapped.
func buttonTapped() {
delegate?.itemCell(didTapButton: theCellsButton)
}
}
Then make your Cart conform to the delegate
extension Cart: ItemCellDelegate {
func itemCell(didTapButton button: UIButton) {
// Do you stuff with button, or any other data you want to pass in
}
}
Then set the delegate before the cell is returned.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CartCell", for: indexPath) as! CartCell
let cart = Cart.currentCart.CartItems[indexPath.row]
cell.qty.text = "\(cart.qty)"
cell.lblMealName.text = "\(cart.items.category): \(cart.items.name)"
cell.lblSubTotal.text = "$\(cart.items.price1 * Float(cart.qty))"
cell.delegate = Cart.currentCart //You should rename this static var to just 'current'
return cell
}

Get UITextField Data from Separate .XIB - Swift/XCode

Here are the binaries:
FieldStyle1.swift:
import UIKit
protocol FieldStyle1Delegate {
func textChange(text: String, tag: NSInteger)
}
class FieldStyle1: UITableViewCell, UITextFieldDelegate {
var delegate: FieldStyle1Delegate?
#IBOutlet var fullnameField: UITextField!
#IBOutlet var usernameField: UITextField!
#IBOutlet var emailField: UITextField!
#IBOutlet var passwordField: UITextField!
#IBOutlet var confirmPasswordField: UITextField!
override func awakeFromNib() {
fullnameField.delegate = self
usernameField.delegate = self
emailField.delegate = self
passwordField.delegate = self
confirmPasswordField.delegate = self
fullnameField.tag = 0
usernameField.tag = 1
emailField.tag = 2
passwordField.tag = 3
confirmPasswordField.tag = 4
}
func textFieldDidEndEditing(_ textField: UITextField) {
delegate?.textChange(text: textField.text!, tag: textField.tag)
}
}
I need to pull the #IBOutlet var passwordField: UITextField! and #IBOutlet var fullnameField: UITextField! from FieldStyle1.swift, into the ViewController.swift create a user function, where
user.username = usernameField.text, but im getting the "use of unresolved identifier "usernameField" error.
Ive tried multiple methods found on stackoverflow but all have been unsuccessful. Please help!
You can't access directly from textFields from table cell So you need to implement protocols as you implemented:
First of all, you need to give FieldStyle1Delegate from the controller to table cell like this way:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier:"identifier") as? FieldStyle1 else {
fatalError("Nil")
}
cell.delegate = self
return cell
}
Then you need to handle it in view controller and get data from table cell:
class ViewController: UIViewController, FieldStyle1Delegate {
func textChange(text: String, tag: NSInteger) {
if tag == 0 {
user.fullname = text
} else if tag == 1 {
user.username = text
} else if tag == 2 {
user.email = text
} else if tag == 3 {
user.password = text
}
}
}
1) When configuring a cell you must assign object that conforms to your FieldStyle1Delegate delegate. If you are configuring cell in you view controller you can assign self:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ...
cell.delegate = self
return cell
}
2) You must conform to that delegate:
class ViewController: FieldStyle1Delegate, UITableViewDataSource {
func textChange(text: String, tag: NSInteger) {
// now you have those values
}
}
Full example:
class ViewController: FieldStyle1Delegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ...
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ...
cell.delegate = self
return cell
}
func textChange(text: String, tag: NSInteger) {
// now you have those values
}
}
Hold reference of FieldStyle1 class in viewController.swift
Class ViewController: UIViewController {
var xibView: FieldStyle1?
func loadNib() {
self.xibView = //..load nib
}
func accessOutlets() {
User.name = xibView?.textField.text
}
}

Select UITableView's row when tapping on its UISwitch in Swift

This question has been approached here in Objective-C. But I am working in Swift and have a similar question.
Once successfully created, how do I select the UITableView's row when I tap on its UISwitch?
I have a boolean in my model and would like to toggle that boolean based on the switches on/off state.
I have some programmatically created cells that contain switches...
View Controller:
var settings : [SettingItem] = [
SettingItem(settingName: "Setting 1", switchState: true),
SettingItem(settingName: "Setting 2", switchState: true)
]
override public func tableView(_tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomSettingCell") as! SettingCell
let settingItem = settings[indexPath.row]
cell.settingsLabel.text = settingItem.settingName
cell.settingsSwitch.enabled = settingItem.switchState!
return cell
}
based off a model in SettingItem.swift:
class SettingItem: NSObject {
var settingName : String?
var switchState : Bool?
init (settingName: String?, switchState : Bool?) {
super.init()
self.settingName = settingName
self.switchState = switchState
}
}
and I have some outlets in SettingCell.swift:
class SettingCell: UITableViewCell {
#IBOutlet weak var settingsLabel: UILabel!
#IBOutlet weak var settingsSwitch: UISwitch!
#IBAction func handledSwitchChange(sender: UISwitch) {
println("switched")
}
Which produces this (please ignore the formatting):
When I want events to propagate from a cell to the containing controller, I usually define a custom delegate, like this:
protocol SettingCellDelegate : class {
func didChangeSwitchState(# sender: SettingCell, isOn: Bool)
}
use it in the cell:
class SettingCell: UITableViewCell {
#IBOutlet weak var settingsLabel: UILabel!
#IBOutlet weak var settingsSwitch: UISwitch!
weak var cellDelegate: SettingCellDelegate?
#IBAction func handledSwitchChange(sender: UISwitch) {
self.cellDelegate?.didChangeSwitchState(sender: self, isOn:settingsSwitch.on)
^^^^
}
}
implement the protocol in the view controller and set the delegate in the cell:
class ViewController : UITableViewController, SettingCellDelegate {
^^^^
override func tableView(_tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomSettingCell") as! SettingCell
let settingItem = settings[indexPath.row]
cell.settingsLabel.text = settingItem.settingName
cell.settingsSwitch.enabled = settingItem.switchState!
cell.cellDelegate = self
^^^^
return cell
}
#pragma mark - SettingCellDelegate
func didChangeSwitchState(#sender: SettingCell, isOn: Bool) {
let indexPath = self.tableView.indexPathForCell(sender)
...
}
}
When the switch is tapped, the event is propagated to the view controller, with the new status and the cell itself passed as arguments. From the cell you can obtain the index path, and then do whatever you need to, such as selecting the row etc.

iOS Swift Dequeued Reusable Cell not showing up

I followed a tutorial earlier to get the basics of the Storyboard down and I'm using that code as a reference to write the app I'm working on. I want to test my prototype cell layout, but even though I set a value to the array at viewDidLoad it still refuses to show anything.
import UIKit
class DetailViewController: UIViewController {
var cards = [Card]()
#IBOutlet weak var cardsTableView: UITableView!
#IBOutlet weak var detailDescriptionLabel: UILabel!
var detailItem: AnyObject? {
didSet {
// Update the view.
self.configureView()
}
}
func configureView() {
// Update the user interface for the detail item.
if let detail: AnyObject = self.detailItem {
if let label = self.detailDescriptionLabel {
label.text = detail.description
}
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cards.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//példányosítunk egy cellát
let cell = tableView.dequeueReusableCellWithIdentifier("CardCell", forIndexPath: indexPath) as CardCell
//kivesszük a sor adatait a listából
let card : Card = self.cards[indexPath.row]
cell.setCardNumber(card.number)
cell.layer.cornerRadius = 8
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
self.cards = [Card(number: 123456789,type: 1)]
dispatch_async(dispatch_get_main_queue(), {
self.cardsTableView.reloadData()
})
self.configureView()
}
}
I started from a Master-Detail structure and I set the class of the Detail Scene to DetailViewController and the class and identifier of the Prototype to CardCell
class Card{
let number: Int
let type: Int
init(number: Int, type: Int){
self.number = number
self.type = type
}
}
import UIKit
class CardCell: UITableViewCell {
#IBOutlet weak var cardNumber: UILabel!
func setCardNumber(number: Int){
cardNumber.text = String(number)
}
}
I'm sure it's something basic, but I've been messing with this for the second day now. Any suggestions are appreciated.
you have to set the dataSource for the table view, i guess the delegate methods get never called.
Additionally add UITableViewDataSource, not necessary but more declarative than just the implementation
class DetailViewController: UIViewController, UITableViewDataSource {

Resources