Existe um evento de redimensionamento do UIView?

102

Eu tenho um modo de exibição que possui linhas e colunas de visualizações de imagens nele.

Se esta visualização for redimensionada, preciso reorganizar as posições das visualizações de imagens.

Esta visão é uma subvisão de outra visão que é redimensionada.

Existe uma maneira de detectar quando esta visualização está sendo redimensionada?

amor ao vivo
fonte
Quando a visualização é redimensionada? Quando o dispositivo gira ou quando o usuário o gira usando o multitoque?
Evan Mulawski,
Esta visualização é redimensionada quando o usuário toca em um botão em outra visualização (visualização mestre).
live-love

Respostas:

98

Como Uli comentou abaixo, a maneira correta de fazer isso é sobrescrever layoutSubviewse fazer o layout dos imageViews lá.

Se, por algum motivo, você não pode criar subclasses e sobrescrever layoutSubviews, a observação boundsdeve funcionar, mesmo quando for meio suja. Pior ainda, há um risco em observar - a Apple não garante que o KVO funcione nas classes UIKit. Leia a discussão com o engenheiro da Apple aqui: Quando um objeto associado é lançado?

resposta original:

Você pode usar a observação de valores-chave:

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

e implementar:

- (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:
    }
}
Michal
fonte
2
Na verdade, o layout de subvisualizações é exatamente para o que serve o layoutSubviews, portanto, observar as alterações de tamanho, embora funcional, é um péssimo design da IMO.
uliwitness
@uliwitness bem, eles continuam mudando essas coisas. Enfim, agora você deve usar viewWillTransitionetc. etc.
Dan Rosenstark
viewWillTransition de para UIViewControllers, não UIView.
Armand
Usar layoutSubviewsainda é um método recomendado se, dependendo do tamanho atual da visualização, diferentes subvisualizações precisarem ser adicionadas / removidas e diferentes restrições precisarem ser adicionadas / removidas?
Chris Prince
1
Bem, acho que acabei de responder isso para o meu caso. Quando eu faço a configuração (dependendo do tamanho), preciso substituir os limites, ele funciona. Quando faço isso em layoutSubviews, não.
Chris Prince
93

Em uma UIViewsubclasse, observadores de propriedade podem ser usados:

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

Sem subclassificação, observação de valores-chave com chave-caminhos inteligentes vai fazer:

var boundsObservation: NSKeyValueObservation?

func beginObservingBounds() {
    boundsObservation = observe(\.bounds) { capturedSelf, _ in
        // ...
    }
}
Rudolf Adamkovič
fonte
2
@Ali Por que você acha isso?
Rudolf Adamkovič,
em mudanças de quadro de carga, mas parece que os limites não (eu sei que é estranho e talvez eu esteja fazendo algo errado)
Ali
3
@Ali: como foi mencionado algumas vezes aqui: frameé uma propriedade derivada e calculada em tempo de execução. não ignore isso, a menos que você tenha um motivo muito inteligente e sabedor para fazê-lo. caso contrário: use boundsou (ainda melhor) layoutSubviews.
auco
3
Uma ótima maneira de criar um círculo. Obrigado! override var bounds: CGRect { didSet { layer.cornerRadius = bounds.size.width / 2 }}
SimplGy
4
Não é garantido que a detecção boundsou framealteração funcione, dependendo de onde você coloca sua visualização na hierarquia de visualizações. Eu substituiria em layoutSubviewsvez disso. Veja isso e essas respostas.
HuaTham
31

Crie uma subclasse de UIView e substitua layoutSubviews

gwang
fonte
11
o problema com isso é que as subvisualizações podem não apenas alterar seu tamanho, mas também animar essa alteração de tamanho. Quando UIView executa a animação, ele não chama layoutSubviews todas as vezes.
David Jeske
1
@DavidJeske UIViewAnimationOptions tem uma opção chamada UIViewAnimationOptionLayoutSubviews. Acho que isso talvez ajude. :)
Neal.Marlin
8

Swift 4 keypath KVO - É assim que detecto a rotação automática e a passagem para o painel lateral do iPad. Deve funcionar qualquer modo de exibição. Tive que observar a camada da 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()
    //...
}
Warren Stringer
fonte
Essa solução funcionou para mim. Eu sou novo no Swift e não tenho certeza da sintaxe \ .bounds que você usou aqui. O que isso significa exatamente?
WBuck
aqui está mais discussão: github.com/ole/whats-new-in-swift-4/blob/master/…
Warren Stringer
.layerfez o truque! Você sabe por que usar view.observenão funciona?
d4Rk
@ d4Rk - Não sei. Meu palpite é que os limites da camada são menos ambíguos do que os quadros UIView. Por exemplo, o contentOffset de UITableView afetará as coordenadas finais de seus subViews. Não tenho certeza.
Warren Stringer
ESTA É UMA GRANDE RESPOSTA PARA MIM. Meu caso é que estou usando o AutoLayout para definir minhas visualizações. MAS UICollectionViewFlowLayout força você a fornecer um itemSize explícito. mas quando o tamanho de seu collectionView muda (como no meu caso, quando estou mostrando uma barra de ferramentas com AutoLayout) - o itemSize permanece o mesmo e joga = [o observador é a abordagem mais limpa que consegui até agora. e o melhor !!
Yitzchak
7

Você pode criar uma subclasse de UIView e substituir o

setFrame: (CGRect) frame

método. Este é o método chamado quando o quadro (ou seja, o tamanho) da visualização é alterado. Faça algo assim:

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

  // Do your custom code here.
}
Rekle
fonte
Sensível e funcional. Boa decisão.
El Zorko
1
FYI Isso não funciona para a subclasse UIImageView. Mais informações aqui: stackoverflow.com/questions/19216684/…
William Entriken
Além disso, setFrame:não foi chamado em minha UITextViewsubclasse durante o redimensionamento causado pela rotação automática, mas sim layoutSubviews:. Nota: Estou usando o layout automático e iOS 7.0.
ma11hew28,
3
nunca substitua setFrame:. frameé uma propriedade derivada. Veja minha resposta
Adlai Holler
7

Muito antigo, mas ainda assim uma boa pergunta. No código de amostra da Apple, e em algumas de suas subclasses UIView privadas, eles substituem setBounds mais ou menos como:

-(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];
}

Substituir setFrame:NÃO é uma boa ideia. frameé derivado de center, boundse transform, portanto, iOS não necessariamente chamará setFrame:.

Adlai Holler
fonte
2
Isso é verdade (em teoria). No entanto, setBounds:também não é chamado ao definir a propriedade do quadro (pelo menos no iOS 7.1). Esta pode ser uma otimização que a Apple adicionou para evitar a mensagem adicional.
nschum
1
… E um design ruim do lado da Apple para não chamar seus próprios acessadores.
osxdirk
1
Na verdade, ambos frame e boundssão derivados da base da visão CALayer; eles apenas chamam o getter da camada. E setFrame:define a moldura da camada, enquanto setBounds:define os limites da camada. Portanto, você não pode simplesmente substituir um ou outro. Além disso, layoutSubviewsé chamado excessivamente (não apenas nas alterações de geometria), portanto, também pode nem sempre ser uma boa opção. Ainda procurando ...
big_m
-3

Se você estiver em uma instância UIViewController, a substituição viewDidLayoutSubviewsresolve.

override func viewDidLayoutSubviews() {
    // update subviews
}
Dan Rosenstark
fonte
Tamanho UIView alterado necessário, não UIViewController.
Gastón Antonio Montes,
@ GastónAntonioMontes obrigado por isso! Você deve ter notado um relacionamento entre UIViewinstâncias e UIViewControllerinstâncias. Portanto, se você tiver uma UIViewinstância sem VC anexado, as outras respostas são ótimas, mas se acontecer de você ser anexado a um VC, este é o seu homem. Desculpe, não se aplica ao seu caso.
Dan Rosenstark,