Как мне автоматически изменить размер UIScrollView в соответствии с его содержимым


130

Есть ли способ сделать UIScrollViewавтоматическую настройку высоты (или ширины) прокручиваемого контента?

Что-то вроде:

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

Ответы:


306

Лучший метод, с которым я когда-либо сталкивался, для обновления размера содержимого a UIScrollViewна основе содержащихся в нем подпредставлений:

Objective-C

CGRect contentRect = CGRectZero;

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

стриж

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

9
Я запускал это, viewDidLayoutSubviewsчтобы авторасстановка завершилась, в iOS7 это работало хорошо, но при тестировании ОС iOS6 автопрокладка по какой-то причине не завершила работу, поэтому у меня были неправильные значения высоты, поэтому я переключился на viewDidAppearтеперь работает нормально .. просто чтобы указать, может быть, это кому-то понадобится. спасибо
Hatem Alimam

1
В iPad iOS6 в правом нижнем углу был загадочный UIImageView (7x7). Я отфильтровал это, приняв только (видимые && альфа) компоненты пользовательского интерфейса, и все заработало. Интересно, связан ли этот imageView с scrollBars, определенно не с моим кодом.
JOM

5
Будьте осторожны, когда включены индикаторы прокрутки! UIImageView будет автоматически создан для каждого из них SDK. вы должны это учитывать, иначе размер вашего контента будет неправильным. (см. stackoverflow.com/questions/5388703/… )
AmitP

Могу подтвердить, что это решение отлично работает для меня в Swift 4
Итан Хамфрис

На самом деле, мы не можем начать объединение с CGRect.zero, это работает, только если вы размещаете некоторые подпредставления в отрицательных координатах. Вы должны начинать объединение рамок подпредставления с первого подпредставления, а не с нуля. Например, у вас есть только одно подпредставление, которое представляет собой UIView с рамкой (50, 50, 100, 100). Размер вашего содержимого будет (0, 0, 150, 150), а не (50, 50, 100, 100).
Евгений

74

UIScrollView не знает высоту своего содержимого автоматически. Вы должны сами рассчитать высоту и ширину

Сделайте это с чем-то вроде

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

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

Но это работает только в том случае, если виды расположены одно под другим. Если у вас есть виды рядом друг с другом, вам нужно только добавить высоту одного, если вы не хотите, чтобы содержимое скроллера было больше, чем оно есть на самом деле.


Как вы думаете, сработает ли что-то подобное? CGFloat scrollViewHeight = scrollView.contentSize.height; [scrollView setContentSize: (CGSizeMake (320, scrollViewHeight))]; Кстати, не забудьте спросить размер, а затем высоту. scrollViewHeight + = view.frame.size.height
jwerre

Инициализация scrollViewHeight до высоты делает ползунок больше, чем его содержимое. Если вы это сделаете, не добавляйте высоту видимых видов к исходной высоте содержимого скроллера. Хотя может быть нужен начальный пробел или еще что-то.
emenegro 01

21
Или просто сделайте:int y = CGRectGetMaxY(((UIView*)[_scrollView.subviews lastObject]).frame); [_scrollView setContentSize:(CGSizeMake(CGRectGetWidth(_scrollView.frame), y))];
Gal

@saintjab, спасибо, я просто добавил это как формальный ответ :)
Гал

41

Решение, если вы используете автоматический макет:

  • Установите translatesAutoresizingMaskIntoConstraintsдля NOвсех задействованных представлений.

  • Разместите и измените размер прокрутки с ограничениями, внешними по отношению к прокрутке.

  • Используйте ограничения, чтобы расположить подвиды внутри представления прокрутки, будучи уверенными, что ограничения привязаны ко всем четырем краям представления прокрутки и не полагаются на представление прокрутки для получения их размера.

Источник: https://developer.apple.com/library/ios/technotes/tn2154/_index.html


10
Imho это реальное решение в мире, где все происходит с автоматическим макетом. Относительно других решений: Какого ... вы серьезно относитесь к расчету высоты, а иногда и ширины каждого вида?
Fawkes

Спасибо за то, что поделился этим! Это должен быть принятый ответ.
SePröbläm

2
Это не работает, если вы хотите, чтобы высота прокрутки была основана на высоте его содержимого. (например, всплывающее окно в центре экрана, где вы не хотите прокручивать до определенного количества контента).

Это не ответ на вопрос
Игорь Васильев

Отличное решение. Однако я бы добавил еще один пункт ... «Не ограничивайте высоту представления дочернего стека, если оно должно расти вертикально. Просто прикрепите его к краям представления прокрутки».
Эндрю Кирна

35

Я добавил это в ответ Espuz и JCC. Он использует позицию 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
Чудесно! Как раз то, что мне было нужно.
Ричард

2
Я удивлен, что такой метод не является частью класса.
Жуан Портела

Почему вы сделали self.scrollView.showsHorizontalScrollIndicator = NO; self.scrollView.showsVerticalScrollIndicator = NO;в начале и self.scrollView.showsHorizontalScrollIndicator = YES; self.scrollView.showsVerticalScrollIndicator = YES;в конце метода?
Жуан Портела

Спасибо richy :) +1 за ответ.
Shraddha

1
@richy, вы правы, индикаторы прокрутки - это подвиды, если они видны, но логика отображения / скрытия неверна. Вам нужно добавить два локальных объекта BOOL: restoreHorizontal = NO, restoreVertical = NO. Если отображаетсяHorizontal, скройте его, установите для параметра restoreHorizontal значение YES. То же самое для вертикального. Затем после цикла for / in вставьте оператор if: if restoreHorizontal, установите его для отображения, то же самое для вертикального. Ваш код просто заставит показывать оба индикатора
нелегальный иммигрант

13

Вот быстрый ответ для тех, кому лень его преобразовать :)

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

и обновлено для Swift3: var contentRect = CGRect.zero для просмотра в self.scrollView.subviews {contentRect = contentRect.union (view.frame)} self.scrollView.contentSize = contentRect.size
нарисовано ..

Это нужно вызывать в основном потоке? а в didLayoutSubViews?
Максим Князев

8

Вот адаптация Swift 3 ответа @leviatan:

РАСПРОСТРАНЕНИЕ

import UIKit


extension UIScrollView {

    func resizeScrollViewContentSize() {

        var contentRect = CGRect.zero

        for view in self.subviews {

            contentRect = contentRect.union(view.frame)

        }

        self.contentSize = contentRect.size

    }

}

ИСПОЛЬЗОВАНИЕ

scrollView.resizeScrollViewContentSize()

Очень прост в использовании!


Очень полезно. После стольких итераций я нашел это решение, и оно идеально.
Hardik Shah 08

Прекрасный! Просто реализовал это.
arvidurs

7

Следующее расширение было бы полезно в 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
    }
}

Логика та же с данными ответами. Тем не менее, он пропускает скрытые представления внутри, UIScrollViewи расчет выполняется после того, как индикаторы прокрутки скрыты.

Кроме того, есть необязательный параметр функции, и вы можете добавить значение смещения, передав параметр функции.


Это решение содержит слишком много предположений, почему вы показываете индикаторы прокрутки в методе, который устанавливает размер содержимого?
Каппе

5

Отличное и лучшее решение от @leviathan. Просто перевод на быстрый, используя подход FP (функциональное программирование).

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

Если вы хотите игнорировать скрытые представления, вы можете отфильтровать их перед переходом на сокращение.
Энди

Обновлено для Swift 3:self.contentSize = self.subviews.reduce(CGRect(), { $0.union($1.frame) }).size
Джон Роджерс

4

Вы можете получить высоту содержимого внутри UIScrollView, вычислив, какой дочерний элемент «достигает дальнейших». Чтобы рассчитать это, вы должны принять во внимание исходную точку Y (начало) и высоту элемента.

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

Поступая таким образом, элементы (подпредставления) не нужно располагать друг под другом.


Вы также можете использовать этот один лайнер внутри цикла: maxHeight = MAX (maxHeight, child.frame.origin.y + child.frame.size.height) ;)
Thomas CG de Vilhena

3

Я придумал другое решение, основанное на решении @ 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)];

По сути, мы выясняем, какой элемент находится дальше всего в представлении, и добавляем отступ в 10 пикселей внизу.


3

Поскольку scrollView может иметь другие scrollView или другое дерево вложенных представлений inDepth, предпочтительнее выполнять глубинный рекурсивный запуск. введите описание изображения здесь

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

использование

scrollView.recalculateVerticalContentSize_synchronous()

2

Или просто сделайте:

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

(Это решение было добавлено мной в качестве комментария на этой странице. Получив 19 голосов за этот комментарий, я решил добавить это решение в качестве официального ответа на благо сообщества!)


2

Для swift4 с использованием reduce:

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

Также стоит проверить, имеет ли хотя бы одно подпредставление origin == CGPoint.zero, или просто присвоить конкретному (например, самому большому) происхождению подпредставления нулю.
Paul B

Это работает только для людей, которые размещают свои взгляды с помощью CGRect (). Если вы используете ограничения, это не работает.
linus_hologram 05

1

Размер зависит от содержимого, загруженного в него, и параметров обрезки. Если это текстовое представление, то это также зависит от обтекания, количества строк текста, размера шрифта и т. Д. Для вас практически невозможно вычислить себя. Хорошая новость в том, что он вычисляется после загрузки представления и в viewWillAppear. До этого все неизвестно, а размер содержимого будет таким же, как и размер кадра. Но в методе viewWillAppear и после него (например, viewDidAppear) размер содержимого будет фактическим.


1

Обернув код Ричи, я создал собственный класс UIScrollView, который полностью автоматизирует изменение размера содержимого!

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

Как использовать:
просто импортируйте файл .h в контроллер представления и объявите экземпляр SBScrollView вместо обычного UIScrollView.


2
layoutSubviews похоже, что это подходящее место для такого кода, связанного с макетом. Но в макете UIScrollView Subview вызывается каждый раз, когда смещение содержимого обновляется во время прокрутки. А поскольку размер содержимого обычно не меняется при прокрутке, это довольно расточительно.
Sven

Это правда. Одним из способов избежать этой неэффективности может быть проверка [self isDragging] и [self isDecelerating] и выполнение макета только в том случае, если оба значения ложны.
Грег Браун

1

Установите размер динамического содержимого следующим образом.

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

0

это действительно зависит от содержимого: content.frame.height может дать вам то, что вы хотите? Зависит от того, является ли контент отдельной вещью или набором вещей.


0

Я также нашел, что ответ левиафана работает лучше всего. Однако он рассчитывал странную высоту. При циклическом просмотре подпредставлений, если scrollview настроен на отображение индикаторов прокрутки, они будут в массиве подвидов. В этом случае решение состоит в том, чтобы временно отключить индикаторы прокрутки перед зацикливанием, а затем восстановить их предыдущую настройку видимости.

-(void)adjustContentSizeToFit - это общедоступный метод в настраиваемом подклассе 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

Я думаю, что это может быть изящным способом обновления размера представления содержимого 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

почему ни одной строчки кода ??

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

}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.