iOS技术点iOS动画效果

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

2018-01-15  本文已影响103人  吊吊的plus
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;用该方法替换掉第一种方式,就是一个浏览全图的功能了。。。
上一篇下一篇

猜你喜欢

热点阅读