UIPickerView delegate inside of TableViewCell not called - ios

I have a TableViewCell that contains a UIPickerView. The problem is, that the delegate of UIPickerView is not getting called
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "testCell") as! TableViewCell
cell.setupCell(indexPath: indexPath.row)
return UITableViewCell()
}
}
class TableViewCell: UITableViewCell {
public var picker = UIPickerView()
private var index = 1
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
print("awakeFromNib")
picker.dataSource = self
picker.delegate = self
}
public func setupCell(indexPath: Int) {
print("setupcell")
index = indexPath
contentView.addSubview(picker)
picker.translatesAutoresizingMaskIntoConstraints = false
picker.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor).isActive = true
picker.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor).isActive = true
picker.topAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
}
extension TableViewCell: UIPickerViewDataSource, UIPickerViewDelegate {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 3
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return 10
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return "abc"
}
}
I tried to reload picker using picker.reloadAllComponents(), but then only func numberOfComponents(in pickerView: UIPickerView) is getting called. What am i missing? The problem is for sure with my cell.

You need to return cell that you setup instead of new cell UITableViewCell()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "testCell") as! TableViewCell
cell.setupCell(indexPath: indexPath.row)
return cell //UITableViewCell()
}

Related

Swift UITextField inputView with UITableView

I'm trying to set a tableView to fill a UITextField via .inputView method. And actually, it's not working at all.. I don't know what I've missed...
Here is the test code I wrote, it works fine with UIPickerView but does not show anything with UITableView.. thanks for your support !
class ViewController: UIViewController, UITextFieldDelegate, UIPickerViewDelegate, UIPickerViewDataSource, UITableViewDelegate, UITableViewDataSource {
var itemPicker = UIPickerView()
var playerTableView = UITableView()
#IBOutlet weak var textFieldOne: UITextField!
#IBOutlet weak var textFieldTwo: UITextField!
var firstTextField = ["a", "b", "c", "d"]
var secondTextField = ["A", "B", "C", "D"]
override func viewDidLoad() {
super.viewDidLoad()
textFieldOne.delegate = self
textFieldTwo.delegate = self
itemPicker.delegate = self
itemPicker.dataSource = self
playerTableView.register(UITableViewCell.self, forCellReuseIdentifier: "playerCell")
playerTableView.delegate = self
playerTableView.dataSource = self
textFieldOne.inputView = itemPicker
textFieldTwo.inputView = playerTableView
}
// PickerView
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return firstTextField.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return firstTextField[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
textFieldOne.text = firstTextField[row]
}
// TableView
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return firstTextField.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = playerTableView.dequeueReusableCell(withIdentifier: "playerCell")
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: "playerCell")
}
cell?.textLabel?.text = firstTextField[indexPath.row]
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
textFieldTwo.text = firstTextField[indexPath.row]
}
}

Passing data from a UIPickerView to a UIViewController in Swift 5

