Daha küçükken UIScrollView içeriğini ortala


140

İçimde yakınlaştırma ve kaydırma için kullandığım UIImageViewbir tane UIScrollViewvar. Kaydırma görünümünün görüntüsü / içeriği kaydırma görünümünden büyükse, her şey yolunda gider. Ancak, görüntü kaydırma görünümünden daha küçük olduğunda, kaydırma görünümünün sol üst köşesine yapışır. Fotoğraflar uygulaması gibi merkezde tutmak istiyorum.

Merkezin içeriğini küçüldüğünde tutmakla ilgili fikir veya örnekler var UIScrollViewmı?

İPhone 3.0 ile çalışıyorum.

Aşağıdaki kod neredeyse çalışıyor. Minimum yakınlaştırma düzeyine ulaştıktan sonra kıstırırsam görüntü sol üst köşeye döner.

- (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)];
}

Bu sorunu hiç çözdünüz mü? Aynı sorunla mücadele ediyorum.
Jonah

1
Dikkat: inset DEĞİL ise (yakınlaştırma yoksa) iç metni hesaplamak için initialZoom değerini kullanın. Örneğin şu satırları kullanın: float topInset = (maxSize.height - imageSize.height * initialZoom) / 2.0; float sideInset = (maxSize.width - imageSize.width * initialZoom) / 2.0; ve son olarak başlangıç ​​zum değerini ayarlayın [imageScrollView setZoomScale: initialZoom];
Felix

Yanıtlar:


228

Çok basit bir çözümüm var! İhtiyacınız olan tek şey ScrollViewDelegate'i yakınlaştırırken alt görünümünüzün merkezini (görüntü görüntüleme) güncellemektir. Yakınlaştırılan görüntü scrollview'den küçükse subview.center'ı ayarlayın, diğer merkez (0,0) 'dır.

- (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
Benim için bu çözüm Liam'ın NYOBetterZoom'unu kullanmaktan daha sarsıntılı. Belki görüntü boyutuna vb. Bağlıdır. Ahlaki; ihtiyaçlarınıza en uygun çözümü kullanın
wuf810

2
Bunun için Stackoverflow GOLD STAR. Bununla uğraşıyordum, ancak çözüm çok basit.
n13

2
basitleştirmek için:CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
Marek R

6
Bu ayar, kaydırma görüntüsünün eklendiğinde bana yardımcı oldu: 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
Bunun, diğer birçok merkezleme tekniği gibi, kullanırken problemlerden muzdarip olduğunu fark ettim zoomToRect:. contentInsetBu işlevselliğe ihtiyacınız varsa yaklaşımı kullanmak daha iyi sonuç verir. Daha fazla bilgi için petersteinberger.com/blog/2013/how-to-center-uiscrollview adresine bakın.
Matej Bukovinski

52

@ EvelynCordner'ın cevabı benim app en iyi oldu. Diğer seçeneklerden çok daha az kod.

İşte birinin ihtiyacı varsa Swift sürümü:

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
Bu harika çalışıyor! Görünüm küçüldükten sonra düzgün bir şekilde canlandırıldı.
Carter Medlin

4
Bu kodu bir fonk içine koydu ve başlangıçta düzgün ayarlandığından emin olmak için viewDidLayoutSubviews de çağırdı .
Carter Medlin

@CarterMedlin iyi çağrı Swift 3 ilk yükümde bana çok yardımcı oldu.
AJ Hernandez

Bu işe yarıyor gibi görünüyor, ama nedenini anlayamıyorum, çünkü her zaman aynı ekleri hesaplıyor gibi görünüyor: kaydırma boyutunun sınırları, içerik boyutu gibi sabittir.
Vaddadi Kartick

İçeriği yakınlaştırdığınızda boyut değişirGörüntü boyutu yalnızca düzeltildi
Michał Ziobro

25

Tamam, son iki gündür bununla savaşıyorum ve sonunda oldukça güvenilir (şimdiye kadar ...) bir çözüme ulaştım. :) Bu çözümle ilgili bir sorun bulursanız lütfen bağırın!

