Toolbar with "Previous" and "Next" for keyboard - ios

I've been trying to implement this toolbar, where only the 'Next' button is enabled when the top textField is the firstResponder and only the 'Previous' button is enabled when the bottom textField is the firstResponder.
It kind of works, but i need to execute my own code by accessing previous, next and done buttons action methods in other classes(like delegates)
Thanks in advance for your suggestions..
extension UIViewController {
func addInputAccessoryForTextFields(textFields: [UITextField], dismissable: Bool = true, previousNextable: Bool = false) {
for (index, textField) in textFields.enumerated() {
let toolbar: UIToolbar = UIToolbar()
toolbar.sizeToFit()
var items = [UIBarButtonItem]()
if previousNextable {
let previousButton = UIBarButtonItem(image: UIImage(named: "Backward Arrow"), style: .plain, target: nil, action: nil)
previousButton.width = 30
if textField == textFields.first {
previousButton.isEnabled = false
} else {
previousButton.target = textFields[index - 1]
previousButton.action = #selector(UITextField.becomeFirstResponder)
}
let nextButton = UIBarButtonItem(image: UIImage(named: "Forward Arrow"), style: .plain, target: nil, action: nil)
nextButton.width = 30
if textField == textFields.last {
nextButton.isEnabled = false
} else {
nextButton.target = textFields[index + 1]
nextButton.action = #selector(UITextField.becomeFirstResponder)
}
items.append(contentsOf: [previousButton, nextButton])
}
let spacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: view, action: #selector(UIView.endEditing))
items.append(contentsOf: [spacer, doneButton])
toolbar.setItems(items, animated: false)
textField.inputAccessoryView = toolbar
}
}
}
I am calling this from other class as :
let field1 = UITextField()
let field2 = UITextField()
addInputAccessoryForTextFields([field1, field2], dismissable: true, previousNextable: true)

Although I'm not 100% convinced I understand your question, here goes:
From other classes, you want to call the actions of your buttons, but your actions are set to UITextField.becomeFirstResponder and UIView.endEditing.
Rather than call these methods directly, create your own methods the actions should call, and put these calls into those methods.
In addInputAccessoryForTextFields(...) change the previousButton's target and action to:
previousButton.target = self
previousButton.action = #selector(handlePreviousButton)
Now add the new method:
#objc func handlePreviousButton()
{
// you'll need to associate the previous button to a specific text field
// and hang onto that association in your class, such as in a property named textFieldRelatedToPreviousButton.
self.textFieldRelatedToPreviousButton.becomeFirstResponder()
}
Now you can call handlePreviousButton() directly from elsewhere in your class, if you wish, or even from other classes.
Update
I just noticed you're extending UIViewController. So you can't add storage by adding a property. You can add storage via objc_setAssociatedObject and then get it via objc_getAssociatedObject, however, to get around this. See this SO or this SO for details on that. So you can, for example, "attach" the textField to your previousButton so that you can access it via the handlePreviousButton() method you add to your extension. And you can pass in the previousButton as a parameter (the sender) to handlePreviousButton() too.
Update 2
Another approach to consider is to use the button's tag property to store the tag value of the related textField. (i.e. each button and its related textField would have the same tag value). So in handlePreviousButton(sender:UIBarButtonItem) you loop through all the UITextField children of your self.view and locate the one whose tag matches sender.tag . Then you can do what you need to that UITextField.

Related

Update rightBarButtonItems when segmented control change

I want to hide/show a UIBarButtonItem when a segmentedControl changes, this is my code:
#objc fileprivate func handleSegmentedChange() {
switch segmentedControl.selectedSegmentIndex {
case index0:
// Set the proper rightBarButtonItems, in the first load this bar button items will be nil, this is why we have to check first
self.navigationItem.rightBarButtonItems?.append(UIBarButtonItem(image: #imageLiteral(resourceName: "Filter2"), style: .plain, target: self, action: #selector(openBottomSheet)))
default:
self.navigationItem.rightBarButtonItems?.remove(at: 0)
}
}
However is not updating the views (hiding or showing anything).
Note I've also tried setting the rightBarButtonItems to nil before adding or removing items, however is not working.
How can I accomplish the desired effect?
If rightBarButtonItems is nil before you try to append or remove items to/from it, then nothing will happen as you cannot append or remove items to/from a non-existent array.
Instead of appending/removing to/from rightBarButtonItems, try just setting it directly to the items you want it to be, like this:
#objc fileprivate func handleSegmentedChange() {
switch segmentedControl.selectedSegmentIndex {
case 0:
let barButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "Filter2"),
style: .plain,
target: self,
action: #selector(openBottomSheet))
navigationItem.rightBarButtonItems = [barButtonItem]
// Note: If you're just dealing with one bar button item,
// you could also just use `navigationItem.rightBarButtonItem` like:
// navigationItem.rightBarButtonItem = barButtonItem
default:
navigationItem.rightBarButtonItems = nil // or `= []`
// Note: If you're just dealing with one bar button item,
// you could also just use `navigationItem.rightBarButtonItem` like:
// navigationItem.rightBarButtonItem = nil
}
}

