How can I invoke a sub-class of UIPickerView inSwift - ios

I have a ViewController with a UIPickerView as a single control myPickerView which is of a class MyPickerView which I created as a sub-class of UIPickerView. I invoke myPickerView in ViewController viewDidLoad by myPickerView.viewDidLoad. However, this does not execute the source functions of MyPickerView.
I need a clarification of how I can make this work. My reason for MyPickerView is that it has a lot of special code that I did not want to clutter up the main ViewController. See the example code below:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var myPickerView: MyPickerView!
override func viewDidLoad() {
super.viewDidLoad()
myPickerView.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
import UIKit
var gSep = ","
class MyPickerView: UIPickerView , UIPickerViewDataSource, UIPickerViewDelegate {
var pickerData = [[" "],[" "]]
var category = [""]
var subCategory = [""]
var dictMenuList = [String:String]()
//MARK:- category/subcategory picker
func viewDidLoad() {
println("MyPickerView: viewDidLoad")
dictMenuList = ["Medical":"Sub-Cat 1.1,Sub-Cat 1.2,Sub-Cat 1.3,Sub-Cat 1.4,Sub-Cat 1.5,Sub-Cat 1.6,Sub-Cat 1.7",
"Taxes": "Sub-Cat 2.1,Sub-Cat 2.2,Sub-Cat 2.3,Sub-Cat 2.4",
"Bills": "Sub-Cat 3.1,Sub-Cat 3.2,Sub-Cat 3.3,Sub-Cat 3.4,Sub-Cat 3.5,Sub-Cat 3.6,Sub-Cat 3.7"]
println("MyPickerView dictMenuList: \(dictMenuList)")
self.reloadAllComponents()
let firstKey = self.loadPickerWithCategory(0)
self.loadPickerWithSubCategory(firstKey)
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
println("MyPickerView: numberOfComponentsInPickerView \(pickerData.count)")
return pickerData.count
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return pickerData[component].count
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if component == 0 {
let selectedKey = category[row]
loadPickerWithSubCategory(selectedKey)
}
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
return pickerData[component][row]
}
func pickerView(pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusingView view: UIView!) -> UIView
{
var pickerLabel = UILabel()
pickerLabel.textColor = UIColor.blackColor()
pickerLabel.text = pickerData[component][row]
pickerLabel.font = UIFont(name: pickerLabel.font.fontName, size: 17)
pickerLabel.textAlignment = NSTextAlignment.Center
return pickerLabel
}
func loadPickerWithCategory (row: Int) -> String{
println("loadPickerWithCategory")
category = [String](dictMenuList.keys)
println("MyPickerView: category: \(category)")
if category.isEmpty {
return ""
}
let n1 = dictMenuList.count
pickerData[0].removeAll(keepCapacity: true)
for i in 0 ..< n1
{
pickerData[0].append(category[i])
}
return category[row]
}
func loadPickerWithSubCategory (key: String) {
println("MyPickerView: loadPickerWithSubCategory")
let x = dictMenuList[key]
subCategory = x!.componentsSeparatedByString(gSep)
let n1 = subCategory.count
pickerData[1].removeAll(keepCapacity: true)
if subCategory.isEmpty {
return
}
for i in 0 ..< n1
{
pickerData[1].append(subCategory[i])
}
self.reloadAllComponents()
}
}

The method viewDidLoad is a view controller method, not a view method. A UIPickerView is a subclass of UIView, not UIViewController, so the system will not call your viewDidLoad method.
You need to override one or more of the init methods.
If you're loading your picker view from a Storyboard or XIB, you probably want to override initWithCoder.
If you're creating your picker in code, you probably want to override initWithFrame.
I sometimes create a method setup that I call from both initWithCoder: and from initWithFrame:. That way my setup code gets called regardless of how the view object is loaded.
I vaguely remember reading that there is a better way of handling this dueling initializers problem in Swift, but I don't remember what it is. (I'm still learning Swift.)
EDIT:
It just occurs to me that you can use the method awakeFromNib to do setup after your view has been loaded and all of it's outlets are set up. That's roughly equivalent to the viewDidLoad call for view controllers. I should have thought of that sooner.
(awakeFromNib is a method of NSObject, so it's a bit hard to find if you don't know it exists.)