Temelde herkesin neler yaşadığını gördüm: StackOverflow, Apple Geliştirici Forumlarında arama yapmak, üç20, ScrollingMadness, ScrollTestSuite vb. ViewController, vb. ama hiçbir şey harika çalıştı (herkes çok öğrendim gibi).

Üzerinde uyuduktan sonra birkaç alternatif açı denedim:

  1. Dinamik olarak kendi boyutunu değiştirmesi için UIImageView'i alt sınıflara ayırmak - bu hiç de işe yaramadı.
  2. UIScrollView'ı kendi içeriğini değiştirecek şekilde alt sınıflara ayırmak Dinamik olarak ofset - bu benim için kazanan gibi görünüyor.

Bu alt sınıflandırma ile UIScrollView yöntemi ile contentOffset mutator geçersiz kılınıyorum, böylece görüntü görünüm penceresinden daha küçük ölçeklendiğinde {0,0} ayarlamıyor - bunun yerine görüntüyü görünüm merkezinde ortalanacak şekilde ofseti ayarlıyor. Şimdiye kadar, her zaman işe yarıyor gibi görünüyor. Ben geniş, uzun boylu, küçük ve büyük görüntüleri ile kontrol ettik ve "çalışır ama minimum yakınlaştırma tutam kırıyor" sorunu yok.

Bu çözümü kullanan github'a örnek bir proje yükledim, burada bulabilirsiniz: http://github.com/nyoron/NYOBetterZoom


2
İlgilenenler için - yukarıdaki bağlantılı projeyi güncelledim, bu yüzden 'doğru olanı' yapan ViewController'a biraz daha az bağımlı, özel UIScrollView kendisi daha fazla ayrıntıyla ilgileniyor.
Liam Jones

2
Liam, sallanıyorsun. Bunu ScrollingMadness'in yazarı olarak söylüyorum. İPad'de BTW 3.2+ ayar içeriğinde scrollViewDidZoom'da set (yeni bir 3.2+ delege yöntemi) Just Works.
Andrey Tarantsov

Çok düzgün bir kod parçası. Bir süredir bununla başımı kaşıyorum. Projeyi handyiphonecode.com
samvermette

Bu harika. Teşekkür ederim. Benim UIStatusBar olsa gizli olması için telafi değildi, bu yüzden hattı anOffset.y = -(scrollViewSize.height - zoomViewSize.height) / 2.0değiştirdianOffset.y = (-(scrollViewSize.height - zoomViewSize.height) / 2.0) + 10;
Gordon Fontenot

Bana göre bi Erdemus'un verdiği çözüm çok daha basit.
gyozo kudor

23

Bu kod, iOS'un çoğu sürümünde çalışmalıdır (ve 3.1 sürümü üzerinde çalıştığı test edilmiştir).

Photoscoller için Apple WWDC kodunu temel alır.

Aşağıdakileri UIScrollView alt sınıfınıza ekleyin ve tileContainerView öğesini görüntünüzü veya kutucukları içeren görünümle değiştirin:

