web-dev-qa-db-ja.com

xcode 6IB_DESIGNABLE-InterfaceBuilderのバンドルからリソースをロードしていません

here で説明されている新しいIB_DESIGNABLEオプションを使用して、InterfaceBuilderでライブで更新するカスタムコントロールを作成しようとしています。

- (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];
}

シミュレーターで実行すると、画像が中央にある赤いボックスが表示されます(予想どおり)。 enter image description here

しかし、Interface Builderを使おうとすると、赤いボックスとしてのみ表示されます(中央に画像は表示されません)。 Rendered in Interface Builder

デバッグすると、[エディター]-> [選択したビューのデバッグ]が表示され、バンドルから読み込まれたものはすべてnilであることがわかります。 plistPathとtsliderOffはどちらもnilとして表示されます。

Btn_slider_off.pngが[ターゲット]-> [myFrameWork]-> [ビルドフェーズ]-> [バンドルリソースのコピー]に含まれていることを確認しました。

Interface Builderが編集中にpngファイルを表示しないのに、実行時に問題がない理由はありますか?レンダリングする画像を読み込めない場合、「InterfaceBuilderでレンダリングするカスタムビューの作成」は少し制限されます...


ricksterによるソリューションに基づいて編集

ricksterは私に解決策を指摘しました-問題は、ファイルがmainBundleに存在せず、[NSBundle bundleForClass:[selfclass]]に存在することです。そして、[UIImage imageNamed:@ "btn_slider_off.png"]は自動的にmainBundleを使用しているようです。

次のコードは機能します!

#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];
28
arinmorf

この質問が最初に行われた時点で、IBで設計可能なコントロールを作成するには、フレームワークターゲットにパッケージ化する必要がありました。もうそれを行う必要はありません—出荷されたXcode 6.0(およびそれ以降)は、アプリターゲットからIBで設計可能なコントロールもプレビューします。ただし、問題と解決策は同じです。

どうして? _[NSBundle mainBundle]_は、現在実行中のアプリのプライマリバンドルを返します。フレームワークからそれを呼び出すと、フレームワークをロードしているアプリに基づいて異なるバンドルが返されます。アプリを実行すると、アプリはフレームワークをロードします。 IBでコントロールを使用すると、特別なXcodeヘルパーアプリがフレームワークをロードします。 IBで設計可能なコントロールがアプリのターゲットにある場合でも、Xcodeは、IB内でコントロールを実行するための特別なヘルパーアプリを作成しています。

ソリューション?代わりに_+[NSBundle bundleForClass:]_(またはSwiftではNSBundle(forClass:))を呼び出します。これにより、指定したクラスの実行可能コードを含むバンドルが取得されます。 (そこでは_[self class]_/_self.dynamicType_を使用できますが、異なるバンドルで定義されたサブクラスでは結果が変わることに注意してください。)

フレームワークアプローチを使用している場合(IBで設計可能なコントロールでは不要になった場合でも、一部のアプリでは役立つ可能性があります)、画像リソースを、それらを使用するコードと同じフレームワークに配置することをお勧めします。フレームワークコードが、フレームワークをロードするアプリによって実行時に提供されるリソースを使用することを想定している場合、それをIB設計可能にするための最善の方法は、それを偽造することです。コントロールにprepareForInterfaceBuilderメソッドを実装し、既知の場所(フレームワークバンドルやXcodeワークスペースの静的パスなど)からリソースをロードするようにします。

48
rickster

私はSwiftで同様の問題に遭遇しました。 Xcode 6ベータ3以降、ライブレンダリングを取得するためにフレームワークを使用する必要はありません。ただし、Xcodeがアセットの場所を認識できるように、ライブビューのバンドルの問題に対処する必要があります。 「btn_slider_off」がImages.xcassetsで設定された画像であるとすると、ライブレンダリングの場合はSwiftでこれを行うことができ、アプリが正常に実行されている場合にも機能します。

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)
}
13
kasplat

次のコード行を使用して修正しました。

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

このように実装した後、画像はInterfaceBuilderに正しく表示されます

3
Antoine

IB_DESIGNABLEでの上記のバンドル/パス解決に関する警告:

UIViewの初期化に含まれる呼び出しの場合、XCodeはリソースを解決しません。 drawRectにいる間だけXCodeにパスを解決させることができました:

上記のバンドル解決手法の優れた/簡単な回避策を見つけました。IBInspectableプロパティをUIImageとして指定し、InterfaceBuilderで画像を指定するだけです。例:

@IBInspectable var mainImage: UIImage?
@IBInspectable var sideImage: UIImage?
3
Taylor Halliday

IN Swift 3.0@ricksterのコードは次のように変更されました。

// 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)
1
LukeSideWalker

私も同じ問題を抱えていました: この答えを見て そして次の WWDC 2014 Interface Builderの新機能 。私はそれをそのように解決しました:

- (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]; 
     }
}
0
Mike.R