Desde Xcode 8 e iOS10, las vistas no se dimensionan correctamente en viewDidLayoutSubviews

Parece que con Xcode 8, en viewDidLoad , todas las subvistas de viewcontroller tienen el mismo tamaño de 1000×1000. Es 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 botón:

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

¡El registro muestra un tamaño de (1000×1000) para myButton! Luego, si inicio sesión en un botón, haga clic, por ejemplo, el registro 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 cada controlador de vista en el guión gráfico, para simular el tamaño de un determinado dispositivo.

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

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

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

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

  • autoresizingMask, que distribuirá cada vista a la que no se haya asociado ninguna restricción ( note que las restricciones de autolayout y de margen ahora son compatibles en la misma vista \ o /! )

Pero esto es un problema para todos los elementos de diseño relacionados con la capa de vista, como cornerRadius , ya que ni la máscara de autoimpresión ni la de aumento de tamaño se aplica a las propiedades de la capa.

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

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

1- ¡Esto solo ocurre cuando se establecen celdas! En las subclases UITableViewCell y UICollectionViewCell , layoutSubview no se invocará después de que las subvistas se desplieguen correctamente.

2- Como @EugenDimboiu comentó (por favor, recomendó su respuesta si es útil para usted), al llamar a [myView layoutIfNeeded] en la subvista no presentada, se distribuirá correctamente justo a tiempo.

 - (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).

PD: No soy nativo del inglés, así que siéntete libre de editar mi publicación si mi gramática debe ser corregida;)

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

¿Estás usando esquinas redondeadas para tu botón? Intenta llamar a layoutIfNeeded() antes.

Sé que esta no fue su pregunta exacta, pero me encontré con un problema similar cuando en la actualización algunos de mis puntos de vista fueron desordenados 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 layout IfNeeded a una vista mueva la vista, pero en versiones anteriores, si la vista traduceAutoresizingMaskIntoConstraints establecido en NO, y si estaba siendo posicionada por restricciones, layoutIfNeeded movería la vista para que coincida con el motor de diseño antes de enviar el diseño al subárbol. Estos cambios corrigen este comportamiento, y la posición del receptor y, por lo general, su tamaño no se verán afectados por el diseño IfNeeded.

Algunos códigos existentes pueden estar confiando en este comportamiento incorrecto que ahora se corrige. No hay ningún cambio de comportamiento para los binarios vinculados antes de iOS 10, pero cuando se construye en iOS 10 puede necesitar corregir algunas situaciones enviando -layoutIfNeeded a una vista superior de la vista tralationsAutoresizingMaskIntoConstraints que era el receptor anterior, o posicionándolo y clasificándolo antes ( o después, dependiendo de su comportamiento deseado) layoutSfNeeded.

Las aplicaciones de terceros con subclases UIView personalizadas que usan Diseño automático que anulan las presentaciones de diseño y el diseño sucio en sí mismas antes de llamar al súper corren el riesgo de desencadenar un ciclo de retroalimentación de diseño cuando se reconstruyen en iOS 10. Cuando se envían correctamente distribuciones subsiguientes Llamadas deben asegurarse deje de ensuciar el diseño en uno mismo en algún momento (tenga en cuenta que esta llamada se saltó en la versión anterior a iOS 10). ”

Básicamente, no se puede invocar layout IfNeeded en un objeto secundario de la Vista si se está utilizando tralatesAutoresizingMaskIntoConstraints. Ahora llamar a layoutIfNeeded tiene que estar en la supervista y todavía se puede llamar a 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 } } 

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 una pantalla completa ViewController.

Si los marcos no son correctos en layoutSubViews (que no lo son), puede enviar un código asincrónico en el hilo principal. Esto le da al sistema un tiempo para hacer el diseño. Cuando se ejecuta el bloque que despacha, los marcos tienen sus tamaños adecuados.

En realidad, viewDidLayoutSubviews tampoco es el mejor lugar para establecer el marco de tu vista. Por lo que yo entiendo, 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 bien, alguien me corrija por favor si no es verdad!

Ya informé de este problema a Apple, este problema existe desde hace mucho tiempo, cuando estás inicializando 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 es necesario para activarlo.

 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. 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 a Will

Súper raro

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()) 

Anular layoutSublayers (de capa: CALayer) en lugar de layoutSubviews en subvista de celda para tener marcos correctos

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

Si está utilizando xib con autolayout en su proyecto, entonces solo tiene que actualizar el marco en la configuración de autolayout, encuentre la imagen para esto. enter image description here