追記
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 |
---|---|
![]() |
![]() |
濃い赤字になっている部分だけ注目してもらえたらと思います。
ひとまずなんかStackViewが増えてんなって感じです。
なにがつらいのか
DefaultではNavigationBarButtonはどのOSでも外側から16pt離れた位置に置かれます。
そして、それを直接操作することはできない。
iOS10以前だとBarの上に直で置けたので、UIButtonのimageInsetをいじったり、func point(inside point: CGPoint, with event: UIEvent?) -> Bool
を使ったりしてなんとかなっていた。
しかし、StackViewが入ってきたことでUIButtonのタップ領域を広げても、StackViewのタップ領域が狭いのでタップできない
→ どうにかしてStackViewのTap領域を広げないといけない!!
流れとか書いてきたけど、限界なのでさっさと結果を書きます。
実装
以下適宜数値を変えて調整してください
/// 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)
}
}
/// 全ての子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
}
}
}
}
}
/// タップ領域を簡単に拡大、縮小できる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
/// タップ領域を簡単に拡大、縮小できる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)
}
}
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です。
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
}