101

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

lorenzo gonzalez
  • 1,894
  • 4
  • 14
  • 18

12 Answers12

188

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.

Jakub Truhlář
  • 20,070
  • 9
  • 74
  • 84
Vlad Khambir
  • 4,313
  • 1
  • 17
  • 25
  • 3
    @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 – david Oct 03 '17 at 12:22
  • @spikesa, it should work on the both iOS versions, probably you have set incorrect constraints. – Vlad Khambir Oct 03 '17 at 13:58
  • @V.Khambir Value of type 'UIBarButtonItem' has no member 'widthAnchor' in Xcode 9 inside an if #available(iOS 11.0, *) conditional? – Ryan Brodie Oct 06 '17 at 09:28
  • @V.Khambir this doesn't seem to work on anything below iOS 11. How do you fix it for iOS 9 & 10? – jped Oct 13 '17 at 16:27
  • 1
    @jped did you find a solution for this? It doesn't work for me as well on iOS 10 with Xcode 9 – swalkner Oct 16 '17 at 14:37
  • @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 Oct 16 '17 at 14:48
  • @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. – swalkner Oct 16 '17 at 14:51
  • @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 – Samarth Kejriwal Nov 03 '17 at 11:42
  • 1
    Great. Gotta love these breaking changes. Has Apple documented a list of these types of changes on a page or release notes somewhere? – GONeale Nov 06 '17 at 01:21
53

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
lorenzo gonzalez
  • 1,894
  • 4
  • 14
  • 18
  • 1
    This worked for me as well, but can anyone explain why this is necessary for iOS 11 when it never was before? – stonedauwg Aug 09 '17 at 20:50
  • 9
    UINavigationBar does now layout its subviews using Auto Layout – mangerlahn Aug 21 '17 at 08:31
  • 2
    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) – xaphod Sep 19 '17 at 20:40
  • 2
    `Value of type 'UIBarButtonItem' has no member 'widthAnchor'` in Xcode 9 inside an `if #available(iOS 11.0, *)` conditional? – Ryan Brodie Oct 06 '17 at 09:27
  • @lorenzo-gonzalez How did you make it round? I'm stuck on this. – javal88 Jan 30 '18 at 14:33
  • @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! – lorenzo gonzalez Jan 30 '18 at 15:20
  • Yes i didn't understood that i'd to add the constraint lines instead of replacing the frame section. Thanks for the advice. – javal88 Jan 31 '18 at 08:13
  • @Alessandro Lucarini did you get it working ? Send me your code to info@imcool.do , i can take a look... – lorenzo gonzalez Feb 01 '18 at 06:33
18

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.

EdwardM
  • 1,116
  • 11
  • 20
Ahmad Farrag
  • 234
  • 2
  • 8
18

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];
SHS
  • 1,414
  • 4
  • 26
  • 43
  • 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. – Karoly Nyisztor Sep 21 '17 at 06:50
  • you are my hero, because i need to refactor old project with objective-c. – Marosdee Uma Mar 29 '18 at 17:11
14

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))
Mandeep Singh
  • 2,810
  • 1
  • 19
  • 31
Karoly Nyisztor
  • 3,505
  • 1
  • 26
  • 21
12

I done this in objective using following lines:

NSLayoutConstraint * widthConstraint = [customButton.widthAnchor constraintEqualToConstant:40];
NSLayoutConstraint * HeightConstraint =[customButton.heightAnchor constraintEqualToConstant:40];
[widthConstraint setActive:YES];
[HeightConstraint setActive:YES];

UIBarButtonItem* customBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:customButton];
self.navigationItem.leftBarButtonItem = customBarButtonItem;

Thanks Happy coding!!

Aleem
  • 3,173
  • 5
  • 33
  • 71
7

What I Did?

In my app, I added profile image on navigationBar at rightBarButton item. before iOS 11 it was working good and display properly but when updated to iOS 11 then change behaviour like blow

enter image description here

So I added UIView in right button item and set UIButton as subview of UIView? Like below,

enter image description here

And I set height and width constraints of UIButton.

enter image description here enter image description here

And my problem is solved. Don't forget to set UIView's background color as clear color.

NOTE: If your button will not work then check your UIView's height might be its 0 here you should change height 0 to 44 or whatever you want. And also do clipToBound = true, Now you can set your button's position and It will be work well.

iPatel
  • 46,010
  • 16
  • 115
  • 137
5

Changing the widthAnchor/heightAnchor will only work on iOS 11+ devices. For iOS 10 devices you need to go the classical way of manually changing the frames. The thing is that none of the two approaches work for both versions, so you absolutely need to programmatically alternate depending on the runtime version, like below:

if #available(iOS 11.0, *)
{
   button.widthAnchor.constraint(equalToConstant: 32.0).isActive = true
   button.heightAnchor.constraint(equalToConstant: 32.0).isActive = true
}else
{
   var frame = button.frame
   frame.size.width = 32.0
   frame.size.height = 32.0
   button.frame = frame
}
Malloc
  • 15,434
  • 34
  • 105
  • 192
3

Even though iOS 11 uses Autolayout for navigation bar it's possible to make it working traditionally setting frames. Here is my code working for ios11 and ios10 or older:

func barItemWithView(view: UIView, rect: CGRect) -> UIBarButtonItem {
    let container = UIView(frame: rect)
    container.addSubview(view)
    view.frame = rect
    return UIBarButtonItem(customView: container)
}

and here is how bar item is composed:

    let btn = UIButton()
    btn.setImage(image.withRenderingMode(.alwaysTemplate), for: .normal)
    btn.tintColor = tint
    btn.imageView?.contentMode = .scaleAspectFit
    let barItem = barItemWithView(view: btn, rect: CGRect(x: 0, y: 0, width: 22, height: 22))
    return barItem
Leszek Zarna
  • 3,253
  • 26
  • 26
3

Putting constraints programmatically worked for me for users running iOS 11.X. However, the bar button was still stretched for users running iOS 10.X. I guess the AppStore reviewers was running iOS 11.X thus couldn't identify my problem so my app got ready for sale and uploaded..

My solution was to simply change my image's dimensions to 30x30 in another software (previous image dimension was 120x120).

Erik Nguyen
  • 346
  • 2
  • 6
0

I've also had success by implementing intrinsicContentSize to return an appropriate size for any custom UIView subclass that I intend to use as a customView.

Chris
  • 39,719
  • 45
  • 189
  • 235
0

I have created a bar button item and then added it on to the navigationbar.

    private var addItem: UIBarButtonItem = {
        let addImage = UIImage(named: "add")
        let addButton = UIButton(type: UIButton.ButtonType.custom)
        addButton.setBackgroundImage(addImage, for: UIControl.State())
        addButton.frame = CGRect(x: 0, y: 0, width: (addImage?.size.width)!, height: (addImage?.size.height)!)
        let addItem = UIBarButtonItem(customView: addButton)
        return addItem
    }()

 private var contactsItem: UIBarButtonItem = {
        let contactsImage = UIImage(named: "contacts")
        let contactsButton = UIButton(type: UIButton.ButtonType.custom)
        contactsButton.setBackgroundImage(contactsImage, for: UIControl.State())
        contactsButton.frame = CGRect(x: 0, y: 0, width: (contactsImage?.size.width)!, height: (contactsImage?.size.height)!)
        let contactsItem = UIBarButtonItem(customView: contactsButton)
        return contactsItem
    }()

In viewDidLoad()

let spacerBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.fixedSpace, target: nil, action: nil)
        spacerBarButtonItem.width = 11
        navigationItem.rightBarButtonItems = [addItem, spacerBarButtonItem, contactsItem]

Here I am having the image of 28x28.

user2807083
  • 2,962
  • 4
  • 29
  • 37
vinny
  • 360
  • 3
  • 10