Bagaimana cara saya mengukur UIScrollView secara otomatis agar sesuai dengan kontennya

130

Apakah ada cara untuk membuat UIScrollViewpenyesuaian otomatis dengan tinggi (atau lebar) dari konten yang sedang bergulir?

Sesuatu seperti:

[scrollView setContentSize:(CGSizeMake(320, content.height))];
jwerre
sumber

Jawaban:

306

Metode terbaik yang pernah saya temui untuk memperbarui ukuran konten UIScrollViewberdasarkan subview yang terkandung:

Objektif-C

CGRect contentRect = CGRectZero;

for (UIView *view in self.scrollView.subviews) {
    contentRect = CGRectUnion(contentRect, view.frame);
}
self.scrollView.contentSize = contentRect.size;

Cepat

let contentRect: CGRect = scrollView.subviews.reduce(into: .zero) { rect, view in
    rect = rect.union(view.frame)
}
scrollView.contentSize = contentRect.size
raksasa
sumber
9
Saya menjalankan ini viewDidLayoutSubviewssehingga autolayout akan selesai, di iOS7 itu bekerja dengan baik, tetapi menguji os iOS6 autolayout karena beberapa alasan tidak menyelesaikan pekerjaan, jadi saya memiliki beberapa nilai ketinggian yang salah, jadi saya beralih ke viewDidAppearsekarang berfungsi dengan baik .. hanya untuk menunjukkan mungkin seseorang akan membutuhkan ini. terima kasih
Hatem Alimam
1
Dengan iOS6 iPad ada UIImageView misterius (7x7) di sudut kanan bawah. Saya menyaringnya dengan hanya menerima komponen UI (terlihat && alpha), lalu berfungsi. Ingin tahu apakah itu imageView terkait dengan scrollBars, pasti bukan kode saya.
JOM
5
Harap berhati-hati saat Indikator Gulir diaktifkan! UIImageView akan secara otomatis dibuat untuk masing-masing oleh SDK. Anda harus memperhitungkannya atau ukuran konten Anda akan salah. (lihat stackoverflow.com/questions/5388703/… )
AmitP
Dapat mengonfirmasi bahwa solusi ini berfungsi sempurna untuk saya di Swift 4
Ethan Humphries
Sebenarnya, kami tidak dapat memulai penyatuan dari CGRect.zero, ini hanya berfungsi jika Anda memasukkan beberapa subview dalam koordinat negatif. Anda harus memulai frame subview gabungan dari subview pertama, bukan dari nol. Misalnya, Anda hanya memiliki satu subview yang merupakan UIView dengan bingkai (50, 50, 100, 100). Ukuran konten Anda adalah (0, 0, 150, 150), bukan (50, 50, 100, 100).
Evgeny
74

UIScrollView tidak mengetahui ketinggian kontennya secara otomatis. Anda harus menghitung tinggi dan lebar untuk diri Anda sendiri

Lakukan dengan sesuatu seperti

CGFloat scrollViewHeight = 0.0f;
for (UIView* view in scrollView.subviews)
{
   scrollViewHeight += view.frame.size.height;
}

[scrollView setContentSize:(CGSizeMake(320, scrollViewHeight))];

Tapi ini hanya berfungsi jika pandangannya satu di bawah yang lain. Jika Anda memiliki tampilan di samping satu sama lain, Anda hanya perlu menambahkan ketinggian satu jika Anda tidak ingin mengatur konten scroller lebih besar dari yang sebenarnya.

emenegro
sumber
Apakah Anda pikir sesuatu seperti ini akan berhasil juga? CGFloat scrollViewHeight = scrollView.contentSize.height; [scrollView setContentSize: (CGSizeMake (320, scrollViewHeight))]; BTW, jangan lupa untuk meminta ukuran lalu tinggi. scrollViewHeight + = view.frame.size.height
jwerre
Menginisialisasi scrollViewHeight ke ketinggian membuat scroller lebih besar dari kontennya. Jika Anda melakukan ini, jangan tambahkan ketinggian tampilan yang terlihat pada ketinggian awal konten scroller. Meskipun mungkin Anda membutuhkan celah awal atau sesuatu yang lain.
emenegro
21
Atau lakukan saja:int y = CGRectGetMaxY(((UIView*)[_scrollView.subviews lastObject]).frame); [_scrollView setContentSize:(CGSizeMake(CGRectGetWidth(_scrollView.frame), y))];
Gal
@ saintjab, terima kasih, saya baru saja menambahkannya sebagai jawaban resmi :)
Gal
40

