Expanding UINavigationBar Height - ios

I followed along here to get a bigger navigation bar working. The result is that it looks bigger, however not all elements of the bar actually expand, meaning that I can only interact with items places in the original size of the navigation bar. This is my pain point because I am trying to expand the UINavigationBar to put buttons in the expanded area and these can't be pressed.
Here's my code:
#IBDesignable
class CustomNavigationBar: UINavigationBar {
var customHeight: CGFloat = 88
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let button = UIButton(frame: CGRect(x: 0, y: 44, width: 100, height: 44))
button.setTitle("Button", for: .normal)
button.backgroundColor = .red
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
self.addSubview(button)
}
#objc func buttonAction() {
print("button pressed")
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
return CGSize(width: UIScreen.main.bounds.width, height: customHeight)
}
override func layoutSubviews() {
super.layoutSubviews()
for subview in self.subviews {
var stringFromClass = NSStringFromClass(subview.classForCoder)
if stringFromClass.contains("UIBarBackground") {
subview.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: customHeight)
subview.backgroundColor = .green
subview.sizeToFit()
}
stringFromClass = NSStringFromClass(subview.classForCoder)
if stringFromClass.contains("UINavigationBarContentView") {
subview.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: customHeight)
subview.backgroundColor = .black
subview.sizeToFit()
}
}
}
}
Here's the screenshot from that output:
You can see here that when I resized the UIBarBackground in the layoutSubviews() function, I made it green and set it to the custom height, which worked. However when I resized the UINavigationBarContentView and set its colour to black, the colour gets set fine, but the height stays at 44.
See a screen shot of the debug view hierarchy below and we see that there are actually two items that still have the original height of 44.
All of this means that I cannot press the button in the view at all. However, if I move it up a bit so that it is inside the 44 height, then I can press it.
Looking for some help as to how I can properly resize all aspects of this nav bar
EDIT
If I remove the subview.sizeToFit() from the UInavigationBarContentView it resizes it to the correct size. I also noticed that the overridden sizeThatFits is never called?

i was try to change the height before but also i couldn't ,but
i makes the custom navigation controller and am call it on the viewDidLoad and hide the native navigation controller by doing
self.navigationController?.navigationBar.isHidden = true
and i display the custom in the same place and i put my views on this navigation controller also you can do animations on height this custom nav bar if you want
i hope this help you

Related

How to create custom navigation bar in swift?

I need to do a task that is about when I scroll my view to the top the navigation bar gets smaller and becomes like in the pictures below.
This is the code that I am using
self.navigationController?.navigationBar.setBackgroundImage(UIImage(named: "navBarImg"), for: UIBarPosition.any, barMetrics: .default)
//set navigation height
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let height = CGFloat(200)
navigationController?.navigationBar.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: height)
}
// when scroll
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.navigationController?.navigationBar.setBackgroundImage(UIImage(named: "back"), for: UIBarMetrics.default)
}
But nothing happens. Please can you tell me how to solve this problem? I know that in android there is solution and it's name is CoordinatorLayout, but in iOS I could not find any solution.

Can I align a button's label with its layout margins?

One way to left-align a UIButton's title is to set the contentHorizontalAlignment to .left (or .leading). But this places the title flush with the left edge of the button with no margin. A common way to add some margin is to set the contentEdgeInstets.
But my button extends from once edge of the screen to the other, so I would like the left and right margins to honor the layoutMargins. These margins might change as the view is resized or the device is rotated.
Is there a way to set the button's insets to observe these margins? Or should I create a button from a custom view where I can use my own label and anchor it to the layoutMarginsGuide?
I guess you can manually set the margin of the button using titleEdgeInsets to match the inset of the Cancel Button.
Have a look at the following, the two buttons are exactly the same aside from the origin.y and the titleEdgeInsets:
let view = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
view.backgroundColor = .white
let button = UIButton(frame: CGRect(x: 40, y: 40, width: 200, height: 80))
button.setTitle("Some really long title", for: .normal)
button.backgroundColor = .red
button.setTitleColor(.black, for: .normal)
let button2 = UIButton(frame: CGRect(x: 40, y: 160, width: 200, height: 80))
button2.setTitle("Some really long title", for: .normal)
button2.backgroundColor = .red
button2.setTitleColor(.black, for: .normal)
button2.titleEdgeInsets = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
view.addSubview(button)
view.addSubview(button2)
It gives the following result:
Hope this helps :)
A subclassed button may work for you...
class RespectSuperviewMarginButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
if let sv = superview {
contentEdgeInsets.left = sv.layoutMargins.left
}
}
}
My current best answer is: no, this is not possible without subclassing.
The subclass implementation I am currently using is simple enough:
class LayoutMarginRespectingButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
contentEdgeInsets = layoutMargins
}
}

