我想以基类不支持的方式缩放和取消缩放.
例如,在接收到双击时.
我正在回答自己的问题,在玩完东西并让它发挥作用之后.
Apple在他们关于如何处理双击的文档中有一个非常简单的例子.
执行程序化缩放的基本方法是自己动手,然后告诉UIScrollView你做了它.
调整内部视图的框架和边界.
将内部视图标记为需要显示.
告诉UIScrollView有关新内容的大小.
计算缩放后应显示的内部视图部分,并将UIScrollView平移到该位置.
同样关键:一旦你告诉UIScrollView你的新内容大小,它似乎重置了当前缩放级别的概念.您现在处于新的1.0缩放系数.所以你几乎肯定想要重置最小和最大缩放系数.
注意:这是非常过时的.它来自iOS 2.x次,并且实际上已经在iOS 3.x中修复.
只保留它用于历史目的.
我想我找到了一个干净的解决方案,我创建了一个UIScrollView子类来封装它.
可以在github.com/andreyvit/ScrollingMadness上找到示例代码,该代码说明了编程缩放(+双击处理)和照片库样式的分页+缩放+滚动以及ZoomScrollView类.
简而言之,我的解决方案是返回一个新的虚拟视图viewForZoomingInScrollView:
,暂时使您的子视图(UIImageView,无论如何).在scrollViewDidEndZooming:
我们反过来说,处理虚拟视图并将内容视图移回滚动视图.
为什么有帮助?这是一种打败我们无法以编程方式更改的持久视图比例的方法.UIScrollView不保留当前视图比例.相反,每个UIView都能够保持其当前视图比例(在_gestureInfo字段指向的UIGestureInfo对象内).通过为每个缩放操作提供新的UIView,我们始终以缩放比例1.00开始.
以及如何做这帮助吗?我们自己存储当前缩放比例,并将其手动应用于我们的内容视图,例如contentView.transform = CGAffineTransformMakeScale(zoomScale, zoomScale)
.但是,这与UIScrollView冲突,希望在用户捏住视图时重置转换.通过为UIScrollView提供另一个带有身份变换的视图来进行缩放,我们不再争取转换相同的视图.UIScrollView可以很高兴地相信它每次都以缩放1.00开始,并且以身份变换开始缩放视图,并且其内部视图具有对应于我们实际当前缩放比例的变换.
现在,ZoomScrollView封装了所有这些东西.这是为了完整性而编写的代码,但我真的建议从GitHub下载示例项目(你不需要使用Git,那里有一个下载按钮).如果您希望收到有关示例代码更新的通知(您应该 - 我打算维护和更新此类!),请关注GitHub上的项目或发送电子邮件至andreyvit@gmail.com.
接口:
/* ZoomScrollView makes UIScrollView easier to use: - ZoomScrollView is a drop-in replacement subclass of UIScrollView - ZoomScrollView adds programmatic zooming (see `setZoomScale:centeredAt:animated:`) - ZoomScrollView allows you to get the current zoom scale (see `zoomScale` property) - ZoomScrollView handles double-tap zooming for you (see `zoomInOnDoubleTap`, `zoomOutOnDoubleTap`) - ZoomScrollView forwards touch events to its delegate, allowing to handle custom gestures easily (triple-tap? two-finger scrolling?) Drop-in replacement: You can replace `[UIScrollView alloc]` with `[ZoomScrollView alloc]` or change class in Interface Builder, and everything should continue to work. The only catch is that you should not *read* the 'delegate' property; to get your delegate, please use zoomScrollViewDelegate property instead. (You can set the delegate via either of these properties, but reading 'delegate' does not work.) Zoom scale: Reading zoomScale property returns the scale of the last scaling operation. If your viewForZoomingInScrollView can return different views over time, please keep in mind that any view you return is instantly scaled to zoomScale. Delegate: The delegate accepted by ZoomScrollView is a regular UIScrollViewDelegate, however additional methods from `NSObject(ZoomScrollViewDelegateMethods)` category will be called on your delegate if defined. Method `scrollViewDidEndZooming:withView:atScale:` is called after any 'bounce' animations really finish. UIScrollView often calls it earlier, violating the documented contract of UIScrollViewDelegate. Instead of reading 'delegate' property (which currently returns the scroll view itself), you should read 'zoomScrollViewDelegate' property which correctly returns your delegate. Setting works with either of them (so you can still set your delegate in the Interface Builder). */ @interface ZoomScrollView : UIScrollView { @private BOOL _zoomInOnDoubleTap; BOOL _zoomOutOnDoubleTap; BOOL _zoomingDidEnd; BOOL _ignoreSubsequentTouches; // after one of delegate touch methods returns YES, subsequent touch events are not forwarded to UIScrollView float _zoomScale; float _realMinimumZoomScale, _realMaximumZoomScale; // as set by the user (UIScrollView's min/maxZoomScale == our min/maxZoomScale divided by _zoomScale) id _realDelegate; // as set by the user (UIScrollView's delegate is set to self) UIView *_realZoomView; // the view for zooming returned by the delegate UIView *_zoomWrapperView; // the disposable wrapper view actually used for zooming } // if both are enabled, zoom-in takes precedence unless the view is at maximum zoom scale @property(nonatomic, assign) BOOL zoomInOnDoubleTap; @property(nonatomic, assign) BOOL zoomOutOnDoubleTap; @property(nonatomic, assign) idzoomScrollViewDelegate; @end @interface ZoomScrollView (Zooming) @property(nonatomic, assign) float zoomScale; // from minimumZoomScale to maximumZoomScale - (void)setZoomScale:(float)zoomScale animated:(BOOL)animated; // centerPoint == center of the scroll view - (void)setZoomScale:(float)zoomScale centeredAt:(CGPoint)centerPoint animated:(BOOL)animated; @end @interface NSObject (ZoomScrollViewDelegateMethods) // return YES to stop processing, NO to pass the event to UIScrollView (mnemonic: default is to pass, and default return value in Obj-C is NO) - (BOOL)zoomScrollView:(ZoomScrollView *)zoomScrollView touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; - (BOOL)zoomScrollView:(ZoomScrollView *)zoomScrollView touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; - (BOOL)zoomScrollView:(ZoomScrollView *)zoomScrollView touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; - (BOOL)zoomScrollView:(ZoomScrollView *)zoomScrollView touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event; @end
执行:
@interface ZoomScrollView (DelegateMethods)@end @interface ZoomScrollView (ZoomingPrivate) - (void)_setZoomScaleAndUpdateVirtualScales:(float)zoomScale; // set UIScrollView's minimumZoomScale/maximumZoomScale - (BOOL)_handleDoubleTapWith:(UITouch *)touch; - (UIView *)_createWrapperViewForZoomingInsteadOfView:(UIView *)view; // create a disposable wrapper view for zooming - (void)_zoomDidEndBouncing; - (void)_programmaticZoomAnimationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(UIView *)context; - (void)_setTransformOn:(UIView *)view; @end @implementation ZoomScrollView @synthesize zoomInOnDoubleTap=_zoomInOnDoubleTap, zoomOutOnDoubleTap=_zoomOutOnDoubleTap; @synthesize zoomScrollViewDelegate=_realDelegate; - (id)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { _zoomScale = 1.0f; _realMinimumZoomScale = super.minimumZoomScale; _realMaximumZoomScale = super.maximumZoomScale; super.delegate = self; } return self; } - (id)initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) { _zoomScale = 1.0f; _realMinimumZoomScale = super.minimumZoomScale; _realMaximumZoomScale = super.maximumZoomScale; super.delegate = self; } return self; } - (id )realDelegate { return _realDelegate; } - (void)setDelegate:(id )delegate { _realDelegate = delegate; } - (float)minimumZoomScale { return _realMinimumZoomScale; } - (void)setMinimumZoomScale:(float)value { _realMinimumZoomScale = value; [self _setZoomScaleAndUpdateVirtualScales:_zoomScale]; } - (float)maximumZoomScale { return _realMaximumZoomScale; } - (void)setMaximumZoomScale:(float)value { _realMaximumZoomScale = value; [self _setZoomScaleAndUpdateVirtualScales:_zoomScale]; } @end @implementation ZoomScrollView (Zooming) - (void)_setZoomScaleAndUpdateVirtualScales:(float)zoomScale { _zoomScale = zoomScale; // prevent accumulation of error, and prevent a common bug in the user's code (comparing floats with '==') if (ABS(_zoomScale - _realMinimumZoomScale) < 1e-5) _zoomScale = _realMinimumZoomScale; else if (ABS(_zoomScale - _realMaximumZoomScale) < 1e-5) _zoomScale = _realMaximumZoomScale; super.minimumZoomScale = _realMinimumZoomScale / _zoomScale; super.maximumZoomScale = _realMaximumZoomScale / _zoomScale; } - (void)_setTransformOn:(UIView *)view { if (ABS(_zoomScale - 1.0f) < 1e-5) view.transform = CGAffineTransformIdentity; else view.transform = CGAffineTransformMakeScale(_zoomScale, _zoomScale); } - (float)zoomScale { return _zoomScale; } - (void)setZoomScale:(float)zoomScale { [self setZoomScale:zoomScale animated:NO]; } - (void)setZoomScale:(float)zoomScale animated:(BOOL)animated { [self setZoomScale:zoomScale centeredAt:CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2) animated:animated]; } - (void)setZoomScale:(float)zoomScale centeredAt:(CGPoint)centerPoint animated:(BOOL)animated { if (![_realDelegate respondsToSelector:@selector(viewForZoomingInScrollView:)]) { NSLog(@"setZoomScale called on ZoomScrollView, however delegate does not implement viewForZoomingInScrollView"); return; } // viewForZoomingInScrollView may change contentOffset, and centerPoint is relative to the current one CGPoint origin = self.contentOffset; centerPoint = CGPointMake(centerPoint.x - origin.x, centerPoint.y - origin.y); UIView *viewForZooming = [_realDelegate viewForZoomingInScrollView:self]; if (viewForZooming == nil) return; if (animated) { [UIView beginAnimations:nil context:viewForZooming]; [UIView setAnimationDuration: 0.2]; [UIView setAnimationDelegate: self]; [UIView setAnimationDidStopSelector: @selector(_programmaticZoomAnimationDidStop:finished:context:)]; } [self _setZoomScaleAndUpdateVirtualScales:zoomScale]; [self _setTransformOn:viewForZooming]; CGSize zoomViewSize = viewForZooming.frame.size; CGSize scrollViewSize = self.frame.size; viewForZooming.frame = CGRectMake(0, 0, zoomViewSize.width, zoomViewSize.height); self.contentSize = zoomViewSize; self.contentOffset = CGPointMake(MAX(MIN(zoomViewSize.width*centerPoint.x/scrollViewSize.width - scrollViewSize.width/2, zoomViewSize.width - scrollViewSize.width), 0), MAX(MIN(zoomViewSize.height*centerPoint.y/scrollViewSize.height - scrollViewSize.height/2, zoomViewSize.height - scrollViewSize.height), 0)); if (animated) { [UIView commitAnimations]; } else { [self _programmaticZoomAnimationDidStop:nil finished:nil context:viewForZooming]; } } - (void)_programmaticZoomAnimationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(UIView *)context { if ([_realDelegate respondsToSelector:@selector(scrollViewDidEndZooming:withView:atScale:)]) [_realDelegate scrollViewDidEndZooming:self withView:context atScale:_zoomScale]; } - (BOOL)_handleDoubleTapWith:(UITouch *)touch { if (!_zoomInOnDoubleTap && !_zoomOutOnDoubleTap) return NO; if (_zoomInOnDoubleTap && ABS(_zoomScale - _realMaximumZoomScale) > 1e-5) [self setZoomScale:_realMaximumZoomScale centeredAt:[touch locationInView:self] animated:YES]; else if (_zoomOutOnDoubleTap && ABS(_zoomScale - _realMinimumZoomScale) > 1e-5) [self setZoomScale:_realMinimumZoomScale animated:YES]; return YES; } // the heart of the zooming technique: zooming starts here - (UIView *)_createWrapperViewForZoomingInsteadOfView:(UIView *)view { if (_zoomWrapperView != nil) // not sure this is really possible [self _zoomDidEndBouncing]; // ...but just in case cleanup the previous zoom op _realZoomView = [view retain]; [view removeFromSuperview]; [self _setTransformOn:_realZoomView]; // should be already set, except if this is a different view _realZoomView.frame = CGRectMake(0, 0, _realZoomView.frame.size.width, _realZoomView.frame.size.height); _zoomWrapperView = [[UIView alloc] initWithFrame:view.frame]; [_zoomWrapperView addSubview:view]; [self addSubview:_zoomWrapperView]; return _zoomWrapperView; } // the heart of the zooming technique: zooming ends here - (void)_zoomDidEndBouncing { _zoomingDidEnd = NO; [_realZoomView removeFromSuperview]; [self _setTransformOn:_realZoomView]; _realZoomView.frame = _zoomWrapperView.frame; [self addSubview:_realZoomView]; [_zoomWrapperView release]; _zoomWrapperView = nil; if ([_realDelegate respondsToSelector:@selector(scrollViewDidEndZooming:withView:atScale:)]) [_realDelegate scrollViewDidEndZooming:self withView:_realZoomView atScale:_zoomScale]; [_realZoomView release]; _realZoomView = nil; } @end @implementation ZoomScrollView (DelegateMethods) - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { if ([_realDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]) [_realDelegate scrollViewWillBeginDragging:self]; } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { if ([_realDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]) [_realDelegate scrollViewDidEndDragging:self willDecelerate:decelerate]; } - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView { if ([_realDelegate respondsToSelector:@selector(scrollViewWillBeginDecelerating:)]) [_realDelegate scrollViewWillBeginDecelerating:self]; } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { if ([_realDelegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]) [_realDelegate scrollViewDidEndDecelerating:self]; } - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { if ([_realDelegate respondsToSelector:@selector(scrollViewDidEndScrollingAnimation:)]) [_realDelegate scrollViewDidEndScrollingAnimation:self]; } - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { UIView *viewForZooming = nil; if ([_realDelegate respondsToSelector:@selector(viewForZoomingInScrollView:)]) viewForZooming = [_realDelegate viewForZoomingInScrollView:self]; if (viewForZooming != nil) viewForZooming = [self _createWrapperViewForZoomingInsteadOfView:viewForZooming]; return viewForZooming; } - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale { [self _setZoomScaleAndUpdateVirtualScales:_zoomScale * scale]; // often UIScrollView continues bouncing even after the call to this method, so we have to use delays _zoomingDidEnd = YES; // signal scrollViewDidScroll to schedule _zoomDidEndBouncing call [self performSelector:@selector(_zoomDidEndBouncing) withObject:nil afterDelay:0.1]; } - (void)scrollViewDidScroll:(UIScrollView *)scrollView { if (_zoomWrapperView != nil && _zoomingDidEnd) { [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_zoomDidEndBouncing) object:nil]; [self performSelector:@selector(_zoomDidEndBouncing) withObject:nil afterDelay:0.1]; } if ([_realDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) [_realDelegate scrollViewDidScroll:self]; } - (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView { if ([_realDelegate respondsToSelector:@selector(scrollViewShouldScrollToTop:)]) return [_realDelegate scrollViewShouldScrollToTop:self]; else return YES; } - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView { if ([_realDelegate respondsToSelector:@selector(scrollViewDidScrollToTop:)]) [_realDelegate scrollViewDidScrollToTop:self]; } @end @implementation ZoomScrollView (EventForwarding) - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { id delegate = self.delegate; if ([delegate respondsToSelector:@selector(zoomScrollView:touchesBegan:withEvent:)]) _ignoreSubsequentTouches = [delegate zoomScrollView:self touchesBegan:touches withEvent:event]; if (_ignoreSubsequentTouches) return; if ([touches count] == 1 && [[touches anyObject] tapCount] == 2) if ([self _handleDoubleTapWith:[touches anyObject]]) return; [super touchesBegan:touches withEvent:event]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { id delegate = self.delegate; if ([delegate respondsToSelector:@selector(zoomScrollView:touchesMoved:withEvent:)]) if ([delegate zoomScrollView:self touchesMoved:touches withEvent:event]) { _ignoreSubsequentTouches = YES; [super touchesCancelled:touches withEvent:event]; } if (_ignoreSubsequentTouches) return; [super touchesMoved:touches withEvent:event]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { id delegate = self.delegate; if ([delegate respondsToSelector:@selector(zoomScrollView:touchesEnded:withEvent:)]) if ([delegate zoomScrollView:self touchesEnded:touches withEvent:event]) { _ignoreSubsequentTouches = YES; [super touchesCancelled:touches withEvent:event]; } if (_ignoreSubsequentTouches) return; [super touchesEnded:touches withEvent:event]; } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { id delegate = self.delegate; if ([delegate respondsToSelector:@selector(zoomScrollView:touchesCancelled:withEvent:)]) if ([delegate zoomScrollView:self touchesCancelled:touches withEvent:event]) _ignoreSubsequentTouches = YES; [super touchesCancelled:touches withEvent:event]; } @end
停止重新发明轮子!看看苹果是怎么做到的!
ScrollViewSuite - > Apple文档页面
ScrollViewSuite Direct Link - > XcodeProject
这正是你所期待的.
干杯!