Giter Club home page Giter Club logo

permissionscope's Introduction

PermissionScope is no longer supported. Please use an alternative if you need updates for newer iOS 10 and 11 APIs!

PermissionScope

Platform: iOS 8+ Language: Swift 3 Carthage compatible Cocoapods compatible License: MIT

InstallationUsageCustomizationKnown bugsIssuesLicense

Inspired by (but unrelated to) Periscope's permission control, PermissionScope is a Swift framework for intelligently requesting permissions from users. It contains not only a simple UI to request permissions but also a unified permissions API that can tell you the status of any given system permission or easily request them.

Some examples of multiple permissions requests, a single permission and the denied alert.

permissionscope gif

PermissionScope gives you space to explain your reasons for requesting permissions and allows users to tackle the system dialogs at their own pace. It presents a straightforward permissions design and is flexible enough to fit in to most UIKit-based apps.

Best of all, PermissionScope detects when your app's permissions have been denied by a user and gives them an easy prompt to go into the system settings page to modify these permissions.

Supported permissions:

  • Notifications
  • Location (WhileInUse, Always)
  • Contacts
  • Events
  • Microphone
  • Camera
  • Photos
  • Reminders
  • Bluetooth
  • Motion

compatibility

PermissionScope requires iOS 8+, compatible with both Swift 3 and Objective-C based projects.

For Swift 2.x support, please use the swift2 branch or the 1.0.2 release version. This branch was up-to-date on 9/6/16 but is not being maintained. All future efforts will go towards Swift 3 development.

installation

Installation for Carthage is simple enough:

github "nickoneill/PermissionScope" ~> 1.0

As for Cocoapods, use this to get the latest release:

use_frameworks!

pod 'PermissionScope'

And import PermissionScope in the files you'd like to use it.

dialog usage

The simplest implementation displays a list of permissions and is removed when all of them have satisfactory access.

class ViewController: UIViewController {
    let pscope = PermissionScope()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Set up permissions
        pscope.addPermission(ContactsPermission(),
            message: "We use this to steal\r\nyour friends")
        pscope.addPermission(NotificationsPermission(notificationCategories: nil),
            message: "We use this to send you\r\nspam and love notes")
        pscope.addPermission(LocationWhileInUsePermission(),
            message: "We use this to track\r\nwhere you live")
	
	// Show dialog with callbacks
        pscope.show({ finished, results in
            print("got results \(results)")
        }, cancelled: { (results) -> Void in
            print("thing was cancelled")
        })   
    }
}

The permissions view will automatically show if there are permissions to approve and will take no action if permissions are already granted. It will automatically hide when all permissions have been approved.

If you're attempting to block access to a screen in your app without permissions (like, say, the broadcast screen in Periscope), you should watch for the cancel closure and take an appropriate action for your app.

customization

You can easily change the colors, label and buttons fonts with PermissionScope by modifying any of these properties:

Field Type Comment
headerLabel UILabel Header UILabel with the message "Hey, listen!" by default.
bodyLabel UILabel Header UILabel with the message "We need a couple things\r\nbefore you get started." by default.
closeButtonTextColor UIColor Color for the close button's text color.
permissionButtonTextColor UIColor Color for the permission buttons' text color.
permissionButtonBorderColor UIColor Color for the permission buttons' border color.
buttonFont UIFont Font used for all the UIButtons
labelFont UIFont Font used for all the UILabels
closeButton UIButton Close button. By default in the top right corner.
closeOffset CGSize Offset used to position the Close button.
authorizedButtonColor UIColor Color used for permission buttons with authorized status
unauthorizedButtonColor UIColor? Color used for permission buttons with unauthorized status. By default, inverse of authorizedButtonColor.
permissionButtonΒorderWidth CGFloat Border width for the permission buttons.
permissionButtonCornerRadius CGFloat Corner radius for the permission buttons.
permissionLabelColor UIColor Color for the permission labels' text color.
contentView UIView Dialog's content view

In addition, the default behavior for tapping the background behind the dialog is to cancel the dialog (which calls the cancel closure you can provide on show). You can change this behavior with backgroundTapCancels during init.

If you'd like more control over the button text for a particular permission, you can use a .strings file for your intended language and override them that way. Please get in touch if you'd like to contribute a localization file for another language!

unified permissions API

PermissionScope also has an abstracted API for getting the state for a given permission and requesting permissions if you need to do so outside of the normal dialog UI. Think of it as a unified iOS permissions API that can provide some features that even Apple does not (such as detecting denied notification permissions).

