{"id":2931,"date":"2013-11-25T15:39:36","date_gmt":"2013-11-25T14:39:36","guid":{"rendered":"http:\/\/t-machine.org\/?p=2931"},"modified":"2013-12-24T15:42:34","modified_gmt":"2013-12-24T14:42:34","slug":"how-to-load-images-from-allany-bundles-in-xcode5","status":"publish","type":"post","link":"http:\/\/new.t-machine.org\/index.php\/2013\/11\/25\/how-to-load-images-from-allany-bundles-in-xcode5\/","title":{"rendered":"How to keep code and images in multiple bundles in Xcode5"},"content":{"rendered":"<p>Apple invented &#8220;bundles&#8221; and &#8220;frameworks&#8221; to greatly improve the ease of development and maintenance of OS X projects. They are really good.<\/p>\n<p>But Apple modified Xcode to try and stop people using these on iOS. I don&#8217;t know why.<\/p>\n<p>Today I delved into this to find out the correct, working, simplest\/easiest solution&#8230;<br \/>\n<!--more--><\/p>\n<h2>What do we want? RE-USE! NO BUGS!<\/h2>\n<p>(and an Easy Life)<\/p>\n<p>We want:<\/p>\n<ul>\n<li>Re-usable code goes in one Target (Apple&#8217;s nomenclature)\n<li>Re-usable assets goes in one Target (ideally the same as above!)\n<li>App targets embed the above targets, and add whatever custom code + assets they need\n<li><em>Everything is updated and maintained automatically<\/em>\n<\/ul>\n<p>The brief solution:<\/p>\n<ol>\n<li>Create a Static Library target and move all your code there\n<li>Create a Bundle and move all your assets there\n<li>&#8230;find that the above Doesn&#8217;t Work becuase of Xcode5 bugs&#8230;\n<li>&#8230;scroll to the end of this page, where there&#8217;s a step-by-step bullet point list of fixes. Become happy again :)\n<\/ol>\n<h2>Static Library bundles are broken in Xcode5<\/h2>\n<p>You&#8217;re a Good Developer, and you follow Apple&#8217;s design and guides. You put your code, images and other re-usable assets into a Static Library Bundle.<\/p>\n<p><strong>Everything fails<\/strong>. It turns out that someone made Xcode5 corrupt iOS apps (iOS only! OS X apps are left alone) if they contain Bundles. There are multiple bugs outstanding against Xcode 4 and Xcode 5 here; the worst of them &#8220;accidentally&#8221; caused Xcode4 to stop submitting apps to the App Store &#8230; but thankfully that appears mostly fixed now (I didn&#8217;t get it with Xcode5).<\/p>\n<p>It&#8217;s important to note that <strong>we are not hacking Xcode here; everything we do is correct and legal<\/strong>, we&#8217;re just working around bugs in Xcode&#8217;s GUI, or its default settings, or its build commands, etc.<\/p>\n<blockquote><p>\nAs far as I can guess &#8230; it&#8217;s all an accidental side-effect of disabling Frameworks in iOS. (Which, of course, is another head-scratcher. <a href=\"https:\/\/github.com\/kstenerud\/iOS-Universal-Framework\">iOS developers have already proven there is no reason to not have Frameworks<\/a>, and in general Apple <em>wants you<\/em> to have Frameworks. But for some reason &#8211; maybe a good one, we don&#8217;t know because Apple hasn&#8217;t divulged it &#8211; the Xcode team broke this stuff years ago and hasn&#8217;t got around to fixing it.\n<\/p><\/blockquote>\n<p>Some googling reveals that you can workaround this by having TWO bundles:<\/p>\n<ol>\n<li>Bundle1: use the &#8220;Static Library&#8221; template in Xcode5. DO NOT INCLUDE ANY IMAGES (or text files, or &#8230; anything that &#8220;doesn&#8217;t get compiled as source-code&#8221;. Xcode5 will silently delete non-source-code items. Xcode5 will, by default, AUTOMATICALLY create a &#8220;copy&#8221; phase for the non-source files (e.g. images) and claim (lies!) that it will copy them. Then <a href=\"http:\/\/www.textfromxcode.com\/\">it will <em>cackle madly at you<\/em> while it deletes them<\/a>.\n<li>Bundle2: use the &#8220;OS X bundle&#8221; template in Xcode5. There is no such thing as an &#8220;OS X bundle&#8221;, but Apple&#8217;s XCode team wants to trick you into &#8220;not using them&#8221;. As a smart developer &#8230; you&#8217;re not fooled! Include all your NON-compiled assets here (images, text files, plist files, CoreData model files, etc)\n<\/ol>\n<p>&#8230;and then it still doesn&#8217;t work. After some more googling you discover a handful of bugs you have to workaround:<\/p>\n<ol>\n<li>BUG 1: Xcode has a major bug where &#8220;iOS apps must have ALL bundles set to &#8220;Skip Install = YES&#8221; or else Xcode5 will silently crash during build phase&#8221; (and it&#8217;ll prevent you uploading to the App Store)\n<li>BUG 2: The error message to tell you that Xcode has crashed internally is missing, and so you get no notification &#8211; but the &#8220;distribute to App Store&#8221; \/ &#8220;distribute to AdHoc\/Enterprise&#8221; buttons simply &#8230; vanish\n<li>BUG 3: sometimes Xcode5 sets &#8220;Skip Install&#8221; to &#8220;No&#8221; for new targets, even though it shouldn&#8217;t, and even though this triggers BUG1 above\n<li>BUG 4: by default, Xcode5 makes bundles &#8220;only work on OS X&#8221; (even though there&#8217;s no such concept &#8211; A bundle is simply &#8220;like a zip file: it&#8217;s a container for random files&#8221;, it has no notion of OS X \/ not OS X)\n<\/ol>\n<h2>[NSBundle mainBundle] methods stop working<\/h2>\n<p>There is a &#8220;correct way&#8221; and an &#8220;incorrect way&#8221; for an operating system to load resources. All OS&#8217;s except Apple&#8217;s do it the &#8220;correct&#8221; way.<\/p>\n<p>Basically &#8230; NSBundle is a very old library, and is missing a core feature that is needed by every non-trivial project: recursive search. You can add it:<\/p>\n<h3>Fix for loadNibNamed:<\/h3>\n<p>[objc]<br \/>\n+(NSArray *) loadNibFromAnyBundleWithName:(NSString*) nibName owner:(id)owner options:(NSDictionary *)options<br \/>\n{<br \/>\n\tif([[NSBundle mainBundle] pathForResource:nibName ofType:@&quot;nib&quot;] != nil)<br \/>\n\t{<br \/>\n\t\treturn [[NSBundle mainBundle] loadNibNamed:nibName owner:owner options:options];<br \/>\n\t}<br \/>\n\telse<br \/>\n\t{<br \/>\n\t\tNSArray* allAppBundles = [NSBundle allBundles];<\/p>\n<p>\t\tfor(  NSBundle* appBundle in allAppBundles )<br \/>\n\t\t{<br \/>\n\t\t\tif([appBundle pathForResource:nibName ofType:@&quot;nib&quot;] != nil)<br \/>\n\t\t\t{<br \/>\n\t\t\t\treturn [appBundle loadNibNamed:nibName owner:owner options:options];<br \/>\n\t\t\t}<br \/>\n\t\t\telse<br \/>\n\t\t\t{<br \/>\n\t\t\t\t\/** This only recurses one level deep; it is exceptionally rare to need to go multi-levels,<br \/>\n\t\t\t\t and really APPLE NEEDS TO FIX THEIR RESOURCE METHODS *\/<br \/>\n\t\t\t\tfor( NSURL* subURL in [appBundle URLsForResourcesWithExtension:@&quot;bundle&quot; subdirectory:nil])<br \/>\n\t\t\t\t{<br \/>\n\t\t\t\t\tNSBundle* subBundle = [NSBundle bundleWithURL:subURL];<\/p>\n<p>\t\t\t\t\tif([subBundle pathForResource:nibName ofType:@&quot;nib&quot;] != nil)<br \/>\n\t\t\t\t\t{<br \/>\n\t\t\t\t\t\treturn [subBundle loadNibNamed:nibName owner:owner options:options];<br \/>\n\t\t\t\t\t}<br \/>\n\t\t\t\t}<br \/>\n\t\t\t}<br \/>\n\t\t}<\/p>\n<p>\t\treturn nil;<br \/>\n\t}<br \/>\n}<br \/>\n[\/objc]<\/p>\n<h3>Fix for CoreData<\/h3>\n<p>[objc]<br \/>\n-(NSManagedObjectModel*) dataModel<br \/>\n{<br \/>\n\tif( _mom == nil )<br \/>\n\t{<br \/>\n\t\tNSString* momdPath = [[NSBundle mainBundle]pathForResource:self.modelName ofType:@&quot;momd&quot;];<br \/>\n\t\tNSURL* momdURL = nil;<\/p>\n<p>\t\t\/**<br \/>\n\t\t WHEREVER your MOMD file is hiding, we&#8217;ll find it!<br \/>\n\t\t *\/<br \/>\n\t\tif( momdPath == nil )<br \/>\n\t\t{<br \/>\n\t\t\tNSArray* allAppBundles = [NSBundle allBundles];<br \/>\n\t\t\tfor(  NSBundle* appBundle in allAppBundles )<br \/>\n\t\t\t{<br \/>\n\t\t\t\tmomdURL = [appBundle URLForResource:self.modelName withExtension:@&quot;momd&quot; subdirectory:nil];<\/p>\n<p>\t\t\t\tif( momdURL == nil ) \/\/ not found, so check the bundle for sub-bundles<br \/>\n\t\t\t\t{<br \/>\n\t\t\t\t\tfor( NSURL* subURL in [appBundle URLsForResourcesWithExtension:@&quot;bundle&quot; subdirectory:nil])<br \/>\n\t\t\t\t\t{<br \/>\n\t\t\t\t\t\tNSBundle* subBundle = [NSBundle bundleWithURL:subURL];<br \/>\n\t\t\t\t\t\tmomdURL = [subBundle URLForResource:self.modelName withExtension:@&quot;momd&quot; subdirectory:nil];<br \/>\n\t\t\t\t\t\tif( momdURL != nil )<br \/>\n\t\t\t\t\t\t{<br \/>\n\t\t\t\t\t\t\tbreak;<br \/>\n\t\t\t\t\t\t}<br \/>\n\t\t\t\t\t}<br \/>\n\t\t\t\t}<\/p>\n<p>\t\t\t\tif( momdURL != nil )<br \/>\n\t\t\t\t{<br \/>\n\t\t\t\t\tbreak;<br \/>\n\t\t\t\t}<br \/>\n\t\t\t}<\/p>\n<p>\t\t\tNSAssert( momdURL != nil, @&quot;Failed to find the momd file for incoming modelName = %@. Maybe you forgot to convert your MOM to a MOMD? (Xcode major bug: used to do this automatically, now it doesn&#8217;t)&quot;, self.modelName );<br \/>\n\t\t}<br \/>\n\t\telse<br \/>\n\t\t\tmomdURL = [NSURL fileURLWithPath:momdPath];<\/p>\n<p>\t\t_mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:momdURL];<br \/>\n\t}<\/p>\n<p>\treturn _mom;<br \/>\n}<br \/>\n[\/objc]<\/p>\n<p>(taken from my <a href=\"https:\/\/github.com\/adamgit\/CoreDataStack\">&#8220;out-dated but still useful&#8221; set of CoreData fixes on GitHub<\/a>)<\/p>\n<h3>Fix for &#8230; <\/h3>\n<p>You get the picture, I think.<\/p>\n<h2>You may be tempted&#8230;<\/h2>\n<p>&#8230;to fix Bug4 above by going to the Build Settings for bundle 2, find the &#8220;build for: OS X&#8221; dropdown, and change it to &#8220;build for: iOS&#8221;, <span style=\"color:red\">and<\/span> <span style=\"color:green\">suddenly<\/span> <span style=\"color:orange\">everything<\/span> <span style=\"color:blue\">works<\/span>!<\/p>\n<p>But it doesn&#8217;t. As you discover when you try to load images.<\/p>\n<h2>&#8230;now you find: UIImage imageNamed: is broken<\/h2>\n<p>Then &#8230; imageNamed: stops working. WTF?<\/p>\n<p>There&#8217;s two things going wrong here:<\/p>\n<ol>\n<li>imageNamed internally re-uses [NSBundle mainBundle], which we know is already crappy\n<li>Xcode5 <span style=\"color:red\">new to Xcode5, Xcode4 didn&#8217;t do this<\/span> silently deletes all PNG files and replaces them with TIFF files\n<\/ol>\n<h3>Problem 1: imageNamed ?<\/h3>\n<p>Googling suggests you&#8217;re screwed. &#8220;imageNamed&#8221; is not (only) a convenience method, it includes a powerful built-in image-cache that&#8217;s part of iOS, and which <em>you cannot use any other way<\/em>.<\/p>\n<p>BUT &#8230; it turns out that some nice person at Apple actually did write (most of) the missing parts of imageNamed:, and simply forgot to tell us they&#8217;d done it. imageNamed: <strong>does support loading from any bundle<\/strong> it just doesn&#8217;t do it transparently<\/p>\n<p>Instead of:<\/p>\n<blockquote><p>\n[UIImage imageNamed:@&#8221;image.png&#8221;];\n<\/p><\/blockquote>\n<p>you simply have to use:<\/p>\n<blockquote><p>\n[UIImage imageNamed:@&#8221;MyBundleName.bundle\/image.png&#8221;];\n<\/p><\/blockquote>\n<p>Really! It&#8217;s almost magical&#8230;<\/p>\n<p><span style=\"color:blue\">Note that Xcode5&#8217;s bundle template moves all image files from their sub-folders into the root of the bundle. Just like the main bundle of your app (sort-of). If your images are in sub-folders &#8230; ignore the sub-folder. Just stick the image name on the end of the bundle name, as above<\/span><\/p>\n<h3>Problem 2: Apple deletes all PNG files<\/h3>\n<p>What. The. Firkin?<\/p>\n<p><a href=\"http:\/\/stackoverflow.com\/a\/12244654\/153422\">Here&#8217;s a StackOverflow answer that explains what&#8217;s happened.<\/a><\/p>\n<p>But why now? Because Xcode5 has been &#8220;upgraded&#8221; so that <strong>it prevents you from changing that OSX specific setting<\/strong>. This is a great idea &#8211; it&#8217;s hiding settings that &#8220;don&#8217;t make sense&#8221; on iOS &#8230; except: <strong>this is another BUG in Xcode5: that setting DOES make sense on iOS<\/strong>.<\/p>\n<p>So &#8230; switch Bundle2 back to compiling for &#8220;OSX&#8221;(instead of iOS), and the missing setting re-appears. Xcode kindly informs us that this setting <em>is currently set to something other than the default, correct value<\/em> (it&#8217;s bolded), so you can either revert it to default, or simply set it to &#8220;FALSE&#8221;\/&#8221;NO&#8221; as per the StackOverflow answer.<\/p>\n<p>&#8230;and then flip it back to building for iOS again, or else Xcode5 will remove the bundle when you make your app.<\/p>\n<h2>TL;DR &#8230; gimme easy steps to fix it!<\/h2>\n<p>Finally, we have enough info to solve the Apple bugs in one fell swoop with an easy, maintainable, robust setup:<\/p>\n<ol>\n<li>Create bundles 1 and 2 above\n<li>Fix bundle2, so that it stops converting all PNG to TIFF\n<li>Set bundle2 to compile as iOS, or it will be silently removed from the app\n<li>Set bundle2 to compile &#8220;all&#8221; architectures, or it will work on sim but not device, or vice versa\n<li>Open the &#8220;Products&#8221; twisty in your left hand bar, and find the named output file for Bundle2. Copy\/paste the name\n<li>&#8230;due to another bug in Xcode5, you can&#8217;t copy\/paste that name. You have to:\n<ol>\n<li>Build the bundle once (works around a THIRD bug in Xcode5) so the name becomes Black rather than Red\n<li>right-click the product, &#8220;Open in Finder&#8221;, and copy\/paste the file-name from Finder.\n<\/ol>\n<li>Change your imageNamed from:\n<ul>\n<li>[UIImage imageNamed:@&#8221;image.png&#8221;];\n<\/ul>\n<li>&#8230;to:\n<ul>\n<li>[UIImage imageNamed:@&#8221;[name of bundle2].bundle\/image.png&#8221;];\n<\/ul>\n<\/ol>\n<p>Et voila! Simple, maintainable, reusable code that works perfectly with Xcode4 and Xcode5! Static libraries, themeable apps, oh MY!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Apple invented &#8220;bundles&#8221; and &#8220;frameworks&#8221; to greatly improve the ease of development and maintenance of OS X projects. They are really good. But Apple modified Xcode to try and stop people using these on iOS. I don&#8217;t know why. Today I delved into this to find out the correct, working, simplest\/easiest solution&#8230;<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[51],"tags":[],"_links":{"self":[{"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/posts\/2931"}],"collection":[{"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/comments?post=2931"}],"version-history":[{"count":13,"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/posts\/2931\/revisions"}],"predecessor-version":[{"id":3059,"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/posts\/2931\/revisions\/3059"}],"wp:attachment":[{"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/media?parent=2931"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/categories?post=2931"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/new.t-machine.org\/index.php\/wp-json\/wp\/v2\/tags?post=2931"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}