- (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
bu kabul edilen cevap olmalı, çünkü daha basit. Teşekkürler. Hayatımı kurtardın!!!!!! : D
Ördek

Tüm iOS 3.2+ API çağrılarını kaldırdıktan sonra, merkezleme mantığı 3.1.3'te değil, sadece iOS 3.2+ olan cihazlarda çalışıyor gibi görünüyor (ürkek, titrek, rastgele ofset alır). 3.1.3 ve 3.2+ arasındaki çerçeve kökeni ve boyutu çıktılarını karşılaştırdım ve eşleşseler bile, bir nedenden ötürü, çocuk görüşü hala yanlış konumlandırılıyor. Çok tuhaf. Benim için sadece Liam Jone'un cevabı işe yaradı.
David H

1
Hem @Erdemus hem de @JosephH çözümleri işe yarıyor, ancak UIScrollViewalt sınıf yaklaşımı tercih ediliyor gibi görünüyor: yakınlaştırma devam ederken görünüm ilk kez görüntülendiğinde + sürekli olarak scrollViewDidZoomçağrılır (oysa kaydırma başına ve olaydan sonra yalnızca bir kez çağrılır)
SwiftArchitect

22

Otomatik yerleşim kullanan kaydırma görünümleri için daha uygun bir çözüm için, kaydırma görünümünüzün alt görünümlerinin karelerini güncellemek yerine kaydırma görünümünün içerik iç metinlerini kullanın.

- (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
Bu benim için çalıştı, ancak görüntü bir şekilde başlamak için daha küçükse (doğrudan çağrıldığında) kaydırma görünümünü güncellemedi. Ben gerekli todo ilk konulmuştur ne UIViewiçinde olduğunu UIScrollviewaynı merkeze ( view.center = scrollview.center;) ve daha sonra içinde scrollViewDidZoomsetine view.frame.origin xve ykarşı 0tekrar.
morksinaanab

Benim için de iyi çalışıyor, ancak ana kaydırma görünümümde aradığım dispatch_asyncana sıraya bir yaptım . Bu, görünümün ortalanmış görüntülerle görünmesini sağlar. viewWillAppearscrollViewDidZoom:
Pascal

viewDidLayoutSubviewsGörünüm ilk kez görüntülendiğinde doğru şekilde ayarlandığından emin olmak için bu kodu arayın .
Carter Medlin

21

Şu anda UIScrollViewgöreve setContentOffset:ayarlamak için alt sınıf ve geçersiz kılma contentSize. Hem tutam hem de programlı yakınlaştırma ile çalışır.

@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

Kısa ve tatlı olmasının yanı sıra, bu kod @Erdemus çözümünden çok daha yumuşak bir zum üretir. RMGallery demosunda çalışırken görebilirsiniz .


6
Bu yöntemi uygulamak için UIScrollView öğesini alt sınıflamaya gerek yoktur. Apple, temsilci yöntemlerinde kaydırma ve yakınlaştırma olaylarını görmenize olanak tanır. Özellikle: - (void) scrollViewDidZoom: (UIScrollView *) scrollView;
Rog

1
Şimdiye kadar gördüğüm en iyi çözüm (kısıtlamaları kullanırken bile çalışın).
Frizlab

12

Bu sorunla savaşarak bir gün geçirdim ve scrollViewDidEndZooming'i uyguladım : withView: atScale: aşağıdaki gibi:

- (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);
}

Bu, görüntü ekrandan daha küçük olduğunda, etrafında tam olarak yeterli alan olmasını sağlar, böylece tam olarak istediğiniz yere konumlandırabilirsiniz.

(UIScrollView cihazınızın görüntüyü tutmak için bir UIImageView içerdiği varsayılarak)

Temel olarak, bunun yaptığı şey, görüntü görünümünüzün genişliğinin / yüksekliğinin ekranın genişliğinden / yüksekliğinden daha küçük olup olmadığını kontrol etmek ve eğer öyleyse, ekranın genişliğinin / yüksekliğinin yarısını bir iç metin oluşturmaktır (görüntünün ekran sınırlarının dışına çıkın).

Bu bir UIScrollViewDelegate yöntemi olduğundan, bir derleme sorunu almamak için görünüm denetleyicinizin bildirimine eklemeyi unutmayın.


7

Apple, 2010 WWDC oturum videolarını iphone geliştirici programının tüm üyelerine yayınladı. Tartışılan konulardan biri nasıl fotoğraf uygulaması yarattı !!! Adım adım çok benzer bir uygulama oluşturuyorlar ve tüm kodları ücretsiz olarak kullanabiliyorlar.

Özel api de kullanmaz. Hiçbir gizlilik anlaşması nedeniyle kod burada koyamazsınız, ama burada örnek kod indirme bir bağlantı. Erişim elde etmek için muhtemelen giriş yapmanız gerekecektir.

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

Ve işte iTunes WWDC sayfasına bir bağlantı:

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
Söz konusu örnek MyImagePicker'dur ve komik bir şekilde aynı sorunu sergilemektedir.
Colin Barrett

1
Daha açık olmalıydım. Söz konusu örnek aslında "MyImagePicker" değil "PhotoScroller" dır. "MyImagePicker" öğesinin düzgün çalışmadığı konusunda haklısınız. Ancak, "PhotoScroller" yapar. Dene.
Jonah

Belki de Photoscroller'ı tartıştıkları WWDC videosunun başlığını hatırlıyor musunuz?
Grzegorz Adam Hankiewicz

1
Buna "Kaydırma Görünümlü Uygulamalar Tasarlama" denir.
Jonah

2

Tamam, bu çözüm benim için çalışıyor. Görüntülenen UIScrollViewbir referans ile bir alt sınıf var UIImageView. Her UIScrollViewyakınlaştırma yaptığınızda, contentSize özelliği ayarlanır. Ayarlayıcıda UIImageViewuygun şekilde ölçeklendiriyorum ve aynı zamanda merkez konumunu da ayarlıyorum.

-(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);
}