I'm currently trying to build a simple app in Swift 5 that has a main UIViewController:
class TheList: UIViewController{
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
(...)
}
The UIViewController has an Extension that utilises the UITableViewDataSource and UITableViewDelegate protocols.
extension TheList: UITableViewDelegate, UITableViewDataSource{
(...)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: "titleViewCell")!
return cell
(...)
}
It populates the table with certain dynamic UITableViewCells, that are their own, separate classes. Here's an example of one:
class AmtTableViewCell: UITableViewCell{
#IBOutlet var hoursPickerView: UIPickerView!
override func awakeFromNib() {
super.awakeFromNib()
populate()
hoursPickerView.delegate = self
hoursPickerView.dataSource = self
}
(...)
}
extension AmtTableViewCell: UIPickerViewDelegate, UIPickerViewDataSource{
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return pickerData.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return String(pickerData[row])
}
}
As you can see, that UITableViewCell has an outlet of a UIPickerView. I would like to somehow pass the data from this class's UIPickerView to my UIViewController, so that I can parse it further there. I've tried to create a method to do so and then call it in my View Controller, but from I understood I had to instantiate it first, which kills the point, as I would like to get the value from the instance the user sees on their screen.
I have also tried to create an outlet for the Picker in my View Controller and then set up its Delegate and Data Source as the AmtTableViewCell class instead of self but I've learned I can't put that UIPickerView in my UIViewController.
Any help is appreciated.
Using closures:
class AmtTableViewCell: UITableViewCell{
#IBOutlet var hoursPickerView: UIPickerView!
var selectedValueInPicker: ((String) -> Void)?
override func awakeFromNib() {
super.awakeFromNib()
populate()
hoursPickerView.delegate = self
hoursPickerView.dataSource = self
}
(...)
}
extension AmtTableViewCell: UIPickerViewDelegate, UIPickerViewDataSource{
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return pickerData.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return String(pickerData[row])
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int){
selectedValueInPicker?(String(pickerData[row]))
}
}
extension TheList: UITableViewDelegate, UITableViewDataSource{
(...)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: "titleViewCell") as? AmtTableViewCell
cell.selectedValueInPicker = { val in
//Do what is needed with the Stirng val that is comming from the cell passes in closure
}
return cell ?? AmtTableViewCell()
(...)
}
Using Protocol delegate
protocol PickerSelectedValue{
func didSelectValueInPicker(value: String)
}
class AmtTableViewCell: UITableViewCell{
#IBOutlet var hoursPickerView: UIPickerView!
var delegate: PickerSelectedValue?
override func awakeFromNib() {
super.awakeFromNib()
populate()
hoursPickerView.delegate = self
hoursPickerView.dataSource = self
}
(...)
}
extension AmtTableViewCell: UIPickerViewDelegate, UIPickerViewDataSource{
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return pickerData.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return String(pickerData[row])
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int){
delegate?.didSelectValueInPicker(String(pickerData[row]))
}
}
extension TheList: UITableViewDelegate, UITableViewDataSource,pickerSelectedValue{
(...)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: "titleViewCell") as? AmtTableViewCell
cell?.delegate = self
return cell ?? AmtTableViewCell()
(...)
func didSelectValueInPicker(value: String){
print("the selected value in picker was",value)
}
}
the delegate protocol is little big long in code, but maybe easier to understand is not confortable with closures.

UILabel in UITableViewCell returns nil

I want to set an UITableView in an UIViewController.
The UITableView has UITableViewCells which has an UILabel.
However, UITableView.UILabels return nil.
Please see the following codes and images and share your thoughts.
Thank you in advance.
I am developing this with Swift5.
import UIKit
class condimentTableCell: UITableViewCell {
#IBOutlet weak var condimentName: UILabel!
}
class SandwichViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var condimentTable: UITableView!
#IBOutlet weak var breadSelector: UIPickerView!
fileprivate let items:[product] = products().getData()
fileprivate let cart:cart = Registry.instance.liveCart
var itemNo: Int = 0
let condiments = ["Lettuce", "Mayo", "Mustard", "Purple Onions", "Tomato"]
let breads = ["Rye", "Sourdough", "Squaw", "Wheat", "White"]
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor(patternImage: UIImage(named: "background.png")!)
self.breadSelector.dataSource = self
self.breadSelector.delegate = self
self.condimentTable.register(condimentTableCell.self, forCellReuseIdentifier: "condimentCell")
self.condimentTable.delegate = self
self.condimentTable.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return condiments.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:condimentTableCell = tableView.dequeueReusableCell(withIdentifier: "condimentCell", for: indexPath) as! condimentTableCell
if (cell.condimentName == nil) {
print("nil")
} else {
cell.condimentName.text = condiments[indexPath.row]
}
print("----------------------------------------------")
return cell
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return breads.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return breads[row]
}
}
simulator
print result
Storyboard-1
Storyboard-2
Remove line
self.condimentTable.register(condimentTableCell.self, forCellReuseIdentifier: "condimentCell")
You are breaking the storyboard connection to the cell.
For detailed explanation see https://stackoverflow.com/a/44107114/669586

