iOS11UINavigationBar的item左右間距調整


相信很多同學都知道在iOS7之后調整導航欄兩側按鈕距離左右間距,其實就是在左右barButtonItem的數組中添加一個寬度為負的占位item。

- (void)addLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem
{
    UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
    space.width = -8;
    [self setLeftBarButtonItems:@[space, leftBarButtonItem]];
}

但是在iOS11之后,發現該方法失效了。新的思路和iOS7的完全不一樣,我們給UINavigationBarContentView加一條約束。怎么加呢?

1 自定義一個customView,使用initWithCustomView創建UIBarButtonItem。

2 在customView的layoutSubviews方法中找到UINavigationBarContentView,添加customView和UINavigationBarContentView之間的約束。

customView定義如下:

#import "UIView.h"

typedef NS_ENUM(NSInteger, LFBarButtonItemViewType) {
    LFBarButtonItemViewTypeLeft,
    LFBarButtonItemViewTypeRight,
};

@interface LFBarButtonItemView : UIView

@property (nonatomic, assign) LFBarButtonItemViewType type;

@end

 

@implementation LFBarButtonItemView

- (void)layoutSubviews {
    [super layoutSubviews];
    if (iOSVersion < 11.0) {
        return;
    }
    //Here is a workaround on iOS 11 UINavigationBarItem init with custom view, position issue
    UIView *view = self;
    while (![view isKindOfClass:[UINavigationBar class]] && [view superview] != nil)
    {
        view = [view superview];
        if ([view isKindOfClass:[UIStackView class]] && [view superview] != nil)
        {
            if (self.type == LFBarButtonItemViewTypeLeft) {
                CGFloat margin = 0.0f;
                // 5.5寸plus間距大一點
                if ([[TWDeviceManager sharedManager] iPhone55]) {
                    margin = 4.0f;
                }
                [view.superview addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:view.superview attribute:NSLayoutAttributeLeading multiplier:1.0 constant:margin]];
                break;
            } else if (self.type == LFBarButtonItemViewTypeRight) {
                CGFloat margin = 0.0f;
                // 5.5寸plus間距大一點
                if ([[TWDeviceManager sharedManager] iPhone55]) {
                    margin = -4.0f;
                }
                [view.superview addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:view.superview attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:margin]];
                break;
            }
        }
    }
}

在創建self.navigationItem.rightBarButtonItem的地方:

- (void)initNavigationBar {
    // rightItem
    UIImage *image = [UIImage imageNamed:@"share-icon-dark"];
    LFBarButtonItemView *rightItemCustomView = [[LFBarButtonItemView alloc] initWithFrame:CGRectMake(0, 0, image.size.width, image.size.height)];
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didClickOnShareButton)];
    [rightItemCustomView addGestureRecognizer:tap];
    rightItemCustomView.type = LFBarButtonItemViewTypeRight;
    UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
    [imageView sizeToFit];
    [rightItemCustomView addSubview:imageView];
    UIBarButtonItem *rightItem = [[UIBarButtonItem alloc] initWithCustomView:rightItemCustomView];
    self.navigationItem.rightBarButtonItem = rightItem;
}

但是這個方法會有約束沖突問題,所以我們把產生沖突的約束刪除。可以用xcode查看視圖層次,以方便理解。

