web-dev-qa-db-ja.com

iOS 11 UINavigationBarバーのボタン項目の配置

アプリケーションをiOS 11にアップグレードしていますが、ナビゲーションバーにいくつかの問題があります。問題の一部は既にここで質問されているため、この質問では言及しません。

問題の特定の問題は、ナビゲーションバーのバーボタン項目の間隔が異なることです。メインの左右のバーボタンアイテムは、画面の水平方向の中央により近くなり、画面の端近くに移動できなくなりました。過去に、カスタムUIButtonサブクラスを使用し、カスタムビューでバーボタンアイテムを作成しました。アライメントソリューションはalignmentRectInsetscontentEdgeInsetsでしたが、このアプローチを使用して期待どおりの結果を得ることができませんでした。

編集:
iOS 11 beta 2で再テストしましたが、問題は残ります。

編集2: iOSベータ3で再テストしましたが、問題は残ります。

16

これに関する良い記事があります: http://www.matrixprojects.net/p/uibarbuttonitem-ios11/

これを使用して、UINavigationBarの末尾から8ピクセルの余白が残るまで、少なくともrightbarbuttonitemsを右にプッシュできます。

本当によく説明した。

4
Mayur Kothawade

IOS 11では、UINavigationBarContentViewを管理して左右の制約を調整し、UIStackViewを管理してボタン(またはその他の要素)を調整できます。

これは、左右にアイテムがあるナビゲーションバーのソリューションです。また、片側に複数のボタンがある場合は修正されます。

- (void) updateNavigationBar {
    for(UIView *view in self.navigationBar.subviews) {
        if ([NSStringFromClass([view class]) containsString:@"ContentView"]) {

            // Adjust left and right constraints of the content view 
            for(NSLayoutConstraint *ctr in view.constraints) {
                if(ctr.firstAttribute == NSLayoutAttributeLeading || ctr.secondAttribute == NSLayoutAttributeLeading) {
                    ctr.constant = 0.f;
                } else if(ctr.firstAttribute == NSLayoutAttributeTrailing || ctr.secondAttribute == NSLayoutAttributeTrailing) {
                    ctr.constant = 0.f;
                }
            }

            // Adjust constraints between items in stack view
            for(UIView *subview in view.subviews) {
                if([subview isKindOfClass:[UIStackView class]]) {
                    for(NSLayoutConstraint *ctr in subview.constraints) {
                        if(ctr.firstAttribute == NSLayoutAttributeWidth || ctr.secondAttribute == NSLayoutAttributeWidth) {
                            ctr.constant = 0.f;
                        }
                    }
                }
            }
        }
    }
}


ご覧のとおり、他の人が行ったように、制約を追加する必要はありません。定義済みの制約があるため、変更できます。

8
JonyMateos

約2日後、ここに私が思いつく最も簡単で安全な解決策があります。このソリューションは、コードが含まれているカスタムビューバーボタン項目でのみ機能します。ナビゲーションバーの左右の余白はiOS10からiOS11に変更されていないことに注意してください。これらはまだ16ピクセルです。このように大きなマージンがあると、クリック領域を十分に大きくすることが難しくなります。

UIStackViewのバーボタンがレイアウトされるようになりました。これらのボタンをマージンに近づける従来の方法では、これらのスタックビューでは処理できない負の固定スペースを使用していました。

サブクラスUINavigationBar

FWNavigationBar.h

@interface FWNavigationBar : UINavigationBar

@end

FWNavigationBar.m

#import "FWNavigationBar.h"

@implementation FWNavigationBar

- (void)layoutSubviews {
    [super layoutSubviews];

    if (@available(iOS 11, *)) {
        self.layoutMargins = UIEdgeInsetsZero;

        for (UIView *subview in self.subviews) {
            if ([NSStringFromClass([subview class]) containsString:@"ContentView"]) {
                subview.layoutMargins = UIEdgeInsetsZero;
            }
        }
    }
}

@end

UINavigationControllerを使用する

#import "FWNavigationBar.h"

UINavigationController *controller = [UINavigationController initWithNavigationBarClass:[FWNavigationBar class] toolbarClass:nil];
[controller setViewControllers:@[rootViewController] animated:NO];

UIBarButtonの作成

このコードをUIBarButtonカテゴリ、またはUIButtonを使用して同一の外観のバーボタン項目を返すバーボタンを使用する予定のファイルに配置します。

+ (UIBarButtonItem *)barButtonWithImage:(UIImage *)image target:(id)target action:(SEL)action {
   UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
   //Note: Only iOS 11 and up supports constraints on custom bar button views
   button.frame = CGRectMake(0, 0, image.size.width, image.size.height);

   button.tintColor = [UIColor lightGrayColor];//Adjust the highlight color

   [button setImage:image forState:UIControlStateNormal];
   //Tint color only applies to this image
   [button setImage:[image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateHighlighted];
   [button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];

   return [[UIBarButtonItem alloc] initWithCustomView:button];
}

コントローラーのバーボタンを設定する

- (void)viewDidLoad {
    [super viewDidLoad];

    self.navigationItem.leftBarButtonItem = [UIBarButtonItem barButtonWithImage:[UIImage imageNamed:@"your_button_image"] target:self action:@selector(leftButtonPressed)];
}

最後に、左右のマージンをゼロのままにして、画像内のボタンの位置を調整することをお勧めします。これにより、画面の端までクリック可能な領域全体を活用できます。画像の高さについても同じことが言えます-高さが44ポイントであることを確認してください。

7
Josh Bernfeld

同様の問題に気づきました。

私たちが気づいた同様の問題について、AppleRadarを報告しました。参照したい場合は#32674764レーダーを作成します。

Appleのforumスレッドも作成しましたが、まだフィードバックはありません: https://forums.developer.Apple.com/message/ 234654

4
Chris Comeau

私はこれにつまずいた: INavigationItem-Margin 。それは魅力のように機能します。

4
zumph

this answer :BarButtonsはAutolayoutを使用するため、制約が必要になります。

if #available(iOS 9.0, *) {
  cButton.widthAnchor.constraint(equalToConstant: customViewButton.width).isActive = true
  cButton.heightAnchor.constraint(equalToConstant: customViewButton.height).isActive = true
}

