Làm cách nào để tôi tự động kích thước UIScrollView để phù hợp với nội dung của nó


130

Có cách nào để UIScrollViewtự động điều chỉnh theo chiều cao (hoặc chiều rộng) của nội dung mà nó đang cuộn không?

Cái gì đó như:

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

Câu trả lời:


306

Phương pháp tốt nhất tôi từng gặp để cập nhật kích thước nội dung UIScrollViewdựa trên các cuộc phỏng vấn có trong đó:

Mục tiêu-C

CGRect contentRect = CGRectZero;

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

Nhanh

let contentRect: CGRect = scrollView.subviews.reduce(into: .zero) { rect, view in
    rect = rect.union(view.frame)
}
scrollView.contentSize = contentRect.size

9
Tôi đã chạy nó viewDidLayoutSubviewsđể tự động thanh toán sẽ kết thúc, trong iOS7 nó hoạt động tốt, nhưng thử nghiệm os iOS6 tự động thanh toán vì một số lý do không hoàn thành công việc, vì vậy tôi có một số giá trị chiều cao sai, vì vậy tôi đã chuyển sang viewDidAppearhoạt động tốt .. chỉ để chỉ ra có lẽ ai đó sẽ cần điều này. cảm ơn
HHR Alimam

1
Với iPad iOS6 có UIImageView bí ẩn (7x7) ở góc dưới bên phải. Tôi đã lọc nó đi bằng cách chỉ chấp nhận các thành phần UI (hiển thị && alpha), sau đó nó hoạt động. Tự hỏi nếu imageView đó có liên quan đến scrollBars, chắc chắn không phải là mã của tôi.
JOM

5
Hãy cẩn thận khi bật Chỉ báo cuộn! một UIImageView sẽ được SDK tự động tạo cho từng người. bạn phải tính đến nó nếu không kích thước nội dung của bạn sẽ sai. (xem stackoverflow.com/questions/5388703/ khăn )
AmitP

Có thể xác nhận rằng giải pháp này hoạt động hoàn hảo với tôi trong Swift 4
Ethan Humphries

Trên thực tế, chúng tôi không thể bắt đầu liên kết từ CGRect.zero, nó chỉ hoạt động nếu bạn về nhà một số cuộc phỏng vấn ở tọa độ phủ định. Bạn nên bắt đầu các khung hình phụ của công đoàn từ lần xem đầu tiên, không phải từ số không. Chẳng hạn, bạn chỉ có một khung nhìn phụ là UIView có khung (50, 50, 100, 100). Kích thước nội dung của bạn sẽ là (0, 0, 150, 150), không phải (50, 50, 100, 100).
Evgeny

74

UIScrollView không tự động biết chiều cao của nội dung. Bạn phải tính toán chiều cao và chiều rộng cho mình

Làm điều đó với một cái gì đó như

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

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

Nhưng cái này chỉ hoạt động nếu các khung nhìn nằm dưới cái kia. Nếu bạn có chế độ xem cạnh nhau, bạn chỉ phải thêm chiều cao của một nếu bạn không muốn đặt nội dung của thanh cuộn lớn hơn thực tế.


Bạn có nghĩ rằng một cái gì đó như thế này sẽ làm việc tốt không? CGFloat scrollViewHeight = scrollView.contentSize.height; [scrollView setContentSize: (CGSizeMake (320, scrollViewHeight))]; BTW, đừng quên hỏi kích thước rồi chiều cao. scrollViewHeight + = view.frame.size.height
jwerre

Khởi tạo scrollViewHeight theo chiều cao làm cho thanh cuộn lớn hơn nội dung của nó. Nếu bạn làm điều này, đừng thêm chiều cao của các khung nhìn hiển thị trên chiều cao ban đầu của nội dung thanh cuộn. Mặc dù có thể bạn cần một khoảng cách ban đầu hoặc một cái gì đó khác.
emlahoma

21
Hoặc chỉ cần làm:int y = CGRectGetMaxY(((UIView*)[_scrollView.subviews lastObject]).frame); [_scrollView setContentSize:(CGSizeMake(CGRectGetWidth(_scrollView.frame), y))];
Gal

@saintjab, cảm ơn, tôi chỉ thêm nó như một câu trả lời chính thức :)
Gal

39

Giải pháp nếu bạn đang sử dụng bố cục tự động:

  • Đặt translatesAutoresizingMaskIntoConstraintsthành NOtrên tất cả các quan điểm liên quan.

  • Vị trí và kích thước chế độ xem cuộn của bạn với các ràng buộc bên ngoài đối với chế độ xem cuộn.

  • Sử dụng các ràng buộc để bố trí các khung nhìn trong chế độ xem cuộn, đảm bảo rằng các ràng buộc đó buộc vào tất cả bốn cạnh của chế độ xem cuộn và không dựa vào chế độ xem cuộn để có kích thước của chúng.

Nguồn: https://developer.apple.com/l Library / ios / technotes / tn2154 / _index.html


9
Imho đây là giải pháp thực sự trên thế giới nơi mọi thứ xảy ra với bố cục tự động. Về các giải pháp khác: Điều gì ... bạn nghiêm túc trong việc tính toán chiều cao và đôi khi chiều rộng của mọi chế độ xem?
Fawkes

Cảm ơn vì đã chia sẻ! Đó nên là câu trả lời được chấp nhận.
SePröbläm

2
Điều này không hoạt động khi bạn muốn chiều cao của cuộn xem dựa trên chiều cao nội dung của nó. (ví dụ: một cửa sổ bật lên tập trung trong màn hình nơi bạn không muốn cuộn cho đến khi một lượng nội dung nhất định).
LeGom

Điều này không trả lời câu hỏi
Igor Vasilev

Giải pháp tuyệt vời. Tuy nhiên, tôi sẽ thêm một viên đạn nữa ... "Đừng giới hạn chiều cao của chế độ xem ngăn xếp con nếu nó tăng theo chiều dọc. Chỉ cần ghim nó vào các cạnh của chế độ xem cuộn."
Andrew Kirna

35

Tôi đã thêm nó vào câu trả lời của Espuz và JCC. Nó sử dụng vị trí y của các cuộc phỏng vấn và không bao gồm các thanh cuộn. Chỉnh sửa Sử dụng dưới cùng của chế độ xem phụ thấp nhất có thể nhìn thấy.

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

1
Tuyệt vời! Đúng thứ tôi cần.
Richard

2
Tôi ngạc nhiên một phương pháp như thế này không phải là một phần của lớp học.
João Portela

Tại sao bạn làm self.scrollView.showsHorizontalScrollIndicator = NO; self.scrollView.showsVerticalScrollIndicator = NO;trong đầu và self.scrollView.showsHorizontalScrollIndicator = YES; self.scrollView.showsVerticalScrollIndicator = YES;cuối phương pháp?
João Portela

Cảm ơn richy :) +1 cho câu trả lời.
Shraddha

1
@richy Các chỉ số cuộn bên phải của bạn là các cuộc phỏng vấn nếu có thể nhìn thấy, nhưng hiển thị / ẩn logic là sai. Bạn cần thêm hai địa phương BOOL: restoreHTHER = NO, restoreV vertical = NO. Nếu hiển thịHTHER, ẩn nó, đặt restoreHTHER thành CÓ. Tương tự cho dọc. Tiếp theo, sau câu lệnh if / in loop if if: if restoreHTHER, đặt thành hiển thị nó, tương tự cho dọc. Mã của bạn sẽ buộc phải hiển thị cả hai chỉ số
người nhập cư bất hợp pháp

13

Đây là câu trả lời được chấp nhận trong swift cho bất cứ ai quá lười biếng để chuyển đổi nó :)

var contentRect = CGRectZero
for view in self.scrollView.subviews {
    contentRect = CGRectUnion(contentRect, view.frame)
}
self.scrollView.contentSize = contentRect.size

và được cập nhật cho Swift3: var contentRect = CGRect.zero để xem trong self.scrollView.subviews {contentRect = contentRect.union (view.frame)} self.scrollView.contentSize = contentRect.size
đã vẽ ..

Điều này có phải được gọi trên chủ đề chính? và trong didLayoutSubViews?
Maksim Kniazev

8

Đây là bản phóng tác Swift 3 của câu trả lời của @ leviatan:

SỰ MỞ RỘNG

import UIKit


extension UIScrollView {

    func resizeScrollViewContentSize() {

        var contentRect = CGRect.zero

        for view in self.subviews {

            contentRect = contentRect.union(view.frame)

        }

        self.contentSize = contentRect.size

    }

}

SỬ DỤNG

scrollView.resizeScrollViewContentSize()

Rất dễ sử dụng!


Rất hữu ích. Sau rất nhiều lần lặp tôi đã tìm thấy giải pháp này và nó thật hoàn hảo.
Hardik Shah

Đáng yêu! Chỉ cần thực hiện nó.
arvidurs

7

Phần mở rộng sau sẽ hữu ích trong 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
    }
}

Logic là giống nhau với các câu trả lời nhất định. Tuy nhiên, Nó bỏ qua các chế độ xem ẩn bên trong UIScrollViewvà tính toán được thực hiện sau khi các chỉ báo cuộn được đặt ẩn.

Ngoài ra, có một tham số chức năng tùy chọn và bạn có thể thêm giá trị bù bằng cách chuyển tham số cho hàm.


Giải pháp này chứa quá nhiều giả định, tại sao bạn lại hiển thị các chỉ báo cuộn trong một phương thức đặt kích thước nội dung?
Kappe

5

Giải pháp tuyệt vời và tốt nhất từ ​​@leviathan. Chỉ cần dịch sang swift bằng cách sử dụng phương pháp tiếp cận FP (lập trình chức năng).

self.scrollView.contentSize = self.scrollView.subviews.reduce(CGRect(), { 
  CGRectUnion($0, $1.frame) 
}.size

Nếu bạn muốn bỏ qua các chế độ xem ẩn, bạn có thể lọc các lượt xem trước khi vượt qua để giảm.
Andy

Cập nhật cho Swift 3:self.contentSize = self.subviews.reduce(CGRect(), { $0.union($1.frame) }).size
John Rogers

4

Bạn có thể lấy chiều cao của nội dung bên trong UIScrollView bằng cách tính toán đứa trẻ nào "đạt đến điểm số". Để tính toán điều này, bạn phải xem xét nguồn gốc Y (bắt đầu) và chiều cao vật phẩm.

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

Bằng cách thực hiện mọi thứ theo cách này, các mục (phỏng vấn) không cần phải được xếp chồng trực tiếp lên nhau.


Bạn cũng có thể sử dụng một lớp lót này trong vòng lặp: maxHeight = MAX (maxHeight, child.frame.origin.y + child.frame.size.height) ;)
Thomas CG de Vilhena

3

Tôi đã đưa ra một giải pháp khác dựa trên giải pháp của @ emlahoma

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

Về cơ bản, chúng tôi tìm ra phần tử nào nằm xa nhất trong chế độ xem và thêm phần đệm 10px ở phía dưới


3

Bởi vì một scrollView có thể có các scrollView khác hoặc cây subViews inDepth khác, nên chạy theo chiều sâu đệ quy là thích hợp hơn. nhập mô tả hình ảnh ở đây

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

Sử dụng

scrollView.recalculateVerticalContentSize_synchronous()

2

Hoặc chỉ cần làm:

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

(Giải pháp này đã được tôi thêm vào dưới dạng nhận xét trong trang này. Sau khi nhận được 19 phiếu bầu cho nhận xét này, tôi đã quyết định thêm giải pháp này làm câu trả lời chính thức vì lợi ích của cộng đồng!)


2

Đối với swift4 sử dụng giảm:

self.scrollView.contentSize = self.scrollView.subviews.reduce(CGRect.zero, {
   return $0.union($1.frame)
}).size

Cũng đáng kiểm tra nếu ít nhất một lượt xem phụ có nguồn gốc == CGPoint.zero hoặc chỉ gán nguồn gốc của một lượt xem phụ cụ thể (ví dụ lớn nhất) về 0.
Paul B

Điều này chỉ hoạt động cho những người đặt quan điểm của họ với CGRect (). Nếu bạn đang sử dụng các ràng buộc thì điều này không hoạt động.
linus_hologram

1

Kích thước tùy thuộc vào nội dung được tải bên trong nó và các tùy chọn cắt. Nếu nó là một textview, thì nó cũng phụ thuộc vào gói, bao nhiêu dòng văn bản, kích thước phông chữ, vân vân và vân vân. Gần như không thể để bạn tự tính toán. Tin tốt là, nó được tính toán sau khi chế độ xem được tải và trong viewWillAppear. Trước đó, tất cả đều chưa biết và kích thước nội dung sẽ giống với kích thước khung. Nhưng, trong phương thức viewWillAppear và sau (như viewDidAppear), kích thước nội dung sẽ là thực tế.


1

Gói mã của Richy Tôi đã tạo một lớp UIScrollView tùy chỉnh tự động thay đổi kích thước nội dung hoàn toàn!

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

Cách sử dụng:
Chỉ cần nhập tệp .h vào trình điều khiển xem của bạn và khai báo một phiên bản SBScrollView thay vì UIScrollView thông thường.


2
layoutSubview có vẻ như đó là nơi thích hợp cho các mã liên quan đến bố cục như vậy. Nhưng trong bố cục UIScrollViewSubview được gọi mỗi khi phần bù nội dung được cập nhật trong khi cuộn. Và vì kích thước nội dung thường không thay đổi trong khi cuộn, điều này khá lãng phí.
Sven

Đúng. Một cách để tránh sự kém hiệu quả này có thể là kiểm tra [self isDragging] và [self isDecelerating] và chỉ thực hiện bố cục nếu cả hai đều sai.
Greg Brown

1

Đặt kích thước nội dung động như thế này.

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

0

nó phụ thuộc vào nội dung thực sự: content.frame.height có thể cung cấp cho bạn những gì bạn muốn? Phụ thuộc nếu nội dung là một điều duy nhất, hoặc một bộ sưu tập các điều.


0

Tôi cũng tìm thấy câu trả lời của leviathan để làm việc tốt nhất. Tuy nhiên, nó đã tính được một chiều cao kỳ lạ. Khi lặp qua các cuộc phỏng vấn, nếu cuộn xem được đặt để hiển thị các chỉ báo cuộn, chúng sẽ nằm trong mảng các cuộc phỏng vấn. Trong trường hợp này, giải pháp là tạm thời vô hiệu hóa các chỉ báo cuộn trước khi lặp, sau đó thiết lập lại cài đặt hiển thị trước đó của chúng.

-(void)adjustContentSizeToFit là một phương thức công khai trên một lớp con tùy chỉnh của 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;
}

0

Tôi nghĩ rằng đây có thể là một cách gọn gàng để cập nhật kích thước chế độ xem nội dung của 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
    }
}

0

Tại sao không phải là một dòng mã ??

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

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

}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.