28

基本クラスがサポートしていない方法でズームおよびズーム解除したいと思います。

たとえば、ダブルタップを受け取ったとき。

4

5 に答える 5

14

物事をいじって機能させた後、私は自分の質問に答えています。

Apple のドキュメントには、ダブルタップの処理方法に関する非常に簡単な例があります。

プログラムによるズームを行うための基本的なアプローチは、自分でズームを行い、それを実行したことを UIScrollView に伝えることです。

  • 内部ビューのフレームと境界を調整します。
  • 内部ビューを表示が必要としてマークします。
  • UIScrollView に新しいコンテンツ サイズを伝えます。
  • ズーム後に表示する内部ビューの部分を計算し、UIScrollView をその場所にパンします。

また重要: UIScrollView に新しいコンテンツのサイズを伝えると、現在のズーム レベルの概念がリセットされたように見えます。これで、新しい 1.0 ズーム係数になりました。したがって、ほとんどの場合、最小ズーム倍率と最大ズーム倍率をリセットする必要があります。

于 2008-10-07T14:27:17.217 に答える
13

車輪の再発明をやめなさい!アップルがどのようにそれを行うかを見てください!

ScrollViewSuite->Appleドキュメントページ

ScrollViewSuiteダイレクトリンク->XcodeProject

それはまさにあなたが探しているものです。

乾杯!

于 2010-10-07T14:32:05.043 に答える
13

注: これはひどく時代遅れです。これは iOS 2.x の頃からあり、実際には iOS 3.x あたりで修正されています。

歴史的な目的のためだけにここに保管してください。


これに対する明確な解決策を見つけたと思います。それをカプセル化する UIScrollView サブクラスを作成しました。

プログラムによるズーム (+ ダブルタップ処理) とフォト ライブラリ スタイルのページング + ズーム + スクロールの両方を ZoomScrollView クラスと共に示すサンプル コードは、github.com/ andreyvit/ScrollingMadnessで入手できます。

簡単に言えば、私の解決策は から新しいダミー ビューを返し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
于 2009-05-07T21:16:01.057 に答える
0

ダレン、上記のアップルの例へのリンクを提供できますか? それとも見つけやすいようにタイトル?http://developer.apple.com/iphone/library/samplecode/Touches/index.htmlが表示されますが、ズームはカバーされていません。

プログラムによるズーム後に発生する問題は、ジェスチャー ズームによってズームがプログラムによるズームが発生する前の状態に戻ることです。UIScrollView はズーム倍率/レベルについて内部的に状態を保持しているようですが、決定的な証拠はありません。

ありがとう、アンドリュー

編集:ズームファクター1.0のサイズを変更して意味を変更することにより、UIScrollViewの内部ズームファクターをほとんど制御できないという事実を回避していることに気付きました。少しハックですが、すべての Apple が私たちに残したようです。おそらく、カスタムクラスはこのトリックをカプセル化できます...

于 2008-10-18T17:57:30.360 に答える