How to use one pickerview for multiple textfield? - swift

I am trying to build something (I am new to Xcode and swift) where I can choose a meal for a day from an array with Picker View, and I want my choice to display in that specific day's textfield. I have got this working, but for 1 day only. How can I get this same function working for all (7) days?
I managed to get the picker view when click on next textfield as well (Tuesday), but as I choose from the list the Mondays textfield will follow what I am doing for Tuesday. They are mirrored. I do get that I should probably make something to get thatTuesday-Picker unique somehow, but that's where Im stuck. I don't know what to change/write. Anyone with any ideas? I have googled around and find a lot regarding picker views but nothing for how they can been used in this specific way...
//
// ViewController.swift
// dropdown
//
// Created by -- on 2019-08-26.
// Copyright © 2019 --. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var monday: UITextField!
#IBOutlet weak var tuesday: UITextField!
#IBOutlet weak var wednesday: UITextField!
#IBOutlet weak var thursday: UITextField!
#IBOutlet weak var friday: UITextField!
#IBOutlet weak var saturday: UITextField!
#IBOutlet weak var sunday: UITextField!
// the menu
let Menu = ["Palak Paner",
"Spagetti Köttfärssås",
"Thai Haloumi",
"Thai Quorn",
"Linssoppa",
"SparrisPasta",
"Gröt",
"Gulasch"]
let tuesdayMenu = ["Palak Paner",
"Spagetti Köttfärssås",
"Thai Haloumi",
"Thai Quorn",
"Linssoppa",
"SparrisPasta",
"Gröt",
"Gulasch"]
//When a menu from the list is selected, it will be shown as a string
var mondaySelectedMenu: String?
var tuesdaySelectedMenu: String?
override func viewDidLoad() {
super.viewDidLoad()
//Call on these functions when loaded
createMondayMenuPicker()
createTuesdayMenuPicker()
createToolbar()
}
// This is the pickerView
func createMondayMenuPicker() {
let mondayMenuPicker = UIPickerView()
mondayMenuPicker.delegate = self
monday.inputView = mondayMenuPicker
}
func createTuesdayMenuPicker() {
let tuesdayMenuPicker = UIPickerView()
tuesdayMenuPicker.delegate = self
tuesday.inputView = tuesdayMenuPicker
}
// This is the "DONE" button
func createToolbar() {
let toolBar = UIToolbar()
toolBar.sizeToFit()
let doneButton = UIBarButtonItem(title: "DONE", style: .plain, target: self, action: #selector(ViewController.dismissKeyboard))
toolBar.setItems([doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
monday.inputAccessoryView = toolBar
tuesday.inputAccessoryView = toolBar
}
#objc func dismissKeyboard() {
view.endEditing(true)
}
}
// This is the details for the pickerView
extension ViewController: UIPickerViewDelegate, UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return Menu.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return Menu[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
mondaySelectedMenu = Menu[row]
tuesdaySelectedMenu = tuesdayMenu[row]
monday.text = mondaySelectedMenu
tuesday.text = tuesdaySelectedMenu
}
}
So, I want to call on that same array when click on all seven days but I want to display the unique choices for everyday chosen from that list. Any ideas? Thanks a lot!