switch PermissionScope().statusContacts() {
case .Unknown:
    // ask
    PermissionScope().requestContacts()
case .Unauthorized, .Disabled:
    // bummer
    return
case .Authorized:
    // thanks!
    return
}

calling request* methods directly

Normally PermissionScope is used to walk users through necessary permissions before they're allowed to do something in your app. Sometimes you may wish to instead call into the various request* permissions-seeking methods of PermissionScope directly, from your own UI.

To call these methods directly, you must first set the viewControllerForAlerts method to your current UIViewController, in case PermissionScope needs to present some alerts to the user for denied or disabled permissions:

let pscope = PermissionScope()
pscope.viewControllerForAlerts = self

You will probably also want to set the onAuthChange, onCancel, and onDisabledOrDenied closures, which are called at the appropriate times when the request* methods are finished, otherwise you won't know when the work has been completed.

pscope.onAuthChange = { (finished, results) in
	println("Request was finished with results \(results)")
	if results[0].status == .Authorized {
		println("They've authorized the use of notifications")
		UIApplication.sharedApplication().registerForRemoteNotifications()
	}
}
pscope.onCancel = { results in
	println("Request was cancelled with results \(results)")
}
pscope.onDisabledOrDenied = { results in
	println("Request was denied or disabled with results \(results)")
}

And then you might call it when the user toggles a switch:

