iOS11自定义大标题的实现方案

自从iOS 11发布后,iPhone的界面风格发生了很大变化,主要在3个设计大方向进行了改进:1、大标题导航栏  2、提示文字设计的层次 3、增强对比效果

于是,APP的设计师们开始疯狂地抄袭,硬要在自己的App设计各种大标题风格,可是到iOS程序猿的手上就头疼了,除了单纯地给导航栏设置大标题,苹果没有开放更多API,公有的API无法满足设计师们的需求。怎么办,设计师却拿着App Store的界面给程序猿说,“为啥别的应用能做,你们却做不出来,是不是技术不过关?”,程序猿表示很无语。

今天给大家分享自定义大标题的一个解决方案,附源码:https://github.com/linchgit/LargeTitleStyle

iOS 11系统自带大标题显示的条件:

1、在UIViewController下,使用[self.navigationController.navigationBar setPrefersLargeTitles:YES];开启大标题;

2、UIView的第一个SubView是UIScrollView或者UIScrollView的子类;

满足以上条件后,通过滑动列表,可以缩放大标题。

利用iOS 11的大标题特性,可以通过监听UINavgationBar的高度,自定义自己的大标题。实现方案:

1、继承UINavgationBar,创建一个可监听导航栏高度的LTSNavigationBar,关键代码:

-(void)layoutSubviews
{
    [super layoutSubviews];
    [[NSNotificationCenter defaultCenter] postNotificationName:KEY_UINavigationBar_Height_Changed object:self userInfo:nil];

}

2、新建UIViewController的Category类:UIViewController (LTS),接收导航栏高度的变化:

//导航栏高度发生变化时调用,如果视图控制器需要开启自定义大标题时,实现下面的方法
@protocol LTSNavigationChangedDelegate
@optional
-(void)LTSNavigationChanged:(LTSNavigationBar *)navigationBar;
@end
@interface UIViewController (LTS)
@end

#import "UIViewController+LTS.h"
@implementation UIViewController (LTS)
//交换方法
+ (void)load
{
    if(@available(iOS 11.0, *)){
        //与系统方法viewDidLoad交换为BLSViewDidLoad方法
        [UIViewController swizzleMethod:@selector(viewDidLoad) withMethod:@selector(BLSViewDidLoad)];
    }
}
-(void)BLSViewDidLoad
{
    [self BLSViewDidLoad];
    if (self.navigationController && self.navigationController.navigationBar.prefersLargeTitles == YES) {
        self.title = @"";
    }
    [self setupLTS];
}
//设置高度变化监听
-(void)setupLTS
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(navigationBarHeightChange:) name:KEY_UINavigationBar_Height_Changed object:nil];
}

//监听UINavigationBar高度变化

-(void)navigationBarHeightChange:(NSNotification *)notification
{
    LTSNavigationBar *notifyNavigationBar = notification.object;
    if ([notifyNavigationBar isKindOfClass:[LTSNavigationBar class]]) {
       [self navigationBarUpdated:notifyNavigationBar];
    }
}

//通知UINavigationBar高度变化
-(void)navigationBarUpdated:(LTSNavigationBar *)notifyNavigationBar{

 if ([self respondsToSelector:@selector(LTSNavigationChanged:)]) {
        [self LTSNavigationChanged:notifyNavigationBar];
    }
}
@end

说明:利用runtime的特性用BLSViewDidLoad置换UIViewController的ViewDidLoad方法,实现监听代码的入侵。

3、创建大标题容器视图:LTSCustomContainerView,在导航栏高度变化时,回调方法刷新容器视图的高度:

//自定义大标题协议
@protocol LTSViewProtocol//父界面发生变化时调用

-(void)LTSSuperViewUpdated;
@end
@interface LTSCustomContainerView : UIView
@property (nonatomic,strong) UIView* contentView;
@property (nonatomic,assign) BOOL maxStretchMode;
//设置最大拉伸模式后,大标题显示最多只能拉伸至96像素(默认大标题的NavigatinBar的高度),相应的UIScrollView也需要限制最大偏移量
-(instancetype)initWithFrame:(CGRect)frame contentView:(UIView *)contentView;
//随导航栏变化而更新
-(void)updateUIWithNavigationBar:(CGFloat)navigationBarHeight scrollView:(UIScrollView *)scrollView;
@end