How to get the changed value of UIPicker to be used for a Label? - Swift-3

I have declared table cells and each cell I have added a label and a UIPicker. Once the UIPicker Value is changed, I would like to show the value in the label of the corresponding label.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet weak var myTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return 2
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let picker = UIPickerView()
let y = 200 * indexPath.row
picker.frame = CGRect(x: 200, y: y, width: 100, height: 150)
picker.delegate = self
picker.dataSource = self
picker.tag = indexPath.row
view.addSubview(picker)
let myLbl = UILabel()
myLbl.frame = CGRect(x: 100, y: y, width: 80, height: 100)
myLbl.backgroundColor = UIColor.gray
myLbl.textColor = UIColor.white
myLbl.textAlignment = .center
myLbl.tag = indexPath.row
myLbl.text = "hi" // I would like to show the value of the UI picker
view.addSubview(myLbl)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.row)
NotificationCenter.default.addObserver(self, selector: #selector(pickerChangeAction), name: NSNotification.Name("pickerChange"), object: self)
}
public func numberOfComponents(in pickerView: UIPickerView) -> Int{
return 1
}
public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int{
return myData.count
}
public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String?{
return myData[row]
}
public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int){
NotificationCenter.default.post(name: NSNotification.Name("pickerChange"), object: self)
print("Hello2")
}
func pickerChangeAction(){
print("Hello1")
}
}
Simple solution using a custom cell and a callback closure:
Create a custom cell in Interface Builder, add an UILabel and an UIPickerView.
Create a custom class PickerViewCell, which contains the outlets, the picker data source and a callback closure.
class PickerViewCell: UITableViewCell {
var pickerDataSource = ["Alpha", "Beta", "Gamma", "Delta"]
var callback : ((String) -> ())?
#IBOutlet var picker : UIPickerView!
#IBOutlet var selectedText : UILabel!
}
extension PickerViewCell : UIPickerViewDataSource, UIPickerViewDelegate
{
func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 }
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return pickerDataSource.count }
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return pickerDataSource[row] }
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { callback?(pickerDataSource[row]) }
}
In Interface Builder
Set the class of the custom cell to PickerViewCell and the identifier to PickerCell.
Connect the UI elements to the outlets.
Connect delegate and datasource of the picker to the custom cell.
Implement cellForRow this way, the callback is used to update the label text and to be able to update the data model and do other things after the user picks a value.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PickerCell", for: indexPath) as! PickerViewCell
cell.callback = { (value) in
cell.selectedText.text = value
// update the data model
}
return cell
}
If the picker instances are supposed to have different data source arrays, declare the arrays in the view controller and set the data source in cellForRow respectively.
Create a custom UITableViewCell.
Add a UILabel and UIPickerView to it.
Set the custom cell as the delegate for the picker view and move the pickers methods to the custom cell.
In the didSelectRow function update the cells corresponding UILabel
Have a look at the following link if you need help creating a custom UITableViewCell: https://www.ralfebert.de/tutorials/ios-swift-uitableviewcontroller/custom-cells/

UIPickerView value inside of UITableView(based on DateCell) change detailLabel