One way to achieve this is to do using the below approach. I have separated out toolbar functionality into its own class.
Create a new class pickerview's toolbar (in this case i have called ToolbarPickerView.swift)
import UIKit
protocol ToolbarPickerViewDelegate: class {
func didTapDone()
func didTapCancel()
}
class ToolbarPickerView: UIPickerView {
public private(set) var toolbar: UIToolbar?
public weak var toolbarDelegate: ToolbarPickerViewDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
private func commonInit() {
let toolBar = UIToolbar()
toolBar.barStyle = UIBarStyle.default
toolBar.isTranslucent = true
toolBar.tintColor = .black
toolBar.sizeToFit()
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(self.doneTapped))
let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let cancelButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(self.cancelTapped))
toolBar.setItems([cancelButton, spaceButton, doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
self.toolbar = toolBar
}
#objc func doneTapped() {
self.toolbarDelegate?.didTapDone()
}
#objc func cancelTapped() {
self.toolbarDelegate?.didTapCancel()
}
}
In ViewController
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var monday: UITextField!
#IBOutlet weak var tuesday: UITextField!
#IBOutlet weak var wednesday: UITextField!
#IBOutlet weak var thursday: UITextField!
#IBOutlet weak var friday: UITextField!
#IBOutlet weak var saturday: UITextField!
#IBOutlet weak var sunday: UITextField!
var daysArray = [UITextField]()
let pickerView = ToolbarPickerView()
let Menu = ["Palak Paner",
"Spagetti Köttfärssås",
"Thai Haloumi",
"Thai Quorn",
"Linssoppa",
"SparrisPasta",
"Gröt",
"Gulasch"]
var selectedMenu : String?
override func viewDidLoad() {
super.viewDidLoad()
setupDelegateForPickerView()
setupDelegatesForTextFields()
}
func setupDelegatesForTextFields() {
//appending textfields in an array
daysArray += [monday, tuesday, wednesday, thursday, friday, saturday, sunday]
//using the array to set up the delegates, inputview for pickerview and also the inputAccessoryView for the toolbar
for day in daysArray {
day.delegate = self
day.inputView = pickerView
day.inputAccessoryView = pickerView.toolbar
}
}
func setupDelegateForPickerView() {
pickerView.dataSource = self
pickerView.delegate = self
pickerView.toolbarDelegate = self
}
}
Create an extension for textfield delegate
extension ViewController : UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
self.pickerView.reloadAllComponents()
}
}
Extension for pickerview and toolbar
extension ViewController : UIPickerViewDelegate, UIPickerViewDataSource {
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return self.Menu.count
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return self.Menu[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
// Check if the textfield isFirstResponder.
if monday.isFirstResponder {
monday.text = self.Menu[row]
} else if tuesday.isFirstResponder {
tuesday.text = self.Menu[row]
} else if wednesday.isFirstResponder {
wednesday.text = self.Menu[row]
} else if thursday.isFirstResponder {
thursday.text = self.Menu[row]
} else if friday.isFirstResponder {
friday.text = self.Menu[row]
} else if saturday.isFirstResponder {
saturday.text = self.Menu[row]
} else if sunday.isFirstResponder {
sunday.text = self.Menu[row]
} else {
//log errors
}
}
}
extension ViewController: ToolbarPickerViewDelegate {
func didTapDone() {
// let row = self.pickerView.selectedRow(inComponent: 0)
// self.pickerView.selectRow(row, inComponent: 0, animated: false)
// selectedMenu = self.Menu[row]
self.view.endEditing(true)
}
func didTapCancel() {
self.view.endEditing(true)
}
}
PickerView's didSelectRow function can be simplified by changing it to below
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
for day in daysArray {
if day.isFirstResponder {
day.text = self.Menu[row]
}
}
}
Hopefully this answer will help you.

Create a common picker class like below:
class PKMultiPicker: UIPickerView, UIPickerViewDelegate, UIPickerViewDataSource {
internal typealias PickerDone = (_ firstValue: String, _ secondValue: String) -> Void
private var doneBlock : PickerDone!
private var firstValueArray : [String]?
private var secondValueArray = [String]()
static var noOfComponent = 2
class func openMultiPickerIn(_ textField: UITextField? , firstComponentArray: [String], secondComponentArray: [String], firstComponent: String?, secondComponent: String?, titles: [String]?, toolBarTint: UIColor = UIColor.black, doneBlock: #escaping PickerDone) {
let picker = PKMultiPicker()
picker.doneBlock = doneBlock
picker.openPickerInTextField(textField, firstComponentArray: firstComponentArray, secondComponentArray: secondComponentArray, firstComponent: firstComponent, secondComponent: secondComponent, toolBarTint: toolBarTint)
if titles != nil {
let label = UILabel(frame: CGRect(x: UIScreen.main.bounds.size.width/4 - 10, y: 0, width: 100, height: 30))
label.text = titles![0].uppercased()
label.font = UIFont.boldSystemFont(ofSize: 18)
picker.addSubview(label)
if PKMultiPicker.noOfComponent > 1 {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 30))
label.text = titles![1].uppercased()
label.font = UIFont.boldSystemFont(ofSize: 18)
picker.addSubview(label)
} else {
label.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 30)
label.textAlignment = NSTextAlignment.center
}
}
}
private func openPickerInTextField(_ textField: UITextField?, firstComponentArray: [String], secondComponentArray: [String], firstComponent: String?, secondComponent: String?, toolBarTint: UIColor = UIColor.black) {
firstValueArray = firstComponentArray
secondValueArray = secondComponentArray
self.delegate = self
self.dataSource = self
let cancelButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(pickerCancelButtonTapped))
cancelButton.tintColor = toolBarTint
let doneButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.done, target: self, action: #selector(pickerDoneButtonTapped))
doneButton.tintColor = toolBarTint
let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action:nil)
let toolbar = UIToolbar()
toolbar.sizeToFit()
let array = [cancelButton, spaceButton, doneButton]
toolbar.setItems(array, animated: true)
toolbar.backgroundColor = UIColor.lightText
textField?.inputView = self
textField?.inputAccessoryView = toolbar
let index = self.firstValueArray?.index(where: {$0.lowercased() == (firstComponent ?? "").lowercased() })
self.selectRow(index ?? 0, inComponent: 0, animated: true)
if PKMultiPicker.noOfComponent > 1 {
let index1 = self.secondValueArray.index(where: {$0.lowercased() == (secondComponent ?? "").lowercased() })
self.selectRow(index1 ?? 0, inComponent: 1, animated: true)
}
}
#IBAction private func pickerCancelButtonTapped(){
UIApplication.shared.keyWindow?.endEditing(true)
}
#IBAction private func pickerDoneButtonTapped(){
UIApplication.shared.keyWindow?.endEditing(true)
let index1 : Int?
let firstValue : String?
index1 = self.selectedRow(inComponent: 0)
if firstValueArray?.count == 0{return}
else{firstValue = firstValueArray?[index1!]}
var index2 :Int!
var secondValue: String!
if PKMultiPicker.noOfComponent > 1 {
index2 = self.selectedRow(inComponent: 1)
secondValue = secondValueArray[index2]
}
self.doneBlock((firstValue ?? ""), (secondValue ?? ""))
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0 {
return firstValueArray!.count
}
return secondValueArray.count
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return PKMultiPicker.noOfComponent
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
switch component {
case 0:
return firstValueArray?[row]
case 1:
return secondValueArray[row]
default:
return ""
}
}
}
Example Code to open picker and manage selection, there are some more options, you can manage number of components also:-
PKMultiPicker.noOfComponent = 1
PKMultiPicker.openMultiPickerIn(textField, firstComponentArray: ["Apple", "Mango","Grapes","Pine apple"], secondComponentArray: [], firstComponent: textField.text, secondComponent: nil, titles: nil, toolBarTint: AppColors.themeGreen) { (firstSelect, secondSelect) in
print("first select : \(firstSelect)")
textField.text = firstSelect // you can set text here to the respective text field.
}

