I wrote a UITextfield extension to build my own toolbar for custom inputs:
func addToolbar(selector: Selector? = nil) {
let action = selector == nil ? #selector(resignFirstResponder) : selector
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: action)
let toolbar = UIToolbar()
toolbar.items = [flexibleSpace, doneButton]
toolbar.sizeToFit()
toolbar.layoutIfNeeded()
inputAccessoryView = toolbar
returnKeyType = .done
}
I use it like this:
let myPickerView = UIPickerView()
myPickerView.dataSource = myPickerViewBehavior
myPickerView.delegate = myPickerViewBehavior
if let selected = value {
let selectedIndex = PickableItemType.index(of: selected.rawValue)
myPickerView.selectRow(selectedIndex, inComponent: 0, animated: true)
}
myTextField.delegate = self
myTextField.inputView = myPickerView
myTextField.addToolbar(selector: #selector(nextTextField.becomeFirstResponder))
As you can imagine I want the next UITextField to becomeFirstResponder after the user pressed done in the toolbar.
But nothing happening.
For testing I've tried using a simple #objs func with print statement and/or nextTextField.becomeFirstResponder inside, same result.
I've also tried to use UITapGestureRecognizer with #selector(nextTextField.becomeFirstResponder), also same result.
So I guess it has something to do with the #selector mechanic why the code doesn't work.
P.S.: if selector == nil inside addToolbar , #selector(resignFirstResponder) works fine.
I believe the problem is caused by the wrong target: while setting up doneButton.
If you also supply the target in addToolbar: method like:
func addToolbar(target: Any? = nil, selector: Selector? = nil) {
let object = target ?? self
let action = selector ?? #selector(resignFirstResponder)
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: object, action: action)
let toolbar = UIToolbar()
toolbar.items = [flexibleSpace, doneButton]
toolbar.sizeToFit()
toolbar.layoutIfNeeded()
inputAccessoryView = toolbar
returnKeyType = .done
}
This should work:
firstTextField.addToolbar(target: secondTextField, selector: #selector(becomeFirstResponder))
Here's a small test result:
https://imgur.com/R7R1ee9
Related
I am creating a custom UIToolBar to add as inputAccessoryView to a UITextField, I would like to add a UIBarButtonItem on the right side of this toolbar to serve as the return key of this textfield, and the text of this barButtonItem should be the same as the keyboard of that textfield.
My approach was:
let buttonDone = UIBarButtonItem(title: myTextField.returnKeyType, style: .done, target: self, action: #selector(pickerDone)
let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
// toolbar
let toolBar = UIToolbar()
toolBar.barStyle = .default
toolBar.items = [space, buttonDone]
toolBar.sizeToFit()
// setup input
myTextField.inputAccessoryView = toolBar
But with this i get this error:
Cannot convert value of type 'UIReturnKeyType?' to expected argument type 'String?'
So, I try something like:
title: myTextField.returnKeyType.text
But returnKeyType doesn't has a .text variable or similar...
Is there any way of doing this?
Should I go another way?
There is no built-in way to convert the UIReturnKeyType enum to a string. You will need to write your own code using a switch on the all the possible values.
Here's one solution using an extension. Add support for the other values as needed.
extension UIReturnKeyType {
var label: String {
switch self {
case .default:
return "Return"
case .go:
return "Go"
case .done:
return "Done"
default:
return "Enter"
}
}
}
Then you can use this as:
let buttonDone = UIBarButtonItem(title: myTextField.returnKeyType.label, style: .done, target: self, action: #selector(pickerDone)
I have a UIDatePicker in which some date is set by initial. With addTarget, I listen to thevalueChanged events and everything works correctly when the wheel is spinning.
I want to allow users to directly select the initial date (for example, by clicking on it). But now the event comes only when the wheel is spinning. To put the current date, the user will have to:
Put a new date
Go to the previous date
My code:
class Picker {
init() {
let datePicker = UIDatePicker()
datePicker.setDate(Date(), animated: false) // Set initial date
datePicker.addTarget(self, action: #selector(handleDatePicker(sender:)), for: .valueChanged)
}
#objc func handleDatePicker(sender: UIDatePicker) {
print(sender.date)
}
}
Picker()
Screenshot:
SOLUTION:
The most correct solution turned out to be the addition the UIToolBar with Done button. The code can be found here: example with UIToolBar
to select first index value on click done button in datePicker
func pickUp(_ textField : UITextField){
// UIPickerView
self.myPickerView = UIPickerView(frame:CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 216))
self.myPickerView.delegate = self
self.myPickerView.dataSource = self
self.myPickerView.backgroundColor = UIColor.white
textField.inputView = self.myPickerView
// ToolBar
let toolBar = UIToolbar()
toolBar.barStyle = .default
toolBar.isTranslucent = true
toolBar.tintColor = UIColor.darkGray
toolBar.sizeToFit()
// Adding Button ToolBar
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(UrVC.doneClick))
let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let cancelButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(UrVC.cancelClick))
toolBar.setItems([cancelButton, spaceButton, doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
textField.inputAccessoryView = toolBar
}
#objc func doneClick() {
pickerTxtfld.resignFirstResponder()
let indexPath = myPickerView.selectedRow(inComponent: 0)
pickerTxtfld.text = dataArray[indexPath]
}
#objc func cancelClick() {
pickerTxtfld.resignFirstResponder()
}
I am using custom textfield to show datepicker and pickerview. I have added UIToolBar as inputAccessoryView. I have added done and cancel barbutton. when i press done and cancel button action is working fine in iOS 11. But In iOS 12 app is crashing on tap of bar button. Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_UIButtonBarButton _setContentCoverViewMode:]: unrecognized selector sent to instance.
#IBDesignable
final class ICTextField: UITextField {
private var pickerView: UIPickerView?
/// To show the date or time input from textfield set this value to true
/// Default is false.
#IBInspectable open var isDatePicker: Bool = false {
didSet {
if self.isDatePicker {
self.inputView = self.datePicker
self.inputAccessoryView = self.configureToolBar()
}
self.reloadInputViews()
}
}
/// Shows the inputview as UITableView. able to select multiple option, default is false
#IBInspectable open var isDropDown: Bool = false {
didSet {
if isDropDown {
self.pickerView = UIPickerView()
self.pickerView?.dataSource = self
self.pickerView?.delegate = self
self.inputAccessoryView = self.configureToolBar()
self.inputView = self.pickerView
self.reloadInputViews()
} else {
self.inputView = nil
self.reloadInputViews()
}
}
}
// MARK: - Public Properties
let datePicker: UIDatePicker = {
let dateDropDown = UIDatePicker()
dateDropDown.datePickerMode = .date
dateDropDown.locale = Locale(identifier: "en_US_POSIX")
dateDropDown.addTarget(self, action: #selector(ICTextField.datePickerValueChanged(sender:)), for: .valueChanged)
return dateDropDown
}()
/// Tool bar to show cancel and done on top of textfield.
open var accessoryToolBar: UIToolbar? {
didSet {
self.inputAccessoryView = accessoryToolBar
self.reloadInputViews()
}
}
private func configureToolBar() -> UIToolbar {
let frame = CGRect(x: 0, y: 0, width: self.frame.width, height: 40.0)
let toolBar = UIToolbar(frame: frame)
toolBar.barStyle = .default
toolBar.barTintColor = UIColor(hex: "4db8ff")
let doneButton = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(donePressed))
let cancelButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(cancelPressed))
let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
toolBar.setItems([cancelButton, spaceButton, doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
toolBar.sizeToFit()
return toolBar
}
#objc fileprivate func donePressed() {
self.resignFirstResponder()
}
#objc private func cancelPressed() {
self.resignFirstResponder()
}
}
In viewcontroller I am using this textField Object.
icTextField.isDatePicker = true
Update
App is getting crashed when keyboard is open and it contains accessory view as toolbar. now if i press navigationbar barbutton or accessory toolbar button app is getting crashed. I think this is apple bug. anyone found solution.
I am relatively new to coding so please bear with me. I have written a simple app which has 6 text fields that need to completed before an action button is pressed to perform a mathematical calculation.
I have everything done but i can't get my next button to move to the next text field. I have added the tool bar and the done and next button but when i press the next button the app crashes and i get a "Thread 1: signal SIGABRT".
I have been through the internet and I am struggling to see how do do it. I would have thought this would be pretty straight forward.
I just can't make the next button perform the action to the next box.
Here is my code
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
//IBOutlet UITextFields are here
override func viewDidLoad() {
super.viewDidLoad()
let toolBar = UIToolbar()
toolBar.barStyle = .default
toolBar.isTranslucent = true
toolBar.sizeToFit()
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil)
let fixedSpaceButton = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.done, target: self, action: #selector(self.doneClicked))
let nextButton = UIBarButtonItem(title: "Next", style: .plain, target: self, action: #selector(self.textFieldShouldReturn(_:)))
toolBar.setItems([fixedSpaceButton, nextButton, flexibleSpace, doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
box1.delegate = self
box2.delegate = self
box3.delegate = self
box4.delegate = self
box5.delegate = self
box6.delegate = self
box1.becomeFirstResponder()
}
func doneClicked() {
view.endEditing(true)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool
{
switch textField
{
case box1: box2.becomeFirstResponder()
break
case box2: box3.becomeFirstResponder()
break
case box3: box4.becomeFirstResponder()
break
case box4: box5.becomeFirstResponder()
break
case box5: box6.becomeFirstResponder()
break
default: textField.resignFirstResponder()
}
return true
}
//IBAction for maths calculations are here
I appreciate any help in advance. I literally have no hair left.
Thanks
#IBOutlet weak var playStopButton: UIBarButtonItem!
var playStopArray = [UIBarButtonSystemItem.Pause, UIBarButtonSystemItem.Play]
var index = 0
#IBOutlet weak var image: UIImageView!
#IBAction func playButton(sender: UIBarButtonItem) {
println("pressed")
playStopButton = UIBarButtonItem(barButtonSystemItem: playStopArray[index], target: self, action: "startMusic:")
println("here")
if index == 0 {
index = 1
}
else {
index = 0
}
}
func startMusic() {
println("test")
}
I expected the bar button to change to the pause symbol, but with no luck. It prints both "pressed" and "here" but "test" does not work. Why is the image not changing?
Your approach is wrong.
In the following line,
playStopButton = UIBarButtonItem(barButtonSystemItem: playStopArray[index], target: self, action: "startMusic:")
you are actually creating a new instance of UIBarButtonItem. This button is not actually added into the view. Instead of adding the UIBarButtonItem through Interface Builder. You can create it programmatically.
Read this question for more information.
toggle between UIBarButtonSystemItemPlay and UIBarButtonSystemItemPause
var playButton:UIBarButtonItem!
var pauseButton:UIBarButtonItem!
func setup()
{
playButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Play, target: self, action: "startMusic:")
pauseButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Pause, target: self, action: "stopMusic:")
}
func startMusic:(button : UIBarButtonItem)
{
self.navigationItem.rightBarButtonItem = pauseButton // Switch to pause.
//Other code.
}
func stopMusic:(button : UIBarButtonItem)
{
self.navigationItem.rightBarButtonItem = playButton// Switch to play.
//Other code.
}
Here is my approach in Swift
func configureBars() {
isPlaying = false
// UIToolbar
let pickBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Search,
target: self,
action: "pickSong")
let playBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Play,
target: self,
action: "playPause")
let pauseBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Pause,
target: self,
action: "playPause")
let leftFlexBBI = UIBarButtonItem(barButtonSystemItem: .FlexibleSpace,
target: nil,
action: nil)
let rightFlexBBI = UIBarButtonItem(barButtonSystemItem: .FlexibleSpace,
target: nil,
action: nil)
playItems = [pickBarButtonItem, leftFlexBBI, playBarButtonItem, rightFlexBBI]
pauseItems = [pickBarButtonItem, leftFlexBBI, pauseBarButtonItem, rightFlexBBI]
toolbar.items = playItems
}
visit http://www.raywenderlich.com/36475/how-to-make-a-music-visualizer-in-ios
or you can check out my Github https://github.com/Charles-Hsu/MusicVisualizer