1

カジュアルな読者への注意:タイトルにもかかわらず、この質問はUIScrollViewプロパティbounces(スクロール関連) またはbouncesZoom.

UIScrollViewカスタムビューにズームを追加するために使用しています。カスタム ビューはサブレイヤーを使用してコンテンツを描画します。各サブレイヤはCALayer、ビューのメイン レイヤに で追加されるインスタンスです[CALayer addSublayer:]。サブレイヤーは CoreGraphics を使用してコンテンツをレンダリングします。

各ズームが完了すると、カスタム ビューは新しいズーム スケールでコンテンツを再描画して、コンテンツが再びくっきりとシャープに表示されるようにする必要があります。私は現在、このSOの質問zoomScaleに示されている作業へのアプローチを取得しようとしています。つまり、各ズーム操作の後にスクロールビューのプロパティを1.0にリセットし、minimumZoomScaleプロパティを調整しmaximumZoomScaleて、ユーザーが元よりもズームイン/ズームアウトできないようにします意図されました。

コンテンツの再描画は既に正しく機能しています (!) が、私が見逃しているのは、ズームされたコンテンツが移動しているように見えずに再描画されるスムーズな GUI の更新です。私の現在のソリューション (コード例はこの質問の最後にあります) では、一種の「バウンス」効果が観察されます: ズーム操作が終了するとすぐに、ズームされたコンテンツは一時的に別の場所に移動し、すぐに元の場所に戻ります。位置。

「バウンス」効果の理由が完全にはわかりません: 2 つの GUI 更新サイクル ( zoomScale1.0 にリセットするためsetNeedsDisplayのものと .他の後。

私の質問は、上記の「バウンス」効果をどのように防ぐことができますか?

更新:以下は、私が話している効果を観察するために単にコピーして貼り付けることができる最小限の完全なコード例です。

  1. 「空のアプリケーション」テンプレートを使用して、新しい Xcode プロジェクトを作成します。
  2. 以下のコードをAppDelegate.hAppDelegate.mにそれぞれ追加します。
  3. プロジェクトのリンク ビルド フェーズで、 への参照を追加します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
4

2 に答える 2

2

サンプル プロジェクトに基づいて、重要なのは、CALayer を直接操作していることです。デフォルトでは、フレームなどの CALayer プロパティを設定すると、アニメーションが発生します。使用する提案[UIView setAnimationsEnabled:NO]は正しい軌道に乗っていましたが、UIView ベースのアニメーションにのみ影響します。CALayer と同等のことを行う場合は、 setNeedsDisplay: メソッドで次のように言います。

[CATransaction begin];
[CATransaction setDisableActions:YES];
self.layerDelegate.layer.frame = self.bounds;
[CATransaction commit];

暗黙のフレーム変更アニメーションを防ぎ、私にはぴったりです。LayerDelegate クラスの CALayerDelegate メソッドを使用して、これらの暗黙的なアニメーションを無効にすることもできます。

- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
    return (id)[NSNull null]; // NSNull means "don't do any implicit animations"
}

元の提案:

もしかしたら、知らないうちにアニメーション ブロックに入っているのではないでしょうか? または、呼び出しているメソッドの 1 つがアニメーション ブロックの設定ではないでしょうか。[UIView setAnimationsEnabled:NO]コードの前に、後で再度有効にするとどうなりますか?

アニメーションでない場合は、おそらくご想像のとおりです。ある種の 2 つのビューの更新。(おそらく、スクロール ビューからの 1 つと、何らかの形でのコードからの 1 つですか?) その場合、実行可能なサンプル コードが最適です。

(好奇心から、ズーム レベルをごまかすのではなく、CALayer の shouldRasterize と rasterizationScale を使用してみましたか?)

于 2013-03-12T20:22:17.237 に答える
-2

X Code ユーザー インターフェイス ビルダーには、Bounce設定があります (下にありScroll Viewます)。

于 2013-03-12T19:56:56.680 に答える