Desde Xcode 8 y iOS10, las vistas no se clasifican correctamente en viewDidLayoutSubviews

Parece que con Xcode 8, en viewDidLoad , todas las subvenciones viewcontroller tienen el mismo tamaño de 1000×1000. Extraño, pero está bien, viewDidLoad nunca ha sido el mejor lugar para dimensionar correctamente las vistas.

¡Pero viewDidLayoutSubviews es!

Y en mi proyecto actual, bash imprimir el tamaño de un button:

 - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; NSLog(@"%@", self.myButton); } 

¡El logging muestra un tamaño de (1000×1000) para myButton! Entonces, si inicio session en un button haga clic, por ejemplo, el logging muestra un tamaño normal.

Estoy usando autolayout

¿Es un error?

Ahora, Interface Builder le permite al usuario cambiar dinámicamente el tamaño de todos los controlleres de vista en storyboard, para simular el tamaño de un determinado dispositivo.

Antes de esta funcionalidad, el usuario debe configurar manualmente el tamaño de cada controller de vista. Entonces, el controller de vista se guardó con un tamaño determinado, que se usó en initWithCoder para establecer el marco inicial.

Ahora, parece que initWithCoder no utiliza el tamaño definido en el guión gráfico y define un tamaño de pxx 1000×1 para la vista viewcontroller y todas sus subvistas.

Esto no es un problema, porque las vistas siempre deben usar cualquiera de estas soluciones de layout:

  • autolayout y todas las restricciones distribuirán correctamente tus vistas

  • autoresizingMask, que distribuirá cada vista que no tenga ninguna restricción asociada ( tenga en count que las restricciones de margen automático y de margen ahora son compatibles en la misma vista \ o /! )

Pero este es un problema para todos los elementos de layout relacionados con la capa de vista, como cornerRadius , ya que ni la máscara de autoajuste ni la máscara de autorrealización se aplican a las properties de la capa.

Para responder a este problema, la forma más común es usar viewDidLayoutSubviews si está en el controller o layoutSubview si está en una vista. En este punto (no olvides llamar a sus methods super relativos), ¡estás bastante seguro de que todo el layout se ha hecho!

¿Bastante seguro? Hum … no totalmente, he comentado, y es por eso que hice esta pregunta, en algunos casos, la vista todavía tiene su tamaño de 1000×1000 en este método. Creo que no hay respuesta a mi propia pregunta. Para dar la máxima información al respecto:

1- ¡Sucede solo cuando se exponen células! En las subclasss UITableViewCell y UICollectionViewCell , no se llamará a layoutSubview después de que las subvistas se distribuyan correctamente.

2- Como comentó @EugenDimboiu (por favor revierta su respuesta si es útil para usted), llamar a [myView layoutIfNeeded] en la subvista que no se ha distribuido lo distribuirá correctamente justo a time.

 - (void)layoutSubviews { [super layoutSubviews]; NSLog (self.myLabel); // 1000x1000 size [self.myLabel layoutIfNeeded]; NSLog (self.myLabel); // normal size } 

3- En mi opinión, este es definitivamente un error. Lo he enviado al radar (id 28562874).

PS: No soy inglesa, así que no dude en editar mi publicación si mi gramática se debe corregir;)

PS2: Si tiene alguna solución mejor, no dude en escribir otra respuesta. Moveré la respuesta aceptada

¿Estás usando esquinas networkingondeadas para tu button? Intenta llamar a layoutIfNeeded() antes.

Sé que esta no fue su pregunta exacta, pero me encontré con un problema similar en el que, como en la actualización, algunas de mis vistas estaban desorientadas a pesar de tener el tamaño de fotogtwig correcto en viewDidLayoutSubviews. De acuerdo con las notas de la versión de iOS 10:

"No se espera que el envío de layoutIfNeeded a una vista mueva la vista, pero en lanzamientos anteriores, si la vista tenía translateAutoresizingMaskIntoConstraints establecido en NO, y si estaba siendo posicionado por restricciones, layoutIfNeeded movería la vista para que coincida con el motor de layout antes de enviar el layout al subtree. Estos cambios corrigen este comportamiento, y la position del receptor y usualmente su tamaño no se verá afectada por layoutIfNeeded.

Es posible que algunos códigos existentes confíen en este comportamiento incorrecto que ahora se corrige. No hay cambios de comportamiento para los binarys vinculados antes de iOS 10, pero cuando se construye en iOS 10 es posible que necesite corregir algunas situaciones enviando -layoutIfNeeded a una vista de supervisión de la vista traduceAutoresizingMaskIntoConstraints que era el receptor anterior, o bien posicionarlo y dimensionarlo antes ( o después, según su comportamiento deseado) layoutIfNeeded.

Las aplicaciones de terceros con subclasss personalizadas de UIView que utilizan el layout automático que anulan el layout Las vistas y el layout sucio en sí mismos antes de llamar al super están en riesgo de desencadenar un ciclo de retroalimentación de layout cuando se reconstruyen en iOS 10. Cuando se envían correctamente las presentaciones subsiguientes, deje de ensuciar el layout en sí mismo en algún momento (tenga en count que esta llamada se saltó en la versión anterior a iOS 10). "

Esencialmente, no puede llamar a layoutIfNeeded en un object secundario de la Vista si está utilizando traduceAuthorizingMaskIntoConstraints, ahora llamar a layoutIfNeeded tiene que estar en la superView, y aún puede llamar a esto en viewDidLayoutSubviews.

Solución: envuelva todo dentro de viewDidLayoutSubviews en DispatchQueue.main.async .

 // swift 3 override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() DispatchQueue.main.async { // do stuff here } } 

