37

In iOS 8, when develop a custom keyboard and set RequestsOpenAccess property to YES in info.plist, there is a toggle button at Settings-> Add New Keyboard named "Allow Full Access". How does the custom keyboard code know that the user enabled or disabled this toggle?

chenmervyn
  • 371
  • 1
  • 3
  • 3
  • Also i dont understand why in iOS 8 GM, they dont show the allow full access switch when enabling the keyboard, one must add it then click on it.. – Daniel Sep 10 '14 at 22:48
  • You can find better answer here: https://stackoverflow.com/a/42307723/6392347 – Daniel Aug 25 '17 at 07:27

15 Answers15

34

UPDATE 08/23/2017 for iOS 10 compatibility:

func isOpenAccessGranted() -> Bool{
    UIPasteboard.general.string = "CHECK"
    return UIPasteboard.general.hasStrings
}

iOS 8:

-(BOOL)isOpenAccessGranted{
   return [UIPasteboard generalPasteboard];
}

Please note that the simulator will always tell you that you have Full Access so for this to work properly you need to run it from a device.

Julio Bailon
  • 3,735
  • 2
  • 33
  • 34
  • Have you found a proper solution yet? – NJanf Sep 23 '14 at 17:43
  • But it took a long time to show the view if I put it in a view controller. Anyway to make it faster? – yong ho Dec 19 '14 at 01:47
  • You may want to bold your last statement to make it stand out, I spent some time trying to figure out why it was always returning true before coming back here. :p – Jordan H Dec 21 '14 at 07:03
  • @Joey, good suggestion. I just made it BOLD. Glad to hear it was useful to you – Julio Bailon Dec 22 '14 at 01:34
  • 4
    Note that due to changed behavior in iOS 8.3, this no longer works - always returns `true` when run on a device. – Jordan H May 19 '15 at 06:12
  • @Joey what is the change in iOS 8.3? What do you use now? – Julio Bailon May 19 '15 at 18:37
  • 2
    @JulioBailon fisch2's answer worked for me. New in iOS 8.3 you can read from shared group containers even if full access is not granted, so the above code always returns true, but you cannot write unless you have full access. Not sure if that's a bug or expected behavior - it's the same in 8.4 betas. – Jordan H May 19 '15 at 18:44
  • @Joey thank you for letting me know. This makes sense and at the same time will break many people's code including mine :( – Julio Bailon May 19 '15 at 18:48
  • Yup, it definitely broke mine. It worked wonderfully for a good while though, so thanks for your answer. :) – Jordan H May 19 '15 at 19:01
  • Thanks for the insight @Joey. Did you manage to figure out the workaround that could detect "write" access and whether its granted or not? Thanks! – daspianist Jul 19 '15 at 01:55
  • stopped working in iOS 10 after apple did some changes in UIPasteboard – iHulk Sep 16 '16 at 09:49
  • In iOS 10, it always returns non-nil. – Daniel Aug 17 '17 at 08:15
  • @PaulStein I just made a new edit. Let me know if it works now – Julio Bailon Aug 23 '17 at 15:25
15

This is the simplest answer by far and doesn't require you to setup app groups. It is tested in production on the app store.

+ (BOOL)isOpenAccessGranted
{
    return [UIPasteboard generalPasteboard];
}
fisch2
  • 2,574
  • 2
  • 26
  • 29
  • 7
    In swift this would be `UIPasteboard.generalPasteboard().isKindOfClass(UIPasteboard)` – hoiberg Feb 02 '15 at 12:38
  • 2
    @hoiberg42 - Good call. When all else failed (in 8.3) – bauerMusic Apr 13 '15 at 16:02
  • 1. The above method should return a BOOL. Therefore use: return [[UIPasteboard generalPasteboard] isKindOfClass:[UIPasteboard class]]; 2. It seems that full access detection via app groups was broken in iOS 8.2. Can someone else confirm this? – user3246173 May 22 '15 at 02:13
  • Has anyone experienced a delay when loading a keyboard without full access, and using this method when the view loads? – KingPolygon Jul 23 '15 at 04:22
  • APPLE REJECTED MY APP. I'm using the above approach ( UIPasteboard.generalPasteboard().isKindOfClass(UIPasteboard) ). It's working on devices but not on the simulator (on simulator it always returns true). Seems like Apple are testing in simulator. Has anyone gotten their app approved using this method? – Thyselius Aug 04 '15 at 21:13
  • @Thyselius sorry this is so long ago but yes, I worked for a company with over 60+ keyboards in the iOS app store, all of which used this method. – fisch2 Dec 30 '15 at 18:17
11

For iOS 10 (Beta 5) they changed the UIPasteboard API but I found the following to work:

let originalString = UIPasteboard.general.string
UIPasteboard.general.string = "TEST"
if UIPasteboard.general.hasStrings
{
    UIPasteboard.general.string = originalString
    hasFullAccess = true
}
else
{
     hasFullAccess = false
}
schlaegerz
  • 377
  • 2
  • 12
  • 1
    Works in Xcode 8 (non beta) as well. Great work, thanks! – Ahmet Akkök Oct 10 '16 at 13:37
  • 1
    Update: it works, but it prints `Returning local object of class NSString` and `PBItemCollectionServicer connection disconnected` every time the 5th line in your code is run. – hoiberg Oct 31 '16 at 18:34
  • 1
    @hoiberg42 I believe I got around that by just caching the answer so I only had to do it once. – schlaegerz Jan 06 '17 at 18:24
  • @aircraft you can put this inside a function and have it return the result – schlaegerz Jan 06 '17 at 18:25
  • @schlaegerz you are assigning the string to the pasteboard. Even if u don't turn on the `Allow Full Access` in settings it returns `true` value. Can please explain? – Ramakrishna Jun 10 '17 at 12:38
  • I can't promise this still works, as i haven't worked on iOS for a bit, but hasStrings would return false if you didn't Allow Full access. Apple changes this constantly, so if this doesn't work anymore you'll have to find a new solution – schlaegerz Nov 22 '17 at 02:15
9

I've been testing this today in iOS 10 and getting access to the pasteboard doesn't appear to be enough. In iOS 10 you can set the pasteboard to a var without full access. Here's a solution I came up with...

func checkFullAccess() -> Bool
{
    var hasFullAccess = false
    if #available(iOSApplicationExtension 10.0, *) {
        let pasty = UIPasteboard.general
        if pasty.hasURLs || pasty.hasColors || pasty.hasStrings || pasty.hasImages {
            hasFullAccess = true
        } else {
            pasty.string = "TEST"
            if pasty.hasStrings {
                hasFullAccess = true
                pasty.string = ""
            }
        }
    } else {
        // Fallback on earlier versions
        var clippy : UIPasteboard?
        clippy = UIPasteboard.general
        if clippy != nil {
            hasFullAccess = true
        }
    }
    return hasFullAccess
}

Testing to see if the pasteboard has some content returns false with full access off, even when there's content on the pasteboard. Of course it could actually be empty so after all those tests you can safely attempt to set something on the pasteboard without worrying about replacing something that's already there. If you do have access and the pasteboard had content then the test would have returned true, if you don't have access then you can't overwrite something that was there.

HTH, Mike

Mike
  • 788
  • 10
  • 27
8

Update: No need for App Group to be enabled, as others have mentioned, just check for access to the pasteboard:

- (BOOL)isFullAccessGranted
{
    return !![UIPasteboard generalPasteboard];
}

NOTE: The following no longer works, even if you do have App Group enabled...

For a custom keyboard with an App Group enabled, the following is a quick, reliable way of testing for the state of the "Allow Full Access" switch:

func isOpenAccessGranted() -> Bool {
    let fm = NSFileManager.defaultManager()
    let containerPath = fm.containerURLForSecurityApplicationGroupIdentifier(
                        "group.com.example")?.path
    var error: NSError?
    fm.contentsOfDirectoryAtPath(containerPath!, error: &error)
    if (error != nil) {
        NSLog("Full Access: Off")
        return false
    }
    NSLog("Full Access: On");
    return true
}
timcour
  • 384
  • 3
  • 9
  • 2
    This works perfectly in the keyboard extension. However, this always returns true in the containing app. Do you know how to check the state of Allow Full Access in the containing app? – Jon Oct 03 '14 at 14:54
  • Tried a few ways, but not yet found a way to do this from the containing app. – timcour Oct 06 '14 at 18:08
  • I'm getting error: `fatal error: unexpectedly found nil while unwrapping an Optional value` at line ` fm.contentsOfDirectoryAtPath(containerPath!, error: &error)` – TomSawyer Nov 04 '14 at 19:36
  • The `fatal error` is most likely caused by a misconfigured App Group. A misconfigured App Group would result in `containerURLForSecurityApplicationGroupIdentifier` returning `nil`. Did you confirm that your App Group is configured correctly? See https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistributionGuide/AddingCapabilities/AddingCapabilities.html#//apple_ref/doc/uid/TP40012582-CH26-SW61. – timcour Nov 04 '14 at 21:40
  • 1
    This answer needs to be updated, that now (iOS 10) Keyboard extension can access container without full access. Now we need to try to write a file to check – Tony Wang Nov 02 '16 at 10:23
5

For iOS 10 using Swift 2.3 (if you don't want to convert your files to Swift 3.0)

func isOpenAccessGranted() -> Bool {
    if #available(iOSApplicationExtension 10.0, *) {

    let originalString = UIPasteboard.generalPasteboard().string
    UIPasteboard.generalPasteboard().string = "Test"

    if UIPasteboard.generalPasteboard().hasStrings {
            UIPasteboard.generalPasteboard().string = originalString
            return true
        } else {
            return false
        }
    } else {
        return UIPasteboard.generalPasteboard().isKindOfClass(UIPasteboard)
    }
}
iOS_Mouse
  • 754
  • 7
  • 13
4

iOS 11 no longer requires any hacks.

  override var hasFullAccess: Bool {
    if #available(iOS 11.0, *) {
      return super.hasFullAccess// super is UIInputViewController.
    }
    if #available(iOS 10.0, *) {
      let original: String? = UIPasteboard.general.string
      UIPasteboard.general.string = " "
      let val: Bool = UIPasteboard.general.hasStrings
      if let str = original {
        UIPasteboard.general.string = str
      }
      return val
    }
    return UIPasteboard.general.isKind(of: UIPasteboard.self)
  }
Mitsuaki Ishimoto
  • 3,162
  • 2
  • 25
  • 32
2

This code works for me, I also on App Groups : https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistributionGuide/AddingCapabilities/AddingCapabilities.html#//apple_ref/doc/uid/TP40012582-CH26-SW61

May be it will work without App Groups also.

And I used this code :

if(isOpenAccessGranted()){ 
            NSLog("FULL ACCESS ON")                
        }
        else{
            NSLog("FULL ACCESS OFF")               
        }
}

func isOpenAccessGranted() -> Bool {
        return UIPasteboard.generalPasteboard().isKindOfClass(UIPasteboard)
}
Tejinder
  • 1,507
  • 19
  • 22
2

Useful Swift solution by @hoiberg42:

func isOpenAccessGranted() -> Bool {
    return UIPasteboard.generalPasteboard().isKindOfClass(UIPasteboard)
}

Works like a charm!

Mohammad Zaid Pathan
  • 16,304
  • 7
  • 99
  • 130
imike
  • 5,515
  • 2
  • 37
  • 44
1

For those of you who are using iOS 10 and Objective-C this will work as intended.

- (BOOL)hasFullAccess
{
    UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
    NSString *originalString = pasteboard.string;
    pasteboard.string = @"TEST";
    if (pasteboard.hasStrings) {
        pasteboard.string = originalString;
        return YES;
    } else {
        return NO;
    }
}
Josh
  • 254
  • 3
  • 19
  • Worked for me (iOS 10+). Please note that `originalstring` could be nil which causes a crash in `pasteboard.string = originalString;`. – hotdogsoup.nl Apr 01 '19 at 13:03
0

just use

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),     
^{
    AudioServicesPlaySystemSound(1104);
    });

the tirck is ,when the full access is enabled ,the sound will play ,if not ,because it's call is in the background thread will no block the main thread

user3180187
  • 117
  • 1
  • 3
0

A swift solution

 if let test = UIPasteboard.generalPasteboard() as? UIPasteboard{
        NSLog("Full Access: On")
        return true
    }else{
        NSLog("Full Access: Off")
        return false
    }

inside the above function of course.

theSiberman
  • 441
  • 4
  • 14
0

Today, for iOS 9.2, you can check opened access by:

func isOpenAccessGranted() -> Bool {
    return UIPasteboard(name: "checkOpenedAccess", create: true) != nil
}
dimpiax
  • 12,093
  • 5
  • 62
  • 45
0

Apps with app group can use :

 func isOpenAccessGranted() -> Bool {
    let fm = NSFileManager.defaultManager()
    let containerPath = fm.containerURLForSecurityApplicationGroupIdentifier(
        AppGroup)?.path
    return fm.isWritableFileAtPath(containerPath!)
}
Ankish Jain
  • 11,305
  • 5
  • 36
  • 34
0

iOS11 and above is easy.

iOS10 Solution: Check all the copy-able types, if one of them is available, you have full access otherwise not.

-- Swift 4.2--

override var hasFullAccess: Bool
{
    if #available(iOS 11.0, *){
        return super.hasFullAccess// super is UIInputViewController.
    }

    if #available(iOSApplicationExtension 10.0, *){
        if UIPasteboard.general.hasStrings{
            return  true
        }
        else if UIPasteboard.general.hasURLs{
            return true
        }
        else if UIPasteboard.general.hasColors{
            return true
        }
        else if UIPasteboard.general.hasImages{
            return true
        }
        else  // In case the pasteboard is blank
        {
            UIPasteboard.general.string = ""

            if UIPasteboard.general.hasStrings{
                return  true
            }else{
                return  false
            }
        }
    } else{
        // before iOS10
        return UIPasteboard.general.isKind(of: UIPasteboard.self)
    }
}
Ahmet Akkök
  • 466
  • 1
  • 5
  • 13