Swift: Is there a cleaner way to pass an extra UIBarButtonItem action: Selector parameter other than using button.tag?

I'm currently working on a snapchat-like menu where clicking the left and right UIBarButtonItem makes the screen go in their respective directions.
TL;DR - I'm wondering if there's a (clean) built-in way of passing through a tag as an Optional type to avoid crashes.
override func viewDidLoad() {
super.viewDidLoad()
// Other setup code here
let leftButton = UIBarButtonItem(title: leftButtonString, style: UIBarButtonItemStyle.Plain, target: self, action: "navButtonClicked:")
let rightButton = UIBarButtonItem(title: rightButtonString, style: UIBarButtonItemStyle.Plain, target: self, action: "navButtonClicked:")
// These tags are not a good solution because they aren't optionals!
leftButton.tag = 0
rightButton.tag = 1 // this isn't necessary, but I don't want it to crash...
// More setup here
}
func navButtonClicked(sender: UIBarButtonItem) {
// Goes right by default
let currentX = self.parentScrollView!.contentOffset.x
var screenDelta = self.parentScrollView!.frame.width
if sender.tag == 0 {
screenDelta *= -1
}
UIView.animateWithDuration(0.5, animations: {() in
self.parentScrollView!.contentOffset = CGPoint(x: currentX + screenDelta, y: 0)
})
}
My current solution works, I'm just working towards writing cleaner code.
Thanks!
Option 1:
Create two properties in your view controller that correspond to each UIBarButtonItem. This way you'll be able to tell which one was tapped.
Option 2:
Sublass UIBarButtonItem and add a property that you want.

Previous and Next keyboard buttons using UITextfields on Static UITableView

I am using a UITableView with static cells to show a form with several UITextFields (each with a unique ordered tag from 9000 to 9015).
When i select a UITextField, the keyboard shows up with a UIToolbar that has 2 buttons, previous and next. The buttons work fine as long as the previous or next UITextField is drawn on screen, otherwise i can not select it because viewWithTag can't find the Field.
DEMO: http://s15.postimg.org/5xjsoiupl/ezgif_com_video_to_gif.gif
EDIT: I tryed using IQKeyboardManagerbut it has the same bug. If the cells are not visible then they are not detected so the next or previous arrow is disabled...
UITextFields
let numberToolbar = UIToolbar(frame: CGRectMake(0, 0, self.view.frame.size.width, 50))
numberToolbar.barStyle = UIBarStyle.Default
numberToolbar.tintColor = color_green
prev_keyboard_button = UIBarButtonItem(title: "Previous", style: UIBarButtonItemStyle.Plain, target: self, action: "keyboardPrevButtonTapped:")
next_keyboard_button = UIBarButtonItem(title: "Next", style: UIBarButtonItemStyle.Plain, target: self, action: "keyboardNextButtonTapped:")
numberToolbar.items = [
prev_keyboard_button,
next_keyboard_button,
UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: nil, action: nil),
UIBarButtonItem(title: "OK", style: UIBarButtonItemStyle.Plain, target: self, action: "keyboardOKButtonTapped:")]
numberToolbar.sizeToFit()
// bind vars
var_input_name.inputAccessoryView = numberToolbar
var_input_name.delegate = self
var_input_name.tag = 9000 // 9000 + index (0,1,2,3,..)
...
Previous and next code:
func textFieldDidBeginEditing(textField: UITextField) {
// current tag
current_input_tag = textField.tag
// check if it is the first
prev_keyboard_button.enabled = !(textField.tag == 9000)
// check if it is the last
next_keyboard_button.enabled = !(textField.tag == 9015)
}
func keyboardPrevButtonTapped(sender: UIBarButtonItem) {
if current_input_tag > 9000 {
// find next input
if let input = self.view.viewWithTag(current_input_tag - 1) as? UITextField {
input.becomeFirstResponder()
}
}
}
func keyboardNextButtonTapped(sender: UIBarButtonItem) {
if current_input_tag < 9015 {
// find next input
if let input = self.view.viewWithTag(current_input_tag + 1) as? UITextField {
input.becomeFirstResponder()
}
}
}
Is there a way to always draw the static cells or should i be implementing this differently?
if the sell is out of the screen it will be released. It's no matter if you use static or dynamic cells. Probably views you try to find is not exists at this moment

