UIScrollview 技巧
設置UIScrollView的contentSize
如果使用自動布局,那么它會自動幫你基于這個scrollview的子視圖的約束來計算這個內容大小。在非自動布局情況下,如果app旋轉導致scrollview 的bounds改變,不會影響到scrollview的contentSize,而如果重新設置contentSize,也不會影響scrollview的子視圖,這個contentSize僅僅是決定了滾動的范圍。
下面我們用代碼創建一個UIScrollView,UILabel在y軸上順序排列:
UIScrollView* sv = [UIScrollView new]; sv.backgroundColor = [UIColor whiteColor]; sv.translatesAutoresizingMaskIntoConstraints = NO; [self.view addSubview:sv]; [self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[sv]|"options:0 metrics:nilviews:@{@"sv":sv}]]; [self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[sv]|"options:0 metrics:nilviews:@{@"sv":sv}]]; UILabel* previousLab = nil; for (int i=0; i<30; i++) {UILabel* lab = [UILabel new];lab.translatesAutoresizingMaskIntoConstraints = NO;lab.text = [NSString stringWithFormat:@"This is label %d", i+1];[sv addSubview:lab];[sv addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(10)-[lab]"options:0 metrics:nilviews:@{@"lab":lab}]];if (!previousLab) { // first one, pin to top[sv addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(10)-[lab]"options:0 metrics:nilviews:@{@"lab":lab}]];} else { // all others, pin to previous[sv addConstraints:[NSLayoutConstraintconstraintsWithVisualFormat:@"V:[prev]-(10)-[lab]"options:0 metrics:nilviews:@{@"lab":lab, @"prev":previousLab}]];}previousLab = lab; }運行代碼,你會發現label 都放置在正確的位置,但是scrollView 卻不能滾動。而且,即使你手動設置了contentSize也無法滾動。原因就是在頁面布局時,scrollview的contentSize會自動根據它和子視圖之間的約束來計算得出。解決方法就是,再添加多一個約束,告訴scroll view 它的contentSize的高度應該是多少:
// 給底部最后一個label設置一個y軸方向的約束,這樣高度就可以確定了 [sv addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"V:[lab]-(10)-|"options:0 metrics:nilviews:@{@"lab":previousLab}]];可以看到,我們可以在垂直方向上滾動scrollview了,在水平方向上仍然不能滾動(contentSize的寬度默認就是0,正好是我們需要的)。
使用一個Content View
我們一般不會把scrollview 的子視圖直接添加到scrollview上,而是添加到UIView上,然后把這個視圖再添加到scrollview上,這個視圖就是這個Content View。這樣可以更加方便的組織和使用。
在自動布局下,我們可以有兩種設置scroll view的contentSize 方法:
設置content view 的translatesAutoresizingMaskIntoConstraints為YES,然后手動設置這個scroll view的 contentSize為這個content view的大小。
設置content view 的translatesAutoresizingMaskIntoConstraints為NO,然后給這個content view 設置寬度和高度約束。
設置content view的大小或者約束這種方式是與它的子視圖怎么定位(設置frame,還是實用約束)無關。有四種可能的組合,這四種組合開頭的代碼都是一樣的:
UIScrollView* sv = [UIScrollView new]; sv.backgroundColor = [UIColor whiteColor]; sv.translatesAutoresizingMaskIntoConstraints = NO; [self.view addSubview:sv]; [self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[sv]|"options:0 metrics:nilviews:@{@"sv":sv}]]; [self.view addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[sv]|"options:0 metrics:nilviews:@{@"sv":sv}]]; UIView* v = [UIView new]; // content view [sv addSubview: v];第一種組合不使用約束:
CGFloat y = 10; for (int i=0; i<30; i++) {UILabel* lab = [UILabel new];lab.text = [NSString stringWithFormat:@"This is label %d", i+1];[lab sizeToFit];CGRect f = lab.frame;f.origin = CGPointMake(10,y);lab.frame = f;[v addSubview:lab]; // add to content view, not scroll viewy += lab.bounds.size.height + 10; } // set content view frame and content size explicitly v.frame = CGRectMake(0,0,0,y); sv.contentSize = v.frame.size;第二種組合,content view使用約束,但它的子視圖不使用:
CGFloat y = 10; for (int i=0; i<30; i++) {// ... same as before, create labels, keep incrementing y } // configure the content view using constraints v.translatesAutoresizingMaskIntoConstraints = NO; [sv addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[v(y)]|"options:0 metrics:@{@"y":@(y)} views:@{@"v":v}]]; [sv addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[v(0)]|"options:0 metrics:nil views:@{@"v":v}]];第三種組合,content view 和它的子視圖都使用約束:
UILabel* previousLab = nil; for (int i=0; i<30; i++) {UILabel* lab = [UILabel new];lab.translatesAutoresizingMaskIntoConstraints = NO;lab.text = [NSString stringWithFormat:@"This is label %d", i+1];[v addSubview:lab];[v addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(10)-[lab]"options:0 metrics:nilviews:@{@"lab":lab}]];if (!previousLab) { // first one, pin to top[v addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(10)-[lab]"options:0 metrics:nilviews:@{@"lab":lab}]];} else { // all others, pin to previous[v addConstraints:[NSLayoutConstraintconstraintsWithVisualFormat:@"V:[prev]-(10)-[lab]"options:0 metrics:nilviews:@{@"lab":lab, @"prev":previousLab}]]; }previousLab = lab; } // last one, pin to bottom, this dictates content size height [v addConstraints: [NSLayoutConstraint constraintsWithVisualFormat:@"V:[lab]-(10)-|"options:0 metrics:nilviews:@{@"lab":previousLab}]]; // configure the content view using constraints v.translatesAutoresizingMaskIntoConstraints = NO; [sv addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[v]|"options:0 metrics:nil views:@{@"v":v}]]; [sv addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[v(0)]|"options:0 metrics:nil views:@{@"v":v}]];第四種組合是很奇怪的組合,content view的子視圖使用約束布局,但是content view 和 scrollview 不使用:
UILabel* previousLab = nil; // ... same as before, add subviews and constraints to content view // autolayout helps us learn the consequences of those constraints CGSize minsz = [v systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; // set content view frame and content size explicitly v.frame = CGRectMake(0,0,0,minsz.height); sv.contentSize = v.frame.size;滾動
在iOS 7 下,由于app都是全屏的,status bar,navigation bar 都是半透明的,所以scroll view 也會在 status bar 下面,那么有可能導致scrollview 的內容被status bar遮蓋,我們可以設置:
sv.contentInset = UIEdgeInsetsMake(20, 0, 0, 0);當我們設置了contentInset,我們通常也會設置scrollIndicatorInsets,讓滾動條也適應:
sv.contentInset = UIEdgeInsetsMake(20, 0, 0, 0); sv.scrollIndicatorInsets = sv.contentInset;這樣,即可以在status bar 看到半透明的scrollview 內容,又不會滾動不下導致的顯示不全,但是我們硬編碼這個20的值好像不是很優雅,我們可以:
- (void) viewWillLayoutSubviews {if (self.sv) {CGFloat top = self.topLayoutGuide.length;CGFloat bot = self.bottomLayoutGuide.length;self.sv.contentInset = UIEdgeInsetsMake(top, 0, bot, 0);self.sv.scrollIndicatorInsets = self.sv.contentInset;} }這些如果在nib中,如果我們設置view controller的automaticallyAdjustsScrollViewInsets為YES,那么iOS 7 運行時會幫我們自動適應這個scrollview 的contentInset 和 scrollIndicatorInsets,而不需要上面的代碼。這個僅僅在nib上有用,一旦我們手動創建scrollview,即使設置這個automaticallyAdjustsScrollViewInsets為YES也沒有用,還是需要上面的代碼。
Tiling 平鋪
假如我們有一個非常大的內容需要顯示在scroll view上,這么大的圖片,用戶可以滾動來查看所有的內容細節。在內存中存放整張圖片是不現實的,也是不可能的。
Tiling是一個種解決方法。我們把內容分成一個個小的矩形區域,當用戶滾動時,我們查找并讓目標的矩形區塊顯示,同時我們可以釋放不在顯示范圍內的矩形區域。
有一個內建的CALayer子類 CATiledLayer 幫我們實現了這個分塊。它的tileSize屬性設置區塊的大小。它的drawLayer:inContext: 會在需要一個空的區塊時被調用;在圖形上下文中調用CGContextGetClipBoundingBox 來截取所需區塊的位置。下面我們借用蘋果自己提供的PhotoScroller例子來說明:
我們給scroll view添加一個子視圖,一個TiledView,這個視圖用來存放我們的CATiledLayer圖層。TILESIZE是256:
-(void)viewDidLoad {CGRect f = CGRectMake(0,0,3*TILESIZE,3*TILESIZE);TiledView* content = [[TiledView alloc] initWithFrame:f];float tsz = TILESIZE * content.layer.contentsScale;[(CATiledLayer*)content.layer setTileSize: CGSizeMake(tsz, tsz)];[self.sv addSubview:content];[self.sv setContentSize: f.size]; }下面是TiledView的代碼,CATiledLayer是它的根圖層,所以TiledView是CATiledLayer的委托。意味著當CATiledLayer的 drawLayer:inContext: 被調用時,TiledView的drawRect:方法也會被調用,而且我們必須使用imageWithContentsOfFile:方法獲取圖片,而不是imageNamed:,防止系統緩存這張圖片:
+ (Class) layerClass {return [CATiledLayer class]; } -(void)drawRect:(CGRect)r {CGRect tile = r;int x = tile.origin.x/TILESIZE;int y = tile.origin.y/TILESIZE;NSString *tileName =[NSString stringWithFormat:@"CuriousFrog_500_%d_%d", x+3, y];NSString *path =[[NSBundle mainBundle] pathForResource:tileName ofType:@"png"];UIImage *image = [UIImage imageWithContentsOfFile:path];[image drawAtPoint:CGPointMake(x*TILESIZE,y*TILESIZE)]; }上面的代碼中并沒有明確釋放離屏區塊,你可以在TiledView中調用setNeedsDisplay 或者setNeedsDisplayInRect: 方法,但是這樣并不會清除離屏的區塊,我們相信這個CATiledLayer會幫我們處理好。
轉載于:https://www.cnblogs.com/YungMing/p/4346768.html
總結
以上是生活随笔為你收集整理的UIScrollview 技巧的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: poj 3131 Cubic Eight
- 下一篇: 技术要求→物理安全→防盗窃和防破坏