小さい場合、UIScrollViewの中央のコンテンツ


140

私はズームとスクロールに使用するUIImageView内部を持っていUIScrollViewます。スクロールビューの画像/コンテンツがスクロールビューよりも大きい場合、すべてが正常に機能します。ただし、画像がスクロールビューよりも小さくなると、スクロールビューの左上隅に固定されます。フォトアプリのように、中央に配置したいと思います。

UIScrollViewそれが小さいときに中央のコンテンツを中央に保つことについてのアイデアや例はありますか?

iPhone 3.0を使用しています。

次のコードはほとんど機能します。最小ズームレベルに達した後で画像をピンチすると、画像が左上隅に戻ります。

- (void)loadView {
    [super loadView];

    // set up main scroll view
    imageScrollView = [[UIScrollView alloc] initWithFrame:[[self view] bounds]];
    [imageScrollView setBackgroundColor:[UIColor blackColor]];
    [imageScrollView setDelegate:self];
    [imageScrollView setBouncesZoom:YES];
    [[self view] addSubview:imageScrollView];

    UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"WeCanDoIt.png"]];
    [imageView setTag:ZOOM_VIEW_TAG];
    [imageScrollView setContentSize:[imageView frame].size];
    [imageScrollView addSubview:imageView];

    CGSize imageSize = imageView.image.size;
    [imageView release];

    CGSize maxSize = imageScrollView.frame.size;
    CGFloat widthRatio = maxSize.width / imageSize.width;
    CGFloat heightRatio = maxSize.height / imageSize.height;
    CGFloat initialZoom = (widthRatio > heightRatio) ? heightRatio : widthRatio;

    [imageScrollView setMinimumZoomScale:initialZoom];
    [imageScrollView setZoomScale:1];

    float topInset = (maxSize.height - imageSize.height) / 2.0;
    float sideInset = (maxSize.width - imageSize.width) / 2.0;
    if (topInset < 0.0) topInset = 0.0;
    if (sideInset < 0.0) sideInset = 0.0;
    [imageScrollView setContentInset:UIEdgeInsetsMake(topInset, sideInset, -topInset, -sideInset)];
}

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    return [imageScrollView viewWithTag:ZOOM_VIEW_TAG];
}

/************************************** NOTE **************************************/
/* The following delegate method works around a known bug in zoomToRect:animated: */
/* In the next release after 3.0 this workaround will no longer be necessary      */
/**********************************************************************************/
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
    [scrollView setZoomScale:scale+0.01 animated:NO];
    [scrollView setZoomScale:scale animated:NO];
    // END Bug workaround

    CGSize maxSize = imageScrollView.frame.size;
    CGSize viewSize = view.frame.size;
    float topInset = (maxSize.height - viewSize.height) / 2.0;
    float sideInset = (maxSize.width - viewSize.width) / 2.0;
    if (topInset < 0.0) topInset = 0.0;
    if (sideInset < 0.0) sideInset = 0.0;
    [imageScrollView setContentInset:UIEdgeInsetsMake(topInset, sideInset, -topInset, -sideInset)];
}

この問題を完全に解決したことがありますか?私は同じ問題で苦労しています。
ジョナ

1
注意:insetが1ではない(ズームしない)場合は、initialZoom値を使用してインセットを計算します。たとえば、次の行を使用します。float topInset =(maxSize.height-imageSize.height * initialZoom)/ 2.0; float sideInset =(maxSize.width-imageSize.width * initialZoom)/ 2.0; 最後に、初期ズーム値を設定します[imageScrollView setZoomScale:initialZoom];
Felix

回答:


228

私は非常に簡単な解決策を持っています!必要なのは、ScrollViewDelegateを拡大しながら、サブビュー(imageview)の中心を更新することだけです。ズームした画像がscrollviewよりも小さい場合、subview.centerを調整します。それ以外の場合、中心は(0,0)です。

- (void)scrollViewDidZoom:(UIScrollView *)scrollView 
{
    UIView *subView = [scrollView.subviews objectAtIndex:0];

    CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
    CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);

    subView.center = CGPointMake(scrollView.contentSize.width * 0.5 + offsetX, 
                                 scrollView.contentSize.height * 0.5 + offsetY);
}

5
私にとって、このソリューションはLiamのNYOBetterZoomを使用するよりもぎくしゃくしています。多分それは画像のサイズなどに依存します。お客様のニーズに最適なソリューションを使用してください
wuf810 2011

2
このためのStackoverflow GOLD STAR。私はこれに苦労していましたが、解決策はとても簡単です。
n13

2
少し簡略化するには:CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
Marek R

6
この調整は、スクロールビューがはめ込まれていた場合に役立ちました。– CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentInset.left - scrollView.contentInset.right - scrollView.contentSize.width) * 0.5, 0.0); CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentInset.top - scrollView.contentInset.bottom - scrollView.contentSize.height) * 0.5, 0.0);
Kieran Harper

