xcode 6 IB_DESIGNABLE – non carica le risorse dal bundle in Interface builder

Sto cercando di creare un controllo personalizzato che si aggiorna dal vivo in Interface Builder utilizzando la nuova opzione IB_DESIGNABLE descritta qui .

- (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); CGRect myFrame = self.bounds; CGContextSetLineWidth(context, 10); CGRectInset(myFrame, 5,5); [[UIColor redColor] set]; UIRectFrame(myFrame); NSBundle *bundle = [NSBundle mainBundle]; NSString *plistPath; plistPath = [bundle pathForResource:@"fileExample" ofType:@"plist"]; UIImage *tsliderOff = [UIImage imageNamed:@"btn_slider_off.png"]; [tsliderOff drawInRect:self.bounds]; } 

Quando corro nel simulatore, ottengo una scatola rossa con la mia immagine al centro (come previsto): inserisci la descrizione dell'immagine qui

Ma quando provo a utilizzare l’Interface Builder, viene visualizzato solo come una casella rossa (nessuna immagine nel mezzo): Reso in Interface Builder

Quando eseguo il debug: Editor-> Debug Selected Views, rivela che qualsiasi cosa caricata dal bundle è nulla. Sia plistPath che tsliderOff appaiono come nulli.

Mi sono assicurato che btn_slider_off.png fosse incluso in Targets-> myFrameWork-> Build Phases-> Copy Bundle Resources.

Qualche idea sul perché Interface Builder non veda il file png durante la modifica, ma mostra ok durante l’esecuzione? “Creare una vista personalizzata che genera in Interface Builder” è un po ‘limitata se non riesco a caricare immagini per renderizzare …


modifica basata su soluzione di rickster

rickster mi ha indicato la soluzione – il problema è che i file non vivono in mainBundle, vivono in [NSBundle bundleForClass: [self class]]; E sembra che [UIImage imageNamed: @ “btn_slider_off.png”] utilizzi automaticamente mainBundle.

Il seguente codice funziona!

 #if !TARGET_INTERFACE_BUILDER NSBundle *bundle = [NSBundle mainBundle]; #else NSBundle *bundle = [NSBundle bundleForClass:[self class]]; #endif NSString *fileName = [bundle pathForResource:@"btn_slider_off" ofType:@"png"]; UIImage *image = [UIImage imageWithContentsOfFile:fileName]; [image drawInRect:self.bounds]; 

A partire da quando questa domanda è stata posta per la prima volta, la creazione di un controllo designato IB ha richiesto di confezionarla in un objective quadro. Non devi più farlo – la spedizione Xcode 6.0 (e successive) mostrerà in anteprima anche i controlli configurabili IB dal tuo objective dell’app. Tuttavia, il problema e la soluzione sono gli stessi.

Perché? [NSBundle mainBundle] restituisce il pacchetto principale dell’app attualmente in esecuzione. Quando lo chiami da un framework, ricevi un pacchetto diverso restituito in base all’app che sta caricando il tuo framework. Quando esegui la tua app, la tua app carica il framework. Quando si utilizza il controllo in IB, un’app speciale Xcode helper carica il framework. Anche se il tuo controllo IB-design è nel tuo target dell’app, Xcode sta creando un’app helper speciale per eseguire il controllo all’interno di IB.

La soluzione? Chiama invece +[NSBundle bundleForClass:] (o NSBundle(forClass:) in Swift). Questo ti porta il pacchetto contenente il codice eseguibile per qualsiasi class tu specifichi. (Puoi usare [self class] / self.dynamicType lì, ma attenzione che il risultato cambierà per sottoclassi definite in diversi pacchetti.)

Se stai usando l’approccio framework – che può essere utile per alcune app anche se non è più necessario per i controlli designabili IB – è meglio mettere le risorse di immagine nello stesso framework con il codice che le usa. Se il tuo codice framework si aspetta di utilizzare le risorse fornite in fase di esecuzione da qualunque applicazione carichi il framework, la cosa migliore da fare per renderlo IB-designable è quello di simularlo. Implementa il metodo prepareForInterfaceBuilder nel tuo controllo e fai caricare le risorse da una posizione nota (come il framework framework o un percorso statico nell’area di lavoro Xcode).

Mi sono imbattuto in un problema simile in Swift. A partire da Xcode 6 beta 3, non è necessario utilizzare un framework per ottenere il rendering dal vivo. Tuttavia, devi ancora affrontare il problema del bundle per Live View in modo che Xcode sappia dove trovare le risorse. Supponendo che “btn_slider_off” sia un’immagine impostata in Images.xcassets, puoi farlo in Swift per il rendering live e funzionerà anche quando l’app viene eseguita normalmente.

 let name = "btn_slider_off" let myBundle = NSBundle(forClass: self.dynamicType) // if you want to specify the class name you can do that instead // assuming the class is named CustomView the code would be // let myBundle = NSBundle(forClass: CustomView.self) let image = UIImage(named: name, inBundle: myBundle, compatibleWithTraitCollection: self.traitCollection) if let image = image { image.drawInRect(self.bounds) } 

Avviso sulla risoluzione del pacchetto / percorso sopra in IB_DESIGNABLE:

XCode non risolverà una risorsa se la chiamata è contenuta nell’inizializzazione di UIView. Ho potuto solo ottenere XCode per risolvere un percorso mentre in drawRect:

Ho trovato una soluzione facile / facile alle tecniche di risoluzione del bundle di cui sopra, basta semplicemente designare una proprietà IBInspectable per essere una UIImage , quindi specificare l’immagine in Interface Builder. Ex:

 @IBInspectable var mainImage: UIImage? @IBInspectable var sideImage: UIImage? 

L’ho risolto usando la seguente riga di codice:

 let image = UIImage(named: "image_name", inBundle: NSBundle(forClass: self.dynamicType), compatibleWithTraitCollection: nil) 

Dopo averlo implementato in questo modo, le immagini vengono visualizzate correttamente in Interface Builder

IN SWIFT 3.0 il codice di @rickster è cambiato in:

 // DYNAMIC BUNDLE DEFINITION FOR DESIGNABLE CLASS let theBundle : Bundle = Bundle(for: type(of: self)) // OR ALTERNATEVLY BY PROVDING THE CONCRETE NAME OF DESIGNABLE CLASS let theBundle : Bundle = Bundle(for: RH_DesignableView.self) // AND THEN SUCCESSFULLY YOU CAN LOAD THE RESSOURCE let theImage : UIImage? = UIImage(named: "Logo", in: theBundle, compatibleWith: nil) 

Ho anche avuto lo stesso problema: guardando questa risposta e seguendo WWDC 2014 che cosa è nuovo in Interface Builder . L’ho risolto così:

 - (void)prepareForInterfaceBuilder{ NSArray *array = [[NSProcessInfo processInfo].environment[@"IB_PROJECT_SOURCE_DIRECTORIES"] componentsSeparatedByString:@":"]; if (array.count > 0) { NSString *str = array[0]; NSString *newStr = [str stringByAppendingPathComponent:@"/MyImage.jpg"]; image = [UIImage imageWithContentsOfFile:newStr]; } }