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.
Related
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
}
}
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.
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
Please take a look at my screenshot, the blue "Back" text always show on iphone plus (6s plus, 7 plus for both simulator and real device) . It does not show on smaller screen iphone. I tried lot of way to hide/change it from present/previous controller but no luck.
So why does it work on smaller iphone but not the plus one ?
Can anyone help me:(. Thanks.
Here is the code:
#IBAction func filter(_ sender: Any) {
let view:FilterViewController = self.storyboard?.instantiateViewController(withIdentifier: "FilterViewController") as! FilterViewController
view.superVC = self
view.currentFilter = currentFilter
self.setLeftCloseNavigation()
self.navigationController?.pushViewController(view, animated: true)
}
func setLeftCloseNavigation(){
self.navigationController?.navigationBar.backgroundColor = UIColor.clear
self.navigationController?.navigationBar.isTranslucent = true
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController?.navigationBar.layer.mask = nil
self.navigationController?.navigationBar.backIndicatorImage = UIImage(named: "icon_close")?.withRenderingMode(.alwaysOriginal)
self.navigationController?.navigationBar.backIndicatorTransitionMaskImage = UIImage(named: "icon_close")?.withRenderingMode(.alwaysOriginal)
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
}
And here is the viewDidLoad in pushed controller:
override func viewDidLoad() {
super.viewDidLoad()
statusBar = UIColor.black
setResetNavigation() }
func setResetNavigation(){
navigationItem.hidesBackButton = false
let skipButton = UIButton(frame: CGRect(x: 0, y: 0, width: 70, height: 30))
skipButton.setTitle("Reset all".localized(), for: .normal)
skipButton.setTitleColor(UIColor.black, for: .normal)
skipButton.titleLabel?.font = UIFont(name: "HJGothamMedium", size: 16)
skipButton.addTarget(self, action: #selector(resetAllClicked), for: .touchUpInside)
let skip = UIBarButtonItem(customView: skipButton)
navigationItem.rightBarButtonItem = skip
}
This is the view hierarchy
Add this function :
override func viewDidAppear(_ animated: Bool) {
setResetNavigation()
self.navigationController?.navigationBar.backItem?.title = ""
}
try this
self.navigationItem.hidesBackButton = true
Or Check your storyboard it will remain
Use the below line to remove the text
navigationController?.navigationBar.topItem?.title = ""
You can inspect your UI hierarchy and if found related view then remove that view :
You can also invoke the view debugger by choosing View UI Hierarchy from the process view options menu in the debug navigator, or by choosing Debug > View Debugging > Capture View Hierarchy.
To hide the back text you need to set navigation item title to space character on the view controller that pushes the presented view controller:
self.navigationItem.title = " "
Be aware you have to set it on the previous view controller and not on top most one. Also you have to set a space character and not an empty string !!!
Also you can do this directly on storyboard
From below code you can set backButton text colour to any colour you want.You can simply set backButton to clear textColor. So, It won't be visible when it presents.
UIBarButtonItem.appearance(whenContainedInInstancesOf: [UINavigationBar.classForCoder() as! UIAppearanceContainer.Type]).setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.clear], for: .normal)
Update: If you want to go for a different approach.Check this post How to customize the navigation back symbol and navigation back text? and accepted answer.
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.