Programattically set barbutton " System Item" - ios

I want to set share button on rightbarbuttonitem of navigation controller.
I don't want to add custom images , I want use share button image provided by Xcode.This is how I do in storyboard.
I set Style , and set System_Item as Action.
Now the question is how do I set System_Item programatically, if I create barbuttonitem programatically ?
let shareButton = UIBarButtonItem(title: "",
style: .plain,
target: self,
action: #selector(shareAction(sender:)))
shareButton.tintColor = AppColor.barButtonColor
navigationItem.rightBarButtonItem = shareButton

you need to use UIBarButtonSystemItemAction option to get share action directly
let share = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(shareAction(sender:)))
shareButton.tintColor = AppColor.barButtonColor
navigationItem.rightBarButtonItem = share
and handle the action as like
#objc func shareAction(sender: UIBarButtonItem) {
}

You can create an extension to do this ->
public extension UIViewController {
func setRightBarButtonItem(tintColor: UIColor = AppColor.barButtonColor) {
let button = UIButton(type: .system)
button.tintColor = tintColor
button.setImage(UIImage(.action), for: .normal) // <-- Set system icon to button
button.translatesAutoresizingMaskIntoConstraints = false
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tap))
button.addGestureRecognizer(tapGesture)
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: button)
}
#objc func tap() {
// Do stuff
}
}
Then in your ViewController
override func viewDidLoad() {
super.viewDidLoad()
setRightBarButtonItem()
// OR
setRightBarButtonItem(tintColor: .blue)
}
UIImage extension to use system icons without version control
extension UIImage {
public convenience init?(_ systemItem: UIBarButtonItem.SystemItem) {
guard let sysImage = UIImage.imageFrom(systemItem: systemItem)?.cgImage else {
return nil
}
self.init(cgImage: sysImage)
}
private class func imageFrom(systemItem: UIBarButtonItem.SystemItem) -> UIImage? {
let sysBarButtonItem = UIBarButtonItem(barButtonSystemItem: systemItem, target: nil, action: nil)
let toolBar = UIToolbar()
toolBar.setItems([sysBarButtonItem], animated: false)
toolBar.snapshotView(afterScreenUpdates: true)
if let buttonView = sysBarButtonItem.value(forKey: "view") as? UIView{
for subView in buttonView.subviews where subView is UIButton {
let button = subView as! UIButton
let image = button.imageView!.image!
return image
}
}
return nil
}
}

Related

custom back button in navigation in iOS