Her seferinde görüntüyü UIScrollViewyeniden boyutlandırırım. UIScrollViewScrollview de benim yarattığım özel sınıftır.

-(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;
}

Bu iki yöntemle, fotoğraf uygulaması gibi davranan bir görünüm elde edebiliyorum.


Bu hile yapıyor gibi görünüyor ve oldukça basit bir çözüm. Bazı yakınlaştırma geçişleri mükemmel değil, ancak düzeltilebileceğine inanıyorum. Biraz daha deneyeceğim.
hpique

Çözüm güncellemeden önce belliydi. Şimdi biraz kafa karıştırıcı. Açıklayabilir misin?
hpique

2

Bunu yapmanın yolu hiyerarşiye fazladan bir görünüm eklemektir:

UIScrollView -> UIView -> UIImageView

Sizinkiyle UIViewaynı en boy oranını verin UIScrollViewve bunu buna ortalayın UIImageView.


Teşekkürler hatfinch. Bunu da deneyeceğim. Görünüm hiyerarşisini nasıl oluşturduğunuzu göstermek için örnek kod gönderebilir veya örnek kodumu değiştirebilir misiniz?
hpique

Bu tür işler. UIView UIScrollView'ın boyutu olduğu için, görüntü daha küçükse (yani, portre yerine manzara) görüntünün bir bölümünü ekrandan kaydırabilirsiniz. Fotoğraf uygulaması buna izin vermez ve çok daha hoş görünür.
Jonah

2

Yukarıdaki bazı cevapların doğru olduğunu biliyorum, ama sadece cevabımı bazı açıklamalarla vermek istiyorum, yorumlar neden böyle yaptığımızı anlamanızı sağlayacaktır.

ScrollView'i ilk kez yüklediğimde, ortalamak için aşağıdaki kodu yazıyorum, lütfen önce ayarladığımızı fark et contentOffset, sonracontentInset

    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)

Sonra, vContent sıkıştırdığımda, ortalamak için aşağıdaki kodu yazıyorum.

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

Başka bir contentInsetşey için gerekli değilse , scrollview içeriğini ortalamak için kullanılabilir.

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)
    }
}

Bu yaklaşım, yine de contentLayoutGuideiçeriği kaydırma görünümünün içine yerleştirmek için kullanabiliyorsanız avantaj

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)
])

veya içeriği Xcode'un Interface Builder uygulamasına sürükleyip bırakmanız yeterlidir.


1

contentSizeUIScrollView özelliğini (anahtar / değer gözlemini veya benzerini kullanarak) izleyebilir ve değişikliklerin kaydırma görünümünün boyutundan daha az olacağı contentInsetzaman otomatik olarak ayarlayabilirsiniz contentSize.


