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
}
Related
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]
}
}
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()
}
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
I have an Expanded TableView, with HeaderView containing a checkBox, a label and a radioButton.
CheckBoxes can have multiple selection. But radioButton has only single selection. If another radio-button is selected, then previously selected button gets deselected in tableView
My problem is, whenever I select a radio-button at section 0, then radio-button at section 8 and 16 also get selected. Upon scrolling the radio-button changes it's state. Any radio-button for any section gets selected. I am aware it is due to the cell reuse property of the tableView, But I am not getting how to solve this. I have referred to numerous solutions here on SO, but none seemed to work. This issue is really troublesome for me, because of which I am not able to proceed further. Kindly guide me if wrong or if I am missing something. Help much appreciated. Thank you!
Here is my code for HeaderView of tableView:
import UIKit
import M13Checkbox
protocol HeaderViewDelegate {
func toggleHeader(header : HeaderView, section : Int)
}
protocol CustomHeaderDelegate: class {
func didTapButton(in section: Int, headerView : HeaderView)
}
class HeaderView: UITableViewHeaderFooterView {
#IBOutlet weak var stateCheckBox: M13Checkbox!
#IBOutlet weak var stateNameLabel: UILabel!
#IBOutlet weak var favouriteState: M13Checkbox!
var delegate : HeaderViewDelegate?
weak var delegateHeader: CustomHeaderDelegate?
var sectionNumber : Int!
var section : Int!
var radioButtonSelected : Bool = false {
didSet {
if radioButtonSelected {
favouriteState.checkState = .checked
}else{
favoriteState.checkState = .unchecked
}
}
}
override func awakeFromNib() {
stateCheckBox.boxType = .square
stateCheckBox = .bounce(.fill)
favouriteState.boxType = .circle
favouriteState.setMarkType(markType: .radio, animated: true)
favouriteState.stateChangeAnimation = .bounce(.stroke)
}
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
self.addGestureRecognizer(UITapGestureRecognizer(target : self, action: #selector(selectHeaderView)))
}
required init?(coder aDecoder: NSCoder) {
super.init(coder : aDecoder)
self.addGestureRecognizer(UITapGestureRecognizer(target : self, action: #selector(selectHeaderView)))
}
func selectHeaderView(gesture : UITapGestureRecognizer) {
let cell = gesture.view as! HeaderView
delegate?.toggleHeader(header: self, section: cell.section)
}
func customInit(titleLabel : String, section : Int, delegate : HeaderViewDelegate) {
self.stateNameLabel.text = titleLabel
self.section = section
self.delegate = delegate
}
#IBAction func selectPrimaryCondition(_ sender: M13Checkbox) {
// get section when favourite state radioButton is selected
delegateHeader?.didTapButton(in: sectionNumber, headerView : self)
}
override func prepareForReuse() {
// What do do hereā¦??
}
}
Here is my ViewController class:
func numberOfSections(in tableView: UITableView) -> Int {
return states.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return states[section].cities.count
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50.0
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if (states[indexPath.section].expanded) {
return 44
}else{
return 0.0
}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableHeaderFooterView(withIdentifier: "headerviewcell") as! HeaderView
var list = states[section]
headerCell.customInit(titleLabel: list.stateName, section: section, delegate: self)
return headerCell
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "subcells") as! CollapsibleCell
cell.selectionStyle = .none
cell.textLabel?.text = states[indexPath.section].cities[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// works for headers or cell??
}
func toggleHeader(header : HeaderView, section : Int){
states[section].expanded = !states[section].expanded
for i in 0 ..< states[section].cites.count {
tableView.reloadRows(at: [IndexPath(row: i, section: section)], with: .automatic)
}
}
extension ViewController: HeaderDelegate {
func didTapButton(in section: Int, headerView : HeaderView) {
print("\(section)")
}
}
Expected Output:
What I am getting:
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/