32

I was wondering if there is a method that would allow me to detect from the keyboard container app whether the associated keyboard has been activated in the the device's Settings app.

For example, I am interested in adding a simple "steps" feature inside the container app where step 1 would be "activate the keyboard", and step 2 would be contingent on step 1's completion. As such, I am interested in figuring out whether there is a way to detect whether the keyboard extension is activated?

Thanks!

daspianist
  • 5,336
  • 8
  • 50
  • 94

4 Answers4

59

Here is a method I have used in one of my projects. I think it is what you asked for, hope it helps you.

- (BOOL)isCustomKeyboardEnabled {
    NSString *bundleID = @"com.company.app.customkeyboard"; // Replace this string with your custom keyboard's bundle ID
    NSArray *keyboards = [[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] objectForKey:@"AppleKeyboards"]; // Array of all active keyboards
    for (NSString *keyboard in keyboards) {
        if ([keyboard isEqualToString:bundleID])
            return YES;
    }

    return NO;
}
Kurt
  • 714
  • 5
  • 5
  • 2
    Brilliant, this did the job! Great discovery too. – daspianist Sep 12 '14 at 00:06
  • 4
    This is super useful! BUT this is more useful in a way where if the keyboard is installed. this wont tell which keyboard is currently active on the screen? or am I missing something? – Cem Kozinoglu Jul 27 '15 at 15:54
  • 1
    @CemKozinoglu I saw you managed to do this with the fantastic Slash Keyboard's intro walkthrough (congrats btw). Would you mind sharing with us how you did this? – daspianist Dec 08 '15 at 01:14
  • 1
    Awesome!! , and how about next step? how do we check full access enable or not in same container app ? @https://stackoverflow.com/users/4030703/kurt – Syed Sadrul Ullah Sahad Nov 19 '17 at 11:59
  • ok i got it but, how to change specific keyboard programatically. – ikbal Nov 27 '18 at 07:56
16

Just in case here is Swift version of Kurt's brilliant and awesome answer:

   func isKeyboardExtensionEnabled() -> Bool {
    guard let appBundleIdentifier = Bundle.main.bundleIdentifier else {
        fatalError("isKeyboardExtensionEnabled(): Cannot retrieve bundle identifier.")
    }

    guard let keyboards = UserDefaults.standard.dictionaryRepresentation()["AppleKeyboards"] as? [String] else {
        // There is no key `AppleKeyboards` in NSUserDefaults. That happens sometimes.
        return false
    }

    let keyboardExtensionBundleIdentifierPrefix = appBundleIdentifier + "."
    for keyboard in keyboards {
        if keyboard.hasPrefix(keyboardExtensionBundleIdentifierPrefix) {
            return true
        }
    }

    return false
}
Awais Mobeen
  • 733
  • 11
  • 19
Valentin Shergin
  • 7,166
  • 2
  • 50
  • 53
  • fatalError is used to deliberately crash the app while testing. Once the code is tested, it could be replaced with `return false` and printing the error to console. Also comparing the prefix in case the app is using multiple extensions could return true even if the keyboard extension has not been added by the user. So it could be compared as a full name. – user3404693 Aug 30 '16 at 14:36
  • Thank you for your valuable feedback. I really appreciate it. (1) Yes, the code had a bug happened when `standardUserDefaults` has no `AppleKeyboards` key. I fix it. (2) I strongly disagree about the purpose of `fatalError`, it has nothing with testing. (3) Yes, the code assumes that the bundle has only one `keyboard` extension. – Valentin Shergin Aug 30 '16 at 17:50
  • @ValentinShergin your answer is helpful. But I don't understand why we checking the bundle identifier is nil or not? I think there is no chance it will nil and without bundle identifier app doesn't work. – Ramakrishna Jun 09 '17 at 05:38
  • Just because the expression `NSBundle.mainBundle().bundleIdentifier` returns an Optional (something that can be nil) at least in that time (that Swift version). – Valentin Shergin Jun 10 '17 at 22:12
3

The current documentation states By default, your extension and its containing app have no direct access to each other’s containers.

It is also stating that the container app can share data with the keyboard in the following fashion:

// Create and share access to an NSUserDefaults object.
NSUserDefaults *mySharedDefaults = [[NSUserDefaults alloc]
initWithSuiteName:@"com.example.domain.MyShareExtension"];
// Use the shared user defaults object to update the user's account.
[mySharedDefaults setObject:theAccountName forKey:@"lastAccountName"];

Read more on this: Communicating and persisting data between apps with App Groups

Obstacle no 1: According to the documentation, for this to work, the RequestsOpenAccess in the plist needs to be set to YES as it would gain the following capability:

Option to use a shared container with the keyboard’s containing app, which enables features such as providing a custom lexicon management UI in the containing app

Requesting full access for a simple case like this is definitely not preferred on my side.

Obstacle no 2: Using this knowledge of setting a NSUserDefault, leaves me to think of a method where this can be set in place. But there's no public method indicating an extension is installed. So this is a dead end for now.

--

[Update 1]

Not super relevant but still worth stating: the shouldAllowExtensionPointIdentifier app delegate method in combination with the constant UIApplicationKeyboardExtensionPointIdentifier can deal with disallowing custom keyboards. The extension point identifiers are not unique identifiers of the extension but of their type.

Read more on this: Can I disable custom keyboards (iOS8) for my app?

--

[Update 2]

Another question with same issue, but w/o solution: How to detect an app extension is enabled in containing app on iOS 8?

--

This is a work-in-progress answer stating my findings so far which I hope to be updating coming days should I find a solution.

Community
  • 1
  • 1
dandoen
  • 1,647
  • 5
  • 26
  • 44
  • Great writeup so far @dandoen. Please update if you find a solution! – daspianist Sep 07 '14 at 20:16
  • 1
    Thanks for updates. It appears that solution provided by Kurt which accesses `[[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] objectForKey:@"AppleKeyboards"]` did the trick :) – daspianist Sep 12 '14 at 00:14
-1

You can use this function (Swift 3 and 4) to check your custom keyboard extension have open access or not:

func isOpenAccessGranted() -> Bool{
    if #available(iOS 10.0, *) {
       let originalString = UIPasteboard.general.string
       UIPasteboard.general.string = "Sour LeangChhean"

       if UIPasteboard.general.hasStrings {

           UIPasteboard.general.string = originalString ?? ""
           return true
       }else{
           UIPasteboard.general.string = ""
           return false
       }
  } else {
    // Fallback on earlier versions
       if UIPasteboard.general.isKind(of: UIPasteboard.self) {
           return true
       }else{
           return false
       }
   }
}
Sour LeangChhean
  • 7,089
  • 6
  • 37
  • 39