Question

I am using the following codes to add two button to self.navigationItem.rightBarButtonItems, and I think in iOS7, the space between two buttons are too wide, is there a way to decrease the space between these two buttons?

UIBarButtonItem *saveStyleButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"save.png"] style:UIBarButtonItemStyleBordered target:self action:@selector(saveStyle)];

UIBarButtonItem *shareStyleButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(shareStyle)];

NSArray *arr= [[NSArray alloc] initWithObjects:shareStyleButton,saveStyleButton,nil];

self.navigationItem.rightBarButtonItems=arr;

Appreciate any hint or idea.

Was it helpful?

Solution

Updated at Jul 2015

A better way to do this is to use storyboard (tested in Xcode 6.4). First, add a UINavigationItem; secondly, add a Bar Button Item; thirdly, add a view to the Bar Button Item you just created in step 2; fourthly, add as many buttons as you wish into that view you just dragged in; lastly, adjust the space with your mouse and constraints.

Related Questions

Can't assign multiple Buttons to UINavigationItem when using Storyboard with iOS 5

How to add buttons to navigation controller visible after segueing?


Old Answer (Only acceptable for small insets)

Use imageInsets property:

leftButton.imageInsets = UIEdgeInsetsMake(0.0, 0.0, 0, -15);
rightButton.imageInsets = UIEdgeInsetsMake(0.0, -15, 0, 0);

for three or more buttons, the middle one(s) get both insets:

leftButton.imageInsets = UIEdgeInsetsMake(0.0, 0.0, 0, -15);
middleButton.imageInsets = UIEdgeInsetsMake(0.0, -15, 0, -15);
rightButton.imageInsets = UIEdgeInsetsMake(0.0, -15, 0, 0);

For the right side buttons, be careful: the FIRST button in the item array is the RIGHT one:

rightButton.imageInsets = UIEdgeInsetsMake(0.0, -15, 0, 0);
middleButton.imageInsets = UIEdgeInsetsMake(0.0, -15, 0, -15);
leftButton.imageInsets = UIEdgeInsetsMake(0.0, 0.0, 0, -15);

IMPORTANT: Split the inset between the two neighbors; if apply the entire inset to one edge, it will become obvious that the buttons are overlapping in the "blank" space - one button gets all of the "gap" touches. Even when "split" the adjustment like this, at -40 on both edges, the tap will definitely go to wrong button sometimes. -15 or -20 is the most to consider using with this technique.

By applying this method, the button could even be moved around in four directions.

OTHER TIPS

My solution is using a custom view for right bar buttons. Create a horizontal stackview with equal spacing and add any number of buttons as subview.

Sample code:

func addRightBarButtonItems()
{
    let btnSearch = UIButton.init(type: .custom)
    btnSearch.setImage(UIImage(systemName: "magnifyingglass"), for: .normal)
    btnSearch.addTarget(self, action: #selector(MyPageContainerViewController.searchButtonPressed), for: .touchUpInside)
    
    let btnEdit = UIButton.init(type: .custom)
    btnEdit.setImage(UIImage(systemName: "pencil"), for: .normal)
    btnEdit.addTarget(self, action: #selector(MyPageContainerViewController.editButtonPressed), for: .touchUpInside)
    
    let stackview = UIStackView.init(arrangedSubviews: [btnEdit, btnSearch])
    stackview.distribution = .equalSpacing
    stackview.axis = .horizontal
    stackview.alignment = .center
    stackview.spacing = 8
    
    let rightBarButton = UIBarButtonItem(customView: stackview)
    self.navigationItem.rightBarButtonItem = rightBarButton
}

Swift 5

In your AppDelegate add this code:

let stackViewAppearance = UIStackView.appearance(whenContainedInInstancesOf: [UINavigationBar.self])
stackViewAppearance.spacing = -10

This will work with no additional code in more recent SDK versions as UIBarButtonItems are already contained in a horizontal UIStackView

First:

For UIBarButtonItem you must use constructor init(customView: UIView)

Second:

Use fixedSpace for set space between buttons

example:

let firstButton = UIButton()
let firstButtonItem = UIBarButtonItem(customView: firstButton)

let secondButton = UIButton()
let secondButtonItem = UIBarButtonItem(customView: secondButton)

let space = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
space.width = WIDTH

self.navigationItem.rightBarButtonItems = [firstButtonItem, space, secondButtonItem]

One line of code is all you need to decrease the space between buttons in the navigation bar:

UIStackView.appearance(whenContainedInInstancesOf: [UINavigationBar.self]).spacing = -10

You must place this line in your code before you add the buttons to the navigation bar.

If you are looking to have 2 buttons on the top right with no space in between them or on the right, this has worked for me.

let imgLeft = UIImage(named: "buttonLeft")?.imageWithRenderingMode(.AlwaysOriginal)
let bLeft = UIBarButtonItem(image: imgLeft, style: UIBarButtonItemStyle.Done, target: self, action: "action1:")
let space = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FixedSpace, target: nil, action: nil)
space.width = -16.0

bLeft.imageInsets = UIEdgeInsetsMake(0, 0, 0, -25.0)


let imgRight = UIImage(named: "buttonRight")?.imageWithRenderingMode(.AlwaysOriginal)
let bRight = UIBarButtonItem(image: imgRight, style: UIBarButtonItemStyle.Done, target: self, action: "action2:")

bRight.imageInsets = UIEdgeInsetsMake(0, -25, 0, 0)


self.navigationItem.rightBarButtonItems = [space,bLeft,bRight ]

My situation was about giving horizontal space to logOut Button to the right edge.

 func addLogOutButtonToNavigationBar(triggerToMethodName: String)
    {
        let button: UIButton = UIButton()
        button.setImage(UIImage(named: "logOff.png"), forState: .Normal)
        button.frame = CGRectMake(20, 0, 30, 25)
        button.contentEdgeInsets = UIEdgeInsets.init(top: 0, left: 10, bottom: 0, right: -10)

        button .addTarget(self, action:Selector(triggerToMethodName), forControlEvents: UIControlEvents.TouchUpInside)
        let rightItem:UIBarButtonItem = UIBarButtonItem()
        rightItem.customView = button
        self.navigationItem.rightBarButtonItem = rightItem
    }

Might be a bit late for this answer however this can help the newest IOS+Swift combination (IOS 10 and Swift 3 in my case). Here I describe a general approach for how to move items right/left for rightBarButtonItems/leftBarButtonItems:

The property you we have use here to move a barButtonItem is "imageEdgeInsets" . So, Here how to use this property -

yourBarButtonItem.imageEdgeInsets = UIEdgeInsetsMake(top, left, bottom, right)

These top, left, bottom, right are of type CGFloat and these are basically margin value that pushes your item from/to each other. For decreasing a space, we can just use minus (-) values like this " -10 ".

So, for example if we want to use this for a group of leftBatButtonItems and say, if we want to move a item to the a bit right, then we can do this -

ourBarButtonItem.imageEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, 0.0, -15)

I hope we get the general idea here and hope it helps :)

Without any code. I just put another UIBarButtonItem in-between the buttons that need spacing in storyboard. The button is just a placeholder for spacing and the UIBarButton should have UIView as subview of the UIBarButtonItem. adjust the view's width for your spacing. See Screen shots.

enter image description here

enter image description here

enter image description here

Create a UIBarButtonItem with type flexible or fixed space. Set the width and add it to the array of barbuttonitems. Try using a negative width, see if that works.

Or, you could maybe adjust your image. The system buttons i think have a fixed size, and might include some transparent part, so even when packed together the still seem spaced.

Swift 5

If you want to add space between two Bar Button items then add a flexible space in between, the two buttons will be pushed to the left and right edge as the flexible space expands to take up most of the toolbar.

For Example:

let toolBar = UIToolbar()

var items = [UIBarButtonItem]()

