--UIKit之UIScrollViewcss
UIKit框架中有大量的控件供开发者使用,在iOS开发中不只能够直接使用这些控件还能够在这些控件的基础上进行扩展打造本身的控件。在这个系列中若是每一个控件都介绍一遍确实没有必要,所谓授人以鱼不如授人以渔,这里会尽量让你们明白其中的原理,找一些典型的控件进行说明,这样一来你们就能够举一反三。今天咱们主要来看一下UIScrollView的内容:ios
在熟悉UIScrollView以前颇有必要说一下UIView的内容。在上一篇文章也简单的对UIView进行了介绍,可是因为那篇文章的主要内容是给你们一个iOS开发的整体印象,所以并无系统的介绍。另外因为UIScrollView的父类是UIView,全部在讨论UIScrollView以前也颇有必要把UIView的知识给补充上,这样你们在使用UIScrollView的某些方法时也不至于无从下手。浏览器
既然UIView是全部控件的父类,那么对于一些经常使用的方法咱们颇有必要弄清楚,其实在Xcode中要了解一个类有哪些属性和方法特别简单,只要按住apple键点击类名就能够定位到这个类中查看相关定义(在往后的开发中咱们会常常这么来作,毕竟要记住iOS开发中全部的API是不现实的,有些API咱们能够经过这种方法来查找),例如咱们能够查看这个类的内容:app
固然UIView的定义文件(.h文件)也是至关长的,咱们若是所有截图也没有意义。这里列出经常使用的属性和方法。框架
属性 | 说明 |
@property(nonatomic) CGRect frame; | 控件的位置和大小,全部的控件必须指定这个属性,不然即便有控件也没法显示 |
@property(nonatomic) CGRect bounds; | 当前控件位置和大小,可是和frame不一样的是它的位置是肯定的(0,0) |
@property(nonatomic) CGPoint center; | 控件的中心位置,通常用户进行控件定位 |
@property(nonatomic) CGAffineTransform transform; | 控件矩阵变化,包括平移、缩放、旋转,默认为CGAffineTransformIdentity |
@property(nonatomic) UIViewAutoresizing autoresizingMask; | 控件旋转时大小自动伸缩,默认为UIViewAutoresizingNone |
@property(nonatomic,readonly) UIView *superview; | 当前控件的父控件 |
@property(nonatomic,readonly,copy) NSArray *subviews; | 当前控件的全部一级子控件,注意其子控件的子控件并不包括在内 |
@property(nonatomic,getter=isHidden) BOOL hidden; | 是否隐藏,默认为NO |
@property(nonatomic) UIViewContentMode contentMode; | 内容模式,主要用于指定控件内容(注意不是子控件)如何填充,通常UIImageView常用,默认为UIViewContentModeScaleToFill |
@property(nonatomic) NSInteger tag; | 控件的标示,能够存储一些和当前控件有关的信息(可是注意只能是整形),默认为0 |
方法 | 说明 |
- (void)addSubview:(UIView *)view; | 添加子控件 |
- (void)removeFromSuperview; | 从父控件中移除当前控件 |
- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index; | 在指定位置插入子控件 |
+ (void)beginAnimations:(NSString *)animationID context:(void *)context; | 开始一段动画 |
+ (void)commitAnimations; | 结束一段动画,注意在开始和结束之间若是控件的某些属性发生变化iOS将以动画方式进行改变 |
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations NS_AVAILABLE_IOS(4_0); | 以block的形式执行一段动画,注意这个方法有几种相关的方法 |
- (void)addGestureRecognizer:(UIGestureRecognizer*)gestureRecognizer NS_AVAILABLE_IOS(3_2); | 添加手势操做 |
- (void)removeGestureRecognizer:(UIGestureRecognizer*)gestureRecognizer NS_AVAILABLE_IOS(3_2); | 移除手势操做 |
注意上面全部的位置属性都是相对于其父控件而言(不是相对于屏幕而言),多数属性比较简单这里再也不详细解释,咱们重点解释一下autoresizingMask、transform属性。iphone
autoresizingMask这个属性通常咱们进行屏幕旋转的时候常常用到,它的值是一个枚举类型:ide
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) { UIViewAutoresizingNone = 0, //不进行自动调整 UIViewAutoresizingFlexibleLeftMargin = 1 << 0, //自动调整与superview左侧距离,右侧距离保持不变 UIViewAutoresizingFlexibleWidth = 1 << 1, //自动调整控件自身宽度,保证与superview左右距离不变 UIViewAutoresizingFlexibleRightMargin = 1 << 2, //自动调整与superview右侧距离,左侧距离保持不变 UIViewAutoresizingFlexibleTopMargin = 1 << 3, //自动调整与superview顶部距离,底部距离保持不变 UIViewAutoresizingFlexibleHeight = 1 << 4, //自动调整控件自身高度,保证与superview上下距离不变 UIViewAutoresizingFlexibleBottomMargin = 1 << 5 //自动调整与superview底部距离,顶部距离保持不变 };
经过注释你们应该大概了解每一个枚举值的意义,可是咱们知道枚举常常进行按位或操做(“|”),例如若是autoresizingMask=UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleLeftMargin那么iOS如何处理呢?此时会自动调整左边的距离和控件自身宽度,右侧距离不变,同时保证左侧距离和控件宽度同等比例的调整(延长或缩短)。例如在iPhone 5中,若是一个按钮假设自身宽度为200,左右侧距离均为60(左侧和宽度比例3:10),当从竖屏旋转到横屏的时候(此时宽度由320变为568,注意若是有状态栏则宽度变为568-20=548),因为右侧边距不变为60,根据比例左侧边距应该是(568-60)*(3/13)=117,宽度为:(568-60)*(10/13)=391。函数
请看下面的代码(下面例子经过纯代码方式建立iOS应用,而且自定义一个KCMainViewController):oop
AppDelegate.m性能
// // AppDelegate.m // UIViewAndUIScrollView // // Created by Kenshin Cui on 14-2-23. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "AppDelegate.h" #import "KCMainViewController.h" #import "KCTransformViewController.h" @interface AppDelegate () @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window=[[UIWindow alloc]initWithFrame:[UIScreen mainScreen].applicationFrame]; KCMainViewController *mainController=[[KCMainViewController alloc]init]; self.window.rootViewController=mainController; self.window.backgroundColor=[UIColor colorWithRed:249/255.0 green:249/255.0 blue:249/255.0 alpha:1]; [self.window makeKeyAndVisible]; return YES; } - (void)applicationWillResignActive:(UIApplication *)application { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } - (void)applicationDidEnterBackground:(UIApplication *)application { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } - (void)applicationWillEnterForeground:(UIApplication *)application { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } - (void)applicationDidBecomeActive:(UIApplication *)application { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } - (void)applicationWillTerminate:(UIApplication *)application { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } @end
KCMainViewController.m
// // KCMainViewController.m // UIViewAndUIScrollView // // Created by Kenshin Cui on 14-2-23. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "KCMainViewController.h" @interface KCMainViewController (){ UIButton *_btn; //私有变量 } @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; //添加一个Button _btn=[[UIButton alloc]initWithFrame:CGRectMake(60, 100, 200, 50)]; _btn.backgroundColor=[UIColor orangeColor]; [_btn setTitle:@"Hello,world!" forState:UIControlStateNormal]; _btn.autoresizingMask=UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleLeftMargin; [self.view addSubview:_btn]; } #pragma mark 屏幕旋转事件 -(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{ NSLog(@"%@",NSStringFromCGRect(_btn.frame)); } @end
在上面的代码中设置了window的背景为灰色,虽然上面有一个UIView可是咱们能够看到最终效果是灰色的,这说明UIView默认是透明的。另外定义了一个私有成员变量_btn,这种定义方式你们之后会常常用到。
运行效果:
竖屏
横屏
注意上面执行前请先隐藏iOS状态栏目,全局隐藏iO状态栏的方法:
1.在info.plist 中设置Status bar is initially hidden为YES
2.在info.plist中设置View controller-based status bar appearance 为NO
transform咱们通常称为形变属性,其本质是经过矩阵变化改变控件的大小、位置、角度等,这里咱们经过一个例子来看一下具体的操做,在下面的例子中咱们也会看到UIImageView控件的经常使用操做。
KCTransformViewController.m
// // KCTransformViewController.m // UIViewAndUIScrollView // // Created by Kenshin Cui on 14-2-23. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "KCTransformViewController.h" //定义rgb颜色 #define NORMAL_COLOR [UIColor colorWithRed:75/255.0 green:160/255.0 blue:253/255.0 alpha:1] #define HIGHLIGHTED_COLOR [UIColor colorWithRed:197/255.0 green:221/225.0 blue:249/225.0 alpha:1] //按钮操做 typedef void(^ ButtonHandle)(); @interface KCTransformViewController (){ UIImageView *_imageView;//图片控件 UIButton *_btnRotation;//旋转按钮 UIButton *_btnScale;//缩放按钮 UIButton *_btnTranslate;//移动按钮 } @end @implementation KCTransformViewController - (void)viewDidLoad { [super viewDidLoad]; [self addImageView]; [self addRotationButton]; [self addScaleButton]; [self addTranslateButton]; } #pragma mark 添加图片控件 -(void)addImageView{ //直接使用图片名称,系统会自动到资源文件中找到对应的文件 UIImage *image=[UIImage imageNamed:@"promo_ios8.png"]; //若是使用initWithImage进行初始化则控件大小会自动设置成图片大小 _imageView=[[UIImageView alloc]initWithImage:image]; _imageView.frame=CGRectMake(20, 20, 280, 154); //设置内容填充模式为等比例填充 _imageView.contentMode=UIViewContentModeScaleAspectFit; //self.view就是每一个视图控制器中的view属性 [self.view addSubview:_imageView]; } #pragma mark 添加旋转按钮 -(void)addRotationButton{ _btnRotation=[self getButton]; _btnRotation.frame=CGRectMake(20, 400, 280, 30); [_btnRotation setTitle:@"旋转" forState:UIControlStateNormal]; //添加按钮点击事件 [_btnRotation addTarget:self action:@selector(rotation:) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:_btnRotation]; } #pragma mark 添加缩放按钮 -(void)addScaleButton{ //在上面一个按钮位置的基础上确认当前位置 CGRect scaleButtonFrame=_btnRotation.frame; scaleButtonFrame.origin.y+=40; _btnScale =[self getButton]; _btnScale.frame=scaleButtonFrame; [_btnScale setTitle:@"缩放" forState:UIControlStateNormal]; //添加按钮点击事件 [_btnScale addTarget:self action:@selector(scale:) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:_btnScale]; } #pragma mark 添加移动按钮 -(void)addTranslateButton{ CGRect translateButtonFrame=_btnScale.frame; translateButtonFrame.origin.y+=40; _btnTranslate =[self getButton]; _btnTranslate.frame=translateButtonFrame; [_btnTranslate setTitle:@"移动" forState:UIControlStateNormal]; [_btnTranslate addTarget:self action:@selector(translate:) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:_btnTranslate]; } #pragma mark 图片旋转方法,注意参数中的btn表示当前点击按钮 -(void)rotation:(UIButton *)btn{ [self animation:^{ //注意旋转角度必须是弧度,不是角度 CGFloat angle=M_PI_4;//M开头的宏都是和数学(Math)相关的宏定义,M_PI_4表示四分之派,M_2_PI表示2派 //使用CGAffineTransformMakeRotation得到一个旋转角度形变 //可是须要注意tranform的旋转不会自动在原来的角度上进行叠加,因此下面的方法旋转一次之后再点击按钮不会旋转了 //_imageView.transform=CGAffineTransformMakeRotation(angle); //利用CGAffineTransformRotate在原来的基础上产生一个新的角度(固然也能够定义一个全局变量本身累加) _imageView.transform=CGAffineTransformRotate(_imageView.transform, angle); }]; } #pragma mark 图片缩放方法 -(void)scale:(UIButton *)btn{ // [self animation:^{ // CGFloat scalleOffset=0.9; // //_imageView.transform=CGAffineTransformMakeScale(scalleOffset, scalleOffset); // _imageView.transform= CGAffineTransformScale(_imageView.transform, scalleOffset, scalleOffset); // }]; //一般咱们使用UIView的静态方法实现动画而不是本身写一个方法 [UIView animateWithDuration:0.5 animations:^{ CGFloat scalleOffset=0.9; //_imageView.transform=CGAffineTransformMakeScale(scalleOffset, scalleOffset); _imageView.transform= CGAffineTransformScale(_imageView.transform, scalleOffset, scalleOffset); }]; } #pragma mark 图片移动方法 -(void)translate:(UIButton *)btn{ [self animation:^{ CGFloat translateY=50; //_imageView.transform=CGAffineTransformMakeTranslation(0, translateY); _imageView.transform=CGAffineTransformTranslate(_imageView.transform, 0, translateY); }]; } #pragma mark 动画执行方法,注意这里可使用UIView的animateWithDuration方法代替这里只是为了演示 -(void)animation:(ButtonHandle)handle{ //开始动画 [UIView beginAnimations:@"animation" context:nil]; //设置动画执行时间 [UIView setAnimationDuration:0.5]; handle(); //执行动画操做 [UIView commitAnimations]; } #pragma mark 取得一个按钮,统一按钮样式 -(UIButton *)getButton{ UIButton *button =[[UIButton alloc]init ]; //设置正常状态下字体颜色 [button setTitleColor:NORMAL_COLOR forState:UIControlStateNormal]; //设置高亮状态下的字体颜色 [button setTitleColor:HIGHLIGHTED_COLOR forState:UIControlStateHighlighted]; return button; } @end
运行效果:
注意在iOS开发中推荐使用png图片,iOS会对png图片进行优化。
经过上面的介绍相信你们对于UIView的基本操做应该比较熟悉了,那么下面就看一下UIView的子控件UIScrollView 。顾名思义,这是一个能够处理滚动操做的视图,UIScrollView在开发过程当中使用很频繁,并且它也常常做为其余控件的子控件,例如UITableView就继承自UIScrollView。 咱们仍是先看一下UIScrollView的经常使用属性和方法:
属性 | 说明 |
@property(nonatomic) CGPoint contentOffset; | 内容偏移量,当前显示的内容的顶点相对此控件顶点的x、y距离,默认为CGPointZero |
@property(nonatomic) CGSize contentSize; | 控件内容大小,不必定在显示区域,若是这个属性不设置,此控件没法滚动,默认为CGSizeZero |
@property(nonatomic) UIEdgeInsets contentInset; | 控件四周边距,相似于css中的margin,注意边距不做为其内容的一部分,默认为UIEdgeInsetsZero |
@property(nonatomic,assign) id<UIScrollViewDelegate> delegate; | 控件代理,通常用于事件监听,在iOS中多数控件都是经过代理进行事件监听的 |
@property(nonatomic) BOOL bounces; | 是否启用弹簧效果,启用弹簧效果后拖动到边缘能够看到内容后面的背景,默认为YES |
@property(nonatomic,getter=isPagingEnabled) BOOL pagingEnabled; | 是否分页,若是分页的话每次左右拖动则移动宽度是屏幕宽度整数倍,默认为NO |
@property(nonatomic,getter=isScrollEnabled) BOOL scrollEnabled; | 是否启用滚动,默认为YES |
@property(nonatomic) BOOL showsHorizontalScrollIndicator; | 是否显示横向滚动条,默认为YES |
@property(nonatomic) BOOL showsVerticalScrollIndicator; | 是否显示纵向滚动条,默认为YES |
@property(nonatomic) CGFloat minimumZoomScale; | 最小缩放倍数,默认为1.0 |
@property(nonatomic) CGFloat maximumZoomScale; | 最大缩放倍数(注意只有maximumZoomScale大于minimumZoomScale才有可能缩放),默认为1.0 |
@property(nonatomic,readonly,getter=isTracking) BOOL tracking; | (状态)是否正在被追踪,手指按下去而且尚未拖动时是YES,其余状况均为NO |
@property(nonatomic,readonly,getter=isDragging) BOOL dragging; | 是否正在被拖拽 |
@property(nonatomic,readonly,getter=isDecelerating) BOOL decelerating; | 是否正在减速 |
@property(nonatomic,readonly,getter=isZooming) BOOL zooming; | 是否正在缩放 |
方法 | 说明 |
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated; | 设置滚动位置,第二个参数表示是否启用动画效果 |
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated; | 滚动并显示指定区域的内容,第二个参数表示是否启用动画效果 |
代理方法 | 说明 |
- (void)scrollViewDidScroll:(UIScrollView *)scrollView; | 滚动事件方法,滚动过程当中会一直循环执行(滚动中…) |
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView; | 开始拖拽事件方法 |
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate; | 拖拽操做完成事件方法 |
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView; | 即将中止滚动事件方法(拖拽松开后开始减速时执行) |
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView; | 滚动中止事件方法(滚动过程当中减速中止后执行) |
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view NS_AVAILABLE_IOS(3_2); | 开始缩放事件方法 |
- (void)scrollViewDidZoom:(UIScrollView *)scrollView NS_AVAILABLE_IOS(3_2); | 缩放操做完成事件方法 |
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView; | 返回缩放视图,注意只有实现这个代理方法才能进行缩放,此方法返回须要缩放的视图 |
contentSize、contentInset、contentOffset在开发中会常用,为了帮助你们理解这里以图形的形式展示三者之间的关系:
关于上面列出的几个方法,咱们有必要说一下它们的执行顺序:
a.若是咱们拖动一个UIScrollView中的子控件移动的时候它的执行顺序以下:开始拖拽,滚动,滚动…,中止拖拽,将要中止滚动,滚动,滚动…,中止滚动。
红色部分有可能执行也有可能不执行,关键看你拖拽的中止的时候是忽然中止仍是有一段惯性让他继续执行(就好像刹车同样,若是是急刹车就没有后面的惯性滑动了,若是是慢慢踩刹车可能会有一段滑动距离)。可是无论怎么样滚动事件会一直执行,所以若是在这个事件中进行某种操做必定要注意性能。
b.若是咱们缩放UIScrollView的子控件的时候它的执行顺序以下:开始缩放,滚动,滚动…,中止缩放。一样在这个过程当中滚动事件会一直调用(固然若是缩放过程当中手指有别的动做也可能会触发其余事件,这个你们能够本身体会一下)。
下面咱们简单作一个例子
KCScrollViewController.h
// // KCScrollViewController.h // UIViewAndUIScrollView // // Created by Kenshin Cui on 14-2-23. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import <UIKit/UIKit.h> @interface KCScrollViewController : UIViewController @property (nonatomic,strong) UIScrollView *scrollView; @property (nonatomic,strong) UIImageView *imageView; @end
KCScrollViewController.m
// // KCScrollViewController.m // UIViewAndUIScrollView // // Created by Kenshin Cui on 14-2-23. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "KCScrollViewController.h" //实现UIScrollView代理 @interface KCScrollViewController ()<UIScrollViewDelegate> @end @implementation KCScrollViewController - (void)viewDidLoad { [super viewDidLoad]; //添加scrollView控件 //注意UIScreen表明当前屏幕对象,其applicationFrame是当前屏幕内容区域 _scrollView =[[UIScrollView alloc]initWithFrame:[UIScreen mainScreen].applicationFrame]; //_scrollView.backgroundColor=[UIColor redColor]; _scrollView.contentMode=UIViewContentModeScaleToFill; [self.view addSubview:_scrollView]; //添加图片控件 UIImage *image=[UIImage imageNamed:@"wwdc14-labs-hero-background.jpg"]; _imageView=[[UIImageView alloc]initWithImage:image]; [_scrollView addSubview:_imageView]; //contentSize必须设置,不然没法滚动,当前设置为图片大小 _scrollView.contentSize=_imageView.frame.size; //实现缩放:maxinumZoomScale必须大于minimumZoomScale同时实现viewForZoomingInScrollView方法 _scrollView.minimumZoomScale=0.6; _scrollView.maximumZoomScale=3.0; //设置代理 _scrollView.delegate=self; //边距,不属于内容部分,内容坐标(0,0)指的是内容的左上角不包括边界 //_scrollView.contentInset=UIEdgeInsetsMake(10, 20, 10, 20); //显示滚动内容的指定位置 //_scrollView.contentOffset=CGPointMake(10, 0); //隐藏滚动条 _scrollView.showsHorizontalScrollIndicator=NO; _scrollView.showsVerticalScrollIndicator=NO; //禁用弹簧效果 //_scrollView.bounces=NO; } #pragma mark 实现缩放视图代理方法,不实现此方法没法缩放 -(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView{ return _imageView; } -(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{ NSLog(@"scrollViewWillBeginDecelerating"); } -(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{ NSLog(@"scrollViewDidEndDecelerating"); } -(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{ NSLog(@"scrollViewWillBeginDragging"); } -(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ NSLog(@"scrollViewDidEndDragging"); } -(void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view{ NSLog(@"scrollViewWillBeginZooming"); } -(void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale{ NSLog(@"scrollViewDidEndZooming"); } //-(void)scrollViewDidScroll:(UIScrollView *)scrollView{ // NSLog(@"scrollViewDidScroll"); //} #pragma mark 当图片小于屏幕宽高时缩放后让图片显示到屏幕中间 -(void)scrollViewDidZoom:(UIScrollView *)scrollView{ CGSize originalSize=_scrollView.bounds.size; CGSize contentSize=_scrollView.contentSize; CGFloat offsetX=originalSize.width>contentSize.width?(originalSize.width-contentSize.width)/2:0; CGFloat offsetY=originalSize.height>contentSize.height?(originalSize.height-contentSize.height)/2:0; _imageView.center=CGPointMake(contentSize.width/2+offsetX, contentSize.height/2+offsetY); } @end
运行效果以下:
默认状况下缩放后的内容会放到UIScrollView的内容起始位置,因此若是要想缩放后内容放到中间咱们必须本身维护它的位置,上面已经给出了设置方法。
iOS5以后引入了ARC特性,程序中不用本身retain、release、autorelease操做,编译器会自动为你管理内存,编译时自动加上内存释放的代码,使用起来很方便。ARC是编译器特性,而不是iOS运行时特性,其实质仍是手动管理内存,只是相应内存管理的代码编译器会自动生成而已。因为ARC是编译器特性,所以它管理内存的规则和以前ObjC内存管理是相似的,只要有一个对象引用(强引用)指向这个对象,那么这个对象就不会被释放。
在开启ARC以后咱们可使用四个关键字修饰咱们的成员变量、局部变量和属性:
注意:
strong和weak在iOS开发过程当中常用,这里简单看一个例子(注意这两个参数仍然能够修饰属性,意义是彻底同样的在此再也不演示)
KCPerson.h
// // KCPerson.h // ARC // // Created by Kenshin Cui on 14-2-23. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import <Foundation/Foundation.h> @interface KCPerson : NSObject @property (nonatomic,assign) int no; @end
KCPerson.m
// // KCPerson.m // ARC // // Created by Kenshin Cui on 14-2-23. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "KCPerson.h" @implementation KCPerson -(NSString *)description{ return [NSString stringWithFormat:@"no=%i",_no]; } @end
main.m
// // main.m // ARC // // Created by Kenshin Cui on 14-2-23. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import <Foundation/Foundation.h> #import "KCPerson.h" int main(int argc, const char * argv[]) { @autoreleasepool { //strong __strong KCPerson *person1=[[KCPerson alloc]init]; __strong KCPerson *person2=person1; person1.no=1; NSLog(@"%@",person2); //结果:no=1 person1=nil; NSLog(@"%@",person2); //结果:no=1 //weak __strong KCPerson *person3=[[KCPerson alloc]init]; __weak KCPerson *person4=person3; person3.no=3; NSLog(@"%@",person4); //结果:no=3 person3=nil; NSLog(@"%@",person4); //结果:(null) } return 0; }
因为person1和person2都指向一个对象而且都是强引用,所以当person1设置为nil时对象仍然不会释放,因此此时person2仍是指向这个对象,能够正常输出;person3和它指向的对象是强引用,而person4是弱引用,所以当person3设置为nil后,对象没有了强引用就会释放,此时再打印person4天然就是null。为了说明strong和weak的使用,下面使用图形方式描绘上面的状况:
strong--person1和person2的关系
weak--person3和person4的关系
由此得出以下结论:
回过头来咱们看一下前面UIScrollView部分的几个属性都设置成了strong,若是设置成weak行不行呢?答案是否认的。若是咱们设置成weak,Xcode首先就会给出提出“Assigning retained object to weak variable; object will be released after assignment”,就是说ObjC对象赋值给一个弱引用变量,赋值以后对象会当即被销毁。其实根据前面介绍的内容很容易理解,就拿上面的scrollView属性来讲,若是设置为weak,当使用“_scrollView =[[UIScrollView alloc]initWithFrame:[UIScreen mainScreen].applicationFrame];”给这个变量赋值,根据前面的知识若是一个对象没有了强引用就会被销毁,赋值完成后运行时看到这个对象只有一个弱引用_scrollView天然就会销毁这个对象,所以若是运行上面的程序就达不到以前看到的效果了。
可是若是使用storyboard来设计界面的时候,咱们会发现系统默认生成的属性就是weak,此时为何不会销毁呢?那是由于它的顶层对象保持了一个强引用strong,所以这个对象不会被销毁。这样一来咱们得出以下结论:
前面介绍了iOS中UIKit的一些简单知识,这里咱们一块儿利用前面的知识作一个例子--图片无限循环滚动。在这个例子中咱们须要解决以下两个问题:
咱们知道在UIScrollView中若是放置其余控件后,只要设置contentSize以后这些图片就能够滚动。若是要让图片无限循环那么只有两种办法,一种是无限循环叠加图片,另外一种就是若是最后一张图片浏览完当即显示第一张图片。很明显第一种方法是不现实的,咱们考虑使用第二种方式。其实使用第二种方式实现原理比较简单,只要在图片先后各放一张图片便可(此时共有n+2个图片在UIScrollView中)。例如咱们有5张图片,只要使用7个UIImageView依次存放:图片5,图片1,图片2,图片3,图片4,图片5,图片1。当从图片1滚动到图片5时因为最后一张是图片1就给用户一种无限循环的感受,当这张图彻底显示后咱们迅速将UIScrollView的contentOffset设置到第二个UIImageView,也就是图片1,接着用户能够继续向后滚动。固然向前滚动原理彻底同样,当滚动到第一张图片(图片5)就迅速设置UIScrollView的contentOffset显示第6张图(图片5)。为了方便说明请看下面的示意图(注意示意图因为宽度有限只描述了3张图片显示的情景):
无限循环实现了,可是咱们知道若是图片过多这些图片势必所有加载到内存,这是咱们不肯意看到的,此时咱们须要优化上面的方案。其实从上面的方案咱们也能够看出端倪,咱们彻底不必建立n+2个UIImageView,其实3个已经足够(事实上也能够用两个实现,你们不妨本身思考一下),只要一直保持显示中间的UIImageView,滚动时动态更改三个UIImageView的图片便可。例如三个UIImageView默认放图片五、图片一、图片2,当前显示中间的UIImageView,也就是图片1,。若是向后滚动那么就会显示图片2,当图片2显示完整后迅速从新设置三个UIImageView的内容为图片一、图片二、图片3,而后经过contentOffset设置显示中间的UIImageView,也就是图片2。继续向后看到图片3,当图片3滚动完成迅速从新设置3个UIImageView的内容为图片二、图片三、图片4,而后设置contentOffset显示中间的UIImageView,也就是图片3。固然,向前滚动原理彻底同样,如此就给用户一种循环错觉,并且不占用过多内存。
下面给出具体的实现,在这个程序中除了UIscrollView咱们还能够看到UIPageControl的使用,实现并不复杂。首先咱们须要将图片信息存储到plist文件中(往后方便扩展),而且设置plist的key表示图片的名称,value表明对应的图片描述,这个描述咱们须要展现在界面上方。具体内容以下:
imageInfo.plist
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>0.jpg</key> <string>iphone 5s</string> <key>1.jpg</key> <string>iphone 5c</string> <key>2.jpg</key> <string>ipad min with retain</string> <key>3.jpg</key> <string>ipad air</string> <key>4.jpg</key> <string>ipod</string> <key>5.jpg</key> <string>ipod touch</string> <key>6.jpg</key> <string>mac book pro</string> <key>7.jpg</key> <string>mac book air</string> <key>8.jpg</key> <string>imac</string> </dict> </plist>
在程序中咱们须要读取plist文件并加载对应的图片,这里咱们将图片按顺序依次命名:0.jpg、1.jpg…8.jpg。咱们的程序主要集中于自定义的KCMainViewController.m中,在这里咱们声明1个UIScrollView和3个UIImageView用于显示图片,同时声明一个UILable显示图片描述信息,声明一个UIPageControl来显示当前图片页数,具体代码以下:
// // KCMainViewController.m // ImageViewer // // Created by Kenshin Cui on 14-2-23. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "KCMainViewController.h" #define SCREEN_WIDTH 320 #define SCREEN_HEIGHT 568 #define IMAGEVIEW_COUNT 3 @interface KCMainViewController ()<UIScrollViewDelegate>{ UIScrollView *_scrollView; UIImageView *_leftImageView; UIImageView *_centerImageView; UIImageView *_rightImageView; UIPageControl *_pageControl; UILabel *_label; NSMutableDictionary *_imageData;//图片数据 int _currentImageIndex;//当前图片索引 int _imageCount;//图片总数 } @end @implementation KCMainViewController - (void)viewDidLoad { [super viewDidLoad]; //加载数据 [self loadImageData]; //添加滚动控件 [self addScrollView]; //添加图片控件 [self addImageViews]; //添加分页控件 [self addPageControl]; //添加图片信息描述控件 [self addLabel]; //加载默认图片 [self setDefaultImage]; } #pragma mark 加载图片数据 -(void)loadImageData{ //读取程序包路径中的资源文件 NSString *path=[[NSBundle mainBundle] pathForResource:@"imageInfo" ofType:@"plist"]; _imageData=[NSMutableDictionary dictionaryWithContentsOfFile:path]; _imageCount=(int)_imageData.count; } #pragma mark 添加控件 -(void)addScrollView{ _scrollView=[[UIScrollView alloc]initWithFrame:[UIScreen mainScreen].bounds]; [self.view addSubview:_scrollView]; //设置代理 _scrollView.delegate=self; //设置contentSize _scrollView.contentSize=CGSizeMake(IMAGEVIEW_COUNT*SCREEN_WIDTH, SCREEN_HEIGHT) ; //设置当前显示的位置为中间图片 [_scrollView setContentOffset:CGPointMake(SCREEN_WIDTH, 0) animated:NO]; //设置分页 _scrollView.pagingEnabled=YES; //去掉滚动条 _scrollView.showsHorizontalScrollIndicator=NO; } #pragma mark 添加图片三个控件 -(void)addImageViews{ _leftImageView=[[UIImageView alloc]initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)]; _leftImageView.contentMode=UIViewContentModeScaleAspectFit; [_scrollView addSubview:_leftImageView]; _centerImageView=[[UIImageView alloc]initWithFrame:CGRectMake(SCREEN_WIDTH, 0, SCREEN_WIDTH, SCREEN_HEIGHT)]; _centerImageView.contentMode=UIViewContentModeScaleAspectFit; [_scrollView addSubview:_centerImageView]; _rightImageView=[[UIImageView alloc]initWithFrame:CGRectMake(2*SCREEN_WIDTH, 0, SCREEN_WIDTH, SCREEN_HEIGHT)]; _rightImageView.contentMode=UIViewContentModeScaleAspectFit; [_scrollView addSubview:_rightImageView]; } #pragma mark 设置默认显示图片 -(void)setDefaultImage{ //加载默认图片 _leftImageView.image=[UIImage imageNamed:[NSString stringWithFormat:@"%i.jpg",_imageCount-1]]; _centerImageView.image=[UIImage imageNamed:[NSString stringWithFormat:@"%i.jpg",0]]; _rightImageView.image=[UIImage imageNamed:[NSString stringWithFormat:@"%i.jpg",1]]; _currentImageIndex=0; //设置当前页 _pageControl.currentPage=_currentImageIndex; NSString *imageName=[NSString stringWithFormat:@"%i.jpg",_currentImageIndex]; _label.text=_imageData[imageName]; } #pragma mark 添加分页控件 -(void)addPageControl{ _pageControl=[[UIPageControl alloc]init]; //注意此方法能够根据页数返回UIPageControl合适的大小 CGSize size= [_pageControl sizeForNumberOfPages:_imageCount]; _pageControl.bounds=CGRectMake(0, 0, size.width, size.height); _pageControl.center=CGPointMake(SCREEN_WIDTH/2, SCREEN_HEIGHT-100); //设置颜色 _pageControl.pageIndicatorTintColor=[UIColor colorWithRed:193/255.0 green:219/255.0 blue:249/255.0 alpha:1]; //设置当前页颜色 _pageControl.currentPageIndicatorTintColor=[UIColor colorWithRed:0 green:150/255.0 blue:1 alpha:1]; //设置总页数 _pageControl.numberOfPages=_imageCount; [self.view addSubview:_pageControl]; } #pragma mark 添加信息描述控件 -(void)addLabel{ _label=[[UILabel alloc]initWithFrame:CGRectMake(0, 10, SCREEN_WIDTH,30)]; _label.textAlignment=NSTextAlignmentCenter; _label.textColor=[UIColor colorWithRed:0 green:150/255.0 blue:1 alpha:1]; [self.view addSubview:_label]; } #pragma mark 滚动中止事件 -(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{ //从新加载图片 [self reloadImage]; //移动到中间 [_scrollView setContentOffset:CGPointMake(SCREEN_WIDTH, 0) animated:NO]; //设置分页 _pageControl.currentPage=_currentImageIndex; //设置描述 NSString *imageName=[NSString stringWithFormat:@"%i.jpg",_currentImageIndex]; _label.text=_imageData[imageName]; } #pragma mark 从新加载图片 -(void)reloadImage{ int leftImageIndex,rightImageIndex; CGPoint offset=[_scrollView contentOffset]; if (offset.x>SCREEN_WIDTH) { //向右滑动 _currentImageIndex=(_currentImageIndex+1)%_imageCount; }else if(offset.x<SCREEN_WIDTH){ //向左滑动 _currentImageIndex=(_currentImageIndex+_imageCount-1)%_imageCount; } //UIImageView *centerImageView=(UIImageView *)[_scrollView viewWithTag:2]; _centerImageView.image=[UIImage imageNamed:[NSString stringWithFormat:@"%i.jpg",_currentImageIndex]]; //从新设置左右图片 leftImageIndex=(_currentImageIndex+_imageCount-1)%_imageCount; rightImageIndex=(_currentImageIndex+1)%_imageCount; _leftImageView.image=[UIImage imageNamed:[NSString stringWithFormat:@"%i.jpg",leftImageIndex]]; _rightImageView.image=[UIImage imageNamed:[NSString stringWithFormat:@"%i.jpg",rightImageIndex]]; } @end
在上面的代码中须要提醒你们的是必定要谨慎在滚动时进行相关操做,前面咱们说过滚动事件会循环执行十分消耗性能,所以若是能不在其中操做的话尽量不要在这个方法中进行相关操做,例如在上面的代码中咱们的核心逻辑主要集中在滚动中止事件中,这个事件在一次滚动操做中只须要执行一次。
运行效果: