Get the View of UIBarButtonItem in Swift - ios

Hopefully, this is an easy question. I am trying to obtain the UIView of a UIBarButtonItem. Looking at the following StackOverflow question I came up with the following code:
let notificationButton = UIBarButtonItem(image: UIImage(named: "icon_notification.png"), style: .plain, target: self, action: nil)
let notificationView = notificationButton.value(forKey: "view") as? UIView
However, notificationView is nil. So, thoughts on what I failed to interpret from the linked StackOverflow question?

Starting from iOS 11 this code will not work cause line of code below will not cast UIView. Also it's counting as private API and seems to be will not pass AppStore review.
guard let view = self.value(forKey: "view") as? UIView else { return }
Thread on: forums.developer.apple

So, DS Dharma gave me a idea which ended up working. The "view" value is only available after it is assigned to the toolbar navigation item like this:
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "icon_notification.png"), style: .plain, target: self, action: nil)
self.navigationItem.rightBarButtonItem?.addBadge(number: 0, withOffset: CGPoint(x: 7.0, y: 0.0) , andColor: UIColor.black, andFilled: true)
where the addBadge() function needs the UIView. BTW, if anyone is wondering, the addBadge function was taken from this post: http://www.stefanovettor.com/2016/04/30/adding-badge-uibarbuttonitem
Highly recommended if you need this piece of functionality.

Related

UIBarButtonItem position incorrect on < iOS 11

On iOS 10 and below my bar buttons are positioned incorrectly within the nav bar. What could be causing this?
This is the function to set the button:
func setBarButton(image: UIImage?, position: BarButtonPosition, target: AnyObject, selector: Selector) -> Void {
let barButton = UIBarButtonItem(title: "", style: .plain, target: target, action: selector)
barButton.image = image
if position == .left {
navigationItem.leftBarButtonItem = barButton
navigationItem.leftBarButtonItem?.tintColor = UIColor.zbPrimary
} else {
navigationItem.rightBarButtonItem = barButton
navigationItem.rightBarButtonItem?.tintColor = UIColor.zbPrimary
}
}
And where I am calling the function within viewDidLoad:
setBarButton(image: #imageLiteral(resourceName: "settings-icon"), position: .right, target: self, selector: #selector(openSettings))
This only happens for UIBarButtonItem where an image is being set, not those with just a title or custom view
The title of your question was misleading but after reading your question I realized it was the same problem I had on one of my projects.
I was setting the barButtonItem title through code and when I changed it from:
navigationItem.leftBarButtonItem?.title = ""
To:
navigationItem.leftBarButtonItem?.title = nil
The problem disappeared.
I almost lost a entire day to fix this.
I hope this solves the problem for you.

filterBarButton?.value(forKey: "view") is nil

Question
Using breakpoints, I found that filterBarButton is not nil, but the filterBarButton?.value(forKey: "view")is nil. The filterBarButton should have a view because I assigned it to a button in the viewDidLoad. Why is this happening? How do I fix this?
I use the frame of right bar button (the Filter button) to place the triangle image underneath it as shown below. I want the triangle image to be centered and directly underneath the Filter button.
Code
override func viewDidLoad() {
let filterButton = UIBarButtonItem(title: "Filter", style: .plain, target: self, action: #selector(filterButtonTapped))
navigationController?.navigationBar.topItem?.rightBarButtonItem = filterButton
setUpFilter()
}
func setUpFilter() {
let filterBarButton = navigationController?.navigationBar.topItem?.rightBarButtonItem
let btnView = filterBarButton?.value(forKey: "view") as AnyObject
}
Reference

Increase NavigationBar height

I have the following code:
func navbarbutton() {
UIView.animateWithDuration(0.2, animations: { () -> Void in
let current = self.navigationController?.navigationBar.frame
self.navigationController?.navigationBar.frame = CGRectMake(self.frame!.origin.x, self.frame!.origin.y, self.frame!.size.width, current!.size.height + 50)
self.navigationController?.navigationBar.layoutIfNeeded()
})
}
I'm able to increase the height of the navigation bar by 50 dp. That's not the issue for me. The issue I'm having is that the UIBarButtonItems are all aligned to the bottom. How can I get them aligned to the top so that I can add more to the bottom myself? I'm getting something as per the image:
Is it possible to get it aligned to the top?
Unfortunately you can't do that.
The view hierarchy within the UINavigationBar is private so you can't manipulate it without iterating over the subviews and going all hacky with it. This probably isn't a good idea.
Out of curiosity I looked at the Messages app in iOS 10 using the view debugger because they obviously do this. They actually achieve the layout by adding their own UIButton to replace the back and rightBarButtonItem. This is something you would be able to do however they also set the alpha of one of the internal (and private) content views to zero so that the original content is no longer visible.
This is something that you won't be able to do so easily unfortunately. You could try to hack things about until it works but remember you also need to handle pushes/pops, in call status bars, interactive transitions and rotation events.
If however you wasn't going for the iMessage style and just wanted to add some content underneath your navigation bar, why not look at pinning a UIVisualEffectView to the topLayoutGuide in your UIViewController? You can get a fairly nice look pretty easily and it saves hacking stuff about a lot. Here's an example:
Try this code:
Note: Code tested in Swift 3.
Answer 1: Updated Answer
class ViewController: UIViewController {
var customBar: UINavigationBar = UINavigationBar()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//Title
title = "Some Title"
// Add bar button item
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Add", style: .plain, target: self, action: #selector(addTapped))
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(addTapped))
self.customBar.frame = CGRect(x:0, y:0, width:view.frame.width, height:(navigationController?.navigationBar.frame.height)! + 50)
self.customBar.backgroundColor = UIColor.green
self.view.addSubview(customBar)
}
func addTapped() {
print("Button Pressed")
}
Output:
Answer 2:
override var isViewLoaded: Bool {
// Add bar button item
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Add", style: .plain, target: self, action: #selector(addTapped))
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(addTapped))
//Vertical and Horizonal barButtonItem position offset
navigationItem.leftBarButtonItem?.setTitlePositionAdjustment(UIOffset(horizontal: 0, vertical: 20), for: UIBarMetrics.default)
navigationItem.rightBarButtonItem?.setTitlePositionAdjustment(UIOffset(horizontal: 0, vertical: 20), for: UIBarMetrics.default)
return true
}
func addTapped() {
print("Button Pressed")
}
Note: Above code only works in isViewLoaded: Bool method.But, No luck.When, I tried this code in other viewLoad method.
Output 1: barButtonItem moved 20 pixel up vertically.
Output 2: barButtonItem moved 20 pixel down vertically.
Hope, Above code fix your problem.

