Skip to content

Commit ee92f87

Browse files
authored
Merge pull request #43 from jevonmao/develop
Implemented #40-prevent-dismiss, merge into 'main' for v1.3.0
2 parents fbcf29c + 6a59372 commit ee92f87

33 files changed

+476
-86
lines changed

Package.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@ let package = Package(
1515
],
1616
dependencies: [
1717
// Dependencies declare other packages that this package depends on.
18-
.package(name: "SnapshotTesting", url: "https://github.com/pointfreeco/swift-snapshot-testing.git", "1.0.0"..<"2.0.0")
18+
.package(name: "SnapshotTesting", url: "https://github.com/pointfreeco/swift-snapshot-testing.git", "1.0.0"..<"2.0.0"),
19+
.package(name: "Introspect", url: "https://github.com/siteline/SwiftUI-Introspect", "0.0.0"..<"1.0.0")
1920
],
2021
targets: [
2122
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
2223
// Targets can depend on other targets in this package, and on products in packages this package depends on.
2324
.target(
2425
name: "PermissionsSwiftUI",
25-
dependencies: [],
26+
dependencies: ["Introspect"],
2627
exclude: ["../../Tests/PermissionsSwiftUITests/__Snapshots__"]
2728
),
2829
.testTarget(

README.md

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
</span>
66

77
# PermissionsSwiftUI: A SwiftUI package to handle permissions
8-
<img src="https://img.shields.io/github/workflow/status/jevonmao/PermissionsSwiftUI/Swift?label=CI%20Build"> <img src="https://img.shields.io/github/contributors/jevonmao/PermissionsSwiftUI"> <img src="https://img.shields.io/badge/License-MIT-blue.svg"> <img src="https://img.shields.io/github/issues/jevonmao/PermissionsSwiftUI?color=orange"> <img src="https://img.shields.io/github/commit-activity/w/jevonmao/PermissionsSwiftUI?color=yellowgreen&logoColor=yellowgreen"> [![Codacy Badge](https://app.codacy.com/project/badge/Grade/6fe1a84c136b4a99823e7d71a8d08625)](https://www.codacy.com/gh/jevonmao/PermissionsSwiftUI/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=jevonmao/PermissionsSwiftUI&amp;utm_campaign=Badge_Grade)
8+
<img src="https://img.shields.io/github/workflow/status/jevonmao/PermissionsSwiftUI/Swift?label=CI%20Build"> <img src="https://img.shields.io/github/contributors/jevonmao/PermissionsSwiftUI"> <img src="https://img.shields.io/badge/License-MIT-blue.svg"> <img src="https://img.shields.io/github/issues/jevonmao/PermissionsSwiftUI?color=orange"> <img src="https://img.shields.io/github/commit-activity/w/jevonmao/PermissionsSwiftUI?color=yellowgreen&logoColor=yellowgreen">
99

1010
`PermissionsSwiftUI` displays and handles permissions in SwiftUI. It is largely inspired by [SPPermissions](https://github.com/varabeis/SPPermissions).
1111
The UI is highly customizable and resembles an **Apple style**. If you like the project, don't forget to `star ★` and follow me on GitHub. <br />
@@ -18,13 +18,20 @@ The UI is highly customizable and resembles an **Apple style**. If you like the
1818
## Navigation
1919
- [Installation](#installation)
2020
- [Quickstart](#quickstart)
21+
<details>
22+
<summary>Usage</summary>
23+
2124
- [Usage](#usage)
2225
- [Customize Permission Texts](#customize-permission-texts)
2326
- [Customize header texts](#customize-header-texts)
2427
- [`onAppear` and `onDisappear` Override](#onappear-and-ondisappear-override)
2528
- [Auto Check Authorization](#auto-check-authorization)
2629
- [Auto Dismiss](#auto-dismiss)
2730
- [Customize Colors](#customize-colors)
31+
- [Restrict Dismissal](#restrict-dismissal)
32+
- [Configuring Health Permissions](#configuring-health-permissions)
33+
</details>
34+
2835
- [Supported Permissions](#supported-permissions)
2936
- [Additional Information](#additional-information)
3037
- [Acknowledgement](#acknowledgement)
@@ -187,6 +194,52 @@ To unleash the full customization of all button colors under all states, you nee
187194
backgroundColor: Color)))
188195
```
189196
For more information regarding the above method, reference the [official documentation](https://jevonmao.github.io/PermissionsSwiftUI/Structs/AllButtonColors.html).
197+
198+
### Restrict Dismissal
199+
PermissionsSwiftUI will by default, prevent the user from dismissing the modal and alert. This restrict dismissal behavior and be overriden by the `var restrictModalDismissal: Bool` or `var restrictAlertDismissal: Bool` properties.
200+
To disable the default restrict dismiss behavior:
201+
```Swift
202+
.JMModal(showModal: $show, for permissions: [.camera], restrictDismissal: false)
203+
```
204+
You can also configure with the model:
205+
```Swift
206+
let model: PermissionStore = {
207+
var model = PermissionStore()
208+
model.permissions = [.camera]
209+
model.restrictModalDismissal = false
210+
model.restrictAlertDismissal = false
211+
return model
212+
}
213+
......
214+
215+
.JMModal(showModal: $showModal, forModel: model)
216+
```
217+
### Configuring Health Permissions
218+
Unlike all the other permissions, the configuration for health permission is a little different. Because Apple require developers to explictly set read and write types, PermissionsSwiftUI greatly simplifies the process.
219+
#### `HKAccess`
220+
The structure HKAccess is required when initalizing health permission’s enum associated values. It encapsulates the read and write type permissions for the health permission.
221+
222+
To set read and write health types (`activeEnergyBurned` is used as example here):
223+
```Swift
224+
let healthTypes = Set([HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!])
225+
.JMModal(showModal: $show, for: [.health(categories: .init(readAndWrite: healthTypes))])
226+
227+
//Same exact syntax for JMAlert styles
228+
.JMAlert(showModal: $show, for: [.health(categories: .init(readAndWrite: healthTypes))])
229+
230+
```
231+
To set read or write individually:
232+
```Swift
233+
let readTypes = Set([HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!])
234+
let writeTypes = Set([HKSampleType.quantityType(forIdentifier: .appleStandTime)!])
235+
.JMModal(showModal: $showModal, for: [.health(categories: .init(read: readTypes, write: writeTypes))])
236+
```
237+
You may also set only read or write type:
238+
```Swift
239+
let readTypes = Set([HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!])
240+
.JMModal(showModal: $showModal, for: [.health(categories: .init(read: readTypes))])
241+
242+
```
190243
## Supported Permissions
191244
Here is a list of all permissions PermissionsSwiftUI already supports support(health not in image but is supported). Yup, even the newest `tracking` permission for iOS 14 so you can stay on top of your game. All permissions in PermissionsSwiftUI come with a default name, description, and a stunning Apple native SF Symbols icon.
192245
<br /> <br /> <br />

Sources/PermissionsSwiftUI/Components/Alert-style/AlertMainView.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ struct AlertMainView: View {
1414
var shouldShowPermission:Bool{
1515
if PermissionStore.shared.autoCheckAlertAuth{
1616
if showAlert.wrappedValue &&
17-
!PermissionStore.shared.permissionsToAsk.isEmpty {
17+
!PermissionStore.shared.undeterminedPermissions.isEmpty {
1818
return true
1919
}
2020
else {
@@ -35,23 +35,23 @@ struct AlertMainView: View {
3535
}
3636
var body: some View {
3737
ZStack{
38+
let insertTransition = AnyTransition.opacity.combined(with: .scale(scale: 1.1)).animation(Animation.default.speed(1.6))
39+
let removalTransiton = AnyTransition.opacity.combined(with: .scale(scale: 0.9)).animation(Animation.default.speed(1.8))
3840
bodyView
39-
40-
if shouldShowPermission{
41+
if shouldShowPermission {
4142
Group{
4243
Blur(style: .systemUltraThinMaterialDark)
43-
.edgesIgnoringSafeArea(.all)
44-
.transition(AnyTransition.opacity.animation(Animation.default.speed(1.8)))
44+
.transition(AnyTransition.opacity.animation(Animation.default.speed(1.6)))
4545
AlertView(showAlert:showAlert)
46-
.transition(AnyTransition.opacity.combined(with: .scale(scale: 0.92)).animation(Animation.default.speed(1.5)))
4746
.onAppear(perform: PermissionStore.shared.onAppear)
4847
.onDisappear(perform: PermissionStore.shared.onDisappear)
4948
}
49+
.transition(.asymmetric(insertion: insertTransition, removal: removalTransiton))
50+
.edgesIgnoringSafeArea(.all)
51+
.animation(.default)
5052

5153
}
5254
}
53-
.edgesIgnoringSafeArea(.all)
54-
.animation(.default)
5555

5656

5757
}

Sources/PermissionsSwiftUI/Components/Alert-style/AlertView.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ struct AlertView: View {
1616
screenSize.width < 400 ? 20-(1000-screenSize.width)/120 : 20
1717
}
1818
var body: some View {
19+
let store = PermissionStore.shared
1920
VStack{
20-
HeaderText(exitButtonAction: {showAlert = false}, isAlert: true)
21+
HeaderView(exitButtonAction: {showAlert = store.isAlertDismissalRestricted}, isAlert: true)
2122
.padding(.bottom, paddingSize/1.5)
2223
PermissionSection(showModal: $showAlert, isAlert:true)
2324

Sources/PermissionsSwiftUI/Components/Modal-style/MainView.swift

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,36 @@
66
//
77

88
import SwiftUI
9+
import Introspect
910

1011
struct MainView: View {
12+
@State var isModalNotShown = true
1113
var showModal: Binding<Bool>
1214
var bodyView: AnyView
13-
var permissionsToAsk: [PermissionType]
14-
var shouldShowPermission:Binding<Bool>{
15+
var _permissionsToAsk: [PermissionType]?
16+
var permissionsToAsk: [PermissionType] {
17+
guard _permissionsToAsk == nil else {
18+
return _permissionsToAsk!
19+
}
20+
return PermissionStore.shared.undeterminedPermissions
21+
}
22+
var shouldShowPermission: Binding<Bool>{
1523
Binding(get: {
1624
let store = PermissionStore.shared
17-
if store.autoCheckModalAuth{
25+
if store.autoCheckModalAuth && isModalNotShown {
1826
return !permissionsToAsk.isEmpty
1927
}
2028
return true
2129
}, set: {_ in})
2230
}
31+
@ObservedObject var store = PermissionStore.shared
32+
2333
init(for bodyView: AnyView,
2434
show showModal: Binding<Bool>,
25-
permissionsToAsk: [PermissionType]=PermissionStore.shared.permissionsToAsk) {
35+
permissionsToAsk: [PermissionType]?=nil) {
2636
self.bodyView = bodyView
2737
self.showModal = showModal
28-
self.permissionsToAsk = permissionsToAsk
38+
self._permissionsToAsk = permissionsToAsk
2939
}
3040

3141
var body: some View {
@@ -34,9 +44,16 @@ struct MainView: View {
3444
ModalView(showModal: showModal)
3545
.onAppear(perform: PermissionStore.shared.onAppear)
3646
.onDisappear(perform:PermissionStore.shared.onDisappear)
37-
.onDisappear{showModal.wrappedValue = false}
47+
.onAppear{isModalNotShown=false}
48+
.onDisappear{showModal.wrappedValue = false; isModalNotShown=true}
49+
.introspectViewController{
50+
if store.restrictModalDismissal {
51+
$0.isModalInPresentation = store.isModalDismissalRestricted
52+
}
53+
}
3854

3955
})
56+
4057

4158
}
4259
//if DEBUG to ensure these functions are never used in production. They are for unit testing only.

Sources/PermissionsSwiftUI/Components/Modal-style/ModalView.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import SwiftUI
1010
struct ModalView: View {
1111
@Binding var showModal: Bool
1212
var mainText:PermissionStore.MainTexts{PermissionStore.shared.mainTexts}
13+
1314
var body: some View {
1415
ScrollView {
1516
VStack {
16-
HeaderText(exitButtonAction: {showModal=false})
17+
let store = PermissionStore.shared
18+
HeaderView(exitButtonAction: {showModal = store.isModalDismissalRestricted})
1719

1820
PermissionSection(showModal:$showModal, isAlert:false)
1921
.background(Color(.systemBackground))

Sources/PermissionsSwiftUI/Components/Shared/HeaderText.swift renamed to Sources/PermissionsSwiftUI/Components/Shared/HeaderView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import SwiftUI
99

10-
struct HeaderText: View {
10+
struct HeaderView: View {
1111
var exitButtonAction:() -> Void
1212
//HeaderText component have slightly different UI for alert and modal.
1313
var isAlert:Bool = false

Sources/PermissionsSwiftUI/Components/Shared/PermissionSection.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,18 @@ struct PermissionSection: View {
1111
@Environment(\.colorScheme) var colorScheme
1212
@Binding var showModal:Bool
1313
var isAlert:Bool
14-
var permissions:[PermissionType]{
14+
var permissions:[PermissionType] {
1515
let store = PermissionStore.shared
1616
if isAlert{
1717
if store.autoCheckAlertAuth{
18-
return store.permissionsToAsk
18+
return store.undeterminedPermissions
1919
}
2020
else {
2121
return store.permissions
2222
}
2323
}
2424
if store.autoCheckModalAuth{
25-
return store.permissionsToAsk
25+
return store.undeterminedPermissions
2626
}
2727
else {
2828
return store.permissions
@@ -38,6 +38,9 @@ struct PermissionSection: View {
3838
}
3939
}
4040
}
41+
// .onAppear{
42+
// PermissionStore
43+
// }
4144

4245
}
4346
}
@@ -109,6 +112,8 @@ struct PermissionSectionCell: View {
109112

110113
Spacer()
111114
if isAlert {
115+
//Call requestPermission (enum function) to make request to Apple API
116+
//The handleButtonState function will be executed based on result of request
112117
AllowButtonSection(action: {
113118
permission.requestPermission(isPermissionGranted: {handleButtonState(for: $0)})
114119
}, allowButtonStatus: $allowButtonStatus)
@@ -128,6 +133,7 @@ struct PermissionSectionCell: View {
128133

129134
func handleButtonState(for authorized:Bool){
130135
var currentPermission = permission.currentPermission
136+
currentPermission.interacted = true
131137
if authorized {
132138
allowButtonStatus = .allowed
133139
currentPermission.authorized = true

Sources/PermissionsSwiftUI/Model/Mocks/MockLocationManager.swift

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ protocol LocationManager {
1616
}
1717

1818

19-
extension CLLocationManager:LocationManager{
19+
extension CLLocationManager: LocationManager{
2020
func authorizationStatus() -> CLAuthorizationStatus {
2121
CLLocationManager.authorizationStatus()
2222
}
@@ -27,31 +27,23 @@ struct MockCLLocationManager:LocationManager{
2727
weak var delegate: CLLocationManagerDelegate?
2828

2929
private static var status:CLAuthorizationStatus = .notDetermined
30+
var whenInUseRequestOverride: CLAuthorizationStatus = .authorizedWhenInUse
31+
var alwaysUseRequestOverride: CLAuthorizationStatus = .authorizedAlways
32+
3033
func authorizationStatus() -> CLAuthorizationStatus {
3134
MockCLLocationManager.status
3235
}
3336

3437
func requestWhenInUseAuthorization() {
35-
MockCLLocationManager.status = .authorizedWhenInUse
38+
MockCLLocationManager.status = whenInUseRequestOverride
3639
}
3740

3841
func requestAlwaysAuthorization() {
39-
MockCLLocationManager.status = .authorizedAlways
42+
MockCLLocationManager.status = alwaysUseRequestOverride
4043

4144
}
4245

4346

4447
}
45-
//class MockCLLocationManager:CLLocationManager{
46-
// var authStatus:CLAuthorizationStatus = .notDetermined
47-
// func authorizationStatus() -> CLAuthorizationStatus{
48-
// return authStatus
49-
// }
50-
// override func requestAlwaysAuthorization() {
51-
// self.authStatus = .authorizedAlways
52-
// }
53-
// override func requestWhenInUseAuthorization() {
54-
// self.authStatus = .authorizedWhenInUse
55-
// }
56-
//}
48+
5749

Sources/PermissionsSwiftUI/Model/PermissionManagers/JMLocationPermissionManager.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ class JMLocationPermissionManager: NSObject, CLLocationManagerDelegate, Permissi
4141
}
4242

4343
if let completionHandler = completionHandler {
44-
let status = CLLocationManager.authorizationStatus()
44+
let status = locationManager.authorizationStatus()
4545
completionHandler(status == .authorizedAlways || status == .authorizedWhenInUse ? true : false)
4646

4747
}
4848
}
4949
//Used to request in use permission (1 of 2 types of iOS location permission)
5050
func requestPermission(_ completionHandler: @escaping (Bool) -> Void) {
5151
self.completionHandler = completionHandler
52-
let status = CLLocationManager.authorizationStatus()
52+
let status = locationManager.authorizationStatus()
5353

5454
switch status {
5555
case .notDetermined:

0 commit comments

Comments
 (0)