In custom iOS keyboard detect the app
Asked Answered
G

3

8

How to detect in which app my custom keyboard used and show different button? E.g. in Twitter I would add @ to string I post into input field and in Reddit /r/

Gavrilla answered 23/5, 2015 at 1:14 Comment(6)
Like Schemetrical said, you can't determine the app you're in (at least, you're not supposed to be able to be, and probably any answer posted here would be considered a loophole and be fixed by Apple), but you can know that you're in a Twitter style text field, and respond appropriately, which might be better than knowing the app, since there are built in APIs to post to Twitter that can be used from any app.Harrietharriett
@BenPious thanks for inspirations! I appreciate any clues about "Twitter style text field"Gavrilla
You have access in the API to an object (I forget which) which implements this protocol: developer.apple.com/library/ios/documentation/UIKit/Reference/…Harrietharriett
UIKeyboardTypeDefault, UIKeyboardTypeASCIICapable, UIKeyboardTypeNumbersAndPunctuation, UIKeyboardTypeURL, UIKeyboardTypeNumberPad, UIKeyboardTypePhonePad, UIKeyboardTypeNamePhonePad, UIKeyboardTypeEmailAddress, UIKeyboardTypeDecimalPad, UIKeyboardTypeTwitter, UIKeyboardTypeWebSearch, UIKeyboardTypeAlphabet = UIKeyboardTypeASCIICapableGavrilla
@BenPious how do you this is that violate developer guidelines or not?Gavrilla
@BenPious I have twitter style text field in Instagram, Slack and other apps. EhGavrilla
W
3

Edit: See above. Things have changed.

This is not possible. An extension runs sandboxed and is only fed information from the API and cannot access anything else. The keyboard can only receive text context changes and activate/deactivate calls. Being able to detect an app lies outside of the extension sandbox and therefore is impossible.

Wife answered 23/5, 2015 at 2:6 Comment(3)
This is just wrong. It is possible to get the host's app bundle identified with the @halfer answer above.Triadelphous
@GrantOganyan this is probably out of date, I’ll deleteWife
Looks like I cant. Hopefully people read above.Wife
P
10

It is possible through following code. As you'll get bundle identifier of the app where you're using your custom keyboard.

Swift

    let hostBundleID = self.parentViewController!.valueForKey("_hostBundleID")
    let currentHostBundleID = String(hostBundleID)
    print(currentHostBundleID);

From bundle identifier you can find app name easily.

Pitta answered 11/8, 2016 at 11:46 Comment(4)
is this allowed by apple?... as in would this cause an app extension to be rejected?Henigman
@WillVonUllrich One of my app approved by Apple which has the same implementation. So hope it works in your case too.Pitta
Is there any API to get app details from bundle id?Polyphonic
This has unfortunately stopped working in iOS 16.Zloty
W
3

Edit: See above. Things have changed.

This is not possible. An extension runs sandboxed and is only fed information from the API and cannot access anything else. The keyboard can only receive text context changes and activate/deactivate calls. Being able to detect an app lies outside of the extension sandbox and therefore is impossible.

Wife answered 23/5, 2015 at 2:6 Comment(3)
This is just wrong. It is possible to get the host's app bundle identified with the @halfer answer above.Triadelphous
@GrantOganyan this is probably out of date, I’ll deleteWife
Looks like I cant. Hopefully people read above.Wife
N
1

Here is the updated solution that also supports iOS 16 and 17:

extension KeyboardViewController {
    var hostBundleId: String? {
        if let id = hostBundleIdValueBefore16 { return id }
        return hostBundleIdValueFor16
    }
    
    private var hostBundleIdValueBefore16: String? {
        let value = parent?.value(forKey: "_hostBundleID") as? String
        return value != "<null>" ? value: nil
    }
    
    private var hostBundleIdValueFor16: String? {
        guard let pid = parent?.value(forKey: "_hostPID") else { return nil }
        let selector = NSSelectorFromString("defaultService")
        guard let anyClass: AnyObject = NSClassFromString("PKService"),
           let pkService = anyClass as? NSObjectProtocol,
           pkService.responds(to: selector),
           let serverInis = pkService.perform(selector).takeUnretainedValue() as? NSObjectProtocol
        else { return nil }
        let lities = serverInis.perform(NSSelectorFromString("personalities")).takeUnretainedValue()
        let bundleId = Bundle.main.bundleIdentifier ?? ""
        guard let infos = lities.object(forKey: bundleId) as? AnyObject,
           let info = infos.object(forKey: pid) as? AnyObject,
           let con = info.perform(NSSelectorFromString("connection")).takeUnretainedValue() as? NSObjectProtocol
        else { return nil }
        let xpcCon = con.perform(NSSelectorFromString("_xpcConnection")).takeUnretainedValue()
        let handle = dlopen("/usr/lib/libc.dylib", RTLD_NOW)
        let sym = dlsym(handle, "xpc_connection_copy_bundle_id")
        typealias xpcFunc = @convention(c) (AnyObject) -> UnsafePointer<CChar>
        let cFunc = unsafeBitCast(sym, to: xpcFunc.self)
        let response = cFunc(xpcCon)
        let hostBundleId = NSString(utf8String: response)
        return hostBundleId as String?
    }
}

Note that you may get rejected by Apple for using this code. However, some apps managed to pass the Apple Review.

Credits: KeyboardKit

Usage:

print(hostBundleId)
// Output in Apple Notes: com.apple.mobilenotes

Make sure you are inside your KeyboardController class.

Nephron answered 30/4 at 8:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.