#selector on UIBarButtonItem

I'm developing my first iOS app in Swift 2.2 and I have the following problem.
In an utility class, I have the following static method, called by some different UIViewController.
static func setNavigationControllerStatusBar(myView: UIViewController, title: String, color: CIColor, style: UIBarStyle) {
let navigation = myView.navigationController!
navigation.navigationBar.barStyle = style
navigation.navigationBar.barTintColor = UIColor(CIColor: color)
navigation.navigationBar.translucent = false
navigation.navigationBar.tintColor = UIColor.whiteColor()
myView.navigationItem.title = title
let menuButton = UIBarButtonItem(image: UIImage(named: "menu"),
style: UIBarButtonItemStyle.Plain ,
target: self, action: #selector("Utils.menuClicked(_:)"))
myView.navigationItem.leftBarButtonItem = menuButton
}
func menuClicked(sender: UIButton!) {
// do stuff
}
I'm trying in some different ways to associate a #selector for this button, however I always have the following error.
No quotes.
#selector(Utils.menuClicked(_:))
func menuClicked should be in your view controller class. But if for some reason it isn't, you can do
class Utils {
static let instance = Utils()
let menuButton = UIBarButtonItem(image: UIImage(named: "menu"),
style: UIBarButtonItemStyle.Plain ,
target: Utils.instance, action: #selector(Utils.menuClicked(_:)))
#objc func menuClicked(sender: UIBarButtonItem) {
// do stuff
}
}
Swift 2.2 deprecates using strings for selectors and instead introduces new syntax: #selector.
Using #selector will check your code at compile time to make sure the method you want to call actually exists. Even better, if the method doesn’t exist, you’ll get a compile error: Xcode will refuse to build your app, thus banishing to oblivion another possible source of bugs.
So remove the double quote for your method in #selector. It should work!

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.

Resources