Subclass UIPickerView swift - ios

I'm trying to create a subclass of UIPickerView so I can use my picker in multiple views. I'm trying to call my picker programmatically as inputView of a UITextField but I can't figure out how to initialise it correctly. I'm in doubt if this is the right approach and how I can get it to work. I hope any of you can help me.
UIPickerView subclass:
import UIKit
class GroupPicker : UIPickerView, UIPickerViewDelegate, UIPickerViewDataSource{
var cdm = CoreDataManager()
var groupObjList : [Group]!
init() {
groupObjList = cdm.groupList()
}
//MARK: - Delegates and data sources
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return groupObjList.count
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
return groupObjList[row].title
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
println("picked \(groupObjList[row].title)")
}
}
How I try to call it in the view controller:
override func viewDidLoad() {
super.viewDidLoad()
groupPicker = GroupViewPicker() //instead of UIPickerView()
groupField.inputView = groupPicker
}

You have to override your init method
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(frame: CGRect) {
super.init(frame:frame)
self.delegate = self
self.dataSource = self
}

It was easy like this.
GroupPicker : UIPickerView {
override init(frame: CGRect){
super.init(frame: frame)
self.groupObjList = cdm.groupList()
}
}
Call it:
viewDidLoad() {
groupField.inputView = GroupPicker(frame: CGRectZero)
}

I presume you will want to present this modally?
If so, you could do the following:
override func viewDidLoad() {
super.viewDidLoad()
groupPicker = GroupViewPicker() //instead of UIPickerView()
//Send the data you want your picker view to handle.
groupPicker.groupObjList = dataForPickerView
self.presentViewController(viewControllerToPresent: groupPicker, animated: , completion: nil)
}
And it should pop up and present itself modally
Tip:
This var groupObjPicker: [Group]! will blow up if you never pass in a group to this array (i.e. you dont set the property as shown above). A safer (depending on what you are trying to achieve ofc.) would be to declare it as an empty array like such var groupObjList = [Group](). Then you know that if nothing is passed to it, it will at least just return 0 when .count is called on it etc.

Related

How do you add a button to a UIPickerView?