3
他の多くのセンタリング手法と同様に、これを使用すると問題が発生するようzoomToRect:です。このcontentInset機能が必要になった場合は、このアプローチを使用するとうまく機能します。詳細については、petersteinberger.com / blog / 2013 / how-to-center-uiscrollviewを参照してください。
Matej Bukovinski、2015年

51

@EvelynCordnerの答えは、私のアプリで最もよく機能したものでした。他のオプションよりもはるかに少ないコード。

必要な場合のSwiftバージョンは次のとおりです。

func scrollViewDidZoom(_ scrollView: UIScrollView) {
    let offsetX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
    let offsetY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
    scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0, 0)
}

4
これはうまくいきます!ビューは縮小後も適切にアニメーション化されました。
カーターメドリン16

4
このコードをfuncに入れ、viewDidLayoutSubviewsでも呼び出して、最初に正しく設定されていることを確認しました。
Carter Medlin 16

良い電話@CarterMedlinは、Swift 3の初期ロードで非常に役立ちました。
AJヘルナンデス

これは機能しているように見えますが、常に同じインセットを計算しているように見えるため、理由がわかりません。スクロールビューの境界はコンテンツサイズと同様に固定されています。
Vaddadi Kartick 2017年

ズーミングscrollViewサイズのみ固定されている場合contentSizeが変化
のMichałZiobro

25

さて、私はこの2日間オンとオフを繰り返して戦い、最終的にかなり信頼できる(これまでのところ...)ソリューションにたどり着きました。:)このソリューションに問題がある場合は、叫んでください!

私は基本的に他の誰もが持っているものを調べました:StackOverflow、Apple Developer Forums、three20のコード、ScrollingMadness、ScrollTestSuiteなどを探しました。UIImageViewフレームを拡大し、UIScrollViewのオフセットやインセットで遊んでみましたViewControllerなどからですが、(他の誰もが見つけたように)何もうまくいきませんでした

その上で眠った後、私はいくつかの別の角度を試しました:

  1. UIImageViewをサブクラス化して、自身のサイズを動的に変更する-これはまったくうまく機能しませんでした。
  2. UIScrollViewをサブクラス化して、独自のcontentOffsetを動的に変更する-これは私にとって勝者のようです。

このサブクラス化されたUIScrollViewメソッドを使用して、contentOffsetミューテーターをオーバーライドしているため、画像がビューポートよりも小さく拡大されても{0,0}は設定されません。代わりに、画像がビューポートの中央に維持されるようにオフセットが設定されます。これまでのところ、それは常に動作するようです。私はそれを幅が広く、高さがあり、小さくて大きな画像で確認しましたが、「機能しますが、最小のズームでピンチすると画像が壊れる」という問題はありません。

このソリューションを使用するサンプルプロジェクトをgithubにアップロードしました。ここで見つけることができます:http : //github.com/nyoron/NYOBetterZoom


2
興味のある方のために-上記のリンクされたプロジェクトを更新したので、「正しいこと」を行うViewControllerへの依存度が少し低くなり、カスタムUIScrollView自体が詳細を処理します。
Liam Jones

2
リアム、あなたはロックします。ScrollingMadnessの作者としてこれを言っています。3.2以上のiPadでのBTWのscrollViewDidZoomでのcontentInsetの設定(新しい3.2以上のデリゲートメソッド)が機能します。
Andrey Tarantsov

非常にきちんとしたコード。私はこれでしばらく頭を悩ませてきました。プロジェクトをhandyiphonecode.comに
samvermette

これは素晴らしい。ありがとうございました。私はラインを変えたように、それは、しかし隠されて私のUIStatusBarを補償されなかったanOffset.y = -(scrollViewSize.height - zoomViewSize.height) / 2.0anOffset.y = (-(scrollViewSize.height - zoomViewSize.height) / 2.0) + 10;
ゴードンFontenot

私の意見では、bi Erdemusの解ははるかに単純です。
ぎょぞうクドール

23

このコードは、iOSのほとんどのバージョンで動作するはずです(3.1以降で動作するようにテストされています)。

これは、photoscollerのApple WWDCコードに基づいています。

以下をUIScrollViewのサブクラスに追加し、tileContainerViewを画像またはタイルを含むビューに置き換えます。

- (void)layoutSubviews {
    [super layoutSubviews];

    // center the image as it becomes smaller than the size of the screen
    CGSize boundsSize = self.bounds.size;
    CGRect frameToCenter = tileContainerView.frame;

    // center horizontally
    if (frameToCenter.size.width < boundsSize.width)
        frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
    else
        frameToCenter.origin.x = 0;

    // center vertically
    if (frameToCenter.size.height < boundsSize.height)
        frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
    else
        frameToCenter.origin.y = 0;

    tileContainerView.frame = frameToCenter;
}

3
これは受け入れられる答えであるはずです。それがより簡単だからです。ありがとう。あなたは私の命を救いました!!!!!!:D
ダック