UINavigationController back button title doesn't update dynamically

I have a text field in my view controller and I want to display a custom title for the back button. This is to represent changes made in the text field.
override func viewDidLoad() {
super.viewDidLoad()
// Update back button in the nav bar
updateBackButton()
// Text field delegation
nameTextField.delegate = self
nameTextField.addTarget(self, action: "updateBackButton", forControlEvents: .EditingChanged)
}
func updateBackButton() {
let backButton = UIBarButtonItem(
title: formHasChanged ? "Cancel" : "Back",
style: .Done,
target: nil,
action: nil
)
print(backButton.title)
navigationController?.navigationBar.topItem?.backBarButtonItem = backButton
}
This does effect the back button only once, in the viewDidLoad method. On subsequent calls to updateBackButton() there's no visible change, even though print(backButton.title) does print the appropriate output.
What's missing from my approach in order to have a dynamically updated back button title?
Output from the updateBackButton() method's print statement.
Optional("Back")
Optional("Cancel")
Optional("Back")
Optional("Cancel")
Optional("Back")
If you want to call a function as an action from any control's target then you'll have to define it as #IBAction and use : while calling it. Just like
override func viewDidLoad() {
super.viewDidLoad()
// Update back button in the nav bar
// updateBackButton() // Now you don't need to call it here, I guess..
// Text field delegation
nameTextField.delegate = self
nameTextField.addTarget(self, action: "updateBackButton:",forControlEvents: .EditingChanged)
}
#IBAction func updateBackButton(sender: AnyObject!) {
let backButton = UIBarButtonItem(
title: formHasChanged ? "Cancel" : "Back",
style: .Done,
target: nil,
action: nil
)
print(backButton.title!)
navigationController?.navigationBar.topItem?.backBarButtonItem = backButton
}
Update :
Try changing this line
navigationController?.navigationBar.topItem?.backBarButtonItem = backButton
with
navigationController?.navigationBar.topItem?.leftBarButtonItem = backButton or simply navigationItem.leftBarButtonItem = backButton
PS : It will not show you < symbol but I think you dont need it as you are dealing with cancel and done.
Its working now. Hope this will work for you too !

access UIBarBarItem button inside tableView didSelectRowAtIndexPath swift

I created two UIBarButtonItem buttons inside viewDidLoad to be grouped on the right side of the navigation bar so that when certain condition is satisfied, one will be enabled and the other disabled and vice versa.
the buttons work fine except recognizing the condition and being enabled and disabled based on the condition.
override func viewDidLoad() {
super.viewDidLoad()
var btnDone: UIBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .Done,
target: self,
action: "btnDoneFunction")
btnDone.style = UIBarButtonItemStyle.Done
let btnAdd: UIBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .Add,
target: self,
action: "btnAddFunction")
var buttons = [btnAdd, btnDone]
self.navigationItem.rightBarButtonItems = buttons
if checked.count >= 1 {
btnDone.enabled = true
btnAdd.enabled = false
} else {
btnDone.enabled = false
btnAdd.enabled = true
}
}
checked is an array that holds row number of checked rows in the table inside didSelectRowAtIndexPath.
I tried moving the condition part:
if checked.count >= 1 {
btnDone.enabled = true
btnAdd.enabled = false
} else {
btnDone.enabled = false
btnAdd.enabled = true
}
inside of didSelectRowAtIndexPath, but i can not access the buttons there.
Is there any way I can implement this? I just want to check if there are any rows checked in the table and disable the 'Add' button and enable the 'Done' button. If no row is checked, the reverse.
This is inside a UITableViewController class.
You are creating the buttons in viewDidLoad so the scope of the variable is just for that function, you need to declare it inside the class.
class myClass{
lazy var btnDone:UIBarButtonItem = {
var done: UIBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .Done,
target: self,
action: "btnDoneFunction")
return done
}()
func anotherFunc(){
if checked.count >= 1 {
btnDone?.enabled = true
} else {
btnDone?.enabled = false
}
}
}
I create a generic example above that declares a btnDone as a class variable with lazy initialization, that means it will be create just as need

Resources