First of all viewDidLoad() is a method of the UIViewController class and is called after the controller's view is loaded into the memory. Read more here. You can not use it in views.
So you should implement an init method inside your custom picker class. I'd recommend to override initWithFrame and initWithCoder and set up your component there.
And you will initialize your custom picker like this:
myPickerView = MyPickerView(frame: yourFrame)

Related

UITableView infinite scrolling like a wheel

I have 2 UITableViews in a single View Controller which represent a 23 cells hours table view and 60 cells minutes table view. What i want to achieve is i can scroll both table view like rolling wheel like when we have scrolled to the end, it can still be scrollable to the first table view by scrolling bottom and vice versa.
I found something like this : https://github.com/bharath2020/UITableViewTricks/tree/master/CircleView/BBTableView , but too bad it's in Obj-c.
I want to be able to achieve the following :
Where the hours and minutes can be scrolled infinitely like from 12 -> 0 or 59 -> 0
How do i do something like that in swift?
Okay So just tested out for you. Here is an example for the circular UIPickerView.
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
let maxRows = 10
private let maxElements = 10000
#IBOutlet weak var myPickerView: UIPickerView!
override func viewDidLoad() {
super.viewDidLoad()
self.myPickerView.dataSource = self;
self.myPickerView.delegate = self;
myPickerView.selectRow(maxElements / 2, inComponent: 0, animated: false)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return maxElements
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
let myRow = row % maxRows
let myString = String(format: "%i", myRow)
return myString
}
}
Now as far as creating a UIPickerPickerView with two components is concerned, You can find easily on google.
I hope it helps you to solve the problem.

trying to make picker view input for several textfields separately

I'm making a GPA calculator using swift and I've run into some problems. I created a picker view with all the letter grades (A to F). I've put 7 text fields (one for each course). I want the user to tap on a textfield and the pickerview should appear, and the grade selected would appear in the textfield. I've managed to do that for the first textfield but I dont know how to write the code that allows me to pick the grade for each textfield. I've tried different methods but I always end up changing the first textfield only.
Any help is appreciated :-)
Here is my code (gradeOne is a textfield for course 1, gradeTwo for course 2, etc.):
import UIKit
class GPA: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet var gradeOne: UITextField!
#IBOutlet var gradeTwo: UITextField!
//Grades array
var grades = ["A+/A (85-100)", "A- (80-84)", "B+ (77-79)", "B (73-76)", "B- (70-72)", "C+ (67-69)", "C (63-66)", "C- (60-62)", "D+ (57-59)", "D (53-56)", "D- (50-52)", "F (0-49)"]
//Grade 1 Picker
func numberOfComponentsInPickerView(gradePickerView: UIPickerView) -> Int {
return 1
}
func pickerView(gradePickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return grades.count
}
func pickerView(gradePickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return grades[row]
}
func pickerView(gradePickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
gradeOne.text = grades[row]
}
//Make keyboard disappear
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let gradePickerView = UIPickerView()
gradePickerView.delegate = self
gradeOne.inputView = gradePickerView
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
view.addGestureRecognizer(tap)
}
func dismissKeyboard() {
//Causes the view (or one of its embedded text fields) to resign the first responder status.
view.endEditing(true)
}
}
You should set gradePickerView as the inputView for all your textfields
gradeTwo.inputView = gradePickerView
gradeThree.inputView = gradePickerView
// set for rest of textfields
and then in your pickerView:didSelectRow: method you need to set the text to the active text field at the time. I suggest setting the active textfield using UITextFieldDelegate
var activeField: UITextField?
override func viewDidLoad() {
// Set VC as textfield's delegate
gradeTwo.delegate = self
gradeThree.delegate = self
// Set for rest of textfields
}
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
activeField = textField
}
func pickerView(gradePickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
activeField.text = grades[row]
}
Use the delegate for UITextField to set all inputviews to pickerView. Make sure you set delegate for all textfields
let gradePickerView = UIPickerView()
override func viewDidLoad() {
super.viewDidLoad()
// set delegates for all textfields
gradePickerView.delegate = self
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
view.addGestureRecognizer(tap)
}
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
textField.inputView = gradePickerView
}
I think you want to tap on 7 textfiel. Each time the pickerView will show. You picker a cell in pickerView and the textfiel(that you just tap) will show your select? If it's true. You should do fowllow this step:
1: Add tag to each textFiel: yourtextFiel.view.tag = 0 (or 1,2,3,4,...)
2: When show pickerView: Add pickerView.tag = yourtextFiel.view.tag
3: in fucion pickerView didSelectRow : get your textFiel's tag will show your selected: let mytag = pickerView.tag.
Find your textFiel with "mytag" and change your text
Googluck

