web-dev-qa-db-ja.com

UIScrollViewズームが自動レイアウトで機能しない

厳密に自動レイアウト環境を使用してUIScrollViewでズームすると、動作しないようです。

IOS 6のリリースノートでは、「純粋な自動レイアウトアプローチ」についてここに書いたときに、間違いなくそれを信じるようになるため、これは特にイライラします http://developer.Apple.com/library/ios/#releasenotes/一般/RN-iOSSDK-6_0/_index.html

セッション202、228、および232のWWDC 2012スライドを確認しましたが、これに対する答えはありませんでした。

特にこの問題についてインターネットで見た唯一の質問は IScrollViewズームと自動レイアウト ですが、問題のコードが提供されておらず、答えはありません。

このユーザー https://stackoverflow.com/users/341994/matt は、UIScrollViewの自動レイアウトの質問に多くの素晴らしい回答を与えており、gitハブのコードにもリンクしていますが、何も見つけることができませんでしたそれがこの問題に答えます。

この問題を明確にするために、この問題を最小限に抑えようとしました。

ストーリーボードを使用して新しいシングルビューアプリケーションを作成しましたが、インターフェイスビルダーは変更しませんでした。

プロジェクト「pic.jpg」に大きな画像ファイルを追加しました。

SVFViewController.h

#import <UIKit/UIKit.h> 
@interface SVFViewController : UIViewController <UIScrollViewDelegate>
@property (nonatomic) UIImageView *imageViewPointer;
@end

SVFViewController.m

#import "SVFViewController.h"

@interface SVFViewController ()

@end

@implementation SVFViewController


- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    UIScrollView *scrollView = [[UIScrollView alloc] init];
    UIImageView *imageView = [[UIImageView alloc] init];
    [imageView setImage:[UIImage imageNamed:@"pic.jpg"]];
    [self.view addSubview:scrollView];
    [scrollView addSubview:imageView];

    scrollView.translatesAutoresizingMaskIntoConstraints = NO;
    imageView.translatesAutoresizingMaskIntoConstraints = NO;
    self.imageViewPointer = imageView;
    scrollView.maximumZoomScale = 2;
    scrollView.minimumZoomScale = .5;
    scrollView.delegate = self;

    NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(scrollView,imageView);
    NSLog(@"Current views dictionary: %@", viewsDictionary);
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
    [scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[imageView]|" options:0 metrics: 0 views:viewsDictionary]];
    [scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[imageView]|" options:0 metrics: 0 views:viewsDictionary]];
}

-(UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView{
    return self.imageViewPointer;
}

@end

IOS 6のリリースノートで提供されているサンプルコードと同じようにこれを行うために特別な努力をしていることに注意してください。

それで、問題?

このアプリケーションを実行してスクロールビューでパンすると、すべてがうまくいきます。しかし、ズームすると問題が明白になり、画像が前後にちらつき、スクロールビュー内の画像の配置がズームごとにさらに悪くなります。

ImageViewのコンテンツオフセットをめぐって争いが起こっているようです。「ズーム」ごとに2つの異なるものによって異なる値に設定されているようです。 (これを確認するために、imageViewのコンテンツオフセットプロパティのNSLogが表示されます)。

ここで何が悪いのですか?純粋な自動レイアウト環境でUIScrollView内にズームをプロパティ実装する方法を知っている人はいますか?これの例はどこかにありますか?

助けてください。

38
Corey

もう一度、もう一度読んでください iOS SDK 6.0リリースノート 私はそれを見つけました:

スクロールビューのスーパービューなど、ビューとスクロールビューのサブツリーの外側のビューの間に制約を作成することにより、スクロールビューのサブビューを他のスクロールコンテンツの上に浮かび上がらせて(スクロールではなく)見せることができます。

解決

サブビューを外部ビューに接続します。つまり、scrollviewが埋め込まれているビューに対して

そして、私はそれを機能させる次の方法で制約を適用します:

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[imageView(width)]" options:0 metrics:@{@"width":@(self.imageViewPointer.image.size.width)} views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[imageView(height)]" options:0 metrics:@{@"height":@(self.imageViewPointer.image.size.height)} views:viewsDictionary]];
14

発生する問題は、ズームプロセス中の画像ビューの場所の変更です。画像ビューの原点の位置は、ズーム中に負の値に変わります。これがぎくしゃくした動きが発生する理由だと思います。また、ズームが完了した後も、imageViewは誤った場所にあり、スクロールがオフセットされているように見えます。