I tried to make my PickerView value to show on my UITableView detail Label. So I set in my FormCell.swift
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
self.detailLabel.text = "\(component)\(row)"
}
It works however, when I click next cell, my detailLabel misses previous value like this.(my default label is "01min 30sec")
Any ideas about this problem? Here is my code about this problem.
// FormCell.swift
import UIKit
class FormCell: UITableViewCell, UIPickerViewDelegate, UIPickerViewDataSource {
// outlet
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var detailLabel: UILabel!
#IBOutlet weak var pickerView: UIPickerView!
// variable
var isObserving = false
let minutes = Array(0...59)
// initialize pickerView delegate, dataSource
override func awakeFromNib() {
super.awakeFromNib()
self.pickerView.delegate = self
self.pickerView.dataSource = self
}
// tableViewCell's height setup
class var expandedHeight: CGFloat { get { return 200 } }
class var defaultheight: CGFloat { get { return 44 } }
func checkHeight() {
pickerView.isHidden = (frame.height < FormCell.expandedHeight)
}
// for pickerView
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return minutes.count
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
let pickerLabel = UILabel()
var titleData = ""
titleData = "\(minutes[row])"
pickerLabel.text = titleData
return pickerLabel
}
func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
return pickerView.frame.width / 3
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
self.detailLabel.text = "\(component)\(row)"
}
// set observer for expanding
func watchFrameChanges() {
if !isObserving {
addObserver(self, forKeyPath: "frame", options: [NSKeyValueObservingOptions.new, NSKeyValueObservingOptions.initial], context: nil)
isObserving = true;
}
}
func ignoreFrameChanges() {
if isObserving {
removeObserver(self, forKeyPath: "frame")
isObserving = false;
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "frame" {
checkHeight()
}
}
}
my Controller
// AddCircuitVC.swift
import UIKit
class AddCircuitVC: UITableViewController {
let formCellID = "formCell"
var selectedIndexPath: IndexPath?
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 9
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let itemData = dataArray[indexPath.row]
var cellID = wordCellID
cellID = formCellID
let cell = tableView.dequeueReusableCell(withIdentifier: cellID) as? FormCell
cell?.titleLabel.text = itemData[titleKey]
return cell!
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let previousIndexPath = selectedIndexPath
if indexPath == selectedIndexPath {
selectedIndexPath = nil
} else {
selectedIndexPath = indexPath
}
var indexPaths: Array<IndexPath> = []
if let previous = previousIndexPath {
indexPaths += [previous]
}
if let current = selectedIndexPath {
indexPaths += [current]
}
if indexPaths.count > 0 {
tableView.reloadRows(at: indexPaths, with: UITableViewRowAnimation.automatic)
}
}
// observer
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
(cell as! FormCell).watchFrameChanges()
}
override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
(cell as! FormCell).ignoreFrameChanges()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
for cell in tableView.visibleCells {
if cell.isKind(of: FormCell.self) {
(cell as! FormCell).ignoreFrameChanges()
}
}
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath == selectedIndexPath {
return FormCell.expandedHeight
} else {
return FormCell.defaultheight
}
}
}
You're only setting your table cell's self.detailLabel.text in your pickerView delegate method.
So when you scroll your cell off screen and then on-screen again, the table view cell "forgets" what it was set to before.
You need to modify your cellForRowAt indexPath: table view data source method to return a value for cell.detailLabel.text, and that means your ultimate data source -- the itemData dictionary in your dataArray -- needs to have a new entry for duration set by the picker.
I'd recommend passing along the itemData dictionary as a property or parameter when the customTableViewCell is created in cellForRowAt indexPath:, and when the user selects a duration, set the dictionary entry and make sure it gets saved / updated in your dataArray.
First, I make global variable timeSetUp
import UIKit
var timeSetup: String!
class AddCircuitVC: UITableViewController {
Second, I edited cellForRowAt from my Controller
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let itemData = dataArray[indexPath.row]
var cellID = formCellID
let cell = tableView.dequeueReusableCell(withIdentifier: cellID) as? FormCell
cell?.titleLabel.text = itemData[titleKey]
// indexPath Check
if indexPath != selectedIndexPath {
if let timeSetup = timeSetup {
cell?.detailLabel.text = timeSetup
print("\(timeSetup)")
}
} else {
print("from else \(timeSetup)")
// initialize value to default one.
cell?.pickerView.selectRow(1, inComponent: 0, animated: true)
cell?.pickerView.selectRow(30, inComponent: 1, animated: true)
cell?.detailLabel.text = "01min 30sec"
}
return cell!
}
Finally, I get data of detailLabel.text from my FormCell View when pickerView didSelectRow
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
self.detailLabel.text = "\(component)\(row)"
// You can access timeSetup variable, because it's global
timeSetup = self.detailLabel.text
}

Resources