今天系统的总结下项目中遇到的问题以及比较好的点,也有利于以后问题的避免和解决。
一、GIF展示。
使用第三方YYIamge来处理GIF的展示,原本想着使用SDWebImage来处理这个问题,但是碰了很多壁这里就不多说了,试来试去还是YYAnimatedImageView用着最方便简介,所以就直接介绍YYAnimatedImageView的使用实例了。
1、首先在cell的.h文件中导入头文件
#import "YYAnimatedImageView.h"
#import "YYWebImage.h"
2、设置ImageView.
@property (nonatomic,retain) YYAnimatedImageView *CellImage;
3、在.m文件中添加处理。先初始化,然后在setUpModel方法中赋值。这里用的是典型的MVC模式。
self.CellImage = [[YYAnimatedImageView alloc]initWithFrame:CGRectMake(0, 0, (ZHWidth-60)/3, (ZHWidth-60)/3)];
if ([model.pkgImg rangeOfString:@"gif"].location !=NSNotFound) {
[self.CellImage yy_setImageWithURL:url options:YYWebImageOptionProgressiveBlur |YYWebImageOptionShowNetworkActivity];
}
else {
[self.CellImage yy_setImageWithURL:url options:YYWebImageOptionProgressiveBlur|YYWebImageOptionSetImageWithFadeAnimation];
}
二、绘本翻页
翻页的效果比较复杂,为了找到一个合适的第三方也是花费了很多时间寻找和调试,最后终于实现了功能。这里有几个注意点:
1、mpflipviewcontroller原项目使用的是上下翻页的效果,我们需要修改一下翻页的方向。在代理中修改反转的方向类型为4
- (MPFlipViewControllerOrientation)flipViewController:(MPFlipViewController *)flipViewController orientationForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
// return MPFlipViewControllerOrientationVertical;
orientation = 4;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
return UIInterfaceOrientationIsPortrait(orientation)? MPFlipViewControllerOrientationVertical : MPFlipViewControllerOrientationHorizontal;
else
return MPFlipViewControllerOrientationHorizontal;
}
这里有一点还没与解决,我在iPhone上面可以横屏,但是在iPad上面无法横屏,还有有些奇怪。待解决......
2、由于翻页的页面布局不同,所有我们需要设置不同的XIb界面进行加载。
mpflipviewcontroller
三、网络封装
1、设置VM,封装网络请求。
//banner 的跳转接口
+(void)latestAudioBoosWithID:(NSNumber *)pkgID callback:(InterfaceManagerBlock)completion;
// 3.19日 新的免费故事&会员专区
+(void)queryAllAudioBookPkg:(NSString *)free pageIndex:(NSNumber *)index callback:(InterfaceManagerBlock)completion;
+(void)queryAllAudioBookPkg:(NSString *)free pageIndex:(NSNumber *)index callback:(InterfaceManagerBlock)completion {
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
SetObjForDic(dic, free, @"subsReqd");
SetObjForDic(dic, @8, @"pageSize");
SetObjForDic(dic, index, @"pageIndex");
[InterfaceManager startRequest:API_AUDIOBOOK_queryLatestAudioBookList
describe:@" "
body:dic
returnClass:[PkgListModel class]
completion:^(BOOL isSucceed, NSString *message, ResultModel *result)
{
if (completion) {
completion(isSucceed, message, result.dataList);
}
}];
}
2、设置InterfaceManager网络请求的管理类,封装GET、POST。
/** 统一接口请求 Post请求*/
+ (NSURLSessionDataTask *)startRequest:(NSString *)action
describe:(NSString *)describe
body:(NSDictionary *)body
returnClass:(Class)returnClass
completion:(InterfaceManagerBlock)completion
{
return [WebService startRequest:action
body:body
returnClass:returnClass
success:^(NSURLSessionTask *task, ResultModel *result)
{
[self succeedWithResult:result describe:describe callback:^(BOOL isSucceed, NSString *message, id data) {
completion(isSucceed,message,data);
}];
} failure:^(NSURLSessionTask *task, NSError *error)
{
LogError(@"%@", error);
completion(NO, @"request failed", nil);
}];
}
3、在WebService类中分别处理传递过来的数据,进行网络请求,然后返回数据。
#pragma 发起POST请求
+ (NSURLSessionDataTask *)startRequest:(NSString *)action
body:(NSDictionary *)body
returnClass:(Class)returnClass
success:(RequestSuccessBlock)sblock
failure:(RequestFailureBlock)fblock
{
NSString *url = [WebService pathUrl:action];
AFHTTPSessionManager* manager = [WebService sessoionConfigration:[url hasPrefix:@"https://"]];
return [manager POST:url
parameters:body
progress:^(NSProgress *uploadProgress){}
success:^(NSURLSessionTask *task, id responseObject) {
NSString *responseStr = [WebService jsonStrFromTask:task
response:responseObject
responseSerializer:manager.responseSerializer];
[WebService callbackWithResponse:responseStr
returnClass:returnClass
task:task
success:sblock
failure:fblock];
} failure:^(NSURLSessionTask *task, NSError *error) {
// 请求失败
if (fblock) {
fblock(task, error);
}
}];
}
4、最重要的一点就是字典转model。
先说一下思路,在请求到数据之后,使用相应的model进行转化,将对应的参数分别转换成model,之后再将model返回,在主界面中使用。由于涉及的代码比较多,我们只截取关键的代码展示即可。
ResultModel *result = [WebService getResultWithString:responseStr
returnClass:returnClass
andError:&err];
getResultWithString:responseString方法中转化model:
ResultModel *aRespond = [[ResultModel alloc] initWithString:aString error:nil];
if (returnClass) {
if([aRespond.data isKindOfClass:[NSDictionary class]]){
NSError *error = nil;
//1、json转化成类 ResultModel继承了jsonModel.
aRespond.data = [[returnClass alloc] initWithDictionary:(NSDictionary *)aRespond.data error:&error];
if (error) {
aRespond.resultMsg = @"Parse the JSON data failed";
}else{
return aRespond;
}
}
四、横竖屏展示
1、进入页面的时候在viewWillAppear中调用如下方法强制横屏,也可以设置屏幕的旋转方向。
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
//隐藏NavigationBar,否则横屏上面会有导航栏。
[self.navigationController setNavigationBarHidden:YES animated:animated];
//禁止本界面左滑返回
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
NSNumber *orientationUnknown = [NSNumber numberWithInt:UIInterfaceOrientationUnknown];
[[UIDevice currentDevice] setValue:orientationUnknown forKey:@"orientation"];
NSNumber *orientationTarget = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeLeft];
[[UIDevice currentDevice] setValue:orientationTarget forKey:@"orientation"];
}
2、在离开界面的时候也要取消界面的横屏。
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
//解除隐藏NavigationBar,否则横屏上面会有导航栏。
[self.navigationController setNavigationBarHidden:NO animated:animated];
//解除本界面左滑返回
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
AppDelegate * appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
appDelegate.allowRotation = NO;//关闭横屏仅允许竖屏
//强制归正: 必须添加否则还是横屏。
if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {
SEL selector = NSSelectorFromString(@"setOrientation:");
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];
[invocation setSelector:selector];
[invocation setTarget:[UIDevice currentDevice]];
int val =UIInterfaceOrientationPortrait;
[invocation setArgument:&val atIndex:2];
[invocation invoke];
}
}
3、还有另外一种方式
3.1在AppDelegate.m中设置:
@property(nonatomic,assign) BOOL allowRotation;
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window{
if (self.allowRotation) {
return UIInterfaceOrientationMaskLandscapeLeft;
}
return UIInterfaceOrientationMaskPortrait;
}
3.2 在需要的类中设置
AppDelegate * appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
appDelegate.allowRotation = YES;//(以上2行代码,可以理解为打开横屏开关)
离开的时候:
AppDelegate * appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
appDelegate.allowRotation = NO;//(以上2行代码,可以理解为打开横屏开关)
五、浸入式编程(自定义透明导航栏)
主要思想是隐藏导航栏,然后自定义一个View,然后在View上面添加各种控件并添加相应的操作。
- (void)setupMyNav{
float titleHeight = 0;
float xheight = 22;
self.myNaviView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, ZHWidth, titleHeight)];
self.myNaviView.backgroundColor = [UIColor whiteColor];
self.myNaviView.alpha = 0;
[self.view addSubview:self.myNaviView];
[self.view bringSubviewToFront:self.myNaviView];
UIView *view = [[UIView alloc] init];
view.frame = CGRectMake(10,28,261,29);
UIView *lineView = [[UIView alloc]initWithFrame:CGRectMake(0, titleHeight-0.5, ZHWidth, 0.5)];
lineView.backgroundColor = [UIColor grayColor];
[self.myNaviView addSubview:lineView];
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
// button.size = CGSizeMake(50, 30);
button.frame = CGRectMake(16, xheight, 90, 40);
[button setImage:[UIImage imageNamed:@"left_search.png"] forState:UIControlStateNormal];
// 让按钮内部的所有内容左对齐
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
// 让按钮的内容往左边偏移10
// button.contentEdgeInsets = UIEdgeInsetsMake(0, -15, 0, 0);
[button addTarget:self action:@selector(moveToSearchView) forControlEvents:UIControlEventTouchUpInside];
[self.myNaviView addSubview:button];
UILabel *titleLabel = [[UILabel alloc]initWithFrame:CGRectMake((ZHWidth-80)/2, xheight, 80, 40)];
titleLabel.text = @"推荐";
titleLabel.textAlignment = NSTextAlignmentCenter;
titleLabel.font = [UIFont systemFontOfSize:20];
[self.myNaviView addSubview:titleLabel];
[self setUpRightBarItem];
}
六、用到的动画
1、音频播放的动画。
- (void)setUpRightBarItem{
UIImage *image1 = [UIImage imageNamed:@"cm2_list_icn_loading1"];
UIImage *image2 = [UIImage imageNamed:@"cm2_list_icn_loading2"];
UIImage *image3 = [UIImage imageNamed:@"cm2_list_icn_loading3"];
UIImage *image4 = [UIImage imageNamed:@"cm2_list_icn_loading4"];
NSArray *array = @[image1,image2,image3,image4,image3,image2];
float high = 0;
if ([[UIScreen mainScreen] bounds].size.height >= 812.0f) {
high = 5;
}
self.imageView = [[UIImageView alloc]initWithFrame:CGRectMake(ZHWidth-35, 25+high, 30, 30)];
self.imageView.userInteractionEnabled = YES;
//先设置一个固定的图片
self.imageView.image =[UIImage imageNamed:@"cm2_list_icn_loading1"];
[self.myNaviView addSubview:self.imageView];
// 创建装图片的数组
self.imageView.animationImages = array; // 装图片的数组(需要做动画的图片数组)
self.imageView.animationDuration = 1; // 动画时间
self.imageView.animationRepeatCount = 0; // 重复次数 0 表示重复
[self.imageView stopAnimating];
//添加手势进行控制,用来跳转界面。
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(storyPlayView)];
[self.imageView addGestureRecognizer:tap];
}
- (void)startBarAnimating{
[self.imageView startAnimating];
}
- (void)stopBarAnimating{
[self.imageView stopAnimating];
}
2、碟片旋转的动画。
- (void)beginRotation
{
[self.iconImageView.layer removeAnimationForKey:@"rotation"];
CABasicAnimation *animation = [[CABasicAnimation alloc] init];
animation.fromValue = @(0);
animation.toValue = @(M_PI * 2);
//旋转的时间
animation.duration = 30;
//绕着Z轴旋转
animation.keyPath = @"transform.rotation.z";
animation.repeatCount = NSIntegerMax;
animation.removedOnCompletion = NO;
[self.iconImageView.layer addAnimation:animation forKey:@"rotation"];
}
3、页面反转的动画。
3.1 立方体旋转
AudioBookPlayViewController *vc = [AudioBookPlayViewController allocController];
vc.model = pkgModel;
[AudioBookPkgModel setPkg:pkgModel];
CATransition* transition = [CATransition animation];
transition.duration = 0.5f;
transition.type = @"cube";
transition.subtype = kCATransitionFromRight;
[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
[self.navigationController pushViewController:vc animated:YES];
3.2 优秀的第三方页面翻转VCTransitionsLibrary
丰富的动画第三方VCTransitionsLibrary
七、自定义弹框
这里说以下两点:
1、在init的时候会调用layoutSubviews方法,可以在此设置UI。
2、设置抖动动画,以及手势移除。
初始化的时候
界面下落到底部,然后再移除出父视图。
- (void)removeView{
[UIView animateWithDuration:0.25 animations:^{
[self.whiteView setFrame:CGRectMake((Screen_Width-300)/2, Screen_Height, 300, 200)];
} completion:^(BOOL finished) {
[self.backView removeFromSuperview];
[self removeFromSuperview];
}];
}
八、Xib布局界面(真的很便捷,一般界面推荐使用哦)
问题1:
主要是主界面的布局,我们使用scrollview添加到界面中,然后在scrollview上添加各种布局,这时候你会发先会报错,说适配有问题。
原因:这是因为scrollview没填充contentSize导致的,所以我们需要手动添加一个UIView来实现这个功能,让view填充整个界面,这时还是会报适配的错误,我们需要再选中view和scrollview,然后设置等宽登高,这样就解决了适配错误。
问题2:
之后在view上添加各种控件,添加完毕之后,发现无法滑动。
原因:需要在scrollview设置滑动的方向,我们选择vertical。
问题3:
即使设置完成之后,我们还是无法滑动,所以我们需要手动添加代码设置,才能实现滑动。
由于xib的延迟加载,我们需要在滞后一点才能加载,不能放到viewDidLoad中哦。
-(void) viewDidAppear:(BOOL)animated{
self.scrollview.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 1024);
//Xib无法设置setContentSize,所以我们需要手动设置。
[self.scrollview setContentSize:CGSizeMake(320, 1400)];
}
九、存储、解归档
1、存储
[NSUserDefault setObject:@1 forKey:@"isMembership"];
[NSUserDefault synchronize];
NSUserDefault作为极为常见的一种简单存储方式,我们这类就不细说了,只要记住一点,存储必须是对象类型。
2、解归档
这里我们先说一下应用的场景,我们需要存储model对象,但是model不能直接存储在NSUserDefault中,必须进行归档处理,然后才能存储。
NScoding 是一个协议,主要有下面两个方法
-(id)initWithCoder:(NSCoder *)coder;//从coder中读取数据,保存到相应的变量中,即反序列化数据
-(void)encodeWithCoder:(NSCoder *)coder;// 读取实例变量,并把这些数据写到coder中去。序列化数据
NSCoder 是一个抽象类,抽象类不能被实例话,只能提供一些想让子类继承的方法。
NSKeyedUnarchiver 从二进制流读取对象。
NSKeyedArchiver 把对象写到二进制流中去。
2.1、解归档必须实现coding协议,所以我们必须先在model中添加NSCoding设置。
@interface AudioBookPkgModel : JSONModel<NSCoding>
2.2、在.m文件中成对实现以下两个方法
//告诉系统归档的属性是那些
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:self.pkgImg forKey:@"pkgImg"];
[aCoder encodeObject:self.bookSubtitle forKey:@"bookSubtitle"];
[aCoder encodeObject:self.pkgType forKey:@"pkgType"];
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeObject:self.nameEng forKey:@"nameEng"];
}
//告诉系统接档的属性是那些
- (id)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
self.pkgImg = [aDecoder decodeObjectForKey:@"pkgImg"];
self.bookSubtitle = [aDecoder decodeObjectForKey:@"bookSubtitle"];
self.pkgType = [aDecoder decodeObjectForKey:@"pkgType"];
self.name = [aDecoder decodeObjectForKey:@"name"];
self.nameEng = [aDecoder decodeObjectForKey:@"nameEng"];
}
2.3、归档存储 (archiver :压缩存储 )
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.model];
[NSUserDefault setObject:data forKey:@"oneStudent"];
[NSUserDefault synchronize];
2.4、解档
NSData *data1 = [NSUserDefault objectForKey:@"oneStudent"];
AudioBookPkgModel *model = [NSKeyedUnarchiver unarchiveObjectWithData:data1];
十、数据的传递
数据传递是我们开发过程中常用的功能,下面我们简要的介绍下项目用到的数据传递的方法。
1、代理
1.1 代理应用的场景是监控持续性的连续操作,比如TableView连续拖动的代理。
@protocol TopicCollectionViewCell3Delegate <NSObject>
//设置代理用于传递相应的数据
- (void)TopBackAnswer:(NSMutableArray *)dataArr;
@end
@property (nonatomic,weak) id<TopicCollectionViewCell3Delegate> delegate;
1.2 在VC中设置
#pragma mark Top3的代理,用户判断返回的数据。
- (void)TopBackAnswer:(NSMutableArray *)dataArr{
}
2、block
2.1 在.h中设置block,注意要用copy,思考下为什么要用copy?
@property (copy,nonatomic) void (^moreActionBlock)(long tag , int rightIndex,CGPoint CGPointMake);
2.2 在.m中,调用block传值。
- (IBAction)buttonB:(id)sender {
UIButton *but = sender;
if (self.moreActionBlock) {
CGPoint point = CGPointMake(but.frame.origin.x+85, but.frame.origin.y+18);
self.moreActionBlock(but.tag,2,point);
}
}
2.3 在VC中接收block的传值.
topCell.moreActionBlock = ^(long tag, int rightIndex, CGPoint CGPointMake) {
self.startPoint = CGPointMake;
self.tagInfo = tag;//标记tag值。
};
3、通知
在A页面中:
NSDictionary *dataDic = [NSDictionary dictionaryWithObject:@1 forKey:@"info"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"infoNotification" object:nil userInfo:dataDic];
在B页面中:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification:) name:@"infoNotification" object:nil];
}
-(void)receiveNotification:(NSNotification *)infoNotification {
NSDictionary *dic = [infoNotification userInfo];
NSString *str = [dic objectForKey:@"info"];
}
4、属性传递
属性的数据传递主要用于不同界面的传值,比如A界面请求的数据model要传递到B界面,我们就需要在B界面设置全局的model,用于接收A界面的数据,B.model = AModel;就像这样。
5、存储
存储在上面已经介绍过了,这里就不再细说了,主要用于用户的状态存储、对象的存储等。
十一、GCD的应用
在推荐页面中,我们设置了五个板块,然后需要有五个请求,且这五个数据都是按照顺序加载的,由于网络请求的异步性,我们怎么才能让数据按照顺序展示呢?这里就用到GCD处理多线程的同步操作,让子线程全部完成之后,我们再统一处理数据。
dispatch_group_enter:通知group,下面的任务马上要放到group中执行了。
dispatch_group_leave:通知group,任务完成了,该任务要从group中移除了。
dispatch_group_notify:所有的子线程完成网络请求之后,在此处理所有的数据。
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//获取全部绘本的内容
[AudioBookVM pictureBookHeader:^(BOOL isSucceed, NSString *message, id result) {
self.arrDic = result;
dispatch_group_leave(group);
}];
});
// 19、免费故事接口
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[AudioBookVM queryAudioBookPkgByFreeConditionCallback:^(BOOL isSucceed, NSString *message, id result) {
NSDictionary *dic = result;
dispatch_group_leave(group);
}];
});
// 20、精品推荐&&最新上线 "tag":"NEWPKG,BEST",
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[AudioBookVM queryAudioBookPkgFindAllCallback:^(BOOL isSucceed, NSString *message, id result) {
//都要转换成[DataListModel class]
NSArray *arr = result;
patch_group_leave(group);
}];
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[self.allCollection reloadData];
});
十一、TableView或者CollectionView下拉顶部图片放大
一开始我的思路还固定在区头放大,但事实并非如此,我们只需要开辟滚动区间,在区间里面放上图片,然后再处理滚动即可,代码如下:
1、首先初始化tableView,然后用contentInset增加表的滚动区域,再设置UIImageView放到表上。
self.tableView = [[UITableView alloc] initWithFrame:[UIScreen mainScreen].bounds style:UITableViewStylePlain];
[self.view addSubview:_tableView];
_tableView.delegate = self;
_tableView.dataSource = self;
//上左下右 设置向上偏移 tableview向下偏移了200个单位 contentInset:增加_tableView的滚动区域
_tableView.contentInset = UIEdgeInsetsMake(200,0, 0, 0);
_tableView.showsHorizontalScrollIndicator = NO;
_tableView.showsVerticalScrollIndicator = NO;
_topImageView = [[UIImageView alloc] init];
_topImageView.frame = CGRectMake(0, -200, [UIScreen mainScreen].bounds.size.width, 200);
_topImageView.contentMode = UIViewContentModeScaleAspectFill;
_topImageView.image = [UIImage imageNamed:@"APP icon.png"];
_topImageView.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth;
_topImageView.clipsToBounds = YES;
_topImageView.tag = 101;
[_tableView addSubview:_topImageView];
2、代理监控表的滑动,改变Frame。
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGPoint point = scrollView.contentOffset;
if (point.y < -200) {
CGRect rect = [self.tableView viewWithTag:101].frame;
rect.origin.y = point.y;
rect.size.height = -point.y;
[self.tableView viewWithTag:101].frame = rect;
}
}
十一、编程思想MVC&MVVM
网友评论