I know this question has been asked before but nothing worked for me and I had to ask it again.
I want an image as my back button in navigation bar, just want to change the appearance of the back button. I don't want to add a button and add selectors for it.
I tried the following code:
let backImage = UIImage(named: "Back_button")
let backAppearance = UIBarButtonItem.appearance()
backAppearance.setBackButtonBackgroundImage(backImage, for: .normal, barMetrics: .default)
navigationController?.navigationBar.backIndicatorTransitionMaskImage = backImage
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItem.Style.plain, target: nil, action: nil)
I also tried setting the back image and back mask using storyboard but both these approaches place a black circle on my back image.
I tried setting another image as back mask by setting its alpha content equal to zero using the code but it didn't work either.
please help.
let backButton = UIBarButtonItem()
backButton.title = "Back"
backButton.image = UIImage(named: "Back_button")
self.navigationController?.navigationBar.topItem?.backBarButtonItem = backButton
You can do this to customize your Back button. And you don't have to worry about adding selectors.
This code works with Swift 5.
let backButton: UIButton = UIButton()
backButton.setImage(UIImage(named: "back"), for: UIControl.State())
backButton.addTarget(self, action:#selector(SearchResultsViewController.onBack), for: UIControl.Event.touchUpInside)
let leftBarButtonItem = UIBarButtonItem(customView: backButton)
navigationItem.leftBarButtonItem = leftBarButtonItem
I used this code to customize the back button on only one of my views:
self.navigationController?.navigationBar.topItem?.backButtonTitle = ""
let backButton = UIBarButtonItem(image: UIImage(named: "back"), style: .plain, target: self, action: #selector(goBack))
navigationItem.leftBarButtonItem = backButton
#objc func goBack() {
self.navigationController?.popViewController(animated: true)
}
Create a custom class for define navigation bar traits
Create an extension to UINavigationController for configure it
import UIKit
private final class MyNavigationBarTraits {
public var backIndicatorImage: UIImage?
public var backIndicatorTransitionMaskImage: UIImage?
public func apply(to navigationBar: UINavigationBar) {
navigationBar.backIndicatorImage = backIndicatorImage
navigationBar.backIndicatorTransitionMaskImage = backIndicatorTransitionMaskImage
}
public init(navigationBar: UINavigationBar) {
backIndicatorImage = navigationBar.backIndicatorImage
backIndicatorTransitionMaskImage = navigationBar.backIndicatorTransitionMaskImage
}
}
public typealias Callback<T> = (_: T) -> Void
public extension UINavigationController {
private struct AssociationKeys {
static var navigationBarTraits = "ws_nc_navigationBarTraits"
}
private var navigationBarTraits: MyNavigationBarTraits? {
get {
return objc_getAssociatedObject(self, &AssociationKeys.navigationBarTraits) as? MyNavigationBarTraits
}
set {
objc_setAssociatedObject(self, &AssociationKeys.navigationBarTraits, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
func configureBar(block: Callback<UINavigationBar>) {
navigationBarTraits = MyNavigationBarTraits(navigationBar: navigationBar)
block(navigationBar)
}
func resetBar() {
navigationBarTraits?.apply(to: navigationBar)
navigationBarTraits = .none
}
}
And then you can configure your navigation bar in your ViewController's viewWillAppear (for example tintColor)
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.configureBar { navigationBar in
// You can customize your navigation bar in here!
navigationBar.tintColor = .red
}
}
If you want to use this customization just in one View Controller you should reset bar in your View Controller's viewWillDisappear
public override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.resetBar()
}
Simply Add Below Methods in Your ViewController :
func setLeftBarBackItem() {
let leftBarBackItem = UIBarButtonItem(image: #imageLiteral(resourceName: "imgBack"), style: .plain, target: self, action: #selector(self.clickToBtnBackItem(_:)))
self.navigationItem.leftBarButtonItem = leftBarBackItem
}
func clickToBtnBackItem(_ sender: UIBarButtonItem) {
view.endEditing(true)
_ = navigationController?.popViewController(animated: true)
}
func setTranspertNavigation()
{
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.isTranslucent = true
self.navigationController?.view.backgroundColor = .clear
}
Inside Your ViewController's ViewDidLoad Method, Set backButton As :
self.navigationController?.isNavigationBarHidden = false
AppDelegate.shared().setupNavigationBar()
setLeftBarBackItem()
setTranspertNavigation()
self.title = "Title Here"

Accessing rightBarButton function from another ViewController in Swift

I'm trying to access the rightBarButton function for showing cart from an another ViewController, although I can call that function but it won't display any image i.e cart image
Below code show how am i calling this function.
override func viewDidLoad() {
super.viewDidLoad()
// Adding Right Bar Button Item
let rightBarButton = RightBarButton()
rightBarButton.addingRightButton()
}
Below code show about the rightBarButton function.
class RightBarButton {
var storyboard = UIStoryboard()
var navigationItem = UINavigationItem()
var navigationController = UINavigationController()
func addingRightButton() {
let image = UIImage(named: "cart")
let finalImage = resizeImage(image: image!, newWidth: 30)
navigationItem.rightBarButtonItem = FFBadgedBarButtonItem(image: finalImage, target: self, action: #selector(rightButtonTouched))
let button = navigationItem.rightBarButtonItem as? FFBadgedBarButtonItem
cartCount { (cartCount) in
print("Api calling done ...")
print(cartCount)
button?.badge = "\(cartCount)"
}
}
#objc func rightButtonTouched() {
// print("Event Called")
}
func cartCount(completion: #escaping (Int) -> Void) {
// print("Api calling ...")
}
}
If I add this code inside the viewDidLoad() the image will display and the click event work as well.
If you find any solution regarding this code please help!
It doesn't work because navigationItem in your RightBarButton class is not part to of any navigation contoller (as it is manually created and not explicitly attached).
Make the created FFBadgedBarButtonItem instance to be accessible to the view controller so you can add it correctly:
override func viewDidLoad() {
super.viewDidLoad()
// Adding Right Bar Button Item
let rightBarButton = RightBarButton()
rightBarButton.addingRightButton()
navigationItem.rightBarButtonItem = rightBarButton.badgeBarItem // badgeBarItem is the property exposed to the view controller
}
The edited RightBarButton class:
class RightBarButton {
var badgeBarItem: FFBadgedBarButtonItem?
func addingRightButton() {
let image = UIImage(named: "cart")
let finalImage = resizeImage(image: image!, newWidth: 30)
badgeBarItem = FFBadgedBarButtonItem(image: finalImage, target: self, action: #selector(rightButtonTouched))
cartCount { [weak self] cartCount in
print("Api calling done ...")
print(cartCount)
self?.badgeBarItem?.badge = "\(cartCount)"
}
}
#objc func rightButtonTouched() {
// print("Event Called")
}
func cartCount(completion: #escaping (Int) -> Void) {
// print("Api calling ...")
}
}
You need to retreive an instance of needed ViewController with rightBarButton, say neededViewController. And then you can call it like here:
neededViewController.viewDidLoad()
or make another method in neededViewController's class with next content:
func addRB () {
// let rightBarButton = RightBarButton(image: UIImage(named: "imageName"), landscapeImagePhone: UIImage(named: "imageName"), style: .done, target: self, action: nil)
let nc = navigationController
let navBar = nc?.navigationBar
let but = UIBarButtonItem(image: UIImage(named: "imageName"), landscapeImagePhone: UIImage(named: "imageName"), style: .done, target: self, action: nil)
// try to create RightBarButton with defined images and a text and pass it instead of `but`
navBar?.topItem?.setRightBarButton(but, animated: false)
}
and call neededViewController.addRB()

How to customize UINavigationBar for the entire app?

This is a bit different question than the previously asked. So be nice.
The entire app has a navigation bar with different colors.
The entire app has few options in its navigation bar. They all are same.
What am I expecting?
I have created this extension for UINavigationController and able to change the navigation bar's background color as per the view controller I will be.
extension UINavigationController {
func updateNavigationBar(withViewControllerID identifier: String?) {
setupNavigationBarButtons()
if identifier == kFirstVCIdentifier {
self.navigationBar.tintColor = .white
self.navigationBar.barTintColor = .red
} else if identifier == kSecondVCIdentifier {
self.navigationBar.tintColor = .white
self.navigationBar.barTintColor = .green
} else if identifier == kThirdVCIdentifier {
self.navigationBar.tintColor = .white
self.navigationBar.barTintColor = .blue
}
self.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white]
self.navigationBar.isTranslucent = false
self.navigationBar.clipsToBounds = false
self.navigationBar.shadowImage = UIImage.init()
self.view.backgroundColor = .clear
}
internal func setupNavigationBarButtons() {
let barButtonItemSettings = UIBarButtonItem.init(title: "Settings", style: .plain, target: self, action: #selector(actionSettings))
let barButtonItemSpace = UIBarButtonItem.init(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let barButtonItemLogOut = UIBarButtonItem.init(title: "LogOut", style: .plain, target: self, action: #selector(actionLogOut))
let buttons = [barButtonItemSettings, barButtonItemSpace, barButtonItemLogOut]
self.navigationItem.rightBarButtonItems = buttons
}
#objc internal func actionSettings() {
print("Settings")
}
#objc internal func actionLogOut() {
print("LogOut")
}
}
then, I am trying to add UIBarButton within that extension but they are not showing up. So I need to fix it. And if it will be visible, how do I get its actions call so that I can handle UIBarButton's touch event in the entire app. Properly? No patch, please.
I am using it like this:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.updateNavigationBar(withViewControllerID: self.restorationIdentifier?)
}
Try this and Make extension for UINavigationItem.
extension UINaviationItem {
func setupNavigationBarButtons()
{
let barButtonItemSettings = UIBarButtonItem.init(title: "Settings", style: .plain, target: self, action: #selector(actionSettings))
let barButtonItemSpace = UIBarButtonItem.init(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let barButtonItemLogOut = UIBarButtonItem.init(title: "LogOut", style: .plain, target: self, action: #selector(actionLogOut))
let buttons = [barButtonItemSettings, barButtonItemSpace, barButtonItemLogOut]
self.rightBarButtonItems = buttons
}
#objc func actionSettings() {
print("Settings")
if let current = UIApplication.shared.delegate?.window
{
var viewcontroller = current!.rootViewController
if(viewcontroller is UINavigationController){
viewcontroller = (viewcontroller as! UINavigationController).visibleViewController
print( "currentviewcontroller is %#/",viewcontroller )
}
}
}
#objc func actionLogOut() {
print("LogOut")
}
}
self.navigationController?.updateNavigationBar(withViewControllerID: "second")
self.navigationItem.setupNavigationBarButtons()

Selector's "jump" in NavigationItem calling function that is in parent NavigationBar

I have the following setup in code:
class MyNavigationBar: UINavigationBar {
override func awakeFromNib() {
super.awakeFromNib()
let myNavItem = MyNavigationItem()
setItems([myNavItem], animated: false)
}
//Weird, the selector of the item calls this function
func doSomething() {
// This do some stuff
}
}
class MyNavigationItem: UINavigationItem {
private let myButton :UIBarButtonItem = {
let barButton = UIBarButtonItem(image: UIImage(named: "ic_topnav_userpic"), style: UIBarButtonItemStyle.Done, target: nil, action: #selector(doSomething))
return barButton
}()
// Initializers
init() {
super.init(title: "Title")
rightBarButtonItem = myButton
}
//..other initializers
//This function is never called
func doSomething() {
// This should do some stuff
}
}
When I make any UINavigationBar in my Storyboard of type MyNavigationBar. Then, the function that is "selected" when I tap over myButton item is the one present in MyNavigationBar's implementation.
Why is this happening?
It should behave this way?, I was expecting that the function called were the one inside MyNavigationItem.
So, here's the thing:
When I stated:
private let myButton :UIBarButtonItem = {
let barButton = UIBarButtonItem(image: UIImage(named: "ic_topnav_userpic"), style: UIBarButtonItemStyle.Done, target: nil, action: #selector(doSomething))
return barButton
}()
This method is executed before having an instance of this class (a.k.a self) so this was preventing me from doing #selector(self.doSomething) as #Dershowitz123 suggested.
A quick way to avoid this is making this let variable a lazy var instead:
private lazy var myButton :UIBarButtonItem = {
let barButton = UIBarButtonItem(image: UIImage(named: "ic_topnav_userpic"), style: UIBarButtonItemStyle.Done, target: self, action: #selector(self.showEditProfile))
return barButton
}()
The only thing that still is weird to me is this jump that
Selectors do when in such situation. 😩
Edit:
let barButton = UIBarButtonItem(image: UIImage(named: "ic_topnav_userpic"), style: UIBarButtonItemStyle.Done, target: nil, action: #selector(doSomething))
Target should be self and #selector(self.doSomething).

How to disable UIBarButtonItem based on tag in swift?

I had created button in toolbar and set tag to it. I had declared that button locally. Now in some other function I want to disable the button as per condition.
Is there any way to disable UIBarButton based on tag without declaring the button globally.
****************************************************
func setToolBar()
{
let toolbar : UIToolbar = UIToolbar()
toolbar.sizeToFit()
let prevButton = UIBarButtonItem(title: "<", style: .Plain, target: self, action:"goBack:")
prevButton.tag = 20
let nextButton = UIBarButtonItem(title: ">", style: .Plain, target: self, action:"goNext:")
nextButton.tag = 30
let flexButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: self, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: .Plain, target: self, action:"done:")
let arrItems : NSArray = [prevButton , nextButton,flexButton, doneButton]
[toolbar.setItems(arrItems as? [UIBarButtonItem], animated: true)]
city.inputAccessoryView = toolbar
birthdate.inputAccessoryView = toolbar
}
****************************************************
func goBack(sender: UIBarButtonItem)
{
let prevTag : NSInteger = CurrentTextFiled.tag - 1
let nextResponder = CurrentTextFiled.superview?.viewWithTag(prevTag)
if((nextResponder) != nil)
{
nextResponder!.becomeFirstResponder()
}
else
{
//Disable UIBarButton here with tag 20
}
}
****************************************************
func goNext(sender: UIBarButtonItem)
{
let prevTag : NSInteger = CurrentTextFiled.tag + 1
let nextResponder = CurrentTextFiled.superview?.viewWithTag(prevTag)
if((nextResponder) != nil)
{
nextResponder!.becomeFirstResponder()
}
else
{
//Disable UIBarButton here with tag 30
}
}
I distilled this down to a code snippet that can be run inside a playground, and verified this within Xcode.
In essence, you want to give a tag to the toolbar and find it with viewWithTag(). Once you have the toolbar, traverse thru its items array and filter out the button you want to manipulate. Don't forget to add the toolbar to your viewController's view as a subview.
import UIKit
import XCPlayground
class MyViewController: UIViewController {
override func viewDidLoad() {
self.setToolBar()
self.disableButton(20)
}
func setToolBar()
{
let toolbar : UIToolbar = UIToolbar()
toolbar.tag = 10
toolbar.sizeToFit()
self.view.addSubview(toolbar) //must add to subview of viewcontroller
let prevButton = UIBarButtonItem(title: "<", style: .Plain, target: self, action:"goBack:")
prevButton.tag = 20
let nextButton = UIBarButtonItem(title: ">", style: .Plain, target: self, action:"goNext:")
nextButton.tag = 30
let flexButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: self, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: .Plain, target: self, action:"done:")
let arrItems : NSArray = [prevButton, nextButton,flexButton, doneButton]
[toolbar.setItems(arrItems as? [UIBarButtonItem], animated: true)]
print("Toolbar set.")
}
func disableButton(tag: Int)
{
if let toolbarWithButtons = self.view.viewWithTag(10) as? UIToolbar {
print("Toolbar found by tag. Trying to disable button with tag \(tag).")
var buttonToDisable: Array<AnyObject>?
if let buttons = toolbarWithButtons.items {
buttonToDisable = buttons.filter({
(x : AnyObject) -> Bool in
if let button = x as? UIBarButtonItem {
if button.tag == tag {
return true
}
}
return false
})
if let button = (buttonToDisable!.first as? UIBarButtonItem){
button.enabled = false
print("Button with tag \(button.tag) enabled: \(button.enabled)")
}
}
}
else {
print("Toolbar not found by tag.")
}
}
}
var ctrl = MyViewController()
XCPShowView("Playground VC", ctrl.view)
I hope this is helpful!
You can get view a using tag.
if let button = self.view.viewWithTag(YOUR_TAG_HERE) as? UIBarButtonItem {
button.enabled = false
}
Instances of UIView have a method to find sub-views based on the tag:
func viewWithTag(_ tag: Int) -> UIView?
I'm pretty sure this will search deeply, so, as long as you can get to a view near the top of the hierarchy, this should work for you.
If some one still need this:
In case where you will have navigationController, so you can access to your UIBarButtonItem like so
if let barButtonItem = navigationItem.rightBarButtonItems?.first(where: {$0.tag == <YOUR_TAG> }) {
// Do staff here
}
and in case with toolBar situation is similar:
if let barButtonItem = toolBar.items?.first(where: {$0.tag == <YOUR_TAG>}) {
// Do staff here
}

Resources