美文网首页iOS技术点iOS动画效果
iOS - 获取StatusBar(仿微信发朋友圈时,浏览全图时

iOS - 获取StatusBar(仿微信发朋友圈时,浏览全图时

作者: 吊吊的plus | 来源:发表于2018-01-15 17:04 被阅读103次
    1.开篇哔哔之言:

    最近在项目中有一个发朋友圈的功能,然后就自主仿了一波,微信的功能,然后就发现了,全图浏览时的一个小动画效果,顿时想玩玩,也算是小经波折,言归正传,走起。。。

    2.动画效果的简介:

    当单击屏幕时,导航栏和状态栏整体的上移一段距离后,渐变消失,两种实现方式:完全的系统自带方法实现(不完美),另外一种自定义导航栏+获取状态栏(自认完美-接近微信效果)。-如果有错,请帮忙纠正!!!!

    —、 使用系统方法实现(不完美):

    先上全部代码,再解释!!!
    .h文件

    #import <UIKit/UIKit.h>
    
    typedef void(^SingleTapBlock)();
    
    @interface YSShowView : UIView
    
    /** 显示的图片 */
    @property (nonatomic, strong) UIImage * img;
    
    /** 点击事件 */
    @property (nonatomic, copy) SingleTapBlock tapBlock;
    
    @end
    
    typedef void(^DeleteBlock)(UIImage * img);
    
    @interface SLBrowseBigImgController : UIViewController
    
    /** 单列 */
    + (instancetype)sharedInstance;
    
    /** 图片的数据源 */
    @property (nonatomic, strong) NSArray * imgArrs;
    
    /** 开始的下标 */
    @property (nonatomic, assign) NSInteger from;
    
    /** 删除图片的block */
    @property (nonatomic, copy) DeleteBlock deleteBlock;
    
    @end
    
    

    .m文件

    #import "SLBrowseBigImgController.h"
    
    
    @interface YSShowView () <UIScrollViewDelegate>
    
    {
        CATransition *_animation;  //缩放动画效果
        CGFloat      _scaleNum;  //图片放大倍数
    }
    
    /** 显示的图片 */
    @property (nonatomic, strong) UIImageView * imgView;
    
    /** 用于捏合放大与缩小的scrollView */
    @property(nonatomic,strong)UIScrollView *scrollview;
    
    @end
    
    @implementation YSShowView
    
    
    - (instancetype)initWithFrame:(CGRect)frame {
        if (self = [super initWithFrame:frame]) {
            _scaleNum = 1;
            [self createUI];
            [self addNotification];
        }
        return self;
    }
    
    - (void)createUI {
        self.backgroundColor = [UIColor blackColor];
        
        [self addSubview:self.scrollview];
        [self.scrollview addSubview:self.imgView];
        //设置UIScrollView的滚动范围和图片的真实尺寸一致
        self.scrollview.contentSize = self.imgView.frame.size;
    }
    
    - (void)addNotification {
        // 双击手势
        UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(handleDoubleTap:)];
        doubleTap.numberOfTapsRequired = 2;
        doubleTap.numberOfTouchesRequired = 1;
        [self addGestureRecognizer:doubleTap];
    }
    
    - (void)layoutSubviews {
        [super layoutSubviews];
        NSLog(@"KScreenWidth:%g -- KScreenHeight:%g",KScreenWidth,KScreenHeight);
        CGFloat imgWidth = _img.size.width;
        CGFloat imgHeight = _img.size.height;
        self.imgView.frame = CGRectMake(0, 0, KScreenWidth, KScreenWidth * imgHeight/imgWidth);
        self.imgView.center = CGPointMake(KScreenWidth*0.5, KScreenHeight*0.5);
        // 设置scrollView的frame
        self.scrollview.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
    }
    
    #pragma mark - 处理双击手势
    - (void)handleDoubleTap:(UIGestureRecognizer *)sender {
        if (_scaleNum >= 1 && _scaleNum <= 2) {
            _scaleNum++;
        }else {
            _scaleNum = 1;
        }
        [self.scrollview setZoomScale:_scaleNum animated:YES];
    }
    
    #pragma mark - UIScrollViewDelegate,告诉scrollview要缩放的是哪个子控件
    - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
        return self.imgView;
    }
    
    #pragma mark - 等比例放大,让放大的图片保持在scrollView的中央
    - (void)scrollViewDidZoom:(UIScrollView *)scrollView {
        NSLog(@"bounds:%@ -- contentSize:%@",NSStringFromCGRect(scrollView.bounds),NSStringFromCGSize(scrollView.contentSize));
        CGFloat offsetX = (self.scrollview.bounds.size.width > self.scrollview.contentSize.width)?(self.scrollview.bounds.size.width - self.scrollview.contentSize.width) *0.5 : 0.0;
        CGFloat offsetY = (self.scrollview.bounds.size.height > self.scrollview.contentSize.height)?
        (self.scrollview.bounds.size.height - self.scrollview.contentSize.height) *0.5 : 0.0;
        self.imgView.center = CGPointMake(self.scrollview.contentSize.width *0.5 + offsetX,self.scrollview.contentSize.height *0.5 + offsetY);
    }
    
    
    - (void)setImg:(UIImage *)img {
        _img = img;
        self.imgView.image = img;
        
        // 更新布局
        [self layoutSubviews];
    }
    
    #pragma mark - Lazy load
    - (UIScrollView *)scrollview {
        if (!_scrollview) {
            //添加捏合手势,放大与缩小图片
            _scrollview = [[UIScrollView alloc] initWithFrame:CGRectMake(0,0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
            //设置实现缩放
            //设置代理scrollview的代理对象
            _scrollview.delegate = self;
            //设置最大伸缩比例
            _scrollview.maximumZoomScale = 3;
            //设置最小伸缩比例
            _scrollview.minimumZoomScale = 1;
            [_scrollview setZoomScale:1 animated:NO];
            _scrollview.scrollsToTop = NO;
            _scrollview.scrollEnabled = YES;
            _scrollview.showsHorizontalScrollIndicator = NO;
            _scrollview.showsVerticalScrollIndicator = NO;
        }
        return _scrollview;
    }
    
    - (UIImageView *)imgView {
        if (!_imgView) {
            _imgView = [[UIImageView alloc] init];
            _imgView.userInteractionEnabled = true;
        }
        return _imgView;
    }
    
    
    @end
    
    @interface SLBrowseBigImgController () <UIScrollViewDelegate>
    {
        BOOL _statusBarHidden;
    }
    
    /** 图片切换的过渡效果 */
    @property (nonatomic, strong) UIScrollView * scrollView;
    
    /** 保存所有的showView */
    @property (nonatomic, strong) NSMutableArray <YSShowView *>* showViewArray;
    
    /** 显示当前是第几张的label */
    @property (nonatomic, strong) UILabel * titleLabel;
    
    @end
    
    @implementation SLBrowseBigImgController
    
    // 创建静态对象 防止外部访问
    static SLBrowseBigImgController *_instance;
    
    + (instancetype)allocWithZone:(struct _NSZone *)zone {
        //    @synchronized (self) {
        //        // 为了防止多线程同时访问对象,造成多次分配内存空间,所以要加上线程锁
        //        if (_instance == nil) {
        //            _instance = [super allocWithZone:zone];
        //        }
        //        return _instance;
        //    }
        // 也可以使用一次性代码
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            if (_instance == nil) {
                _instance = [super allocWithZone:zone];
            }
        });
        return _instance;
    }
    // 为了使实例易于外界访问 我们一般提供一个类方法
    // 类方法命名规范 share类名|default类名|类名
    + (instancetype)sharedInstance {
        //return _instance;
        // 最好用self 用Tools他的子类调用时会出现错误
        return [[self alloc]init];
    }
    // 为了严谨,也要重写copyWithZone 和 mutableCopyWithZone
    - (id)copyWithZone:(NSZone *)zone {
        return _instance;
    }
    - (id)mutableCopyWithZone:(NSZone *)zone {
        return _instance;
    }
    
    
    
    #pragma mark - 隐藏状态栏
    - (BOOL)prefersStatusBarHidden {
        return _statusBarHidden;
    }
    
    - (UIStatusBarAnimation)preferredStatusBarUpdateAnimation {
        return UIStatusBarAnimationFade;
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        // 添加导航栏右侧的btn
        self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithCustomView:[self createBtnWithAction:@selector(deleteImgEvent)]];
    }
    
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        // 关闭系统自适应的高度
        self.automaticallyAdjustsScrollViewInsets = false;
        
        // 禁用返回手势
        if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
            self.navigationController.interactivePopGestureRecognizer.enabled = false;
        }
        
        // 添加只执行一次添加tap事件
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            [self.navigationController.barHideOnTapGestureRecognizer addTarget:self action:@selector(tapAction:)];
        });
        
        // 开启当点击屏幕时,隐藏导航栏的tap事件
        self.navigationController.hidesBarsOnTap = true;
        
        // 隐藏导航栏
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self setStatusBarAndNavBar:true];
        });
    }
    
    - (void)viewWillDisappear:(BOOL)animated {
        [super viewWillDisappear:animated];
        
        // 开启返回手势
        if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
            self.navigationController.interactivePopGestureRecognizer.enabled = true;
        }
        
        // 显示导航栏的tap事件
        self.navigationController.hidesBarsOnTap = false;
    }
    
    // 让状态栏跟随导航栏一起消失
    - (void)tapAction:(UITapGestureRecognizer *)recognizer {
        
        _statusBarHidden = !_statusBarHidden;
        // 更新状态栏
        [self setNeedsStatusBarAppearanceUpdate];
    }
    
    - (void)setStatusBarAndNavBar:(BOOL)isHidden {
        if (!isHidden) {
            // 导航栏
            self.navigationController.navigationBarHidden = false;
            // 状态栏
            _statusBarHidden = false;
        }else {
            // 导航栏
            self.navigationController.navigationBarHidden = true;
            // 状态栏
            _statusBarHidden = true;
        }
        [self setNeedsStatusBarAppearanceUpdate];
    }
    
    - (void)createShowView {
        // 在scrollView上添加对应的showView
        YSShowView *lastView = nil;
        for (int i = 0; i < self.imgArrs.count; i++) {
            YSShowView *showView = [[YSShowView alloc] init];
            showView.img = [self.imgArrs objectAtIndex:i];
            [self.scrollView addSubview:showView];
            // 布局
            [self.showViewArray addObject:showView];
            [showView mas_makeConstraints:^(MASConstraintMaker *make) {
                if (i == 0) {
                    make.top.left.equalTo(self.scrollView);
                    make.size.mas_equalTo(CGSizeMake(KScreenWidth, KScreenHeight));
                }else {
                    make.top.equalTo(self.scrollView.mas_top);
                    make.left.equalTo(lastView.mas_right);
                    make.size.mas_equalTo(CGSizeMake(KScreenWidth, KScreenHeight));
                }
            }];
            lastView = showView;
        }
        
        // 设置scrollView的contentSize
        self.scrollView.contentSize = CGSizeMake(KScreenWidth * self.imgArrs.count, 0);
        // 设置scrollView的内容偏移
        [self.scrollView setContentOffset:CGPointMake(_from *KScreenWidth, 0)];
        // 设置显示第几张的title
        self.title = [NSString stringWithFormat:@"%ld / %ld",_from+1,self.imgArrs.count];
    }
    
    - (void)setImgArrs:(NSArray *)imgArrs {
        _imgArrs = imgArrs;
        // 添加底部scrollView
        if (!self.scrollView.superview) {
            // 添加左右滑动切换图骗的scrollView
            [self.view addSubview:self.scrollView];
            [self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
                make.edges.equalTo(self.view);
            }];
        }
        // 移除已存在的
        [self clearExistView];
        // 如果数据源数组为空直接返回
        if (self.imgArrs.count == 0) {
            return;
        }
        // 布局 - 在scrollView上添加对应的showView
        [self createShowView];
    }
    
    - (void)clearExistView {
        if (self.showViewArray.count == 0) {
            return;
        }
        for (YSShowView *showView in self.showViewArray) {
            if (showView.superview) {
                [showView removeFromSuperview];
            }
        }
        [self.showViewArray removeAllObjects];
    }
    
    - (void)deleteImgEvent {
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:@"要删除这张照片吗?" preferredStyle:UIAlertControllerStyleActionSheet];
        UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"删除" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {
            if (self.deleteBlock) {
                // 获取将要删除的照片
                UIImage *deleteImg = [self.imgArrs objectAtIndex:_from];
                // 删除数据源
                self.deleteBlock(deleteImg);
                // 当前的下标也需要减1
                if (--_from < 0) {
                    _from = 0;
                }
                // 删除本界面数据源
                NSMutableArray *temp = [NSMutableArray arrayWithArray:self.imgArrs];
                [temp removeObject:deleteImg];
                if (temp.count == 0) {
                    [self.navigationController popViewControllerAnimated:true];
                }else {
                    self.imgArrs = temp;
                }
            }
        }];
        UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
            
        }];
        [alert addAction:cancelAction];
        [alert addAction:okAction];
        
        [self presentViewController:alert animated:true completion:nil];
    }
    
    - (UIButton *)createBtnWithAction:(SEL)action {
        UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom];
        [btn setBackgroundColor:[UIColor clearColor]];
        [btn setImage:[UIImage imageNamed:@"delete"] forState:UIControlStateNormal];
        [btn addTarget:self action:action forControlEvents:UIControlEventTouchUpInside];
        [btn sizeToFit];
        return btn;
    }
    
    - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
        _from = scrollView.contentOffset.x / KScreenWidth;
        if (_from < self.imgArrs.count && self.imgArrs.count > 1) {
            self.title = [NSString stringWithFormat:@"%ld / %ld",_from+1,self.imgArrs.count];
        }
    }
    
    #pragma mark - Lazy load
    
    - (NSMutableArray<YSShowView *>*)showViewArray {
        if (!_showViewArray) {
            _showViewArray = [NSMutableArray array];
        }
        return _showViewArray;
    }
    
    - (UIScrollView *)scrollView {
        if (!_scrollView) {
            _scrollView = [[UIScrollView alloc] init];
            _scrollView.scrollEnabled = true;
            _scrollView.pagingEnabled = true;
            _scrollView.showsHorizontalScrollIndicator = false;
            _scrollView.showsVerticalScrollIndicator = false;
            _scrollView.delegate = self;
        }
        return _scrollView;
    }
    
    - (void)dealloc {
        NSLog(@"%s👋👋👋",__func__);
    }
    
    重点在于:

    1.UINavigationController导航控制器有一个属性hidesBarsOnTap,可以控制器导航栏的显示和隐藏,当设置为true时,点击屏幕,就会有一个带的隐藏导航栏的动画效果,再次点击时,就会显示;
    2.但是状态栏就会很傲娇的一动不动,此时就需要引入UINavigationController的另外一个属性barHideOnTapGestureRecognizer,可以给该属性添加一个tap事件,当点击屏幕时,导航栏在隐藏/显示的同时,也会触发该tap件事,就可以操作状态栏了。


    6FA512FE-0F7B-4C8E-B33F-83424F369BC6.png

    3.使用系统的方式,显示或隐藏状态栏就得重写用到两个系统方法和调用另外一个方法: [self setNeedsStatusBarAppearanceUpdate] (更新状态栏)

    #pragma mark - 隐藏状态栏
    - (BOOL)prefersStatusBarHidden {
        return _statusBarHidden;
    }
    
    - (UIStatusBarAnimation)preferredStatusBarUpdateAnimation {
        return UIStatusBarAnimationFade;
    }
    

    当调用[self setNeedsStatusBarAppearanceUpdate] 时,系统就会重调用上面两个方法,从而达到控制状态栏的隐藏和显示。

    总结:虽然最终都达到了,隐藏导航栏和状态栏的效果,屏幕使用率最大化,但是,缺点有三:

    1.状态栏和导航栏的隐藏和显示动画是不同步的,状态栏是瞬间显示和隐藏的,而导航栏可以自定义动画;
    2.需要在控制器消失之前重置状态栏和导航栏,避免影响到其他的正常界面,且容易出导航栏错乱的bug;
    3.当用到,按住最左边滑动返回前一个界面的功能时,前一个界面的导航栏会提前显示,并不会显示当前即将消失的导航栏;

    二、自定义导航栏+获取状态栏(自认完美-接近微信效果)

    先上关键代码(.m文件):

    
    #import "TestingNavBarController.h"
    
    
    #define KScreenWidth  [UIScreen mainScreen].bounds.size.width
    
    @interface TestingNavBarController ()
    {
        BOOL _isSelect;
    }
    
    /** 自定义导航栏 */
    @property (nonatomic, strong) UINavigationBar * customNavBar;
    
    /** 获取状态栏的父view */
    @property (nonatomic, weak) UIView * statusBarSuperView;
    
    /** 状态栏 */
    @property (nonatomic, weak) UIView * statusBar;
    
    @end
    
    @implementation TestingNavBarController
    
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        [self.navigationController setNavigationBarHidden:true];
    }
    
    - (void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
        
        // 将状态栏添加到自定义的导航栏上
        UIApplication *app = [UIApplication sharedApplication];
        // 获取状态栏
        self.statusBar = [app valueForKey:@"statusBar"];
        if (self.statusBar) {
            self.statusBarSuperView = self.statusBar.superview;
            [self.customNavBar addSubview:self.statusBar];
            // 获取状态栏的子view
            NSArray *subViews = [[self.statusBar valueForKey:@"foregroundView"] subviews];
            for (UIView *view in subViews) {
                NSLog(@"====%@",view);
            }
        }
    }
    
    - (void)viewWillDisappear:(BOOL)animated {
        [super viewWillDisappear:animated];
        [self.navigationController setNavigationBarHidden:false];
        
        [self.statusBarSuperView addSubview:self.statusBar];
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        [self.view addSubview:self.customNavBar];
        
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapClickEvent)];
        [self.view addGestureRecognizer:tap];
    }
    
    - (UINavigationBar *)customNavBar {
        if (!_customNavBar) {
            _customNavBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, KScreenWidth, 64)];
            _customNavBar.backgroundColor = [UIColor lightGrayColor];
            // 自定义导航栏的title,用UILabel实现
            UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 50, 44)];
            titleLabel.text = @"自定义";
            titleLabel.textColor = [UIColor blackColor];
            titleLabel.font = [UIFont systemFontOfSize:18];
            
            // 创建导航栏组件
            UINavigationItem *navItem = [[UINavigationItem alloc] init];
            // 设置自定义的title
            navItem.titleView = titleLabel;
            
            // 创建左侧按钮
            UIBarButtonItem *leftButton = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:self action:@selector(leftButtonClick)];
            leftButton.tintColor = [UIColor purpleColor];
            
            // 创建右侧按钮
            UIBarButtonItem *rightButton = [[UIBarButtonItem alloc] initWithTitle:@"确定" style:UIBarButtonItemStylePlain target:self action:@selector(rightButtonClick)];
            rightButton.tintColor = [UIColor orangeColor];
            
            // 添加左侧、右侧按钮
            [navItem setLeftBarButtonItem:leftButton animated:false];
            [navItem setRightBarButtonItem:rightButton animated:false];
            // 把导航栏组件加入导航栏
            [_customNavBar pushNavigationItem:navItem animated:false];
        }
        return _customNavBar;
    }
    
    - (void)leftButtonClick {
        [self.navigationController popViewControllerAnimated:true];
    }
    
    - (void)rightButtonClick {
    
    }
    
    - (void)tapClickEvent {
        _isSelect = !_isSelect;
        if (_isSelect) {
            [UIView animateWithDuration:0.5 animations:^{
                CGRect frame = self.customNavBar.frame;
                frame.origin.y = -64;
                self.customNavBar.frame = frame;
            }];
        }else {
            [UIView animateWithDuration:0.5 animations:^{
                CGRect frame = self.customNavBar.frame;
                frame.origin.y = 0;
                self.customNavBar.frame = frame;
            }];
        }
    }
    
    重点在于:

    获取状态栏:self.statusBar = [[UIApplication sharedApplication] valueForKey:@"statusBar"],然后添加到自定义的导航栏上,就可以实现整体的隐藏/显示了,此时有两点切记:
    1.获取并添加状态栏到自定义的导航栏的时机,必须是,在视图加载完后添加,否则在push到该界面时,会看到前一个界面的状态栏消失了,而且,状态栏的显示宽度会随控制器的宽度渐渐显示;

    2.在pop该控制器时,需要将状态栏还回原来的父view,因为状态栏是一个单列的对象,所有的界面共用的,且必须在视图将要消失时,还回去(有借有还,再借不难!!) F74E776F-9C83-475D-A292-171D368B93BB.png
    总结:该方法较第一种方式,需要手动控制的对象和对象属性比较少,而且,目前没有bug;用该方法替换掉第一种方式,就是一个浏览全图的功能了。。。

    相关文章

      网友评论

      • 散夜:最近刚好遇到类似的问题,博主有demo详细学习么?

      本文标题:iOS - 获取StatusBar(仿微信发朋友圈时,浏览全图时

      本文链接:https://www.haomeiwen.com/subject/dvhdoxtx.html