iOS 11 UINavigationBar栏按钮项对齐

时间:2017-06-21 13:05:24

标签: ios11

我正在将我的应用程序升级到iOS 11,我看到导航栏出现了一些问题,我的部分问题已经在这里提出问题,所以我在这个问题中没有提到它们。

所讨论的特定问题是导航栏的条形按钮项间隔不同。我的主左右按钮项目现在更接近屏幕的水平中心,我无法将它们移动到屏幕边缘附近。在过去,我使用了自定义UIButton子类,并使用自定义视图创建了条形按钮项。对齐解决方案是alignmentRectInsetscontentEdgeInsets,现在我无法使用此方法产生预期结果。

修改
我已经重新测试了iOS 11 beta 2,问题仍然存在。

编辑2: 我已经重新测试了iOS beta 3,问题仍然存在。

10 个答案:

答案 0 :(得分:8)

现在,在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;
                        }
                    }
                }
            }
        }
    }
}


如您所见,没有必要像其他人那样添加更多约束。已经定义了约束,因此可以更改它们。

答案 1 :(得分:7)

大约两天后,这是我能想到的最简单,最安全的解决方案。此解决方案仅适用于自定义视图栏按钮项,其中包含代码。值得注意的是,导航栏上的左右边距没有从iOS10更改为iOS11 - 它们仍然是16px。如此大的余量使得难以获得足够大的点击区域。

条形按钮现在与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点。

答案 2 :(得分:4)

我注意到了类似的问题。

我报告了一个Apple 雷达,因为我们注意到了类似的问题,如果你想引用它,那么#32674764,如果你创建一个雷达。

我还在Apple的论坛中创建了一个帖子,但还没有反馈意见: https://forums.developer.apple.com/message/234654

答案 3 :(得分:4)

有关于此的好文章:http://www.matrixprojects.net/p/uibarbuttonitem-ios11/

使用此功能,我们至少可以将右侧按钮按钮向右推,直到UINavigationBar尾随8个像素边距。

解释得很好。

答案 4 :(得分:4)

我偶然发现了这个问题:UINavigationItem-Margin。 它就像一个魅力。

答案 5 :(得分:3)

解决方案1: 鉴于Apple的回应是预期的行为,我们通过删除工具栏并添加自定义视图解决了这个问题。

我意识到在所有情况下这可能都不可能,但是常规UIView比苹果控制按钮定位的工具栏和导航栏更容易定制应用程序的外观。

我们不是将自定义按钮设置为ui bar按钮对象的自定义视图,而是将其设置为自定义视图中空白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上不起作用,这可能只会影响被迫向后兼容的微小百分比。

解决方案3

我们真正更关心按钮的向内拥挤是导航栏的标题遇到自定义左按钮的事实,边距的大小不太重要,并用作制作空间的工具。解决方案是简单地在左侧项目中添加间隔项。

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

答案 6 :(得分:3)

取自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;
}

答案 7 :(得分:1)

开发者版本的开发者3214的自定义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)
            }
        }
    }
}

答案 8 :(得分:1)

我有同样的问题。我在导航栏堆栈视图上有三个按钮作为右边的barbutton项。因此,我更新了导航栏子视图的插图。

Date

这里11是我需要的空间。根据您的要求可以是任何东西。 另外,如果您希望按钮具有0个插入,请将override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() for view in (self.navigationController?.navigationBar.subviews)! { view.layoutMargins = UIEdgeInsets.init(top: 0, left: 11, bottom: 0, right: 11) } } 替换为view.layoutMargins = UIEdgeInsets.init(top: 0, left: 11, bottom: 0, right: 11)

答案 9 :(得分:1)

Another answer可能会有所帮助。

我的解决方案:它适用于ios 9-12。您应该在函数 viewDidAppear(_动画:Bool) viewDidLayoutSubviews中调用 fixNavigationItemsMargin(margin:) () 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
            }
        }
    }
}
}