Si los cuadros no son correctos en layoutSubViews (que no son), puede enviar un código asíncrono al hilo principal. Esto le da al sistema algo de time para hacer el layout. Cuando se ejecuta el bloque que envía, los cuadros tienen sus tamaños adecuados.

Esto solucionó el problema (ridículamente molesto) para mí:

 - (void) viewDidLayoutSubviews { [super viewDidLayoutSubviews]; self.view.frame = CGRectMake(0,0,[[UIScreen mainScreen] bounds].size.width,[[UIScreen mainScreen] bounds].size.height); } 

Editar / Nota: Esto es para un ViewController de pantalla completa.

En realidad, viewDidLayoutSubviews tampoco es el mejor lugar para establecer el marco de su vista. Hasta donde entendí, de ahora en adelante el único lugar donde debería hacerse es el método layoutSubviews en el código de la vista real. Ojalá no estuviera en lo cierto, ¡alguien me corrige por favor si no es verdad!

Ya he notificado este problema a apple, este problema existe desde hace mucho time, cuando inicializas UIViewController desde Xib, pero encontré una solución bastante buena. Además de eso, encontré ese problema en algunos casos cuando layoutIfNeeded en UICollectionView y UITableView cuando el origen de datos no está configurado en el momento inicial, y también lo necesitaba para swizzle.

 extension UIViewController { open override class func initialize() { if self !== UIViewController.self { return } DispatchQueue.once(token: "io.inspace.uiviewcontroller.swizzle") { ins_applyFixToViewFrameWhenLoadingFromNib() } } @objc func ins_setView(view: UIView!) { // View is loaded from xib file if nibBundle != nil && storyboard == nil && !view.frame.equalTo(UIScreen.main.bounds) { view.frame = UIScreen.main.bounds view.layoutIfNeeded() } ins_setView(view: view) } private class func ins_applyFixToViewFrameWhenLoadingFromNib() { UIViewController.swizzle(originalSelector: #selector(setter: UIViewController.view), with: #selector(UIViewController.ins_setView(view:))) UICollectionView.swizzle(originalSelector: #selector(UICollectionView.layoutSubviews), with: #selector(UICollectionView.ins_layoutSubviews)) UITableView.swizzle(originalSelector: #selector(UITableView.layoutSubviews), with: #selector(UITableView.ins_layoutSubviews)) } } extension UITableView { @objc fileprivate func ins_layoutSubviews() { if dataSource == nil { super.layoutSubviews() } else { ins_layoutSubviews() } } } extension UICollectionView { @objc fileprivate func ins_layoutSubviews() { if dataSource == nil { super.layoutSubviews() } else { ins_layoutSubviews() } } } 

Despacho una vez extensión:

 extension DispatchQueue { private static var _onceTracker = [String]() /** Executes a block of code, associated with a unique token, only once. The code is thread safe and will only execute the code once even in the presence of multithreaded calls. - parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID - parameter block: Block to execute once */ public class func once(token: String, block: (Void) -> Void) { objc_sync_enter(self); defer { objc_sync_exit(self) } if _onceTracker.contains(token) { return } _onceTracker.append(token) block() } } 

Extensión Swizzle:

 extension NSObject { @discardableResult class func swizzle(originalSelector: Selector, with selector: Selector) -> Bool { var originalMethod: Method? var swizzledMethod: Method? originalMethod = class_getInstanceMethod(self, originalSelector) swizzledMethod = class_getInstanceMethod(self, selector) if originalMethod != nil && swizzledMethod != nil { method_exchangeImplementations(originalMethod!, swizzledMethod!) return true } return false } } 

Mi problema se resolvió cambiando el uso de

 -(void)viewDidLayoutSubviews{ [super viewDidLayoutSubviews]; self.viewLoginMailTop.constant = -self.viewLoginMail.bounds.size.height; } 

a

 -(void)viewWillLayoutSubviews{ [super viewWillLayoutSubviews]; self.viewLoginMailTop.constant = -self.viewLoginMail.bounds.size.height; } 

Entonces, de Did to Will

Súper extraño

Mejor solución para mí.

 protocol LayoutComplementProtocol { func didLayoutSubviews(with targetView_: UIView) } private class LayoutCaptureView: UIView { var targetView: UIView! var layoutComplements: [LayoutComplementProtocol] = [] override func layoutSubviews() { super.layoutSubviews() for layoutComplement in self.layoutComplements { layoutComplement.didLayoutSubviews(with: self.targetView) } } } extension UIView { func add(layoutComplement layoutComplement_: LayoutComplementProtocol) { func findLayoutCapture() -> LayoutCaptureView { for subView in self.subviews { if subView is LayoutCaptureView { return subView as? LayoutCaptureView } } let layoutCapture = LayoutCaptureView(frame: CGRect(x: -100, y: -100, width: 10, height: 10)) // not want to show, want to have size layoutCapture.targetView = self self.addSubview(layoutCapture) return layoutCapture } let layoutCapture = findLayoutCapture() layoutCapture.layoutComplements.append(layoutComplement_) } } 

Utilizando

 class CircleShapeComplement: LayoutComplementProtocol { func didLayoutSubviews(with targetView_: UIView) { targetView_.layer.cornerRadius = targetView_.frame.size.height / 2 } } myButton.add(layoutComplement: CircleShapeComplement()) 

Según la nueva actualización en ios, esto es en realidad un error, pero podemos networkingucir esto usando:

Si está utilizando xib con autolayout en su proyecto, entonces tiene que actualizar el marco en la configuration de autolayout, por favor busque la image para esto. introduzca la descripción de la imagen aquí

Reemplazar layoutSublayers (de capa: CALayer) en lugar de layoutSubviews en subvista de celda para tener frameworks correctos