Pusatkan konten UIScrollView saat lebih kecil

140

Saya memiliki bagian UIImageViewdalam UIScrollViewyang saya gunakan untuk memperbesar dan menggulir. Jika gambar / konten tampilan gulir lebih besar dari tampilan gulir, semuanya berfungsi dengan baik. Namun, ketika gambar menjadi lebih kecil dari tampilan gulir, itu menempel ke sudut kiri atas tampilan gulir. Saya ingin membuatnya tetap terpusat, seperti aplikasi Foto.

Adakah ide atau contoh tentang menjaga konten yang UIScrollViewterpusat saat lebih kecil?

Saya bekerja dengan iPhone 3.0.

Kode berikut hampir berfungsi. Gambar kembali ke sudut kiri atas jika saya menjepitnya setelah mencapai tingkat zoom minimum.

- (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)];
}
hpique
sumber
Apakah Anda pernah menyelesaikan masalah ini sepenuhnya? Saya berjuang dengan masalah yang sama.
Jonah
1
Perhatian: Gunakan nilai initialZoom untuk menghitung inset jika BUKAN satu (tidak ada pembesaran). Misalnya, gunakan baris berikut: float topInset = (maxSize.height - imageSize.height * initialZoom) / 2.0; float sideInset = (maxSize.width - imageSize.width * initialZoom) / 2.0; dan akhirnya mengatur nilai zoom awal [imageScrollView setZoomScale: initialZoom];
Felix

Jawaban:

228

Saya punya solusi yang sangat sederhana! Yang Anda butuhkan adalah memperbarui pusat subview Anda (imageview) saat memperbesar di ScrollViewDelegate. Jika gambar yang diperbesar lebih kecil dari scrollview maka sesuaikan pusat subview.center else adalah (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);
}
Erdemus
sumber
5
Bagi saya solusi ini lebih tersentak daripada menggunakan NYOBetterZoom Liam. Mungkin itu tergantung pada ukuran gambar dll. Moral; gunakan solusi yang paling sesuai dengan kebutuhan Anda
wuf810
2
Stackoverflow GOLD STAR untuk ini. Saya berjuang dengan ini, namun solusinya sangat sederhana.
n13
2
untuk menyederhanakan sedikit:CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
Marek R
6
Penyesuaian ini membantu saya keluar ketika scrollview memiliki insets: 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
Saya perhatikan bahwa ini, seperti banyak teknik pemusatan lainnya, tampaknya mengalami masalah saat menggunakan zoomToRect:. Menggunakan contentInsetpendekatan berfungsi lebih baik, jika Anda membutuhkan fungsi itu. Lihat petersteinberger.com/blog/2013/how-to-center-uiscrollview untuk detail lebih lanjut.
Matej Bukovinski
52

@ EvelynCordner jawaban adalah yang paling berhasil di aplikasi saya. Kode jauh lebih sedikit daripada opsi lain juga.

Inilah versi Swift jika ada yang membutuhkannya:

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)
}
William T.
sumber
4
Ini bekerja dengan baik! Tampilannya bahkan animasi dengan benar setelah menyusut.
Carter Medlin
4
Saya memasukkan kode ini ke dalam func dan juga menyebutnya di viewDidLayoutSubviews untuk memastikan kode tersebut telah diatur dengan benar pada awalnya.
Carter Medlin
Panggilan bagus @CarterMedlin sangat membantu saya dengan muatan awal Swift 3 saya.
AJ Hernandez
Ini tampaknya berhasil, tetapi saya tidak dapat mengerti mengapa, karena sepertinya selalu menghitung inset yang sama: batas tampilan gulir sudah diperbaiki, seperti ukuran konten.
Vaddadi Kartick
contentSize berubah ketika memperbesar ukuran scrollView hanya diperbaiki
Michał Ziobro
25

Oke, saya telah memperjuangkan ini selama dua hari terakhir dan akhirnya dan akhirnya menemukan solusi yang cukup andal (sejauh ini ...) saya pikir saya harus membagikannya dan menyelamatkan orang lain dari rasa sakit. :) Jika Anda menemukan masalah dengan solusi ini, silakan berteriak!

Saya pada dasarnya telah melalui apa yang dimiliki orang lain: mencari StackOverflow, Forum Pengembang Apple, melihat kode untuk three20, ScrollingMadness, ScrollTestSuite, dll. Saya sudah mencoba memperbesar bingkai UIImageView, bermain dengan offset dan / atau insets UIScrollView. dari ViewController, dll. tetapi tidak ada yang berhasil (karena semua orang juga tahu).

Setelah tidur di atasnya, saya mencoba beberapa sudut alternatif:

  1. Subclassing the UIImageView sehingga mengubah ukurannya sendiri secara dinamis - ini tidak bekerja dengan baik sama sekali.
  2. Subclassing the UIScrollView sehingga mengubah itu sendiri contentOffset secara dinamis - ini adalah salah satu yang tampaknya menjadi pemenang bagi saya.

Dengan metode UIScrollView subkelas ini, saya mengganti mutator contentOffset sehingga tidak menetapkan {0,0} ketika gambar diskalakan lebih kecil dari viewport - alih-alih pengaturan offset sehingga gambar akan tetap dipusatkan di viewport. Sejauh ini, sepertinya selalu berhasil. Saya telah memeriksanya dengan gambar yang lebar, tinggi, kecil & besar dan tidak memiliki masalah "berfungsi tapi jepit pada zoom minimum yang merusaknya".

Saya telah mengunggah contoh proyek ke github yang menggunakan solusi ini, Anda dapat menemukannya di sini: http://github.com/nyoron/NYOBetterZoom

Liam Jones
sumber
2
Bagi yang berminat - Saya telah memperbarui proyek yang ditautkan di atas sehingga agak kurang bergantung pada ViewController melakukan 'hal yang benar', UIScrollView kustom itu sendiri mengurus lebih banyak detail.
Liam Jones
2
Liam, kamu batu. Saya mengatakan ini sebagai penulis ScrollingMadness. BTW di iPad dalam pengaturan konten 3,2+Aset di scrollViewDidZoom (metode delegasi 3,2+ baru) Hanya Bekerja.
Andrey Tarantsov
Sepotong kode yang sangat rapi. Aku sudah menggaruk kepalaku dengan ini untuk sementara waktu. Menambahkan proyek ke handyiphonecode.com
samvermette
Ini bagus. Terima kasih. Itu tidak mengkompensasi UIStatusBar saya yang disembunyikan, jadi saya mengubah jalur anOffset.y = -(scrollViewSize.height - zoomViewSize.height) / 2.0keanOffset.y = (-(scrollViewSize.height - zoomViewSize.height) / 2.0) + 10;
Gordon Fontenot
Solusi yang diberikan bi Erdemus jauh lebih sederhana menurut saya.
gyozo kudor
23

Kode ini harus bekerja pada sebagian besar versi iOS (dan telah diuji untuk bekerja pada 3.1 ke atas).

Ini didasarkan pada kode Apple WWDC untuk photoscoller.

Tambahkan di bawah ini ke subclass Anda dari UIScrollView, dan ganti tileContainerView dengan tampilan yang mengandung gambar atau ubin Anda:

- (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;
}
JosephH
sumber
3
ini harus menjadi jawaban yang diterima, karena lebih sederhana. Terima kasih. Anda menyelamatkan hidup saya!!!!!! : D
Duck
Setelah menghapus semua panggilan API iOS 3.2+, logika pemusatan hanya berfungsi pada perangkat dengan iOS 3.2+ dan bukan 3.1.3 (gelisah, kerlipan, mendapat offset acak). Saya telah membandingkan output asal bingkai dan ukuran antara 3.1.3 dan 3.2+ dan meskipun mereka cocok, untuk beberapa alasan, pandangan anak masih diposisikan salah. Sangat aneh. Hanya jawaban Liam Jone yang berhasil untukku.
David H
1
Baik solusi @Erdemus dan @JephephH bekerja, tetapi UIScrollViewpendekatan subclass tampaknya lebih disukai: itu dipanggil ketika tampilan pertama kali ditampilkan + terus menerus saat zooming sedang berlangsung (sedangkan scrollViewDidZoomhanya dipanggil sekali per gulir dan setelah fakta)
SwiftArchitect
22

Untuk solusi yang lebih cocok untuk tampilan gulir yang menggunakan autolayout, gunakan inset konten tampilan gulir daripada memperbarui bingkai subview tampilan gulir Anda.

- (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);
}
Evelyn Cordner
sumber
1
Ini bekerja untuk saya, namun jika gambar lebih kecil untuk memulai dengan entah bagaimana kode ini (ketika dipanggil langsung) tidak memperbarui scrollview. Apa yang perlu saya lakukan adalah pertama-tama meletakkan UIViewyang ada di dalam UIScrollviewke pusat yang sama ( view.center = scrollview.center;) dan kemudian di scrollViewDidZoomset view.frame.origin xdan yke 0lagi.
morksinaanab
Bekerja dengan baik untuk saya juga, tetapi saya melakukan dispatch_asyncke antrian utama di viewWillAppearmana saya memanggil scrollViewDidZoom:tampilan gulir utama saya. Ini membuat tampilan muncul dengan gambar tengah.
Pascal
Lakukan panggilan ke kode ini viewDidLayoutSubviewsuntuk memastikan sudah diatur dengan benar ketika tampilan menunjukkan pertama kali.
Carter Medlin
21

Saat ini saya sedang subclassing UIScrollViewdan overriding setContentOffset:untuk menyesuaikan offset berdasarkan contentSize. Ini berfungsi baik dengan pinch dan zooming terprogram.

@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

Selain pendek dan manis, kode ini menghasilkan zoom yang jauh lebih mulus daripada solusi @Erdemus. Anda dapat melihatnya beraksi di demo RMGallery .

hpique
sumber
6
Anda tidak perlu mensubclass UIScrollView untuk mengimplementasikan metode ini. Apple memungkinkan Anda melihat gulir dan memperbesar acara dalam metode delegasi. Khususnya: - (void) scrollViewDidZoom: (UIScrollView *) scrollView;
Rog
1
Solusi terbaik yang pernah saya lihat (bekerja bahkan ketika menggunakan kendala).
Frizlab
12

Saya menghabiskan satu hari berjuang dengan masalah ini, dan akhirnya menerapkan scrollViewDidEndZooming: withView: atScale: sebagai berikut:

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

Ini memastikan bahwa ketika gambar lebih kecil dari layar, masih ada ruang yang cukup di sekitarnya sehingga Anda dapat memposisikannya ke tempat yang Anda inginkan.

(dengan asumsi bahwa UIScrollView Anda berisi UIImageView untuk menampung gambar)

Pada dasarnya, apa yang dilakukan adalah memeriksa apakah lebar / tinggi tampilan gambar Anda lebih kecil dari lebar / tinggi layar, dan jika demikian, buat inset setengah lebar / tinggi layar (Anda mungkin bisa membuat ini lebih besar jika Anda ingin gambar tersebut keluar dari batas layar).

Perhatikan bahwa karena ini adalah metode UIScrollViewDelegate , jangan lupa untuk menambahkannya ke deklarasi pengontrol tampilan Anda, jadi untuk menghindari masalah build.

Vicarius
sumber
7

Apple telah merilis video sesi WWDC 2010 untuk semua anggota program pengembang iphone. Salah satu topik yang dibahas adalah bagaimana mereka membuat aplikasi foto !!! Mereka membangun aplikasi langkah demi langkah yang sangat mirip dan telah membuat semua kode tersedia secara gratis.

Itu tidak menggunakan api pribadi juga. Saya tidak dapat memasukkan kode apa pun di sini karena perjanjian non-pengungkapan, tetapi di sini ada tautan ke unduhan kode sampel. Anda mungkin harus masuk untuk mendapatkan akses.

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

