Existe-t-il un événement de redimensionnement UIView?

102

J'ai une vue qui contient des lignes et des colonnes d'images vues.

Si cette vue est redimensionnée, je dois réorganiser les positions des vues d'image.

Cette vue est une sous-vue d'une autre vue qui est redimensionnée.

Existe-t-il un moyen de détecter le redimensionnement de cette vue?

l'amour en direct
la source
Quand la vue est-elle redimensionnée? Lorsque l'appareil pivote ou lorsque l'utilisateur le fait pivoter à l'aide du multi-touch?
Evan Mulawski
Cette vue est redimensionnée lorsque l'utilisateur appuie sur un bouton sur une autre vue (vue principale).
live-love

Réponses:

98

Comme Uli l'a commenté ci-dessous, la bonne façon de le faire est de remplacer layoutSubviewset de mettre en page les imageViews ici.

Si, pour une raison quelconque, vous ne pouvez pas sous-classer et remplacer layoutSubviews, l'observation boundsdevrait fonctionner, même si elle est un peu sale. Pire encore, il y a un risque avec l'observation - Apple ne garantit pas que KVO fonctionne sur les classes UIKit. Lisez la discussion avec l'ingénieur Apple ici: Quand un objet associé est-il publié?

réponse originale:

Vous pouvez utiliser l'observation des valeurs-clés:

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

et mettre en œuvre:

- (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
la source
2
Vraiment, la présentation des sous-vues est exactement ce à quoi sert layoutSubviews, donc observer les changements de taille, bien que fonctionnel, est une mauvaise conception de l'OMI.
uliwitness
@uliwitness eh bien, ils continuent de changer ces choses. Quoi qu'il en soit, maintenant vous devriez utiliser viewWillTransitionetc. etc.
Dan Rosenstark
viewWillTransition de pour UIViewControllers, pas UIView.
Armand
Est-il layoutSubviewstoujours recommandé d' utiliser une méthode si, en fonction de la taille actuelle de la vue, différentes sous-vues devaient être ajoutées / supprimées et différentes contraintes devaient être ajoutées / supprimées?
Chris Prince
1
Eh bien, je pense que je viens de répondre à cela pour mon cas. Lorsque je fais la configuration (en fonction de la taille) dont j'ai besoin pour remplacer les limites, cela fonctionne. Quand je le fais dans layoutSubviews, ce n'est pas le cas.
Chris Prince
93

Dans une UIViewsous - classe, les observateurs de propriétés peuvent être utilisés:

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

Sans sous-classification, l' observation de valeur-clé avec des chemins de clé intelligents fera:

var boundsObservation: NSKeyValueObservation?

func beginObservingBounds() {
    boundsObservation = observe(\.bounds) { capturedSelf, _ in
        // ...
    }
}
Rudolf Adamkovič
la source
2
@Ali Pourquoi pensez-vous cela?
Rudolf Adamkovič
sur les changements de cadre de charge, mais il semble que les limites ne le soient pas (je sais que c'est étrange et peut-être que je me trompe)
Ali
3
@Ali: comme cela a été mentionné à plusieurs reprises ici: frameest une propriété calculée dérivée et calculée à l'exécution. n'écrasez pas cela, sauf si vous avez une raison très intelligente et bien informée de le faire. sinon: utilisez boundsou (encore mieux) layoutSubviews.
auco
3
Une excellente façon de créer un cercle. Merci! override var bounds: CGRect { didSet { layer.cornerRadius = bounds.size.width / 2 }}
SimplGy
4
Il n'est pas garanti que la détection boundsou la framemodification fonctionne, selon l'endroit où vous placez votre vue dans la hiérarchie des vues. Je remplacerais layoutSubviewsplutôt. Voyez ceci et cela répond.
HuaTham
31

Créer une sous-classe de UIView et remplacer la disposition

gwang
la source
11
le problème avec ceci est que les sous-vues peuvent non seulement changer leur taille, mais elles peuvent animer ce changement de taille. Lorsque UIView exécute l'animation, il n'appelle pas layoutSubviews à chaque fois.
David Jeske
1
@DavidJeske UIViewAnimationOptions a une option appelée UIViewAnimationOptionLayoutSubviews. Je pense que cela peut aider. :)
Neal.Marlin
8

Swift 4 keypath KVO - C'est ainsi que je détecte la rotation automatique et le déplacement vers le panneau latéral de l'iPad. Devrait fonctionner n'importe quelle vue. Dû observer la couche de l'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
la source
Cette solution a fonctionné pour moi. Je suis nouveau sur Swift et je ne suis pas sûr de la syntaxe \ .bounds que vous avez utilisée ici. Qu'est-ce que cela signifie exactement?
WBuck
voici plus de discussion: github.com/ole/whats-new-in-swift-4/blob/master/…
Warren Stringer
.layera fait l'affaire! Savez-vous pourquoi l'utilisation view.observene fonctionne pas?
d4Rk le
@ d4Rk - Je ne sais pas. Je suppose que les limites de la couche sont moins ambiguës que les cadres UIView. Par exemple, contentOffset de UITableView affectera les coordonnées finales de ses sous-vues. Pas certain.
Warren Stringer
CECI EST UNE BELLE RÉPONSE POUR MOI. Mon cas est que j'utilise AutoLayout pour présenter mes vues. MAIS UICollectionViewFlowLayout vous oblige à donner un itemSize explicite. mais lorsque la taille de votre collectionView change (comme dans mon cas lorsque j'affiche une barre d'outils avec AutoLayout) - le itemSize reste le même et jette = [l'observateur est l'approche la plus propre que j'ai eue jusqu'à présent. et le meilleur !!
Yitzchak
7

Vous pouvez créer une sous-classe de UIView et remplacer le

setFrame: cadre (CGRect)

méthode. C'est la méthode appelée lorsque le cadre (c'est-à-dire la taille) de la vue est modifié. Faites quelque chose comme ceci:

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

  // Do your custom code here.
}
rekle
la source
Sensible et fonctionnel. Bon appel.
El Zorko
1
FYI Cela ne fonctionne pas pour la sous-classe UIImageView. Plus d'informations ici: stackoverflow.com/questions/19216684/…
William Entriken
De plus, setFrame:n'a pas été appelé sur ma UITextViewsous - classe lors du redimensionnement causé par l'autorotation alors que layoutSubviews:c'était. Remarque: j'utilise la mise en page automatique et iOS 7.0.
ma11hew28
3
ne jamais passer outre setFrame:. frameest une propriété dérivée. Voir ma réponse
Adlai Holler
7

