iOS开发UIScrollView的悬停以及下拉缩放效果
一:在实际开发中常常涉及UIScrollView或是tableView的悬停以及缩放效果,如图:向上滚动视图时,红色的view一起随蓝色视图一起滚动,当到达顶端时,红色View留在顶部,蓝色view可继续向上滚动。当向下滚动蓝色视图时,红色视图随蓝色视图一起滚动下来。向上向下拉动蓝色的视图,则图中的图片有缩放的效果


二:代码
1 #import "ViewController.h" 2 3 @interface ViewController () <UIScrollViewDelegate> 4 @property (weak, nonatomic) IBOutlet UIView *redView; 5 @property (weak, nonatomic) IBOutlet UIImageView *imageView; 6 @property (weak, nonatomic) IBOutlet UIView *blueView; 7 @property (weak, nonatomic) IBOutlet UIScrollView *scrollView; 8 @end 9 10 @implementation ViewController 11 12 - (void)viewDidLoad { 13 [super viewDidLoad]; 14 15 16 //1:设置scrollView的contentSize 17 self.scrollView.contentSize = CGSizeMake(0, CGRectGetMaxY(self.blueView.frame)); 18 } 19 20 #pragma mark - <UIScrollViewDelegate> 21 - (void)scrollViewDidScroll:(UIScrollView *)scrollView 22 { 23 //2:实时监听scrollView的滚动 24 25 /* 26 1:scrollView向上滚动时,scrollView的偏移量contentoffset 值为正数,向下滚动时,scrollView的偏移量contentoffset为负数,当scrollView向上滚动,偏移量为图片的高度时,让红色view悬停 27 2:如何让红色view悬停:改变红色view的frame,CGRect 和 CGPoint都是结构体类型,不能直接更改,先要取出,更改,再赋值。红色view默认是添加在scrollView上的,改变其frame,将红色view添加到self.view上,红色view就不会随着scrollView一起滚动了。添加到self.view上时,不用从scrollView上移除,调用addSubview就默认将红色view从scrollView上移除添加到self.view上 28 3:当偏移量小于图片高度时,再更改红色view的frame,将红色view添加到scrollView 29 30 4:缩放效果:监听scrollView的实时滚动,当scrollView偏移量小于0,也就是向下拉时,才会产生缩放效果,缩放效果用transform来实现,缩放比例scale的计算:默认scale为1,scale = 1 - 偏移量/一定数值。因为偏移量为负数,所以scale大于1,会产生缩放效果。transform的缩放效果,默认会从其中心点按比例进行缩放 31 32 */ 33 34 35 36 //1:悬停效果 37 CGFloat imageH = self.imageView.frame.size.height; 38 CGFloat offsetY = scrollView.contentOffset.y; 39 if (offsetY >= imageH) { 40 // 将红色控件添加到控制器的view中,设置Y值为0 41 CGRect redF = self.redView.frame; 42 redF.origin.y = 0; 43 self.redView.frame = redF; 44 [self.view addSubview:self.redView]; 45 } else { 46 // 将红色控件添加到scrollView中,设置Y值为图片的高度 47 CGRect redF = self.redView.frame; 48 redF.origin.y = 140; 49 self.redView.frame = redF; 50 [self.scrollView addSubview:self.redView]; 51 } 52 53 //2:下拉的缩放效果:两种判断方式 54 55 // if (offsetY < 0) { 56 // CGFloat scale = 1 - (offsetY / 70); 57 // self.imageView.transform = CGAffineTransformMakeScale(scale, scale); 58 // } 59 60 CGFloat scale = 1 - (offsetY / 70); 61 scale = (scale >= 1) ? scale : 1; 62 self.imageView.transform = CGAffineTransformMakeScale(scale, scale); 63 } 64 65 @end
三:知识点总结
1:封装思想:1:对于控制器或是view的UI来说要想到分层封装的思想:将控制器或是view的UI分隔成一个个模块,将零散的控件封装在一个整体中。若是这些控件在项目其他地方也可以用到,则考虑继承,A继承B,或是B继承A,或是将AB公共部分抽成父类,不同的部分分别在AB中实现。如何封装控制器或是View的UI? 用instancetype类方法快速返回封装的一个对象,alloc内部会调用allocInitWithFrame,重写该方法,在此方法内部,先设置封装对象自身的属性,懒加载子控件,设置子控件的属性,添加到父视图上,在layoutSubView(1:该方法只能在继承于UIView的子控件中调用,且配合setNeedsLayout,该方法会异步调用layoutSubView,但不会立即调用,只有当在一个函数方法中,所有方法调用完毕后,layoutSubView才会立即被调用,setLayoutIfNeeds会检测有无标记setNeedsLayout,有则立即调用layoutSubView,没有的话,则不会被调用,当view首次加载时,默认存在setNeedsLayout标记,所以可直接调用setLayoutIfNeeds 3:layoutSubView在addSubView时,滚动视图时,屏幕旋转,控件的frame发生改变的时候都会被触发调用,不能手动调用,只能被重写)中设置子控件的frame,如何拿到子控件?1:父类为UIView时,直接从子控件数组中取出,self.subviews,得到子控件的数组,对于UIScrollView上的子控件会多出两个,多出的两个为横竖滚动条 2:属性,成员变量,设置tag值取出可得到子控件(设置tag值时:1:可以采取普通设置012设置 2:按钮做回调监听时,最好采取枚举值设置tag 3:fromIndex toIndex 还可以btn。tag = self.subViews.count) 3:大数组思想:将控件放到一个大数组中,从数组中取出控件,若是封装的控件中含有多个不同类型的控件,如button,lable等,可采取定义多个数组,分别将其加入到大数组中,分别将控件从数组中取出 4:也可以提前给View设置tag值,遍历(在遍历时,要想到做条件过滤用continue,当找到目标时,break *stop停止遍历)self.subViews,得到子控件,通过tag值将子控件取出5:做回调监听:1:协议代理,block,通知,继承于UIControl的可以addTarget,其中协议dialing,block多用于层级较浅的回调,通知适用于层级较深的回调,addTarget也可以做监听(valueChanged)2:还可以重写系统的方法做监听,做一些设置,到那时不要忘了调用super,否则系统的方法不能正常使用 2:对于系统控件的监听:查看系统控件的API,有没有监听方法,协议代理,系统通知,若没有,则查看父类的代理,系统通知,若还没有则遍历系统控件的子控件,查看系统控件的子控件有没有条件符合,若还没有,则采取自定义控件来替代系统的控件,kvc方法,setValue forKeyPath,第二个参数就填系统控件中需要替换的属性就可以实现替换。3:再用协议代理做回调监听时,有时回调方法不能调用,原因是此时代理还没有去设置,此时可重写setdelegate方法,在调用其他方法(控件自己也可以成为自己代理,遵守协议去监听自身的属性或是值的改变)5:外部的数据模型接口:1:当需要判断某种情况或是某个值是否存在时,属性定义Bool值,重写set方法,去设置 2:外界向封装控件提供数据模型,重写model的set方法,若是set方法或是某个方法调用频繁,则没必要在此方法内频繁创建或是销毁控件或是数据模型,采用懒加载,保证只初始化一次,不必管理代码的创建顺序,若是在构造数据模型时,遍历得到很多属性,则最好要考虑定义一个新的数据模型来保存这些属性,趋于模型的开发 3:若外部还想调用,重写model的set方法,则可以重写给model赋值,则就会重新调用model的set方法 6:外部创建对象,设置frame,传递数据model,封装时,一定要考虑让外部调用最简单,而且一定要考虑到项目后期的拓展性和可维护性
2:网络层的封装:1:将AFN封装一层HTTP,提供get,post请求接口做数据请求,再封装一层HttpBaseTool,调用HTTP封装的get,post接口,HttpBaseTool这一层封装主要做网络请求的数据转模型操作,再封装一层不同业务逻辑的数据请求类,继承于HttpBaseTool,这一层封装,主要是将url封装在网络请求类内部,向控制器只提供参数接口,回调成功失败接口。2:此封装方法,也需要将参数封装成模型,若是不同业务需求有相同的参数,则可以用继承,将相同参数抽成父类,子类去继承父类,若是某些参数为必传参数且需要赋值,则父类提供模型参数的instancetype类方法初始化方法,在初始化方法内部,alloc init创建对象,利用属性为固定参数赋值,也可以重写init方法,在此方法中为固定属性赋值,则子类在初始化时,会调用父类初始化方法,在父类初始化方法中则就会为固定的参数赋值了。在构造结果模型,利用MJExtension实现字典转模型数据(注意多种情况下的转换方法)。在控制器中数据模型的处理:1:数组没有初始化可直接赋值另一个数组 2:若初始化,addObjectFromArray,inset object atIndexset 3:先定义一个range,再设置location和length的值。其实数据层:就是网络层,参数模型,结果模型的离线缓存数据库(在数据请求的工具类中实现离线缓存,+(void)initliazi方法中创建表)转换处理。3:利用MVVM设计思想,构建View-model,将model作为View-model的一个属性,设置一些固定属性内容,控制器或是view中传入View-mode,View-mode层就是将控制器或是view中的数据又封装一层。
3:业务逻辑的分层封装:1:将每一模块的业务逻辑采取分层封装思想。若是控制器或是view中涉及一些业务逻辑的处理,则一定要想到采取分层封装的思想。1:若此块业务逻辑涉及到每个控件,则用继承关系,自定义控件,将控件的业务逻辑封装在自身的内部 2:若是不涉及某个控件的业务逻辑,则考虑自定义工具类将业务逻辑的代码封装起来,当项目中各处都可用到,还可以写分类,例如给NSString,UIImage,MBprogress,UIBarButton,UIview写分类,将业务逻辑封装在分类的内部,分类提供类方法或是对象方法。2:封装可采用单例或是类方法,在类方法中药是想拥有某个成员变量,则用static定义下划线的成员变量,还可以采用懒加载,保证该变量只被初始化一次,如果想在外界获得该变量,则把该变量的get方法暴露在.h中。
4:控制器或是view的方法代码的封装:要力求每一行的代码都是最简洁的。1:当涉及到大量重复的代码时,想要抽方法封装,相同的部分封装在方法的内部,不同的部分作为参数 2:抽代码三部曲,最上边定义变量,中间赋值变量,最下边赋值属性 3:一些if判断或是其他的方法,当含有大量的代码时,要想到简化代码,让代码永远是最简洁的
浙公网安备 33010602011771号