カジュアルな読者への注意:タイトルにもかかわらず、この質問はUIScrollView
プロパティbounces
(スクロール関連) またはbouncesZoom
.
UIScrollView
カスタムビューにズームを追加するために使用しています。カスタム ビューはサブレイヤーを使用してコンテンツを描画します。各サブレイヤはCALayer
、ビューのメイン レイヤに で追加されるインスタンスです[CALayer addSublayer:]
。サブレイヤーは CoreGraphics を使用してコンテンツをレンダリングします。
各ズームが完了すると、カスタム ビューは新しいズーム スケールでコンテンツを再描画して、コンテンツが再びくっきりとシャープに表示されるようにする必要があります。私は現在、このSOの質問zoomScale
に示されている作業へのアプローチを取得しようとしています。つまり、各ズーム操作の後にスクロールビューのプロパティを1.0にリセットし、minimumZoomScale
プロパティを調整しmaximumZoomScale
て、ユーザーが元よりもズームイン/ズームアウトできないようにします意図されました。
コンテンツの再描画は既に正しく機能しています (!) が、私が見逃しているのは、ズームされたコンテンツが移動しているように見えずに再描画されるスムーズな GUI の更新です。私の現在のソリューション (コード例はこの質問の最後にあります) では、一種の「バウンス」効果が観察されます: ズーム操作が終了するとすぐに、ズームされたコンテンツは一時的に別の場所に移動し、すぐに元の場所に戻ります。位置。
「バウンス」効果の理由が完全にはわかりません: 2 つの GUI 更新サイクル ( zoomScale
1.0 にリセットするためsetNeedsDisplay
のものと .他の後。
私の質問は、上記の「バウンス」効果をどのように防ぐことができますか?
更新:以下は、私が話している効果を観察するために単にコピーして貼り付けることができる最小限の完全なコード例です。
- 「空のアプリケーション」テンプレートを使用して、新しい Xcode プロジェクトを作成します。
- 以下のコードを
AppDelegate.h
とAppDelegate.m
にそれぞれ追加します。 - プロジェクトのリンク ビルド フェーズで、 への参照を追加します
QuartzCore.framework
。
入るものAppDelegate.h
:
#import <UIKit/UIKit.h>
@class LayerView;
@interface AppDelegate : UIResponder <UIApplicationDelegate, UIScrollViewDelegate>
@property (nonatomic, retain) UIWindow* window;
@property (nonatomic, retain) LayerView* layerView;
@end
入るものAppDelegate.m
:
#import "AppDelegate.h"
#import <QuartzCore/QuartzCore.h>
@class LayerDelegate;
@interface LayerView : UIView
@property (nonatomic, retain) LayerDelegate* layerDelegate;
@end
@interface LayerDelegate : NSObject
@property(nonatomic, retain) CALayer* layer;
@property (nonatomic, assign) CGFloat zoomScale;
@end
static CGFloat kMinimumZoomScale = 1.0;
static CGFloat kMaximumZoomScale = 5.0;
@implementation AppDelegate
- (void) dealloc
{
self.window = nil;
self.layerView = nil;
[super dealloc];
}
- (BOOL) application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
[UIApplication sharedApplication].statusBarHidden = YES;
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
self.window.backgroundColor = [UIColor whiteColor];
UIScrollView* scrollView = [[[UIScrollView alloc] initWithFrame:self.window.bounds] autorelease];
[self.window addSubview:scrollView];
scrollView.contentSize = scrollView.bounds.size;
scrollView.delegate = self;
scrollView.minimumZoomScale = kMinimumZoomScale;
scrollView.maximumZoomScale = kMaximumZoomScale;
scrollView.zoomScale = 1.0f;
scrollView.bouncesZoom = NO;
self.layerView = [[[LayerView alloc] initWithFrame:scrollView.bounds] autorelease];
[scrollView addSubview:self.layerView];
[self.window makeKeyAndVisible];
return YES;
}
- (UIView*) viewForZoomingInScrollView:(UIScrollView*)scrollView
{
return self.layerView;
}
- (void) scrollViewDidEndZooming:(UIScrollView*)scrollView withView:(UIView*)view atScale:(float)scale
{
CGPoint contentOffset = scrollView.contentOffset;
CGSize contentSize = scrollView.contentSize;
scrollView.maximumZoomScale = scrollView.maximumZoomScale / scale;
scrollView.minimumZoomScale = scrollView.minimumZoomScale / scale;
// Big change here: This resets the scroll view's contentSize and
// contentOffset, and also the LayerView's frame, bounds and transform
// properties
scrollView.zoomScale = 1.0f;
CGFloat newZoomScale = self.layerView.layerDelegate.zoomScale * scale;
self.layerView.layerDelegate.zoomScale = newZoomScale;
self.layerView.frame = CGRectMake(0, 0, contentSize.width, contentSize.height);
scrollView.contentSize = contentSize;
[scrollView setContentOffset:contentOffset animated:NO];
[self.layerView setNeedsDisplay];
}
@end
@implementation LayerView
- (id) initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.layerDelegate = [[[LayerDelegate alloc] init] autorelease];
[self.layer addSublayer:self.layerDelegate.layer];
// super's initWithFrame already invoked setNeedsDisplay, but we need to
// repeat because at that time our layerDelegate property was still empty
[self setNeedsDisplay];
}
return self;
}
- (void) dealloc
{
self.layerDelegate = nil;
[super dealloc];
}
- (void) setNeedsDisplay
{
[super setNeedsDisplay];
// Zooming changes the view's frame, but not the frame of the layer
self.layerDelegate.layer.frame = self.bounds;
[self.layerDelegate.layer setNeedsDisplay];
}
@end
@implementation LayerDelegate
- (id) init
{
self = [super init];
if (self)
{
self.layer = [CALayer layer];
self.layer.delegate = self;
self.zoomScale = 1.0f;
}
return self;
}
- (void) dealloc
{
self.layer = nil;
[super dealloc];
}
- (void) drawLayer:(CALayer*)layer inContext:(CGContextRef)context
{
CGRect layerRect = self.layer.bounds;
CGFloat radius = 25 * self.zoomScale;
CGFloat centerDistanceFromEdge = 5 * self.zoomScale + radius;
CGPoint topLeftCenter = CGPointMake(CGRectGetMinX(layerRect) + centerDistanceFromEdge,
CGRectGetMinY(layerRect) + centerDistanceFromEdge);
[self drawCircleWithCenter:topLeftCenter radius:radius fillColor:[UIColor redColor] inContext:context];
CGPoint layerCenter = CGPointMake(CGRectGetMidX(layerRect), CGRectGetMidY(layerRect));
[self drawCircleWithCenter:layerCenter radius:radius fillColor:[UIColor greenColor] inContext:context];
CGPoint bottomRightCenter = CGPointMake(CGRectGetMaxX(layerRect) - centerDistanceFromEdge,
CGRectGetMaxY(layerRect) - centerDistanceFromEdge);
[self drawCircleWithCenter:bottomRightCenter radius:radius fillColor:[UIColor blueColor] inContext:context];
}
- (void) drawCircleWithCenter:(CGPoint)center
radius:(CGFloat)radius
fillColor:(UIColor*)color
inContext:(CGContextRef)context
{
const int startRadius = [self radians:0];
const int endRadius = [self radians:360];
const int clockwise = 0;
CGContextAddArc(context, center.x, center.y, radius,
startRadius, endRadius, clockwise);
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillPath(context);
}
- (double) radians:(double)degrees
{
return degrees * M_PI / 180;
}
@end