UIPickerView inside a Custom UITableViewCell not initialising, awakeFromNib() not being called - ios

I'm trying to get a UIPickerView inside a custom UITableViewCell to load. The cell loads fine, and I'm able to pass the data (an array of strings) from the UITableViewController to the custom cell through a delegate but the picker just loads blank. I can't for the life of me figure out why.
This seems to be a similar issue but the suggested solution (namely to reload the components of the UIPickerView after setting the pickerData array in the cellForRowAt method) doesn't seem to work.
Any help or insights greatly appreciated.
Custom Cell with UIPickerView
class GradeSelectionCell: UITableViewCell, UIPickerViewDataSource, UIPickerViewDelegate, ReusableView {
#IBOutlet var pickerView: UIPickerView!
var pickerData = [String]()
var numComponents = Int()
override func awakeFromNib() {
// Initialization code
self.pickerData = Array<String>()
self.pickerView.delegate = self
self.pickerView.dataSource = self
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return numComponents
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return pickerData.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
print(pickerData[row])
return pickerData[row]
}
}
extension GradeSelectionCell: PickerSetupDelegate {
func setupPicker(data: [String], numberOfCompents: Int){
pickerData = data
numComponents = numberOfCompents
print("PickerSetupDelegate Called")
print("pickerData \(String(describing: pickerData))")
}
}
cellForRowAt
let cell = tableView.dequeueReusableCell(withIdentifier: "GradeSelectionCell", for: indexPath) as! GradeSelectionCell
cell.setupPicker(data: grades, numberOfCompents: 1)
// cell.pickerView.reloadAllComponents()
return cell
Registering Cell in viewDidLoad
tableView.register(GradeSelectionCell.self, forCellReuseIdentifier: "GradeSelectionCell")

You're registering the cell using the cell class initializer:
func register(_ cellClass: AnyClass?, forCellReuseIdentifier identifier: String)
But it seems you're trying initialize the cell from a xib. So you should be using the UINib initializer:
func register(_ nib: UINib?, forCellReuseIdentifier identifier: String)
e.g.
tableView.register(UINib(nibName: "GradeSelectionCell", bundle: nil), forCellReuseIdentifier: "GradeSelectionCell"))
(or whatever the filename of the xib you're using is).
Otherwise, you're just loading an instance of the class directly without the user interface you laid out in the xib.
Note, if you're using a cell defined in a storyboard in your controller's view, then it would already be registered and you do not need to register it again.
And it's not crashing because you're never actually calling the pickerView property — awakeFromNib won't be called, and setupPicker just sets data. Presumably it crashes when you uncomment the line cell.pickerView.reloadAllComponents()?

Related

PickerView in TableViewCell works but not Update

i made a simple apps for practice. I'm a beginner in programming.
First of all I will explained in detail what I did and what I want to do.
Create a File a Swift file for data :
class done {
var meal: [String] = ["tomato","banana","potato","peanuts"]
}
ViewController Setup I have a Textfield and a Button to go to the next page this will add the data of the textfield that the person enter and will add to my meal data list:
import UIKit
class ViewController: UIViewController {
var data = done()
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
print("Data now : \(data.meal)")
}
#IBAction func buttonaction(_ sender: UIButton) {
var newitem = textField.text ?? ""
if textField.text == "" {
newitem = "empty"
}
data.meal.append(newitem)
performSegue(withIdentifier: "toView", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toView" {
let successVCG = segue.destination as! TableViewController
successVCG.data = self.data
}
}
}
I have a second ViewController that will display in a TableView with a TableViewcell and Inside I have a Pickerview in each row..
import UIKit
class TableViewController: UIViewController{
#IBOutlet weak var table: UITableView!
var data = done()
override func viewDidLoad() {
super.viewDidLoad()
print("Transfert Data OK : \(data.meal)")
table.delegate = self
table.dataSource = self
}
#IBAction func `return`(_ sender: Any) {
print(data.meal)
}
#IBAction func update(_ sender: Any) {
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toViewa" {
let successVCG = segue.destination as! ViewController
successVCG.data = self.data
}
}
}
extension TableViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 300
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "cellule") as? TableViewCell {
return cell
}
let cell2 = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
return cell2
}
}
And it's the configuration of the TableViewCell
import UIKit
class TableViewCell: UITableViewCell {
#IBOutlet weak var picker: UIPickerView!
var data = done()
override func awakeFromNib() {
super.awakeFromNib()
picker.delegate = self
picker.dataSource = self
}
}
extension TableViewCell: UIPickerViewDataSource, UIPickerViewDelegate {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return data.meal.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return data.meal[row]
}
}
The Picker view is display the data.meal correctly but never Updater while I add new stuff in the variable.
In my print. I see the new data update In the ViewController and the TableViewController but I try everything I could with my learning. Can't update the PickerView. I can't send the new value in the TableViewCell...mPlease help. Thank you very Much. Love to code!
This is because you don't implement the pickerView(_:didSelectRow:inComponent:) picker view's delegate method in your table view cell. So even if the user selects a new row from your picker view, the changes simply won't get reflected in your data variable. You could implement this method and change your data model accordingly from within this method.
An other thing I noticed is that you're not passing your data from the parent view to your table view cell, so the data variable in your table view cell never gets the latest data. You could simply pass the data to your cell in the cellForRow(at:) method of your table view.
Also I would recommend you to name your data model something different than done. This name is kind of confusing (because it would usually refer to an action), and the capitalization is misleading because for example when in your code you write var done = done() it feels like you're calling a function or a closure, while, from my understanding, you're actually initializing your data model.
This is because in Swift you would name by convention all your methods, functions and variables using lower camel case (for example let myVariable: Double = 0), and types such as structs, enums and classes in upper camel case (for example myData = MyDataModel()).