@implementation LTSCustomContainerView-(instancetype)initWithFrame:(CGRect)frame contentView:(UIView *)contentView
{
  self = [super initWithFrame:frame];
    self.contentView = contentView;
    [self addSubview:self.contentView];
    return self;
}

-(void)layoutSubviews
{
    [super layoutSubviews];
    [self.contentView LTSSuperViewUpdated];
}

-(void)updateUIWithNavigationBar:(CGFloat)navigationBarHeight scrollView:(UIScrollView *)scrollView
{
    CGPoint contentOffset = scrollView.contentOffset;
    if (contentOffset.y>44) {
        CGRect frame = self.frame;
        frame.size.height = navigationBarHeight;
        self.frame = frame;
        [self layoutIfNeeded];
    }else
    {
        CGRect frame = self.frame;
        if (self.maxStretchMode) {
            frame.size.height = MIN(navigationBarHeight, 96);
        }else
        {
            frame.size.height = navigationBarHeight;
        }
        self.frame = frame;        
        [self layoutIfNeeded];
    }
}
@end

4、创建自定义视图类LTSMenuView,实现LTSViewProtocol
@interface LTSMenuView : UIView@property (nonatomic, weak) id menuDelegate;

- (instancetype)initViewWithFrame:(CGRect)frame andTextArray:(NSArray *)textArray;

@end

LTSMenuView关键代码:

@interface LTSMenuView()

@property(nonatomic, strong) NSMutableArray *titleArray;

@property(nonatomic, assign) CGFloat leftDistance;

@property(nonatomic, assign) CGFloat curTitleWidth;

@property(nonatomic, weak) UIButton *selectButton;

@property(nonatomic, strong) NSMutableArray *lineArray;

@property(nonatomic, weak) UIView *selectLineView;

@property(nonatomic,assign) NSInteger curSelectedIndex;

@property(nonatomic,assign) BOOL isLargeTitleMode;

@end

@implementation LTSMenuView

-(void)LTSSuperViewUpdated{

    CGRect superFrame =self.superview.frame;

    CGFloat superHeight = CGRectGetHeight(superFrame);

    self.isLargeTitleMode = (superFrame.size.height>53)?YES:NO;

    if (self.isLargeTitleMode) {

        CGFloat height = superHeight - 44;

        height = (height<38)?38:height;

        height = MIN(height, 52);

        self.frame = CGRectMake(0, CGRectGetHeight(superFrame)-height, CGRectGetWidth(self.frame), height);

    }else

    {
    self.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame), 44);
    }
    [self updateItemView];
}

- (instancetype)initViewWithFrame:(CGRect)frame andTextArray:(NSArray *)textArray{

    if(!_titleArray && textArray.count < 1)   {
     return nil;
    }
    self.leftDistance = 0.0;
    self.curTitleWidth = 0.0;
    self = [super initWithFrame:frame];
    if(self)
    {
        for(NSInteger i = 0; i < textArray.count ;i++)
        {
            NSString *titleStr = textArray[i];
            CGSize size = [self getTextSize:titleStr];
            if(size.height != 0 && size.width != 0)
            {
                UIButton *button = [[UIButton alloc] init];
                [button setTitle:titleStr forState:UIControlStateNormal];
                button.tag = i;
                [button addTarget:self action:@selector(titleButtonClick:) forControlEvents:UIControlEventTouchDown];
                [self.titleArray addObject:button];

                UIView *lineView = [[UIView alloc] init];
               [lineView setBackgroundColor:[UIColor blackColor]];
                [self.lineArray addObject:lineView];
                if(i == 0)
                {
                    [self selctedBtn:button];
                }
                else
                {
                    lineView.hidden = YES;
                }
                [self addSubview:button];
                [self addSubview:lineView];
            }
        }
        [self updateItemView];
    }
    return self;
}