Here #pawan_kumar has described a way for using same picker view for multiple textfields with same data. but you can bind different data sources for that as well. https://stackoverflow.com/a/60631018/10505343 has a sample code for that. here I use UITextField.isFirstResponder to decide what data should be loaded to the picker view. Hope this answer also will be helpful for you.

Related

Done/Cancel Button is not showing Issue In Toolbar(Simulator tested)

I added a toolbar with done and cancel button but the buttons are not appearing on the toolbar. I could not find the cause of this issue.
i tried many changes but the issue is not resolved.
This is the code regarding the toolbar:
#IBOutlet weak var textFieldYear: UITextField!
#IBOutlet weak var viewMonth: UIView!
#IBOutlet weak var textFieldMonth: UITextField!
#IBOutlet weak var viewDatePicker: UIView!
#IBOutlet weak var datePicker: UIPickerView!
func setUpDatePickerView(){
let date = Date()
arrYear.add(date.year)
if date.month == "November" || date.month == "December" {
let newDate = Calendar.current.date(byAdding: .year, value: 1, to: date)
arrYear.add(newDate?.year ?? "")
}
let toolBar = UIToolbar().ToolbarPiker(mySelect: #selector(self.donePicker), cancel: #selector(self.dismissPicker))
viewDatePicker.addSubview(toolBar)
datePicker.reloadAllComponents()
datePicker.selectRow(0, inComponent: 0, animated: true)
textFieldYear.text = (arrYear[0] as! String)
textFieldMonth.text = Calendar.current.date(byAdding: .month, value: 1, to: Date())?.month
}
// ToolBar
extension UIToolbar {
func ToolbarPiker(mySelect : Selector, cancel : Selector) -> UIToolbar {
let toolBar = UIToolbar()
toolBar.barStyle = UIBarStyle.default
toolBar.isTranslucent = true
toolBar.tintColor = UIColor.black
toolBar.sizeToFit()
let doneButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.done, target: self, action: mySelect)
let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.fixedSpace, target: nil, action: nil)
spaceButton.width = 225
let cancelButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.cancel, target: self, action: cancel)
toolBar.setItems([cancelButton, spaceButton, doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
return toolBar
}
}
#objc func donePicker() {
self.view.endEditing(true)
}
#objc func dismissPicker() {
self.view.endEditing(true)
viewDatePicker.isHidden = true
}
I think, you might be missing to add the toolbar as an accessory view to the input element. For example here we will add a toolbar as an accessory view to a textField.
textField1.inputAccessoryView = toolBar
Please let me know if it worked
I would subclass UITextField and add your pickerview and toolbar there:
import UIKit
class MonthField: UITextField, UIPickerViewDelegate, UIPickerViewDataSource {
let pickerView = UIPickerView()
var dataSource: [String] { return Calendar.current.monthSymbols }
var month: Int = 0
func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 }
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return dataSource.count }
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return dataSource[row] }
// Configure toolbar and picker view
override func didMoveToWindow() {
let toolbar = UIToolbar()
toolbar.setItems([
.init(title: "Cancel", style: .plain, target: self, action: #selector(cancel)),
.flexibleSpace,
.init(title: "Done", style: .plain, target: self, action: #selector(done)),
], animated: false)
pickerView.delegate = self
pickerView.dataSource = self
pickerView.selectRow(Date().month-1, inComponent: 0, animated: false)
inputView = pickerView
inputAccessoryView = toolbar
toolbar.sizeToFit()
placeholder = "Select Month"
}
override func caretRect(for position: UITextPosition) -> CGRect { return .zero }
#objc func done(_ barButtonItem: UIBarButtonItem) {
month = pickerView.selectedRow(inComponent: 0) + 1
text = dataSource[month-1]
endEditing(false)
}
#objc func cancel(_ barButtonItem: UIBarButtonItem) {
endEditing(false)
}
}
extension UIBarButtonItem {
static let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
}
extension Date {
var month: Int { return Calendar.current.component(.month, from: self) }
}

How to set a UIPickerView selected value from Realm?

I have setup a viewcontroller where there are 7 "questions" and 7 "answers". I've created a UIPickerView that allows users to select their answer when they click on the answer's UITextField, and save it in realm.
However, when a user has already selected an answer (example: "Agree"), "Agree" doesn't show up as the selected row in the UIPickerView when I click the UITextField for that answer.
How do I set the default or "selectedrow" for the UIPickerview to the answer that is displayed in the UITextField and is saved in Realm?
import UIKit
import RealmSwift
class QuestionViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupDelegateForPickerView()
setupDelegatesForTextFields()
}
override func viewWillAppear(_ animated: Bool) {
loadAnswers()
}
#IBOutlet weak var Question1TextField: UITextField!
#IBOutlet weak var Question2TextField: UITextField!
#IBOutlet weak var Question3TextField: UITextField!
#IBOutlet weak var Question4TextField: UITextField!
#IBOutlet weak var Question5TextField: UITextField!
#IBOutlet weak var Question6TextField: UITextField!
#IBOutlet weak var Question7TextField: UITextField!
// Setting up a UIPickerview from the ToolbarPickerView class
let realm = try! Realm()
var answers: Results<Answer>?
var answerArray = [UITextField]()
func loadAnswers() {
var answersGiven = realm.objects(Answer.self).filter("id = 1")
for answer in answersGiven {
Question1TextField.text = answer.Answer1
Question2TextField.text = answer.Answer2
Question3TextField.text = answer.Answer3
Question4TextField.text = answer.Answer4
Question5TextField.text = answer.Answer5
Question6TextField.text = answer.Answer6
Question7TextField.text = answer.Answer7
}
}
let pickerView = ToolbarPickerView()
let Menu = ["Strongly Disagree",
"Disagree",
"Neutral",
"Agree",
"Strongly Agree"]
var selectedMenu : String?
func setupDelegatesForTextFields() {
//appending textfields in an array
answerArray += [Question1TextField, Question2TextField, Question3TextField, Question4TextField, Question5TextField, Question6TextField, Question7TextField]
//using the array to set up the delegates, inputview for pickerview and also the inputAccessoryView for the toolbar
for answer in answerArray {
answer.delegate = self
answer.inputView = pickerView
answer.inputAccessoryView = pickerView.toolbar
}
}
func setupDelegateForPickerView() {
pickerView.dataSource = self
pickerView.delegate = self
pickerView.toolbarDelegate = self
}
func setDefaultValue(item: String, inComponent: Int){
if let indexPosition = Menu.firstIndex(of: item){
pickerView.selectRow(indexPosition, inComponent: inComponent, animated: true)
}
}
// Dismissing Keyboard on tapped
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
}
extension QuestionViewController : UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
self.pickerView.reloadAllComponents()
}
}
extension QuestionViewController : UIPickerViewDelegate, UIPickerViewDataSource {
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return self.Menu.count
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return self.Menu[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let answer = Answer()
answer.id = 1
// Check if the textfield isFirstResponder.
if Question1TextField.isFirstResponder {
Question1TextField.text = self.Menu[row]
answer.Answer1 = Question1TextField.text
try! realm.write {
realm.create(Answer.self, value: ["id": 1, "Answer1": self.Menu[row]], update: .modified)
}
} else if Question2TextField.isFirstResponder {
Question2TextField.text = self.Menu[row]
answer.Answer2 = Question2TextField.text
try! realm.write {
realm.create(Answer.self, value: ["id": 1, "Answer2": self.Menu[row]], update: .modified)
}
} else if Question3TextField.isFirstResponder {
Question3TextField.text = self.Menu[row]
answer.Answer3 = Question3TextField.text
try! realm.write {
realm.create(Answer.self, value: ["id": 1, "Answer3": self.Menu[row]], update: .modified)
}
} else if Question4TextField.isFirstResponder {
Question4TextField.text = self.Menu[row]
answer.Answer4 = Question4TextField.text
try! realm.write {
realm.create(Answer.self, value: ["id": 1, "Answer4": self.Menu[row]], update: .modified)
}
} else if Question5TextField.isFirstResponder {
Question5TextField.text = self.Menu[row]
answer.Answer5 = Question5TextField.text
try! realm.write {
realm.create(Answer.self, value: ["id": 1, "Answer5": self.Menu[row]], update: .modified)
}
} else if Question6TextField.isFirstResponder {
Question6TextField.text = self.Menu[row]
answer.Answer6 = Question6TextField.text
try! realm.write {
realm.create(Answer.self, value: ["id": 1, "Answer6": self.Menu[row]], update: .modified)
}
} else if Question7TextField.isFirstResponder {
Question7TextField.text = self.Menu[row]
answer.Answer7 = Question7TextField.text
try! realm.write {
realm.create(Answer.self, value: ["id": 1, "Answer7": self.Menu[row]], update: .modified)
}
} else {
//log errors
}
}
}
extension QuestionViewController: ToolbarPickerViewDelegate {
func didTapDone() {
let row = self.pickerView.selectedRow(inComponent: 0)
self.pickerView.selectRow(row, inComponent: 0, animated: false)
selectedMenu = self.Menu[row]
self.view.endEditing(true)
}
func didTapCancel() {
self.view.endEditing(true)
}
}
Here is the Toolbar Class just incase.
import UIKit
protocol ToolbarPickerViewDelegate: class {
func didTapDone()
func didTapCancel()
}
class ToolbarPickerView: UIPickerView {
public private(set) var toolbar: UIToolbar?
public weak var toolbarDelegate: ToolbarPickerViewDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
private func commonInit() {
let toolBar = UIToolbar()
toolBar.sizeToFit()
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(self.doneTapped))
let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let cancelButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(self.cancelTapped))
toolBar.setItems([cancelButton, spaceButton, doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
toolBar.backgroundColor = UIColor(red: 0/255.0, green: 142.0/255.0, blue: 44.0/255.0, alpha: 0.5)
toolBar.tintColor = UIColor(red: 0/255.0, green: 142.0/255.0, blue: 44.0/255.0, alpha: 0.5)
UIBarButtonItem.appearance().setTitleTextAttributes([NSAttributedString.Key.foregroundColor : UIColor(red: 0/255.0, green: 142.0/255.0, blue: 44.0/255.0, alpha: 1.0),
NSAttributedString.Key.font: UIFont(name: "Clinton", size: 14)], for: UIControl.State.normal)
self.toolbar = toolBar
}
#objc func doneTapped() {
self.toolbarDelegate?.didTapDone()
}
#objc func cancelTapped() {
self.toolbarDelegate?.didTapCancel()
}
}
extension String {
// formatting text for currency textField
func currencyFormatting() -> String {
if let value = Double(self) {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.maximumFractionDigits = 0
if let str = formatter.string(for: value) {
return str
}
}
return ""
}
}

