该文章参考苹果官方文档:Scroll View Programming Guide for iOS
scroll View在iOSAPP的使用场景是当显示的内容超出屏幕区域时.
使用scroll view可以解决两个问题:
- 让用户拖动显示的内容
- 让用户使用捏合手势(UIPinchGestureRecognizer)缩放屏幕上显示的内容
下面是一个使用UIScrollView
的例子,在scroll view中有个UIImageView
,显示了一个男孩;当用户拖动他/她的手指时,屏幕显示的图片会移动,如下图所示;同时还会显示一个导航条(scroll indicators),当手指离开屏幕时,导航条就会消失不见.
预览
UIScrollView
提供了下面的功能:
- 可以滚动显示的内容
- 可以缩放显示的内容
- 支持翻页滚动显示的内容(paging mode)
UIScrollView
内部并不包含一些特殊的控件的视图,只是滚动它的子视图.
ScrollView的滚动
- 简单的滚动非常容易实现
- 通过简单的拖拽或者轻划屏幕可以实现滚动,并不需要类似继承或者实现委托等操作来实现.
UIScrollView
的实例提供了设置content size的接口,也可通过Interface Builder来实现. 具体请看-创建并设置scroll view
scrollView缩放
-
如何进行缩放
- 通过委托(delegation)来实现缩放手势
- 实现缩放手势需要用到
UIScrollViewDelegate
协议,你需要实现其中的一些代理方法来指定那个子 view可以缩放,也需要设置minimum和maximum等影响因素.
简单缩放-scrollView自带捏合缩放
高级缩放-双击缩放 具体请看-通过点击来缩放
高级缩放-缩放时保持图像清晰度不变
scrollView的翻页模式
- 只需要三个子视图就可以实现scroll view的paging模式
- 在实现翻页效果时,记得只需要三个子视图就可以了,不要太多,因为考虑到内存消耗和性能问题,三个subview就可以了,具体请看-scroll view的翻页
scrollView的嵌套
- 同向嵌套
- 交叉嵌套
创建并设置scroll view
scroll view可以通过代码和interface builder创建.只需要很少的设置即可获得滚动.
创建Scroll Views
scroll view的创建和使用就是其他视图没啥两样,可以插入controller中或者其他view hierarchy中.另外需要再做两步设置来进行scroll view创建和设置:
- 必须要设置contentSize,来进行scroll view的内容大小的设置
- 必须向scroll view的中加入子view,scroll view就是通过子view来显示内容
你可以选择性的配置你应用的视觉元素(visual cues),比如scroll view的垂直/水平indicators,是否拖动/缩放弹跳,是否固定方向的滑动.
使用Interface Builder创建Scroll View
打开Interface Builder,然后在视图库中拖出scroll view到容器中,然后你可以将UIViewController
的view和scroll view绑定,将scroll view当做controller中的self.view.如下图,scroll view是File's Owner UIViewController
的view outlet:
虽然你可以在interface builder中设置UIScrollView
的大部分属性,但是控制scroll view的滚动区域(scrollable area)的属性contentSize
需要你通过代码手动设置,设置的位置可以在controller(scroll view的拥有者File's Owner)中的-viewDidLoad
方法中,如下代码清单:
//设置scroll view的大小
- (void)viewDidLoad {
[super viewDidLoad];
UIScrollView *tempScrollView = (UIScrollView *)self.view;
tempScrollView.contentSize = CGSizeMake(1280,960);
}
设置好scroll view的大小后,你可以将显示内容加入到scroll view中,这个过程既可以通过代码也可以通过Interface Builder.
通过代码创建scroll view
可以完全用代码来创建scroll view,通常是在controller中来创建,更确切的说是在controller中的-loadView
方法中是实现,下面代码清单是一个示例:
//使用代码来创建scroll view
- (void)loadView {
CGRect fullScreenRect = [[UIScreen mainScreen] applicationsFrame];
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:fullScreenRect];
scrollView.contentSize = CGSizeMake(320,758);
// do any further configuration to the scroll view
// add a view, or views, as a subview of the scroll view.
// set scrollView as self.view returns it
self.view = scrollView;
}
上面的代码创建的是一个大小为屏幕大小的scroll view,并将scroll view设置为self.view. contentSize设置为(320,758),所以该scroll view可以在垂直方向上滚动.
上面的代码可以进一步对scrollview进行设置,比如加入subViews
添加subviews
当你创建好scrollview后,你就需要往里面添加内容(subviews)了.
- 如果你需要支持(zooming)缩放功能,那你通常需要将许多内容组合在一个subview中,然后将subview添加到scrollview中.
- 如果对缩放不做要求,那么你就可以随意往scrollview中添加内容即可.
注意:虽然大部分情况下,都是往scroll view添加一个subview以支持缩放,但如果你需要对scroll view的subviews选择性地来进行缩放,可以通过委托方法
-viewForZoomingInScrollView:
来指定需要需要进行缩放的subview. 这部分内容会在使用捏合手势进行简单缩放讲到
对scroll view的content size, content inset, scroll indicators等属性设置
contentSize
contentSize是用来控制scrollView的内容大小的.下图是展示了contentSize对内容大小的控制:
contentInset
如果想给你的内容边缘加上padding,比如有时scroll view中的内容会被conroller中一些navigationBar/toolBar等控件遮住,这是需要在上/下边缘给scrollview中的内容加上padding,要实现这个功能,需要用scroll view的另一个重要属性contentInset
.通过设置contentInset可以在scrollview的四周增加一个缓冲区域(buffer area). 你也可以认为通过设置contentInset来增大scrollview的contentSize,从而不改变它内部的subviews的大小.
contentInset是一个UIEdgeInsets
结构体,有个 top,bottom,left,right四个成员,如下图是对contentInset的一个展示:
通过设置contentInset为(64,0,44,0),这样就可以显示controller的导航栏和toolbar而不遮住scrollview的内容了.
代码展示contentInset的设置
CGRect fullScreenRect = [[UIScreen mainScreen] applicationFrame];
UIScrollView* scrollView = [[UIScrollView alloc] initWithFrame:fullScreenRect];
self.view = scrollView;
scrollView.contentSize = CGSizeMake(320,758);
scrollView.contentInset = UIEdgeInsetsMake(64.0,0.0,44.0,0.0);
// do any further configuration to the scroll view.
self.view = scrollView;
下图显示contentInset对scroll view显示的影响. 当将scroll滚动最上方时(左图),屏幕上方留下了navigation bar和status bar的空间. 右图显示的是将scroll滚动到最底部,留了给toolbar的空间.
然而,改变contentInset的值,会产生一个对scroll view的indicator无法预测的副作用.当用户拖动内容到屏幕的顶部或者底部时,indicator会滚动到navigation/tool bar的范围,将超出scroll view内容显示的区域.
为了纠正这个bug,需要同时设置scrollIndicatorInsets
属性来配合contentInset
使用,下面代码清单展示了这个场景:
- (void)loadView {
CGRect fullScreenRect = [[UIScreen mainScreen] applicationFrame];
UIScrollView * scrollView = [[UIScrollView alloc] initWithFrame:fullScreenRect];
scrollView.contentSize=CGSizeMake(320,758);
scrollView.contentInset=UIEdgeInsetsMake(64.0,0.0,44.0,0.0);
scrollView.scrollIndicatorInsets=UIEdgeInsetsMake(64.0,0.0,44.0,0.0);
self.view=scrollView;
}
滚动和scroll view的内容
scroll view开始滚动的一般发生在用户用直接用手指拖动操作屏幕. 然后scrollview中的内容开始响应用户的操作,这个过程可以称为拖动手势(drag gesture).
轻划(flick gesture)是拖动手势的一个变种. 轻划手势是用户用手指快速在屏幕上划动,然后离开屏幕.该手势不仅会使屏幕滚动,还会产生一个冲量(imparts a momentum),既手指离开屏幕后,滚动的势头不会立即停止而是会继续做减速滚动.这种手势UIScrollView
默认帮开发者实现了.
但有时候,有些特殊的需求需要开发者手动实现这些手势,UIScrollView
也提供了接口供开发者实现这部分特性需求,在UIScrollView的委托协议中UIScrollViewDelegate
提供了一些方法供开发这来控制scroll view的滚动过程.
通过代码来控制滚动
scrollview的滚动不一定都是通过用户手势来控制,也可通过代码设置来进行特殊的滚动,比如:
- 设置scroll view的
contentOffset
属性 - 滚动到特定的区域(exposed the specific rectangular)
- 滚动的顶部(scrolls to the top)
滚动到特定offset
要是scrollView的内容滚动特定的位置(top-left,contentOffset属性)可以通过两种办法实现.
- 方法
setContentOffset:animated:
的调用,参数animated设置为YES;scrollView会匀速滚动到特定的位置,如果animated参数设置NO,那么会瞬间跳动到特定位置.- 不管animated是NO还是YES,delegate都会调用
scrollViewDidScroll:
方法. - 如果animated=YES,在滚动动画期间delegate会多次调用
scrollViewDidScroll:
,当动画结束后delegate会调用scrollViewDidEndScrollingAnimation:
- 不管animated是NO还是YES,delegate都会调用
- 直接通过代码设置contentOffset(CGPoint),不会产生动画,调用一次
scrollViewDidScroll:
显示特定区域(rectangle)
有时需要将scroll view滚动到特定区域,以显示特定区域的内容,特别地,当要展示的内容是一个在屏幕显示区域外的控件时,这个功能比较有用.
- 方法
scrollRectToVisible:animated:
可以指定特定的区域滚动到显示区域.当需要做动画是,animated设置YES. - 当animated为YES时,delegate会多次调用
scrollViewDidScroll:
,动画结束后再调用scrollViewDidEndScrollingAnimation:
- 使用
scrollRectToVisible:animated:
滚动特定区域时,scrollView的属性tracking
和dragging
的值为NO(这些属性后面会讲到)
滚动到顶部(scroll to top)
如果状态栏可见,可以单击状态栏使scrollView滚动到顶部.这个特性在很多应用都有,非常方便用户浏览顶部的内容,比如iPhone自带应用Photos有这个特性,方便用户上翻内容. 大多数UITableView(UIScrollView的子类)实现了这个功能.
- 要想支持该特性只需要实现委托方法
scrollViewShouldScrollToTop:
,在里面return YES就好了,在里面还可以判断那个scrollView需要支持该特性. - 当滚动到顶部结束后,delegate会调用
scrollViewDidScrollToTop:
,里面指定了是那个ScrollView.
scroll View滚动时,delegate回调委托方法的过程
当scroll view滚动时,scroll view会同时跟踪一些属性值的改变以记录当前scroll view的状态,这些属性有:tracking,dragging,decelerating,zooming,zoomBouncing. 另外属性contentOffset记录了当前内容的左上角在屏幕上的位置,既当前scrollview滚动到了那个位置.
State property | Description |
---|---|
tracking | YES 当用户的手指接触屏幕时 |
dragging | YES 当用户在屏幕上拖动时 |
decelerating | YES 当用户使用flick手势时,或者拖动scrollView超过边界弹跳时 |
zooming | YES 当用户使用捏合手势时去改变scrollview的属性zoomScale时 |
contentOffset | 它的值为CGPoint,它表示内容滚动的位置 |
在滚动的时候,没必要循环遍历上述属性值,在滚动时delegate会调用的方法来告诉开发者当前scroll view的状态. 在相应的委托方法中,开发者可以做一些相应的处理来使scrollview符合自己的需求.在这些方法中可以访问上述的几个状态值,以确定scrollView的状态.
标记scroll view滚动开始和结束的简单方式
- 如果你的应用只关心scroll的起始和结束状态,那么你只需要关心少数几个委托方法.
- 当scroll开始滚动时delegate会调用
scrollViewWillBeginDragging:
- 当scrollview结束滚动式会调用两个
scrollViewDidEndDragging:willDecelerate:
(decelerate 参数为NO时)和scrollViewDidEndDecelerating:
,只要调用二者之一就说明scroll结束了.
滚动时delegate方法调用的整个过程(Delegate-Message-Sequence)
- 当用户接触屏幕时
tracking
立即改为YES,只要用户一直接触屏幕tracking的值就不会改变. - 如果用户手指按住屏幕不动,scroll的content view开始响应改触摸事件,导致delegate方法调用过程结束, 如果用户开始移动手指的话,message-sequence开始
- 当用户拖动手指时,scroll view会取消touch事件的处理程序,直接开始进行delegate的message-sequence处理.
- 当delegate调用
scrollViewWillBeginDragging:
时,dragging的值为YES. - 当用户拖动手指时,delegate调用
scrollViewDidScroll:
,而且拖动过程中该方法会一直被调用.在方法中可以访问contentOffset的值来查看当前scrollView滚动到了那个位置. - 如果用户使用flick手势的话,里面涉及到了一个滚动减速问题. tracking的值为NO,delegate调用
scrollViewDidEndDragging:willDecelerate:
方法,并且decelerate参数为YES.开始做减速滚动,这一滚动过程中的减速运动受属性decelerationRate
的影响. 属性decelerating
为YES. - 当用停止拖动时,delegate会调用
scrollViewDidEndDragging:willDecelerate
,这时因为是拖动手势,所以参数deceleration为NO.同时将tracking和decelerating设置为NO,message-sequence结束. - 另外当用户给允许scrollview可以弹跳(bounces)的话,delegate也会调用
scrollViewDidEndDragging:willDecelerate
,deceleration参数为YES;即使用户长按scrollView然后离开屏幕后也会调用上述方法. 这个过程受到属性bounces/alwaysBounceVertical/alwaysBounceHorizontal的影响,另外当bounces为NO时,其他两个属性也不管用了.当bounces为YES时,scrollView的contentSize小于scrollView的bounds时才会发生bouncing. - 只要调用了
scrollViewDidEndDragging:willDecelerate:
方法,且deceleration参数为YES时,在scrollView做减速滚动过程中delegate会继续调用scrollViewWillBeginDecelerating:
,而且还会多次调用scrollViewDidScroll:
,此时dragging/tracking为NO,decelerating为YES - 最终,减速滚动结束,delegate会调用
scrollViewDidEndDecelerating:
方法,且decelerating为NO.
注意:当scrollView进行缩放时,tracking/dragging的值可能一直为NO,zooming为YES,这种情况存在.
使用捏合手势进行简单缩放
UIScrollView的缩放非常容易实现,scrollView本身自带捏合手势(pinch gesture)进行缩放. 实现步骤是:
- 设置缩放因子(zoom factors:对内容的缩放程度),设置
minimumZoomScale
和maximumZoomScale
- 然后再实现一个delegate方法
viewForZoomingInScrollView:
如何使用Pinch Gesture来进行缩放
-
捏合手势是iOS应用的一个标准手势,分pinch-in/pinch-out两个动作,pinch-in进行缩小,pinch-out进行放大,如下图展示:
为了使scrollView支持缩放,开发者需要给scrollView设置一个delegate.该delegate需要实现
UISCrollViewDelegate
协议中的viewForZommingInScrollView:
方法,在该方法中返回需要进行缩放的内容(contents,or subviews).在下面代码清单展示了如何使用
viewForZommingInScrollView:
delegate方法来指定需要缩放的内容,当进行捏合手势时,控制器的Imageview需要响应该手势进行缩放动作:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return self.imageView;
}
- 可以通过设置属性
minimumZoomScale
和maximumZoomScale
这两个缩放因子来控制缩放程度,这两个属性的初始值为1.0, 进行缩放时maximumZoomScale必须大于minimumZoomScale. 可以通过代码或者InterfaceBuilder来进行设置,下面的代码展示了如何使用这两个属性:
- (void)viewDidLoad {
[super viewDidLoad];
self.scrollView.minimumZoomScale=0.5;
self.scrollView.maximumZoomScale=6.0;
self.scrollView.contentSize=CGSizeMake(1280, 960);
self.scrollView.delegate=self;
}
- 设置缩放因子(zoom factors)和实现delegate方法这两个步骤是使scrollView支持缩放的基本条件
代码控制缩放
-
除了使用捏合手势进行控制缩放,还有其他条件可以进行scrollView的缩放,比如用户双击手势(double taps gestures)或者其他手势.所以UIScrollView还提供了下面两个接口供开发者控制缩放:
- 方法
setZoomScale:animated:
- 方法
zoomToRect:animated:
- 方法
使用
setZoomScale:animated:
给属性zoomScale
赋值,该值范围是minimumZoomScale-maximumZoomScale. 如果animated = YES,那么缩放过程是一个动画,如果为NO,那么缩放效果是瞬间完成,直接给zoomScale赋值也是瞬间的,没有动画效果. 在缩放过程中,进行缩放的view的center不改变,也就是位置不变,只缩放.使用
zoomToRect:animated:
方法进行缩放时,会缩放以刚好充满指定的rect.该方法在进行缩放时会改变位置,参数animated的值对缩放的影响和setZoomScale:animted:
一样.有时需要根据用户点击屏幕(tap gesture)来设置zoom scale和location, 开发者经常会遇到这样的需求.因为
setZoomScale:animated:
进行缩放时位置不变,所以满足不了需求,所以要使用zoomToRect:animated:
,下面是一个工具方法,用来计算rect(位置):
- (CGRect)zoomRectForScrollView:(UIScrollView *)scrollView withScale:(float)scale withCenter:(CGPoint)center {
CGRect zoomRect;
// The zoom rect is in the content view's coordinates.
// At a zoom scale of 1.0, it would be the size of the
// imageScrollView's bounds.
// As the zoom scale decreases, so more content is visible,
// the size of the rect grows.
zoomRect.size.height = scrollView.frame.size.height / scale;
zoomRect.size.width = scrollView.frame.size.width / scale;
// choose an origin so as to get the right center.
zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0);
zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0);
return zoomRect;
}
上面的工具方法在"双击进行缩放"时很有用. 要使用上面的工具方法需要传一个scrollView,一个newScale(通常为zoomScale+zoomAmount或者zoomScale*zoomAmout),需要进行缩放的中心点,得到rect后,将rect传入zoomToRect:animated:
方法中
当zoom结束时通知delegate
- 当scrollView的缩放结束后,delegate会调用
scrollViewDidEndZooming:withView:atScale:
方法 - 该方法调用时会传入scrollView,进行缩放的subview,缩放因子scaleFactor
在进行缩放时如何保持图像清晰
- scrollView在缩放时,仅仅是对内容根据zoomScale进行大小缩放,这会导致图像不够清晰.因为缩放时,内容不会重绘.
- 如果你希望内容在缩放时需要保持清晰,那么你可以去参考苹果提供的demo:ScrollViewSuit. ScrollViewSuit会使用一种提前渲染(pre-rendering)的技术来缩放内容,然后再单独展示它.
- 如果你进行缩放的内容是实时绘制且缩放时还要保持清晰,那么需要用到
Core Animation
,将UIView的layer改为CATileLayer,并且使用drawLayer:inContext:
进行绘制 - 下面的代码清单展示了UIView的子类ZoomableView.该类用于缩放且能保持清晰时使用.
#import "ZoomableView.h"
#import <QuartzCore/QuartzCore.h>
@implementation ZoomableView
// Set the UIView layer to CATiledLayer
+(Class)layerClass
{
return [CATiledLayer class];
}
// Initialize the layer by setting
// the levelsOfDetailBias of bias and levelsOfDetail
// of the tiled layer
-(id)initWithFrame:(CGRect)r
{
self = [super initWithFrame:r];
if(self) {
CATiledLayer *tempTiledLayer = (CATiledLayer*)self.layer;
tempTiledLayer.levelsOfDetail = 5;
tempTiledLayer.levelsOfDetailBias = 2;
self.opaque=YES;
}
return self;
}
// Implement -drawRect: so that the UIView class works correctly
// Real drawing work is done in -drawLayer:inContext
-(void)drawRect:(CGRect)r
{
}
-(void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context
{
// The context is appropriately scaled and translated such that you can draw to this context
// as if you were drawing to the entire layer and the correct content will be rendered.
// We assume the current CTM will be a non-rotated uniformly scaled
// affine transform, which implies that
// a == d and b == c == 0
// CGFloat scale = CGContextGetCTM(context).a;
// While not used here, it may be useful in other situations.
// The clip bounding box indicates the area of the context that
// is being requested for rendering. While not used here
// your app may require it to do scaling in other
// situations.
// CGRect rect = CGContextGetClipBoundingBox(context);
// Set and draw the background color of the entire layer
// The other option is to set the layer as opaque=NO;
// eliminate the following two lines of code
// and set the scroll view background color
CGContextSetRGBFillColor(context, 1.0,1.0,1.0,1.0);
CGContextFillRect(context,self.bounds);
// draw a simple plus sign
CGContextSetRGBStrokeColor(context, 0.0, 0.0, 1.0, 1.0);
CGContextBeginPath(context);
CGContextMoveToPoint(context,35,255);
CGContextAddLineToPoint(context,35,205);
CGContextAddLineToPoint(context,135,205);
CGContextAddLineToPoint(context,135,105);
CGContextAddLineToPoint(context,185,105);
CGContextAddLineToPoint(context,185,205);
CGContextAddLineToPoint(context,285,205);
CGContextAddLineToPoint(context,285,255);
CGContextAddLineToPoint(context,185,255);
CGContextAddLineToPoint(context,185,355);
CGContextAddLineToPoint(context,135,355);
CGContextAddLineToPoint(context,135,255);
CGContextAddLineToPoint(context,35,255);
CGContextClosePath(context);
// Stroke the simple shape
CGContextStrokePath(context);
}
注意: 上述代码有很大的使用限制,UIKit绘制时线程不安全的,而core graphic是线程安全的,drawLayer:inRect:
的调用是发生在后台线程中的,所以里面的绘制要是core graphic.
通过点击来缩放
通过上面学习我们知道要进行缩放很简单,通过捏合手势等很容易实现.但有些场景的缩放需求比较复杂,比如双击缩放,我们需要对tap手势的探测来进行特定的缩放,比较典型的例子如地图的双击缩放效果.根据点击的手指数,点击次数,连续点击的速度,可以进行不同的缩放处理,所以这一效果比较复杂,需要重写UIView中touch的处理方法(touchesBegan..,touchesEnded..,touchesCanceled..)
重写UIView的Touch-Handing方法
为检测不同点击动作响应不同的缩放效果,所以需要重写touch-handing方法,这里可以参考ScrollViewSuit中的列子TapToZoom中的类TapDetectingImageView,它是UIImageView的子类.下面就开始讲者个类的实现
Initialization
- 进行"点击缩放"的视图需要在使用
initWithImage
初始化时开启User Interaction(用户交互)和multiple touches(多次安触碰) - 变量
twoFingerTapIsPossible
用来记录触摸屏幕的手指数是否超过2(手指数>2:NO) - 变量
multipleTouches
用来记录touches event的数量是否超过1(touch event > 1:YES) - 变量
tapLocation
用来记录点击的位置(当是双手指的时候,表示两个指头间的中间点)
请看下面的代码
- (id)initWithImage:(UIImage *)image {
self = [super initWithImage:image];
if (self) {
[self setUserInteractionEnabled:YES];
[self setMultipleTouchEnabled:YES];
twoFingerTapIsPossible = YES;
multipleTouches = NO;
}
return self;
}
The touchesBegan:withEvent: Implementation
- 取消单击手势动作
handleSingleTap
- 设置状态变量
multipleTouches
,twoFingerTapIsPossible
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// Cancel any pending handleSingleTap messages.
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleSingleTap) object:nil];
// Update the touch state.
if ([[event touchesForView:self] count] > 1)
multipleTouches = YES;
if ([[event touchesForView:self] count] > 2)
twoFingerTapIsPossible = NO;
}
The touchesEnded:withEvent: Implementation
- 主要实现都在这个方法,比较长
- 函数
midPointBetweenPoints
用来计算两个touch的中间点
代码如下:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
BOOL allTouchesEnded = ([touches count] == [[event touchesForView:self] count]);
// first check for plain single/double tap, which is only possible if we haven't seen multiple touches
if (!multipleTouches) {
UITouch *touch = [touches anyObject];
tapLocation = [touch locationInView:self];
if ([touch tapCount] == 1) {
[self performSelector:@selector(handleSingleTap)
withObject:nil
afterDelay:DOUBLE_TAP_DELAY];
} else if([touch tapCount] == 2) {
[self handleDoubleTap];
}
}
// Check for a 2-finger tap if there have been multiple touches
// and haven't that situation has not been ruled out
else if (multipleTouches && twoFingerTapIsPossible) {
// case 1: this is the end of both touches at once
if ([touches count] == 2 && allTouchesEnded) {
int i = 0;
int tapCounts[2];
CGPoint tapLocations[2];
for (UITouch *touch in touches) {
tapCounts[i] = [touch tapCount];
tapLocations[i] = [touch locationInView:self];
i++;
}
if (tapCounts[0] == 1 && tapCounts[1] == 1) {
// it's a two-finger tap if they're both single taps
tapLocation = midpointBetweenPoints(tapLocations[0],
tapLocations[1]);
[self handleTwoFingerTap];
}
}
// Case 2: this is the end of one touch, and the other hasn't ended yet
else if ([touches count] == 1 && !allTouchesEnded) {
UITouch *touch = [touches anyObject];
if ([touch tapCount] == 1) {
// If touch is a single tap, store its location
// so it can be averaged with the second touch location
tapLocation = [touch locationInView:self];
} else {
twoFingerTapIsPossible = NO;
}
}
// Case 3: this is the end of the second of the two touches
else if ([touches count] == 1 && allTouchesEnded) {
UITouch *touch = [touches anyObject];
if ([touch tapCount] == 1) {
// if the last touch up is a single tap, this was a 2-finger tap
tapLocation = midpointBetweenPoints(tapLocation,
[touch locationInView:self]);
[self handleTwoFingerTap];
}
}
}
// if all touches are up, reset touch monitoring state
if (allTouchesEnded) {
twoFingerTapIsPossible = YES;
multipleTouches = NO;
}
}
The touchesCancelled:withEvent: Implementation
如果scrollview上的点击手势变成拖动手势时,调用此方法:
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
twoFingerTapIsPossible = YES;
multipleTouches = NO;
}
ScrollViewSuite-苹果讲解ScrollView高级用法的示例代码
在ScrollViewSuite包含了很多scrollView的高级用法代码实例,比如TapToZoom可以用在地图应用,可以多去参考借鉴
scroll view的翻页
UIScrollView有种模式叫翻页模式(paging mode),指用户只能一屏一屏的滚动scrollView中的内容.经常用来展示一系列的内容,比如电纸书/指导页
如何设置翻页模式
- scrollView的翻页模式需要使用代码设置
- 初始化scrollView后需要将属性
pagingMode
设置为YES - 如果
contentSize
的height设置为屏幕的高度,那么width需要为屏幕宽度的整数倍 - 使用UIPageControl替代UIScrollView自带的indicator
- 下图显示展示了例子PageControl: Using a Paginated UIScrollView
如何设置翻页的内容
有两种方式:
- 在同一个view中一次性全部加载完内容(适合内容较少的时候)
- 使用多个(最好3个)view来部分加载当前要显示的内容和将要显示的内容(适合内容较多,加载完全部内容需要更多时间),具体请看apple实例代码:PageControl:USing a Paginated UIScrollView
在使用多个view展示内容时:
- 使用3个页面来展示展示内容,第一页展示已经显示过得内容,第二页展示当前显示的内容,第三页显示将要显示的内容,这样既不浪费内存又不耽误显示
- 在controller初始化时,翻页的三个页面就要开始初始化,计算好三个页面的位置,准备滚动操作,三个页面要交替循环显示对应的内容.
- 需要实现delegate方法
scrollViewDidScroll:
,用来跟踪scrollView的contentOffset,判断何时超过scrollview的中间.根据手指滑动的方向判断要显示的页面(next/first page).然后重绘将要显示的内容 - 根据上面的策略可以显示大量的页面
- 如果页面的创建比较耗时,可以创建一个view pool来存放将要显示的页面,类似tableView
scrollView的嵌套(Nesting Scroll View)
iOS3.0之前不支持嵌套,之后的话嵌套变得比较容易了
scrollView的嵌套分为同向嵌套(same-direction scrolling)和交叉嵌套(cross-direction scrolling)
同向嵌套
指scrollView中的subview也是scrollView,且滚动方向是相同的,如下图展示两种不同的嵌套
交叉嵌套
子scrollView的滚动方向和父scrollView的滚动方向是垂直的.
就像Apple自带的股票
应用一样,底层是水平方向翻页的scrollView,但顶层是一个垂直方向滚动的tableView
示例代码
- Scroll 展示了scroll view的滚动效果
- PageControl:USing a Paginated UIScrollView 展示了scroll view的翻页效果
- ScrollViewSuite 展示了scroll view的高级使用
- PhotoScroller 使用UIPageViewController,UIScrollView和CATiledLayer来展示巨型(高分辨率)图片,能在翻页/拖动/缩放时无压力