34

Is it possible to get the containing app's NSBundle from within an app extension? I would like to get the main app's display name, not the extension's display name.

Jordan H
  • 52,571
  • 37
  • 201
  • 351

3 Answers3

65

The +mainBundle method returns the bundle containing the "current application executable", which is a subfolder of your app when called from within an extension.

This solution involves peeling off two directory levels from the URL of the bundle, when it ends in "appex".

Objective-C

NSBundle *bundle = [NSBundle mainBundle];
if ([[bundle.bundleURL pathExtension] isEqualToString:@"appex"]) {
    // Peel off two directory levels - MY_APP.app/PlugIns/MY_APP_EXTENSION.appex
    bundle = [NSBundle bundleWithURL:[[bundle.bundleURL URLByDeletingLastPathComponent] URLByDeletingLastPathComponent]];
}

NSString *appDisplayName = [bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];

Swift 2.2

var bundle = NSBundle.mainBundle()
if bundle.bundleURL.pathExtension == "appex" {
    // Peel off two directory levels - MY_APP.app/PlugIns/MY_APP_EXTENSION.appex
    bundle = NSBundle(URL: bundle.bundleURL.URLByDeletingLastPathComponent!.URLByDeletingLastPathComponent!)!
}

let appDisplayName = bundle.objectForInfoDictionaryKey("CFBundleDisplayName")

Swift 3

var bundle = Bundle.main
if bundle.bundleURL.pathExtension == "appex" {
    // Peel off two directory levels - MY_APP.app/PlugIns/MY_APP_EXTENSION.appex
    let url = bundle.bundleURL.deletingLastPathComponent().deletingLastPathComponent()
    if let otherBundle = Bundle(url: url) {
        bundle = otherBundle
    }
}

let appDisplayName = bundle.object(forInfoDictionaryKey: "CFBundleDisplayName")

This will break if the pathExtension or the directory structure for an iOS extension ever changes.

phatblat
  • 3,804
  • 3
  • 33
  • 31
  • Thanks for this! Have you been able to get an AppStore app approved this way? – ewindsor Apr 19 '16 at 21:43
  • Yes, no private APIs are used here. We used this in our watchOS 1 extension to load the managed object model for a `UIManagedDocument` from the main iOS app bundle. – phatblat Apr 21 '16 at 16:54
  • Ah awesome. Thanks for the insight. – ewindsor Apr 22 '16 at 21:05
  • Could someone please convert to Swift? I'm having a little trouble following along. Thanks! – Matt Schwartz Jun 22 '16 at 06:08
  • @MattSchwartz I added the Swift version as a new answer. – phatblat Jun 23 '16 at 13:21
  • :-D I've just added the Swift version to your answer. Which one should we keep? – FelixSFD Jun 23 '16 at 13:24
  • 1
    @FelixSFD I like how yours is split out by language and version. Personally, I avoid force unwrapping optionals as much as possible, especially in example code for others. One benefit of this in the Swift 3 code is you can use `try?` to alleviate the need for the `do/catch` statements. – phatblat Jun 23 '16 at 18:44
  • This works, but I sure wish this wasn't so hacky. I hope Apple fixes this problem and doesn't break all the apps that are resorting to this. – Gandalf458 Jan 05 '17 at 20:04
12

Building upon @phatblat's answer, here is a solution which is less likely to break from file structure changes.

extension Bundle {
    /// Return the main bundle when in the app or an app extension.
    static var app: Bundle {
        var components = main.bundleURL.path.split(separator: "/")
        var bundle: Bundle?

        if let index = components.lastIndex(where: { $0.hasSuffix(".app") }) {
            components.removeLast((components.count - 1) - index)
            bundle = Bundle(path: components.joined(separator: "/"))
        }

        return bundle ?? main
    }
}
cnotethegr8
  • 7,342
  • 8
  • 68
  • 104
0

Use this extensions and you'll be able to get the main bundle from target extensions:

extension Bundle {
    
    func getBundleName() -> String {
        var finalBundleName = Bundle.main.bundleIdentifier ?? "unknow"
        if(SwiftUtils.isRunningOnExtension()){
            _ = finalBundleName.replaceRegex(#"\.\w+$"#)
        }
        return finalBundleName
    }
}

extension String {

    public mutating func replaceRegex(_ pattern: String, replaceWith: String = "") -> Bool {
        do {
            let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive)
            let range = NSMakeRange(0, self.count)
            self = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith)
            return true
        } catch {
            return false
        }
    }
}
Rafael Setragni
  • 160
  • 1
  • 6