Two UIPickerViews in one ViewController

Two PickerViews selectTypeOfWorkChoices & selectLocationChoices do not appear correctly.
A function dismissPickerView() seems working well. However, another function "createPickerView()" has some problems. Although UIpickerviews appear, I cannot see the choices in UIPickerViews and I don't know why.
Could anyone help me figure out what's wrong with my code, please??
#IBOutlet weak var selectTypeOfWorkChoices: UIPickerView!
#IBOutlet weak var selectLocationChoices: UIPickerView!
override func viewDidLoad() {
super.viewDidLoad()
createPickerView()
dismissPickerView()
}
var typeOfWork = ["--", "a", "b", "c"]
var location = ["--", "A", "B", "C"]
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
var countrows : Int = typeOfWork.count
if pickerView == selectLocationChoices {
countrows = self.location.count
}
return countrows
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if pickerView == selectTypeOfWorkChoices {
let titleRow = typeOfWork[row]
return titleRow
}
else if pickerView == selectLocationChoices {
let titleRow = location[row]
return titleRow
}
return ""
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if pickerView == selectTypeOfWorkChoices {
selectedPriority = typeOfWork[row]
selectTypeOfWork.text = selectedPriority
self.selectTypeOfWork.text = self.typeOfWork[row]
}
else if pickerView == selectLocationChoices {
locationSelectedPriority = location[row]
selectLocation.text = locationSelectedPriority
self.selectLocation.text = self.location[row]
}
}
var selectedPriority : String?
var locationSelectedPriority : String?
func createPickerView() {
let pickerView = UIPickerView()
pickerView.delegate = self
self.selectTypeOfWorkChoices.delegate = self
self.selectTypeOfWorkChoices.dataSource = self
self.selectLocationChoices.delegate = self
self.selectLocationChoices.dataSource = self
selectTypeOfWork.inputView = selectTypeOfWorkChoices
selectLocation.inputView = selectLocationChoices
}
#objc func dismissPickerView() {
let toolBar = UIToolbar()
toolBar.sizeToFit()
let doneButton = UIBarButtonItem(title:"Done", style: .plain, target: self, action: #selector(self.dismissKeyboard))
toolBar.setItems([doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
selectTypeOfWork.inputAccessoryView = toolBar
selectLocation.inputAccessoryView = toolBar
}
#objc func dismissKeyboard () {
view.endEditing(true)
}
Reload the picker view after assigning an input view to your text field.
func createPickerView() {
let pickerView = UIPickerView()
pickerView.delegate = self
self.selectTypeOfWorkChoices.delegate = self
self.selectTypeOfWorkChoices.dataSource = self
self.selectLocationChoices.delegate = self
self.selectLocationChoices.dataSource = self
selectTypeOfWork.inputView = selectTypeOfWorkChoices
selectLocation.inputView = selectLocationChoices
//Reload Pickerview
self.selectTypeOfWorkChoices.reloadAllComponents()
self.selectLocationChoices.reloadAllComponents()
}

Is there any way to set inputView for TextField in SwiftUI?

I want to set picker as my inputView for TextField, can I do it with SwiftUI only or I have to use UIKit components with help of framework integration?
Code example in UIKit:
textField.inputView = UIPickerView()
I want to do same, but with SwiftUI's TextField
The only issue which I found using the above-mentioned solution was that whenever the keyboard gets into the editing phase, then the picker was presented and along with it the keyboard also gets presented.
So there was no way to hide the keyboard and present the picker.
Therefore I have written a custom struct to handle this behaviour similar to what we do using UITextField inputView.
You can use it. This works for my use case.
You can also customise the picker, as well as textfield in the makeUIView methods like I, have done with the background colour of the picker.
struct TextFieldWithPickerAsInputView : UIViewRepresentable {
var data : [String]
var placeholder : String
#Binding var selectionIndex : Int
#Binding var text : String?
private let textField = UITextField()
private let picker = UIPickerView()
func makeCoordinator() -> TextFieldWithPickerAsInputView.Coordinator {
Coordinator(textfield: self)
}
func makeUIView(context: UIViewRepresentableContext<TextFieldWithPickerAsInputView>) -> UITextField {
picker.delegate = context.coordinator
picker.dataSource = context.coordinator
picker.backgroundColor = .yellow
picker.tintColor = .black
textField.placeholder = placeholder
textField.inputView = picker
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<TextFieldWithPickerAsInputView>) {
uiView.text = text
}
class Coordinator: NSObject, UIPickerViewDataSource, UIPickerViewDelegate , UITextFieldDelegate {
private let parent : TextFieldWithPickerAsInputView
init(textfield : TextFieldWithPickerAsInputView) {
self.parent = textfield
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return self.parent.data.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return self.parent.data[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
self.parent.$selectionIndex.wrappedValue = row
self.parent.text = self.parent.data[self.parent.selectionIndex]
self.parent.textField.endEditing(true)
}
func textFieldDidEndEditing(_ textField: UITextField) {
self.parent.textField.resignFirstResponder()
}
}
}
You can use this as:-
struct ContentView : View {
#State var gender : String? = nil
#State var arrGenders = ["Male","Female","Unknown"]
#State var selectionIndex = 0
var body : some View {
VStack {
TextFieldWithPickerAsInputView(data: self.arrGenders, placeholder: "select your gender", selectionIndex: self.$selectionIndex, text: self.$gender)
}
}
}
As of Xcode 11.4, SwiftUI's TextField does not have an equivalent of the inputView property of UITextField.
You can work around it by bridging a UIKit UITextField to SwiftUI, and by bridging a SwiftUI Picker to UIKit. You'll need to set the text field's inputViewController property rather than its inputView property.
To bridge a UITextField to SwiftUI
Use UIViewRepresentable to wrap the UITextField in a SwiftUI View. Since you create the UITextField, you can set its inputViewController property to a UIViewController that you create.
To bridge a SwiftUI Picker into UIKit
UseUIHostingController to wrap a SwiftUI Picker in a UIViewController. Set the text field's inputViewController to your UIHostingController instance.
If you want to have a TextField and choose its text using a Picker in SwiftUI. And you don't want to integrate UIKit in SwiftUI, the bellow solution may give you some ideas:
import SwiftUI
struct ContentView: View {
#State private var selection = 0
#State private var textfieldValue = ""
#State private var textfieldValue2 = ""
#State private var ispickershowing = false
var values = ["V1", "V2", "V3"]
var body: some View {
VStack {
TextField("Pick one from the picker:", text: $textfieldValue, onEditingChanged: {
edit in
if edit {
self.ispickershowing = true
} else {
self.ispickershowing = false
}
})
if ispickershowing {
Picker(selection: $selection, label:
Text("Pick one:")
, content: {
ForEach(0 ..< values.count) { index in
Text(self.values[index])
.tag(index)
}
})
Text("you have picked \(self.values[self.selection])")
Button(action: {
self.textfieldValue = self.values[self.selection]
}, label: {
Text("Done")
})
}
TextField("simple textField", text: $textfieldValue2)
}
}
}
This is a textfield that can have an input view that is either a picker, a datepicker, or a keyboard:
import Foundation
import SwiftUI
struct CTextField: UIViewRepresentable {
enum PickerType {
case keyboard(type: UIKeyboardType, autocapitalization: UITextAutocapitalizationType, autocorrection: UITextAutocorrectionType)
case datePicker(minDate: Date, maxDate: Date)
case customList(list: [String])
}
var pickerType: CTextField.PickerType
#Binding var text: String {
didSet{
print("text aha: ", text)
}
}
let placeholder: String
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
textField.placeholder = placeholder
textField.frame.size.height = 36
textField.borderStyle = .roundedRect
textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
if !self.text.isEmpty{
textField.text = self.text
}
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
switch pickerType {
case .datePicker:
uiView.text = self.text
case .customList:
uiView.text = self.text
default:
break
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
final class Coordinator: NSObject {
var parent: CTextField
init(_ parent: CTextField) {
self.parent = parent
}
private func setPickerType(textField: UITextField) {
switch parent.pickerType {
case .keyboard(let type, let autocapitalization, let autocorrection):
textField.keyboardType = type
textField.inputView = nil
textField.autocapitalizationType = autocapitalization
textField.autocorrectionType = autocorrection
case .customList(let list):
textField.inputView = getPicker()
let row = list.firstIndex(of: parent.text)
let myPicker = textField.inputView as! UIPickerView
myPicker.selectRow(row!, inComponent: 0, animated: true)
case .datePicker(let minDate, let maxDate):
textField.inputView = getDatePicker(minDate: minDate, maxDate: maxDate)
}
textField.inputAccessoryView = getToolBar()
}
private func getPicker() -> UIPickerView {
let picker = UIPickerView()
picker.backgroundColor = UIColor.systemBackground
picker.delegate = self
picker.dataSource = self
return picker
}
private func getDatePicker(minDate: Date, maxDate: Date) -> UIDatePicker {
let picker = UIDatePicker()
picker.datePickerMode = .date
picker.backgroundColor = UIColor.systemBackground
picker.maximumDate = maxDate
picker.minimumDate = minDate
picker.addTarget(self, action: #selector(handleDatePicker(sender:)), for: .valueChanged)
return picker
}
#objc func handleDatePicker(sender: UIDatePicker) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd MMM yyyy"
parent.text = dateFormatter.string(from: sender.date)
}
private func getToolBar() -> UIToolbar {
let toolBar = UIToolbar()
toolBar.barStyle = UIBarStyle.default
toolBar.backgroundColor = UIColor.systemBackground
toolBar.isTranslucent = true
toolBar.sizeToFit()
let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: UIBarButtonItem.Style.done, target: self, action: #selector(self.donePicker))
toolBar.setItems([spaceButton, doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
return toolBar
}
#objc func donePicker() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
}
extension CTextField.Coordinator: UIPickerViewDataSource{
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
switch parent.pickerType {
case .customList(let list):
return list.count
default:
return 0
}
}
}
extension CTextField.Coordinator: UIPickerViewDelegate {
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
switch parent.pickerType {
case .customList(let list):
return list[row]
default:
return ""
}
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
switch parent.pickerType {
case .customList(let list):
parent.text = list[row]
print("parent.text is now: ", parent.text)
default:
break
}
}
}
extension CTextField.Coordinator: UITextFieldDelegate {
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
setPickerType(textField: textField)
return true
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
defer {
if let currentText = textField.text, let stringRange = Range(range, in: currentText) {
parent.text = currentText.replacingCharacters(in: stringRange, with: string)
}
}
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
donePicker()
}
}
Assuming you have a ViewModel like this:
import SwiftUI
import Combine
let FRIENDS = ["Raquel", "Shekinah", "Sedh", "Sophia"]
class UserSettingsVM: ObservableObject {
#Published var name = FRIENDS[0]
#Published var greet = ""
}
You can use it like this:
import SwiftUI
struct FriendsView: View {
#ObservedObject var vm = UserSettingsVM()
var body: some View {
ScrollView {
VStack {
Group {
Text(vm.name)
.padding()
Text(vm.greet)
.padding()
CTextField(pickerType: .customList(list: FRIENDS), text: $vm.name, placeholder: "Required")
CTextField(pickerType: .keyboard(type: .default, autocapitalization: .none, autocorrection: .no
), text: $vm.greet, placeholder: "Required")
}
.padding()
}
}
}
}

Exporting multiple pickerView selected values to numbers on a UILabel

I'm trying to export the result of my 'pickerViews' right beside each other in a numeric form, as in if the 'pickerViews' have selected "A", "B" and "C", I want a 'func' to print them out on a 'UILabel' as "123" (Each number for each letter). This is how I've coded my 'pickerView' :
import UIKit
class PickerTextField: UITextField,UIPickerViewDelegate,UIPickerViewDataSource {
let pickerView = UIPickerView()
var itemList = [String]()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
#objc func textEdited(_ sender:PickerTextField)
{
self.text = itemList[pickerView.selectedRow(inComponent: 0)]
}
override func draw(_ rect: CGRect) {
super.draw(rect)
self.tintColor = UIColor.clear
self.addTarget(self, action: #selector(textEdited(_:)), for: .editingChanged)
pickerView.showsSelectionIndicator = true
pickerView.delegate = self
pickerView.dataSource = self
self.inputView = pickerView
let toolBar = UIToolbar()
toolBar.barStyle = UIBarStyle.default
toolBar.isTranslucent = true
toolBar.tintColor = .black
toolBar.sizeToFit()
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(doneBtnAction(_:)))
let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let cancelButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(doneBtnAction(_:)))
toolBar.items = [cancelButton, spaceButton, doneButton]
self.inputAccessoryView = toolBar
}
#objc func doneBtnAction(_ sender:UIBarButtonItem) {
resignFirstResponder()
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return itemList.count
}
func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
let title = itemList[row]
return NSAttributedString(string: title, attributes: [NSAttributedStringKey.foregroundColor:UIColor.black])
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
self.text = itemList[row]
}
}
class ViewController: UIViewController {
#IBOutlet weak var servicesField: PickerTextField!
#IBOutlet weak var brandsField: PickerTextField!
#IBOutlet weak var modelsField: PickerTextField!
#IBOutlet weak var Capacity: UITextField!
#IBOutlet weak var Details: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
servicesField.itemList = ["Select", "Services", "others"]
brandsField.itemList = ["Select" ,"Apple", "Samsung"]
modelsField.itemList = ["Select", "5", "5s"]
}
}
any help here? Thanks.
Create a struct for your data
struct Item {
var id:Int
var title:String
}
Change PickerTextFiled class to use this struct
class PickerTextField: UITextField,UIPickerViewDelegate,UIPickerViewDataSource {
let pickerView = UIPickerView()
var itemList = [Item]()
var selectedItem:Item?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
#objc func textEdited(_ sender:PickerTextField)
{
self.text = itemList[pickerView.selectedRow(inComponent: 0)].title
}
override func draw(_ rect: CGRect) {
super.draw(rect)
self.tintColor = UIColor.clear
self.addTarget(self, action: #selector(textEdited(_:)), for: .editingChanged)
pickerView.showsSelectionIndicator = true
pickerView.delegate = self
pickerView.dataSource = self
self.inputView = pickerView
let toolBar = UIToolbar()
toolBar.barStyle = UIBarStyle.default
toolBar.isTranslucent = true
toolBar.tintColor = .black
toolBar.sizeToFit()
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(doneBtnAction(_:)))
let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let cancelButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(doneBtnAction(_:)))
toolBar.items = [cancelButton, spaceButton, doneButton]
self.inputAccessoryView = toolBar
}
#objc func doneBtnAction(_ sender:UIBarButtonItem) {
resignFirstResponder()
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return itemList.count
}
func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
let title = itemList[row].title
return NSAttributedString(string: title, attributes: [NSAttributedStringKey.foregroundColor:UIColor.black])
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
self.selectedItem = itemList[row]
self.text = itemList[row].title
}
}
Then you can get the id wherever you need
class ViewController: UIViewController {
#IBOutlet weak var servicesField: PickerTextField!
#IBOutlet weak var brandsField: PickerTextField!
#IBOutlet weak var modelsField: PickerTextField!
override func viewDidLoad() {
super.viewDidLoad()
servicesField.itemList = [Item(id: -1, title: "Select"),Item(id: -1, title: "Services"),Item(id: -1, title: "others")]
brandsField.itemList = [Item(id: -1, title: "Select"),Item(id: -1, title: "Apple"),Item(id: -1, title: "Samsung")]
modelsField.itemList = [Item(id: -1, title: "Select"),Item(id: -1, title: "5"),Item(id: -1, title: "5s")]
}
func printAll() {
if let servicesFieldId = servicesField.selectedItem?.id, let brandsFieldId = brandsField.selectedItem?.id, let modelsFieldId = servicesField.selectedItem?.id {
label.text = String(servicesFieldId) + String(brandsFieldId) + String(modelsFieldId)
}
}
}