Есть ли событие изменения размера UIView?

102

У меня есть представление, в котором есть строки и столбцы изображений.

Если размер этого представления изменен, мне нужно изменить положение представлений изображений.

Это представление является частью другого представления, размер которого изменяется.

Есть ли способ определить, когда размер этого представления изменяется?

живи любя
источник
Когда изменяется размер представления? Когда устройство вращается или когда пользователь поворачивает его с помощью мультитач?
Эван Мулавски
Размер этого представления изменяется, когда пользователь нажимает кнопку в другом представлении (основное представление).
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
На самом деле, макет subviews - это именно то, для чего предназначен layoutSubviews, поэтому наблюдение за изменениями размера, хотя и функционально, является плохим дизайном IMO.
uliwitness
@uliwitness ну они все время меняют этот материал. В любом случае, теперь вы должны использовать viewWillTransitionи т.
Д. И
viewWillTransition для UIViewControllers, а не UIView.
Armand
Используется ли по- 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.
auco
3
Такой отличный способ создать круг. Спасибо! override var bounds: CGRect { didSet { layer.cornerRadius = bounds.size.width / 2 }}
SimplGy
4
Работа обнаружения boundsили frameизменения не гарантируется, в зависимости от того, где вы помещаете свое представление в иерархию представлений. layoutSubviewsВместо этого я бы переопределил . Смотрите это и это ответы.
HuaTham
32

Создайте подкласс UIView и переопределите layoutSubviews

Gwang
источник
11
Проблема в том, что подпредставления могут не только изменять свой размер, но и анимировать это изменение размера. Когда UIView запускает анимацию, он не вызывает layoutSubviews каждый раз.
Дэвид Джеске
1
@DavidJeske UIViewAnimationOptions имеет параметр под названием UIViewAnimationOptionLayoutSubviews. Думаю, это может помочь. :)
Neal.Marlin
8

Swift 4 keypath KVO - так я обнаруживаю автоповорот и переход на боковую панель 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
вот еще обсуждение: github.com/ole/whats-new-in-swift-4/blob/master/…
Уоррен Стрингер
.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.
}
Рекле
источник
Разумно и функционально. Хороший звонок.
Эль Зорко
1
FYI Это не работает для подкласса UIImageView. Дополнительная информация здесь: stackoverflow.com/questions/19216684/…
Уильям Энтрикен,
Кроме того, setFrame:мой UITextViewподкласс не был вызван во время изменения размера, вызванного авторотацией, тогда как layoutSubviews:был. Примечание: я использую автоматический макет и iOS 7.0.
ma11hew28
3
никогда не отменять setFrame:. frameявляется производным свойством. Смотрите мой ответ
Адлай Холлер
7

Довольно старый, но все же хороший вопрос. В примере кода Apple и в некоторых их частных подклассах UIView они переопределяют setBounds примерно так:

-(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:он также не вызывается при установке свойства frame (по крайней мере, в 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ónAntonioMontes, спасибо за это! Вы могли заметить связь междуUIView экземплярами и UIViewControllerэкземплярами. Так что, если у вас есть UIViewэкземпляр без подключенного VC, другие ответы будут отличными, но если вы оказались подключенными к VC, это ваш мужчина. Извините, это не применимо к вашему случаю.
Дэн Розенстарк