すべてのiOS 3.2+ API呼び出しを削除した後、センタリングロジックはiOS 3.2+を搭載したデバイスでのみ機能し、3.1.3では機能しないように見えます(ジャンプ、ちらつき、ランダムオフセットを取得)。フレームの原点とサイズの出力を3.1.3と3.2+の間で比較しましたが、それらが一致しても、何らかの理由で、子ビューが依然として正しく配置されていません。非常に奇妙な。リアム・ジョーンの答えだけがうまくいきました。
デビッドH

1
@Erdemusと@JosephHの両方のソリューションは機能しますが、UIScrollViewサブクラスアプローチが望ましいようです。これは、ビューが最初に表示されたときに呼び出されます+ズームの進行中に継続的にscrollViewDidZoom呼び出されます(一方、スクロールごとに1回と、その後にのみ呼び出されます)
SwiftArchitect

22

自動レイアウトを使用するスクロールビューにより適したソリューションの場合は、スクロールビューのサブビューのフレームを更新するのではなく、スクロールビューのコンテンツインセットを使用してください。

- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
    CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
    CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);

    self.scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0.f, 0.f);
}

1
これは私にとってはうまくいきましたが、画像がどういうわけか最初に小さい場合、このコード(直接呼び出されたとき)はスクロールビューを更新しませんでした。私はTODOを必要とすることは最初に置かれたUIView内部にあるものUIScrollviewと同じ中央に(view.center = scrollview.center;)し、その後にscrollViewDidZoomセットview.frame.origin xしてy0もう一度を。
morksinaanab

私も同じように機能しますが、メインスクロールビューを呼び出すdispatch_asyncメインキューにviewWillAppear移動しましたscrollViewDidZoom:。これにより、ビューが中央に配置された画像で表示されます。
Pascal、

このコードを呼び出してviewDidLayoutSubviews、ビューが初めて表示されたときにコードが適切に設定されていることを確認します。
カーターメドリン16

21

現在、に基づいてオフセットを調整するためにサブクラス化UIScrollViewおよびオーバーライドsetContentOffset:していcontentSizeます。ピンチとプログラムズームの両方で機能します。

@implementation HPCenteringScrollView

- (void)setContentOffset:(CGPoint)contentOffset
{
    const CGSize contentSize = self.contentSize;
    const CGSize scrollViewSize = self.bounds.size;

    if (contentSize.width < scrollViewSize.width)
    {
        contentOffset.x = -(scrollViewSize.width - contentSize.width) / 2.0;
    }

    if (contentSize.height < scrollViewSize.height)
    {
        contentOffset.y = -(scrollViewSize.height - contentSize.height) / 2.0;
    }

    [super setContentOffset:contentOffset];
}

@end

このコードは短くて甘いだけでなく、@ Erdemusソリューションよりもはるかにスムーズなズームを生成します。RMGalleryデモで実際の動作を確認できます。


6
このメソッドを実装するためにUIScrollViewをサブクラス化する必要はありません。Appleでは、デリゲートメソッドでスクロールイベントとズームイベントを確認できます。具体的には、-(void)scrollViewDidZoom:(UIScrollView *)scrollView;
Rog

1
私が今まで見た中で最高のソリューション(制約を使用しているときでも機能します)
Frizlab、2015

12

私はこの問題との戦いに1日費やし、次のようにscrollViewDidEndZooming:withView:atScale:を実装することになりました

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
    CGFloat screenWidth = [[UIScreen mainScreen] bounds].size.width;
    CGFloat screenHeight = [[UIScreen mainScreen] bounds].size.height;
    CGFloat viewWidth = view.frame.size.width;
    CGFloat viewHeight = view.frame.size.height;

    CGFloat x = 0;
    CGFloat y = 0;

    if(viewWidth < screenWidth) {
        x = screenWidth / 2;
    }
    if(viewHeight < screenHeight) {
        y = screenHeight / 2 ;
    }

    self.scrollView.contentInset = UIEdgeInsetsMake(y, x, y, x);
}

これにより、画像が画面よりも小さい場合でも、適切な場所に配置できるように、画像の周囲に十分なスペースが確保されます。

(UIScrollViewに画像を保持するUIImageViewが含まれていると想定)

基本的に、これは画像ビューの幅/高さが画面の幅/高さよりも小さいかどうかを確認し、そうである場合は、画面の幅/高さの半分のはめ込みを作成します(画像が必要な場合は、これを大きくすることができます)画面の境界から出ます)。

これはUIScrollViewDelegateメソッドなので、ビルドの問題が発生しないように、ビューコントローラーの宣言に追加することを忘れないでください。


7

Appleは2010 WWDCセッションのビデオをiphone開発者プログラムのすべてのメンバーに公開しました。議論されているトピックの1つは、写真アプリの作成方法です!!! 非常によく似たアプリを段階的に構築し、すべてのコードを無料で利用できるようにしました。