この間に-(void) scrollViewDidZoom:(UIScrollView *)scrollViewを実装してUIImageViewのフレームをログに記録すると、Originの変化を確認できます。

私は this のような戦略を実装することで物事をうまくさせてしまいました

さらに、ズーム中にcontentViewのフレームを変更します

-(void) scrollViewDidZoom:(UIScrollView *)scrollView {
    CGRect cFrame = self.contentView.frame;
    cFrame.Origin = CGPointZero;
    self.contentView.frame = cFrame;
}
10
Brett

これらのソリューションはすべてうまくいきます。これは私がやったことです、このセットアップでハックやサブクラスは必要ありません:

[view]
  [scrollView]
    [container]
      [imageView]
      [imageView2]
  1. IBでは、scrollViewの先頭、先頭、末尾、末尾をviewに接続します。
  2. containerの先頭、先頭、末尾、末尾をscrollViewに接続します。
  3. containerのc​​enter-xとcenter-yをscrollViewのc​​enter-xとcenter-yに接続そしてとしてマークを付けるビルド時間。これは、IBの警告を黙らせるためにのみ必要です。
  4. 実装viewForZoomingInScrollView:ビューコントローラーで、scrollViewのデリゲートである必要があり、そのメソッドからcontainerを返します。
  5. imageViewの画像を設定するときは、最小ズームスケールを決定し(現在はネイティブサイズで表示されます)、設定します。

    CGSize mySize = self.view.bounds.size;
    CGSize imgSize = _imageView.image.size;
    CGFloat scale = fminf(mySize.width / imgSize.width,
                          mySize.height / imgSize.height);
    _scrollView.minimumZoomScale = scale;
    _scrollView.zoomScale = scale;
    _scrollView.maximumZoomScale = 4 * scale;
    

これは私にとっては機能します。画像を設定すると、スクロールビューが画像全体を表示するようにズームし、初期サイズの4倍に拡大できます。

3
Pascal

UIImageView」内の「UIScrollView」内のストーリーボード「UIView」にあるとします。

UIScrollView」のすべての制約をビューコントローラー+ UIViewの2つの制約にリンクします(Horizontal Space - Scroll View - ViewHorizontal Space - Scroll View - View)。

UIScrollView」のビューコントローラのASデリゲートを設定します。

次に、このコードを実装します。

@interface VC () <UIScrollViewDelegate>
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray* constraints;
@end

@implementation FamilyTreeImageVC

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.scrollView removeConstraints:self.constraints];
}


- (UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    return self.imageView;
}

-(void) scrollViewDidZoom:(UIScrollView *)scrollView {
    CGRect cFrame = self.imageView.frame;
    cFrame.Origin = CGPointZero;
    self.imageView.frame = cFrame;
}
1
Hizabr

ScrollViewのみを使用してストーリーボードプロジェクトからズームを実装しようとすると、同じ問題が発生しました。

別のピンチジェスチャー認識機能を追加して修正しました。ツールボックスからシーンにドラッグしました。次に、ズームを実装する "doPinch"と呼ばれるアクションに接続しました。 「pinchRecognizer」と呼ばれるコンセントに接続して、scaleプロパティにアクセスできるようにしました。これは、scrollViewの組み込みズームをオーバーライドするようで、ジャンプが消えます。多分それは起源と同じ間違いをしないか、より優雅にそれを扱います。 IBのレイアウト上での作業はほとんどありません。

ピンチジェスチャー認識機能をシーンに導入したらすぐに、actionメソッドとviewForZoomingInScrollViewメソッドの両方が必要になります。どちらかを外すと、ズームが機能しなくなります。

私のView Controllerのコードはこれです:

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.zoomableImage;
}

- (IBAction)doPinch:(id)sender

{
NSLog(@"In the pinch action now with scale: %f", self.pinchRecognizer.scale);
[scrollView setZoomScale:self.pinchRecognizer.scale animated:NO];
}

この非常に基本的な実装には副作用があります。ズームされた画像に戻ってさらにズームすると、スケールの値は1.0fになるため、元のスケールに戻ります。

これを整理するには、「currentScale」プロパティを導入してスケールを追跡し、ズームを再開したときにピンチジェスチャー認識スケールを設定します。ジェスチャーレコグナイザーの状態プロパティを使用する必要があります。