How to differentiate between instances of the same view in prototyped UITableViewCells

I'm using a UITableViewCell prototype that contains a UIPickerView and use that prototype for 4 different cells with 4 different PickerView in the tableView. I use the following code to supply the cell to tableView (tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) and to set each picker to a different instance variable in order to differentiate between the pickers later (since the same UITableViewController instance is the delegate/datasource for all of them, for example).
However, when running the code, all 4 instance variables end up pointing to the same UIPickerView. How can I ensure that it uses 4 distinct UIPickerViews instead?
func PickerCell(tableView: UITableView, indexPath: NSIndexPath, inout picker: UIPickerView?) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("PickerCell")
if let pickerFromCell = cell?.contentView.viewWithTag(1) as? UIPickerView {
pickerFromCell.reloadAllComponents()
pickerFromCell.dataSource = self
pickerFromCell.delegate = self
picker = pickerFromCell
}
return cell!
}
Instead of using tag try something like this. Change your didSelectRow of PickerViewDelegate like this
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let cell = imageView.superview?.superview as! UITableViewCell //You have to use as may super as your UITableViewCell hierarchy
let indexPath = self.tabelView.indexPathForCell(cell)
self.pickerSelectedIndexArr[indexpath.row] = row
}
Also add pickerSelectedIndexArr array in your file and assign it in viewDidLoad like following way
var pickerSelectedIndexArr: [Int] = [Int]()
override func viewDidLoad() {
super.viewDidLoad()
self.pickerSelectedIndexArr = [0, 0, 0 ,0]
}
Now you can easily get all the picker selected value any time you want
Hope this will help you.
var dict: [Int, UIPickerView]
...
if dict[indexPath.row] == nil {
// Create UIPickerView and assign it to dict[indexPath.row].
}
As this question is tagged with Objective C, I am giving your answer in OBJC.
Here is what you can do, to deal with controls in reusable cell,
Tip for solving such problem, when you have controls in UICollectionViewCell or UITableViewCell, give tag to your controls depending upon your indexPath.
Giving example with collectionViewCell you can do it with tableViewCell
case 1 : If CollectionView is sectional, then your tags will be like [0][0], [1][0] ... In such case do something like this,
collectionViewCell.picker.tag = [[NSString stringWithFormat:#"%ld%ld",(long)indexPath.section,(long)indexPath.item] intValue]; // If it is sectional collection view
case 2 : If Collection View is non-sectional,do something like this,
collectionViewCell.picker.tag = [[NSString stringWithFormat:#"%ld",(long)indexPath.item] intValue]; // If it is non-sectional collection view
Hope this will solve your problem or give you idea to manage accordingly.
If you have more than one control in your cell then just give tags like indexPath.item + 1 + 2 ...
I agree with Nirav, that you should use the cell to determine the NSIndexPath of the cell in question.
Personally, I'd make the cell subclass the data source and delegate of the picker and have it take responsibility for the picker. This completely eliminates any confusion about what picker is associated with which cell. But I'd have a protocol by which the cell can inform the view controller when the user selected a value from the picker and the view controller can update the model.
For example, consider this UITableViewCell subclass:
// CustomCell.swift
import UIKit
/// Protocol that the view controller will conform to in order to receive updates
/// from the cell regarding which row in the picker was picked.
///
/// - note: This is a `class` protocol so that I can use `weak` reference.
protocol CustomCellDelegate: class {
func cell(customCell: CustomCell, pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int)
}
/// Custom cell subclass, which is the picker's data source and delegate
class CustomCell: UITableViewCell, UIPickerViewDataSource, UIPickerViewDelegate {
/// The delegate who we will inform of any picker changes.
///
/// This is weak to avoid strong reference cycle.
weak var delegate: CustomCellDelegate?
/// The array of values to be shown in the picker.
///
/// If the `values` changes, this reloads the picker view.
var values: [String]? {
didSet {
pickerView.reloadComponent(0)
}
}
/// The outlet to the picker in the cell.
#IBOutlet weak var pickerView: UIPickerView!
// MARK: UIPickerViewDataSource
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return values?.count ?? 0
}
// MARK: UIPickerViewDelegate
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return values?[row] ?? ""
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
delegate?.cell(self, pickerView: pickerView, didSelectRow: row, inComponent: component)
}
}
And then the view controller:
// ViewController.swift
import UIKit
class ViewController: UITableViewController, CustomCellDelegate {
// an array of arrays of acceptable values
let troupes = [
["Mo", "Larry", "Curly"],
["Abbott", "Costello"],
["Groucho", "Harpo", "Zeppo", "Chico", "Gummo"],
["Laurel", "Hardy"],
["Graham Chapman", "John Cleese", "Terry Gilliam", "Eric Idle", "Terry Jones", "Michael Palin"]
]
/// An array that indicates which is item is selected for each table view cell
var selectedItemForRow: [Int]!
override func viewDidLoad() {
super.viewDidLoad()
selectedItemForRow = troupes.map { _ in return 0 } // initialize the `selectedItemForRow` array with zeros
// Whatever further initialization of the view controller goes here.
}
// MARK: UITableViewDataSource
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return troupes.count
}
// Populate cell, setting delegate, list of acceptable values, and currently selected value.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomCell", forIndexPath: indexPath) as! CustomCell
cell.delegate = self
cell.values = troupes[indexPath.row]
cell.pickerView.selectRow(selectedItemForRow[indexPath.row], inComponent: 0, animated: false)
return cell
}
// MARK: CustomCellDelegate
// When the cell tells us that the user changed the selected row, let's update our model accordingly.
func cell(customCell: CustomCell, pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if let indexPath = tableView.indexPathForCell(customCell) {
selectedItemForRow[indexPath.row] = row
}
}
}