How do you add a single button to UIPickerView that can be used to dismiss or hide the PickerView?
I found a few solutions to these problems and many did not seem to come up with the answer I wanted. This question was the closest I could find to what I was asking, but it is very outdated so I wanted to display my solution. I have a subclass of UIPickerView that I wanted to add a UIButton to be able to dismiss on. I do not want a UIPickerView with a UIToolBar inside.
The below image describes exactly what I am looking for where the done button is added to my subclass of UIPickerView
It may seem trivial that all you need to do is add a UIButton to the UIPickerView and add a target to call on a method, because I also want the PickerView to respond to user selection on the rows, pressing the Done button caused no response
Short answer: Don't do that.
Apple says not to mess with their components' view hierarchy. You should create a component that includes a picker and your button, and make them look like a single, multi-part component. That way if Apple changes the internal structure of UIPickerView in future releases it won't break you.
Create subclass of UIView as such
class CustomViewWithPicker: UIView {
let picker = UIPickerView(frame: .zero)
let pickerTitle = UILabel(frame: .zero)
let button = UIButton(frame: .zero)
let title: String = "Picker Title"
let buttonName: String = "Button"
override init(frame: CGRect) {
super.init(frame: frame)
didLoad()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
didLoad()
}
func didLoad() {
self.addSubview(picker)
self.addSubview(pickerTitle)
self.addSubview(button)
picker.backgroundColor = .tertiarySystemBackground
picker.layer.cornerRadius = 20
picker.frame = .zero
pickerTitle.text = title
pickerTitle.font = .boldSystemFont(ofSize: 22)
pickerTitle.textAlignment = .center
pickerTitle.backgroundColor = .tertiarySystemBackground
button.setTitle(buttonName, for: .normal)
button.contentHorizontalAlignment = .right
button.contentVerticalAlignment = .top
button.isSelected = true
self.updateConstraints()
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.point(inside: point, with: event) {
return super.hitTest(point, with: event)
}
guard isUserInteractionEnabled, !isHidden, alpha > 0 else {
return nil
}
for subview in subviews.reversed() {
let convertedPoint = subview.convert(point, from: self)
if let hitView = subview.hitTest(convertedPoint, with: event) {
return hitView
}
}
return nil
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func updateConstraints() {
// Make Constraints ...
}
}
In the ViewController conform to UIPickerViewDelegate and UIPickerViewDataSource
class MyViewController : UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UIGestureRecognizerDelegate {
let customView = CustomViewWithPicker()
let labels = ["label0", "label1", "label2", "label3", "label4", "label5"]
var selectedRow = 0
override func viewDidLoad() {
super.viewDidLoad()
customView.picker.delegate = self
customView.picker.dataSource = self
customView.button.addTarget(self, action: #selector(doneButtonTapped(_:)), for: .touchUpInside)
self.view.addSubview(customView)
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return labels.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return labels[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
selectedRow = row
}
#objc func doneButtonTapped(_ selectedButton: UIButton) {
if selectedButton.isSelected {
print("Done Button Tapped")
}
}
}
I looked far and wide for a UIPickerView implementation that did not rely on UIToolBar to detect a tap on the button to no avail.
Thank you to Duncan C. for the input and advice

Swift - creating a UIPickerView within a UIStackView PROGRAMMATICALLY

I'm a complete beginner to swift & iOS dev in general, so be easy on me :)
In my app, I have a horizontal StackView.
Within that StackView - I have a label and a button, and now I would like to add a PickerView that would be populated from some list of options.
I've been googling and reading threads, but the closest I've gotten was getting the PickerView to show its position (using some background color) but with no actual values inside.
This is the code where I create and customize my StackView's components:
class SingleReportInputStackView: UIStackView {
... // creating and customizing my StackView
private func getObjects() -> (UILabel, UIButton, UIPickerView) {
let myLabel: UILabel = {
... // creating UILabel
}()
let myButton: UIButton = {
... // creating UIButton
}()
class MyPicker: NSObject, UIPickerViewDelegate, UIPickerViewDataSource {
let dataArray = ["English", "Maths", "History", "German", "Science"]
let UIPicker: UIPickerView = UIPickerView()
override init() {
super.init()
self.UIPicker.delegate = self
self.UIPicker.dataSource = self
self.UIPicker.backgroundColor = UIColor.white
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return dataArray.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
let row = dataArray[row]
return row
}
}
let myPicker = MyPicker()
return (myLabel, myButton, myPicker.UIPicker)
}
...
}
Then, I add those components to my Horizontal StackView by calling setupSingleInput():
class SingleReportInputStackView: UIStackView {
...
private func setupSingleInput() {
let (myLabel, myButton, myPicker) = getObjects()
self.addArrangedSubview(myLabel)
self.addArrangedSubview(myButton)
self.addArrangedSubview(myPicker)
self.translatesAutoresizingMaskIntoConstraints = false
}
...
}
As I've said, I can see the label, the button and the PickerView's white background (looks like an empty, white rectangle).
BTW, I don't have a storyboard (if that wasn't obvious already) - I'm creating the UI programatically.
Can someone help me out? Why is my PickerView not being properly populated by my dataArray?
I'm a complete beginner to swift & iOS dev in general ...
I'd recommend starting a bit simpler... embedding a class inside a func is almost certainly not the way to go here.
The biggest problem is that you create an instance of MyPicker inside your getObjects() func, but then you return a UI element from that class, and the class instance goes away -- it goes out of scope:
private func getObjects() -> (UILabel, UIButton, UIPickerView) {
// ... all the stuff you're doing in here
let myPicker = MyPicker()
// as soon as you return, myPicker no longer exists!!!
return (myLabel, myButton, myPicker.UIPicker)
}
So, you have returned a UIPickerView, but it no longer has any code (its Delegate and DataSource) backing it.
Here's a quick modification:
class SingleReportInputStackView: UIStackView {
private var myPicker: MyPicker!
override init(frame: CGRect) {
super.init(frame: frame)
setupSingleInput()
}
required init(coder: NSCoder) {
super.init(coder: coder)
setupSingleInput()
}
private func setupSingleInput() {
let (myLabel, myButton) = getObjects()
myPicker = MyPicker()
self.addArrangedSubview(myLabel)
self.addArrangedSubview(myButton)
self.addArrangedSubview(myPicker.UIPicker)
self.translatesAutoresizingMaskIntoConstraints = false
}
private func getObjects() -> (UILabel, UIButton) {
let myLabel: UILabel = {
let v = UILabel()
v.text = "The Label"
return v
}()
let myButton: UIButton = {
let v = UIButton(type: .system)
v.setTitle("The Button", for: [])
return v
}()
return (myLabel, myButton)
}
private class MyPicker: NSObject, UIPickerViewDelegate, UIPickerViewDataSource {
let dataArray = ["English", "Maths", "History", "German", "Science"]
let UIPicker: UIPickerView = UIPickerView()
override init() {
super.init()
self.UIPicker.delegate = self
self.UIPicker.dataSource = self
self.UIPicker.backgroundColor = UIColor.white
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return dataArray.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
let row = dataArray[row]
return row
}
}
}
and a sample view controller to try it:
class UriYakirViewController: UIViewController {
let singleReportStack = SingleReportInputStackView()
override func viewDidLoad() {
super.viewDidLoad()
// a yellow-ish background so we can see the white picker view frame
view.backgroundColor = UIColor(red: 1.0, green: 0.8, blue: 0.5, alpha: 1)
view.addSubview(singleReportStack)
singleReportStack.axis = .vertical
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
singleReportStack.centerXAnchor.constraint(equalTo: g.centerXAnchor),
singleReportStack.centerYAnchor.constraint(equalTo: g.centerYAnchor),
])
}
}
That code will give you this result:

