簡體   English   中英

如何以編程方式縮放UIScrollView?

[英]How do I programmatically zoom a UIScrollView?

我想以基類不支持的方式縮放和取消縮放。

例如,在接收到雙擊時。

我正在回答自己的問題,在玩完東西並讓它發揮作用之后。

Apple在他們關於如何處理雙擊的文檔中有一個非常簡單的例子。

執行程序化縮放的基本方法是自己動手,然后告訴UIScrollView你做了它。

  • 調整內部視圖的框架和邊界。
  • 將內部視圖標記為需要顯示。
  • 告訴UIScrollView有關新內容的大小。
  • 計算縮放后應顯示的內部視圖部分,並將UIScrollView平移到該位置。

同樣關鍵:一旦你告訴UIScrollView你的新內容大小,它似乎重置了當前縮放級別的概念。 您現在處於新的1.0縮放系數。 所以你幾乎肯定想要重置最小和最大縮放系數。

停止重新發明輪子! 看看蘋果是怎么做到的!

ScrollViewSuite - > Apple文檔頁面

ScrollViewSuite Direct Link - > XcodeProject

這正是你所期待的。

干杯!

注意:這是非常過時的。 它來自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) id<UIScrollViewDelegate> zoomScrollViewDelegate;

@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) <UIScrollViewDelegate>
@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<UIScrollViewDelegate>)realDelegate {
    return _realDelegate;
}
- (void)setDelegate:(id<UIScrollViewDelegate>)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

我想我找出了Darron所指的文件。 在“iPhone OS編程指南”文檔中,有一節“處理多點觸摸事件”。 那包含清單7-1:

- (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
 UIScrollView  *scrollView = (UIScrollView*)[self superview];
 UITouch       *touch = [touches anyObject];
 CGSize        size;
 CGPoint       point;

 if([touch tapCount] == 2) {
    if(![_viewController _isZoomed]) {
        point = [touch locationInView:self];
        size = [self bounds].size;
        point.x /= size.width;
        point.y /= size.height;

        [_viewController _setZoomed:YES];

        size = [scrollView contentSize];
        point.x *= size.width;
        point.y *= size.height;
        size = [scrollView bounds].size;
        point.x -= size.width / 2;
        point.y -= size.height / 2;
        [scrollView setContentOffset:point animated:NO];
    }
        else
        [_viewController _setZoomed:NO];
    }
 }

}

Darren,你能提供蘋果公司示例的鏈接嗎? 或者標題讓我可以找到它? 我看到http://developer.apple.com/iphone/library/samplecode/Touches/index.html ,但這並不包括縮放。

我在編程變焦后看到的問題是手勢變焦將縮放拍攝回到程序變焦發生之前的狀態。 似乎UIScrollView在內部保持關於縮放因子/級別的狀態,但我沒有確鑿的證據。

謝謝,-andrew

編輯:我剛剛意識到,你正在解決這樣一個事實,你通過調整大小和改變zoom-factor 1.0的含義,幾乎無法控制UIScrollView的內部縮放系數。 有點像黑客,但似乎所有Apple都離開了我們。 也許自定義類可以封裝這個技巧......

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM