MY Scenario, I am trying to create button click to open UIPickerView and selecte picker data to show on button title using swift. Here, Toolbar also I would like to add into the pickerview controller.
let button = UIButton(type: .custom)
button.setTitle("",for: .normal)
button.frame = CGRect(x: CGFloat(amount_textfield.frame.size.width - 9), y: CGFloat(5), width: CGFloat(9), height: CGFloat(20))
button.addTarget(self, action: #selector(self.refresh), for: .touchUpInside)
amount_textfield.leftView = button
amount_textfield.leftViewMode = .always
#IBAction func refresh(_ sender: Any) {
// Here, I need to execute picker view
}
There are few steps that you have to follow:
Create initially hidden UIPickerView and set its delegate and dataSource
#IBOutlet weak var pickerView: UIPickerView!
override func viewDidLoad() {
super.viewDidLoad()
pickerView.delegate = self
pickerView.dataSource = self
}
Have data source array which you use for this picker view
class ViewController: UIViewController {
var array = [String]()
}
extension ViewController: UIPickerViewDelegate, UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return array.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return array[row]
}
}
Unhide your picker view when button is pressed
pickerView.isHidden = false
Integrate picker view's delegate method pickerView(_:didSelectRow:inComponent:). When row is selected, set title of button as element from your data source array at index as selected row
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
button.setTitle(array[row], for: .normal)
}
Optional: Hide picker view
pickerView.isHidden = true
Related
I have this demo app:
As you can see, the alpha of the background is changing to black according to the value.
But the problem is that there is no smooth transition:
As you can see from the GIF, the background is only changing after the scrolling is over. And I don't want it to be like that.
this is my code:
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet weak var pickerView: UIPickerView!
#IBOutlet weak var backView: UIView!
let max = 100
override func viewDidLoad() {
super.viewDidLoad()
pickerView.delegate = self
pickerView.dataSource = self
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return max + 1 // To include '0'
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
let l = UILabel(frame: .zero)
l.text = String(max - row)
l.textColor = .white
l.font = UIFont.preferredFont(forTextStyle: .title3)
l.textAlignment = .center
return l
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
backView.alpha = CGFloat(max - row) / 100
}
}
I am giving the delegate UIView instead of String, because I had an idea: to test every time the location of each row is changing, and when the row is in the middle of the screen I should update the background. But unfortunately I don't know how to do that.
Maybe you can help? or perhaps suggest any other ideas?
Thank you!
The delegate method didSelectRow is only called when the rolling stops, so this is not the place you should update your alpha. UIPickerView has no delegate method which will notify you about the change during the rolling, however the UIPickerView will call your data source and delegate methods to get the title or in your case the view for a given row so it can be displayed as the user scrolls. So what you should do is just move your alpa changing logic there:
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
backView.alpha = CGFloat(max - row) / 100
}
Note that this delegate method will be called when the UIPickerView is loaded, so maybe you should disable the alpha changing until view is not layout out correctly(maybe viewDidAppear will do it).
As the delegate method can sometimes behave unexpectedly(calling not just the next lines, but any line from the picker), we should store and also check if the row is just one step ahead or behind the last saved value, otherwise we should just ignore that. I made a simple demo to demonstrate how it works:
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
private let pickerView = UIPickerView()
private var isPickerReady = false
private var lastValue = 0
private let max = 100
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(pickerView)
pickerView.translatesAutoresizingMaskIntoConstraints = false
pickerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
pickerView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
pickerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
pickerView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
pickerView.delegate = self
pickerView.dataSource = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
isPickerReady = true
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return max
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
// Do not update the value until the view is not loaded
// Only consider delegate methods which are one step ahead or behind the last value
if row + 1 == lastValue && isPickerReady || row - 1 == lastValue && isPickerReady {
lastValue = row
view.alpha = CGFloat(max - lastValue ) / 100
}
return "Tiltle \(row)"
}
}
It seems that I cannot select the very first row in the UIPickerView when I first load it. I want the textfield to update immediately when I load the picker view. It only updates when I select something else, and then go back to the first row.
var platformData = ["XBOX", "PS4", "PC"]
var picker = UIPickerView()
#IBOutlet var platformSelected: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
picker.delegate = self
picker.dataSource = self
platformSelected.inputView = picker
let toolbar = UIToolbar()
toolbar.sizeToFit()
let doneButton = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(self.doneAction))
toolbar.items = [doneButton]
platformSelected.inputAccessoryView = toolbar
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return platformData.count
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
platformSelected.text = platformData[row]
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return platformData[row]
}
Improve your code.
platformSelected.delegate = self
......
// MARK: - UITextFieldDelegate
extension YourUIViewController: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
// TODO: - Specify index what you need. After editing it changed and you should get selected index using its position in platformData Array
let index = 0
picker.selectRow(index, inComponent: 0, animated: true)
}
}
In my app I have a settings page, and I would like a UIPickerView that works as a check box(lists items and you can enable or disable them). I've looked up different solutions and all I could find was using viewForRow and using UIButton. I am able to add the buttons and it looks good but the buttons don't seem to be tappable.
Heres my implementation:
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView{
let button = UIButton(type: .system)
button.addTarget(self, action: #selector(screenSelectorButtonPress(_:)), for: .touchUpInside)
button.tag = 0
if row == 0{
button.setTitle(Array3[row], for: .normal)
}else{
button.setTitle("\u{2610} \(Array3[row])", for: .normal)
}
return button
}
#objc func screenSelectorButtonPress(_ sender:UIButton!){
print("test")
}
The cells all populate properly but tapping them does nothing.
The functionality that I want is exactly as seen here in safari.
Is there another way to do this or am I doing something wrong with the button? Also yes I could just use switches but our Android version of the app uses a check list like how safari does and we would like to keep it consistent.
You should implement
func pickerView(_ pickerView: UIPickerView,
didSelectRow row: Int,
inComponent component: Int)
//
self.pickerView.delegate = self
//
class ViewController: UIViewController , UIPickerViewDelegate,UIPickerViewDataSource , UIGestureRecognizerDelegate {
let pickerView = UIPickerView()
override func viewDidLoad() {
super.viewDidLoad()
pickerView.frame = CGRect(x: 0, y: 30, width: self.view.frame.width, height: 300)
pickerView.delegate = self
pickerView.dataSource = self
view.addSubview(pickerView)
let tap = UITapGestureRecognizer(target: self, action: #selector(pickerTapped))
tap.delegate = self
pickerView.addGestureRecognizer(tap)
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return Array3.count
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
print(row)
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if row == 0 {
return Array3[row]
}
else {
return "\u{2610} \(Array3[row])"
}
}
#objc func pickerTapped(tapRecognizer: UITapGestureRecognizer) {
if tapRecognizer.state == .ended {
let rowHeight = self.pickerView.rowSize(forComponent: 0).height
let selectedRowFrame = self.pickerView.bounds.insetBy(dx: 0, dy: (self.pickerView.frame.height - rowHeight) / 2)
let userTappedOnSelectedRow = selectedRowFrame.contains(tapRecognizer.location(in: self.pickerView))
if userTappedOnSelectedRow {
let selectedRow = self.pickerView.selectedRow(inComponent: 0)
pickerView(self.pickerView, didSelectRow: selectedRow, inComponent: 0)
}
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
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
recently I need a solution to show a present-like pickerView for choosing data for some property, like profile.
what I did is
1, create a empty project
2, create a AViewController which will be used as subview. And I add a pickerView and a navigation bar into that view and adjust the view's height to around 260.
3, write code in that AViewController's class.
class APickerViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {
#IBOutlet weak var pickerView:UIPickerView!
let dataList = [["a","b","c","d"]]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//pickerView.dataSource = self
//pickerView.delegate = self
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return dataList.count
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return dataList[component].count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return dataList[component][row]
}
4, write code in Main ViewController to add the AViewController's View as subview.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let pickerViewController = storyboard?.instantiateViewController(withIdentifier: String(APickerViewController.self)) as! APickerViewController
self.view.addSubview(pickerViewController.view)
self.view.bringSubview(toFront: pickerViewController.view)
pickerViewController.view.frame = CGRect(x: 0, y: self.view.frame.height - 300, width: self.view.frame.width, height: 300)
}
and also one question here actually I'm not that sure the difference.
A:
let pickerViewController = storyboard?.instantiateViewController(withIdentifier: String(APickerViewController.self)) as! APickerViewController
B:
let pickerViewController = APickerViewController()
What's the different of A and B ? If I do some #IBOutlet settings by Interface Builder will be affected or what ?
5, go and ... get weired ...
the function
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return dataList[component].count
}
sometime's get called many times and sometimes not get called.
and the data was not show in pickerView and sometimes only show row 1,2 and no others and if I flick up or down those 1,2 row data disappeared.
if anyone have a better solution of using pickerView as option popup window ?
Help.
Thanks
class EditViewController: UIViewController,UITextFieldDelegate,UIPickerViewDelegate{
var picker : UIPickerView = UIPickerView()
var toolBar : UIToolbar = UIToolbar()
#IBOutlet var txtFieldState: UITextField!
var stateArray = ["Andaman & Nicobar Island","Andhra Pradesh","Arunachal Pradesh","Assam","Bihar","Chandigarh","Chhattisgarh","Dadra & Nagar"]
override func viewDidLoad() {
super.viewDidLoad()
txtFieldState.delegate = self
}
func textFieldDidBeginEditing(textField: UITextField) {
if textField == txtFieldState{
txtFieldState.inputView = picker
txtFieldState.inputAccessoryView = toolBar
self.configurePicker()
}
}
func configurePicker()
{
picker.alpha = 1.0
picker.backgroundColor = UIColor(red:241/255.0, green:241/255.0, blue:241/255.0, alpha: 1.0)
picker.showsSelectionIndicator = true
picker.delegate = self
picker.selectedRowInComponent(0)
let screenRect = self.view.frame
let pickerSize = picker.sizeThatFits(CGSizeZero)
let x = screenRect.origin.x + (screenRect.size.width / 2) - (pickerSize.width / 2)
let pickerRect = CGRectMake(x,
screenRect.origin.y + (screenRect.size.height) - (pickerSize.height),
pickerSize.width,
pickerSize.height)
picker.frame = pickerRect
let toolbarSize = toolBar.sizeThatFits(CGSizeZero)
toolBar.frame = CGRectMake(x,pickerRect.origin.y, // right under the picker
pickerSize.width, // make them the same width
toolbarSize.height)
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int){
dropState = row
txtFieldState.text = "\(stateArray[row])"
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String?
{
return stateArray[row]
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int
{
return stateArray.count
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int
{
return 1
}
}