iOS: How to show a shadow image in a button?

My designer send me a button image with shadow which is complicate, so I need to use the button image instead of implementing the shadow myself. For example, the clickable red area is 20*20 and the whole image with shadow is 60*60. The red area is not in the center of the image.
The question is I want to make a 20*20 button and it can show the whole image.
The implement I'm using is adding an UIImageView (size=60*60) into a button(size=20*20). Is there any other way to do it? I think there's a way to use only image and button without an additional UIImageView
Here's a simple example:
func createView(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) {
// creating the container view
let view = UIView()
view.frame = CGRect(x: x, y: y, width: width, height: height)
view.backgroundColor = .systemRed
// creating the image view, set the image to the one you want
let imageView = UIImageView()
imageView.frame = CGRect(x: 0, y: 0, width: width, height: height)
let image = UIImage(named: "myImage")
imageView.image = image
// creating the button
let button = UIButton()
button.frame = CGRect(x: 0, y: 0, width: width / 3, height: height / 3)
button.backgroundColor = .systemBlue
// adding a tap response functionality to the button
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
// adding the button and image view to the container
view.addSubview(imageView)
view.addSubview(button)
//adding the container to the superview
self.view.addSubview(view)
}
Adding the method to handle a button tap:
#objc func buttonTapped() {
// all the code for handling a tap goes here
print("button tapped")
}
And finally calling createView() in viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
createView(x: 100, y: 100, width: 60, height: 60)
}
It is important to note that the x and y values of the button and image do not refer to that of the superview, but rather to that of the container view.
If you want to add the shadow image, it must be include in the Assets.xcassets folder. From there, you can simply change the name to that of the image and it will be uploaded.

center navigation bar views vertically

I managed to increase the height of my navigation bar, but I faced with the problem, that title and buttons are situated in the bottom of navigator bar.
extension UINavigationBar {
open override func sizeThatFits(_ size: CGSize) -> CGSize {
let v = self.value(forKey: "frame") as? CGRect
return v?.size ?? CGSize(width: UIScreen.main.bounds.width, height: 44)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationBar.frame = CGRect(x: 0, y: 0, width: bounds.width, height: 64)
}
I tried to set offset, using
navigationBar.setTitleVerticalPositionAdjustment(-10, for: .default)
navigationItem.leftBarButtonItem?.setBackgroundVerticalPositionAdjustment(-10, for: .default)
navigationItem.rightBarButtonItem?.setBackgroundVerticalPositionAdjustment(-10, for: .default)
navigationItem.rightBarButtonItem?.setBackButtonBackgroundVerticalPositionAdjustment(-10, for: .default)
navigationItem.leftBarButtonItem?.setBackButtonBackgroundVerticalPositionAdjustment(-10, for: .default)
Actually that setting only apply to my title, and the position of button bars remain the same.
But I don't think my solution is the best.
Its not a good practice to increase the size of the navigation bar. Instead you can add a uiview right under navigation bar with same color and remove the navigation bar border to make to look like height increased. Here is an example how yelp does it.
I posted the solution of this problem in another thread. Hope this helps.
vertically aligning UINavigationItems

iOS. UINavigationBar with custom height -> vertically center titleView & barButtonItems

I have a custom UINavigationBar:
class NavBar: UINavigationBar {
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
private func commonInit() {
self.barTintColor = .orange
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
return CGSize(width: UIScreen.main.bounds.width, height: 66)
}
}
When I set titleView and/or barButtonItems they are not vertically centered:
class ViewController : UIViewController {
override func viewDidLoad() {
self.viewDidLoad()
self.edgesForExtendedLayout = []
let searchField = UITextField(frame: CGRect(x: 0, y: 0, width: 100, height: 44))
searchField.backgroundColor = .green
self.navigationItem.titleView = searchField
}
}
Result:
How can I vertically center textView & barButtonItems inside UINavigationBar when it's height is not default?
I've tried setting textView's bounds, frame - does not work.
With:
let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 44))
view.backgroundColor = .green
let searchField = UITextField(frame: CGRect(x: 0, y: -20, width: 100, height: 44))
searchField.backgroundColor = .red
view.addSubview(searchField)
self.navigationItem.titleView = view
I can change the position of the searchField, but it seems like I am doing it wrong.
Can I somehow set UINavigationBar 'padding'/'margin' or textView & barButtonItem offset in the NavBar class?
You're basically hacking UINavigationBar right now that's why I wouldn't expect stable behaviour from it. Apple is discouraging your from doing that. Even in the Apple example of extended UINavBar they just adding another view and make transition between bar and view seamless. You can also try
setTitleVerticalPositionAdjustment(_:for:)
method but I believe it only affects title and not tab bar items. I would use apple solution to avoid unexpected issues or create custom nav bar without subclassing UINavBar.

Resources