LoginSignup
38
39

More than 3 years have passed since last update.

iOS11からNavigationBarButton周りの仕様が変わったせいで、UIBarButtonItemの位置調整が上手くいかなくなった

Last updated at Posted at 2018-03-19

追記

iOS13対応版あげました

iOS13でもNavigationBarButtonの位置を調整したい - Qiita
https://qiita.com/rd0501/items/8e8254a23d87a9b2fea2

参考

iOS 11 - UIBarButtonItem horizontal position | Apple Developer Forums
https://forums.developer.apple.com/thread/80075
ios11 - iOS 11 UINavigationBar bar button items alignment - Stack Overflow
https://stackoverflow.com/questions/44677018/ios-11-uinavigationbar-bar-button-items-alignment
UIBarButtonItem & iOS 11
http://www.matrixprojects.net/p/uibarbuttonitem-ios11/

 環境

Xcode9.2
iOS10 or iOS11

何がいいたいかというと

iOS11からNavigationBarButton周りの仕様が変わったので、NavigationBarButtonの位置を調整していた場合は、OS毎にやり方を変えないとうまくいかないですよって話。
特に、Defaultの位置よりも外側にButtonを置きたい場合。

まずは、OS間での違い

iOS11以降とiOS10以前ではNavigationBarButtonがなんか違う!
View Hierarchyを見てみると、見たことないStackViewが入ってました。

わかりにくいですが...

iOS11 iOS10
スクリーンショット 2018-03-19 15.12.28.png スクリーンショット 2018-03-19 15.06.17.png

濃い赤字になっている部分だけ注目してもらえたらと思います。
ひとまずなんかStackViewが増えてんなって感じです。

なにがつらいのか

DefaultではNavigationBarButtonはどのOSでも外側から16pt離れた位置に置かれます。
そして、それを直接操作することはできない。
iOS10以前だとBarの上に直で置けたので、UIButtonのimageInsetをいじったり、func point(inside point: CGPoint, with event: UIEvent?) -> Boolを使ったりしてなんとかなっていた。
しかし、StackViewが入ってきたことでUIButtonのタップ領域を広げても、StackViewのタップ領域が狭いのでタップできない

→ どうにかしてStackViewのTap領域を広げないといけない!!

流れとか書いてきたけど、限界なのでさっさと結果を書きます。

実装

以下適宜数値を変えて調整してください

BaseNavigationController.swift
/// UINavigationControllerのサブクラス。
/// 基本的にこのクラスのinit(rootViewController:)のみ使用。
/// swift:BaseNavigationBarをBarに設定するためです。
final class BaseNavigationController: UINavigationController {

    // このInit以外は使用しないでください。(Storyboard & Xib からの呼び出しもやめて)
    // このInitを使用(BaseNavigationBarを使用)しないとNavigationBarButtonの表示位置がおかしくなります
    override init(rootViewController: UIViewController) {
        if #available(iOS 11, *) {
            super.init(navigationBarClass: BaseNavigationBar.self, toolbarClass: UIToolbar.self)
            self.viewControllers = [rootViewController]
        } else {
            super.init(rootViewController: rootViewController)
        }
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }   
}
BaseNavigationBar.swift
/// 全ての子StackViewのMarginをZeroにするUINavigationBar
final class BaseNavigationBar: UINavigationBar {

    override func layoutSubviews() {
        super.layoutSubviews()

        if #available(iOS 11, *) {
            loop: for view in subviews {
                for stack in view.subviews where stack is UIStackView {
                    stack.superview?.layoutMargins = .zero
                    break loop
                }
            }
        }

    }
}

ExpansionButton.swift
/// タップ領域を簡単に拡大、縮小できるUIButton
final class ExpansionButton: UIButton {