Setting a delegate to an outlet in a class without an initialiser

I am trying to assign a delegate to a pickerView outlet in a class with a UITableViewCell protocol.
My application is rather basic, it has an usual UIViewController class
class ADScreen: UIViewController{
#IBOutlet var tableView: UITableView!
override func viewDidLoad(){
super.viewDidLoad()
self.tableView.dataSource = self
self.tableView.delegate = self
}
}
with an extension
extension ADScreen: UITableViewDelegate, UITableViewDataSource{
(...)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: "tag")!
return cell
(...)
}
As you can see, I am populating the table with custom cells.
class TTViewCell: UITableViewCell{
#IBOutlet var pickerView: UIPickerView!
(...)
}
extension TTViewCell: UIPickerViewDelegate, UIPickerViewDataSource{
(...)
}
The problem is that the class of the cell also has a UIPickerView outlet that I would like to populate, but can't because I have no initialiser to put the pickerView.delegate = self line in, and I'm not sure where do I put it in this case. I've tried to create a custom init() function but it got really complicated over time and wouldn't work anyway with my implementation.
Use override method awakeFromNib() to initialize any properties.
class TTViewCell: UITableViewCell{
#IBOutlet var pickerView: UIPickerView!
override func awakeFromNib() {
super.awakeFromNib()
pickerView.delegate = self
}
}
extension TTViewCell: UIPickerViewDelegate, UIPickerViewDataSource{
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 10 // set number of components
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return 5 // set number of rows
}
}
Check this code.

reloaddata() function closes tableview, and returns to be previous screen