プライベートAPIも使用していません。非開示契約のため、ここにコードを配置することはできませんが、ここにサンプルコードのダウンロードへのリンクがあります。アクセスするには、ログインする必要があります。

http://connect.apple.com/cgi-bin/WebObjects/MemberSite.woa/wa/getSoftware?code=y&source=x&bundleID=20645

そして、これがiTunes WWDCページへのリンクです。

http://insideapple.apple.com/redir/cbx-cgi.do?v=2&la=en&lc=&a=kGSol9sgPHP%2BtlWtLp%2BEP%2FnxnZarjWJglPBZRHd3oDbACudP51JNGS8KlsFgxZto9X%2BTsnqSbeUSWX0doe%2Fzv%2FN5XV55%2FomsyfRgFBysOnIVggO%2Fn2p%2BiweDK%2F%2FmsIXj


1
問題の例はMyImagePickerで、陽気に、これと同じ問題が発生します。
コリンバレット

1
もっとはっきりしていたはずです。問題の例は、実際には「MyImagePicker」ではなく「PhotoScroller」です。「MyImagePicker」が正しく機能しないのはあなたのとおりです。しかし、「PhotoScroller」はそうです。それを試してみてください。
ジョナ

フォトスクローラーについて話しているWWDCビデオのタイトルを覚えていますか?
Grzegorz Adam Hankiewicz

1
それは「スクロールビューでアプリをデザインする」と呼ばれています。
ジョナ

2

わかりました、このソリューションは私のために働いています。私はそれが表示UIScrollViewしてUIImageViewいるものへの参照を持つサブクラスを持っています。UIScrollViewズームするたびに、contentSizeプロパティが調整されます。UIImageView適切にスケーリングし、中心位置を調整するのはセッターです。

-(void) setContentSize:(CGSize) size{
CGSize lSelfSize = self.frame.size;
CGPoint mid;
if(self.zoomScale >= self.minimumZoomScale){
    CGSize lImageSize = cachedImageView.initialSize;
    float newHeight = lImageSize.height * self.zoomScale;

    if (newHeight < lSelfSize.height ) {
        newHeight = lSelfSize.height;
    }
    size.height = newHeight;

    float newWidth = lImageSize.width * self.zoomScale;
    if (newWidth < lSelfSize.width ) {
        newWidth = lSelfSize.width;
    }
    size.width = newWidth;
    mid = CGPointMake(size.width/2, size.height/2);

}
else {
    mid = CGPointMake(lSelfSize.width/2, lSelfSize.height/2);
}

cachedImageView.center = mid;
[super  setContentSize:size];
[self printLocations];
NSLog(@"zoom %f setting size %f x %f",self.zoomScale,size.width,size.height);
}

いつでも私はUIScrollViewそれのサイズを変更した上に画像を設定しました。UIScrollViewscrollviewでも、私が作成したカスタムクラスです。

-(void) resetSize{
    if (!scrollView){//scroll view is view containing imageview
        return;
    }

    CGSize lSize = scrollView.frame.size;

    CGSize lSelfSize = self.image.size; 
    float lWidth = lSize.width/lSelfSize.width;
    float lHeight = lSize.height/lSelfSize.height;

    // choose minimum scale so image width fits screen
    float factor  = (lWidth<lHeight)?lWidth:lHeight;

    initialSize.height = lSelfSize.height  * factor;
    initialSize.width = lSelfSize.width  * factor;

    [scrollView setContentSize:lSize];
    [scrollView setContentOffset:CGPointZero];
    scrollView.userInteractionEnabled = YES;
}

これらの2つの方法で、写真アプリと同じように動作するビューを作成できます。


これでうまくいくようで、かなり簡単な解決策です。一部のズーム遷移は完全ではありませんが、修正できると思います。もう少し実験してみます。
hpique

更新前の解決策は明確でした。今は少し混乱しています。明確にできますか?
hpique

2

これを行う方法は、階層にビューを追加することです。

UIScrollView -> UIView -> UIImageView

あなた与えるUIViewあなたと同じアスペクト比をUIScrollView、そしてあなたの中心UIImageViewにいるの。


ありがとう、hatfinch。これも試します。ビュー階層を構築する方法を示すために、サンプルコードを投稿したり、サンプルコードを変更したりできますか?
hpique 2009

この種の作品。UIViewはUIScrollViewのサイズであるため、画像が小さい場合(つまり、縦ではなく横)、画面の外に画像の一部をスクロールできるという事実を除きます。写真アプリはこれを許可しておらず、ずっと見栄えが良いです。
ジョナ

2

上記の回答が正しいことはわかっていますが、説明を付けて回答をお伝えしたいと思います。コメントを読むと、このような理由がわかります。