I can't set the uipickerview datasource and delegate from an outside file

I can't seem to set my outside pickerview datasource and delegate to my main view. Is there away I can set it? I want to keep my pickerview in a separate file due to other future views that might use it
This is my custom picker view
final class LenghtPickerView: UIPickerView, UIPickerViewDataSource, UIPickerViewDelegate {
let feet = Array(4...7)
let inches = Array(1...11)
private var textFieldBeginEdited: UITextField?
var selectedValue: String {
get {
return "\(feet[selectedRow(inComponent: 0)]) ft \(inches[selectedRow(inComponent: 0)]) in"
}
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0 {
return feet.count
} else {
return inches.count
}
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if component == 0 {
return String(feet[row])
} else {
return String(inches[row])
}
}
}
and in my main view declaring it
private let lenghtPickerView = LenghtPickerView()
also tried
private let lenghtPickerView: LenghtPickerView = LenghtPickerView()
cant set the datasource and delegate in order for the data to be render
lenghtPickerView.delegate = self
lenghtPickerView.dataSource = self
If you set
lenghtPickerView.delegate = self
lenghtPickerView.dataSource = self
from your Main View.
That means delegate & datasource of your picker should be adopted in your Main View
If you're just trying to get your data to show up, you can simply set the dataSource and delegate like so:
lenghtPickerView.delegate = lenghtPickerView
lenghtPickerView.dataSource = lenghtPickerView
https://ibb.co/2gRVTxJ
But if I understand your question correctly, and you're trying to get a value from a pickerview and set the textfield value from it and make it reusable, you might want to do a little differently.
Here is one option:
the LengthPickerViewDelegate are optional, if you don't need done besides setting the textfield.text value
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
let pickerViewTextField = LengthPickerView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 50))
pickerViewTextField.placeholder = "Select Length"
pickerViewTextField.center = self.view.center
pickerViewTextField.pickerDelegate = self
self.view.addSubview(pickerViewTextField)
}
}
extension MainViewController: LengthPickerViewDelegate {
func doSomething() {
//
print("Picker view value changed")
}
}
protocol LengthPickerViewDelegate: class {
// do something, pass values to mainVC if needed
func doSomething()
}
class LengthPickerView: UITextField {
let feet = Array(4...7)
let inches = Array(1...11)
lazy var pickerView: UIPickerView = {
let pickerView = UIPickerView()
pickerView.delegate = self
pickerView.dataSource = self
return pickerView
}()
weak var pickerDelegate: LengthPickerViewDelegate?
var selectedValue: String {
get {
return "\(feet[pickerView.selectedRow(inComponent: 0)]) ft \(inches[pickerView.selectedRow(inComponent: 1)]) in"
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.inputView = pickerView
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension LengthPickerView: UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0 {
return feet.count
} else {
return inches.count
}
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if component == 0 {
return String(feet[row])
} else {
return String(inches[row])
}
}
}
extension LengthPickerView: UIPickerViewDelegate {
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
// do something with selected values, if you need a calculation done
self.pickerDelegate?.doSomething()
// or if you only want to set the textfield value
self.text = selectedValue
}
}
If you trying to do delegate pickerView in UIView class, try this.
check awakeFromNib() function.
Because mine was looks like this
override class func awakeFromNib() {
super.awakeFromNib()
pickerView.delegate = self //Cant delegate
pickerView.dataSource = self
}
After a couple minutes I figured out something different here and I delete the "class" from "override class func awakeFromNib(){} "
Now its working I can delegate and my awakeFromNib() function look like this:
override func awakeFromNib() {
super.awakeFromNib()
pickerView.delegate = self
pickerView.dataSource = self
}

