18

我正在将我的应用程序升级到 iOS 11,并且我发现导航栏存在一些问题,我的部分问题已经在这里提出了问题,所以我不会在这个问题中提及它们。

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

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

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

4

10 回答 10

9

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


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

于 2017-10-03T16:50:14.767 回答
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 点。

于 2017-10-10T07:27:57.227 回答
5

有一篇很好的文章: http: //www.matrixprojects.net/p/uibarbuttonitem-ios11/

使用它,我们至少可以将 rightbarbuttonitems 向右推,直到它从 UINavigationBar 的尾部留下 8 个像素的边距。

解释得真好。

于 2017-11-29T10:02:14.013 回答
4

我注意到一个类似的问题。

我报告了一个 Apple Radar,我们注意到了一个类似的问题,#32674764 如果你想参考它,如果你创建了一个雷达。

我还在苹果论坛上创建了一个帖子,但还没有反馈: https ://forums.developer.apple.com/message/234654

于 2017-06-22T14:03:43.750 回答
4

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

于 2018-03-11T22:50:34.040 回答
3

解决方案 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 上不起作用,这可能只会影响一小部分被迫向后兼容的开发人员。

解决方案 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];
于 2017-08-14T14:36:44.863 回答
3

取自这个答案:BarButtons 现在使用自动布局,因此需要约束。

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;
}
于 2017-10-01T17:02:53.907 回答
1

developer3214 的自定义 UINavigationBar 子类的 layoutSubviews 方法的 Swift 版本(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)
            }
        }
    }
}
于 2017-11-07T20:22:31.497 回答
1

我遇到过同样的问题。我在导航栏堆栈视图上有三个按钮作为右栏按钮项。所以,我更新了导航栏子视图的插图。

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

于 2018-09-17T14:34:59.137 回答
1

另一个答案可能会有所帮助。

我的解决方案:它适用于 ios 9 - 12。您应该在函数viewDidAppear(_ animated: 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
            }
        }
    }
}
}
于 2018-11-08T10:14:38.170 回答