scrollViewを初めてロードするとき、次のコードを記述して中央に配置します。contentOffset最初に設定してから、contentInset

    scrollView.maximumZoomScale = 8
    scrollView.minimumZoomScale = 1

    // set vContent frame
    vContent.frame = CGRect(x: 0,
                            y: 0  ,
                            width: vContentWidth,
                            height: vContentWidth)
    // set scrollView.contentSize
    scrollView.contentSize = vContent.frame.size

    //on the X direction, if contentSize.width > scrollView.bounds.with, move scrollView from 0 to offsetX to make it center(using `scrollView.contentOffset`)
    // if not, don't need to set offset, but we need to set contentInset to make it center.(using `scrollView.contentInset`)
    // so does the Y direction.
    let offsetX = max((scrollView.contentSize.width - scrollView.bounds.width) * 0.5, 0)
    let offsetY = max((scrollView.contentSize.height - scrollView.bounds.height) * 0.5, 0)
    scrollView.contentOffset = CGPoint(x: offsetX, y: offsetY)

    let topX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
    let topY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
    scrollView.contentInset = UIEdgeInsets(top: topY, left: topX, bottom: 0, right: 0)

次に、vContentをピンチすると、次のコードを記述して中央に配置します。

func scrollViewDidZoom(_ scrollView: UIScrollView) {
    //we just need to ensure that the content is in the center when the contentSize is less than scrollView.size.
    let topX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
    let topY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
    scrollView.contentInset = UIEdgeInsets(top: topY, left: topX, bottom: 0, right: 0)
}

2

contentInset他に何も必要ない場合は、スクロールビューのコンテンツを中央に配置するために使用できます。

class ContentCenteringScrollView: UIScrollView {

    override var bounds: CGRect {
        didSet { updateContentInset() }
    }

    override var contentSize: CGSize {
        didSet { updateContentInset() }
    }

    private func updateContentInset() {
        var top = CGFloat(0)
        var left = CGFloat(0)
        if contentSize.width < bounds.width {
            left = (bounds.width - contentSize.width) / 2
        }
        if contentSize.height < bounds.height {
            top = (bounds.height - contentSize.height) / 2
        }
        contentInset = UIEdgeInsets(top: top, left: left, bottom: top, right: left)
    }
}

このアプローチがcontentLayoutGuideスクロールビュー内にコンテンツを配置するために引き続き使用できる場合の利点

scrollView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    imageView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
    imageView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
    imageView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
    imageView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor)
])

または、XcodeのInterface Builderでコンテンツをドラッグアンドドロップするだけです。


1

contentSize(Key-Value監視などを使用して)UIScrollView のプロパティを監視しcontentInsetcontentSize変更がスクロールビューのサイズより小さくなるようにいつでも自動的に調整できます。


しようとします。これは、contentSizeを監視する代わりに、UIScrollViewDelegateメソッドを使用して実行できますか?
hpique 2009

最も可能性が高い; - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scaledeveloper.apple.com/iphone/library/documentation/UIKit/…で説明されています)を使用しますがcontentSize、それ以外の場合は新しいズームスケールを破棄してビューから検索するため、観察することをお勧めします。(ただし、ビューの相対的なサイズに基づいて素晴らしい数学を実行できない場合
Tim

ありがとう、ティム。scrollViewDidEndZoomingは私が使用しているものです。問題のコードを参照してください(最初の返信の後に追加しました)。ほとんど動作します。残っている唯一の問題は、minimumZoomScaleに達した後に画像をピンチすると、左上隅に戻ることです。
hpique 2009

それは、最小ズームスケール以外のスケールからピンチする場合に機能し(画像を中央に配置し)、最小スケールになったらもう一度ピンチしようとしたときにのみ壊れることを意味しますか?それとも、最小スケールへのピンチにはまったく機能しませんか?
ティム

最初の1つ。最小ズームスケール以外のスケールから画像をピンチするために画像を中央に配置し、最小スケールになったらもう一度ピンチしようとすると画像が壊れます。また、最初に最小ズームスケールに達したとき、コンテンツは上から来るように短時間アニメーション化され、短い奇妙な効果を引き起こします。これは、少なくとも3.0シミュレータで発生します。
hpique 2009

1

のコンテンツを中央に配置するエレガントな方法の1つUISCrollViewはこれです。

オブザーバーをのcontentSizeに追加してUIScrollView、コンテンツが変更されるたびにこのメソッドが呼び出されるようにします...

[myScrollView addObserver:delegate 
               forKeyPath:@"contentSize"
                  options:(NSKeyValueObservingOptionNew) 
                  context:NULL];

オブザーバーメソッドで:

- (void)observeValueForKeyPath:(NSString *)keyPath   ofObject:(id)object   change:(NSDictionary *)change   context:(void *)context { 

    // Correct Object Class.
    UIScrollView *pointer = object;

    // Calculate Center.
    CGFloat topCorrect = ([pointer bounds].size.height - [pointer viewWithTag:100].bounds.size.height * [pointer zoomScale])  / 2.0 ;
            topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );

    topCorrect = topCorrect - (  pointer.frame.origin.y - imageGallery.frame.origin.y );

    // Apply Correct Center.
    pointer.center = CGPointMake(pointer.center.x,
                                 pointer.center.y + topCorrect ); }
  • を変更する必要があり[pointer viewWithTag:100]ます。コンテンツビューに置き換えますUIView

    • またimageGallery、ウィンドウサイズを指すように変更します。