Assez vieux mais toujours une bonne question. Dans l'exemple de code d'Apple et dans certaines de leurs sous-classes UIView privées, ils remplacent setBounds à peu près comme:

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

Remplacer setFrame:n'est PAS une bonne idée. frameest dérivé de center, boundset transform, donc iOS n'appellera pas nécessairement setFrame:.

Adlai Holler
la source
2
C'est vrai (en théorie). Cependant, setBounds:n'est pas appelée non plus lors de la définition de la propriété frame (au moins sur iOS 7.1). Cela pourrait être une optimisation ajoutée par Apple pour éviter le message supplémentaire.
nschum
1
… Et une mauvaise conception du côté d'Apple pour ne pas appeler ses propres accesseurs.
osxdirk
1
En fait, les deux frame et boundssont dérivés du sous-jacent de la vue CALayer; ils appellent simplement le getter de la couche. Et setFrame:définit le cadre du calque, tandis que setBounds:définit les limites du calque. Vous ne pouvez donc pas simplement passer outre l'un ou l'autre. De plus, layoutSubviewsest appelé de manière excessive (pas seulement en cas de changements de géométrie), ce n'est donc pas toujours une bonne option non plus. Toujours à la recherche ...
big_m
-3

Si vous êtes dans une instance UIViewController, la substitution viewDidLayoutSubviewsfait l'affaire.

override func viewDidLayoutSubviews() {
    // update subviews
}
Dan Rosenstark
la source
La taille de UIView a changé, pas UIViewController.
Gastón Antonio Montes
@ GastónAntonioMontes merci pour cela! Vous avez peut-être remarqué une relation entre les UIViewinstances et les UIViewControllerinstances. Donc, si vous avez une UIViewinstance sans VC attaché, les autres réponses sont excellentes, mais si vous êtes attaché à un VC, c'est votre homme. Désolé, cela ne s'applique pas à votre cas.
Dan Rosenstark