- (IBAction)doPinch:(id)sender
{
NSLog(@"In the pinch action now with scale: %f", self.pinchRecognizer.scale);
NSLog(@"Gesture recognizer state is: %d", self.pinchRecognizer.state);

switch (self.pinchRecognizer.state)
{
    case 1:
        NSLog(@"Zoom begins, with current scale set to: %f", self.currentScale);
        [self.pinchRecognizer setScale:self.currentScale];
        break;
    case 3:
        NSLog(@"Zoom ends, with pinch recognizer scale set to: %f", self.pinchRecognizer.scale);
        self.currentScale = self.pinchRecognizer.scale;
    default:
        break;
}

[scrollView setZoomScale:self.pinchRecognizer.scale animated:NO];
}
0
Tim

これが私がなんとか解決したものです。

これが私の変更したオリジナルです:

@interface ScrollViewZoomTestViewController () <UIScrollViewDelegate>

@property (nonatomic, strong) UIImageView* imageViewPointer;

// These properties are new
@property (nonatomic, strong) NSMutableArray* imageViewConstraints;
@property (nonatomic) BOOL imageViewConstraintsNeedUpdating;
@property (nonatomic, strong) UIScrollView* scrollViewPointer;

@end

@implementation ScrollViewZoomTestViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIScrollView *scrollView = [[UIScrollView alloc] init];
    UIImageView *imageView = [[UIImageView alloc] init];
    [imageView setImage:[UIImage imageNamed:@"pic.jpg"]];
    [self.view addSubview:scrollView];
    [scrollView addSubview:imageView];

    scrollView.translatesAutoresizingMaskIntoConstraints = NO;
    imageView.translatesAutoresizingMaskIntoConstraints = NO;
    self.imageViewPointer = imageView;
    // New
    self.scrollViewPointer = scrollView;
    scrollView.maximumZoomScale = 2;
    scrollView.minimumZoomScale = .5;
    scrollView.delegate = self;

    NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(scrollView, imageView);
    NSLog(@"Current views dictionary: %@", viewsDictionary);
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];

    // Saving the image view width & height constraints
    self.imageViewConstraints = [[NSMutableArray alloc] init];
    // Constrain the image view to be the same width & height of the scroll view
    [_imageViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[imageView(scrollView)]|" options:0 metrics: 0 views:viewsDictionary]];
    [_imageViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[imageView(scrollView)]|" options:0 metrics: 0 views:viewsDictionary]];

    // Add the image view constraints to the VIEW, not the scroll view
    [self.view addConstraints:_imageViewConstraints];

    // Flag
    self.imageViewConstraintsNeedUpdating = YES;
}

ここで要約すると、すべての制約をself.viewに追加し、UIImageViewに設定された制約をNSMutableArrayプロパティに保存し、UIImageView制約を更新する必要があります。

UIImageViewに対するこれらの初期制約は、最初から設定するように機能します。 UIScrollViewと同じ幅と高さになります。ただし、これでは画像ビューをズームできません。スクロールビューと同じwidth/heightを維持します。私たちが望むものではありません。これが、制約を保存してフラグを設定する理由です。すぐに対処します。

次に、ズームするビューを設定します。

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
    return self.imageViewPointer;
}

では、実際にズームできるようにする必要があります。最初のUIImageView制約を削除して新しい制約をいくつか追加します。今回はUIScrollViewcontentSizewidthheightに制約します:

- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
{
    if(_imageViewConstraintsNeedUpdating)
    {
        // Remove the previous image view constraints
        [self.view removeConstraints:_imageViewConstraints];

        // Replace them with new ones, this time constraining against the `width` & `height` of the scroll view's content, not the scroll view itself
        NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(_scrollViewPointer, _imageViewPointer);
        [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_imageViewPointer(width)]|" options:0 metrics:@{@"width" : @(_scrollViewPointer.contentSize.width)} views:viewsDictionary]];
        [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_imageViewPointer(height)]|" options:0 metrics:@{@"height" : @(_scrollViewPointer.contentSize.height)} views:viewsDictionary]];

        self.imageViewConstraintsNeedUpdating = NO;
    }
}

@end

画像がUIImageViewにまだレンダリングされていないため、-viewDidLoadでこのように制約を設定することはできません。したがって、UIScrollViewcontentSize{0,0}になります。

これは私にはかなりハックに思えますが、機能します。純粋な自動レイアウトを使用します。それを行うためのより良い方法を見つけることができません。 Appleは、UIScrollViewでコンテンツをズームするためのより良い方法を提供する必要があり、自動レイアウト制約を使用する必要があります。

0
Jordan Bondo