let backBarButton =  UIBarButtonItem(image: UIImage(named: "icon-back.png"), style: .done, target: self, action: #selector(backButtonTapped))

let nextBarButton =  UIBarButtonItem(image: UIImage(named: "icon-next.png"), style: .done, target: self, action: #selector(nextButtonTapped))

let spacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)

items.append(backBarButton)
items.append(spacer)
items.append(nextBarButton)

toolBar.setItems(items, animated: true)

To accomplish this in code without adding an extra container view, use a UIBarButtonItem with the system item type set to FixedSpace. Then set the width of the fixed space to -10 and place it between the two buttons.

another answer : It works in ios 9 - 12. You should call fixNavigationItemsMargin(margin:) in function viewDidAppear(_ animated: Bool) and viewDidLayoutSubviews(). fixNavigationItemsMargin(margin:) would modify the UINavigationController stack.

you could call fixNavigationItemsMargin(margin:) in BaseNavigationController ,do the common work. And call fixNavigationItemsMargin(margin:) in UIViewController do precise layout.

// do common initilizer
class BaseNavigationController: UINavigationController {
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        fixNavigationItemsMargin()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        fixNavigationItemsMargin()
    }

}

extension UINavigationController {
func fixNavigationItemsMargin(_ margin: CGFloat = 8) {
    let systemMajorVersion = ProcessInfo.processInfo.operatingSystemVersion.majorVersion
    if systemMajorVersion >= 11 {
        // iOS >= 11
        guard let contentView = navigationBar.subviews
            .first(
                where: { sub in
                    String(describing: sub).contains("ContentView")
            }) else { return }

        // refer to: https://www.matrixprojects.net/p/uibarbuttonitem-ios11/
        // if rightBarButtonItems has not any custom views, then margin would be 8(320|375)/12(414)
        // should use customView
        let needAdjustRightItems: Bool
        if let currentVC = viewControllers.last,
            let rightItems = currentVC.navigationItem.rightBarButtonItems,
            rightItems.count > 0,
            rightItems.filter({ $0.customView != nil }).count > 0 {
            needAdjustRightItems = true
        } else {
            print("Use 8(320|375)/12(414), if need precious margin ,use UIBarButtonItem(customView:)!!!")
            needAdjustRightItems = false
        }

        let needAdjustLeftItems: Bool
        if let currentVC = viewControllers.last,
            let leftItems = currentVC.navigationItem.leftBarButtonItems,
            leftItems.count > 0,
            leftItems.filter({ $0.customView != nil }).count > 0 {
            needAdjustLeftItems = true
        } else {
            print("Use 8(320|375)/12(414), if need precious margin ,use UIBarButtonItem(customView:)!!!")
            needAdjustLeftItems = false
        }

        let layoutMargins: UIEdgeInsets
        if #available(iOS 11.0, *) {
            let directionInsets = contentView.directionalLayoutMargins
            layoutMargins = UIEdgeInsets(
                top: directionInsets.top,
                left: directionInsets.leading,
                bottom: directionInsets.bottom,
                right: directionInsets.trailing)
        } else {
            layoutMargins = contentView.layoutMargins
        }

        contentView.constraints.forEach(
            { cst in

                // iOS 11 the distance between rightest item and NavigationBar should be  margin
                // rightStackView trailing space is -margin / 2
                // rightestItem trailing to rightStackView trailing is -margin / 2
                let rightConstant = -margin / 2

                switch (cst.firstAttribute, cst.secondAttribute) {
                case (.leading, .leading), (.trailing, .trailing):

                    if let stackView = cst.firstItem as? UIStackView,
                        stackView.frame.minX < navigationBar.frame.midX {
                        // is leftItems
                        if needAdjustLeftItems {
                            cst.constant = margin - layoutMargins.left
                        }
                    } else if let layoutGuide = cst.firstItem as? UILayoutGuide,
                        layoutGuide.layoutFrame.minX < navigationBar.frame.midX {
                        // is leftItems
                        if needAdjustLeftItems {
                            cst.constant = margin - layoutMargins.left
                        }
                    }

                    if let stackView = cst.firstItem as? UIStackView,
                        stackView.frame.maxX > navigationBar.frame.midX {
                        // is rightItems
                        if needAdjustRightItems {
                            cst.constant = rightConstant
                        }
                    } else if let layoutGuide = cst.firstItem as? UILayoutGuide,
                        layoutGuide.layoutFrame.maxX > navigationBar.frame.midX {
                        // is rightItems
                        if needAdjustRightItems {
                            cst.constant = rightConstant
                        }
                    }

                default: break
                }

        })

        // ensure items space == 8, minispcae
        contentView.subviews.forEach(
            { subsub in
                guard subsub is UIStackView else { return }
                subsub.constraints.forEach(
                    { cst in
                        guard cst.firstAttribute == .width
                            || cst.secondAttribute == .width
                            else { return }
                        cst.constant = 0
                })
        })

    } else {
        // iOS < 11

        let versionItemsCount: Int
        if systemMajorVersion == 10 {
            // iOS 10 navigationItem.rightBarButtonItems == 0
            // space = 16(320|375) / 20(414)
            // should adjust margin
            versionItemsCount = 0
        } else {
            // iOS 9 navigationItem.rightBarButtonItems == 0
            // space = 8(320|375) / 12(414)
            // should not adjust margin
            versionItemsCount = 1
        }

        let spaceProducer = { () -> UIBarButtonItem in
            let spaceItem = UIBarButtonItem(
                barButtonSystemItem: .fixedSpace,
                target: nil,
                action: nil)
            spaceItem.width = margin - 16
            return spaceItem
        }
        if let currentVC = viewControllers.last,
            var rightItems = currentVC.navigationItem.rightBarButtonItems,
            rightItems.count > versionItemsCount,
            let first = rightItems.first {

            // ensure the first BarButtonItem is NOT fixedSpace
            if first.title == nil && first.image == nil && first.customView == nil {
                print("rightBarButtonItems SPACE SETTED!!!  SPACE: ", abs(first.width))

            } else {
                rightItems.insert(spaceProducer(), at: 0)

                // arranged right -> left
                currentVC.navigationItem.rightBarButtonItems = rightItems
            }
        }

        if let currentVC = viewControllers.last,
            var leftItems = currentVC.navigationItem.leftBarButtonItems,
            leftItems.count > versionItemsCount,
            let first = leftItems.first {
            if first.title == nil && first.image == nil && first.customView == nil {
                print("leftBarButtonItems  SPACE SETTED!!!  SPACE: ", abs(first.width))
            } else {
                leftItems.insert(spaceProducer(), at: 0)

                // arranged left -> right
                currentVC.navigationItem.leftBarButtonItems = leftItems
            }
        }
    }
}
}