Dan, berikut ini tautan ke halaman 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

Jonah
sumber
1
Contoh yang dimaksud adalah MyImagePicker dan itu, dengan meriah, menunjukkan masalah yang sama.
Colin Barrett
1
Seharusnya aku lebih jelas. Contoh yang dimaksud sebenarnya "PhotoScroller" bukan "MyImagePicker". Anda benar bahwa "MyImagePicker" tidak berfungsi dengan benar. Tapi, "PhotoScroller" tidak. Cobalah.
Jonah
Apakah Anda ingat mungkin judul video WWDC tempat mereka membahas Photoscroller?
Grzegorz Adam Hankiewicz
1
Ini disebut "Merancang Aplikasi Dengan Tampilan Gulir".
Jonah
2

Ok, solusi ini bekerja untuk saya. Saya memiliki subkelas UIScrollViewdengan referensi untuk UIImageViewitu ditampilkan. Setiap kali UIScrollViewzoom, properti contentSize disesuaikan. Di setter itulah saya mengatur skala dengan UIImageViewtepat dan juga menyesuaikan posisi tengahnya.

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

Lembur saya mengatur gambar pada UIScrollViewsaya mengubah ukurannya. The UIScrollViewdalam scrollview juga merupakan kelas khusus yang saya buat.

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

Dengan dua metode ini saya dapat memiliki tampilan yang berperilaku seperti aplikasi foto.

AHA
sumber
Ini sepertinya melakukan trik, dan ini solusi yang cukup sederhana. Beberapa transisi zoom tidak sempurna, tetapi saya yakin ini bisa diperbaiki. Saya akan bereksperimen lagi.
hpique
Solusinya jelas sebelum pembaruan. Sekarang agak membingungkan. Bisakah Anda mengklarifikasi?
hpique
2

Cara saya melakukan ini adalah dengan menambahkan tampilan ekstra ke dalam hierarki:

UIScrollView -> UIView -> UIImageView

Berikan UIViewrasio aspek yang sama dengan Anda UIScrollView, dan pusatkan UIImageViewke dalam itu.

hatfinch
sumber
Terima kasih, hatfinch. Akan mencoba ini juga. Bisakah Anda memposting kode sampel atau mengubah kode sampel saya untuk menunjukkan bagaimana Anda membangun hierarki tampilan?
hpique
Jenis pekerjaan ini. Kecuali kenyataan bahwa karena UIView adalah ukuran dari UIScrollView, jika gambar lebih kecil (yaitu lanskap bukan potret), Anda dapat menggulirkan bagian gambar dari layar. Aplikasi foto tidak memungkinkan ini dan terlihat jauh lebih bagus.
Jonah
2

Saya tahu beberapa jawaban di atas benar, tetapi saya hanya ingin memberikan jawaban saya dengan beberapa penjelasan, komentarnya akan membuat Anda mengerti mengapa kami menyukai ini.

Ketika saya memuat scrollView untuk pertama kalinya, saya menulis kode berikut untuk membuatnya menjadi pusat, perhatikan kami mengatur contentOffsetterlebih dahulu, kemudiancontentInset

    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)

Lalu, ketika saya mencubit vContent, saya menulis kode berikut untuk membuatnya menjadi pusat.

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)
}
Changwei
sumber
2

Jika contentInsettidak diperlukan untuk hal lain, itu dapat digunakan untuk memusatkan konten scrollview.

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

Keuntungan jika pendekatan ini adalah Anda masih dapat menggunakan contentLayoutGuideuntuk menempatkan konten di dalam scrollview

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

atau cukup seret dan jatuhkan konten dalam Xcode's Interface Builder.

Wojciech Nagrodzki
sumber
1

Anda bisa menonton contentSizeproperti UIScrollView (menggunakan mengamati kunci-nilai atau serupa), dan secara otomatis menyesuaikan contentInsetsetiap kali contentSizeperubahan menjadi kurang dari ukuran tampilan gulir.

