Skip to content
This repository has been archived by the owner on Feb 27, 2019. It is now read-only.

Added Disabled status, parameter to set tap-to-disappear, localization and assert fixes #12

Merged
merged 5 commits into from
Apr 27, 2015
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 89 additions & 71 deletions PermissionScope/PermissionScope.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,22 @@ public enum PermissionType: String {
case Microphone = "Microphone"
case Camera = "Camera"
case Photos = "Photos"

var prettyName: String {
switch self {
case .LocationAlways, .LocationInUse:
return "Location"
default:
return self.rawValue
}
}
}

public enum PermissionStatus: String {
case Authorized = "Authorized"
case Unauthorized = "Unauthorized"
case Unknown = "Unknown"
case Disabled = "Disabled" // System-level
}

public enum PermissionDemands: String {
Expand All @@ -39,9 +49,9 @@ public struct PermissionConfig {
let message: String

let notificationCategories: Set<UIUserNotificationCategory>?
public init(type: PermissionType, demands: PermissionDemands, message: String, notificationCategories: Set<UIUserNotificationCategory>? = nil) {
assert(notificationCategories != .None && type != .Notifications,
"Only .Notifications Permission can have notificationCategories not nil")

public init(type: PermissionType, demands: PermissionDemands, message: String, notificationCategories: Set<UIUserNotificationCategory>? = .None) {
assert((notificationCategories != .None && type == .Notifications) || (notificationCategories == .None && type != .Notifications), "Only .Notifications Permission can have notificationCategories not nil")

self.type = type
self.demands = demands
Expand Down Expand Up @@ -69,6 +79,11 @@ extension UIColor {
return self
}
}
extension String {
var localized: String {
return NSLocalizedString(self, comment: "")
}
}

public class PermissionScope: UIViewController, CLLocationManagerDelegate, UIGestureRecognizerDelegate {
// constants
Expand Down Expand Up @@ -103,13 +118,12 @@ public class PermissionScope: UIViewController, CLLocationManagerDelegate, UIGes
let permissionsArray = getResultsForConfig()
return permissionsArray.filter { $0.status != .Authorized }.isEmpty
}

var requiredAuthorized: Bool {
let permissionsArray = getResultsForConfig()
return permissionsArray.filter { $0.status != .Authorized && $0.demands == .Required }.isEmpty
}

public init() {
public init(enableSkipOnBackgroundTap: Bool) {
super.init(nibName: nil, bundle: nil)

// Set up main view
Expand All @@ -120,9 +134,11 @@ public class PermissionScope: UIViewController, CLLocationManagerDelegate, UIGes
// Base View
baseView.frame = view.frame
baseView.addSubview(contentView)
let tap = UITapGestureRecognizer(target: self, action: Selector("cancel"))
tap.delegate = self
baseView.addGestureRecognizer(tap)
if enableSkipOnBackgroundTap {
let tap = UITapGestureRecognizer(target: self, action: Selector("cancel"))
tap.delegate = self
baseView.addGestureRecognizer(tap)
}
// Content View
contentView.backgroundColor = UIColor.whiteColor()
contentView.layer.cornerRadius = 10
Expand Down Expand Up @@ -156,6 +172,10 @@ public class PermissionScope: UIViewController, CLLocationManagerDelegate, UIGes

contentView.addSubview(finalizeButton)
}

public convenience init() {
self.init(enableSkipOnBackgroundTap: true)
}

required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
Expand Down Expand Up @@ -197,66 +217,19 @@ public class PermissionScope: UIViewController, CLLocationManagerDelegate, UIGes
button.frame.offset(dx: -contentView.frame.origin.x, dy: -contentView.frame.origin.y)
button.frame.offset(dx: 0, dy: -80 + CGFloat(index * baseOffset))

// TODO: New func to setUnauthorizedStyle ? new tintColor also ?
// Question: Use (XXX, YYY) tuple instead of case XXX: if status() = YYY ? => Each Permission should know how to get it's status
let type = configuredPermissions[index].type
switch type {
case .LocationAlways:
if statusLocationAlways() == .Authorized {
setButtonAuthorizedStyle(button)
button.setTitle("Got Location".uppercaseString, forState: UIControlState.Normal)
} else if statusNotifications() == .Unauthorized {
setButtonUnauthorizedStyle(button)
button.setTitle("Denied Location".uppercaseString, forState: UIControlState.Normal)
}
case .LocationInUse:
if statusLocationInUse() == .Authorized {
setButtonAuthorizedStyle(button)
button.setTitle("Got Location".uppercaseString, forState: UIControlState.Normal)
} else if statusLocationInUse() == .Unauthorized {
setButtonUnauthorizedStyle(button)
button.setTitle("Denied Location".uppercaseString, forState: UIControlState.Normal)
}
case .Contacts:
if statusContacts() == .Authorized {
setButtonAuthorizedStyle(button)
button.setTitle("Allowed Contacts".uppercaseString, forState: UIControlState.Normal)
} else if statusContacts() == .Unauthorized {
setButtonUnauthorizedStyle(button)
button.setTitle("Denied \(type.rawValue)".uppercaseString, forState: UIControlState.Normal)
}
case .Notifications:
if statusNotifications() == .Authorized {
setButtonAuthorizedStyle(button)
button.setTitle("Allowed Notifications".uppercaseString, forState: UIControlState.Normal)
} else if statusNotifications() == .Unauthorized {
setButtonUnauthorizedStyle(button)
button.setTitle("Denied \(type.rawValue)".uppercaseString, forState: UIControlState.Normal)
}
case .Microphone:
if statusMicrophone() == .Authorized {
setButtonAuthorizedStyle(button)
button.setTitle("Allowed \(type.rawValue)".uppercaseString, forState: UIControlState.Normal)
} else if statusMicrophone() == .Unauthorized {
setButtonUnauthorizedStyle(button)
button.setTitle("Denied \(type.rawValue)".uppercaseString, forState: UIControlState.Normal)
}
case .Camera:
if statusCamera() == .Authorized {
setButtonAuthorizedStyle(button)
button.setTitle("Allowed \(type.rawValue)".uppercaseString, forState: UIControlState.Normal)
} else if statusCamera() == .Unauthorized {
setButtonUnauthorizedStyle(button)
button.setTitle("Denied \(type.rawValue)".uppercaseString, forState: UIControlState.Normal)
}
case .Photos:
if statusPhotos() == .Authorized {
setButtonAuthorizedStyle(button)
button.setTitle("Allowed \(type.rawValue)".uppercaseString, forState: UIControlState.Normal)
} else if statusPhotos() == .Unauthorized {
setButtonUnauthorizedStyle(button)
button.setTitle("Denied \(type.rawValue)".uppercaseString, forState: UIControlState.Normal)
}

let currentStatus = statusForPermission(type)
let prettyName = type.prettyName
if currentStatus == .Authorized {
setButtonAuthorizedStyle(button)
button.setTitle("Allowed \(prettyName)".localized.uppercaseString, forState: .Normal)
} else if currentStatus == .Unauthorized {
setButtonUnauthorizedStyle(button)
button.setTitle("Denied \(prettyName)".localized.uppercaseString, forState: .Normal)
} else if currentStatus == .Disabled {
// setButtonDisabledStyle(button)
button.setTitle("\(prettyName) Disabled".localized.uppercaseString, forState: .Normal)
}

let label = permissionLabels[index]
Expand All @@ -273,7 +246,7 @@ public class PermissionScope: UIViewController, CLLocationManagerDelegate, UIGes
public func addPermission(config: PermissionConfig) {
assert(!config.message.isEmpty, "Including a message about your permission usage is helpful")
assert(configuredPermissions.count < 3, "Ask for three or fewer permissions at a time")
assert(!configuredPermissions.filter { $0.type == config.type }.isEmpty, "Permission for \(config.type.rawValue) already set")
assert(configuredPermissions.filter { $0.type == config.type }.isEmpty, "Permission for \(config.type.rawValue) already set")
if config.type == .Notifications && config.demands == .Required {
assertionFailure("We cannot tell if notifications have been denied so it's unwise to mark this as required")
}
Expand All @@ -293,9 +266,9 @@ public class PermissionScope: UIViewController, CLLocationManagerDelegate, UIGes
// this is a bit of a mess, eh?
switch type {
case .LocationAlways, .LocationInUse:
button.setTitle("Enable Location".uppercaseString, forState: UIControlState.Normal)
button.setTitle("Enable \(type.prettyName)".localized.uppercaseString, forState: UIControlState.Normal)
default:
button.setTitle("Allow \(type.rawValue)".uppercaseString, forState: UIControlState.Normal)
button.setTitle("Allow \(type.rawValue)".localized.uppercaseString, forState: UIControlState.Normal)
}

button.addTarget(self, action: Selector("request\(type.rawValue)"), forControlEvents: UIControlEvents.TouchUpInside)
Expand Down Expand Up @@ -331,6 +304,10 @@ public class PermissionScope: UIViewController, CLLocationManagerDelegate, UIGes
// MARK: dealing with system permissions

public func statusLocationAlways() -> PermissionStatus {
if !CLLocationManager.locationServicesEnabled() {
return .Disabled
}

let status = CLLocationManager.authorizationStatus()
switch status {
case .AuthorizedAlways:
Expand All @@ -349,12 +326,18 @@ public class PermissionScope: UIViewController, CLLocationManagerDelegate, UIGes
locationManager.requestAlwaysAuthorization()
case .Unauthorized:
self.showDeniedAlert(.LocationAlways)
case .Disabled:
self.showDisabledAlert(.LocationInUse)
default:
break
}
}

public func statusLocationInUse() -> PermissionStatus {
if !CLLocationManager.locationServicesEnabled() {
return .Disabled
}

let status = CLLocationManager.authorizationStatus()
// if you're already "always" authorized, then you don't need in use
// but the user can still demote you! So I still use them separately.
Expand All @@ -375,6 +358,8 @@ public class PermissionScope: UIViewController, CLLocationManagerDelegate, UIGes
locationManager.requestWhenInUseAuthorization()
case .Unauthorized:
self.showDeniedAlert(.LocationInUse)
case .Disabled:
self.showDisabledAlert(.LocationInUse)
default:
break
}
Expand Down Expand Up @@ -665,7 +650,7 @@ public class PermissionScope: UIViewController, CLLocationManagerDelegate, UIGes

func showDeniedAlert(permission: PermissionType) {
var alert = UIAlertController(title: "Permission for \(permission.rawValue) was denied.",
message: "Please enable access to \(permission.rawValue) in Settings",
message: "Please enable access to \(permission.rawValue) in the App's Settings",
preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "OK",
style: .Cancel,
Expand All @@ -680,6 +665,17 @@ public class PermissionScope: UIViewController, CLLocationManagerDelegate, UIGes
animated: true, completion: nil)
}

func showDisabledAlert(permission: PermissionType) {
var alert = UIAlertController(title: "\(permission.rawValue) is currently disabled.",
message: "Please enable access to \(permission.rawValue) in Settings",
preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "OK",
style: .Cancel,
handler: nil))
self.presentViewController(alert,
animated: true, completion: nil)
}

// MARK: gesture delegate

public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
Expand All @@ -698,4 +694,26 @@ public class PermissionScope: UIViewController, CLLocationManagerDelegate, UIGes

detectAndCallback()
}

// MARK: Helpers

func statusForPermission(type: PermissionType) -> PermissionStatus {
// :(
switch type {
case .LocationAlways:
return statusLocationAlways()
case .LocationInUse:
return statusLocationInUse()
case .Contacts:
return statusContacts()
case .Notifications:
return statusNotifications()
case .Microphone:
return statusMicrophone()
case .Camera:
return statusCamera()
case .Photos:
return statusPhotos()
}
}
}