Чи є подія зміни розміру UIView?


103

У мене є вид, у якому є рядки та стовпці перегляду зображень.

Якщо цей погляд буде змінено, мені потрібно змінити положення перегляду зображень.

Цей вид є підзаглядом іншого перегляду, який має розмір.

Чи існує спосіб виявити зміни розміру цього подання?


Коли розмір перегляду буде змінено? Коли пристрій обертається, або коли користувач обертає його за допомогою мультитач?
— Еван Мулавський

Цей огляд змінюється, коли користувач натискає кнопку на іншому вікні (головний вигляд).
— live-love

Відповіді:


98

Як прокоментував Улі нижче, правильний спосіб зробити це переосмислення layoutSubviewsта розміщення imageViews там.

Якщо з якихось причин ви не можете підкласирувати та переосмислювати layoutSubviews, спостереження boundsповинно працювати, навіть якщо ви є брудними. Що ще гірше, ризик спостерігається - Apple не гарантує, що KVO працює на класах UIKit. Прочитайте дискусію з інженером Apple тут: Коли асоційований об’єкт звільняється?

оригінальна відповідь:

Ви можете використовувати ключові значення, спостерігаючи:

[yourView addObserver:self forKeyPath:@"bounds" options:0 context:nil];

та реалізувати:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (object == yourView && [keyPath isEqualToString:@"bounds"]) {
        // do your stuff, or better schedule to run later using performSelector:withObject:afterDuration:
    }
}

2
Дійсно, викладення підвидів - саме те, для чого розміщується layoutSubviews, тому спостереження за зміною розміру, функціональним, є поганим дизайном IMO.
— uliwitness

@uliwitness добре, що вони все одно змінюють цей матеріал. У будь-якому випадку, тепер ви повинні використовувати viewWillTransitionetc. etc.
— Dan Rosenstark

viewWillTransition для UIViewControllers, а не UIView.
— Арман

Чи layoutSubviewsвсе-таки використовується рекомендований метод, якщо залежно від поточного розміру представлення потрібні різні підгляди, які потрібно додати / видалити, і потрібно додати / видалити різні обмеження?
— Кріс Принс

1
Ну, я думаю, що я просто відповів на це у своїй справі. Коли я роблю налаштування (залежно від розміру), який мені потрібен у перекритті меж, це працює. Коли я це роблю в layoutSubviews, це не так.
— Кріс Принс

93

У UIViewпідкласі спостерігачі власності можуть використовуватися:

override var bounds: CGRect {
    didSet {
        // ...
    }
}

Без підкласифікації спостереження за значенням ключа з інтелектуальними шляхами ключів виконає:

var boundsObservation: NSKeyValueObservation?

func beginObservingBounds() {
    boundsObservation = observe(\.bounds) { capturedSelf, _ in
        // ...
    }
}

2
@Ali Чому ти так вважаєш?
— Рудольф Адамкович

щодо зміни рамки завантаження, але, схоже, меж немає (я знаю, що це дивно, і, можливо, я щось не так)
— Алі

3
@Ali: як це вже згадувалося пару разів: frameце похідна та обчислена властивість під час виконання. не перекривайте це, якщо у вас немає дуже розумних і знаючих причин для цього. інакше: використовувати boundsабо (ще краще) layoutSubviews.
— ауко

3
Такий чудовий спосіб створити коло. Дякую! override var bounds: CGRect { didSet { layer.cornerRadius = bounds.size.width / 2 }}
— SimplGy

4
Виявлення boundsчи frameзміна не гарантується, залежно від того, де ви розмістили свій погляд у ієрархії перегляду. Я б замість layoutSubviewsцього замінив . Дивіться це і цю відповідь.
— HuaTham

31

Створіть підклас UIView та замініть layoutSubviews


11
Проблема з цим полягає в тому, що підзагляди можуть не тільки змінювати свій розмір, але й анімувати цю зміну. Коли UIView запускає анімацію, вона не викликає layoutSubviews кожен раз.
— Девід Джеске

1
@DavidJeske UIViewAnimationOptions має опцію під назвою UIViewAnimationOptionLayoutSubviews. Я думаю, це може допомогти. :)
— Neal.Marlin

8

Швидкий шлях 4 КВО - саме так я виявляю автоматичне поворот і переміщення на бічну панель iPad. Чи повинен працювати будь-який погляд. Довелося спостерігати шар UIView.

private var observer: NSKeyValueObservation?

override func viewDidLoad() {
    super.viewDidLoad()
    observer = view.layer.observe(\.bounds) { object, _ in
        print(object.bounds)
    }
    // ...
}
override func viewWillDisappear(_ animated: Bool) {
    observer?.invalidate()
    //...
}

Це рішення спрацювало на мене. Я новачок у Swift, і я не впевнений у синтаксисі \ .bounds, який ви тут використовували. Що це означає саме?
— WBuck


.layerзробив трюк! Чи знаєте ви, чому використання view.observeне працює?
— d4Rk

@ d4Rk - я не знаю. Я здогадуюсь, що шар виявляється менш неоднозначним, ніж кадри UIView. Наприклад, contentOffset UITableView вплине на кінцеві координати його підпоглядів. Не впевнений.
— Воррен Стрінгер

ЦЕ ВЕЛИКИЙ ВІДПОВІД для мене. Моя справа в тому, що я використовую AutoLayout для викладу своїх поглядів. АЛЕ UICollectionViewFlowLayout змушує вас надати явний itemSize. але коли розмір вашого collectionView змінюється (як, наприклад, у моєму випадку, коли я показую панель інструментів з AutoLayout) - itemSize залишається тим самим і кидає = [спостерігач - це найчистіший підхід, який я отримав поки що. і найкращий !!
— Іцчак

7

Ви можете створити підклас UIView і замінити

setFrame: (CGRect) кадр

метод. Це метод, який називається при зміні кадру (тобто розміру) подання. Зробіть щось подібне:

- (void) setFrame:(CGRect)frame
{
  // Call the parent class to move the view
  [super setFrame:frame];

  // Do your custom code here.
}

Чуттєвий і функціональний. Гарний дзвінок.
— El Zorko

1
FYI Це не працює для підкласу UIImageView. Більш детальна інформація тут: stackoverflow.com/questions/19216684 / ...
— William Entriken

Крім того, setFrame:не був викликаний мій UITextViewпідклас під час зміни розміру, викликаного авторотацією, тоді як layoutSubviews:. Примітка: я використовую автоматичний макет та iOS 7.0.
— ma11hew28

3
ніколи не перестарайтеся setFrame:. frameє похідною властивістю. Дивіться мою відповідь
— Адлай Холлер

7

Досить старе, але все-таки гарне запитання. У зразковому коді Apple і в деяких їх приватних підкласах UIView вони змінюють набірBounds приблизно так:

-(void)setBounds:(CGRect)newBounds {
    BOOL const isResize = !CGSizeEqualToSize(newBounds.size, self.bounds.size);
    if (isResize) [self prepareToResizeTo:newBounds.size]; // probably saves 
    [super setBounds:newBounds];
    if (isResize) [self recoverFromResizing];
}

Переоцінка setFrame:НЕ є гарною ідеєю. frameпоходить від center, boundsі transform, тому iOS не обов'язково викликатиме setFrame:.


2
Це правда (теоретично). Однак setBounds:не викликається ні при встановленні властивості кадру (принаймні, на iOS 7.1). Це може бути оптимізацією, яку Apple додала, щоб уникнути додаткового повідомлення.
— nschum

1
… І поганий дизайн на стороні Apple, щоб не викликати власних постачальників.
— osxdirk

1
Насправді і те, frameі boundsпохідне від основної точки зору CALayer; вони просто зателефонують до геттера шару. І setFrame:встановлює рамку шару, а setBounds:встановлює межі шару. Таким чином, ви не можете просто перекрити те чи інше. Крім того, layoutSubviewsйого називають надмірно (не лише щодо зміни геометрії), тому це не завжди може бути і хорошим варіантом. Ще дивлячись ...
— big_m

-3

Якщо ви знаходитесь в екземплярі UIViewController, переосмислення viewDidLayoutSubviewsвиконує завдання.

override func viewDidLayoutSubviews() {
    // update subviews
}

Необхідно змінити розмір UIView, а не UIViewController.
— Gastón Antonio Montes

@ GastónAntonioMontes дякую за це! Можливо, ви помітили зв’язок між UIViewекземплярами та UIViewControllerекземплярами. Отже, якщо у вас є UIViewекземпляр, на якому не доданий VC, інші відповіді чудові, але якщо вам трапляється приєднатись до ВК, це ваш чоловік. Вибачте, це не стосується вашої справи.
— Дан Розенстарк
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.