Чи є подія зміни розміру 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.