- (void)layoutSubviews {
    [super layoutSubviews];
    if (iOSVersion < 11.0) {
        return;
    }
    //Here is a workaround on iOS 11 UINavigationBarItem init with custom view, position issue
    UIView *view = self;
    while (![view isKindOfClass:[UINavigationBar class]] && [view superview] != nil)
    {
        view = [view superview];
        if ([view isKindOfClass:[UIStackView class]] && [view superview] != nil)
        {
            if (self.type == LFBarButtonItemViewTypeLeft) {
                CGFloat margin = kAppAdaptHeight(15);
                //刪除原來的leading約束
                for (NSLayoutConstraint *constraint in view.superview.constraints) {
                    if ([constraint.firstItem isKindOfClass:[UILayoutGuide class]] &&
                        constraint.firstAttribute == NSLayoutAttributeTrailing) {
                        [view.superview removeConstraint:constraint];
                    }
                }
                //添加新約束
                NSLayoutConstraint *leadingConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:view.superview attribute:NSLayoutAttributeLeading multiplier:1.0 constant:margin];
                leadingConstraint.priority = UILayoutPriorityRequired;
                [view.superview addConstraint:leadingConstraint];

                break;
            } else if (self.type == LFBarButtonItemViewTypeRight) {
                CGFloat margin = -kAppAdaptHeight(15);
                
                //刪除原來的leading約束
                for (NSLayoutConstraint *constraint in view.superview.constraints) {
                    if ([constraint.firstItem isKindOfClass:[UILayoutGuide class]] &&
                        constraint.firstAttribute == NSLayoutAttributeTrailing) {
                        [view.superview removeConstraint:constraint];
                    }
                }
                
                NSLayoutConstraint *trailingConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:view.superview attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:margin];
                trailingConstraint.priority = UILayoutPriorityRequired;
                [view.superview addConstraint:trailingConstraint];
                break;
            }
        }
    }
}

現在看起來問題解決了,但是某一個界面在push一個新界面之后再返回回來之后位置就還原了 
解決方案其實很簡單,只要將設置leftItem的方法寫在viewWillAppear中即可,這樣即可保證約束不會被系統重置。但是,這樣的方案,是不是覺得不完美!

現在有一個終極解決方案:

UINavigationBarContentView平鋪在導航欄中作為iOS11的各個按鈕的父視圖,該視圖的所有的子視圖都會有一個layoutMargins被占用,也就是系統調整的占位,我們只要把這個置空就行了.那樣的話該視圖下的所有的子視圖的空間就會變成我們想要的那樣,當然為了保險起見,該視圖的父視圖也就是bar的layoutMargins也置空,這樣 整個bar就會跟一個普通視圖一樣了 左右的占位約束就不存在了

給UINavigationBar寫一個分類:

#import "UINavigationBar+iOS11Spacing.h"
#import <objc/runtime.h>

#define kSpacerWidth kAppAdaptWidth(15)

@implementation UINavigationBar (iOS11Spacing)

+(void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self swizzleInstanceMethodWithOriginSel:@selector(layoutSubviews)
                                     swizzledSel:@selector(sx_layoutSubviews)];
    });
}

-(void)sx_layoutSubviews{
    [self sx_layoutSubviews];
    
    if (iOS11_OR_LATER && !kSpacerWidth) {//需要調節
        self.layoutMargins = UIEdgeInsetsZero;
        CGFloat space = kSpacerWidth;
        for (UIView *subview in self.subviews) {
            if ([NSStringFromClass(subview.class) containsString:@"ContentView"]) {
                subview.layoutMargins = UIEdgeInsetsMake(0, space, 0, space);//可修正iOS11之后的偏移
                break;
            }
        }
    }
}

+ (void)swizzleInstanceMethodWithOriginSel:(SEL)oriSel swizzledSel:(SEL)swiSel {
    Method originAddObserverMethod = class_getInstanceMethod(self, oriSel);
    Method swizzledAddObserverMethod = class_getInstanceMethod(self, swiSel);
    
    [self swizzleMethodWithOriginSel:oriSel oriMethod:originAddObserverMethod swizzledSel:swiSel swizzledMethod:swizzledAddObserverMethod class:self];
}

