iOS开发系列--无限循环的图片浏览器
概述
UIKit框架中有大量的控件供開發者使用,在iOS開發中不僅可以直接使用這些控件還可以在這些控件的基礎上進行擴展打造自己的控件。在這個系列中如果每個控件都介紹一遍確實沒有必要,所謂授人以魚不如授人以漁,這里會盡可能讓大家明白其中的原理,找一些典型的控件進行說明,這樣一來大家就可以觸類旁通。今天我們主要來看一下UIScrollView的內容:
UIView
在熟悉UIScrollView之前很有必要說一下UIView的內容。在上一篇文章也簡單的對UIView進行了介紹,但是由于那篇文章的主要內容是給大家一個iOS開發的總體印象,因此并沒有系統的介紹。另外由于UIScrollView的父類是UIView,所有在討論UIScrollView之前也很有必要把UIView的知識給補充上,這樣大家在使用UIScrollView的某些方法時也不至于無從下手。
既然UIView是所有控件的父類,那么對于一些常用的方法我們很有必要弄清楚,其實在Xcode中要了解一個類有哪些屬性和方法特別簡單,只要按住apple鍵點擊類名就可以定位到這個類中查看相關定義(在日后的開發中我們會經常這么來做,畢竟要記住iOS開發中所有的API是不現實的,有些API我們可以通過這種方法來查找),例如我們可以查看這個類的內容:
當然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屬性。
autoresizingMask
autoresizingMask這個屬性一般我們進行屏幕旋轉的時候經常用到,它的值是一個枚舉類型:
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):
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:. }@endKCMainViewController.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
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- 獲得CGAffineTransform有多種方法,例如使用CGAffineTransformMake,但是對于矩陣操作相對比較麻煩,事實上iOS已經為我們準備好了三個方法:CGAffineTransformMakeRotation(旋轉)、CGAffineTransformMakeScale(縮放)、CGAffineTransformMakeTranslation(移動);
- transform進行旋轉、縮放、移動的時候不是在原來的基礎上增量形變的,因此如果需要持續在原來的基礎上旋轉、縮放、移動那么每次需要在原來的基礎上增加或減少。當然,我們可以定義一個全局變量進行累加,但是事實上iOS已經為我們提供好了三個對應的方法,分別用于在原來的角度、縮放、移動位置的基礎上做出修改:CGAffineTransformRotate、CGAffineTransformScale、CGAffineTransformTranslate;
- Objc語法規定不允許直接修改一個對象的結構體屬性的成員,只能給這個屬性直接賦值為一個結構體類型,例如上面的代碼中如果寫成“_btnRotation.frame.origin.x=380;”是不正確的;
- 上面的代碼中我們用到了UIView的動畫相關方法,在iOS開發中動畫開發異常簡單,而且動畫和邏輯處理是完全分離的,只要在兩個動畫方法之間修改一個控件的屬性那么當代碼執行時就會自動添加動畫效果,為了復習前面的block這里我們實現了一個類似于animation方法,類似于UIView的animateWithDuration靜態方法的功能,僅僅為了說明它的實現原理,實際開發中可以直接調用animateWithDuration即可(而且它有多種重載);
運行效果:
注意在iOS開發中推薦使用png圖片,iOS會對png圖片進行優化。
UIScrollView
通過上面的介紹相信大家對于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;@endKCScrollViewController.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的內容起始位置,所以如果要想縮放后內容放到中間我們必須自己維護它的位置,上面已經給出了設置方法。
擴展—ARC
iOS5之后引入了ARC特性,程序中不用自己retain、release、autorelease操作,編譯器會自動為你管理內存,編譯時自動加上內存釋放的代碼,使用起來很方便。ARC是編譯器特性,而不是iOS運行時特性,其實質還是手動管理內存,只是相應內存管理的代碼編譯器會自動生成而已。由于ARC是編譯器特性,因此它管理內存的規則和之前ObjC內存管理是類似的,只要有一個對象引用(強引用)指向這個對象,那么這個對象就不會被釋放。
在開啟ARC之后我們可以使用四個關鍵字修飾我們的成員變量、局部變量和屬性:
- strong(修飾變量用__strong):強引用,相當于原來的retain,每次賦值引用計數器加1,只要指針引用這個對象,這個對象就不會被銷毀;
- weak(修飾變量用__weak):弱引用,相當于assign,和assign不同的是當對象釋放后該變量會設置為nil防止野指針(雖然之前講過的內容中assign都是應用于基本數據類型,其實它也完全可以修飾對象類型的屬性);
- unsafe_unretained(修飾變量用__unsafe_unretained):和weak類似,區別就是如果對象釋放后它不會像weak一樣自動將指針設置為nil,有可能出現野指針;
- __autoreleasing(只能修飾變量不能修飾屬性):修飾一個對象在使用完之后自動釋放,通常用于延遲釋放內存,同在MRC下調用對象的autorelease方法是等效的;
注意:
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;@endKCPerson.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]; }@endmain.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=1person1=nil;NSLog(@"%@",person2); //結果:no=1//weak__strong KCPerson *person3=[[KCPerson alloc]init];__weak KCPerson *person4=person3;person3.no=3;NSLog(@"%@",person4); //結果:no=3person3=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,因此這個對象不會被銷毀。這樣一來我們得出如下結論:
UIScrollView實戰
前面介紹了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默認放圖片5、圖片1、圖片2,當前顯示中間的UIImageView,也就是圖片1,。如果向后滾動那么就會顯示圖片2,當圖片2顯示完整后迅速重新設置三個UIImageView的內容為圖片1、圖片2、圖片3,然后通過contentOffset設置顯示中間的UIImageView,也就是圖片2。繼續向后看到圖片3,當圖片3滾動完成迅速重新設置3個UIImageView的內容為圖片2、圖片3、圖片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在上面的代碼中需要提醒大家的是一定要謹慎在滾動時進行相關操作,前面我們說過滾動事件會循環執行十分消耗性能,因此如果能不在其中操作的話盡可能不要在這個方法中進行相關操作,例如在上面的代碼中我們的核心邏輯主要集中在滾動停止事件中,這個事件在一次滾動操作中只需要執行一次。
運行效果:
總結
以上是生活随笔為你收集整理的iOS开发系列--无限循环的图片浏览器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: thinkphp验证是否登录并跳转
- 下一篇: SQL获取刚插入的记录的自动增长列ID的