Tim
sumber
Akan mencoba. Apakah ini mungkin dilakukan dengan metode UIScrollViewDelegate daripada mengamati contentSize?
hpique
Yang paling disukai; Anda akan menggunakan - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale(dijelaskan di developer.apple.com/iphone/library/documentation/UIKit/... ), tetapi saya lebih suka mengamati contentSizekarena jika tidak, Anda akhirnya membuang skala zoom baru dan tetap menemukannya dari tampilan. (Kecuali jika Anda dapat melakukan beberapa matematika luar biasa berdasarkan ukuran relatif dari pandangan Anda.)
Tim
Terima kasih, Tim. scrollViewDidEndZooming adalah apa yang saya gunakan. Lihat kode yang dipermasalahkan (Saya telah menambahkannya setelah balasan pertama Anda). Hampir berhasil. Satu-satunya masalah yang tersisa jika itu jika saya mencubit gambar setelah minimumZoomScale tercapai, itu kembali ke sudut kiri atas.
hpique
Apakah maksud Anda ini berfungsi (memusatkan gambar) untuk menjepit dari skala apa pun selain skala zoom minimum, dan hanya pecah jika Anda mencoba menjepitnya lagi setelah Anda berada di skala minimum? Atau tidak berhasil mencubit ke skala minimum?
Tim
Yang pertama. Ini memusatkan gambar untuk mencubit dari skala apa pun selain skala zoom minimum, dan rusak jika Anda mencoba mencubitnya lagi begitu Anda berada di skala minimum. Juga, ketika mencapai skala zoom minimum pertama kali, konten dianimasikan secara singkat seperti berasal dari atas, yang menyebabkan efek aneh singkat. Setidaknya ini terjadi pada simulator 3.0.
hpique
1

Salah satu cara elegan untuk memusatkan konten UISCrollViewadalah ini.

Tambahkan satu pengamat ke contentSize Anda UIScrollView, sehingga metode ini akan dipanggil setiap kali konten berubah ...

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

Sekarang pada metode pengamat Anda:

- (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 ); }
  • Anda harus mengubah [pointer viewWithTag:100]. Ganti dengan tampilan konten Anda UIView.

    • Ubah juga imageGallerymenunjuk ke ukuran jendela Anda.

Ini akan memperbaiki pusat konten setiap kali ukurannya berubah.

CATATAN: Satu-satunya cara konten ini tidak berfungsi dengan baik adalah dengan fungsionalitas zoom standar UIScrollView.

Tim Pengembangan SEQOY
sumber
Tidak bisa membuat ini berfungsi. Sepertinya Anda memusatkan scrollView dan bukan kontennya. Mengapa ukuran jendela penting? Bukankah seharusnya kode memperbaiki posisi x juga?
hpique
1

Ini adalah solusi saya untuk masalah itu yang berfungsi cukup baik untuk segala jenis tampilan di dalam scrollview.

-(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];
  }
}
beat843796
sumber
1

Ada banyak solusi di sini, tetapi saya berisiko mengambilnya di sini sendiri. Ini bagus karena dua alasan: ini tidak mengacaukan pengalaman pembesaran, seperti halnya memperbarui bingkai tampilan gambar yang sedang berlangsung, dan juga menghormati insets tampilan gulir asli (katakanlah, didefinisikan dalam xib atau storyboard untuk penanganan yang anggun dari toolbar semi-transparan dll) .

Pertama, tentukan pembantu kecil:

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

Untuk mempertahankan inset asli, bidang yang ditentukan:

UIEdgeInsets originalScrollViewInsets;

Dan di suatu tempat di viewDidLoad, isi:

originalScrollViewInsets = self.scrollView.contentInset;

