【轉】 UIScrollview 無限循環的圖片瀏覽器

轉自崔江濤(KenshinCui) http://www.cnblogs.com/kenshincui/p/3913885.htmlcss

 

概述html

UIKit框架中有大量的控件供開發者使用,在iOS開發中不只能夠直接使用這些控件還能夠在這些控件的基礎上進行擴展打造本身的控件。在這個系列中若是每一個控件都介紹一遍確實沒有必要,所謂授人以魚不如授人以漁,這裏會儘量讓你們明白其中的原理,找一些典型的控件進行說明,這樣一來你們就能夠舉一反三。今天咱們主要來看一下UIScrollView的內容:ios

  1. UIView
  2. UIScrollView
  3. 實戰--圖片瀏覽器

UIView

在熟悉UIScrollView以前頗有必要說一下UIView的內容。在上一篇文章也簡單的對UIView進行了介紹,可是因爲那篇文章的主要內容是給你們一個iOS開發的整體印象,所以並無系統的介紹。另外因爲UIScrollView的父類是UIView,全部在討論UIScrollView以前也頗有必要把UIView的知識給補充上,這樣你們在使用UIScrollView的某些方法時也不至於無從下手。瀏覽器

既然UIView是全部控件的父類,那麼對於一些經常使用的方法咱們頗有必要弄清楚,其實在Xcode中要了解一個類有哪些屬性和方法特別簡單,只要按住apple鍵點擊類名就能夠定位到這個類中查看相關定義(在往後的開發中咱們會常常這麼來作,畢竟要記住iOS開發中全部的API是不現實的,有些API咱們能夠經過這種方法來查找),例如咱們能夠查看這個類的內容:app

UIViewSourceCode

固然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

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,這種定義方式你們之後會常常用到。

運行效果:

豎屏

autoSize_portrait

橫屏

autoSize_landscape

 

注意上面執行前請先隱藏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便可(並且它有多種重載);

運行效果:

transform

注意在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在開發中會常用,爲了幫助你們理解這裏以圖形的形式展示三者之間的關係:

UIScrollViewLayout

關於上面列出的幾個方法,咱們有必要說一下它們的執行順序:

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

運行效果以下:

scroll

默認狀況下縮放後的內容會放到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方法是等效的;

注意:

  1. 除了weak(注意不是__weak)以外其餘的修飾符在非ARC(MRC)下使用也不會報錯,可是這麼作並無什麼意義,由於在編譯時會被忽略。舉例來講:在MRC下使用__autoreleasing修飾一個對象也不會自動釋放,而是應該使用autorelease方法。一樣的,在MRC下使用__strong來修飾一個變量也一樣是會直接忽略這個關鍵字;
  2. unsafe_unretained(或者__unsafe_unretained)和weak(或__weak)的區別不大,只是weak(或__weak)作釋放以後會將變量設置爲nil避免野指針,之因此目前兩個關鍵字還存在主要是由於後者在在iOS5.0及lion以後纔出現,出於兼容性考慮,所以推薦使用weak或__weak;
  3. __autoreleasing主要用於函數參數是ObjC對象指針的狀況下(也就是參數」NSObject **obj」類型,指針的指針),典型的應用就是NSError的使用。在這種狀況下,常常須要在函數內部從新建立一個對象給傳入的參數賦值(修改參數內容),若是使用__autorelesing參數編譯器在處理內部函數時會使用自動釋放池,即保證內部對象可以正常釋放又能夠修改外部變量。之因此不少時候使用NSError做爲參數傳遞到一些方法中沒有將變量聲明爲__autoreleasing是由於編譯器已經自動作了處理(所以,若是考慮到性能推薦仍是加上此參數);

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的關係

strong2

weak--person3和person4的關係

weak2

由此得出以下結論:

  1. 無論是怎麼管理內存都是針對對象類型而言(不管是strong,weak都不能應用到基本數據類型),對於基本數據類型直接聲明爲assign就能夠了,它不須要咱們本身管理內存; 
  2. 全部的指針變量默認都是__strong類型,所以咱們一般省略不寫__strong; 
  3. 若是一個對象沒有強引用以後即便存在弱引用它也會被釋放,與此同時弱引用將被設置爲nil;

回過頭來咱們看一下前面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,所以這個對象不會被銷燬。這樣一來咱們得出以下結論:

  1. 在iOS開發中使用strong、weak代替以前的retain、assign(基本類型使用assign); 
  2. 若是一個屬性使用IBOutlet修飾(也就是此屬性時strongboard中組件)那麼使用weak; 
  3. 若是一個屬性不是storyboard組件(通常純代碼編寫界面時),使用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張圖片顯示的情景):

loopScroll2

如何優化性能?

無限循環實現了,可是咱們知道若是圖片過多這些圖片勢必所有加載到內存,這是咱們不肯意看到的,此時咱們須要優化上面的方案。其實從上面的方案咱們也能夠看出端倪,咱們徹底不必建立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

在上面的代碼中須要提醒你們的是必定要謹慎在滾動時進行相關操做,前面咱們說過滾動事件會循環執行十分消耗性能,所以若是能不在其中操做的話儘量不要在這個方法中進行相關操做,例如在上面的代碼中咱們的核心邏輯主要集中在滾動中止事件中,這個事件在一次滾動操做中只須要執行一次。

運行效果:

loopScrollTuning

相關文章
相關標籤/搜索