This article is also available in English: iOS Dev: how to solve 'Failed to render and update autolayout status' when using @IBDesignable
Si dice che un'immagine vale più di mille parole.
Questo dev'essere ciò che Apple deve aver pensato quando ha introdotto le proprietà @IBDesignable e @IBInspectable in XCode 6, durante il WWDC2014.
Queste proprietà permettono di visualizzare e interagire direttamente nella storyboard con viste e controlli personalizzati.
"It just works!"... oppure no?
I problemi arrivano quando si vuole personalizzare una sottoclasse di UIView in un file Xib esterno e usarla nella storyboard principale: in questo caso, XCode non solo non è in grado di visualizzare la preview della nostra personalizzazione, ma addirittura ha problemi a visualizzare l'intera storyboard.
Tuttavia, dal momento che l'app nel simulatore continua a funzionare come dovrebbe, archiviamo il problema come una delle tante stranezze delle storyboard di XCode, e procediamo con altri aspetti dello sviluppo.
Ma perché questo accade?
Questo accade perché il processo di renderizzazione della preview ha smesso di funzionare, e ce lo fa sapere con messaggi di questo tipo:
- Failed to render and update auto layout status for X: The agent threw an exception
- Failed to render and update auto layout status for X: The agent crashed
dove X è una view o un view controller nella cui gerarchia c'è la nostra vista (o controllo) @IBDesignable.
E perché questo è un problema? Per tenere le cose semplici e concise, diciamo semplicemente che XCode renderizza le preview della storyboard tramite una app differente, con un bundle differente...
Come si definisce una UIView personalizzata?
Solitamente, in Swift 4 e XCode 9, abbiamo un due file, es. TestView.xib e il suo owner TestView.swift, con una definizione più o meno così:
import UIKit @IBDesignable class TestView: UIView { @IBOutlet var theView: UIView! // La UIView principale dello Xib @IBInspectable var someString: String? override init(frame: CGRect) { super.init(frame: frame) commonInit() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) commonInit() } private func commonInit() { Bundle.main.loadNibNamed(String(describing: TestView.self), owner: self, options: nil) addSubview(testView) } }
Come risolvere l'errore?
Quel Bunde.main dovrebbe avervi fatto suonare una campanella...
Dal momento che la renderizzazione avviene tramite un'app diversa... il Bundle.main sarà relativo a quell'app, per tanto la deserializzazione dello Xib non andrà a buon fine.
Per risolvere l'errore è quindi sufficiente sostituire tale chiamata con:
let bundle = Bundle(for: TestView.self) bundle.loadNibNamed(String(describing: TestView.self), owner: self, options: nil)
e XCode riprenderà a visualizzare le preview per le nostre @IBDesignable, come atteso.
Hai risolto il problema?
Hai qualcosa da aggiungere? Il tuo team di sviluppo ha bisogno di supporto professionale e personalizzato per la realizzazione di app native e ibride per iOS e Android?