ios - example - uisearchbar swift 4
Desde Xcode 8 y iOS10, las vistas no tienen el tamaƱo adecuado en viewDidLayoutSubviews (12)
Parece que con Xcode 8, en
viewDidLoad
, todas las subvistas de viewcontroller tienen el mismo tamaño de 1000x1000.
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, intento imprimir el tamaño de un botón:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSLog(@"%@", self.myButton);
}
¡El registro muestra un tamaño de (1000x1000) para myButton! Luego, si inicio sesión haciendo clic en un botón, por ejemplo, el registro muestra un tamaño normal.
Estoy usando autolayout.
¿Es un error?
¿Estás usando esquinas redondeadas para tu botón?
Intente llamar a
layoutIfNeeded()
antes.
Ahora, Interface Builder 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 configurar manualmente cada tamaño de controlador de vista.
Entonces, el controlador de vista se guardó con un cierto tamaño, que se utilizó en
initWithCoder
para establecer el marco inicial.
Ahora, parece que
initWithCoder
no usa el tamaño definido en el guión gráfico y define un tamaño de 1000x1000 px para la vista del controlador de vista 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 diseñarán correctamente sus vistas
-
autoresizingMask, que diseñará cada vista que no tenga ninguna restricción adjunta ( tenga en cuenta que las restricciones de margen automático y la reproducción automática ahora son compatibles en la misma vista / o /! )
Pero este
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
autoajuste
ni la
máscara de autorización se
aplican 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 hecho!
¿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 de 1000x1000 en este método. Creo que no hay respuesta a mi propia pregunta. Para dar la máxima información al respecto:
1- ¡ocurre solo cuando se colocan células!
En las subclases
UITableViewCell
y
UICollectionViewCell
,
layoutSubview
no se llamará
después de que las
subvistas se distribuyan correctamente.
2- Como comentó @EugenDimboiu (por favor, vote su respuesta si es útil para usted), llamando a
[myView layoutIfNeeded]
en la subvista no distribuida lo 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, esto definitivamente es un error. Lo envié al radar (id 28562874).
PD: no soy nativo de inglés, así que siéntete libre de editar mi publicación si mi gramática debe corregirse;)
PS2: Si tienes alguna solución mejor, no dudes en escribir otra respuesta. Moveré la respuesta aceptada.
Anule layoutSublayers (de capa: CALayer) en lugar de layoutSubviews en la subvista de celda para tener los marcos correctos
En realidad,
viewDidLayoutSubviews
tampoco es el mejor lugar para establecer el marco de su vista.
Según tengo entendido, de ahora en adelante, el único lugar en el que debería hacerse es el método
layoutSubviews
en el código de la vista real.
Desearía no estar en lo cierto, ¡alguien me corrija por favor si no es verdad!
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.
Mejor solución para mi.
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())
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
Super raro
Sé que esta no era su pregunta exacta, pero me encontré con un problema similar en el que, como en la actualización, algunas de mis vistas estaban en mal estado a pesar de tener el tamaño de marco correcto en viewDidLayoutSubviews. De acuerdo con las notas de lanzamiento de iOS 10:
"No se espera que el envío de layoutIfNeeded a una vista mueva la vista, pero en versiones anteriores, si la vista traduceAutoresizingMaskIntoConstraints establecida 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 layoutIfNeeded.
Algunos códigos existentes pueden depender de este comportamiento incorrecto que ahora se corrige. No hay cambio de comportamiento para los archivos binarios vinculados antes de iOS 10, pero al compilar en iOS 10 es posible que deba corregir algunas situaciones enviando -layoutIfNeeded a una vista general de la vista traductoresAutoresizingMaskIntoConstraints que era el receptor anterior, o bien posicionándolo y dimensionándolo antes ( o después, dependiendo de su comportamiento deseado) layoutIfNeeded.
Las aplicaciones de terceros con subclases de UIView personalizadas que utilizan diseño automático que anulan las subvistas de diseño y el diseño sucio en sí mismo antes de llamar a Super están en riesgo de desencadenar un bucle de retroalimentación de diseño cuando se reconstruyen en iOS 10. Cuando se envían correctamente las llamadas de diseño y subvista posteriores, deben asegurarse de deje de ensuciar el diseño en sí mismo en algún momento (tenga en cuenta que esta llamada se omitió en la versión anterior a iOS 10) ".
Esencialmente, no puede llamar a layoutIfNeeded en un objeto secundario de la Vista si está utilizando translatesAutoresizingMaskIntoConstraints; ahora, llamar a layoutIfNeeded tiene que estar en el superView, y aún puede llamar a esto en viewDidLayoutSubviews.
Si los marcos no son correctos en layoutSubViews (que no lo son), puede enviar un código asíncrono en el hilo principal. Esto le da al sistema algo de tiempo para hacer el diseño. Cuando se ejecuta el bloque que despacha, los marcos tienen sus tamaños adecuados.
Ya informé este problema a Apple, este problema existe desde hace mucho tiempo, cuando está 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 necesito mezclarlo.
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
}
}
Solución:
Envuelva todo dentro de
viewDidLayoutSubviews
en
DispatchQueue.main.async
.
// swift 3
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
DispatchQueue.main.async {
// do stuff here
}
}