これにより、サイズが変わるたびにコンテンツの中心が修正されます。

注:このコンテンツがうまく機能しない唯一の方法は、の標準ズーム機能ですUIScrollView


これを機能させることができませんでした。コンテンツではなくscrollViewを中央に配置しているようです。ウィンドウサイズが重要なのはなぜですか?コードはx位置も修正すべきではありませんか?
hpique

1

これは、スクロールビュー内のあらゆる種類のビューでかなりうまく機能する、その問題に対する私の解決策です。

-(void)scrollViewDidZoom:(__unused UIScrollView *)scrollView 
    {
    CGFloat top;
    CGFloat left;
    CGFloat bottom;
    CGFloat right;

    if (_scrollView.contentSize.width < scrollView.bounds.size.width) {
        DDLogInfo(@"contentSize %@",NSStringFromCGSize(_scrollView.contentSize));

        CGFloat width = (_scrollView.bounds.size.width-_scrollView.contentSize.width)/2.0;

        left = width;
        right = width;


    }else {
        left = kInset;
        right = kInset;
    }

    if (_scrollView.contentSize.height < scrollView.bounds.size.height) {

        CGFloat height = (_scrollView.bounds.size.height-_scrollView.contentSize.height)/2.0;

        top = height;
        bottom = height;

    }else {
        top = kInset;
        right = kInset;
    }

    _scrollView.contentInset = UIEdgeInsetsMake(top, left, bottom, right);



  if ([self.tiledScrollViewDelegate respondsToSelector:@selector(tiledScrollViewDidZoom:)])
  {
        [self.tiledScrollViewDelegate tiledScrollViewDidZoom:self];
  }
}

1

ここにはたくさんの解決策がありますが、私はここに自分の解決策を置くリスクがあります。2つの理由で良いです。進行中の画像ビューフレームの更新のように、ズームエクスペリエンスを台無しにしないことと、元のスクロールビューのインセットを尊重すること(たとえば、半透明のツールバーを適切に処理するためにxibまたはストーリーボードで定義されていること) 。

まず、小さなヘルパーを定義します。

CGSize CGSizeWithAspectFit(CGSize containerSize, CGSize contentSize) {
    CGFloat containerAspect = containerSize.width / containerSize.height,
            contentAspect = contentSize.width / contentSize.height;

    CGFloat scale = containerAspect > contentAspect
                    ? containerSize.height / contentSize.height
                    : containerSize.width / contentSize.width;

    return CGSizeMake(contentSize.width * scale, contentSize.height * scale);
}

元のインセットを保持するには、定義されたフィールド:

UIEdgeInsets originalScrollViewInsets;

そして、viewDidLoadのどこかにそれを埋めます:

originalScrollViewInsets = self.scrollView.contentInset;

UIImageViewをUIScrollViewに配置するには(UIImage自体がloadedImage varにあると想定):

CGSize containerSize = self.scrollView.bounds.size;
containerSize.height -= originalScrollViewInsets.top + originalScrollViewInsets.bottom;
containerSize.width -= originalScrollViewInsets.left + originalScrollViewInsets.right;

CGSize contentSize = CGSizeWithAspectFit(containerSize, loadedImage.size);

UIImageView *imageView = [[UIImageView alloc] initWithFrame:(CGRect) { CGPointZero, contentSize }];
imageView.autoresizingMask = UIViewAutoresizingNone;
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.image = loadedImage;

[self.scrollView addSubview:imageView];
self.scrollView.contentSize = contentSize;

[self centerImageViewInScrollView];

scrollViewDidZoom:そのスクロールビューのUIScrollViewDelegateから:

- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
    if (scrollView == self.scrollView) {
        [self centerImageViewInScrollView];
    }
}

最後に、それ自体を中心に:

- (void)centerImageViewInScrollView {
    CGFloat excessiveWidth = MAX(0.0, self.scrollView.bounds.size.width - self.scrollView.contentSize.width),
            excessiveHeight = MAX(0.0, self.scrollView.bounds.size.height - self.scrollView.contentSize.height),
            insetX = excessiveWidth / 2.0,
            insetY = excessiveHeight / 2.0;

    self.scrollView.contentInset = UIEdgeInsetsMake(
            MAX(insetY, originalScrollViewInsets.top),
            MAX(insetX, originalScrollViewInsets.left),
            MAX(insetY, originalScrollViewInsets.bottom),
            MAX(insetX, originalScrollViewInsets.right)
    );
}

私はまだ方向変更をテストしていません(つまり、UIScrollView自体のサイズ変更に対する適切な反応)が、それを修正するのは比較的簡単です。


1