@IBAction func notificationsChanged(sender: UISwitch) {
	if sender.on {
		// turn on notifications
		if PermissionScope().statusNotifications() == .Authorized {
			UIApplication.sharedApplication().registerForRemoteNotifications()
		} else {
			pscope.requestNotifications()
		}
	} else {
	    // turn off notifications
	}

If you're also using PermissionScope in the traditional manner, don't forget to set viewControllerForAlerts back to it's default, the instance of PermissionScope. The easiest way to do this is to set it explicitly before you call a request* method, and then reset it in your closures.

pscope.viewControllerForAlerts = pscope as UIViewController

PermissionScope registers user notification settings, not remote notifications

Users will get the prompt to enable notifications when using PermissionScope but it's up to you to watch for results in your app delegate's didRegisterUserNotificationSettings and then register for remote notifications independently. This won't alert the user again. You're still responsible for handling the shipment of user notification settings off to your push server.

extra requirements for permissions

location

You must set these Info.plist keys for location to work

Trickiest part of implementing location permissions? You must implement the proper key in your Info.plist file with a short description of how your app uses location info (shown in the system permissions dialog). Without this, trying to get location permissions will just silently fail. Software!

Use NSLocationAlwaysUsageDescription or NSLocationWhenInUseUsageDescription where appropriate for your app usage. You can specify which of these location permissions you wish to request with .LocationAlways or .LocationInUse while configuring PermissionScope.

bluetooth

The NSBluetoothPeripheralUsageDescription key in the Info.plist specifying a short description of why your app needs to act as a bluetooth peripheral in the background is optional.

However, enabling background-modes in the capabilities section and checking the acts as a bluetooth LE accessory checkbox is required.

known bugs

  • ITC app rejection with the following reason: "This app attempts to access privacy-sensitive data without a usage description". (#194)

Solution: TBD

  • When the user is taken to the Settings.app, if any of the app's permissions are changed (whilst the app was in the background), the app will crash. (#160)

Solution: None. Works as intended by the OS.

  • Link "Show me" does not work on denied a permission (#61)

Solution: Run your app without the debugger.

  • When using Carthage, the following error occurs: Module file was created by an older version of the compiler.

Solution: Use the --no-use-binaries flag (e.g: carthage update --no-use-binaries).

license

PermissionScope uses the MIT license. Please file an issue if you have any questions or if you'd like to share how you're using this tool.

permissionscope's People

Contributors

agapovone avatar aleksandrshoshiashvili avatar bre7 avatar chrisamanse avatar delebedev avatar egv avatar evermeer avatar felix-dumit avatar jjaffeux avatar ky1ejs avatar lfarah avatar mirceapasoi avatar nickoneill avatar ninoscript avatar pedrovereza avatar readmecritic avatar rynecheow avatar sammy-sc avatar seapy avatar stefanoventurin avatar tmspzz avatar vfuc avatar winzig avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

permissionscope's Issues

Notifications detected as Denied

How to reproduce:

  1. Launch example
  2. Tap Multi
  3. Tap Notifications (Don't select any option on the new alert)
  4. Reboot device

When the app is re-opened, the Notification's button will be titled "DENIED" instead of "ALLOW"

PermissionScope becomes unresponsive after asking for Notification permissions if already user rejected

Steps to reproduce:

Reset simulator
In example App, tap "Do a thing"
Tap "Allow Notification"
Tap "Don't allow"

Observe: Cannot interact with app

Addenda / separate issue:
After doing the above, kill the app and restart the App (Not simulator)
Tap "Do a thing" and "Allow Notification"

Observe: Cannot interact with app

Suggestion:
Store wether or not the user has ever been asked for notification permissions in NSUserDefaults and do not show notification permissions if they have been asked for before.

currentUserNotificationSettings: Unauthorized or Unknown

Could someone test the following scenarios and report currentUserNotificationSettings' value?:

  • First time the app is ever installed (Should be nil)
    • Returns optional with no types
  • Allow the app to send notifications, then print the value
    • Returns optional with accepted notification types
  • Deny the app to send notifications, then print the value
    • Returns optional with no types
  • Allow then deny the app to send notifications, then print the value
    • Returns optional with no types

currentUserNotificationSettings() returns an optional value now.

Add an option to reinitialize PermissionScope

As discussed in issue #58:

When PermissionScope is shown and an event was triggered that should cause a reevaluation of the permissions then currently the following workaround is required:
First execute the hide function, then wait for a second and call the show function.

I used this workaround in in EVCloudKitDao after the NSUbiquityIdentityDidChangeNotification was called the moment the PermissionScope screen was still open.

It would be nice if there was a method in PermissionScope to reinitialize everything.

How can we update LocationInUse to LocationAlways authorization?

As far as I can tell it works like this:

If the app has LocationInUse authorization and LocationAlways is requested, it will display the request normally ONCE and give users the ability to "upgrade". If a user hits cancel, the authorization status will continue to return with LocationInUse (no change) but no request will show if we request LocationAlways in code again.

Release 1.2

Some things we have to do for a stable API release:

  • Bluetooth support (#40)
  • CoreMotion support (#53)
  • Solidify API for non-UI requests (#39)
  • Fix Request methods (#125 and #130)

Removed:

  • Automated testing in place (#45)
  • Fix deallocated instance error (#30)

This list is not exhaustive, just a start.

Link "Show me" does not work on denied a permission

I am surprised that I did not find this as an issue already filed or something in the readme file about it.

Anyway, if I click in the example app on "Show me" for a denied permission, it does jump to the general settings entry for this app (which is just blank), and not to Settings/Privacy/Contacts/AppName (as an example). This makes the link somehow useless and confusing for the user I think.

If the link can't be done right, I guess hiding the "Show me" would be better than jumping to the wrong place.
Thanks,

Permissions API and refactoring

  • Individual PermissionConfig renamed to Permission
  • message should be a parameter of addPermission(:) instead of PermissionConfig
  • Move status/request methods to each PermissionConfig (Read #79 (comment))

Check NSLocationWhenInUseUsageDescription when asking for location permission

When doing an .addPermission for LocationWhileInUsePermission or LocationAlwaysPermission the key NSLocationAlwaysUsageDescription or NSLocationWhenInUseUsageDescription has to be setup in the info.plist.

You can test it with code like this:

let hasAlwaysKey = !NSBundle.mainBundle()
    .objectForInfoDictionaryKey("NSLocationAlwaysUsageDescription").isNil
let hasWhenInUseKey = !NSBundle.mainBundle()
    .objectForInfoDictionaryKey("NSLocationWhenInUseUsageDescription").isNil

And if it's false then execute something like:

assert(hasAlwaysKey, "To use location services in iOS 8+, your Info.plist must provide a value for either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription.")

Layout Issue

Hello and thanks for sharing this library.
There is an issue with the layout of the presented view.
To replicate just to do this.

  • open the app for the 1st time.
  • tap to open the popup.
  • tap outside of the popup to close it without pressing any of the buttons.
  • tap to open the popup again. all items are shown in the top left corner.

screenshot attached below:

Add framework archive to release

Incase you didn't know (I only found out today 😂), all you have to do is:

$ carthage build --no-skip-current

$ carthage archive PermissionScope

Then just upload the .zip in the project root.

This will save a bunch of time on CI server and the like.

Resizing depending on # of requests

How about resizing the view containing the requests depending on the # of requests?
So lets say I need 3 permissions then the view will have its complete size while for 1 it will automatically be smaller? (in sense of shorter on the y-axis)

Thanks :)

Message sent to deallocated instance

Cause of #27, the current solution is to set the PermissionScope() variable as a global one.

PermissionScope-example[98522:2797823] *** -[PermissionScope.PermissionScope gestureRecognizer:shouldReceiveTouch:]:
message sent to deallocated instance 0x7fafc1264690

It happens with any event, not only after cancelling (like #29).

Steps to reproduce:

  1. Open the example project
  2. Delete let singlePscope = PermissionScope() (Line 14)
  3. Paste the above code in viewDidLoad() (Line 20)
  4. Run
  5. Tap anywhere, to trigger the dismissal of the pscope dialog or any of the actions assigned to the permission buttons

Set a new Denied alert messsage for HealthKit

... To enable HealthKit related permissions, the user will have to go to Settings > Privacy > Health > App_Name to enable/disable each object since they don't appear in Settings > App_Name

Add CloudKit discoverability permissions.

For this you can use the following 2 Async calls:

let container = CKContainer.defaultContainer()
container.statusForApplicationPermission(CKApplicationPermissions.PermissionUserDiscoverability,
    completionHandler: { applicationPermissionStatus, error in
        // handle applicationPermissionStatus for statuses like
        // CKApplicationPermissionStatus.Granted, .Denied, .CouldNotComplete, .InitialState
})


let container = CKContainer.defaultContainer()
container.accountStatusWithCompletionHandler({status, error in
    switch status {
        case .Available, .Restricted:
        container.requestApplicationPermission(CKApplicationPermissions.PermissionUserDiscoverability,
            completionHandler: { applicationPermissionStatus, error in
            // handle applicationPermissionStatus for statuses like CKApplicationPermissionStatus.Granted, .Denied, .CouldNotComplete, .InitialState
        })
        case .CouldNotDetermine, .NoAccount:
            // Ask user to login to iCloud
    }
})

Edit: Formatted code.

Bug after cancelling Requests

I open the request view with a button. When I cancel the request by using the default feature by tapping the background and then press the button again it looks the following:

screen shot 2015-06-03 at 23 54 03

Prevent retain cycles

Should go through and clean up references to self: no self unless required ("explicit capture semantics") and add [unowned self] in the right places.

Notifications and actions

Easy and fast ways to do it:

  • Add an extra attribute to the PermissionConfig struct (AnyObject? 🚫 )
  • PermissionConfig class instead of struct (each class also would implement a protocol [status and request functions]). LocationConfig would have an extra attribute called "category: {UIMutableUserNotificationCategory}"

HealthPermission changes

As discussed in #17:

Your approach might be a good first shot. Some alternatives I was thinking about are:
• breaking the health items up by read / share (one config, shows as two items)
• breaking the health items up into individual data points to be requested (needs some other design for the buttons, they would still all be requested at once)
• providing HealthKit as a convenience API only, not allowing it to be requested in the dialog with other items (it already provides a granular interface, little need to do the same thing again)

Add spinner to button on async status calls

When calling the statusCloudKit method it could take a couple of seconds before the callback function is called. It would be nice if the button had an unknown state that maybe would include a spinner inside the button. Currently you will see an 'allow cloudkit' button which will suddenly change to 'allowed cloudkit' and changes color.

Extra init parameters alternatives for PermissionConfig

We need a better approach instead of adding extra parameters to PermissionConfig's initializer.
If associated values are used in enums, allValues can't be used since we don't have the parameters for them.

Associated values, extra init parameters or a better solution (some sort of plugin system?)

Issue with NotificationRequest

Hey,
First off: this is really cool and I am happy that I came across this, so thank you!!

Now, I am having an issue with requesting notifications. When I use this code:

let pscope = PermissionScope() pscope.addPermission(PermissionConfig(type: .Notifications, demands: .Optional, message: "We use this to send you\r\nspam and love notes", notificationCategories: Set([somePreviouslyConfiguredCategory]))) pscope.show()

...what do I have to insert for somePreviouslyConfiguredCategory? It says it is an extra argument so I removed everything until the last quote. When I do so it runs and works until pressing the Enable Notifications.

What can I do?
Thanks again and keep up the great work :)

Set up tests for supported permissions

I haven't played with the UI testing in Xcode 7 yet but I expect we'll need lots of UI tests since permissions require user input. Hopefully we can automate this.

My preference would be something like this:

  • Run a set of UI tests that create a few dialogs with each of the permissions
  • Verify all permissions can be denied and detected as such
  • Verify all permissions can be approved and detected as such
  • Any tests that require devices (bluetooth, for one) can be hooked up to a device and run similarly (denied, delete/reset and then approved)

Resetting the simulator gives a fresh slate for permissions, unlike just deleting an app from a device. We'll have to figure out how to balance this with permissions that require a device.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.