¿Hay un evento de cambio de tamaño de UIView?

102

Tengo una vista que tiene filas y columnas de vistas de imágenes.

Si se cambia el tamaño de esta vista, necesito reorganizar las posiciones de las vistas de imágenes.

Esta vista es una subvista de otra vista cuyo tamaño cambia.

¿Hay alguna forma de detectar cuándo se está cambiando el tamaño de esta vista?

vive el amor
fuente
¿Cuándo se cambia el tamaño de la vista? ¿Cuándo gira el dispositivo o cuando el usuario lo gira usando multitáctil?
Evan Mulawski
Esta vista cambia de tamaño cuando el usuario toca un botón en otra vista (vista maestra).
live-love

Respuestas:

98

Como comentó Uli a continuación, la forma correcta de hacerlo es anular layoutSubviewsy diseñar los imageViews allí.

Si, por alguna razón, no puede subclasificar y anular layoutSubviews, la observación boundsdebería funcionar, incluso cuando esté algo sucio. Peor aún, existe un riesgo al observar: Apple no garantiza que KVO funcione en las clases de UIKit. Lea la discusión con el ingeniero de Apple aquí: ¿ Cuándo se libera un objeto asociado?

respuesta original:

Puede utilizar la observación de pares clave-valor:

[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
fuente
2
Realmente, diseñar subvistas es exactamente para lo que es layoutSubviews, por lo que observar los cambios de tamaño, aunque funcional, es un mal diseño de la OMI.
uliwitness
@ulitestigo bueno, siguen cambiando estas cosas. De todos modos, ahora debería usar, viewWillTransitionetc., etc.
Dan Rosenstark
viewWillTransition de para UIViewControllers, no UIView.
Armand
¿ layoutSubviewsSigue siendo un método recomendado si, según el tamaño actual de la vista, se deben agregar / eliminar diferentes subvistas y se deben agregar / eliminar diferentes restricciones?
Chris Prince
1
Bueno, creo que acabo de responder esto para mi caso. Cuando hago la configuración (dependiendo del tamaño) que necesito en una anulación de límites, funciona. Cuando lo hago en layoutSubviews, no es así.
Chris Prince
93

En una UIViewsubclase, se pueden utilizar observadores de propiedades :

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

Sin subclases, clave-valor observación con clave caminos inteligentes hará:

var boundsObservation: NSKeyValueObservation?

func beginObservingBounds() {
    boundsObservation = observe(\.bounds) { capturedSelf, _ in
        // ...
    }
}
Rudolf Adamkovič
fuente
2
@Ali ¿Por qué crees eso?
Rudolf Adamkovič
en los cambios del marco de carga, pero parece que los límites no (sé que es extraño y tal vez estoy haciendo algo mal)
Ali
3
@Ali: como se ha mencionado un par de veces aquí: framees una propiedad derivada y calculada en tiempo de ejecución. no anule esto, a menos que tenga una razón muy inteligente y consciente para hacerlo. de lo contrario: use boundso (incluso mejor) layoutSubviews.
auco
3
Una forma genial de crear un círculo. ¡Gracias! override var bounds: CGRect { didSet { layer.cornerRadius = bounds.size.width / 2 }}
SimplGy
4
No se garantiza que la detección boundso el framecambio funcionen, dependiendo de dónde coloque su vista en la jerarquía de vistas. En su layoutSubviewslugar, anularía . Vea esta y esta respuesta.
HuaTham
31

Cree una subclase de UIView y anule el diseño

gwang
fuente
11
El problema con esto es que las subvistas no solo pueden cambiar su tamaño, sino que también pueden animar ese cambio de tamaño. Cuando UIView ejecuta la animación, no llama a layoutSubviews cada vez.
David Jeske
1
@DavidJeske UIViewAnimationOptions tiene una opción llamada UIViewAnimationOptionLayoutSubviews. Creo que esto quizás ayude. :)
Neal.Marlin
8