Erdemusによって投稿されたソリューションが機能することがわかりますが、…scrollViewDidZoomメソッドが呼び出されず、画像が左上隅にスタックしている場合があります。簡単な解決策は、次のように、最初に画像を表示するときにメソッドを明示的に呼び出すことです。

[self scrollViewDidZoom: scrollView];

多くの場合、このメソッドを2回呼び出す可能性がありますが、これはこのトピックの他の回答のいくつかよりも明確な解決策です。


1

AppleのPhoto Scroller Exampleは、まさにあなたが探しているものを実行します。これをUIScrollViewサブクラスに入れ、_zoomViewをUIImageViewに変更します。

-(void)layoutSubviews{
  [super layoutSubviews];
  // center the zoom view as it becomes smaller than the size of the screen
  CGSize boundsSize = self.bounds.size;
  CGRect frameToCenter = self.imageView.frame;
  // center horizontally
  if (frameToCenter.size.width < boundsSize.width){
     frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
  }else{
    frameToCenter.origin.x = 0;
  }
  // center vertically
  if (frameToCenter.size.height < boundsSize.height){
     frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
  }else{
    frameToCenter.origin.y = 0;
  }
  self.imageView.frame = frameToCenter; 
}

Appleの写真スクローラーサンプルコード


1

アニメーションの流れを良くするために、

self.scrollview.bouncesZoom = NO;

そして、この関数を使用します(この答えでメソッドを使用して中心を見つける)

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
    [UIView animateWithDuration:0.2 animations:^{
        float offsetX = MAX((scrollView.bounds.size.width-scrollView.contentSize.width)/2, 0);
        float offsetY = MAX((scrollView.bounds.size.height-scrollView.contentSize.height)/2, 0);
        self.imageCoverView.center = CGPointMake(scrollView.contentSize.width*0.5+offsetX, scrollView.contentSize.height*0.5+offsetY);
    }];
}

これは跳ね返りの効果を生み出しますが、突然の動きを事前に含みません。


1

迅速に承認された回答だけですが、デリゲートを使用してサブクラス化しません

func centerScrollViewContents(scrollView: UIScrollView) {
    let contentSize = scrollView.contentSize
    let scrollViewSize = scrollView.frame.size;
    var contentOffset = scrollView.contentOffset;

    if (contentSize.width < scrollViewSize.width) {
        contentOffset.x = -(scrollViewSize.width - contentSize.width) / 2.0
    }

    if (contentSize.height < scrollViewSize.height) {
        contentOffset.y = -(scrollViewSize.height - contentSize.height) / 2.0
    }

    scrollView.setContentOffset(contentOffset, animated: false)
}

// UIScrollViewDelegate    
func scrollViewDidZoom(scrollView: UIScrollView) {
    centerScrollViewContents(scrollView)
}

1

内部のimageViewに初期の特定の幅(300など)があり、その幅を初期の幅よりも小さいズームでのみ中央に置きたい場合は、これも役立つ場合があります。

 func scrollViewDidZoom(scrollView: UIScrollView){
    if imageView.frame.size.width < 300{
        imageView.center.x = self.view.frame.width/2
    }
  }

0

これが私がこの仕事をしている現在の方法です。それは良いですが、まだ完璧ではありません。設定してみてください:

 myScrollView.bouncesZoom = YES; 

でビューが中央に配置されない問題を修正しminZoomScaleます。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGSize screenSize = [[self view] bounds].size;//[[UIScreen mainScreen] bounds].size;//
CGSize photoSize = [yourImage size];
CGFloat topInset = (screenSize.height - photoSize.height * [myScrollView zoomScale]) / 2.0;
CGFloat sideInset = (screenSize.width - photoSize.width * [myScrollView zoomScale]) / 2.0;

if (topInset < 0.0)
{ topInset = 0.0; }
if (sideInset < 0.0)
{ sideInset = 0.0; } 
[myScrollView setContentInset:UIEdgeInsetsMake(topInset, sideInset, -topInset, -sideInset)];
ApplicationDelegate *appDelegate = (ApplicationDelegate *)[[UIApplication sharedApplication] delegate];

CGFloat scrollViewHeight; //Used later to calculate the height of the scrollView
if (appDelegate.navigationController.navigationBar.hidden == YES) //If the NavBar is Hidden, set scrollViewHeight to 480
{ scrollViewHeight = 480; }
if (appDelegate.navigationController.navigationBar.hidden == NO) //If the NavBar not Hidden, set scrollViewHeight to 360
{ scrollViewHeight = 368; }

imageView.frame = CGRectMake(0, 0, CGImageGetWidth(yourImage)* [myScrollView zoomScale], CGImageGetHeight(yourImage)* [myScrollView zoomScale]);

[imageView setContentMode:UIViewContentModeCenter];
}

また、ズームアウト後に画像が横にくっつくのを防ぐために、次のことを行います。

- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
myScrollView.frame = CGRectMake(0, 0, 320, 420);
 //put the correct parameters for your scroll view width and height above
}