客観的C

if (@available(iOS 9, *)) {
  [cButton.widthAnchor constraintEqualToConstant: standardButtonSize.width].active = YES;
  [cButton.heightAnchor constraintEqualToConstant: standardButtonSize.height].active = YES;
}
3
Dane Jordan

解決策1:これは予想される動作であるというAppleの応答を踏まえて、ツールバーを削除してカスタムビューを追加することで問題を回避しました。

すべての場合にこれが可能であるとは限りませんが、通常のUIViewは、Appleがボタンの位置を制御するツールバーとナビゲーションバーよりも、アプリの外観をカスタマイズする方がはるかに簡単です。

カスタムボタンをuiバーボタンオブジェクトのカスタムビューとして設定する代わりに、カスタムビューの空白のuiボタンのサブビューとして設定しました。

これを行うと、iOS 10アプリと同じ外観に戻ることができました

解決策2:少々厄介です。カスタムビューボタンを外側のUIButtonにラップして、フレームの位置を設定できるようにしました。これにより、ボタンの左外側のエッジが残念ながらタップ不可になりますが、ボタンの位置の外観は修正されます。例を参照してください:

UIButton* outerButton = [UIButton new]; //the wrapper button
BorderedButton* button = [self initBorderedButton]; //the custom button
[button setTitle:label forState:UIControlStateNormal];
[button setFrame:CGRectMake(-10, 0, [label length] * 4 + 35, 30)];
[button addTarget:controller action:@selector(popCurrentViewController) forControlEvents:UIControlEventTouchUpInside];
[outerButton addSubview:button]; //add custom button to outer wrapper button
[outerButton setFrameWidth:button.frameWidth]; //make sure title gives button appropriate space
controller.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:outerButton]; //add wrapper button to the navigation bar
controller.navigationItem.hidesBackButton = YES;

その方法を使用して、ナビゲーションバーを保持し、必要な場所にボタンを配置できます。

編集:ソリューション2はiOS 10では動作しないことがわかりました。これはおそらく、下位互換性を余儀なくされた開発者のごく一部にしか影響しません。

ソリューション

ボタンの内側への密集について本当に心配していたのは、ナビゲーションバーのタイトルがカスタムの左ボタンにぶつかり、余白のサイズはそれほど重要ではなく、スペースを作るためのツールとして使用されるという事実でした。解決策は、左のアイテムにスペーサーアイテムを追加するだけです。

UIBarButtonItem* backButton = [[UIBarButtonItem alloc] initWithCustomView:button];
UIBarButtonItem* spacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
controller.navigationItem.leftBarButtonItems = [NSArray arrayWithObjects:backButton, spacer, nil];
3
Sashah

Developer3214のカスタムUINavigationBarサブクラスのlayoutSubviewsメソッドの迅速なバージョン( https://stackoverflow.com/a/46660888/4189037 ):

override func layoutSubviews() {
    super.layoutSubviews();
    if #available(iOS 11, *){
        self.layoutMargins = UIEdgeInsets()
        for subview in self.subviews {
            if String(describing: subview.classForCoder).contains("ContentView") {
                let oldEdges = subview.layoutMargins
                subview.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: oldEdges.right)
            }
        }
    }
}
1

別の回答 が役立つ場合があります。

私の解決策:ios 9-12で動作しますfixNavigationItemsMargin(margin:) in function viewDidAppear(_ animated:Bool) and viewDidLayoutSubviews()fixNavigationItemsMargin(margin:)は、UINavigationControllerスタックを変更します。

baseNavigationControllerでfixNavigationItemsMargin(margin:)を呼び出して、一般的な作業を行うことができます。そして、UIViewControllerでfixNavigationItemsMargin(margin:)を呼び出して、正確なレイアウトを行います。

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

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

// 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)
}
}

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
            }
        }
    }
}
}
1
Jules

同じ問題がありました。ナビゲーションバーのスタックビューには、右のbarbuttonアイテムとして3つのボタンがありました。そのため、ナビゲーションバーのサブビューのインセットを更新しました。

_override func viewWillLayoutSubviews() {
                super.viewWillLayoutSubviews()
                for view in (self.navigationController?.navigationBar.subviews)! {
                    view.layoutMargins = UIEdgeInsets.init(top: 0, left: 11, bottom: 0, right: 11)
                }
            }
_

ここで11は必要なスペースです。要件に応じて何でもかまいません。また、インセットが0のボタンが必要な場合は、view.layoutMargins = UIEdgeInsets.init(top: 0, left: 11, bottom: 0, right: 11)を_view.layoutMargins = UIEdgeInsets.zero_に置き換えます

1
vinny