Swift 4 keypath KVO: así es como detecto la rotación automática y el movimiento al panel lateral del iPad. Debería funcionar trabajar cualquier vista. Tuve que observar la capa de 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
fuente
Esta solución funcionó para mí. Soy nuevo en Swift y no estoy seguro de la sintaxis \ .bounds que usó aquí. ¿Qué significa eso exactamente?
WBuck
aquí hay más discusión: github.com/ole/whats-new-in-swift-4/blob/master/…
Warren Stringer
.layer¡Hizo el truco! ¿Sabes por qué view.observeno funciona el uso?
d4Rk
@ d4Rk - No lo sé. Supongo que los límites de capa son menos ambiguos que los marcos de UIView. Por ejemplo, contentOffset de UITableView afectará las coordenadas finales de sus subViews. No estoy seguro.
Warren Stringer
ESTA ES UNA GRAN RESPUESTA PARA MÍ. Mi caso es que estoy usando AutoLayout para diseñar mis vistas. PERO UICollectionViewFlowLayout te obliga a dar un itemSize explícito. pero cuando cambia el tamaño de su collectionView (como en mi caso cuando muestro una barra de herramientas con AutoLayout), el itemSize sigue siendo el mismo y arroja = [el observador es el enfoque más limpio que obtuve hasta ahora. y el mejor !!
Yitzchak
7

Puede crear una subclase de UIView y anular la

setFrame: marco (CGRect)

método. Este es el método llamado cuando se cambia el marco (es decir, el tamaño) de la vista. Haz algo como esto:

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

  // Do your custom code here.
}
rebotar
fuente
Sensible y funcional. Buena llamada.
El Zorko
1
Para su información, esto no funciona para la subclase UIImageView. Más información aquí: stackoverflow.com/questions/19216684/…
William Entriken
Además, setFrame:no se llamó a mi UITextViewsubclase durante el cambio de tamaño causado por la autorrotación mientras que sí layoutSubviews:. Nota: estoy usando el diseño automático y iOS 7.0.
ma11hew28
3
nunca anule setFrame:. framees una propiedad derivada. Vea mi respuesta
Adlai Holler
7

Bastante viejo, pero sigue siendo una buena pregunta. En el código de muestra de Apple, y en algunas de sus subclases privadas de UIView, anulan setBounds más o 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];
}

Anular setFrame:NO es una buena idea. framese deriva de center, boundsy transform, por lo iOS no necesariamente llamar setFrame:.

Adlai Holler
fuente
2
Esto es cierto (en teoría). Sin embargo, setBounds:tampoco se llama al configurar la propiedad del marco (al menos en iOS 7.1). Esta podría ser una optimización que Apple agregó para evitar el mensaje adicional.
nschum
1
… Y mal diseño por parte de Apple para no llamar a sus propios accesadores.
osxdirk
1
De hecho, ambos frame y boundsse derivan del subyacente de la vista CALayer; simplemente llaman al captador de la capa. Y setFrame:establece el marco de la capa, mientras setBounds:establece los límites de la capa. Por lo tanto, no puede simplemente anular uno u otro. Además, layoutSubviewsse llama en exceso (no solo por cambios de geometría), por lo que tampoco siempre es una buena opción. Todavía
estoy
-3

Si está en una instancia de UIViewController, anular viewDidLayoutSubviewsfunciona.

override func viewDidLayoutSubviews() {
    // update subviews
}
Dan Rosenstark
fuente
Se necesita un cambio de tamaño de UIView, no de UIViewController.
Gastón Antonio Montes
@ GastónAntonioMontes ¡gracias por eso! Es posible que haya notado una relación entre UIViewinstancias e UIViewControllerinstancias. Entonces, si tiene una UIViewinstancia sin VC adjunta, las otras respuestas son excelentes, pero si está adjunto a un VC, este es su hombre. Lo siento, no se aplica a su caso.
Dan Rosenstark