こんにちはジョナ。しばらく同じ問題を扱ってきたようです。2つの解決策を確認し、すぐに返信します。
hpique 2010年

こんにちはジョナ、私はあなたの最新の解決策を試しました。ただし、temporaryImageとは何ですか。temporaryImage = imageView.imageを入れてみました。ただし、ズームすると画像が消えます。ありがとう、Pannag
psanketi

Pannag、temporaryImageの名前は不適切です。それはあなたが使用しているどんな画像でもあるのでmyImageと呼ばれるべきです。混乱させて申し訳ありません。
ジョナ2011年

0

さて、私はこの問題のかなり良い解決策を見つけたと思います。コツは常にimageView'sフレームを再調整することです。これは、contentInsetsまたはを常に調整するよりもはるかに効果的contentOffSetsです。縦向きと横向きの両方の画像に対応するために、コードを少し追加する必要がありました。

これがコードです:

- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {

CGSize screenSize = [[self view] bounds].size;

if (myScrollView.zoomScale <= initialZoom +0.01) //This resolves a problem with the code not working correctly when zooming all the way out.
{
    imageView.frame = [[self view] bounds];
    [myScrollView setZoomScale:myScrollView.zoomScale +0.01];
}

if (myScrollView.zoomScale > initialZoom)
{
    if (CGImageGetWidth(temporaryImage.CGImage) > CGImageGetHeight(temporaryImage.CGImage)) //If the image is wider than tall, do the following...
    {
        if (screenSize.height >= CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the height of the screen is greater than the zoomed height of the image do the following...
        {
            imageView.frame = CGRectMake(0, 0, 320*(myScrollView.zoomScale), 368);
        }
        if (screenSize.height < CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the height of the screen is less than the zoomed height of the image do the following...
        {
            imageView.frame = CGRectMake(0, 0, 320*(myScrollView.zoomScale), CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]);
        }
    }
    if (CGImageGetWidth(temporaryImage.CGImage) < CGImageGetHeight(temporaryImage.CGImage)) //If the image is taller than wide, do the following...
    {
        CGFloat portraitHeight;
        if (CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale] < 368)
        { portraitHeight = 368;}
        else {portraitHeight = CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale];}

        if (screenSize.width >= CGImageGetWidth(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the width of the screen is greater than the zoomed width of the image do the following...
        {
            imageView.frame = CGRectMake(0, 0, 320, portraitHeight);
        }
        if (screenSize.width < CGImageGetWidth (temporaryImage.CGImage) * [myScrollView zoomScale]) //If the width of the screen is less than the zoomed width of the image do the following...
        {
            imageView.frame = CGRectMake(0, 0, CGImageGetWidth(temporaryImage.CGImage) * [myScrollView zoomScale], portraitHeight);
        }
    }
    [myScrollView setZoomScale:myScrollView.zoomScale -0.01];
}

0

ページネーションを無効にするだけでうまくいきます:

scrollview.pagingEnabled = NO;

0

私はまったく同じ問題を抱えていました。これが私が解決した方法です

このコードは、次の結果として呼び出されます。 scrollView:DidScroll:

CGFloat imageHeight = self.imageView.frame.size.width * self.imageView.image.size.height / self.imageView.image.size.width;
BOOL imageSmallerThanContent = (imageHeight < self.scrollview.frame.size.height) ? YES : NO;
CGFloat topOffset = (self.imageView.frame.size.height - imageHeight) / 2;

// If image is not large enough setup content offset in a way that image is centered and not vertically scrollable
if (imageSmallerThanContent) {
     topOffset = topOffset - ((self.scrollview.frame.size.height - imageHeight)/2);
}

self.scrollview.contentInset = UIEdgeInsetsMake(topOffset * -1, 0, topOffset * -1, 0);

0

質問は少し古いですが、問題はまだ存在しています。私はそれを解決Xcodeの7(この場合、最上位の項目の垂直空間の制約を作ることによってtopLabelsuperViewsに)( scrollView)トップIBOutlet、次いでたびscrollViewのサブビューの高さに応じてコンテンツの変更を、その定数を再計算(topLabel及びbottomLabel)。

class MyViewController: UIViewController {

    @IBOutlet weak var scrollView: UIScrollView!
    @IBOutlet weak var topLabel: UILabel!
    @IBOutlet weak var bottomLabel: UILabel!
    @IBOutlet weak var toTopConstraint: NSLayoutConstraint!

    override func viewDidLayoutSubviews() {
        let heightOfScrollViewContents = (topLabel.frame.origin.y + topLabel.frame.size.height - bottomLabel.frame.origin.y)
        // In my case abs() delivers the perfect result, but you could also check if the heightOfScrollViewContents is greater than 0.
        toTopConstraint.constant = abs((scrollView.frame.height - heightOfScrollViewContents) / 2)
    }

    func refreshContents() {
        // Set the label's text …

        self.view.layoutIfNeeded()
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.