I have an app with two screens (view controller and tableview) I am trying to reload data in my table view each time user updates a filter used on the data. For example, my array holds train schedule data, I filter this data based on direction. The user updates the direction and then the tableview should reload based and update filter for direction.
I have tried to use
DispatchQueue.main.async { self.tableView.reloadData() }
I have function that is called by a button action to update direction
#IBAction func ChangeDirection(_ sender: Any) {
changeDirection()
}
func changeDirection()
{
if direction == "Southbound" {
direction = "Northbound"
}
else {
direction = "Southbound"
}
DispatchQueue.main.async { self.tableView.reloadData() }
}
I use view controller with ui picker so user can select station they want train schedule on, the ui picker accesses a singleton class that holds the train scheduled data for each station (I am wondering if this maybe the issue) maybe this needs to be accessed from tableview.
if segue.identifier == "Stations"
{
let vc = segue.destination as? TableViewController
let xmlParserSchedule = XMLParserSchedule()
xmlParserSchedule.runScheudledParser(userStation:StationsDecoder.shared.uiStationsSorted[station])
vc?.stationData=xmlParserSchedule.sortedArrival
}
I expect the tableview to reload based on new direction selected by the user, however tableview is closed and we go back to view controller ui picker screen. When I debug step into code when calling reloaddata() I have noted the memory seems to leak infinitely.
I am adding complete code from viewcontroller and table view incase this is helpful:
view controller:
import UIKit
#available(iOS 9.0, *)
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
//stores picker selection for array index in station data
var station = 0
//UI Picker Outlet
#IBOutlet weak var UIStationPicker: UIPickerView!
override func viewDidLoad() {
super.viewDidLoad()
//self.UIStationPicker.setValue(UIColor.white, forKey: "textColor")
self.UIStationPicker.delegate = self
self.UIStationPicker.dataSource = self
self.UIStationPicker.reloadAllComponents()
//check if station file is already loaded by accessing the singletons/create plist throw method
do {
try? CreatePlist.shared.createPlist()
//throw needed here to reach the catch block
throw CreatePlist.ErrorsToThrow.fileAlreadyExists
}catch {
print("file already exists")
print("time to get decoding")
//decode the station plist using singleton class method
StationsDecoder.shared.decoder()
//get stations in array of dictionaries using singlton class method
StationsDecoder.shared.getStations()
self.UIStationPicker.reloadAllComponents()
}
}
//uiPicker delegate methods
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Number of columns of data
}
func numberOfComponents(in pickerView: UIPickerView) ->Int {
return 1
}
// The number of rows of data
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int)-> Int {
return StationsDecoder.shared.uiStationsSorted.count
}
// The data to return for the row and component (column) that's being passed in
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int)-> String? {
return StationsDecoder.shared.uiStationsSorted[row]
}
func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
let titleData = StationsDecoder.shared.uiStationsSorted[row]
let myTitle = NSAttributedString(string: titleData, attributes: [NSAttributedString.Key.font:UIFont(name: "Georgia", size: 28.0)!,NSAttributedString.Key.foregroundColor:UIColor.white])
return myTitle
}
// Capture the picker view selection
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
station = row
performSegue(withIdentifier: "Stations", sender: Any?.self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if segue.identifier == "Stations"
{
let vc = segue.destination as? TableViewController
let xmlParserSchedule = XMLParserSchedule()
xmlParserSchedule.runScheudledParser(userStation:StationsDecoder.shared.uiStationsSorted[station])
vc?.stationData=xmlParserSchedule.sortedArrival
}
}
}
tableview
import UIKit
var direction:String = "Southbound"
class TableViewController: UITableViewController{
//this code here is interesting, it dismisses the presented view with orignally presented view ie tableview is replaced with initial view controller. this avoids calling dataload method again in view controller which can duplicate your data
#IBAction func GoBack(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
#IBAction func ChangeDirection(_ sender: Any) {
changeDirection()
}
func changeDirection()
{
if direction == "Southbound" {
direction = "Northbound"
}
else {
direction = "Southbound"
}
DispatchQueue.main.async { self.tableView.reloadData() }
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
//array of tuples to hold station data
var stationData: [(stationfullname:String,origin: String,destination:String,lastLocation: String, dueIn: Int, late: Int, due: Int, expArrival: String, direction: String) ] = []
//array literal, for tuples
//var data = [String:(stationfullname:String,origin: String,destination:String,lastLocation: String, dueIn: Int, late: Int, due: Int, expArrival: String, direction: String)].self
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// As long as `total` is the last case in our TableSection enum,
// this method will always be dynamically correct no mater how many table sections we add or remove.
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return stationData.filter{($0.direction == direction)}.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return direction
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "StationLevel", for: indexPath)
cell.textLabel?.text = stationData.filter{($0.direction == direction)}[indexPath.row].destination
cell.detailTextLabel?.text = "\(String(stationData.filter{($0.direction == direction)}[indexPath.row].due)) mins"
return cell
}
}
Oh Lordie,turns out I had an extra sent event for the button that was sending back to view controller, there is 4 hours I will never get back.
Thanks for the help regardless Parvin.

UIPickerView and UISearchController not displaying data