+ (void)swizzleMethodWithOriginSel:(SEL)oriSel
                         oriMethod:(Method)oriMethod
                       swizzledSel:(SEL)swizzledSel
                    swizzledMethod:(Method)swizzledMethod
                             class:(Class)cls {
    BOOL didAddMethod = class_addMethod(cls, oriSel, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    
    if (didAddMethod) {
        class_replaceMethod(cls, swizzledSel, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    } else {
        method_exchangeImplementations(oriMethod, swizzledMethod);
    }
}

這樣就有一個好處,在原來代碼的基礎上,判斷iOS11,什么都不做,iOS7-iOS11之間版本使用老方法修改間距。

舉一個設置LeftBarButtonItem的例子:

#import "UINavigationItem+iOS7Spacing.h"

#import <objc/runtime.h>

 

#define xSpacerWidth -8

 

@implementation UINavigationItem (iOS7Spacing)

 

- (UIBarButtonItem *)spacer

{

    UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];

    space.width = xSpacerWidth;

    return space;

}

 

- (void)mk_setLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem

{

    if (iOS11_OR_LATER) {

        [self mk_setLeftBarButtonItem:leftBarButtonItem];

    } else if (iOS7_OR_LATER) {

        if (leftBarButtonItem && (leftBarButtonItem.customView !=nil || leftBarButtonItem.image !=nil)) {

            [self mk_setLeftBarButtonItem:nil];

            [self mk_setLeftBarButtonItems:@[[self spacer], leftBarButtonItem]];

        } else {

            if (iOS7_OR_LATER) {

                [self mk_setLeftBarButtonItems:nil];

            }

            [self mk_setLeftBarButtonItem:leftBarButtonItem];

        }

    } else {

        [self mk_setLeftBarButtonItem:leftBarButtonItem];

    }

}

 

- (void)mk_setLeftBarButtonItems:(NSArray *)leftBarButtonItems

{

    if (iOS7_OR_LATER && leftBarButtonItems && leftBarButtonItems.count > 0 ) {

        

        NSMutableArray *items = [[NSMutableArray alloc] initWithCapacity:leftBarButtonItems.count + 1];

        [items addObject:[self spacer]];

        [items addObjectsFromArray:leftBarButtonItems];

        

        [self mk_setLeftBarButtonItems:items];

    } else {

        [self mk_setLeftBarButtonItems:leftBarButtonItems];

    }

}

 

- (void)mk_setRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem

{

    if (iOS11_OR_LATER) {

        [self mk_setRightBarButtonItem:rightBarButtonItem];

    } else if (iOS7_OR_LATER) {

        if (rightBarButtonItem && (rightBarButtonItem.customView !=nil || rightBarButtonItem.image != nil)) {

            [self mk_setRightBarButtonItem:nil];

            [self mk_setRightBarButtonItems:@[[self spacer], rightBarButtonItem]];

        } else {

            if (iOS7_OR_LATER) {

                [self mk_setRightBarButtonItems:nil];

            }

            [self mk_setRightBarButtonItem:rightBarButtonItem];

        }

    } else {

        [self mk_setRightBarButtonItem:rightBarButtonItem];

    }

}

 

- (void)mk_setRightBarButtonItems:(NSArray *)rightBarButtonItems

{

    if (iOS7_OR_LATER && rightBarButtonItems && rightBarButtonItems.count > 0) {

        

        NSMutableArray *items = [[NSMutableArray alloc] initWithCapacity:rightBarButtonItems.count + 1];

        [items addObject:[self spacer]];

        [items addObjectsFromArray:rightBarButtonItems];

        

        [self mk_setRightBarButtonItems:items];

    } else {

        [self mk_setRightBarButtonItems:rightBarButtonItems];

    }

}

 

+ (void)mk_swizzle:(SEL)aSelector

{

    SEL bSelector = NSSelectorFromString([NSString stringWithFormat:@"mk_%@", NSStringFromSelector(aSelector)]);

    

    Method m1 = class_getInstanceMethod(self, aSelector);

    Method m2 = class_getInstanceMethod(self, bSelector);

    

    method_exchangeImplementations(m1, m2);

}

 

+ (void)load

{

    [self mk_swizzle:@selector(setLeftBarButtonItem:)];

    [self mk_swizzle:@selector(setLeftBarButtonItems:)];

    [self mk_swizzle:@selector(setRightBarButtonItem:)];

    [self mk_swizzle:@selector(setRightBarButtonItems:)];

}

參考文章: iOS11 導航欄按鈕位置問題的解決


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM