Як я автоматично розміщую UIScrollView, щоб відповідати його вмісту


130

Чи є спосіб зробити UIScrollViewавтоматичне налаштування під висоту (або ширину) вмісту, який він прокручує?

Щось на зразок:

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

Відповіді:


306

Найкращий спосіб, з якого я коли-небудь стикався, щоб оновлювати розмір вмісту на UIScrollViewоснові вмісту його підвидів:

Ціль-С

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 він працював добре, але тестування OS iOS6 автолайн чомусь не закінчило роботу, тому у мене були неправильні значення висоти, тому я перейшов на viewDidAppearтеперішній час працює добре .. просто зазначити, можливо, комусь це знадобиться. дякую
Хатем Алімам

1
З iOS6 iPad в нижньому правому куті був загадковий UIImageView (7x7). Я відфільтрував це, прийнявши лише (видимі && альфа) компоненти інтерфейсу користувача, потім він працював. Цікаво, чи пов’язаний цей imageView з прокруткою, точно не мій код.
JOM

5
Будьте уважні, коли ввімкнено індикатори прокрутки! SDK автоматично створить для кожного з них UIImageView. ви повинні це врахувати, інакше розмір вмісту буде неправильним. (Див 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

21
Або просто зробіть:int y = CGRectGetMaxY(((UIView*)[_scrollView.subviews lastObject]).frame); [_scrollView setContentSize:(CGSizeMake(CGRectGetWidth(_scrollView.frame), y))];
Гал

@saintjab, дякую, я щойно додав це як формальну відповідь :)
Гал

40

Рішення, якщо ви використовуєте автоматичний макет:

  • Встановіть translatesAutoresizingMaskIntoConstraintsдля NOвсіх залучених поглядів.

  • Розмістіть і розміріть свій перегляд прокрутки з обмеженнями, зовнішніми для подання прокрутки.

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

Джерело: https://developer.apple.com/library/ios/technotes/tn2154/_index.html


10
Імхо, це справжнє рішення у світі, де все відбувається з автоматичним компонуванням. Щодо інших рішень: що ... Ви серйозно ставитеся до обчислення висоти та часом ширини кожного виду?
Фокс

Дякуємо, що поділилися цим! Це повинна бути прийнята відповідь.
SePröbläm

2
Це не працює, якщо ви хочете, щоб висота прокрутки базувалася на його висоті вмісту. (наприклад, спливаюче вікно з центром на екрані, де ви не хочете прокручувати до певної кількості вмісту).

Це не відповідає на запитання
Ігор Василев

Прекрасне рішення. Однак я б додав ще одну кулю ... "Не обмежуйте висоту дочірнього виду, якщо він повинен рости вертикально. Просто закріпіть його на краях подання прокрутки."
Андрій Кірна

35

Я додав це до відповіді Еспуза та СКК. Він використовує 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;в кінці методу?
Жоао Портела

Дякую багатим :) +1 за відповідь.
Шрадда

1
@richy Ви правильні індикатори прокрутки, якщо вони видимі, є підзаглядом, але логіка показу / приховування неправильна. Вам потрібно додати два локальних знаки BOOL: RestoHorizontal = NO, RestoVertical = NO. Якщо показано Horizontal, прихойте його, встановіть RestoHorizontal на YES. Те саме для вертикалі. Далі, після циклу for / in вставити if: якщо відновитиHorizontal, встановіть його для показу, те ж саме для вертикалі. Ваш код просто змусить показувати обидва показники
нелегальний іммігрант

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

Ось адаптація відповіді @ leviatan Swift 3:

РОЗШИРЕННЯ

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

Дуже простий у використанні!


Дуже корисний. Після стількох ітерацій я знайшов це рішення, і воно ідеально.
Хардік Шах

Прекрасна! Просто його реалізували.
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 може мати інше scrollViews або інше дерево inDepth subViews, бажано запускати в глибину рекурсивно. введіть тут опис зображення

Швидкий 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 з використанням зменшення:

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

Також варто перевірити, чи принаймні одне підрозділ має вихід == CGPoint.zero або просто призначити походження певного (найбільшого, наприклад) подвида до нуля.
Павло Б

Це працює лише для людей, які розміщують свої погляди за допомогою CGRect (). Якщо ви використовуєте обмеження, це не працює.
linus_hologram

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 виглядає, що це правильне місце для такого коду, пов’язаного з компонуванням. Але в макеті UIScrollViewSubview викликається щоразу, коли зміщення змісту оновлюється під час прокрутки. А оскільки розмір вмісту зазвичай не змінюється під час прокрутки, це досить марно.
Свен

Це правда. Одним із способів уникнути цієї неефективності може бути перевірка [self isDragging] та [self isDecelerating] та виконувати макет лише у тому випадку, коли обидва є помилковими.
Грег Браун

1

Встановіть такий динамічний розмір вмісту.

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

0

це дійсно залежить від вмісту: content.frame.height може дати вам те, що ви хочете? Залежить, чи вміст є єдиною річчю чи колекцією речей.


0

Я також знайшов відповідь левіафана, щоб він працював найкраще. Однак він обчислював дивну висоту. Якщо циклічно проглядати підпрогляди, якщо параметр прокрутки встановлений для відображення індикаторів прокрутки, вони будуть знаходитись у масиві підпрезентацій. У цьому випадку рішення полягає в тимчасовому відключенні індикаторів прокрутки перед циклом, а потім відновлення їх попереднього налаштування видимості.

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