hiding the keyboard when I used PickerView with textField

I want to realize one simple app, but I have some trouble. I add textField and PickerView. When I tab at textField, PickerView is appeared. But standard keyboard is appeared too (I dont want it). And when I again tab on textField, PickerView don't appear. How can I receive this problem? Thank's a lot!
This is my code:
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UITextFieldDelegate {
#IBOutlet weak var pickerView1: UIPickerView!
#IBOutlet weak var textField1: UITextField!
var age = ["8", "18", "28", "38", "48"]
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return age.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if pickerView == pickerView1 {
}
return age[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if pickerView == pickerView1 {
self.textField1.text = self.age[row]
self.pickerView1.isHidden = true
}
}
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == self.textField1 {
self.pickerView1.isHidden = false
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
why you are not try the simple way, just add your pickerview as inputview of your textfield
textField1.inputView = pickerView1
This will not give you a direct way to dismiss the view since your UIPickerView has no return button, which is why I recommend to use the inputAccessoryView property to display a toolbar with a done button
let myToolbar = UIToolbar(frame: CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(320), height: CGFloat(44)))
//should code with variables to support view resizing
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(self.inputAccessoryViewDidFinish))
//using default text field delegate method here, here you could call
//myTextField.resignFirstResponder to dismiss the views
myToolbar.setItems([doneButton], animated: false)
lblcurrentText.inputAccessoryView = myToolbar
and call the function as
func inputAccessoryViewDidFinish() {
lblcurrentText.resignFirstResponder()
}
at the same time after selection on your pickerview hide the text field like
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if pickerView == pickerView1 {
self.textField1.text = self.age[row]
self.inputAccessoryViewDidFinish()
}
}
Tutorial
for example you can get the step by step tutorial in here

How to return selected value in UIPickerDelegate when created programatically

I have a viewController that has a bunch of inputs on it. Some of them are normal text but some require input from a picker. I decided to move the implementation of each PickerDelegate and PickerDataSource into a separate file to keep things a little cleaner. The data is appearing but on select of the Picker I don't know how to get the event to fire in my viewController. Currently it only fires in the PickerDelete class.
Some basic layout information is
class MyClass: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
let jobTypePickerValues = ["value1", "value2"]
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
// returns the # of rows in each component..
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return jobTypePickerValues.count
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return jobTypePickerValues[row]
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int)
{
//println("I want this in the view controller that set up the picker")
}
In my view controller which is showing the picker I have the following
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
myClassSource = MyClass()
pickerView = UIPickerView()
pickerView.dataSource = myClassSource
pickerView.delegate = myClassSource
// set the input view of the job type to be a picker view
textInput.inputView = pickerView
textInput.text = jobTypePickerValues[0]
}
You need to create one custom delegate and implement in the ViewController where you are creating pickerView.
protocol CustomPickerDelegate {
func selectedItem(item: String)
}
Now create instance of this protocol inside MyClass and implement inside the `ViewController``
class MyClass: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
let jobTypePickerValues = ["value1", "value2"]
var delegate: CustomPickerDelegate?
.. Other code and methods
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
delegate?. selectedItem(jobTypePickerValues[row])
}
}
Now set the delegate in viewDidLoad and implement the method of protocol.
class ViewController: UIViewController, CustomPickerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
myClassSource = MyClass()
myClassSource.delegate = self
pickerView = UIPickerView()
pickerView.dataSource = myClassSource
pickerView.delegate = myClassSource
// set the input view of the job type to be a picker view
textInput.inputView = pickerView
textInput.text = jobTypePickerValues[0]
}
func selectedItem(item: String) {
textInput.text = item
}
}

Resources