navigation bar rightbaritem image-button bug iOS 11

39,930

Solution 1

Reason

The problem appears because from ios 11 UIBarButtonItem uses autolayout instead of dealing with frames.

Solution

You should add width constraint for this image-button if you use Xcode 9.

 button.widthAnchor.constraint(equalToConstant: 32.0).isActive = true
 button.heightAnchor.constraint(equalToConstant: 32.0).isActive = true

PS

button is not UIBarButtonItem, it is UIButton inside UIBarButtonItem. You should set constraint not for UIBarButtonItem, but for elements inside it.

Solution 2

Thanks all for contributing! you guys are right!. for xcode9 ios11 you need to put a constraint.

 let widthConstraint = button.widthAnchor.constraint(equalToConstant: 32)
 let heightConstraint = button.heightAnchor.constraint(equalToConstant: 32)
 heightConstraint.isActive = true
 widthConstraint.isActive = true

Solution 3

Well, The new barButtonItem uses autolayout instead of dealing with frames.

The image you were adding to the button is larger than the button size itself. That's why the button itself got stretched to the image's size. You have to resize the image to match the needed button's size, before adding it to the button.

Solution 4

Objective C code is obsolete now. But for the user who has to build/maintain Objective C projects in iOS 11 has following translation from Swift ( Karoly Nyisztor answer ) to Objective C helpful.

//  UIView+Navbar.h

#import <UIKit/UIKit.h>

@interface UIView (Navbar)

- (void)applyNavBarConstraints:(CGFloat)width height:(CGFloat)height;

@end

//----------

//  UIView+Navbar.m

#import "UIView+Navbar.h"

@implementation UIView (Navbar)

- (void)applyNavBarConstraints:(CGFloat)width height:(CGFloat)height
{
    if (width == 0 || height == 0) {
        return;
    }

    NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:height];
    NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:width];
    [heightConstraint setActive:TRUE];
    [widthConstraint setActive:TRUE];
}

//----------

// Usage :-
[button applyNavBarConstraints:33 height:33];

Solution 5

I wrote a tiny extension for setting the constraints on navbar items:

import UIKit

extension UIView {
    func applyNavBarConstraints(size: (width: CGFloat, height: CGFloat)) {
    let widthConstraint = self.widthAnchor.constraint(equalToConstant: size.width)
    let heightConstraint = self.heightAnchor.constraint(equalToConstant: size.height)
    heightConstraint.isActive = true
    widthConstraint.isActive = true
  }
}

// Usage
button.applyNavBarConstraints(size: (width: 33, height: 33))
Share:
39,930

Related videos on Youtube

lorenzo gonzalez
Author by

lorenzo gonzalez

Updated on September 18, 2020