Solusi jika Anda menggunakan tata letak otomatis:

  • Setel translatesAutoresizingMaskIntoConstraintske NOsemua tampilan yang terlibat.

  • Posisikan dan ukuran tampilan gulir Anda dengan kendala di luar tampilan gulir.

  • Gunakan batasan untuk meletakkan subview dalam tampilan gulir, pastikan kendala mengikat ke keempat tepi tampilan gulir dan jangan mengandalkan tampilan gulir untuk mendapatkan ukurannya.

Sumber: https://developer.apple.com/library/ios/technotes/tn2154/_index.html

Adam Waite
sumber
10
Saya ini adalah solusi nyata di dunia di mana semuanya terjadi dengan tata letak otomatis. Mengenai solusi lain: Apa ... apakah Anda serius dalam menghitung tinggi dan lebar setiap tampilan?
Fawkes
Terima kasih telah berbagi ini! Itu harus menjadi jawaban yang diterima.
SePröbläm
2
Ini tidak berfungsi saat Anda ingin ketinggian tampilan gulir didasarkan pada tinggi kontennya. (misalnya sembulan yang dipusatkan di layar tempat Anda tidak ingin menggulir hingga sejumlah konten tertentu).
LeGom
Ini tidak menjawab pertanyaan
Igor Vasilev
Solusi bagus Namun, saya akan menambahkan satu peluru lagi ... "Jangan membatasi ketinggian tampilan tumpukan anak jika harus tumbuh secara vertikal. Sematkan saja ke tepi tampilan gulir."
Andrew Kirna
35

Saya menambahkan ini ke jawaban Espuz dan JCC. Ini menggunakan posisi y dari subview dan tidak termasuk bilah gulir. Edit Menggunakan bagian bawah sub tampilan terendah yang terlihat.

+ (CGFloat) bottomOfLowestContent:(UIView*) view
{
    CGFloat lowestPoint = 0.0;

    BOOL restoreHorizontal = NO;
    BOOL restoreVertical = NO;

    if ([view respondsToSelector:@selector(setShowsHorizontalScrollIndicator:)] && [view respondsToSelector:@selector(setShowsVerticalScrollIndicator:)])
    {
        if ([(UIScrollView*)view showsHorizontalScrollIndicator])
        {
            restoreHorizontal = YES;
            [(UIScrollView*)view setShowsHorizontalScrollIndicator:NO];
        }
        if ([(UIScrollView*)view showsVerticalScrollIndicator])
        {
            restoreVertical = YES;
            [(UIScrollView*)view setShowsVerticalScrollIndicator:NO];
        }
    }
    for (UIView *subView in view.subviews)
    {
        if (!subView.hidden)
        {
            CGFloat maxY = CGRectGetMaxY(subView.frame);
            if (maxY > lowestPoint)
            {
                lowestPoint = maxY;
            }
        }
    }
    if ([view respondsToSelector:@selector(setShowsHorizontalScrollIndicator:)] && [view respondsToSelector:@selector(setShowsVerticalScrollIndicator:)])
    {
        if (restoreHorizontal)
        {
            [(UIScrollView*)view setShowsHorizontalScrollIndicator:YES];
        }
        if (restoreVertical)
        {
            [(UIScrollView*)view setShowsVerticalScrollIndicator:YES];
        }
    }

    return lowestPoint;
}
kaya
sumber
1
Hebat! Apa yang saya butuhkan.
Richard
2
Saya terkejut metode seperti ini bukan bagian dari kelas.
João Portela
Mengapa Anda melakukannya self.scrollView.showsHorizontalScrollIndicator = NO; self.scrollView.showsVerticalScrollIndicator = NO;di awal dan self.scrollView.showsHorizontalScrollIndicator = YES; self.scrollView.showsVerticalScrollIndicator = YES;di akhir metode?
João Portela
Terima kasih richy :) +1 untuk jawabannya.
Shraddha
1
@ Richy Anda benar indikator gulir adalah subview jika terlihat, tetapi menunjukkan / menyembunyikan logika salah. Anda perlu menambahkan dua lokal BOOL: restoreHorizontal = NO, restoreVertical = NO. Jika showsHorizontal, sembunyikan, atur restoreHorizontal ke YES. Sama untuk vertikal. Selanjutnya, setelah untuk / dalam masukkan loop jika pernyataan: jika restoreHorizontal, atur untuk menampilkannya, sama untuk vertikal. Kode Anda hanya akan memaksa untuk menampilkan kedua indikator
ilegal-imigran
13

