项目开发过程中,我们在有些页面展示之前,需要一些页面效果来过渡。例如:网络数据加载出来之前,页面的展示;复杂的业务逻辑处理完成之前的页面显示;上传文件未完成之前的页面样式等等。当然,需求不同,其展示样式也就千变万化。今天来实现一下上图所示的功能。
本篇文章,只作为个人学习使用。
具体实现代码如下:
创建UIView的category
UIView+Animated.h文件
#import <UIKit/UIKit.h>
typedef enum {
TABViewLoadAnimationDefault = 0, //默认没有动画
TABViewLoadAnimationShort, //动画先变短再变长
TABViewLoadAnimationLong //动画先变长再变短
}TABViewLoadAnimationStyle; //view动画类型枚举
@interface UIView (Animated)
@property (nonatomic) TABViewLoadAnimationStyle loadStyle;
@end
UIView+Animated.m文件
#import "UIView+Animated.h"
#import <objc/runtime.h>
@implementation UIView (Animated)
- (TABViewLoadAnimationStyle)loadStyle {
NSNumber *value = objc_getAssociatedObject(self, @selector(loadStyle));
return value.intValue;
}
- (void)setLoadStyle:(TABViewLoadAnimationStyle)loadStyle {
objc_setAssociatedObject(self, @selector(loadStyle), @(loadStyle), OBJC_ASSOCIATION_ASSIGN);
}
@end
创建UITableView的category
UITableView+Animated.h文件
#import <UIKit/UIKit.h>
typedef enum {
TABTableViewAnimationDefault = 0, //没有动画,默认
TABTableViewAnimationStart, //开始动画
TABTableViewAnimationEnd //结束动画
}TABTableViewAnimationStyle; //table动画状态枚举
@interface UITableView (Animated)
@property (nonatomic) TABTableViewAnimationStyle animatedStyle;
@end
UITableView+Animated.m文件
#import "UITableView+Animated.h"
#import <objc/runtime.h>
@implementation UITableView (Animated)
+ (void)load {
//Ensure that the exchange method executed only once.
//保证交换方法只执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//Gets the viewDidLoad method to the class,whose type is a pointer to a objc_method structure.
//获取到这个类的viewDidLoad方法,它的类型是一个objc_method结构体的指针
Method originMethod = class_getInstanceMethod([self class], @selector(setDelegate:));
//Get the method you created.
//获取自己创建的方法
Method newMethod = class_getInstanceMethod([self class], @selector(tab_setDelegate:));
IMP newIMP = method_getImplementation(newMethod);
BOOL isAdd = class_addMethod([self class], @selector(tab_setDelegate:), newIMP, method_getTypeEncoding(newMethod));
if (isAdd) {
class_replaceMethod([self class], @selector(setDelegate:), newIMP, method_getTypeEncoding(newMethod));
}else {
//exchange
method_exchangeImplementations(originMethod, newMethod);
}
});
}
- (void)tab_setDelegate:(id<UITableViewDelegate>)delegate {
//Ensure that the exchange method executed only once.
//保证交换方法只执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL oldSelector = @selector(tableView:numberOfRowsInSection:);
SEL newSelector = @selector(tab_tableView:numberOfRowsInSection:);
Method oldMethod_del = class_getInstanceMethod([delegate class], oldSelector);
Method oldMethod_self = class_getInstanceMethod([self class], oldSelector);
Method newMethod = class_getInstanceMethod([self class], newSelector);
// 若未实现代理方法,则先添加代理方法
BOOL isSuccess = class_addMethod([delegate class], oldSelector, class_getMethodImplementation([self class], newSelector), method_getTypeEncoding(newMethod));
if (isSuccess) {
class_replaceMethod([delegate class], newSelector, class_getMethodImplementation([self class], oldSelector), method_getTypeEncoding(oldMethod_self));
} else {
// 若已实现代理方法,则添加 hook 方法并进行交换
BOOL isVictory = class_addMethod([delegate class], newSelector, class_getMethodImplementation([delegate class], oldSelector), method_getTypeEncoding(oldMethod_del));
if (isVictory) {
class_replaceMethod([delegate class], oldSelector, class_getMethodImplementation([self class], newSelector), method_getTypeEncoding(newMethod));
}
}
});
[self tab_setDelegate:delegate];
}
#pragma mark - TABTableViewDelegate
- (NSInteger)tab_tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (tableView.animatedStyle == TABTableViewAnimationStart) {
return 5;
}
return [self tab_tableView:tableView numberOfRowsInSection:section];
}
#pragma mark - Getter / Setter
- (TABTableViewAnimationStyle)animatedStyle {
NSNumber *value = objc_getAssociatedObject(self, @selector(animatedStyle));
//动画过程中设置为不可滚动,暂时不要修改,滚动会造成一定的问题
if (value.intValue == 1) {
self.scrollEnabled = NO;
}else {
self.scrollEnabled = YES;
}
return value.intValue;
}
- (void)setAnimatedStyle:(TABTableViewAnimationStyle)animatedStyle {
objc_setAssociatedObject(self, @selector(animatedStyle), @(animatedStyle), OBJC_ASSOCIATION_ASSIGN);
}
@end
创建LGJViewAnimated 继承自NSObject
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "UIView+Animated.h"
@interface LGJViewAnimated : NSObject
+ (void)startOrEndAnimated:(UITableViewCell *)cell;
/**
加载CALayer,设置动画,同时启动
@param view 需要动画的view
@param color 动画颜色
*/
+ (void)initLayerWithView:(UIView *)view withColor:(UIColor *)color;
/**
根据动画类型设置对应基础动画
@param style 动画类型
@return 动画
*/
+ (LGJViewAnimated *)scaleXAnimation:(TABViewLoadAnimationStyle)style;
@end
#import "LGJViewAnimated.h"
#import "TABMethod.h"
#import "UITableView+Animated.h"
@implementation LGJViewAnimated
+ (void)startOrEndAnimated:(UITableViewCell *)cell {
UITableView *superView = (UITableView *)cell.superview;
switch (superView.animatedStyle) {
case TABTableViewAnimationStart:
//添加并开启动画
for (int i = 0; i < cell.contentView.subviews.count; i++) {
UIView *v = cell.contentView.subviews[i];
if ( v.loadStyle != TABViewLoadAnimationDefault ) {
[TABViewAnimated initLayerWithView:v withColor:kBackColor];
}
}
break;
case TABTableViewAnimationEnd:
if ( cell.contentView.subviews.count > 0 ) {
//移除动画图层
for (int i = 0; i < cell.contentView.subviews.count; i++) {
UIView *v = cell.contentView.subviews[i];
if ( v.layer.sublayers.count > 0 ) {
NSArray<CALayer *> *subLayers = v.layer.sublayers;
NSArray<CALayer *> *removedLayers = [subLayers filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
if ([evaluatedObject isKindOfClass:[CALayer class]]) {
//找出CALayer是你需要移除的,这里根据背景色来判断的
CALayer *layer = (CALayer *)evaluatedObject;
if (CGColorEqualToColor(layer.backgroundColor,kBackColor.CGColor)) {
return YES;
}
return NO;
}
return NO;
}]];
[removedLayers enumerateObjectsUsingBlock:^(CALayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[obj removeFromSuperlayer];
}];
}
}
}
break;
default:
break;
}
}
+ (void)initLayerWithView:(UIView *)view withColor:(UIColor *)color {
CALayer *layer = [[CALayer alloc]init];
layer.frame = CGRectMake(0, 0, view.frame.size.width, view.frame.size.height);
layer.backgroundColor = kBackColor.CGColor;
layer.anchorPoint = CGPointMake(0, 0);
layer.position = CGPointMake(0, 0);
// 添加一个基本动画
[layer addAnimation:[self scaleXAnimation:view.loadStyle] forKey:@"scaleAnimation"];
[view.layer addSublayer:layer];
}
+ (CABasicAnimation *)scaleXAnimation:(TABViewLoadAnimationStyle)style {
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform.scale.x"];
anim.removedOnCompletion = NO;
anim.duration = 0.4;
anim.autoreverses = YES; //往返都有动画
anim.repeatCount = MAXFLOAT; //执行次数
switch (style) {
case TABViewLoadAnimationShort:{
anim.toValue = @0.6;
}
break;
case TABViewLoadAnimationLong:{
anim.toValue = @1.6;
}
break;
default:{
anim.toValue = @0.6;
}
}
return anim;
}
@end
以上就是具体实现工作过程,那么怎么用呢?很简单,我们只需要在需要此功能列表的cell 的layoutSubviews方法的最后加入代码
//运行动画/移除动画
[TABViewAnimated startOrEndAnimated:self];
在需要开始动画的时刻执行
_mainTV.animatedStyle = TABTableViewAnimationStart; //开启动画
在需要结束动画的时刻执行
//停止动画
_mainTV.animatedStyle = TABTableViewAnimationEnd;
即可。
另外,在动画页面显示的时候尽量不要做给cell赋值的操作
//在加载动画的时候,即未获得数据时,不要走加载控件数据的方法
if (_mainTV.animatedStyle != TABTableViewAnimationStart) {
[cell initWithData:dataArray[indexPath.row]];
}