    @IBInspectable var top: CGFloat {
        get { return insets.top }
        set { insets.top = newValue }
    }
    @IBInspectable var left: CGFloat {
        get { return insets.left }
        set { insets.left = newValue }
    }
    @IBInspectable var bottom: CGFloat {
        get { return insets.bottom }
        set { insets.bottom = newValue }
    }
    @IBInspectable var right: CGFloat {
        get { return insets.right }
        set { insets.right = newValue }
    }

    var insets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        var rect = bounds
        rect.origin.x -= insets.left
        rect.origin.y -= insets.top
        rect.size.width += insets.left + insets.right
        rect.size.height += insets.top + insets.bottom

        return rect.contains(point)
    }
}

引用
【iOS】UIButtonのタップ領域だけを拡大する - Qiita
https://qiita.com/KokiEnomoto/items/264f26bfa92d06b1996e

ExpansionView.swift
/// タップ領域を簡単に拡大、縮小できるUIView
class ExpansionView: UIView {

    @IBInspectable var top: CGFloat {
        get { return insets.top }
        set { insets.top = newValue }
    }
    @IBInspectable var left: CGFloat {
        get { return insets.left }
        set { insets.left = newValue }
    }
    @IBInspectable var bottom: CGFloat {
        get { return insets.bottom }
        set { insets.bottom = newValue }
    }
    @IBInspectable var right: CGFloat {
        get { return insets.right }
        set { insets.right = newValue }
    }

    var insets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        var rect = bounds
        rect.origin.x -= insets.left
        rect.origin.y -= insets.top
        rect.size.width += insets.left + insets.right
        rect.size.height += insets.top + insets.bottom

        return rect.contains(point)
    }
}
UIBarButtonItem+.swift
enum UIBarButtonItemPotition {
    case right
    case left
}

extension UIBarButtonItem {

    /// UIBarButtonItemを使用する場合は基本的にこれを使ってね!
    /// 画面の右端、左端からそれぞれ6pt離れた位置にアイコンが表示されるよ!
    /// アイコンサイズは28pt固定になっているよ!適宜直してね!
    ///
    /// - Parameters:
    ///   - image: Bar Button Icon Image
    ///   - position: NavigationBarの右か左か
    ///   - target: Tapした時に呼ばれるTarget
    ///   - action: Tapした時に呼ばれるAction
    /// - Returns: UIBarButtonItem
    static func createBarButton(image: UIImage, position: UIBarButtonItemPotition, target: Any?, action: Selector) -> UIBarButtonItem {
        let button = ExpansionButton()
        if #available(iOS 11, *) {
            button.frame = CGRect(x: 0, y: 0, width: 40, height: 28)
            button.insets = UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0)
            button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 6, bottom: 0, right: 6)
        } else {
            button.frame = CGRect(x: 0, y: 0, width: 28, height: 28)
            // UINavigationBarのButtonは余白が強制的に16入るので、そこからデザインに合わせて位置をずらしています
            switch position {
            case .left:
                // なんかiOS10でタップ領域鬼広いんだけどなんでだろ?
                // 左にずらす
                button.insets = UIEdgeInsets(top: 10, left: 0, bottom: 10, right: -4)
                button.imageEdgeInsets = UIEdgeInsets(top: 0, left: -10, bottom: 0, right: 10)
            case .right:
                // 右にずらす
                button.insets = UIEdgeInsets(top: 10, left: -4, bottom: 10, right: 0)
                button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: -10)
            }
        }
        button.setImage(image, for: .normal)
        button.addTarget(target, action: action, for: .touchUpInside)
        return UIBarButtonItem(customView: button)
    }
}

上記を全部コピペして、以下のように使用すればOKです。

HogeViewController.swift
override func viewDidLoad() {
    super.viewDidLoad()

    self.navigationItem.leftBarButtonItem = UIBarButtonItem.createBarButton(image: UIImage(named: "back")!, position: .left, target: self, action: #selector(back(_:)))
}

func back(_ button: UIButton) {
    // hogehoge
}

38
39
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
39