Inilah jawaban yang diterima dengan cepat untuk siapa saja yang terlalu malas untuk mengonversi itu :)

var contentRect = CGRectZero
for view in self.scrollView.subviews {
    contentRect = CGRectUnion(contentRect, view.frame)
}
self.scrollView.contentSize = contentRect.size
Josh
sumber
dan diperbarui untuk Swift3: var contentRect = CGRect.zero untuk dilihat di self.scrollView.subviews {contentRect = contentRect.union (view.frame)} self.scrollView.contentSize = contentRect.size
drew ..
Apakah ini harus dipanggil di utas utama? dan di didLayoutSubViews?
Maksim Kniazev
8

Inilah adaptasi Swift 3 dari jawaban @ leviatan:

PERPANJANGAN

import UIKit


extension UIScrollView {

    func resizeScrollViewContentSize() {

        var contentRect = CGRect.zero

        for view in self.subviews {

            contentRect = contentRect.union(view.frame)

        }

        self.contentSize = contentRect.size

    }

}

PEMAKAIAN

scrollView.resizeScrollViewContentSize()

Sangat mudah digunakan!

fredericdnd
sumber
Sangat berguna. Setelah begitu banyak iterasi saya menemukan solusi ini dan itu sempurna.
Hardik Shah
Menyenangkan! Hanya menerapkannya.
arvidurs
7

Mengikuti ekstensi akan sangat membantu di Swift .

extension UIScrollView{
    func setContentViewSize(offset:CGFloat = 0.0) {
        // dont show scroll indicators
        showsHorizontalScrollIndicator = false
        showsVerticalScrollIndicator = false

        var maxHeight : CGFloat = 0
        for view in subviews {
            if view.isHidden {
                continue
            }
            let newHeight = view.frame.origin.y + view.frame.height
            if newHeight > maxHeight {
                maxHeight = newHeight
            }
        }
        // set content size
        contentSize = CGSize(width: contentSize.width, height: maxHeight + offset)
        // show scroll indicators
        showsHorizontalScrollIndicator = true
        showsVerticalScrollIndicator = true
    }
}

Logikanya sama dengan jawaban yang diberikan. Namun, ini menghilangkan tampilan tersembunyi di dalam UIScrollViewdan perhitungan dilakukan setelah indikator gulir ditetapkan tersembunyi.

Selain itu, ada parameter fungsi opsional dan Anda dapat menambahkan nilai offset dengan meneruskan parameter ke fungsi.

gokhanakkurt
sumber
Solusi ini mengandung terlalu banyak asumsi, mengapa Anda menunjukkan indikator gulir dalam metode yang menentukan ukuran konten?
Kappe
5

Solusi hebat & terbaik dari @leviathan. Hanya menerjemahkan ke swift menggunakan pendekatan FP (pemrograman fungsional).

self.scrollView.contentSize = self.scrollView.subviews.reduce(CGRect(), { 
  CGRectUnion($0, $1.frame) 
}.size
othierry42
sumber
Jika Anda ingin mengabaikan tampilan tersembunyi, Anda dapat memfilter subview sebelum melewati untuk mengurangi.
Andy
Diperbarui untuk Swift 3:self.contentSize = self.subviews.reduce(CGRect(), { $0.union($1.frame) }).size
John Rogers
4

Anda bisa mendapatkan tinggi konten di dalam UIScrollView dengan menghitung anak mana yang "mencapai lebih jauh". Untuk menghitung ini, Anda harus mempertimbangkan asal Y (mulai) dan tinggi barang.

float maxHeight = 0;
for (UIView *child in scrollView.subviews) {
    float childHeight = child.frame.origin.y + child.frame.size.height;
    //if child spans more than current maxHeight then make it a new maxHeight
    if (childHeight > maxHeight)
        maxHeight = childHeight;
}
//set content size
[scrollView setContentSize:(CGSizeMake(320, maxHeight))];

Dengan melakukan hal-hal seperti ini, item (subview) tidak harus ditumpuk secara langsung di bawah yang lain.

paxx
sumber
Anda juga dapat menggunakan liner satu ini di dalam loop: maxHeight = MAX (maxHeight, child.frame.origin.y + child.frame.size.height) ;)
Thomas CG de Vilhena
3

Saya datang dengan solusi lain berdasarkan pada solusi @ emenegro

NSInteger maxY = 0;
for (UIView* subview in scrollView.subviews)
{
    if (CGRectGetMaxY(subview.frame) > maxY)
    {
        maxY = CGRectGetMaxY(subview.frame);
    }
}
maxY += 10;
[scrollView setContentSize:CGSizeMake(scrollView.frame.size.width, maxY)];

Pada dasarnya, kami mencari tahu elemen mana yang paling jauh dalam tampilan dan menambahkan padding 10px ke bawah

Chris
sumber
3

Karena scrollView dapat memiliki scrollViews lain atau pohon subViews inDepth yang berbeda, jalankan secara mendalam secara rekursif lebih disukai. masukkan deskripsi gambar di sini

Cepat 2

extension UIScrollView {
    //it will block the mainThread
    func recalculateVerticalContentSize_synchronous () {
        let unionCalculatedTotalRect = recursiveUnionInDepthFor(self)
        self.contentSize = CGRectMake(0, 0, self.frame.width, unionCalculatedTotalRect.height).size;
    }

    private func recursiveUnionInDepthFor (view: UIView) -> CGRect {
        var totalRect = CGRectZero
        //calculate recursevly for every subView
        for subView in view.subviews {
            totalRect =  CGRectUnion(totalRect, recursiveUnionInDepthFor(subView))
        }
        //return the totalCalculated for all in depth subViews.
        return CGRectUnion(totalRect, view.frame)
    }
}

Pemakaian

scrollView.recalculateVerticalContentSize_synchronous()
iluvatar_GR
sumber
2

Atau lakukan saja:

int y = CGRectGetMaxY(((UIView*)[_scrollView.subviews lastObject]).frame); [_scrollView setContentSize:(CGSizeMake(CGRectGetWidth(_scrollView.frame), y))];

(Solusi ini ditambahkan oleh saya sebagai komentar di halaman ini. Setelah mendapat 19 suara untuk komentar ini, saya memutuskan untuk menambahkan solusi ini sebagai jawaban resmi untuk kepentingan masyarakat!)

Gal
sumber
2

Untuk swift4 menggunakan kurangi:

self.scrollView.contentSize = self.scrollView.subviews.reduce(CGRect.zero, {
   return $0.union($1.frame)
}).size
mm282
sumber
Juga patut diperiksa jika setidaknya satu subview memiliki asal == CGPoint.zero atau hanya menetapkan asal subview tertentu (terbesar misalnya) ke nol.
Paul B
Ini hanya berfungsi untuk orang yang menempatkan pandangan mereka dengan CGRect (). Jika Anda menggunakan kendala ini tidak berfungsi.
linus_hologram
1

Ukurannya tergantung pada konten yang dimuat di dalamnya, dan opsi kliping. Jika ini adalah tampilan teks, maka itu juga tergantung pada pembungkusnya, berapa banyak baris teks, ukuran font, dan seterusnya. Hampir tidak mungkin bagi Anda untuk menghitung sendiri. Berita baiknya adalah, ini dihitung setelah tampilan dimuat dan di viewWillAppear. Sebelum itu, semua tidak diketahui dan ukuran konten akan sama dengan ukuran bingkai. Namun, dalam metode viewWillAppear dan setelah (seperti viewDidAppear) ukuran konten akan menjadi aktual.

PapaSmurf
sumber
1

Membungkus kode Richy, saya membuat kelas UIScrollView khusus yang mengotomatisasi pengubahan ukuran konten sepenuhnya!

SBScrollView.h

@interface SBScrollView : UIScrollView
@end

SBScrollView.m:

@implementation SBScrollView
- (void) layoutSubviews
{
    CGFloat scrollViewHeight = 0.0f;
    self.showsHorizontalScrollIndicator = NO;
    self.showsVerticalScrollIndicator = NO;
    for (UIView* view in self.subviews)
    {
        if (!view.hidden)
        {
            CGFloat y = view.frame.origin.y;
            CGFloat h = view.frame.size.height;
            if (y + h > scrollViewHeight)
            {
                scrollViewHeight = h + y;
            }
        }
    }
    self.showsHorizontalScrollIndicator = YES;
    self.showsVerticalScrollIndicator = YES;
    [self setContentSize:(CGSizeMake(self.frame.size.width, scrollViewHeight))];
}
@end

Cara menggunakan:
Cukup impor file .h ke pengontrol tampilan Anda dan nyatakan instance SBScrollView alih-alih yang normal UIScrollView.

AmitP
sumber
2
layoutSubviews sepertinya itu adalah tempat yang tepat untuk kode terkait tata letak tersebut. Tetapi dalam layoutSubview UIScrollView dipanggil setiap kali offset konten diperbarui saat menggulir. Dan karena ukuran konten biasanya tidak berubah saat menggulir, ini agak boros.
Sven
Itu benar. Salah satu cara untuk menghindari inefisiensi ini mungkin dengan memeriksa [self isDragging] dan [self isDecelerating] dan hanya melakukan tata letak jika keduanya salah.
Greg Brown
1

Tetapkan ukuran konten dinamis seperti ini.

 self.scroll_view.contentSize = CGSizeMake(screen_width,CGRectGetMaxY(self.controlname.frame)+20);
Monika Patel
sumber
0

itu benar-benar tergantung pada konten: content.frame.height mungkin memberi Anda apa yang Anda inginkan? Tergantung apakah konten adalah satu hal, atau kumpulan hal-hal.

Andiih
sumber
0

Saya juga menemukan jawaban leviathan untuk bekerja yang terbaik. Namun, itu menghitung ketinggian yang aneh. Saat mengulangi subview, jika scrollview diatur untuk menampilkan indikator scroll, itu akan berada dalam array subview. Dalam hal ini, solusinya adalah untuk menonaktifkan sementara indikator gulir sebelum mengulang, kemudian membangun kembali pengaturan visibilitas sebelumnya.

-(void)adjustContentSizeToFit adalah metode publik pada subkelas kustom UIScrollView.

-(void)awakeFromNib {    
    dispatch_async(dispatch_get_main_queue(), ^{
        [self adjustContentSizeToFit];
    });
}

-(void)adjustContentSizeToFit {

    BOOL showsVerticalScrollIndicator = self.showsVerticalScrollIndicator;
    BOOL showsHorizontalScrollIndicator = self.showsHorizontalScrollIndicator;

    self.showsVerticalScrollIndicator = NO;
    self.showsHorizontalScrollIndicator = NO;

    CGRect contentRect = CGRectZero;
    for (UIView *view in self.subviews) {
        contentRect = CGRectUnion(contentRect, view.frame);
    }
    self.contentSize = contentRect.size;

    self.showsVerticalScrollIndicator = showsVerticalScrollIndicator;
    self.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator;
}
clayzar
sumber
0

Saya pikir ini bisa menjadi cara yang rapi untuk memperbarui ukuran tampilan konten UIScrollView.

extension UIScrollView {
    func updateContentViewSize() {
        var newHeight: CGFloat = 0
        for view in subviews {
            let ref = view.frame.origin.y + view.frame.height
            if ref > newHeight {
                newHeight = ref
            }
        }
        let oldSize = contentSize
        let newSize = CGSize(width: oldSize.width, height: newHeight + 20)
        contentSize = newSize
    }
}
Furqan Khan
sumber
0

mengapa tidak satu baris kode ??

_yourScrollView.contentSize = CGSizeMake(0, _lastView.frame.origin.y + _lastView.frame.size.height);
Ali
sumber
0
import UIKit

class DynamicSizeScrollView: UIScrollView {

    var maxHeight: CGFloat = UIScreen.main.bounds.size.height
    var maxWidth: CGFloat = UIScreen.main.bounds.size.width

    override func layoutSubviews() {
        super.layoutSubviews()
        if !__CGSizeEqualToSize(bounds.size,self.intrinsicContentSize){
            self.invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        let height = min(contentSize.height, maxHeight)
        let width = min(contentSize.height, maxWidth)
        return CGSize(width: width, height: height)
    }

}
pengguna6127890
sumber