UIPickerView not displaying data even after requery

Let's say I have a stream of dogs private var dogs: Observable<[Dogs]>. Every time a new value is produced my block is called where I create a new dataSource and delegate for my UIPickerView and then within the block I call pickerView.reloadAllComponents() but my view appears with an empty pickerView, even though the dataSource and delegate are queried.
Example code:
self.dataStream
.subscribeNext {
self.dataSource = PickerViewDataSource(data: $0)
self.pickerView.dataSource = self.dataSource
self.delegate = PickerViewDelegate(data: $0, selectedRow: self._selectedRowStream)
self.pickerView.delegate = self.delegate
self.pickerView.reloadAllComponents()
}.addDisposableTo(self.disposeBag)
Debugging the dataSource and delegate I know these are queried and the reason I am keep dataSource and delegate reference in the UIViewController is due to the fact that the UIPickerView holds a weak reference for these.
This one of the last strategies I have tried and still get the same result. Any help would be appreciated. Thanks.
Update:
DogPickerViewDataSource:
class DogPickerViewDataSource: NSObject, UIPickerViewDataSource {
private var dogs: [Dog]
init(
dogs: [Dog]
) {
self.dogs = dogs
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return dogs.count
}
}
DogPickerViewDelegate:
class DogPickerViewDelegate: NSObject, UIPickerViewDelegate {
private var selectedRow: BehaviorSubject<Int>
private var dogs: [Dog]
init(
dogs: [Dog],
selectedRow: BehaviorSubject<Int>
) {
self.dogs = dogs
self.selectedRow = selectedRow
}
func pickerView(pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
let dogName = (self.dogs[row].name)!
return NSAttributedString(string: dogName)
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
self.selectedRow.onNext(row)
}
}
If I understand you correctly, on viewDidLoad you're initialising your pickerView with new delegate and data source, calling reloadAllComponents and expecting that everything should be displayed properly. But you're still displaying pickerView from previous view controller. You should try to hide picker view on previous view controller's viewWillDisappear method and display new one in viewDidAppear method of new view controller.