Untuk menempatkan UIImageView ke dalam UIScrollView (dengan asumsi UIImage itu sendiri ada di loadImage 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: dari UIScrollViewDelegate untuk tampilan gulir itu:

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

Akhirnya, berpusat pada dirinya sendiri:

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

Saya belum menguji perubahan orientasi (yaitu reaksi yang tepat untuk mengubah ukuran UIScrollView sendiri), tetapi memperbaikinya harus relatif mudah.

Jazzcat
sumber
1

Anda akan menemukan bahwa solusi yang diposting oleh Erdemus berfungsi, tetapi ... Ada beberapa kasus di mana metode scrollViewDidZoom tidak dipanggil & gambar Anda macet di sudut kiri atas. Solusi sederhana adalah dengan secara eksplisit memanggil metode ketika Anda awalnya menampilkan gambar, seperti ini:

[self scrollViewDidZoom: scrollView];

Dalam banyak kasus, Anda mungkin menggunakan metode ini dua kali, tetapi ini adalah solusi yang lebih bersih daripada beberapa jawaban lain dalam topik ini.

russes
sumber
1

Contoh Penggulung Foto Apple melakukan persis seperti yang Anda cari. Letakkan ini di Subclass UIScrollView Anda dan ubah _zoomView menjadi UIImageView Anda.

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

Contoh Kode Penggulung Foto Apple

Korey Hinton
sumber
1

Untuk membuat animasi mengalir dengan baik, atur

self.scrollview.bouncesZoom = NO;

dan gunakan fungsi ini (menemukan pusat menggunakan metode pada jawaban ini )

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

Ini menciptakan efek memantul tetapi tidak melibatkan gerakan mendadak sebelumnya.

ldanilek
sumber
1

Hanya jawaban yang disetujui dalam cepat, tetapi tanpa subklas menggunakan delegasi

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)
}
LightMan
sumber
1

Dalam hal imageView batin Anda memiliki lebar spesifik awal (mis. 300) dan Anda hanya ingin memusatkan lebarnya hanya pada zoom yang lebih kecil dari lebar awalnya, ini mungkin membantu Anda juga.

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

Inilah cara saya saat ini membuat karya ini. Lebih baik tapi masih belum sempurna. Coba atur:

 myScrollView.bouncesZoom = YES; 

untuk memperbaiki masalah dengan tampilan yang tidak terpusat saat di 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];
}

Juga, saya melakukan hal berikut untuk mencegah gambar menempel pada sisi setelah memperkecil.

- (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
}
Jonah
sumber
Hai Yunus. Sepertinya kita sudah berurusan dengan masalah yang sama untuk sementara waktu. Akan memeriksa dua solusi Anda dan membalas segera.
hpique
Hai Jonah, saya mencoba solusi terbaru Anda. Namun, apa itu temporaryImage? Saya mencoba meletakkan temporaryImage = imageView.image; namun, begitu saya memperbesar, gambarnya hilang. Terima kasih, Pannag
psanketi
Pannag, temporaryImage tidak disebutkan namanya. Itu harus disebut myImage karena hanya gambar apa pun yang Anda gunakan. Maaf bila membingungkan.
Jonah
0

Oke, saya pikir saya sudah menemukan solusi yang cukup bagus untuk masalah ini. Caranya adalah dengan terus-menerus menyesuaikan imageView'sframe. Saya menemukan ini bekerja lebih baik daripada terus-menerus menyesuaikan contentInsetsataucontentOffSets . Saya harus menambahkan sedikit kode tambahan untuk mengakomodasi gambar potret dan lanskap.

Berikut kodenya:

- (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];
}
Jonah
sumber
0

Cukup nonaktifkan pagination, sehingga berfungsi dengan baik:

scrollview.pagingEnabled = NO;
Nagaraj
sumber
0

Saya memiliki masalah yang sama persis. Inilah cara saya menyelesaikannya

Kode ini harus dipanggil sebagai hasil dari 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);
aryaxt
sumber
0

Meskipun pertanyaannya agak lama namun masalahnya masih ada. Saya memecahkannya dalam Xcode 7 dengan membuat batasan ruang vertikal dari item paling atas (dalam hal ini topLabel) ke superViews (the scrollView) atas IBOutletdan kemudian menghitung ulang konstanta setiap kali konten berubah tergantung pada ketinggian subview scrollView ( topLabeldan 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()
    }
}
Nick Podratz
sumber