Show or hide UIBarButtonItem in UIToolbar programmatically - ios

var toolBar: UIToolbar!
let nextBarButton = UIBarButtonItem(title: "Next", style: .Plain, target: self, action: "nextButtonPressed")
self.toolBar.setItems([nextBarButton], animated: true)
How to hide nextButton in ToolBar?
I used the following code and it did not work.
self.toolbar.items.indexOf(1).hidden = true

This answer is inspired by this answer.
I will improve on it and make all the work programmatically. No need to update/set the class of the UIBarButtonItem instance to use the new subclass anymore.
We could just add isHidden attribute to the UIBarButtonItem. Then just use it as you want.
extension UIBarButtonItem {
var isHidden: Bool = false {
didSet {
isEnabled = !isHidden
tintColor = isHidden ? UIColor.clear : UIColor.black
}
}
}
In your case, after you add the extension (outside any class). You could use it as:
self.toolbar.items.indexOf(1).isHidden = true

Looks like it's a 'next' button, the standard way to handle when there is nothing to move to next is to simply disable it like this:
nextBarButton.enabled = NO;
The icon is greyed out automatically too.

UIBarButton does not have a "hidden" property so you cannot hide. You can remove it. Since you only have one button on the toolbar, you can clear all the items on the toolbar.
toolBar.setItems([], animated: true)
If you wanted to remove a specific item, you can use the index.
var items = toolBar.items
items?.removeAtIndex(0)
toolBar.setItems(items, animated: true)

You can add simple function, for hiding state
extension UIBarButtonItem {
func set(hide: Bool) {
isEnabled = !hide
tintColor = hide ? .clear : .white
}
}

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
}
}

Toolbar with "Previous" and "Next" for keyboard

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.

How to change selected segment tintColor of UISegmentedControl in Swift

I want to change the tintColor of UISegmentedControl selected segment in Swift 3.
I've searched a lot of answers in Objective-c...
This is my code:
class ViewController:UIViewController{
var segment:UISegmentedControl
override func viewDidLoad() {
super.viewDidLoad()
segment.insertSegment(withTitle: "AAA", at: 0, animated: true)
segment.insertSegment(withTitle: "BBB", at: 1, animated: true)
segment.insertSegment(withTitle: "CCC", at: 2, animated: true)
segment.addTarget(self, action: #selector(changeValue), for: .valueChanged)
segment.selectedSegmentIndex = 0
view.addSubview(segment)
}
func changeValue(sender:AnyObject) {
//I don't know how to do that change color when segment selected
//
}
}
Thanks!
Use the code below for above ios 13,
if #available(iOS 13.0, *) {
segment.selectedSegmentTintColor = .red
} else {
segment.tintColor = .red
}
To programmatically change tint color of segment,
segment.tintColor = UIColor.yellow
if you want to set the color of the title for example, you can do it like this:
let titleTextAttributes = [NSForegroundColorAttributeName: Color.blue]
segmentedControl.setTitleTextAttributes(titleTextAttributes, forState: .Selected)
In Main.storyboard, select segmentControl and change the property "Tint" as shown in below screenshot:
If you create the segmentedControl programmatically, then use this:
segment.tintColor = UIColor.red
A new property was added in iOS 13: selectedSegementTintColor. The old tintColor property no longer works in iOS 13.
You can find a more complete writeup of the changes here iOS 13 UISegmentedControl: 3 important changes
Add the following code to your changeValue function:
func changeValue(sender: UISegmentedControl){
for i in 0..<sender.subviews.count {
if sender.subviews[i].isSelected() {
var tintcolor = UIColor.red // choose the color you want here
sender.subviews[i].tintColor = tintcolor
}
else {
sender.subviews[i].tintColor = nil
}
}
}
This is a swift version of the accepted answer from this question: UISegmentedControl selected segment color

Simple way to set Button Active Color in iOS Swift

Ive been looking around for methods to change active state on Button click. I have 4 buttons when clicked the reposition my scroll view as programmed.
I am trying to set the background color to fade a bit if clicked. Im able to set the background color but it stays the same faded color when another is clicked. It doesnt return to an inactive state.
Any simple way to acheive this onckick button behavior globally?
heres my button click func's:
#IBAction func tab1(sender: UIButton)
{
slScrollView.setContentOffset(CGPointMake(0.0, 0.0), animated: true)
tab1.backgroundColor = UIColor.grayColor()
tab2.selected = false
tab3.selected = false
}
#IBAction func tab2(sender: UIButton)
{
slScrollView.setContentOffset(CGPointMake(0.0, 650.0), animated: true)
tab2.backgroundColor = UIColor.grayColor()
tab1.selected = false
tab3.selected = false
}
#IBAction func tab3(sender: UIButton) {
slScrollView.setContentOffset(CGPointMake(0.0, 1370.0), animated: true)
tab3.backgroundColor = UIColor.grayColor()
tab1.selected = false
tab2.selected = false
}
First, create an IBOutletCollection (or four separate outlets) to the buttons. Then create an IBAction method and set all four buttons to fire it when tapped. In the method, do the background fade animation on the button that fired the action (which is passed into the handler as its sender argument, then reset the states of the other outlet buttons.
The way I would code it:
// Outlet to all of the buttons. ctrl+drag each button to this outlet.
#IBOutletCollection buttons = [UIButton]()
// Set *all* of the buttons to fire this method.
#IBAction func buttonTapped(sender: AnyObject!) {
(sender as? UIButton).backgroundColor = <whatever>
for button in buttons.filter({ $0 != sender }) {
button.backgroundColor = <default>
}
}

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