Deneyecek. Bu, contentSize'i gözlemlemek yerine UIScrollViewDelegate yöntemleriyle yapmak mümkün müdür?
hpique

Büyük ihtimalle; kullanırsınız - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale( developer.apple.com/iphone/library/documentation/UIKit/… adresinde açıklanmıştır ), ancak gözlemlemeyi tercih ederim contentSizeçünkü aksi halde yeni yakınlaştırma ölçeğini atmak ve yine de görünümden bulmak. (Görüşlerinizin göreli boyutlarına göre harika bir matematik çekemezseniz.)
Tim

Teşekkürler, Tim. scrollViewDidEndZooming kullandığım şey. Söz konusu koda bakın (ilk cevabınızdan sonra ekledim). Neredeyse işe yarıyor. MinimumZoomScale'e ulaşıldıktan sonra görüntüyü sıkıştırırsam, sol üst köşeye geri dönerse kalan tek sorun.
hpique

Yani, minimum yakınlaştırma ölçeği dışındaki herhangi bir ölçekte sıkıştırmak için çalışıyor (görüntüyü ortalıyor) ve yalnızca minimum ölçekte olduğunuzda tekrar sıkıştırmaya çalıştığınızda kırılıyor mu? Yoksa minimum ölçeğe sıkıştırmak için işe yaramıyor mu?
Tim

İlki. Görüntüyü minimum yakınlaştırma ölçeği dışındaki herhangi bir ölçeğe göre sıkıştırmak için ortalar ve minimum ölçekte olduğunuzda tekrar sıkıştırmaya çalıştığınızda kırılır. Ayrıca, ilk kez minimum zum ölçeğine ulaştığında, içerik kısa bir şekilde garip bir etkiye neden olacak şekilde üstten geliyormuş gibi kısaca canlandırılır. Bu en azından 3.0 simülatöründe olur.
hpique

1

İçeriğini ortalamanın zarif bir yolu UISCrollView budur.

Bir gözlemci ekle contentSize aramalarınızdan UIScrollViewbu yöntem her içerik değişikliği adı verilecek, böylece ...

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

Şimdi gözlemci yönteminizde:

- (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 ); }
  • Değiştirmelisiniz [pointer viewWithTag:100]. İçerik görünümünüzle değiştirin UIView.

    • Ayrıca imageGallerypencere boyutunuzu gösterecek şekilde değiştirin .

Bu, boyut değişikliği her değiştiğinde içeriğin merkezini düzeltir.

NOT: Bu içeriğin çok iyi çalışmadığı tek yol, standart zum işleviyle ilgilidir UIScrollView.


Bu işe yaramadı. İçeriğini değil, scrollView'u ortaladığınız anlaşılıyor. Pencere boyutu neden önemlidir? Kod x konumunu da düzeltmemeli mi?
hpique

1

Bu, bir kaydırma görünümünde her türlü görünüm için oldukça iyi çalışan bu soruna benim çözümüm.

-(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

Burada çok sayıda çözüm var, ama buraya kendiminkini koyma riskim var. İki nedenden dolayı iyidir: yakınlaştırma deneyimini bozmaz, devam eden görüntü görünümü çerçevesini günceller ve ayrıca orijinal kaydırma görünümü iç metinlerine (yarı saydam araç çubuklarının zarif kullanımı için xib veya storyboard'da tanımlanır) saygılıdır. .

İlk olarak, küçük bir yardımcı tanımlayın:

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);
}

Orijinal ekleri korumak için tanımlanmış alan:

UIEdgeInsets originalScrollViewInsets;

Ve viewDidLoad'da bir yere doldurun:

originalScrollViewInsets = self.scrollView.contentInset;

UIImageView öğesini UIScrollView'a yerleştirmek için (UIImage öğesinin loadedImage var'da olduğu varsayılarak):

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: bu kaydırma görünümü için UIScrollViewDelegate'ten:

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

Sonunda kendini merkezleyen:

- (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)
    );
}

Henüz oryantasyon değişikliğini test etmedim (yani UIScrollView'in kendisini yeniden boyutlandırmak için uygun reaksiyon), ancak bunun için düzeltme nispeten kolay olmalıdır.


1

Erdemus tarafından gönderilen çözümün işe yaradığını göreceksiniz, ancak… scrollViewDidZoom yönteminin çağrılmadığı ve görüntünüzün sol üst köşeye yapıştığı bazı durumlar vardır. Basit bir çözüm, ilk olarak aşağıdaki gibi bir görüntü görüntülediğinizde yöntemi açıkça çağırmaktır:

[self scrollViewDidZoom: scrollView];

Çoğu durumda, bu yöntemi iki kez çağırıyor olabilirsiniz, ancak bu, bu konudaki diğer cevaplardan daha temiz bir çözümdür.


1

Apple'ın Fotoğraf Kaydırıcı Örneği tam olarak aradığınızı yapar. Bunu UIScrollView Alt Sınıfınıza koyun ve _zoomView'u UIImageView'ınız olarak değiştirin.

-(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'ın Fotoğraf Kaydırıcısı Örnek Kodu


1

Animasyonun güzelce akmasını sağlamak için

self.scrollview.bouncesZoom = NO;

ve bu işlevi kullanın ( bu cevaptaki yöntemi kullanarak merkezi bulma )

- (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);
    }];
}

Bu zıplayan etki yaratır, ancak önceden ani hareketler içermez.


1

Sadece onaylanmış cevap hızlıdır, ancak delege kullanarak alt sınıflama yapmadan

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'inizin başlangıçta belirli bir genişliği (örn. 300) olması ve yalnızca genişliğini yalnızca başlangıç ​​genişliğinden daha küçük zoom üzerinde ortalamak istiyorsanız, bu da size yardımcı olabilir.

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

0

İşte bu işi şu anda yapıyorum. Daha iyi ama yine de mükemmel değil. Ayarlamayı deneyin:

 myScrollView.bouncesZoom = YES; 

saat görünümünde ortalanmadığında sorunu düzeltmek için 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];
}

Ayrıca, görüntüyü uzaklaştırdıktan sonra bir tarafa yapışmasını önlemek için aşağıdakileri yapıyorum.

- (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
}

Merhaba Jonah. Bir süredir aynı konuyla uğraşıyoruz gibi görünüyor. İki çözümünüzü kontrol edecek ve yakında cevap verecektir.
hpique

Merhaba Jonah, en son çözümünüzü denedim. Ancak, tempoImage nedir? Geçici görüntü = imageView.image koyarak denedim; ancak, yakınlaştırdığımda görüntü kaybolur. Teşekkürler, Pannag
psanketi

Pannag, temporaryImage kötü adlandırılmış. Sadece kullandığınız resim olduğu için myImage olarak adlandırılmalıdır. Karışıklık için özür dilerim.
Jonah

0

Tamam, sanırım bu soruna oldukça iyi bir çözüm buldum. İşin püf noktası sürekli olarak imageView'sçerçeveyi yeniden ayarlamaktır . Bunun sürekli olarak ayarlanması contentInsetsveyacontentOffSets . Hem portre hem de manzara görüntüleri karşılamak için ekstra kod biraz eklemek zorunda kaldı.

İşte kod:

- (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

Sayfalandırmayı devre dışı bırakmanız yeterlidir, böylece iyi çalışır:

scrollview.pagingEnabled = NO;

0

Ben de aynı problemi yaşadım. İşte böyle çözdüm

Bu kod, 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

Soru biraz eski olmasına rağmen, sorun hala var. Ben bunu çözmüş Xcode 7 (bu durumda en üst öğenin dikey boşluk kısıtlamayı yaparak topLabelsuperViews için) ( scrollView) bir üst IBOutletve daha sonra her seferinde scrollview en subviews yüksekliğine bağlı olarak içerik değişiklikleri kendi sabit recalculating ( topLabelve 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()
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.