//自定义标题分为大标题模式和非大标题模式
-(void)updateItemView{
    for (NSInteger i =0;i<[self.titleArray count];i++) {
        UIButton *button = self.titleArray[i];
        if(i == 0)
        {
            self.leftDistance = 20.0;
        }
        else
        {
            self.leftDistance = self.curTitleWidth + 12;
        }
        UIFont *font = nil;
        if (button.tag == self.curSelectedIndex) {
            [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
            if (self.isLargeTitleMode) {
                font = [UIFont boldSystemFontOfSize:34];
            }else
            {
                font = [UIFont boldSystemFontOfSize:20];
            }
        }else
        {
            [button setTitleColor:RGB(170, 170, 170) forState:UIControlStateNormal];
            if (self.isLargeTitleMode) {
                font = [UIFont boldSystemFontOfSize:20];
            }else
            {
                font = [UIFont boldSystemFontOfSize:17];
            }
        }
        CGSize size = [button.titleLabel.text sizeWithAttributes:@{NSFontAttributeName:font}];
        button.titleLabel.font = font;
        self.curTitleWidth = size.width + self.leftDistance;
        button.frame = CGRectMake(self.leftDistance, 8, size.width, 22);
        UIView *lineView = self.lineArray[i];
        lineView.frame = CGRectMake(self.leftDistance + 2 , size.height + 12, size.width - 2, 2);
        lineView.hidden = self.isLargeTitleMode || button.tag != self.curSelectedIndex;
    }
}

@end

5、最后,在UIViewController的子类实现大标题风格:

@interface MenuTableViewController : UITableViewController

@end

#import "UIViewController+LTS.h"
#import "LTSCustomContainerView.h"
#import "LTSMenuView.h"
@interface MenuTableViewController ()
@property(nonatomic,strong) LTSCustomContainerView *headerView;
@end

@implementation MenuTableViewController
-(LTSCustomContainerView *)headerView
{
    if(!_headerView)
    {
        CGRect frame = self.navigationController.navigationBar.bounds;
        LTSMenuView *titleView = [[LTSMenuView alloc] initViewWithFrame:frame andTextArray:@[@"时刻", @"人物", @"地点", @"动画影集"]];
        _headerView = [[LTSCustomContainerView alloc] initWithFrame:frame contentView:titleView];
        [_headerView setMaxStretchMode:NO];
    }
    return _headerView;

}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationItem.title=@"";
    self.tabBarItem.title = @"菜单大标题";
     if(@available(iOS 11.0, *)){
         [self.navigationController.navigationBar setPrefersLargeTitles:YES];
     }
    [self.navigationController.navigationBar addSubview:self.headerView];
}
#pragma mark LTSNavigationChangedDelegate
-(void)LTSNavigationChanged:(UINavigationBar *)navigationBar
{
    [self.headerView updateUIWithNavigationBar:navigationBar.frame.size.height scrollView:self.tableView];
}
#pragma mark - Table view data source

....

@end

MenuTableViewController使用iOS 11大标题特性,默认把self.title作为大标题,因此需要代码屏蔽掉title:self.navigationItem.title=@"";,详细源码,请看源码:

最终效果图:

原创文章,转载请注明出处。write by linch

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容

  • 前言 最近忙完项目比较闲,想写一篇博客来分享一些自学iOS的心得体会,希望对迷茫的你有所帮助。博主非科班出身,一些...
    GitHubPorter阅读 1,424评论 9 5
  • 直方图主要用在数据图表,作为对比数据,用柱体高度的高低,形象直观地表达出来,往往与折线图配合使用,而折线图便于从众...
    理想是试阅读 946评论 0 0
  • 风把自己藏起来 叶子不再喧哗 蜷曲着 同谁在捉迷藏 鸟的冀翅穿透烈日 深耕天空 却把爱昧深埋在疯狂的草垛里 交配在...
    安若胡阅读 264评论 0 3
  • Promise简介 Promise是一种解决异步回调问题而发展的编程语言特性,在各种语法中都有支持,比如在Java...
    李卓立阅读 746评论 0 2
  • 戳上面的蓝字关注我们哦! 《墨麒雕刻艺术》以匠之心,造木之艺 弘扬雕刻文化︱传承工匠精神 明杨慎《升庵外集》有云:...
    墨麒雕刻艺术阅读 763评论 0 0