How to build a variable from another variable

I hope the title makes some sense but what I am trying to do is to set a field value to the item selected variable from my dataPicker. I have been able to make this work when there is only one field to set but my project will have multiple fields on each view that will call data from the dataPicker based on what field it is. I hope that is clear. Maybe as you look at the code it will.
I have set up a test project to limit things to this issue only. So my variable to tell the view what array to populate in the dataPicker is either season or sport. the field that will receive the data from the season/sport array is enterSeason and enterSport. When the picker has returned a value from season, I want to combine it with enter to create the var enterSeason to set that == itemSelected. This language is very new to me so I am trying the only way I have used before to combine text and variables in one value. It is obviously not working. Help is appreciated.
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UIGestureRecognizerDelegate {
#IBOutlet var enterSeason: UITextField!
#IBOutlet var enterSport: UITextField!
var dataPickerView = UIPickerView()
var season = ["2013", "2014", "2015"] //multi-season
//var season = ["2015"] //single-season
var sport = ["Baeball", "Football", "Basketball", "Hokey"]
var activeField = []
override func viewDidLoad() {
super.viewDidLoad()
enterSeason.inputView = dataPickerView
dataPickerView.delegate = self
dataPickerView.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return activeField.count
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
return activeField[row] as! String
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
var itemSelected = activeField[row] as! String
self.enter"\activeField".text = itemSelected
}
}
EDIT : How do you show and hide the picker? Your code anly shows variable declarations and the delegate methods... answers could vary accordingly..
Since you show the picker as text field's input view, set UITextFieldDelegate for each of these text fields .. and in the textFieldDidBeginEditing check which field becomes active with simple if else
func textFieldDidBeginEditing(textField: UITextField) {
if textField === enterSeason {
activeField = season
}
else if textField === enterSport {
activeField = sport
}
}
And in the picker selector, set value of the relevant text field as per current activeField object
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if activeField === season {
enterSeason.text = season[row] as! String
}
else if activeField === sport {
enterSeason.text = sport[row] as! String
}
}
Setting the delegate for your text fields in storboard/xib :
P.S.
- Rename activeField to activeDataArray or somethiong more appropriate
EDIT 2 : As you mentioned, second approach i have mentioned below is not suitable for you because there are too many of these fields i am still keeping it as part of the answer as it may help someone else
But what you are trying to achieve is very simple and approach is too convoluted / weird. So heres another way you can implement the whole thing..
The easiest (but still probably not the best) way is to have two instances of the UIPickerView for each field. you can directly check pickerView == seasonPickerView OR pickerView == sportPickerViewin an if else block and do the conditional programming and you wont need the activeField variable..

Resources