Comments

  • lorenzo gonzalez
    lorenzo gonzalez almost 4 years

    This code works ok in ios10. i get my label and an image button which is the user photo profile, circular round.. ok. but when running xcode 9 ios11 simulator i get it streched out. the button frame has to be 32x32 , when checking on the sim and getting the view and telling xcode to describe the view i get output as 170x32 or somethint like that.

    heres my code.

    let labelbutton = UIButton( type: .system)
        labelbutton.addTarget(self, action:#selector(self.toLogin(_:)), for: .touchUpInside)
        labelbutton.setTitleColor(UIColor.white, for: .normal)
        labelbutton.contentHorizontalAlignment = .right
        labelbutton.titleLabel?.font = UIFont.systemFont(ofSize: 18.00)
    
    
    
        let button = UIButton(type: .custom)
         button.addTarget(self, action:#selector(self.toLogin(_:)), for: .touchUpInside)
         button.frame = CGRect(x: 0, y: 0, width: 32, height: 32)
         button.setTitleColor(UIColor.white, for: .normal)
         button.setTitleColor(UIColor.white, for: .highlighted)
    
    
        var buttomItem : UIBarButtonItem = UIBarButtonItem()
        buttomItem.customView = button
        buttomItem.target = self
        buttomItem.action = "ToLogin"
    
        var labelItem : UIBarButtonItem = UIBarButtonItem()
        labelItem.customView = labelbutton
        labelItem.target = self
        labelItem.action = "ToLogin"
    
    
        if let user = PFUser.current() {
            print("LOGIN : checkiando si existe usuario ")
                labelbutton.setTitle(USERNAME, for: UIControlState.normal)
                labelbutton.sizeToFit()
    
            if(user["profile_photo_url"] != nil) {
                print(" ENCONTRO PROFILE PHOTO URL NOT NIL Y ES \(user["profile_photo_url"])")
                let photoURL = user["profile_photo_url"] as! String
                let a = LoginService.sharedInstance
                a.downloadImage(url: photoURL, complete: { (complete) in
    
                    if (complete) {
    
                        button.setImage(LoginService.sharedInstance.profile_photo! , for: UIControlState.normal)
    
                        button.layer.cornerRadius = 0.5 * button.bounds.size.width
                       // button.imageView!.contentMode = .scaleAspectFit
                       // button.imageView!.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
                        //button.imageView!.contentMode = .scaleAspectFit
                        //button.imageView!.clipsToBounds = true
                        //button.imageView!.layer.cornerRadius = 60
                        button.clipsToBounds = true
                        self.NavigationItem.rightBarButtonItems = [buttomItem,labelItem]
                    }
    
    
                })
            } else {
                    self.NavigationItem.rightBarButtonItem = labelItem
    
            }
                print(" EL FRAME DEL BUTTON ES \(button.frame)")
    
        } else {
    
            labelbutton.setTitle("Login", for: UIControlState.normal)
            labelbutton.sizeToFit()
            self.NavigationItem.rightBarButtonItem = labelItem
    
        }
    

    enter image description here

    • Vlad Khambir
      Vlad Khambir about 7 years
      Did you use stack view in the navigation bar?
    • lorenzo gonzalez
      lorenzo gonzalez about 7 years
      @V.Khambir Nop... :/
    • Edu
      Edu almost 7 years
      is this bug report anywhere?
    • onmyway133
      onmyway133 over 6 years
      iOS 11 uses AutoLayout to layout navigation items. In case you need to shift the UIButton inside UIBarButtonItem, use button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: -20)
  • stonedauwg
    stonedauwg almost 7 years
    This worked for me as well, but can anyone explain why this is necessary for iOS 11 when it never was before?
  • mangerlahn
    mangerlahn almost 7 years
    UINavigationBar does now layout its subviews using Auto Layout
  • xaphod
    xaphod almost 7 years
    Don't make the same mistake I did: I turned off translatesAutoresizingMaskIntoConstraints and then later found out that totally broke iOS 9 (but looked fine on iOS 10 and 11)
  • Karoly Nyisztor
    Karoly Nyisztor almost 7 years
    Cool. Actually, I needed the Objective-C version, too. Btw, Xcode 9 keeps randomly resizing the navbar items in the storyboard. I have to double-check each time before pushing my changes. Hope this gets eventually fixed soon.
  • david
    david over 6 years
    @V.Khambir now i am getting this issue. But with this same solution with xcode 9, and if i run in ios 1 device its fine..But if i run in ios 10 version device..my bar button images r not at all showing. How can i fix for all version for this . I checked in device of both ios 11, ios 10. Only showing good in ios 1 version. in ios 10, image not at all showing
  • Vlad Khambir
    Vlad Khambir over 6 years
    @spikesa, it should work on the both iOS versions, probably you have set incorrect constraints.
  • Ryan Brodie
    Ryan Brodie over 6 years
    Value of type 'UIBarButtonItem' has no member 'widthAnchor' in Xcode 9 inside an if #available(iOS 11.0, *) conditional?
  • Ryan Brodie
    Ryan Brodie over 6 years
    @V.Khambir Value of type 'UIBarButtonItem' has no member 'widthAnchor' in Xcode 9 inside an if #available(iOS 11.0, *) conditional?
  • jped
    jped over 6 years
    @V.Khambir this doesn't seem to work on anything below iOS 11. How do you fix it for iOS 9 & 10?
  • swalkner
    swalkner over 6 years
    @jped did you find a solution for this? It doesn't work for me as well on iOS 10 with Xcode 9
  • swalkner
    swalkner over 6 years
    @jped Update: I now see that the botton is there but in the very top of my app; the toolbar is on the bottom, so there's something messed up with the y-coordinate...
  • swalkner
    swalkner over 6 years
    @jped Update2: in iOS 10 I have to set the centerYAnchor to the one of the toolbar to make it work, in iOS 11 it crashes with that - weird.
  • Paulo Sigales
    Paulo Sigales over 6 years
    Because navigationbar in ios10 is autoresizing and ios11 is autolayout, you need handle with if #available(iOS 11, *){ }
  • Samarth Kejriwal
    Samarth Kejriwal over 6 years
    @V.Khambir I have used the following code to set the navigation bar left and right bar button item, ` navigationItem.rightBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: self, action: #selector(submit)) navigationItem.rightBarButtonItem?.image = UIImage(named : "tick") navigationItem.leftBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: self, action: #selector(dismissview)) navigationItem.leftBarButtonItem?.image = UIImage(named : "cancel")` . How to make the above changes on this code
  • GONeale
    GONeale over 6 years
    Great. Gotta love these breaking changes. Has Apple documented a list of these types of changes on a page or release notes somewhere?
  • Sentry.co
    Sentry.co over 6 years
    This works, however the button stops working when you add it in a view. Any tips?
  • iPatel
    iPatel over 6 years
    @GitSync Check your view's height it will be 0 change it to 44 or whatever you want and make clipToBound = true and then set button.
  • Sentry.co
    Sentry.co over 6 years
    Wow. works! Pro tip: use .pdf assets instead of .png
  • Shobhakar Tiwari
    Shobhakar Tiwari over 6 years
    I was going the through the new changes and i found this question , but this answer i guess developer should use
  • Brittany
    Brittany over 6 years
    Great, simple answer. Thank you. Nothing else was working and it was driving me crazy.
  • Clifton Labrum
    Clifton Labrum over 6 years
    Thanks for the iOS 10 tip! I had the iOS 11 fix working but couldn't figure it out for iOS 10.
  • igrek
    igrek over 6 years
    is the container view necessary? Can't i just use UIButton directly?
  • Alessandro Ornano
    Alessandro Ornano over 6 years
    Great idea! Applied this one the UIButton works very well.
  • javal88
    javal88 over 6 years
    @lorenzo-gonzalez How did you make it round? I'm stuck on this.
  • lorenzo gonzalez
    lorenzo gonzalez over 6 years
    @AlessandroLucarini the make it round part is easy : button.layer.cornerRadius = 0.5 * button.bounds.size.width , button.clipsToBounds = true , of course must have height and width contraints in a 1:1 ratio .. 32x32, 50x50..etc.. .. let me know how it goes!
  • javal88
    javal88 over 6 years
    Yes i didn't understood that i'd to add the constraint lines instead of replacing the frame section. Thanks for the advice.
  • lorenzo gonzalez
    lorenzo gonzalez over 6 years
    @Alessandro Lucarini did you get it working ? Send me your code to [email protected] , i can take a look...
  • Marosdee Uma
    Marosdee Uma about 6 years
    you are my hero, because i need to refactor old project with objective-c.