I am experiencing some trouble at the moment and was hoping one of the experts could assess and hopefully answer my question. I have made a UIPickerView and a UISearchController (which is in a UITableViewController) and both of which have no been showing the data from the arrays I made. This is a school project and is not due till Tuesday, but I am hoping I can fix these small (hopefully bugs). I will show you my ViewControllers for both the UIPickerView and the UISearchController.
So here is the UIPickerView :
import UIKit
class SecondViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UITextFieldDelegate {
#IBOutlet weak var txtTest: UITextField!
#IBOutlet weak var Picker1: ThePickerView!
var sellInvestments = ["Airbnb", "CloudFlare", "GitHub", "slack", "Snapchat", "Uber"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.Picker1.delegate = self
self.Picker1.dataSource = self
[self.Picker1.reloadAllComponents]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func pickerView(_pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
return sellInvestments[row]
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return sellInvestments.count
}
public func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
}
And now here is the UITableViewController (for the UISearch)
import UIKit
class NewTableViewController: UITableViewController, UISearchResultsUpdating {
let searchInvestments = ["Berlark Company", "Snapchat", "IBM", "Twitter", "Cornerstone on Demand", "Aukvern Capital", "Serene Technologies", "Viacom Industries", "Suvertec", "HIppit", "Avigti Technologies", "Avaret", "Sivlot", "Zebra Sci Automation", "Google", "Apple", "Facebook", "Gradience Imaging", "Vitris", "Voxtrat", "WhattsApp", "Apphat", "Nividia", "Kik", "Cyber Dust", "Turing Technologies", "Sobel Imaging Systems", "Yavid", "Tensor Automation", "Vistapint", "LinkedIn", "Yahoo", "Yelp", "TwitchTv", "OculusRift", "Lg", "Intel", "Amazon", "Sony", "Samsung", "Microsoft", "HP", "Vencore", "AT&T", "Verizon", "Dell", "MicroTech", "Flickr"]
var filteredInvestments = [String]()
var resultSearchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
self.resultSearchController = UISearchController(searchResultsController: nil)
self.resultSearchController.searchResultsUpdater = self
self.resultSearchController.dimsBackgroundDuringPresentation = false
self.resultSearchController.searchBar.sizeToFit()
self.tableView.tableHeaderView = self.resultSearchController.searchBar
self.tableView.reloadData()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if self.resultSearchController.isActive
{
return self.filteredInvestments.count
}
else {
return self.searchInvestments.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as UITableViewCell?
if self.resultSearchController.isActive
{
cell!.textLabel?.text = self.filteredInvestments[indexPath.row]
}
else {
cell!.textLabel?.text = self.searchInvestments[indexPath.row]
}
return cell!
}
func updateSearchResults(for searchController: UISearchController) {
self.filteredInvestments.removeAll(keepingCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
let array = (self.filteredInvestments as NSArray).filtered(using: searchPredicate)
self.filteredInvestments = array as! [String]
self.tableView.reloadData()
}
}
OK so there is the code of both the PickerView and Search view.
*There are no compiling errors and the app does run. I have it hooked up to my iPhone 5c and am able to run the app nicely.
Here are some pictures of the app running. (With the unfilled views)
here are the links to the images (because I don't have a 10 reputation)
http://imgur.com/a/0Gp4P - PickerView
http://imgur.com/a/JiUFe - UISearch
Thank you so much if you are able to answer this, because I have looked around the web and most of the time there was little to no help, and the ones with help, turned out not to do anything. So to summarize, the strings I put into my arrays are not showing up on either when I run it.
Thank you Very Much guys!
For your UIPickerView you forgot the function:
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView
{
let pickerLabel = UILabel() //Create label
pickerLabel.textColor = UIColor.black //Create text color
pickerLabel.text = pickerData[row] //Make text for row the one in array
pickerLabel.font = UIFont(name: "Helvetica-Light", size: 20) //Text font
pickerLabel.textAlignment = NSTextAlignment.center //Alignment
return pickerLabel
}
As for the search, use a similar function as above, just without the picker view it will be for search view. Autocomplete should help find it.

How to initialize UiPickerView inside custom tableViewCell?

I am trying to create PickerView inside tableViewCell. I made my custom cell conform UIPickerViewDelegate and UIPickerViewDataSource protocols. I send data array to cell from the controller (I think this doesn't conform to MVC pattern, may be you also can suggest me how to fix that?). But when tableview calls dequeueReusableCellWithIdentifier the cell calls pickerView function. However, pickerView function uses pickerData which is not initialized yet. How do I fix that?
Below is my code for the cell:
class PickerTableViewCell: UITableViewCell, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet weak var picker: UIPickerView!
#IBOutlet weak var title: UILabel!
var pickerData: Array<String>!
override func awakeFromNib() {
self.picker.delegate = self;
self.picker.dataSource = self;
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return pickerData.count // I get fatal error here due to pickerData is nil
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return pickerData[row]
}
}
And here is the code for the cell's initialization:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("picker", forIndexPath: indexPath) as! PickerTableViewCell
cell.title.text = fieldModel.editFieldArray[indexPath.row].title
cell.pickerData = (fieldModel.editFieldArray[indexPath.row] as! PickerEditField).pickerData
return cell
}
Thanks a lot for any help!
Your issue is that you have reload the components of PickerView so make one small change in your cellForRowAtIndexPath and just reload the components of PickerView after setting the pickerData Array.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("picker", forIndexPath: indexPath) as! PickerTableViewCell
cell.title.text = fieldModel.editFieldArray[indexPath.row].title
cell.pickerData = (fieldModel.editFieldArray[indexPath.row] as! PickerEditField).pickerData
cell.picker.reloadAllComponents();
return cell
}
Also in awakeFromNib initialize your pickerData object
override func awakeFromNib() {
self.pickerData = Array<String>()
self.picker.delegate = self;
self.picker.dataSource = self;
super.awakeFromNib()
}

Resources