// do precise layout
class ViewController: UIViewController {
  override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    navigationController?.fixNavigationItemsMargin(40)
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    navigationController?.fixNavigationItemsMargin(40)
}

Found a crazy idea that works.

func createCustomToolbar(items: [UIBarButtonItem]) -> UIToolbar
{
    // no spacing between bar buttons
    let customToolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: items.count*45, height: 44))
    customToolbar.items = items
    customToolbar.barStyle = UIBarStyle(rawValue: -1)!
    customToolbar.clearsContextBeforeDrawing = false
    customToolbar.backgroundColor = UIColor.clearColor()
    customToolbar.tintColor = UIColor.clearColor()
    customToolbar.translucent = true
    return customToolbar
}


let customToolbar = createCustomToolbar([item0,item1,item2,item3])
navigationItem.rightBarButtonItems = [UIBarButtonItem(customView: customToolbar)]

Tested on iOS7 and upper. Even this is written in swift the concept is clear.

I gave up with fighting this bug, and came up with the following extension:

import UIKit

extension UIBarButtonItem {

    convenience init(buttonImage: UIImage?, target: Any?, action: Selector?) {
        let button = UIButton(type: .system)
        button.frame = CGRect(origin: CGPoint.zero, size: buttonImage != nil ? buttonImage!.size : CGSize.zero)
        button.setImage(buttonImage, for: .normal)

        if let action = action {
            button.addTarget(target, action: action, for: .touchUpInside)
        }

        self.init(customView: button)
    }

    public func updateButton(image: UIImage?) {
        if let button = self.customView as? UIButton {
            button.setImage(image, for: .normal)

            let size = image != nil ? image!.size : CGSize.zero
            let frame = button.frame
            button.frame = frame.insetBy(dx: ceil((frame.size.width - size.width) / 2), dy: ceil((frame.size.height - size.height) / 2))
        }
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top