【问题标题】:Negative spacer for UIBarButtonItem in navigation bar on iOS 11iOS 11 导航栏中 UIBarButtonItem 的负间隔
【发布时间】:2018-01-14 15:28:29
【问题描述】:

在 iOS 10 及更低版本中,有一种方法可以在导航栏中的按钮数组中添加负间隔,如下所示:

UIBarButtonItem *negativeSpacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
negativeSpacer.width = -8;
self.navigationItem.leftBarButtonItems = @[negativeSpacer, [self backButtonItem]];

这不再适用于 iOS 11(分隔符变为正数,而不是负数)。我检查了条形按钮项的视图层次结构,它现在嵌入到_UIButtonBarStackView 中。 iOS 11如何调整bar按钮的位置?

【问题讨论】:

    标签: ios objective-c uistackview ios11


    【解决方案1】:

    编辑:

    这可能不再适用于 iOS 13。您可能会收到错误消息:

    客户端尝试更改私有视图的布局边距时出错

    旧答案:

    我在 Apple 开发者论坛上发现了一个有点 hacky 的解决方案: https://forums.developer.apple.com/thread/80075

    看起来问题出在 iOS 11 如何处理 UIBarButtonItem .fixedSpace 按钮以及 UINavigationBar 在 iOS 11 中的布局方式。导航栏现在使用自动布局和布局边距来布局按钮。该帖子(底部)中提出的解决方案是将所有布局边距设置为您想要的某个值。

    class InsetButtonsNavigationBar: UINavigationBar {
    
        override func layoutSubviews() {
            super.layoutSubviews()
    
            for view in subviews {
                // Setting the layout margins to 0 lines the bar buttons items up at
                // the edges of the screen. You can set this to any number to change
                // the spacing.
                view.layoutMargins = .zero
            }
        }
    
    }
    

    要使用这个带有自定义按钮间距的新导航栏,您需要使用以下代码更新创建导航控制器的位置:

    let navController = UINavigationController(navigationBarClass: InsetButtonsNavigationBar.self, 
                                                     toolbarClass: UIToolbar.self)
    navController.viewControllers = [yourRootViewController]
    

    【讨论】:

    • 不确定这是否与 iPhone X 横向扩展空间配合得很好。
    • 我希望有另一种处理方式。我的应用程序中有太多导航控制器,为每个控制器设置自定义导航栏会相当乏味。如果没有其他选择,我会接受这个答案。
    • 感谢您的回答。似乎现在应该更改设计以适应 iOS 11 的新 UI 更改,没有必要进行黑客攻击并冒着预期功能造成损失或 UI 被破坏的风险。..
    • 这里是其他视图的列表,它们的布局边距也会不必要地更改。这可能会在以后的 iOS 更新或当前更新中产生不良影响。我不会推荐这个<_UIBarBackground: layoutMargins = {8, 8, 8, 8},<_UINavigationBarLargeTitleView: layoutMargins = {0, 16, 0, 16}, <_UINavigationBarContentView: layoutMargins = {0, 16, 0, 16}, <_UINavigationBarModernPromptView: layoutMargins = {8, 8, 8, 8}
    • 在 Xcode 13 beta7、iOS 13 中,将引发异常:Client error attempting to change layout margins of a private view
    【解决方案2】:

    只是我的情况的一种解决方法,它可能对某些人有帮助。我想实现这一点:

    以前我也使用过negativeSpacer。现在我想出了这个解决方案:

            let logoImage = UIImage(named: "your_image")
            let logoImageView = UIImageView(image: logoImage)
            logoImageView.frame = CGRect(x: -16, y: 0, width: 150, height: 44)
            logoImageView.contentMode = .scaleAspectFit
            let logoView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 44))
            **logoView.clipsToBounds = false**
            logoView.addSubview(logoImageView)
            let logoItem = UIBarButtonItem(customView: logoView)
            navigationItem.leftBarButtonItem = logoItem
    

    【讨论】:

    • 这个解决方案的触控区域是什么?如果您一直点击视图左侧,它会记录触摸吗?
    • @keithbhunter 不幸的是,触摸区域只是 logoView 而不是 logoImageView。
    【解决方案3】:

    根据 keithbhunter 的回答,我创建了一个自定义 UINavigationBar:

    NavigationBarCustomMargins.h:

    #import <UIKit/UIKit.h>
    
    @interface NavigationBarCustomMargins : UINavigationBar
    
    @property (nonatomic) IBInspectable CGFloat leftMargin;
    @property (nonatomic) IBInspectable CGFloat rightMargin;
    
    @end
    

    NavigationBarCustomMargins.m:

    #import "NavigationBarCustomMargins.h"
    
    #define DefaultMargin 16
    #define NegativeSpacerTag 87236223
    
    @interface NavigationBarCustomMargins ()
    
    @property (nonatomic) BOOL leftMarginIsSet;
    @property (nonatomic) BOOL rightMarginIsSet;
    
    @end
    
    @implementation NavigationBarCustomMargins
    
    @synthesize leftMargin = _leftMargin;
    @synthesize rightMargin = _rightMargin;
    
    - (void)layoutSubviews {
        [super layoutSubviews];
    
        if (([[[UIDevice currentDevice] systemVersion] compare:@"11.0" options:NSNumericSearch] != NSOrderedAscending)) {
            BOOL isRTL = [UIApplication sharedApplication].userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft;
            for (UIView *view in self.subviews) {
                view.layoutMargins = UIEdgeInsetsMake(0, isRTL ? self.rightMargin : self.leftMargin, 0, isRTL ? self.leftMargin : self.rightMargin);
            }
        } else {
            //left
            NSMutableArray *leftItems = [self.topItem.leftBarButtonItems mutableCopy];
            if (((UIBarButtonItem *)leftItems.firstObject).tag != NegativeSpacerTag) {
    
                UIBarButtonItem *negativeSpacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
                negativeSpacer.tag = NegativeSpacerTag;
                negativeSpacer.width = self.leftMargin - DefaultMargin;
                [leftItems insertObject:negativeSpacer atIndex:0];
    
                [self.topItem setLeftBarButtonItems:[leftItems copy] animated:NO];
            }
    
            //right
            NSMutableArray *rightItems = [self.topItem.rightBarButtonItems mutableCopy];
            if (((UIBarButtonItem *)rightItems.firstObject).tag != NegativeSpacerTag) {
    
                UIBarButtonItem *negativeSpacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
                negativeSpacer.tag = NegativeSpacerTag;
                negativeSpacer.width = self.rightMargin - DefaultMargin;
                [rightItems insertObject:negativeSpacer atIndex:0];
                [self.topItem setRightBarButtonItems:[rightItems copy] animated:NO];
            }
        }
    }
    
    - (CGFloat)leftMargin {
        if (_leftMarginIsSet) {
            return _leftMargin;
        }
        return DefaultMargin;
    }
    
    - (CGFloat)rightMargin {
        if (_rightMarginIsSet) {
            return _rightMargin;
        }
        return DefaultMargin;
    }
    
    - (void)setLeftMargin:(CGFloat)leftMargin {
        _leftMargin = leftMargin;
        _leftMarginIsSet = YES;
    }
    
    - (void)setRightMargin:(CGFloat)rightMargin {
        _rightMargin = rightMargin;
        _rightMarginIsSet = YES;
    }
    
    @end
    

    之后,我在 Interface Builder 中将自定义类设置为 UINavigationController 并设置所需的边距: Screenshot 1

    工作正常。支持 RTL 和 iOS 11 之前的版本: Screenshot 2

    【讨论】:

    • 我认为您需要在 setLeftMargin: 和 setRightMargin: 的底部添加 [self setNeedsLayout]; 这样,layoutSubviews 将在下一个可用机会中使用更新的边距参数被调用
    【解决方案4】:

    可以将leftBarButtonItem/rightBarButtonItem的customView设置为透明视图,然后将需要添加的按钮添加到这个透明视图中,再给按钮添加一些约束。

    override func viewDidLoad() {
        super.viewDidLoad()
        setNavigationItemButton()
    }
    
    func setNavigationItemButton() {
        let leftCustomView = UIView()
        let rightCustomView = UIView()
        leftCustomView.backgroundColor = .clear
        rightCustomView.backgroundColor = .clear
            
        navigationItem.leftBarButtonItem = UIBarButtonItem(customView: leftCustomView)
        navigationItem.rightBarButtonItem = UIBarButtonItem(customView: rightCustomView)
        navigationItem.leftBarButtonItem?.customView?.addSubview(searchButton)
        navigationItem.rightBarButtonItem?.customView?.addSubview(settingButton)
            
        // The size of the customView must be set through a constraint,
        // otherwise the customView size may change to 0, making the button doesn't work.
        let height = navigationController?.navigationBar.frame.height ?? 0
    
          navigationItem.leftBarButtonItem?.customView?.translatesAutoresizingMaskIntoConstraints = false
        navigationItem.leftBarButtonItem?.customView?.widthAnchor.constraint(equalToConstant: height).isActive = true
        navigationItem.leftBarButtonItem?.customView?.heightAnchor.constraint(equalToConstant: height).isActive = true
        navigationItem.rightBarButtonItem?.customView?.translatesAutoresizingMaskIntoConstraints = false
        navigationItem.rightBarButtonItem?.customView?.widthAnchor.constraint(equalToConstant: height).isActive = true
        navigationItem.rightBarButtonItem?.customView?.heightAnchor.constraint(equalToConstant: height).isActive = true
            
        // You must set the constraint for the button,
        // just set frame to be modified by the UIKit constraints and cannot achieve the purpose.
        searchButton.translatesAutoresizingMaskIntoConstraints = false
        searchButton.centerYAnchor.constraint(equalTo: leftCustomView.centerYAnchor).isActive = true
        searchButton.leadingAnchor.constraint(equalTo: leftCustomView.leadingAnchor, constant: -8).isActive = true
        searchButton.widthAnchor.constraint(equalToConstant: 32).isActive = true
        searchButton.heightAnchor.constraint(equalTo: searchButton.widthAnchor).isActive = true
            
        settingButton.translatesAutoresizingMaskIntoConstraints = false
        settingButton.centerYAnchor.constraint(equalTo: rightCustomView.centerYAnchor).isActive = true
        settingButton.trailingAnchor.constraint(equalTo: rightCustomView.trailingAnchor, constant: 8).isActive = true
        settingButton.widthAnchor.constraint(equalToConstant: 32).isActive = true
        settingButton.heightAnchor.constraint(equalTo: settingButton.widthAnchor).isActive = true
    }
    

    我在 iOS 13 和 iOS 14 上测试过,这个方法可行,但需要注意的是,你可能需要调整按钮的触摸范围。

    下面是一个例子:

    【讨论】:

      【解决方案5】:

      对我来说这个答案帮助https://stackoverflow.com/a/44896832
      特别是我设置了 imageEdgeInsetstitleEdgeInsets 因为我的按钮同时具有图像和标题

      【讨论】:

      • 这个问题是按钮的触摸区域在图片的右边
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-12-27
      • 2018-02-12
      • 2018-09-23
      • 2018-01-03
      • 2018-03-12
      • 1970-01-01
      • 2018-03-05
      相关资源
      最近更新 更多