Sharing bundled assets between app and extension
Asked Answered
M

5

23

My Photo Sharing extension plans to use the same design assets (for navigation and for adding 'stamps / stickers' to the photo).

As explained in App Sandbox Design Guide, groups of sandboxed apps that need to share files and other information can request a container directory as part of their entitlements. These directories are stored in ~/Library/Group Containers/. https://developer.apple.com/documentation/foundation/nsfilemanager/1412643-containerurlforsecurityapplicati

Currently the assets are bundled in or downloaded and added to the Documents folder. If I want to use the assets in the Photo Sharing extension, would it make sense to put everything in the ~/Library/Group Containers/ and both the container app and the extension get the assets from there?

Manard answered 6/8, 2014 at 0:40 Comment(0)
S
40

Regarding sharing data:

If you want to share files between your iOS8 extension and your Containing app, you want to write files to a shared container or use the shared user defaults.

From Apple's iOS8 current documentation:

By default, your containing app and its extensions have no direct access to each other’s containers

You want to create an App Group, and add your containing app and its extensions to this group. This is pretty simple using Xcode 6, here's how to do it in xcode:

how to create an app group using xcode 6

In the app group name, input a constant like:

group.com.bundle.app.soething

This creates a shared container for your containing app and extension, which allows:

  • Share NSUserDefaults:

    [[NSUserDefaults alloc] initWithSuiteName:@"group.com.bundle.app.soething"];
    
  • Share a directory on the filesystem:

    NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.bundle.app.soething"];
    

EDIT This will print out all the path combinations we have:

// App group's path

NSURL  *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.test.app"];

NSLog(@"App group: %@", containerURL.path);



// Bundle path

NSLog (@"Bundle: %@", [[NSBundle mainBundle] bundlePath]);



// Good old Documents path

NSArray *Paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

NSString *path = [Paths objectAtIndex:0];

NSLog(@"Documents: %@ ", path);

EDIT - and for Swift:

  • Share NSUserDefaults:

    var sharedDefaults = NSUserDefaults(suiteName: "group.com.bundle.app.soething")
    
  • Share a directory on the filesystem:

    var containerURL = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("group.com.bundle.app.soething")!
    

I hope this helps you :)

Resources: Apple's iOS8.0 Extensions Development Guide - https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW1

Apple's iOS8.0 NSFileManager Class Reference - https://developer.apple.com/documentation/foundation/nsfilemanager/1412643-containerurlforsecurityapplicati

Spoke answered 13/8, 2014 at 19:18 Comment(6)
I can confirm that this is the sanctioned approach and the way I have done similar things.Apostate
@ChrisWagner by 'sanctioned', do you mean that apple rejected your apps because of using this approach ?Spoke
Sorry probably a bad word choice, I meant to say this is the approach that Apple says to use in their documentation.Apostate
@nurne thanks - I'm more interested / worried about items in the Documents folder. Instead of just adding downloaded IAP content to the Documents folder, can I just add it to the Group Containers folder?Manard
Does saving files in App group directory safe? Will all the files remain forever like saving files in .UserDomainMaskSumac
For Xcode 9 and Swift, use: let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier:"group.com.bundle.app.soething")!. Also don't forget to add the group capability for each target that needs access to the group.Hadrian
W
9

I need to share bundled resources between the app and extension. At first, I was thinking I could copy them into the shared group area on first launch, but what if the user calls up the extension and never launches the app?

As mxcl notes, you can include the assets in both targets but that will expand the size of your final submitted binary.

I've confirmed putting assets in a framework works in Xcode 6.1 (it may have worked before, but I'm just trying it now):

You can have standalone images in your framework or an asset catalog. Both will work.

In the framework, just set up a class with accessor methods that return UIImage objects. Something like this (Objective-C):

@implementation AssetService
+ (UIImage *)frameworkImageNamed:(NSString *)name {
  return [UIImage imageNamed:name
          inBundle:[NSBundle bundleForClass:[self class]]
          compatibleWithTraitCollection:nil];
}
@end

Or in Swift:

public class AssetService {
  public init() {
  }

  // class method version
  public class func frameworkImageNamed(name: String) -> UIImage? {
    return UIImage(named: name,
                   inBundle: NSBundle(forClass: AssetService.self),
                   compatibleWithTraitCollection: nil)!
  }
}

Note that since the above code is inside the framework but will run in "app space" you need to specify the bundle.

Then, your app or extension can just link in the framework, and call [AssetService frameworkImageNamed:@"myimage"] or AssetService.frameworkImageNamed("myimage").

Caveat: despite returning an optional UIImage? the code gets very angry if it can't find the image named and will crash. But if it's your own framework, you should know what's in there and catch this kind of bug.

Weeny answered 22/10, 2014 at 14:53 Comment(3)
How are you able to share images with the extension added to the asset bundle? It works when making development builds, but in release builds the images fail to load inside the extension.Foliaceous
Does using App Groups help ? Or is this solution really wrong (I will try with release build now to confirm).Dianthe
@Dianthe I've used app groups, and a copy from bundle to app group container area on first launch. That means the app needs to be launched first before the extension.Weeny
H
3

here the steps to resolve your issue:

  1. go to xcode open your app which contains the app extension feature

  2. go to target in your xcode project and select your app extension name

  3. go to build phases

  4. expand the copy bundle resources then click "+" button and add the image assets folder from the finder.

Herwin answered 8/9, 2014 at 15:28 Comment(1)
This doubles your asset storage requirements since they are now bundled into your app twice.Dufour
A
2

Sounds like you want to use a framework instead. Your framework will be primarily resources instead of code, however:

OS X makes extensive use of frameworks to distribute shared code and resources, such as the interfaces to the system itself. You can create frameworks of your own to provide shared code and resources to one or more of your company’s applications. You can also create frameworks containing class libraries or add-on modules with the intention of distributing them to other developers.

https://developer.apple.com/library/ios/documentation/MacOSX/Conceptual/BPFrameworks/Frameworks.html#//apple_ref/doc/uid/10000183i

Aegyptus answered 9/8, 2014 at 0:27 Comment(4)
A framework isn't going to help with files on disk. Frameworks will let you re-use compiled code across targets though.Apostate
Steve, I've read you can't add assets to a Framework - is this true? If you can, how do you do so?Manard
You would create your own Framework then "import it" into the other project. So when you wanted to add more files, you just update the framework.Aegyptus
Got it working with .xcassets and plain old images in the framework target. Check out my answer to this question.Weeny
G
1

Assets can be added to a framework and reused in both app/extension. See "Loading Resources From A Framework" for more details.

  public class AmazingView: UIView {
  private func setupView() {
    let bundle = Bundle(for: AmazingView.self)
    guard let image = UIImage(named: "MyImage", in: bundle, compatibileWith: nil) else {
      fatalError("Missing MyImage...")
    }
  }
}
Godfry answered 1/9, 2019 at 8:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.