├── .gitignore
├── Example
├── AppDelegate.swift
├── Base.lproj
│ ├── LaunchScreen.xib
│ └── Main.storyboard
├── Example-Bridging-Header.h
├── Images.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Info.plist
├── TableCell.swift
├── TableViewController.swift
└── ViewController.swift
├── LICENSE
├── MRLocalNotificationFacade.podspec
├── MRLocalNotificationFacade.xcodeproj
├── project.pbxproj
└── project.xcworkspace
│ └── contents.xcworkspacedata
├── MRLocalNotificationFacade
├── MRLocalNotificationFacade.h
└── MRLocalNotificationFacade.m
├── README.md
├── Tests
└── Info.plist
└── notification.jpg
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | build/
4 | *.pbxuser
5 | !default.pbxuser
6 | *.mode1v3
7 | !default.mode1v3
8 | *.mode2v3
9 | !default.mode2v3
10 | *.perspectivev3
11 | !default.perspectivev3
12 | xcuserdata
13 | *.xccheckout
14 | *.moved-aside
15 | DerivedData
16 | *.hmap
17 | *.ipa
18 | *.xcuserstate
19 |
20 | # CocoaPods
21 | #
22 | # We recommend against adding the Pods directory to your .gitignore. However
23 | # you should judge for yourself, the pros and cons are mentioned at:
24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
25 | #
26 | #Pods/
27 |
--------------------------------------------------------------------------------
/Example/AppDelegate.swift:
--------------------------------------------------------------------------------
1 |
2 | import UIKit
3 |
4 | @UIApplicationMain
5 | class AppDelegate: UIResponder, UIApplicationDelegate {
6 |
7 | var window: UIWindow?
8 |
9 | let notificationFacade = MRLocalNotificationFacade.defaultInstance()
10 |
11 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
12 | let notification = notificationFacade.getNotificationFromLaunchOptions(launchOptions)
13 | notificationFacade.handleDidReceiveLocalNotification(notification)
14 | return true
15 | }
16 |
17 | func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
18 | NSNotificationCenter.defaultCenter().postNotificationName("reloadData", object: self)
19 | notificationFacade.handleDidReceiveLocalNotification(notification)
20 | }
21 |
22 | func application(application: UIApplication, handleActionWithIdentifier identifier: String?, forLocalNotification notification: UILocalNotification, completionHandler: () -> Void) {
23 | notificationFacade.handleActionWithIdentifier(identifier, forLocalNotification: notification, completionHandler: completionHandler)
24 | }
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/Example/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/Example/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
--------------------------------------------------------------------------------
/Example/Example-Bridging-Header.h:
--------------------------------------------------------------------------------
1 |
2 | #import "MRLocalNotificationFacade.h"
3 |
--------------------------------------------------------------------------------
/Example/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/Example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Example/TableCell.swift:
--------------------------------------------------------------------------------
1 |
2 | import UIKit
3 |
4 | class TableCell: UITableViewCell {
5 |
6 | @IBOutlet weak var alertTitleTextfield: UITextField!
7 | @IBOutlet weak var datePicker: UIDatePicker!
8 |
9 | func setUp(notification: UILocalNotification) {
10 | if (notification.fireDate!.timeIntervalSinceNow > 0) {
11 | alertTitleTextfield.alpha = 1
12 | datePicker.alpha = 1
13 | } else {
14 | alertTitleTextfield.alpha = 0.5
15 | datePicker.alpha = 0.5
16 | }
17 | alertTitleTextfield.text = notification.alertBody
18 | datePicker.date = notification.fireDate!
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/Example/TableViewController.swift:
--------------------------------------------------------------------------------
1 |
2 | import UIKit
3 |
4 | class TableViewController: UITableViewController, UITextFieldDelegate {
5 |
6 | @IBOutlet var titleTextField: UITextField!
7 | @IBOutlet var bodyTextField: UITextField!
8 | @IBOutlet var actionTextField: UITextField!
9 | @IBOutlet var datePicker: UIDatePicker!
10 | @IBOutlet var repeatSwitch: UISwitch!
11 | @IBOutlet var soundSwitch: UISwitch!
12 | @IBOutlet var badgeTextField: UITextField!
13 | @IBOutlet var defaultContextSwitch: UISwitch!
14 | @IBOutlet var minimalContextSwitch: UISwitch!
15 |
16 | let notificationFacade = MRLocalNotificationFacade.defaultInstance()
17 |
18 | @IBAction func createNotificationAction(sender: AnyObject) {
19 | var category: String?
20 | if (defaultContextSwitch.on) {
21 | if (minimalContextSwitch.on) {
22 | category = "all"
23 | } else {
24 | category = "default"
25 | }
26 | } else {
27 | if (minimalContextSwitch.on) {
28 | category = "minimal"
29 | } else {
30 | category = nil
31 | }
32 | }
33 | let notification = notificationFacade.buildNotificationWithDate(datePicker.date, timeZone: false, category: category, userInfo: nil)
34 | notificationFacade.customizeNotificationAlert(notification, title: titleTextField.text, body: bodyTextField.text, action: actionTextField.text, launchImage: nil)
35 | if (repeatSwitch.on) {
36 | notificationFacade.customizeNotificationRepeat(notification, interval: NSCalendarUnit.Day)
37 | }
38 | if let badgeString = badgeTextField.text {
39 | if let badge = Int(badgeString) {
40 | notificationFacade.customizeNotification(notification, appIconBadge: badge, sound: soundSwitch.on)
41 | }
42 | }
43 | // show error alert if needed
44 | do {
45 | try notificationFacade.canScheduleNotification(notification, withRecovery: false)
46 | }
47 | catch {
48 | let alert = notificationFacade.buildAlertControlForError(error as NSError)
49 | notificationFacade.showAlertController(alert)
50 | }
51 | // schedule notification if possible
52 | do {
53 | try notificationFacade.scheduleNotification(notification)
54 | navigationController?.popViewControllerAnimated(true)
55 | }
56 | catch {
57 |
58 | }
59 | }
60 |
61 | func textFieldShouldReturn(textField: UITextField) -> Bool {
62 | textField.resignFirstResponder()
63 | return false
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/Example/ViewController.swift:
--------------------------------------------------------------------------------
1 |
2 | import UIKit
3 |
4 | class ViewController: UIViewController, /*UITableViewDataSource,*/ UITableViewDelegate {
5 |
6 | @IBOutlet var tableView: UITableView!
7 | @IBOutlet var toolbar: UIToolbar!
8 | @IBOutlet var countItem: UIBarButtonItem!
9 |
10 | let textCellIdentifier = "TableCell";
11 | let notificationFacade = MRLocalNotificationFacade.defaultInstance()
12 |
13 | var selectedIndex: NSIndexPath?
14 |
15 | @IBAction func reloadData() {
16 | tableView.reloadData()
17 | }
18 |
19 | @IBAction func registerAction(sender: AnyObject) {
20 | let min = [
21 | notificationFacade.buildAction("minimal-background", title: "background", destructive: true, backgroundMode: false, authentication: true),
22 | notificationFacade.buildAction("minimal-foreground", title: "foreground", destructive: false, backgroundMode: false, authentication: true)
23 | ]
24 | let def = [
25 | notificationFacade.buildAction("default-background-0", title: "background", destructive: true, backgroundMode: false, authentication: true),
26 | notificationFacade.buildAction("default-background-1", title: "foreground", destructive: false, backgroundMode: false, authentication: true),
27 | notificationFacade.buildAction("default-foreground-0", title: "background", destructive: true, backgroundMode: false, authentication: true),
28 | notificationFacade.buildAction("default-foreground-1", title: "foreground", destructive: false, backgroundMode: false, authentication: true) ]
29 | let categoryAll = notificationFacade.buildCategory("all", minimalActions: min, defaultActions: def)
30 | let categoryDefault = notificationFacade.buildCategory("default", minimalActions: nil, defaultActions: def)
31 | let categoryMinimal = notificationFacade.buildCategory("minimal", minimalActions: min, defaultActions: nil)
32 | let categories = NSMutableSet()
33 | categories.addObject(categoryAll)
34 | categories.addObject(categoryMinimal)
35 | categories.addObject(categoryDefault)
36 | notificationFacade.registerForNotificationWithBadges(true, alerts: true, sounds: true, categories: categories as Set);
37 | if (!notificationFacade.isRegisteredForNotifications()) {
38 | print("touch ↺ button after enabling notifications for reloading table view")
39 | }
40 | }
41 |
42 | @IBAction func removeNotificationAction(sender: AnyObject) {
43 | let index = selectedIndex?.indexAtPosition(1)
44 | if (index != nil) {
45 | let notifications = notificationFacade.scheduledNotifications()
46 | if (notifications.count > index) {
47 | let notification = notifications[index!] as! UILocalNotification
48 | notificationFacade.cancelNotification(notification)
49 | selectedIndex = nil
50 | self.reloadData()
51 | }
52 | }
53 | }
54 |
55 | // MARK: UITableViewDataSource
56 |
57 | func numberOfSectionsInTableView(tableView: UITableView) -> Int
58 | {
59 | return 1
60 | }
61 |
62 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
63 | {
64 | let count = notificationFacade.scheduledNotifications().count
65 | countItem.title = "count: \(count)"
66 | return count
67 | }
68 |
69 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> TableCell
70 | {
71 | let cell = tableView.dequeueReusableCellWithIdentifier(textCellIdentifier) as! TableCell
72 | let notifications = notificationFacade.scheduledNotifications()
73 | let notification = notifications[indexPath.row] as! UILocalNotification
74 | cell.setUp(notification)
75 | return cell
76 | }
77 |
78 | // MARK: UITableViewDelegate
79 |
80 | func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
81 | {
82 | if (selectedIndex == indexPath) {
83 | tableView.deselectRowAtIndexPath(indexPath, animated: true)
84 | selectedIndex = nil
85 | } else {
86 | selectedIndex = indexPath
87 | }
88 | }
89 |
90 | // MARK: UIViewController
91 |
92 | override func viewDidAppear(animated: Bool) {
93 | super.viewDidAppear(animated)
94 | self.reloadData()
95 | }
96 |
97 | // MARK: NSObject
98 |
99 | override func awakeFromNib() {
100 | super.awakeFromNib();
101 | // Add observer
102 | NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(reloadData), name: "reloadData", object: nil)
103 | // Set email for support
104 | notificationFacade.setContactSupportURLWithEmailAddress("support@example.com", subject: nil, body: nil)
105 | // Add some logs
106 | notificationFacade.onDidReceiveNotification = { (notification, pointer) -> Void in
107 | print("notification received")
108 | }
109 | notificationFacade.onDidCancelNotificationAlert = { (notification) -> Void in
110 | print("notification alert cancelled")
111 | }
112 | notificationFacade.onDidCancelErrorAlert = { (error) -> Void in
113 | print("error alert cancelled")
114 | }
115 | notificationFacade.setNotificationHandler({ (identifier, notification) -> Void in
116 | print(identifier)
117 | }, forActionWithIdentifier:"minimal-foreground");
118 | notificationFacade.setNotificationHandler({ (identifier, notification) -> Void in
119 | print(identifier)
120 | }, forActionWithIdentifier:"minimal-background");
121 | notificationFacade.setNotificationHandler({ (identifier, notification) -> Void in
122 | print(identifier)
123 | }, forActionWithIdentifier:"default-background0");
124 | notificationFacade.setNotificationHandler({ (identifier, notification) -> Void in
125 | print(identifier)
126 | }, forActionWithIdentifier:"default-foreground1");
127 | notificationFacade.setNotificationHandler({ (identifier, notification) -> Void in
128 | print(identifier)
129 | }, forActionWithIdentifier:"default-foreground1");
130 | }
131 |
132 | deinit {
133 | NSNotificationCenter.defaultCenter().removeObserver(self)
134 | }
135 |
136 | }
137 |
138 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Héctor Marqués Ranea
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
--------------------------------------------------------------------------------
/MRLocalNotificationFacade.podspec:
--------------------------------------------------------------------------------
1 |
2 | Pod::Spec.new do |s|
3 | s.name = "MRLocalNotificationFacade"
4 | s.version = "2.0.0"
5 | s.summary = "MRLocalNotificationFacade is a class that wraps most of the APIs required for dealing with local notifications in iOS."
6 | s.homepage = "https://github.com/hectr/MRLocalNotificationFacade"
7 | s.screenshots = "https://github.com/hectr/MRLocalNotificationFacade/blob/master/notification.jpg?raw=true"
8 | s.license = 'MIT'
9 | s.author = { "hectr" => "h@mrhector.me" }
10 | s.source = { :git => "https://github.com/hectr/MRLocalNotificationFacade.git", :tag => s.version.to_s }
11 | s.social_media_url = 'https://twitter.com/hectormarquesra'
12 |
13 | s.platform = :ios, '8.0'
14 | s.requires_arc = true
15 |
16 | s.source_files = 'MRLocalNotificationFacade'
17 |
18 | s.frameworks = 'UIKit'
19 | end
20 |
--------------------------------------------------------------------------------
/MRLocalNotificationFacade.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 84C43D7C1B3EA1E1002238EC /* libMRLocalNotificationFacade.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C43D701B3EA1E0002238EC /* libMRLocalNotificationFacade.a */; };
11 | 84C43D9A1B3EA3F5002238EC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C43D991B3EA3F5002238EC /* AppDelegate.swift */; };
12 | 84C43D9C1B3EA3F5002238EC /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C43D9B1B3EA3F5002238EC /* ViewController.swift */; };
13 | 84C43D9F1B3EA3F5002238EC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 84C43D9D1B3EA3F5002238EC /* Main.storyboard */; };
14 | 84C43DA11B3EA3F5002238EC /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84C43DA01B3EA3F5002238EC /* Images.xcassets */; };
15 | 84C43DA41B3EA3F5002238EC /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 84C43DA21B3EA3F5002238EC /* LaunchScreen.xib */; };
16 | 84C43DF21B3EA614002238EC /* MRLocalNotificationFacade.m in Sources */ = {isa = PBXBuildFile; fileRef = 84C43DF11B3EA614002238EC /* MRLocalNotificationFacade.m */; };
17 | 84C43DF31B3EA616002238EC /* MRLocalNotificationFacade.m in Sources */ = {isa = PBXBuildFile; fileRef = 84C43DF11B3EA614002238EC /* MRLocalNotificationFacade.m */; };
18 | 84C43DF41B3EA617002238EC /* MRLocalNotificationFacade.m in Sources */ = {isa = PBXBuildFile; fileRef = 84C43DF11B3EA614002238EC /* MRLocalNotificationFacade.m */; };
19 | 84C43E2F1B3EAE3B002238EC /* TableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C43E2E1B3EAE3B002238EC /* TableCell.swift */; };
20 | 84CF6C2E1B4F15A60071301F /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CF6C2D1B4F15A60071301F /* TableViewController.swift */; };
21 | /* End PBXBuildFile section */
22 |
23 | /* Begin PBXContainerItemProxy section */
24 | 84C43D7D1B3EA1E1002238EC /* PBXContainerItemProxy */ = {
25 | isa = PBXContainerItemProxy;
26 | containerPortal = 84C43D681B3EA1E0002238EC /* Project object */;
27 | proxyType = 1;
28 | remoteGlobalIDString = 84C43D6F1B3EA1E0002238EC;
29 | remoteInfo = MRLocalNotificationFacade;
30 | };
31 | /* End PBXContainerItemProxy section */
32 |
33 | /* Begin PBXCopyFilesBuildPhase section */
34 | 84C43D6E1B3EA1E0002238EC /* CopyFiles */ = {
35 | isa = PBXCopyFilesBuildPhase;
36 | buildActionMask = 2147483647;
37 | dstPath = "include/$(PRODUCT_NAME)";
38 | dstSubfolderSpec = 16;
39 | files = (
40 | );
41 | runOnlyForDeploymentPostprocessing = 0;
42 | };
43 | /* End PBXCopyFilesBuildPhase section */
44 |
45 | /* Begin PBXFileReference section */
46 | 84C43D701B3EA1E0002238EC /* libMRLocalNotificationFacade.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libMRLocalNotificationFacade.a; sourceTree = BUILT_PRODUCTS_DIR; };
47 | 84C43D7B1B3EA1E1002238EC /* MRLocalNotificationFacadeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MRLocalNotificationFacadeTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
48 | 84C43D8A1B3EA286002238EC /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Tests/Info.plist; sourceTree = SOURCE_ROOT; };
49 | 84C43D951B3EA3F5002238EC /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
50 | 84C43D981B3EA3F5002238EC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
51 | 84C43D991B3EA3F5002238EC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
52 | 84C43D9B1B3EA3F5002238EC /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
53 | 84C43D9E1B3EA3F5002238EC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
54 | 84C43DA01B3EA3F5002238EC /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
55 | 84C43DA31B3EA3F5002238EC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; };
56 | 84C43DF01B3EA614002238EC /* MRLocalNotificationFacade.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MRLocalNotificationFacade.h; sourceTree = ""; };
57 | 84C43DF11B3EA614002238EC /* MRLocalNotificationFacade.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MRLocalNotificationFacade.m; sourceTree = ""; };
58 | 84C43E2D1B3EA795002238EC /* Example-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Example-Bridging-Header.h"; sourceTree = ""; };
59 | 84C43E2E1B3EAE3B002238EC /* TableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableCell.swift; sourceTree = ""; };
60 | 84CF6C2D1B4F15A60071301F /* TableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; };
61 | /* End PBXFileReference section */
62 |
63 | /* Begin PBXFrameworksBuildPhase section */
64 | 84C43D6D1B3EA1E0002238EC /* Frameworks */ = {
65 | isa = PBXFrameworksBuildPhase;
66 | buildActionMask = 2147483647;
67 | files = (
68 | );
69 | runOnlyForDeploymentPostprocessing = 0;
70 | };
71 | 84C43D781B3EA1E1002238EC /* Frameworks */ = {
72 | isa = PBXFrameworksBuildPhase;
73 | buildActionMask = 2147483647;
74 | files = (
75 | 84C43D7C1B3EA1E1002238EC /* libMRLocalNotificationFacade.a in Frameworks */,
76 | );
77 | runOnlyForDeploymentPostprocessing = 0;
78 | };
79 | 84C43D921B3EA3F5002238EC /* Frameworks */ = {
80 | isa = PBXFrameworksBuildPhase;
81 | buildActionMask = 2147483647;
82 | files = (
83 | );
84 | runOnlyForDeploymentPostprocessing = 0;
85 | };
86 | /* End PBXFrameworksBuildPhase section */
87 |
88 | /* Begin PBXGroup section */
89 | 84C43D671B3EA1E0002238EC = {
90 | isa = PBXGroup;
91 | children = (
92 | 84C43D721B3EA1E0002238EC /* MRLocalNotificationFacade */,
93 | 84C43D961B3EA3F5002238EC /* Example */,
94 | 84C43D7F1B3EA1E1002238EC /* Tests */,
95 | 84C43D711B3EA1E0002238EC /* Products */,
96 | );
97 | sourceTree = "";
98 | };
99 | 84C43D711B3EA1E0002238EC /* Products */ = {
100 | isa = PBXGroup;
101 | children = (
102 | 84C43D701B3EA1E0002238EC /* libMRLocalNotificationFacade.a */,
103 | 84C43D7B1B3EA1E1002238EC /* MRLocalNotificationFacadeTests.xctest */,
104 | 84C43D951B3EA3F5002238EC /* Example.app */,
105 | );
106 | name = Products;
107 | sourceTree = "";
108 | };
109 | 84C43D721B3EA1E0002238EC /* MRLocalNotificationFacade */ = {
110 | isa = PBXGroup;
111 | children = (
112 | 84C43DF01B3EA614002238EC /* MRLocalNotificationFacade.h */,
113 | 84C43DF11B3EA614002238EC /* MRLocalNotificationFacade.m */,
114 | );
115 | path = MRLocalNotificationFacade;
116 | sourceTree = "";
117 | };
118 | 84C43D7F1B3EA1E1002238EC /* Tests */ = {
119 | isa = PBXGroup;
120 | children = (
121 | 84C43D801B3EA1E1002238EC /* Supporting Files */,
122 | );
123 | name = Tests;
124 | path = MRLocalNotificationFacadeTests;
125 | sourceTree = "";
126 | };
127 | 84C43D801B3EA1E1002238EC /* Supporting Files */ = {
128 | isa = PBXGroup;
129 | children = (
130 | 84C43D8A1B3EA286002238EC /* Info.plist */,
131 | );
132 | name = "Supporting Files";
133 | sourceTree = "";
134 | };
135 | 84C43D961B3EA3F5002238EC /* Example */ = {
136 | isa = PBXGroup;
137 | children = (
138 | 84C43E2D1B3EA795002238EC /* Example-Bridging-Header.h */,
139 | 84C43D991B3EA3F5002238EC /* AppDelegate.swift */,
140 | 84C43D9B1B3EA3F5002238EC /* ViewController.swift */,
141 | 84CF6C2D1B4F15A60071301F /* TableViewController.swift */,
142 | 84C43E2E1B3EAE3B002238EC /* TableCell.swift */,
143 | 84C43D9D1B3EA3F5002238EC /* Main.storyboard */,
144 | 84C43DA01B3EA3F5002238EC /* Images.xcassets */,
145 | 84C43DA21B3EA3F5002238EC /* LaunchScreen.xib */,
146 | 84C43D971B3EA3F5002238EC /* Supporting Files */,
147 | );
148 | path = Example;
149 | sourceTree = "";
150 | };
151 | 84C43D971B3EA3F5002238EC /* Supporting Files */ = {
152 | isa = PBXGroup;
153 | children = (
154 | 84C43D981B3EA3F5002238EC /* Info.plist */,
155 | );
156 | name = "Supporting Files";
157 | sourceTree = "";
158 | };
159 | /* End PBXGroup section */
160 |
161 | /* Begin PBXNativeTarget section */
162 | 84C43D6F1B3EA1E0002238EC /* MRLocalNotificationFacade */ = {
163 | isa = PBXNativeTarget;
164 | buildConfigurationList = 84C43D841B3EA1E1002238EC /* Build configuration list for PBXNativeTarget "MRLocalNotificationFacade" */;
165 | buildPhases = (
166 | 84C43D6C1B3EA1E0002238EC /* Sources */,
167 | 84C43D6D1B3EA1E0002238EC /* Frameworks */,
168 | 84C43D6E1B3EA1E0002238EC /* CopyFiles */,
169 | );
170 | buildRules = (
171 | );
172 | dependencies = (
173 | );
174 | name = MRLocalNotificationFacade;
175 | productName = MRLocalNotificationFacade;
176 | productReference = 84C43D701B3EA1E0002238EC /* libMRLocalNotificationFacade.a */;
177 | productType = "com.apple.product-type.library.static";
178 | };
179 | 84C43D7A1B3EA1E1002238EC /* MRLocalNotificationFacadeTests */ = {
180 | isa = PBXNativeTarget;
181 | buildConfigurationList = 84C43D871B3EA1E1002238EC /* Build configuration list for PBXNativeTarget "MRLocalNotificationFacadeTests" */;
182 | buildPhases = (
183 | 84C43D771B3EA1E1002238EC /* Sources */,
184 | 84C43D781B3EA1E1002238EC /* Frameworks */,
185 | 84C43D791B3EA1E1002238EC /* Resources */,
186 | );
187 | buildRules = (
188 | );
189 | dependencies = (
190 | 84C43D7E1B3EA1E1002238EC /* PBXTargetDependency */,
191 | );
192 | name = MRLocalNotificationFacadeTests;
193 | productName = MRLocalNotificationFacadeTests;
194 | productReference = 84C43D7B1B3EA1E1002238EC /* MRLocalNotificationFacadeTests.xctest */;
195 | productType = "com.apple.product-type.bundle.unit-test";
196 | };
197 | 84C43D941B3EA3F5002238EC /* Example */ = {
198 | isa = PBXNativeTarget;
199 | buildConfigurationList = 84C43DB11B3EA3F5002238EC /* Build configuration list for PBXNativeTarget "Example" */;
200 | buildPhases = (
201 | 84C43D911B3EA3F5002238EC /* Sources */,
202 | 84C43D921B3EA3F5002238EC /* Frameworks */,
203 | 84C43D931B3EA3F5002238EC /* Resources */,
204 | );
205 | buildRules = (
206 | );
207 | dependencies = (
208 | );
209 | name = Example;
210 | productName = Example;
211 | productReference = 84C43D951B3EA3F5002238EC /* Example.app */;
212 | productType = "com.apple.product-type.application";
213 | };
214 | /* End PBXNativeTarget section */
215 |
216 | /* Begin PBXProject section */
217 | 84C43D681B3EA1E0002238EC /* Project object */ = {
218 | isa = PBXProject;
219 | attributes = {
220 | LastSwiftUpdateCheck = 0730;
221 | LastUpgradeCheck = 0730;
222 | TargetAttributes = {
223 | 84C43D6F1B3EA1E0002238EC = {
224 | CreatedOnToolsVersion = 6.2;
225 | };
226 | 84C43D7A1B3EA1E1002238EC = {
227 | CreatedOnToolsVersion = 6.2;
228 | };
229 | 84C43D941B3EA3F5002238EC = {
230 | CreatedOnToolsVersion = 6.2;
231 | };
232 | };
233 | };
234 | buildConfigurationList = 84C43D6B1B3EA1E0002238EC /* Build configuration list for PBXProject "MRLocalNotificationFacade" */;
235 | compatibilityVersion = "Xcode 3.2";
236 | developmentRegion = English;
237 | hasScannedForEncodings = 0;
238 | knownRegions = (
239 | en,
240 | Base,
241 | );
242 | mainGroup = 84C43D671B3EA1E0002238EC;
243 | productRefGroup = 84C43D711B3EA1E0002238EC /* Products */;
244 | projectDirPath = "";
245 | projectRoot = "";
246 | targets = (
247 | 84C43D6F1B3EA1E0002238EC /* MRLocalNotificationFacade */,
248 | 84C43D7A1B3EA1E1002238EC /* MRLocalNotificationFacadeTests */,
249 | 84C43D941B3EA3F5002238EC /* Example */,
250 | );
251 | };
252 | /* End PBXProject section */
253 |
254 | /* Begin PBXResourcesBuildPhase section */
255 | 84C43D791B3EA1E1002238EC /* Resources */ = {
256 | isa = PBXResourcesBuildPhase;
257 | buildActionMask = 2147483647;
258 | files = (
259 | );
260 | runOnlyForDeploymentPostprocessing = 0;
261 | };
262 | 84C43D931B3EA3F5002238EC /* Resources */ = {
263 | isa = PBXResourcesBuildPhase;
264 | buildActionMask = 2147483647;
265 | files = (
266 | 84C43D9F1B3EA3F5002238EC /* Main.storyboard in Resources */,
267 | 84C43DA41B3EA3F5002238EC /* LaunchScreen.xib in Resources */,
268 | 84C43DA11B3EA3F5002238EC /* Images.xcassets in Resources */,
269 | );
270 | runOnlyForDeploymentPostprocessing = 0;
271 | };
272 | /* End PBXResourcesBuildPhase section */
273 |
274 | /* Begin PBXSourcesBuildPhase section */
275 | 84C43D6C1B3EA1E0002238EC /* Sources */ = {
276 | isa = PBXSourcesBuildPhase;
277 | buildActionMask = 2147483647;
278 | files = (
279 | 84C43DF41B3EA617002238EC /* MRLocalNotificationFacade.m in Sources */,
280 | );
281 | runOnlyForDeploymentPostprocessing = 0;
282 | };
283 | 84C43D771B3EA1E1002238EC /* Sources */ = {
284 | isa = PBXSourcesBuildPhase;
285 | buildActionMask = 2147483647;
286 | files = (
287 | 84C43DF31B3EA616002238EC /* MRLocalNotificationFacade.m in Sources */,
288 | );
289 | runOnlyForDeploymentPostprocessing = 0;
290 | };
291 | 84C43D911B3EA3F5002238EC /* Sources */ = {
292 | isa = PBXSourcesBuildPhase;
293 | buildActionMask = 2147483647;
294 | files = (
295 | 84C43D9C1B3EA3F5002238EC /* ViewController.swift in Sources */,
296 | 84C43D9A1B3EA3F5002238EC /* AppDelegate.swift in Sources */,
297 | 84C43E2F1B3EAE3B002238EC /* TableCell.swift in Sources */,
298 | 84CF6C2E1B4F15A60071301F /* TableViewController.swift in Sources */,
299 | 84C43DF21B3EA614002238EC /* MRLocalNotificationFacade.m in Sources */,
300 | );
301 | runOnlyForDeploymentPostprocessing = 0;
302 | };
303 | /* End PBXSourcesBuildPhase section */
304 |
305 | /* Begin PBXTargetDependency section */
306 | 84C43D7E1B3EA1E1002238EC /* PBXTargetDependency */ = {
307 | isa = PBXTargetDependency;
308 | target = 84C43D6F1B3EA1E0002238EC /* MRLocalNotificationFacade */;
309 | targetProxy = 84C43D7D1B3EA1E1002238EC /* PBXContainerItemProxy */;
310 | };
311 | /* End PBXTargetDependency section */
312 |
313 | /* Begin PBXVariantGroup section */
314 | 84C43D9D1B3EA3F5002238EC /* Main.storyboard */ = {
315 | isa = PBXVariantGroup;
316 | children = (
317 | 84C43D9E1B3EA3F5002238EC /* Base */,
318 | );
319 | name = Main.storyboard;
320 | sourceTree = "";
321 | };
322 | 84C43DA21B3EA3F5002238EC /* LaunchScreen.xib */ = {
323 | isa = PBXVariantGroup;
324 | children = (
325 | 84C43DA31B3EA3F5002238EC /* Base */,
326 | );
327 | name = LaunchScreen.xib;
328 | sourceTree = "";
329 | };
330 | /* End PBXVariantGroup section */
331 |
332 | /* Begin XCBuildConfiguration section */
333 | 84C43D821B3EA1E1002238EC /* Debug */ = {
334 | isa = XCBuildConfiguration;
335 | buildSettings = {
336 | ALWAYS_SEARCH_USER_PATHS = NO;
337 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
338 | CLANG_CXX_LIBRARY = "libc++";
339 | CLANG_ENABLE_MODULES = YES;
340 | CLANG_ENABLE_OBJC_ARC = YES;
341 | CLANG_WARN_BOOL_CONVERSION = YES;
342 | CLANG_WARN_CONSTANT_CONVERSION = YES;
343 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
344 | CLANG_WARN_EMPTY_BODY = YES;
345 | CLANG_WARN_ENUM_CONVERSION = YES;
346 | CLANG_WARN_INT_CONVERSION = YES;
347 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
348 | CLANG_WARN_UNREACHABLE_CODE = YES;
349 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
350 | COPY_PHASE_STRIP = NO;
351 | ENABLE_STRICT_OBJC_MSGSEND = YES;
352 | ENABLE_TESTABILITY = YES;
353 | GCC_C_LANGUAGE_STANDARD = gnu99;
354 | GCC_DYNAMIC_NO_PIC = NO;
355 | GCC_OPTIMIZATION_LEVEL = 0;
356 | GCC_PREPROCESSOR_DEFINITIONS = (
357 | "DEBUG=1",
358 | "$(inherited)",
359 | );
360 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
361 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
362 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
363 | GCC_WARN_UNDECLARED_SELECTOR = YES;
364 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
365 | GCC_WARN_UNUSED_FUNCTION = YES;
366 | GCC_WARN_UNUSED_VARIABLE = YES;
367 | IPHONEOS_DEPLOYMENT_TARGET = 8.2;
368 | MTL_ENABLE_DEBUG_INFO = YES;
369 | ONLY_ACTIVE_ARCH = YES;
370 | SDKROOT = iphoneos;
371 | };
372 | name = Debug;
373 | };
374 | 84C43D831B3EA1E1002238EC /* Release */ = {
375 | isa = XCBuildConfiguration;
376 | buildSettings = {
377 | ALWAYS_SEARCH_USER_PATHS = NO;
378 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
379 | CLANG_CXX_LIBRARY = "libc++";
380 | CLANG_ENABLE_MODULES = YES;
381 | CLANG_ENABLE_OBJC_ARC = YES;
382 | CLANG_WARN_BOOL_CONVERSION = YES;
383 | CLANG_WARN_CONSTANT_CONVERSION = YES;
384 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
385 | CLANG_WARN_EMPTY_BODY = YES;
386 | CLANG_WARN_ENUM_CONVERSION = YES;
387 | CLANG_WARN_INT_CONVERSION = YES;
388 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
389 | CLANG_WARN_UNREACHABLE_CODE = YES;
390 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
391 | COPY_PHASE_STRIP = NO;
392 | ENABLE_NS_ASSERTIONS = NO;
393 | ENABLE_STRICT_OBJC_MSGSEND = YES;
394 | GCC_C_LANGUAGE_STANDARD = gnu99;
395 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
396 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
397 | GCC_WARN_UNDECLARED_SELECTOR = YES;
398 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
399 | GCC_WARN_UNUSED_FUNCTION = YES;
400 | GCC_WARN_UNUSED_VARIABLE = YES;
401 | IPHONEOS_DEPLOYMENT_TARGET = 8.2;
402 | MTL_ENABLE_DEBUG_INFO = NO;
403 | SDKROOT = iphoneos;
404 | VALIDATE_PRODUCT = YES;
405 | };
406 | name = Release;
407 | };
408 | 84C43D851B3EA1E1002238EC /* Debug */ = {
409 | isa = XCBuildConfiguration;
410 | buildSettings = {
411 | OTHER_LDFLAGS = "-ObjC";
412 | PRODUCT_NAME = "$(TARGET_NAME)";
413 | SKIP_INSTALL = YES;
414 | };
415 | name = Debug;
416 | };
417 | 84C43D861B3EA1E1002238EC /* Release */ = {
418 | isa = XCBuildConfiguration;
419 | buildSettings = {
420 | OTHER_LDFLAGS = "-ObjC";
421 | PRODUCT_NAME = "$(TARGET_NAME)";
422 | SKIP_INSTALL = YES;
423 | };
424 | name = Release;
425 | };
426 | 84C43D881B3EA1E1002238EC /* Debug */ = {
427 | isa = XCBuildConfiguration;
428 | buildSettings = {
429 | FRAMEWORK_SEARCH_PATHS = (
430 | "$(SDKROOT)/Developer/Library/Frameworks",
431 | "$(inherited)",
432 | );
433 | GCC_PREPROCESSOR_DEFINITIONS = (
434 | "DEBUG=1",
435 | "$(inherited)",
436 | );
437 | INFOPLIST_FILE = Tests/Info.plist;
438 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
439 | PRODUCT_BUNDLE_IDENTIFIER = "me.mrhector.$(PRODUCT_NAME:rfc1034identifier)";
440 | PRODUCT_NAME = "$(TARGET_NAME)";
441 | };
442 | name = Debug;
443 | };
444 | 84C43D891B3EA1E1002238EC /* Release */ = {
445 | isa = XCBuildConfiguration;
446 | buildSettings = {
447 | FRAMEWORK_SEARCH_PATHS = (
448 | "$(SDKROOT)/Developer/Library/Frameworks",
449 | "$(inherited)",
450 | );
451 | INFOPLIST_FILE = Tests/Info.plist;
452 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
453 | PRODUCT_BUNDLE_IDENTIFIER = "me.mrhector.$(PRODUCT_NAME:rfc1034identifier)";
454 | PRODUCT_NAME = "$(TARGET_NAME)";
455 | };
456 | name = Release;
457 | };
458 | 84C43DB21B3EA3F5002238EC /* Debug */ = {
459 | isa = XCBuildConfiguration;
460 | buildSettings = {
461 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
462 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
463 | GCC_PREPROCESSOR_DEFINITIONS = (
464 | "DEBUG=1",
465 | "$(inherited)",
466 | );
467 | INFOPLIST_FILE = Example/Info.plist;
468 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
469 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
470 | PRODUCT_BUNDLE_IDENTIFIER = "me.mrhector.$(PRODUCT_NAME:rfc1034identifier)";
471 | PRODUCT_NAME = "$(TARGET_NAME)";
472 | SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/Example/Example-Bridging-Header.h";
473 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
474 | TARGETED_DEVICE_FAMILY = "1,2";
475 | };
476 | name = Debug;
477 | };
478 | 84C43DB31B3EA3F5002238EC /* Release */ = {
479 | isa = XCBuildConfiguration;
480 | buildSettings = {
481 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
482 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
483 | INFOPLIST_FILE = Example/Info.plist;
484 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
485 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
486 | PRODUCT_BUNDLE_IDENTIFIER = "me.mrhector.$(PRODUCT_NAME:rfc1034identifier)";
487 | PRODUCT_NAME = "$(TARGET_NAME)";
488 | SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/Example/Example-Bridging-Header.h";
489 | TARGETED_DEVICE_FAMILY = "1,2";
490 | };
491 | name = Release;
492 | };
493 | /* End XCBuildConfiguration section */
494 |
495 | /* Begin XCConfigurationList section */
496 | 84C43D6B1B3EA1E0002238EC /* Build configuration list for PBXProject "MRLocalNotificationFacade" */ = {
497 | isa = XCConfigurationList;
498 | buildConfigurations = (
499 | 84C43D821B3EA1E1002238EC /* Debug */,
500 | 84C43D831B3EA1E1002238EC /* Release */,
501 | );
502 | defaultConfigurationIsVisible = 0;
503 | defaultConfigurationName = Release;
504 | };
505 | 84C43D841B3EA1E1002238EC /* Build configuration list for PBXNativeTarget "MRLocalNotificationFacade" */ = {
506 | isa = XCConfigurationList;
507 | buildConfigurations = (
508 | 84C43D851B3EA1E1002238EC /* Debug */,
509 | 84C43D861B3EA1E1002238EC /* Release */,
510 | );
511 | defaultConfigurationIsVisible = 0;
512 | defaultConfigurationName = Release;
513 | };
514 | 84C43D871B3EA1E1002238EC /* Build configuration list for PBXNativeTarget "MRLocalNotificationFacadeTests" */ = {
515 | isa = XCConfigurationList;
516 | buildConfigurations = (
517 | 84C43D881B3EA1E1002238EC /* Debug */,
518 | 84C43D891B3EA1E1002238EC /* Release */,
519 | );
520 | defaultConfigurationIsVisible = 0;
521 | defaultConfigurationName = Release;
522 | };
523 | 84C43DB11B3EA3F5002238EC /* Build configuration list for PBXNativeTarget "Example" */ = {
524 | isa = XCConfigurationList;
525 | buildConfigurations = (
526 | 84C43DB21B3EA3F5002238EC /* Debug */,
527 | 84C43DB31B3EA3F5002238EC /* Release */,
528 | );
529 | defaultConfigurationIsVisible = 0;
530 | defaultConfigurationName = Release;
531 | };
532 | /* End XCConfigurationList section */
533 | };
534 | rootObject = 84C43D681B3EA1E0002238EC /* Project object */;
535 | }
536 |
--------------------------------------------------------------------------------
/MRLocalNotificationFacade.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MRLocalNotificationFacade/MRLocalNotificationFacade.h:
--------------------------------------------------------------------------------
1 | // MRLocalNotificationFacade.h
2 | //
3 | // Copyright (c) 2015 Héctor Marqués
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 |
23 | #import
24 |
25 | NS_ASSUME_NONNULL_BEGIN
26 |
27 | /**
28 | Predefined domain for errors from `MRLocalNotificationFacade`.
29 | */
30 | extern NSString *const MRLocalNotificationErrorDomain;
31 |
32 | /**
33 | The corresponding value is the URL associated to the recovery option.
34 | */
35 | extern NSString *const MRRecoveryURLErrorKey;
36 |
37 | /**
38 | Error codes within the `MRLocalNotificationErrorDomain`.
39 | */
40 | typedef enum {
41 | /** Unknown error (not currently used). */
42 | MRLocalNotificationErrorUnknown = 1950,
43 | /** Missing notification object. */
44 | MRLocalNotificationErrorNilObject = 1951,
45 | /** Notification object is not valid. */
46 | MRLocalNotificationErrorInvalidObject = 1952,
47 | /** No notification types allowed. */
48 | MRLocalNotificationErrorNoneAllowed = 1953,
49 | /** No notification sound type allowed. */
50 | MRLocalNotificationErrorSoundNotAllowed = 1954,
51 | /** No notification alert type allowed. */
52 | MRLocalNotificationErrorAlertNotAllowed = 1955,
53 | /** No notification badge type allowed. */
54 | MRLocalNotificationErrorBadgeNotAllowed = 1956,
55 | /** Notification fire date is not valid. */
56 | MRLocalNotificationErrorInvalidDate = 1957,
57 | /** Missing notification fire date. */
58 | MRLocalNotificationErrorMissingDate = 1958,
59 | /** Notification already scheduled. */
60 | MRLocalNotificationErrorAlreadyScheduled = 1959,
61 | /** Notification category not registered. */
62 | MRLocalNotificationErrorCategoryNotRegistered = 1960,
63 | /** Missing notification alert body. */
64 | MRLocalNotificationErrorMissingAlertBody = 1961,
65 | } MRLocalNotificationErrorCode;
66 |
67 |
68 | /**
69 | `MRLocalNotificationFacade` wraps most of the APIs related with local notifications.
70 | */
71 | @interface MRLocalNotificationFacade : NSObject
72 |
73 | /**
74 | Returns the singleton `MRLocalNotificationFacade` instance.
75 |
76 | @return The default instance of the receiver.
77 | */
78 | + (instancetype)defaultInstance;
79 |
80 | /**
81 | Sound name used when one is needed for customizing a notification object.
82 |
83 | Default value is `UILocalNotificationDefaultSoundName`.
84 | */
85 | @property (nullable, nonatomic, strong) NSString *defaultSoundName;
86 |
87 | /**
88 | Creates an `UILocalNotification` and initializes it with the given parameters.
89 |
90 | @param fireDate The date and time when the system should deliver the notification.
91 | @param timeZone A boolean value indicating whether the notification should use the `defaultTimeZone` property value or not.
92 | @param category The name of a group of actions to display in the alert.
93 | @param userInfo A dictionary for passing custom information to the notified app. You may add arbitrary key-value pairs to this dictionary. However, the keys and values must be valid property-list types; if any are not, an exception is raised.
94 | @return An initialized `UILocalNotification`.
95 | */
96 | - (UILocalNotification *)buildNotificationWithDate:(NSDate *)fireDate
97 | timeZone:(BOOL)timeZone
98 | category:(nullable NSString *)category
99 | userInfo:(nullable NSDictionary *)userInfo;
100 |
101 | /**
102 | Creates an `UILocalNotification` and initializes it with the given parameters.
103 |
104 | @param fireInterval The time interval (since now) after which the system should deliver the notification.
105 | @param category The name of a group of actions to display in the alert.
106 | @param userInfo A dictionary for passing custom information to the notified app. You may add arbitrary key-value pairs to this dictionary. However, the keys and values must be valid property-list types; if any are not, an exception is raised.
107 | @return An initialized `UILocalNotification`.
108 | */
109 | - (UILocalNotification *)buildNotificationWithInterval:(NSTimeInterval)fireInterval
110 | category:(nullable NSString *)category
111 | userInfo:(nullable NSDictionary *)userInfo;
112 |
113 | /**
114 | Creates an `UILocalNotification` and initializes it with the given parameters.
115 |
116 | @param fireRegion The geographic region that triggers the notification.
117 | @param regionTriggersOnce A boolean value indicating whether crossing a geographic region boundary delivers only one notification.
118 | @param category The name of a group of actions to display in the alert.
119 | @param userInfo A dictionary for passing custom information to the notified app. You may add arbitrary key-value pairs to this dictionary. However, the keys and values must be valid property-list types; if any are not, an exception is raised.
120 | @return An initialized `UILocalNotification`.
121 | */
122 | - (UILocalNotification *)buildNotificationWithRegion:(CLRegion *)fireRegion
123 | triggersOnce:(BOOL)regionTriggersOnce
124 | category:(nullable NSString *)category
125 | userInfo:(nullable NSDictionary *)userInfo;
126 |
127 | /**
128 | Customizes the given `notification` with the given `calendarUnit` and the `defaultCalendar` property value.
129 |
130 | @param notification The notification object that will be customized.
131 | @param calendarUnit The calendar interval at which to reschedule the notification.
132 | */
133 | - (void)customizeNotificationRepeat:(UILocalNotification *)notification
134 | interval:(NSCalendarUnit)calendarUnit;
135 |
136 | /**
137 | Customizes the given `notification` with the given parameters.
138 |
139 | @param notification The notification object that will be customized.
140 | @param alertTitle A short description of the reason for the alert.
141 | @param alertBody The message displayed in the notification alert.
142 | @param alertAction The title of the action button or slider.
143 | @param alertLaunchImage Identifies the image used as the launch image when the user taps (or slides) the action button (or slider).
144 | */
145 | - (void)customizeNotificationAlert:(UILocalNotification *)notification
146 | title:(nullable NSString *)alertTitle
147 | body:(nullable NSString *)alertBody
148 | action:(nullable NSString *)alertAction
149 | launchImage:(nullable NSString *)alertLaunchImage;
150 |
151 | /**
152 | Customizes the given `notification` with the given `badgeNumber` and a sound if `hasSound` is `YES`.
153 |
154 | @param notification The notification object that will be customized.
155 | @param badgeNumber The number to display as the app’s icon badge.
156 | @param hasSound A boolean that will determine whether to use or not the `defaultSoundName` property as the name of the file containing the sound to play when an alert is displayed.
157 | */
158 | - (void)customizeNotification:(UILocalNotification *)notification
159 | appIconBadge:(NSInteger)badgeNumber
160 | sound:(BOOL)hasSound;
161 |
162 | /**
163 | All currently scheduled local notifications.
164 | */
165 | - (NSArray *)scheduledNotifications;
166 |
167 | /**
168 | Cancels the delivery of the specified scheduled local notification.
169 |
170 | Calling this method also programmatically dismisses the notification if it is currently displaying an alert.
171 |
172 | @param notification The local notification to cancel. If it is `nil`, the method returns immediately.
173 | */
174 | - (void)cancelNotification:(UILocalNotification *)notification;
175 |
176 | /**
177 | Cancels the delivery of all scheduled local notifications.
178 | */
179 | - (void)cancelAllNotifications;
180 |
181 | @end
182 |
183 |
184 | @interface MRLocalNotificationFacade (UIMutableUserNotificationCategory)
185 |
186 | /**
187 | Creates an `UIMutableUserNotificationAction` and initializes it with the given parameters.
188 |
189 | @param identifier The string that you use internally to identify the action.
190 | @param title The localized string to use as the button title for the action.
191 | @param isDestructive A Boolean value indicating whether the action is destructive.
192 | @param runsInBackground Whether the app will run background or not when the action is performed.
193 | @param authRequired A Boolean value indicating whether the user must unlock the device before the action is performed.
194 | @return An initialized `UIMutableUserNotificationAction`.
195 | */
196 | - (UIMutableUserNotificationAction *)buildAction:(NSString *)identifier
197 | title:(NSString *)title
198 | destructive:(BOOL)isDestructive
199 | backgroundMode:(BOOL)runsInBackground
200 | authentication:(BOOL)authRequired;
201 | /**
202 | Creates an `UIMutableUserNotificationAction` and initializes it with the given parameters.
203 |
204 | @param identifier The name of the action group.
205 | @param minimalActions An array of `UIUserNotificationAction` objects representing the actions to display for `UIUserNotificationActionContextMinimal`.
206 | @param defaultActions An array of `UIUserNotificationAction` objects representing the actions to display for `UIUserNotificationActionContextDefault`.
207 | @return An initialized `UIMutableUserNotificationCategory`.
208 | */
209 | - (UIMutableUserNotificationCategory *)buildCategory:(NSString *)identifier
210 | minimalActions:(nullable NSArray *)minimalActions
211 | defaultActions:(nullable NSArray *)defaultActions;
212 |
213 | /**
214 | Resets the given actions associated with the `UIUserNotificationActionContextMinimal` context.
215 |
216 | @param category `UIMutableUserNotificationCategory` object that will be customized.
217 | @param action0 First `UIUserNotificationAction` object (may be nil).
218 | @param action1 Second `UIUserNotificationAction` object (may be nil).
219 | */
220 | - (void)customizeMinimalCategory:(UIMutableUserNotificationCategory *)category
221 | action0:(nullable UIUserNotificationAction *)action0
222 | action1:(nullable UIUserNotificationAction *)action1;
223 |
224 | /**
225 | Resets the given actions associated with the `UIUserNotificationActionContextDefault` context.
226 |
227 | @param category `UIMutableUserNotificationCategory` object that will be customized.
228 | @param action0 First `UIUserNotificationAction` object (may be nil).
229 | @param action1 Second `UIUserNotificationAction` object (may be nil).
230 | @param action2 Third `UIUserNotificationAction` object (may be nil).
231 | @param action3 Forth `UIUserNotificationAction` object (may be nil).
232 | */
233 | - (void)customizeDefaultCategory:(UIMutableUserNotificationCategory *)category
234 | action0:(nullable UIUserNotificationAction *)action0
235 | action1:(nullable UIUserNotificationAction *)action1
236 | action2:(nullable UIUserNotificationAction *)action2
237 | action3:(nullable UIUserNotificationAction *)action3;
238 |
239 | /**
240 | Stores the given `handler` block associated with the given `identifier`.
241 |
242 | @param handler The block that should be invoked for handling an action for a notification `userInfo`.
243 | @param identifier The identifier of the action that should be associated with the handler.
244 | */
245 | - (void)setNotificationHandler:(void(^_Nullable)(NSString *identifier, UILocalNotification *notification))handler
246 | forActionWithIdentifier:(NSString *)identifier;
247 |
248 | @end
249 |
250 |
251 | @interface MRLocalNotificationFacade (UIUserNotificationSettings)
252 |
253 | /**
254 | Registers your preferred options for notifying the user.
255 |
256 | @param badgeType The app badges its icon.
257 | @param alertType The app posts an alert.
258 | @param soundType The app plays a sound.
259 | @param categories A set of `UIUserNotificationCategory` objects that define the groups of actions a notification may include.
260 | */
261 | - (void)registerForNotificationWithBadges:(BOOL)badgeType
262 | alerts:(BOOL)alertType
263 | sounds:(BOOL)soundType
264 | categories:(nullable NSSet *)categories;
265 |
266 | /**
267 | Returns the user notification settings for the app.
268 |
269 | @return A user notification settings object indicating the types of notifications that your app may use.
270 | */
271 | - (nullable UIUserNotificationSettings *)currentUserNotificationSettings;
272 |
273 | /**
274 | Returns the category from the given user notification settings object whose identifier matches the given one.
275 |
276 | @param categoryIdentifier The category identifier.
277 | @param notificationSettings The user notification settings object.
278 | @return The category object contained in `notificationSettings` whose identifier matches `categoryIdentifier`.
279 | */
280 | - (nullable UIUserNotificationCategory *)getCategoryForIdentifier:(nullable NSString *)categoryIdentifier
281 | fromUserNotificationSettings:(nullable UIUserNotificationSettings *)notificationSettings;
282 |
283 |
284 | /**
285 | Returns a Boolean indicating whether the app is currently registered for any of the types of local notifications.
286 |
287 | @return `YES` if the app is registered for any of the types of local notifications or `NO` if registration has not occurred or all types have been denied by the user.
288 | */
289 | - (BOOL)isRegisteredForNotifications;
290 |
291 | /**
292 | Returns whether the app is allowed to badge app's icon or not.
293 |
294 | @return `YES` if the app can badge app's icon; `NO` otherwise.
295 | */
296 | - (BOOL)isBadgeTypeAllowed;
297 |
298 | /**
299 | Returns whether the app is allowed to play a sound or not.
300 |
301 | @return `YES` if the app can play a sound; `NO` otherwise.
302 | */
303 | - (BOOL)isSoundTypeAllowed;
304 |
305 | /**
306 | Returns whether the app is allowed to post an alert or not.
307 |
308 | @return `YES` if the app can post an alert; `NO` otherwise.
309 | */
310 | - (BOOL)isAlertTypeAllowed;
311 |
312 | @end
313 |
314 |
315 | @interface MRLocalNotificationFacade (UIAlertController)
316 |
317 | /**
318 | View controller used as presenting view controller by the method `showAlertController:`.
319 |
320 | If `defaultAlertPresenter` is `nil`, a new view controller instance is created for every `showAlertController:` invocation.
321 | */
322 | @property (nullable, nonatomic, strong) UIViewController *defaultAlertPresenter;
323 |
324 | /**
325 | Block invoked when the user cancels an alert created with the `buildAlertControlForNotification:` method.
326 | */
327 | @property (nullable, nonatomic, copy) void(^onDidCancelNotificationAlert)(UILocalNotification *notification);
328 |
329 | /**
330 | Creates an alert for displaying the given notification.
331 |
332 | This method makes no use notification's `alertAction`, but uses its `category` as option buttons.
333 |
334 | @param notification `UILocalNotification` used for customizing alert message and buttons.
335 | @return An initialized alert controller object.
336 | */
337 | - (UIAlertController *)buildAlertControlForNotification:(UILocalNotification *)notification;
338 |
339 | /**
340 | Presents the given alert controller.
341 |
342 | @param alert Alert controller object that needs to be presented.
343 | */
344 | - (void)showAlertController:(UIAlertController *)alert;
345 |
346 | @end
347 |
348 |
349 | @interface MRLocalNotificationFacade (UIApplication)
350 |
351 | /**
352 | Returns the `UIApplication` instance.
353 |
354 | Default value is `UIApplication.sharedApplication`.
355 | */
356 | @property (nullable, nonatomic, strong) UIApplication *defaultApplication;
357 |
358 | /**
359 | Block invoked by `application:didReceiveLocalNotification:` method when a notification is passed as parameter.
360 |
361 | `shouldShowAlert` is given a default value according to the application state, but can be changed within the block.
362 | */
363 | @property (nullable, nonatomic, copy) void(^onDidReceiveNotification)(UILocalNotification *notification, BOOL *shouldShowAlert);
364 |
365 | /**
366 | Returns `YES` if the method `application:didRegisterUserNotificationSettings:` is known to have been received with a not `nil` notification settings parameter.
367 |
368 | The return value is taken from the user defaults.
369 | */
370 | @property (nonatomic, readonly) BOOL hasRegisteredNotifications;
371 |
372 | /**
373 | Returns the local notification object (if any) from the given launch options dictionary.
374 | @param launchOptions Launch options dictionary.
375 | @returns The local notification object or `nil` if `UIApplicationLaunchOptionsLocalNotificationKey` does not contain a local notification or the given `launchOptions` was `nil`.
376 | */
377 | - (nullable UILocalNotification *)getNotificationFromLaunchOptions:(nullable NSDictionary *)launchOptions;
378 |
379 | /**
380 | The number currently set as the badge of the app icon in Springboard.
381 | */
382 | - (NSInteger)applicationIconBadgeNumber;
383 |
384 | /**
385 | Sets the number for the badge of the app icon in Springboard.
386 |
387 | @param applicationIconBadgeNumber New value for the badge of the app icon.
388 | */
389 | - (void)setApplicationIconBadgeNumber:(NSInteger)applicationIconBadgeNumber;
390 |
391 | /**
392 | Handles `application:didRegisterUserNotificationSettings:`.
393 |
394 | This method stores the value returned by `hasRegisteredNotifications`.
395 |
396 | @param notificationSettings The user notification settings that are available to your app. The settings in this object may be different than the ones you originally requested. If it is `nil`, the method returns immediately.
397 | */
398 | - (void)handleDidRegisterUserNotificationSettings:(nullable UIUserNotificationSettings *)notificationSettings;
399 |
400 | /**
401 | Handles `application:didReceiveLocalNotification:`.
402 |
403 | This method invokes `onDidReceiveNotification` block and may display the given notification using an alert controller when the application is in active state (`UIApplicationStateActive`).
404 |
405 | @param notification A local notification that encapsulates details about the notification, potentially including custom data. If it is `nil`, the method returns immediately.
406 | */
407 | - (void)handleDidReceiveLocalNotification:(nullable UILocalNotification *)notification;
408 |
409 | /**
410 | Handles `application:handleActionWithIdentifier:forLocalNotification:completionHandler:`.
411 |
412 | This method invokes the action hanlder block associated with the given action `identifier`.
413 |
414 | @param identifier The identifier associated with the custom action. This string corresponds to the identifier from the `UILocalNotificationAction` object that was used to configure the action in the local notification.
415 | @param notification The local notification object that was triggered.
416 | @param completionHandler A block to call when you are finished performing the action.
417 | */
418 | - (void)handleActionWithIdentifier:(nullable NSString *)identifier
419 | forLocalNotification:(nullable UILocalNotification *)notification
420 | completionHandler:(void (^_Nullable)())completionHandler;
421 |
422 | @end
423 |
424 |
425 | @interface MRLocalNotificationFacade (NSDate)
426 |
427 | /**
428 | Time zone used when one is needed for building a notification object.
429 | */
430 | @property (nullable, nonatomic, strong) NSTimeZone *defaultTimeZone;
431 |
432 | /**
433 | Calendar used when one is needed for building `NSDate` objects or customizing notification's *repeat interval*.
434 |
435 | Default value is `NSCalendar.autoupdatingCurrentCalendar`.
436 | */
437 | @property (nullable, nonatomic, strong) NSCalendar *defaultCalendar;
438 |
439 | /**
440 | Returns a new `NSDate` object representing the absolute time calculated from given components and `defaultCalendar` property.
441 |
442 | The parameters are interpreted in the context of the calendar with which it is used (`defaultCalendar`).
443 |
444 | @param day The number of day units for the receiver.
445 | @param month The number of month units for the receiver.
446 | @param year The number of year units for the receiver.
447 | @param hour The number of hour units for the receiver.
448 | @param minute The number of minute units for the receiver.
449 | @param second The number of second units for the receiver.
450 | @return A new `NSDate` object representing the absolute time calculated from `defaultCalendar` property and `day`, `month`, `year`, `hour`, `minute` and `second`.
451 | */
452 | - (NSDate *)buildDateWithDay:(NSInteger)day
453 | month:(NSInteger)month
454 | year:(NSInteger)year
455 | hour:(NSInteger)hour
456 | minute:(NSInteger)minute
457 | second:(NSInteger)second;
458 |
459 | /**
460 | Converts the given date from GMT to the `defaultTimeZone`.
461 |
462 | @param gmtDate `NSDate` object in GMT.
463 | @return `NSDate` object converted from the receiver's default time zone equivalent to the given GMT date.
464 | */
465 | - (NSDate *)convertDateToDefaultTimeZone:(NSDate *)gmtDate;
466 |
467 | /**
468 | Converts the given `date` from `defaultTimeZone` to GMT.
469 |
470 | @param timeDate `NSDate` object in the receiver's default time zone.
471 | @return `NSDate` object converted from GMT equivalent to the given time zone date.
472 | */
473 | - (NSDate *)convertDateToGMT:(NSDate *)timeDate;
474 |
475 | /**
476 | Retrieves the components of the given `date` using the receiver's `defaultCalendar`.
477 |
478 | @param date The date.
479 | @param day A pointer for storing the day value.
480 | @param month A pointer for storing the month value.
481 | @param year A pointer for storing the year value.
482 | @param hour A pointer for storing the hour value.
483 | @param minute A pointer for storing the minute value.
484 | @param second A pointer for storing the second value.
485 | */
486 | - (void)date:(NSDate *)date
487 | getDay:(nullable NSInteger *)day
488 | month:(nullable NSInteger *)month
489 | year:(nullable NSInteger *)year
490 | hour:(nullable NSInteger *)hour
491 | minute:(nullable NSInteger *)minute
492 | second:(nullable NSInteger *)second;
493 |
494 | /**
495 | Returns the `fireDate` of the given `notification` converted to GMT (if needed).
496 |
497 | @param notification The notification object.
498 | @return The fire date converted from the notification's `timeZone` to GMT or `nil` if the given `notification` hasn't got `fireDate` or it is `nil` itself.
499 | */
500 | - (NSDate *)getGMTFireDateFromNotification:(UILocalNotification *)notification;
501 |
502 | @end
503 |
504 |
505 | @interface MRLocalNotificationFacade (NSErrorRecoveryAttempting)
506 |
507 | /**
508 | URL used for handling a 'Contact Support' action.
509 |
510 | This property is used when building the error objects used by the `scheduleNotification:withError:` method.
511 | */
512 | @property (nullable, nonatomic, strong) NSURL *contactSupportURL;
513 |
514 | /**
515 | Block invoked when the user cancels an alert created with the `buildAlertControlForError:` method.
516 | */
517 | @property (nullable, nonatomic, copy) void(^onDidCancelErrorAlert)(NSError *error);
518 |
519 | /**
520 | Sets a URL in the `contactSupportURL` property using the `mailto` scheme and the given email address.
521 |
522 | @param emailAddress The support email address.
523 | @param subject The email subject (optional).
524 | @param body The email body (optional).
525 | */
526 | - (void)setContactSupportURLWithEmailAddress:(NSString *)emailAddress
527 | subject:(nullable NSString *)subject
528 | body:(nullable NSString *)body;
529 |
530 | /**
531 | Presents a local notification immediately.
532 |
533 | Prior to presenting any local notifications, you must call the registerUserNotificationSettings: method to let the system know what types of alerts, if any, you plan to display to the user.
534 |
535 | @param notification The local notification object that you want to present.
536 | @param errorPtr If the notification cannot be presented, upon return contains an instance of `NSError` that describes the problem.
537 | @return `YES` if the notification has been presented; `NO` otherwise.
538 | */
539 | - (BOOL)presentNotificationNow:(nullable UILocalNotification *)notification
540 | withError:(NSError *_Nullable*_Nullable)errorPtr;
541 |
542 | /**
543 | Checks if a local notification can be presented now.
544 |
545 | @param notification The local notification object that you want to check.
546 | @param errorPtr If the notification cannot be presented, upon return contains an instance of `NSError` that describes the problem.
547 | @return `YES` if the notification can be presented now; `NO` otherwise.
548 | */
549 | - (BOOL)canPresentNotificationNow:(nullable UILocalNotification *)notification
550 | error:(NSError *_Nullable*_Nullable)errorPtr;
551 |
552 | /**
553 | Returns whether the `scheduledNotifications` array contains an object thas is equal (`isEqual:`) to the given `notification` or not.
554 |
555 | @param notification The object used for checking equality with each element of the array.
556 | @return `YES` if there is an object equal; `NO` otherwise.
557 | */
558 | - (BOOL)scheduledNotificationsContainsNotification:(UILocalNotification *)notification;
559 |
560 | /**
561 | Schedules a local notification for delivery at its encapsulated date and time.
562 |
563 | Prior to scheduling any local notifications, you must call the registerUserNotificationSettings: method to let the system know what types of alerts, if any, you plan to display to the user.
564 |
565 | @param notification The local notification object that you want to schedule.
566 | @param errorPtr If the notification cannot be scheduled or if it has been scheduled but some problem has been detected, upon return contains an instance of `NSError` that describes the problem.
567 | @return `YES` if the notification has been scheduled; `NO` otherwise.
568 | */
569 | - (BOOL)scheduleNotification:(nullable UILocalNotification *)notification
570 | withError:(NSError *_Nullable*_Nullable)errorPtr;
571 |
572 | /**
573 | Checks if a local notification can be scheduled.
574 |
575 | @param notification The local notification object that you want to check.
576 | @param recovery If `recovery` parameter is `NO`, any error generated will be considered enough to prevent the given notification from being scheduled; if `YES` is passed, only non-recoverable errors will.
577 | @param errorPtr If the notification cannot be scheduled or if some problem has been detected, upon return contains an instance of `NSError` that describes the problem.
578 | @return `YES` if the notification can be scheduled; `NO` otherwise.
579 | */
580 | - (BOOL)canScheduleNotification:(nullable UILocalNotification *)notification
581 | withRecovery:(BOOL)recovery
582 | error:(NSError *_Nullable*_Nullable)errorPtr;
583 |
584 | /**
585 | Creates an alert for displaying the given error.
586 |
587 | @param error `NSError` used for customizing alert message and buttons.
588 | @return An initialized alert controller object.
589 | */
590 | - (UIAlertController *)buildAlertControlForError:(NSError *)error;
591 |
592 | @end
593 |
594 | NS_ASSUME_NONNULL_END
595 |
--------------------------------------------------------------------------------
/MRLocalNotificationFacade/MRLocalNotificationFacade.m:
--------------------------------------------------------------------------------
1 | // MRLocalNotificationFacade.m
2 | //
3 | // Copyright (c) 2015 Héctor Marqués
4 | //
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 | //
12 | // The above copyright notice and this permission notice shall be included in
13 | // all copies or substantial portions of the Software.
14 | //
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | // THE SOFTWARE.
22 |
23 | #import "MRLocalNotificationFacade.h"
24 |
25 |
26 | NSString *const MRLocalNotificationErrorDomain = @"MRLocalNotificationErrorDomain";
27 |
28 | NSString *const MRRecoveryURLErrorKey = @"MRRecoveryURLErrorKey";
29 |
30 | static NSString *const kMRUserNotificationsRegisteredKey = @"kMRUserNotificationsRegisteredKey";
31 |
32 |
33 | #pragma mark - MRLocalNotificationFacadeAlertViewController_ -
34 |
35 |
36 | // `UIViewController` subclass with custom rotation handling.
37 | @interface MRLocalNotificationFacadeAlertViewController_ : UIViewController
38 | @end
39 |
40 |
41 | @implementation MRLocalNotificationFacadeAlertViewController_
42 |
43 | #pragma mark Private
44 |
45 | - (CGFloat)mr_degreesToRadians:(CGFloat const)degrees
46 | {
47 | return degrees*M_PI/180;
48 | }
49 |
50 | - (CGAffineTransform)mr_transformForOrientation:(UIInterfaceOrientation const)orientation
51 | {
52 | switch (orientation) {
53 |
54 | case UIInterfaceOrientationLandscapeLeft:
55 | return CGAffineTransformMakeRotation(-[self mr_degreesToRadians:90]);
56 |
57 | case UIInterfaceOrientationLandscapeRight:
58 | return CGAffineTransformMakeRotation([self mr_degreesToRadians:90]);
59 |
60 | case UIInterfaceOrientationPortraitUpsideDown:
61 | return CGAffineTransformMakeRotation([self mr_degreesToRadians:180]);
62 |
63 | case UIInterfaceOrientationPortrait:
64 | default:
65 | return CGAffineTransformMakeRotation([self mr_degreesToRadians:0]);
66 | }
67 | }
68 |
69 | #pragma mark NSNotification
70 |
71 | - (void)statusBarDidChangeFrame:(NSNotification *const)notification
72 | {
73 | UIApplication *const application = UIApplication.sharedApplication;
74 | UIInterfaceOrientation const orientation = [application statusBarOrientation];
75 | UIWindow *const window = self.view.window;
76 | window.transform = [self mr_transformForOrientation:orientation];
77 | dispatch_async(dispatch_get_main_queue(), ^{
78 | window.bounds = application.keyWindow.bounds;
79 | });
80 | }
81 |
82 | - (void)viewDidAppear:(BOOL const)animated
83 | {
84 | [super viewDidAppear:animated];
85 | UIApplication *const application = UIApplication.sharedApplication;
86 | UIInterfaceOrientation const orientation = [application statusBarOrientation];
87 | CGAffineTransform const transform = [self mr_transformForOrientation:orientation];
88 | UIWindow *const window = self.view.window;
89 | window.transform = transform;
90 | CGRect const frame = window.frame;
91 | window.frame = CGRectMake(0, 0, frame.size.width, frame.size.height);
92 | }
93 |
94 | #pragma mark - UIViewController
95 |
96 | - (BOOL)shouldAutorotate
97 | {
98 | return NO;
99 | }
100 |
101 | #pragma mark - NSObject
102 |
103 | - (instancetype)init
104 | {
105 | self = [super init];
106 | if (self) {
107 | NSNotificationCenter *const defaultCenter = NSNotificationCenter.defaultCenter;
108 | [defaultCenter addObserver:self
109 | selector:@selector(statusBarDidChangeFrame:)
110 | name:UIApplicationDidChangeStatusBarFrameNotification
111 | object:nil];
112 | }
113 | return self;
114 | }
115 |
116 | - (void)dealloc
117 | {
118 | NSNotificationCenter *const defaultCenter = NSNotificationCenter.defaultCenter;
119 | [defaultCenter removeObserver:self];
120 | }
121 |
122 | @end
123 |
124 |
125 | #pragma mark - MRLocalNotificationFacade -
126 |
127 |
128 | @interface MRLocalNotificationFacade ()
129 | @property (nonatomic, strong) UIApplication *defaultApplication;
130 | @property (nonatomic, copy) void(^onDidReceiveNotification)(UILocalNotification *n, BOOL *alert);
131 | @property (nonatomic, readwrite) BOOL hasRegisteredNotifications;
132 | @property (nonatomic, strong) NSTimeZone *defaultTimeZone;
133 | @property (nonatomic, strong) NSCalendar *defaultCalendar;
134 | @property (nonatomic, strong) UIViewController *defaultAlertPresenter;
135 | @property (nonatomic, copy) void(^onDidCancelNotificationAlert)(UILocalNotification *notification);
136 | @property (nonatomic, strong) NSMutableDictionary *actionHandlers;
137 | @property (nonatomic, strong) NSURL *contactSupportURL;
138 | @property (nonatomic, copy) void(^onDidCancelErrorAlert)(NSError *error);
139 | @end
140 |
141 |
142 | @implementation MRLocalNotificationFacade
143 |
144 | + (instancetype)defaultInstance
145 | {
146 | static MRLocalNotificationFacade *__defaultInstance;
147 | static dispatch_once_t onceToken;
148 | dispatch_once(&onceToken, ^{
149 | __defaultInstance = [[self alloc] init];
150 | });
151 | return __defaultInstance;
152 | }
153 |
154 | - (UIMutableUserNotificationAction *)buildAction:(NSString *const)identifier
155 | title:(NSString *const)title
156 | destructive:(BOOL const)isDestructive
157 | backgroundMode:(BOOL const)runsInBackground
158 | authentication:(BOOL const)authRequired
159 | {
160 | NSParameterAssert(identifier);
161 | NSParameterAssert(title);
162 | NSParameterAssert(runsInBackground || authRequired);
163 | UIMutableUserNotificationAction *const action =
164 | [[UIMutableUserNotificationAction alloc] init];
165 | if (runsInBackground) {
166 | action.activationMode = UIUserNotificationActivationModeBackground;
167 | } else {
168 | action.activationMode = UIUserNotificationActivationModeForeground;
169 | }
170 | action.title = title;
171 | action.identifier = identifier;
172 | action.destructive = isDestructive;
173 | action.authenticationRequired = authRequired;
174 | return action;
175 | }
176 |
177 | - (UIMutableUserNotificationCategory *)buildCategory:(NSString *const)identifier
178 | minimalActions:(NSArray *const)minimalActions
179 | defaultActions:(NSArray *const)defaultActions
180 | {
181 | NSParameterAssert(identifier);
182 | NSParameterAssert(minimalActions.count <= 2);
183 | NSParameterAssert(defaultActions.count <= 4);
184 | UIMutableUserNotificationCategory *const category =
185 | [[UIMutableUserNotificationCategory alloc] init];
186 | category.identifier = identifier;
187 | [category setActions:minimalActions forContext:UIUserNotificationActionContextMinimal];
188 | [category setActions:defaultActions forContext:UIUserNotificationActionContextDefault];
189 | return category;
190 | }
191 |
192 | - (void)customizeMinimalCategory:(UIMutableUserNotificationCategory *const)category
193 | action0:(UIUserNotificationAction *const)action0
194 | action1:(UIUserNotificationAction *const)action1
195 | {
196 | NSParameterAssert(category);
197 | NSMutableArray *const actions = [NSMutableArray arrayWithCapacity:4];
198 | if (action0) {
199 | [actions addObject:action0];
200 | }
201 | if (action1) {
202 | [actions addObject:action1];
203 | }
204 | [category setActions:actions forContext:UIUserNotificationActionContextMinimal];
205 | }
206 |
207 | - (void)customizeDefaultCategory:(UIMutableUserNotificationCategory *const)category
208 | action0:(UIUserNotificationAction *const)action0
209 | action1:(UIUserNotificationAction *const)action1
210 | action2:(UIUserNotificationAction *const)action2
211 | action3:(UIUserNotificationAction *const)action3
212 | {
213 | NSParameterAssert(category);
214 | NSMutableArray *const actions = [NSMutableArray arrayWithCapacity:4];
215 | if (action0) {
216 | [actions addObject:action0];
217 | }
218 | if (action1) {
219 | [actions addObject:action1];
220 | }
221 | if (action2) {
222 | [actions addObject:action2];
223 | }
224 | if (action3) {
225 | [actions addObject:action3];
226 | }
227 | [category setActions:actions forContext:UIUserNotificationActionContextDefault];
228 | }
229 |
230 | - (void)setNotificationHandler:(void(^const)(NSString *identifier, UILocalNotification *n))handler
231 | forActionWithIdentifier:(NSString *const)identifier
232 | {
233 | NSParameterAssert(identifier);
234 | if (handler) {
235 | void(^const handlerCopy)(NSString *, UILocalNotification *) = [handler copy];
236 | [self.actionHandlers setObject:handlerCopy forKey:identifier];
237 | } else {
238 | [self.actionHandlers removeObjectForKey:identifier];
239 | }
240 | }
241 |
242 | - (UILocalNotification *)buildNotificationWithDate:(NSDate *const)fireDate
243 | timeZone:(BOOL const)timeZone
244 | category:(NSString *const)category
245 | userInfo:(NSDictionary *const)userInfo
246 | {
247 | NSParameterAssert(fireDate);
248 | UILocalNotification *const notification = [[UILocalNotification alloc] init];
249 | notification.fireDate = fireDate;
250 | if (timeZone) {
251 | notification.timeZone = self.defaultTimeZone;
252 | }
253 | notification.category = category;
254 | notification.userInfo = userInfo;
255 | return notification;
256 | }
257 |
258 | - (UILocalNotification *)buildNotificationWithInterval:(NSTimeInterval)fireInterval
259 | category:(NSString *)category
260 | userInfo:(NSDictionary *)userInfo
261 | {
262 | NSParameterAssert(fireInterval >= 0);
263 | NSDate *const fireDate = [NSDate dateWithTimeIntervalSinceNow:fireInterval];
264 | return [self buildNotificationWithDate:fireDate
265 | timeZone:NO
266 | category:category
267 | userInfo:userInfo];
268 | }
269 |
270 | - (UILocalNotification *)buildNotificationWithRegion:(CLRegion *const)fireRegion
271 | triggersOnce:(BOOL const)regionTriggersOnce
272 | category:(NSString *const)category
273 | userInfo:(NSDictionary *const)userInfo
274 | {
275 | NSParameterAssert(fireRegion);
276 | UILocalNotification *const notification = [[UILocalNotification alloc] init];
277 | notification.region = fireRegion;
278 | notification.regionTriggersOnce = regionTriggersOnce;
279 | notification.category = category;
280 | notification.userInfo = userInfo;
281 | return notification;
282 | }
283 |
284 | - (void)customizeNotificationRepeat:(UILocalNotification *const)notification
285 | interval:(NSCalendarUnit const)calendarUnit
286 | {
287 | NSParameterAssert(notification);
288 | notification.repeatInterval = calendarUnit;
289 | notification.repeatCalendar = self.defaultCalendar;
290 | }
291 |
292 | - (void)customizeNotificationAlert:(UILocalNotification *const)notification
293 | title:(NSString *const)alertTitle
294 | body:(NSString *const)alertBody
295 | action:(NSString *const)alertAction
296 | launchImage:(NSString *const)alertLaunchImage
297 | {
298 | NSParameterAssert(notification);
299 | if ([notification respondsToSelector:@selector(setAlertTitle:)]) {
300 | notification.alertTitle = alertTitle;
301 | } else if (alertTitle.length > 0) {
302 | NSLog(@"setAlertTitle: not supported");
303 | }
304 | notification.alertBody = alertBody;
305 | notification.alertAction = alertAction;
306 | notification.hasAction = (alertAction.length > 0);
307 | notification.alertLaunchImage = alertLaunchImage;
308 | }
309 |
310 | - (void)customizeNotification:(UILocalNotification *const)notification
311 | appIconBadge:(NSInteger const)badgeNumber
312 | sound:(BOOL const)hasSound
313 | {
314 | NSParameterAssert(notification);
315 | notification.applicationIconBadgeNumber = badgeNumber;
316 | if (hasSound) {
317 | notification.soundName = self.defaultSoundName;
318 | }
319 | }
320 |
321 | - (void)presentNotificationNow:(UILocalNotification *const)notification
322 | {
323 | NSParameterAssert(notification);
324 | if (!NSThread.isMainThread) {
325 | NSLog(@"presenting notification from a thread other than the main thread");
326 | }
327 | UIApplication *const application = self.defaultApplication;
328 | [application presentLocalNotificationNow:notification];
329 | }
330 |
331 | - (void)scheduleNotification:(UILocalNotification *const)notification
332 | {
333 | NSParameterAssert(notification);
334 | UIApplication *const application = self.defaultApplication;
335 | [application scheduleLocalNotification:notification];
336 | }
337 |
338 | - (NSArray *)scheduledNotifications
339 | {
340 | UIApplication *const application = self.defaultApplication;
341 | NSArray *const localNotifications = application.scheduledLocalNotifications;
342 | return (localNotifications ?: @[]);
343 | }
344 |
345 | - (void)cancelNotification:(UILocalNotification *const)notification
346 | {
347 | UIApplication *const application = self.defaultApplication;
348 | if (notification) {
349 | [application cancelLocalNotification:notification];
350 | }
351 | }
352 |
353 | - (void)cancelAllNotifications
354 | {
355 | UIApplication *const application = self.defaultApplication;
356 | [application cancelAllLocalNotifications];
357 | }
358 |
359 | #pragma mark Accessors
360 |
361 | - (void)setDefaultApplication:(UIApplication *const)defaultApplication
362 | {
363 | [self willChangeValueForKey:@"defaultApplication"];
364 | _defaultApplication = defaultApplication;
365 | if (![defaultApplication isEqual:UIApplication.sharedApplication]) {
366 | NSLog(@"using %p instead of UIApplication.sharedApplication", defaultApplication);
367 | }
368 | [self didChangeValueForKey:@"defaultApplication"];
369 |
370 | }
371 |
372 | #pragma mark - NSObject
373 |
374 | - (instancetype)init
375 | {
376 | self = [super init];
377 | if (self) {
378 | _defaultApplication = UIApplication.sharedApplication;
379 | _defaultTimeZone = NSTimeZone.defaultTimeZone;
380 | _defaultCalendar = NSCalendar.autoupdatingCurrentCalendar;
381 | _defaultSoundName = UILocalNotificationDefaultSoundName;
382 | _actionHandlers = NSMutableDictionary.dictionary;
383 | NSUserDefaults *const userDefaults = NSUserDefaults.standardUserDefaults;
384 | _hasRegisteredNotifications = ([userDefaults boolForKey:kMRUserNotificationsRegisteredKey]
385 | || self.isRegisteredForNotifications);
386 | }
387 | return self;
388 | }
389 |
390 | @end
391 |
392 |
393 | #pragma mark - MRLocalNotificationFacade (UIUserNotificationSettings) -
394 |
395 |
396 | @implementation MRLocalNotificationFacade (UIUserNotificationSettings)
397 |
398 | - (void)registerForNotificationWithBadges:(BOOL const)badgeType
399 | alerts:(BOOL const)alertType
400 | sounds:(BOOL const)soundType
401 | categories:(NSSet *const)categories
402 | {
403 | UIUserNotificationType types = UIUserNotificationTypeNone;
404 | if (badgeType) {
405 | types |= UIUserNotificationTypeBadge;
406 | }
407 | if (alertType) {
408 | types |= UIUserNotificationTypeAlert;
409 | }
410 | if (soundType) {
411 | types |= UIUserNotificationTypeSound;
412 | }
413 | UIUserNotificationSettings *const settings =
414 | [UIUserNotificationSettings settingsForTypes:types
415 | categories:categories];
416 | UIApplication *const application = self.defaultApplication;
417 | [application registerUserNotificationSettings:settings];
418 | }
419 |
420 | - (UIUserNotificationSettings *)currentUserNotificationSettings
421 | {
422 | UIApplication *const application = self.defaultApplication;
423 | UIUserNotificationSettings *const settings = application.currentUserNotificationSettings;
424 | return settings;
425 | }
426 |
427 | - (UIUserNotificationCategory *)getCategoryForIdentifier:(NSString *const)categoryIdentifier
428 | fromUserNotificationSettings:(UIUserNotificationSettings *const)notificationSettings;
429 | {
430 | NSSet *const categories = notificationSettings.categories;
431 | for (UIUserNotificationCategory *const category in categories) {
432 | if ([category.identifier isEqual:categoryIdentifier]) {
433 | return category;
434 | }
435 | }
436 | return nil;
437 | }
438 |
439 | - (BOOL)isRegisteredForNotifications
440 | {
441 | UIUserNotificationSettings *const settings = self.currentUserNotificationSettings;
442 | UIUserNotificationType const types = settings.types;
443 | return (types != UIUserNotificationTypeNone);
444 | }
445 |
446 | - (BOOL)isBadgeTypeAllowed
447 | {
448 | UIUserNotificationSettings *const settings = self.currentUserNotificationSettings;
449 | return (settings.types & UIUserNotificationTypeBadge) == UIUserNotificationTypeBadge;
450 | }
451 |
452 | - (BOOL)isSoundTypeAllowed
453 | {
454 | UIUserNotificationSettings *const settings = self.currentUserNotificationSettings;
455 | return (settings.types & UIUserNotificationTypeSound) == UIUserNotificationTypeSound;
456 | }
457 |
458 | - (BOOL)isAlertTypeAllowed
459 | {
460 | UIUserNotificationSettings *const settings = self.currentUserNotificationSettings;
461 | return (settings.types & UIUserNotificationTypeAlert) == UIUserNotificationTypeAlert;
462 | }
463 |
464 | @end
465 |
466 |
467 | #pragma mark - MRLocalNotificationFacade (UIAlertController) -
468 |
469 |
470 | @implementation MRLocalNotificationFacade (UIAlertController)
471 |
472 | - (UIAlertController *)buildAlertControlForNotification:(UILocalNotification *const)notification
473 | {
474 | NSParameterAssert(notification);
475 | NSBundle *const mainBundle = NSBundle.mainBundle;
476 | NSDictionary *const localizedInfoDictionary = mainBundle.localizedInfoDictionary ?: mainBundle.infoDictionary;
477 | NSString *const bundleName = localizedInfoDictionary[(NSString *)kCFBundleNameKey];
478 | NSString *const alertBody = notification.alertBody;
479 | UIAlertControllerStyle const preferredStyle = UIAlertControllerStyleAlert;
480 | UIAlertController *const alert = [UIAlertController alertControllerWithTitle:bundleName
481 | message:alertBody
482 | preferredStyle:preferredStyle];
483 | UIUserNotificationSettings *const settings = self.currentUserNotificationSettings;
484 | NSString *const categoryIdentifier = notification.category;
485 | UIUserNotificationCategory *const category = [self getCategoryForIdentifier:categoryIdentifier
486 | fromUserNotificationSettings:settings];
487 | NSArray *const actions = [category actionsForContext:UIUserNotificationActionContextDefault];
488 | if (alertBody.length == 0 && actions == nil) {
489 | return nil;
490 | }
491 | __weak typeof(self) welf = self;
492 | for (UIUserNotificationAction *const notificationAction in actions) {
493 | UIAlertAction *const alertAction =
494 | [UIAlertAction actionWithTitle:notificationAction.title
495 | style:UIAlertActionStyleDefault
496 | handler:^(UIAlertAction *const alertAction) {
497 | [welf handleActionWithIdentifier:notificationAction.identifier
498 | forLocalNotification:notification
499 | completionHandler:nil];
500 | }];
501 | [alert addAction:alertAction];
502 | }
503 | UIAlertAction *const cancelAction =
504 | [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil)
505 | style:UIAlertActionStyleCancel
506 | handler:
507 | ^(UIAlertAction *const action) {
508 | void(^const onCancel)(UILocalNotification *) = welf.onDidCancelNotificationAlert;
509 | if (onCancel) {
510 | onCancel(notification);
511 | }
512 | }];
513 | [alert addAction:cancelAction];
514 | return alert;
515 | }
516 |
517 | - (void)showAlertController:(UIAlertController *const)alert
518 | {
519 | NSParameterAssert(alert);
520 | UIViewController *presentingViewController = self.defaultAlertPresenter;
521 | if (presentingViewController == nil) {
522 | UIApplication *const application = self.defaultApplication;
523 | UIWindow *const window = [[UIWindow alloc] initWithFrame:application.keyWindow.frame];
524 | window.windowLevel = UIWindowLevelAlert;
525 | presentingViewController = MRLocalNotificationFacadeAlertViewController_.new;
526 | window.rootViewController = presentingViewController;
527 | window.hidden = NO;
528 | }
529 | [presentingViewController presentViewController:alert animated:YES completion:nil];
530 | }
531 |
532 | @end
533 |
534 |
535 | #pragma mark - MRLocalNotificationFacade (UIApplication) -
536 |
537 |
538 | @implementation MRLocalNotificationFacade (UIApplication)
539 |
540 | - (UILocalNotification *)getNotificationFromLaunchOptions:(NSDictionary *const)launchOptions
541 | {
542 | UILocalNotification *notification;
543 | id const candidate = launchOptions[UIApplicationLaunchOptionsLocalNotificationKey];
544 | if ([candidate isKindOfClass:UILocalNotification.class]) {
545 | notification = candidate;
546 | }
547 | return notification;
548 | }
549 |
550 | - (NSInteger)applicationIconBadgeNumber
551 | {
552 | UIApplication *const application = self.defaultApplication;
553 | return application.applicationIconBadgeNumber;
554 | }
555 |
556 | - (void)setApplicationIconBadgeNumber:(NSInteger const)applicationIconBadgeNumber
557 | {
558 | UIApplication *const application = self.defaultApplication;
559 | application.applicationIconBadgeNumber = applicationIconBadgeNumber;
560 | }
561 |
562 | #pragma mark Private
563 |
564 | - (void)mr_setHasRegisteredLocalNotifications
565 | {
566 | NSUserDefaults *const userDefaults = NSUserDefaults.standardUserDefaults;
567 | [userDefaults setBool:YES forKey:kMRUserNotificationsRegisteredKey];
568 | [userDefaults synchronize];
569 | self.hasRegisteredNotifications = YES;
570 | }
571 |
572 | #pragma mark - UIApplicationDelegate
573 |
574 | - (void)handleDidRegisterUserNotificationSettings:(UIUserNotificationSettings *const)settings
575 | {
576 | if (settings == nil) {
577 | return;
578 | }
579 | [self mr_setHasRegisteredLocalNotifications];
580 | }
581 |
582 | - (void)handleDidReceiveLocalNotification:(UILocalNotification *const)notification
583 | {
584 | if (notification == nil) {
585 | return;
586 | }
587 | void(^const handler)(UILocalNotification *, BOOL *) = self.onDidReceiveNotification;
588 | UIApplication *const application = self.defaultApplication;
589 | BOOL shouldShowAlert = application.applicationState == UIApplicationStateActive;
590 | if (handler) {
591 | handler(notification, &shouldShowAlert);
592 | }
593 | if (shouldShowAlert) {
594 | UIAlertController *const alert = [self buildAlertControlForNotification:notification];
595 | if (alert) {
596 | [self showAlertController:alert];
597 | }
598 | }
599 | }
600 |
601 | - (void)handleActionWithIdentifier:(NSString *const)identifier
602 | forLocalNotification:(UILocalNotification *const)notification
603 | completionHandler:(void (^const)())completionHandler
604 | {
605 | if (identifier && notification) {
606 | void(^const handler)(NSString *, UILocalNotification *) = self.actionHandlers[identifier];
607 | if (handler) {
608 | handler(identifier, notification);
609 | }
610 | }
611 | if (completionHandler) {
612 | completionHandler();
613 | }
614 | }
615 |
616 | @end
617 |
618 |
619 | #pragma mark - MRLocalNotificationFacade (NSDate) -
620 |
621 |
622 | @implementation MRLocalNotificationFacade (NSDate)
623 |
624 | - (NSDate *)buildDateWithDay:(NSInteger const)day
625 | month:(NSInteger const)month
626 | year:(NSInteger const)year
627 | hour:(NSInteger const)hour
628 | minute:(NSInteger const)minute
629 | second:(NSInteger const)second
630 | {
631 | NSCalendar *const calendar = self.defaultCalendar;
632 | calendar.timeZone = self.defaultTimeZone;
633 | NSDateComponents *const dateComponents = [[NSDateComponents alloc] init];
634 | dateComponents.day = day;
635 | dateComponents.month = month;
636 | dateComponents.year = year;
637 | dateComponents.hour = hour;
638 | dateComponents.minute = minute;
639 | dateComponents.second = second;
640 | NSDate *const date = [calendar dateFromComponents:dateComponents];
641 | return date;
642 | }
643 |
644 | - (NSDate *)convertDateToDefaultTimeZone:(NSDate *const)gmtDate
645 | {
646 | NSParameterAssert(gmtDate);
647 | NSTimeZone *const timeZone = self.defaultTimeZone;
648 | NSDate *const timeDate = [self mr_convertDate:gmtDate
649 | toTimeZone:timeZone
650 | reverse:NO];
651 | return timeDate;
652 | }
653 |
654 | - (NSDate *)convertDateToGMT:(NSDate *const)timeDate
655 | {
656 | NSParameterAssert(timeDate);
657 | NSTimeZone *const timeZone = self.defaultTimeZone;
658 | NSDate *const gmtDate = [self mr_convertDate:timeDate
659 | toTimeZone:timeZone
660 | reverse:YES];
661 | return gmtDate;
662 | }
663 |
664 | - (void)date:(NSDate *const)date
665 | getDay:(NSInteger *const)day
666 | month:(NSInteger *const)month
667 | year:(NSInteger *const)year
668 | hour:(NSInteger *const)hour
669 | minute:(NSInteger *const)minute
670 | second:(NSInteger *const)second
671 | {
672 | NSParameterAssert(date);
673 | NSCalendar *const calendar = self.defaultCalendar;
674 | NSCalendarUnit const mask = (NSCalendarUnitSecond |
675 | NSCalendarUnitMinute |
676 | NSCalendarUnitHour |
677 | NSCalendarUnitDay |
678 | NSCalendarUnitMonth |
679 | NSCalendarUnitYear);
680 | NSDateComponents *const components = [calendar components:mask
681 | fromDate:date];
682 | if (second) {
683 | *second = components.second;
684 | }
685 | if (minute) {
686 | *minute = components.minute;
687 | }
688 | if (hour) {
689 | *hour = components.hour;
690 | }
691 | if (day) {
692 | *day = components.day;
693 | }
694 | if (month) {
695 | *month = components.month;
696 | }
697 | if (year) {
698 | *year = components.year;
699 | }
700 | }
701 |
702 | - (NSDate *)getGMTFireDateFromNotification:(UILocalNotification *const)notification
703 | {
704 | NSParameterAssert(notification);
705 | NSDate *gmtDate;
706 | NSTimeZone *const timeZone = notification.timeZone;
707 | NSDate *const fireDate = notification.fireDate;
708 | if (timeZone) {
709 | gmtDate = [self mr_convertDate:fireDate
710 | toTimeZone:timeZone
711 | reverse:YES];
712 | } else {
713 | gmtDate = notification.fireDate;
714 | }
715 | return gmtDate;
716 | }
717 |
718 | #pragma mark Private
719 |
720 | - (NSDate *)mr_convertDate:(NSDate *const)date
721 | toTimeZone:(NSTimeZone *const)timeZone
722 | reverse:(BOOL const)reverse
723 | {
724 | NSParameterAssert(date);
725 | NSParameterAssert(timeZone);
726 | NSInteger const seconds = [timeZone secondsFromGMTForDate:date];
727 | NSInteger const signedSeconds = (reverse ? -1 : 1)*seconds;
728 | NSDate *const convertedDate = [NSDate dateWithTimeInterval:signedSeconds
729 | sinceDate:date];
730 | return convertedDate;
731 | }
732 |
733 | @end
734 |
735 |
736 | #pragma mark - MRLocalNotificationFacade (NSErrorRecoveryAttempting) -
737 |
738 |
739 | @implementation MRLocalNotificationFacade (NSErrorRecoveryAttempting)
740 |
741 | - (BOOL)presentNotificationNow:(UILocalNotification *const)notification
742 | withError:(NSError **const)errorPtr
743 | {
744 | BOOL const recoverable = [self canPresentNotificationNow:notification
745 | error:errorPtr];
746 | if (recoverable) {
747 | UIApplication *const application = self.defaultApplication;
748 | switch (application.applicationState) {
749 | case UIApplicationStateActive:
750 | NSLog(@"presenting notification in active state");
751 | break;
752 | case UIApplicationStateInactive:
753 | NSLog(@"presenting notification in inactive state");
754 | break;
755 | case UIApplicationStateBackground:
756 | // expected case
757 | break;
758 | }
759 | [self presentNotificationNow:notification];
760 | }
761 | return recoverable;
762 | }
763 |
764 | - (BOOL)canPresentNotificationNow:(UILocalNotification *const)notification
765 | error:(NSError **const)errorPtr
766 | {
767 | BOOL const canSchedule = [self mr_isNotificationValid:notification
768 | withRecovery:NO
769 | error:errorPtr];
770 | return canSchedule;
771 | }
772 |
773 | - (BOOL)scheduledNotificationsContainsNotification:(UILocalNotification *const)notification
774 | {
775 | NSParameterAssert(notification);
776 | NSArray *const scheduledNotifications = self.scheduledNotifications;
777 | for (UILocalNotification *const scheduledNotification in scheduledNotifications) {
778 | if ([scheduledNotification isEqual:notification]) {
779 | return YES;
780 | }
781 | }
782 | return NO;
783 | }
784 |
785 | - (BOOL)scheduleNotification:(UILocalNotification *const)notification
786 | withError:(NSError **const)errorPtr
787 | {
788 | BOOL const recoverable = [self canScheduleNotification:notification
789 | withRecovery:YES
790 | error:errorPtr];
791 | if (recoverable) {
792 | [self scheduleNotification:notification];
793 | if (![self scheduledNotificationsContainsNotification:notification]) {
794 | NSLog(@"notification not scheduled yet");
795 | }
796 | }
797 | return recoverable;
798 | }
799 |
800 | - (BOOL)canScheduleNotification:(UILocalNotification *const)notification
801 | withRecovery:(BOOL const)recovery
802 | error:(NSError **const)errorPtr
803 | {
804 | NSError *error;
805 | BOOL recoverable = [self mr_isNotificationValid:notification
806 | withRecovery:recovery
807 | error:&error];
808 | if (recoverable && [self getGMTFireDateFromNotification:notification].timeIntervalSinceNow < 0) {
809 | recoverable = [self mr_buildError:&error
810 | withCode:MRLocalNotificationErrorInvalidDate];
811 | }
812 | if (recoverable && notification.region == nil && notification.fireDate == nil) {
813 | recoverable = [self mr_buildError:&error
814 | withCode:MRLocalNotificationErrorMissingDate];
815 | }
816 | if (recoverable && [self scheduledNotificationsContainsNotification:notification]) {
817 | recoverable = [self mr_buildError:&error
818 | withCode:MRLocalNotificationErrorAlreadyScheduled];
819 | }
820 | if (error && errorPtr) {
821 | *errorPtr = error;
822 | }
823 | BOOL const canSchedule = (recovery ? recoverable : error == nil);
824 | return canSchedule;
825 | }
826 |
827 | - (UIAlertController *)buildAlertControlForError:(NSError *const)error
828 | {
829 | NSParameterAssert(error);
830 | NSError *const underlyingError = error.userInfo[NSUnderlyingErrorKey];
831 | NSString *const title = error.localizedDescription ?:
832 | underlyingError.localizedDescription;
833 | NSMutableString *const message = @"\n".mutableCopy;
834 | NSString *const failureReason = (error.localizedFailureReason ?:
835 | underlyingError.localizedFailureReason);
836 | if (failureReason.length > 0) {
837 | [message appendString:failureReason];
838 | [message appendString:@"\n"];
839 | }
840 | NSString *const recoverySuggestion = error.localizedRecoverySuggestion;
841 | if (recoverySuggestion) {
842 | [message appendString:@"\n"];
843 | [message appendString:recoverySuggestion];
844 | }
845 | UIAlertControllerStyle const preferredStyle = UIAlertControllerStyleAlert;
846 | UIAlertController *const alert = [UIAlertController alertControllerWithTitle:title
847 | message:message
848 | preferredStyle:preferredStyle];
849 | NSArray *const recoveryOptions = error.localizedRecoveryOptions;
850 | NSObject *const recoveryAttempter = error.recoveryAttempter;
851 | __weak typeof(self) welf = self;
852 | NSInteger optionIndex = 0;
853 | for (NSString *const recoveryOption in recoveryOptions) {
854 | UIAlertAction *const alertAction =
855 | [UIAlertAction actionWithTitle:recoveryOption
856 | style:UIAlertActionStyleDefault
857 | handler:^(UIAlertAction *const alertAction) {
858 | [recoveryAttempter attemptRecoveryFromError:error
859 | optionIndex:optionIndex];
860 | }];
861 | [alert addAction:alertAction];
862 | optionIndex += 1;
863 | }
864 | NSString *const helpAnchor = error.helpAnchor;
865 | if (helpAnchor.length > 0) {
866 | UIAlertAction *const helpAction =
867 | [UIAlertAction actionWithTitle:NSLocalizedString(@"Help", nil)
868 | style:UIAlertActionStyleDefault
869 | handler:
870 | ^(UIAlertAction *const action) {
871 | UIAlertController *const helpAlert =
872 | [UIAlertController alertControllerWithTitle:nil
873 | message:helpAnchor
874 | preferredStyle:preferredStyle];
875 | UIAlertAction *const cancelHelpAction =
876 | [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil)
877 | style:UIAlertActionStyleCancel
878 | handler:
879 | ^(UIAlertAction *const action) {
880 | [self showAlertController:alert];
881 | }];
882 | [helpAlert addAction:cancelHelpAction];
883 | [self showAlertController:helpAlert];
884 | }];
885 | [alert addAction:helpAction];
886 | }
887 | UIAlertAction *const cancelAction =
888 | [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil)
889 | style:UIAlertActionStyleCancel
890 | handler:
891 | ^(UIAlertAction *const action) {
892 | void(^const onCancel)(NSError *) = welf.onDidCancelErrorAlert;
893 | if (onCancel) {
894 | onCancel(error);
895 | }
896 | }];
897 | [alert addAction:cancelAction];
898 | return alert;
899 | }
900 |
901 | #pragma mark Private
902 |
903 | - (BOOL)mr_isNotificationValid:(UILocalNotification *const)notification
904 | withRecovery:(BOOL const)recovery
905 | error:(NSError **const)errorPtr
906 | {
907 | NSError *error;
908 | BOOL recoverable = YES;
909 | if (notification == nil) {
910 | recoverable = [self mr_buildError:&error
911 | withCode:MRLocalNotificationErrorNilObject];
912 | }
913 | if (recoverable && ![notification isKindOfClass:UILocalNotification.class]) {
914 | recoverable = [self mr_buildError:&error
915 | withCode:MRLocalNotificationErrorInvalidObject];
916 | }
917 | if (recoverable && notification.alertBody.length == 0 && notification.applicationIconBadgeNumber <= 0) {
918 | recoverable = [self mr_buildError:&error
919 | withCode:MRLocalNotificationErrorMissingAlertBody];
920 | }
921 | if (recoverable && notification.soundName && !self.isSoundTypeAllowed) {
922 | recoverable = [self mr_buildError:&error
923 | withCode:MRLocalNotificationErrorSoundNotAllowed];
924 | }
925 | if (recoverable && notification.applicationIconBadgeNumber != 0 && !self.isBadgeTypeAllowed) {
926 | recoverable = [self mr_buildError:&error
927 | withCode:MRLocalNotificationErrorBadgeNotAllowed];
928 | }
929 | if (recoverable && notification.alertBody && !self.isAlertTypeAllowed) {
930 | recoverable = [self mr_buildError:&error
931 | withCode:MRLocalNotificationErrorAlertNotAllowed];
932 | }
933 | if (recoverable && !self.isRegisteredForNotifications) {
934 | recoverable = [self mr_buildError:&error
935 | withCode:MRLocalNotificationErrorNoneAllowed];
936 | }
937 | if (recoverable) {
938 | NSString *const category = notification.category;
939 | UIUserNotificationSettings *const settings = self.currentUserNotificationSettings;
940 | if (category && [self getCategoryForIdentifier:category
941 | fromUserNotificationSettings:settings] == nil) {
942 | recoverable = [self mr_buildError:&error
943 | withCode:MRLocalNotificationErrorCategoryNotRegistered];
944 | }
945 | }
946 | if (error && errorPtr) {
947 | *errorPtr = error;
948 | }
949 | BOOL const isValid = (recovery ? recoverable : error == nil);
950 | return isValid;
951 | }
952 |
953 | - (BOOL)mr_buildError:(NSError **const)errorPtr withCode:(MRLocalNotificationErrorCode const)code
954 | {
955 | BOOL validNotification;
956 | NSString *description;
957 | NSString *recoverySuggestion;
958 | NSArray *recoveryOptions;
959 | UIApplication *const application = self.defaultApplication;
960 | BOOL contactSupport = NO;
961 | NSURL *recoveryURL;
962 | if ([self mr_isNonRecoverableErrorCode:code]) {
963 | validNotification = NO;
964 | description = NSLocalizedString(@"Error scheduling notification", nil);
965 | NSURL *const contactSupportURL = self.contactSupportURL;
966 | contactSupport = (contactSupportURL && [application canOpenURL:contactSupportURL]);
967 | } else {
968 | validNotification = YES;
969 | description = NSLocalizedString(@"Local notifications", nil);
970 | if (code == MRLocalNotificationErrorCategoryNotRegistered) {
971 | NSURL *const contactSupportURL = self.contactSupportURL;
972 | contactSupport = (contactSupportURL && [application canOpenURL:contactSupportURL]);
973 | }
974 | }
975 | if (contactSupport) {
976 | recoveryOptions = @[ NSLocalizedString(@"Contact Support", nil) ];
977 | recoverySuggestion = NSLocalizedString(@"If the problem persists, please contact support.", nil);
978 | recoveryURL = self.contactSupportURL;
979 | } else {
980 | recoverySuggestion = NSLocalizedString(@"Please go to Settings and enable missing notification types.", nil);
981 | recoveryOptions = @[ NSLocalizedString(@"Settings", nil) ];
982 | recoveryURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
983 | }
984 | NSString *const failureReason = [self mr_localizedFailureReasonForCode:code];
985 | NSDictionary * const userInfo = @{ NSLocalizedDescriptionKey: description,
986 | NSLocalizedFailureReasonErrorKey: failureReason,
987 | NSLocalizedRecoverySuggestionErrorKey: recoverySuggestion,
988 | NSLocalizedRecoveryOptionsErrorKey: recoveryOptions,
989 | NSRecoveryAttempterErrorKey: self,
990 | MRRecoveryURLErrorKey: recoveryURL };
991 | NSError *const error = [NSError errorWithDomain:MRLocalNotificationErrorDomain
992 | code:code
993 | userInfo:userInfo];
994 | if (errorPtr) {
995 | *errorPtr = error;
996 | }
997 | return validNotification;
998 | }
999 |
1000 | - (BOOL)mr_isNonRecoverableErrorCode:(MRLocalNotificationErrorCode const)code
1001 | {
1002 | return (code == MRLocalNotificationErrorUnknown ||
1003 | code == MRLocalNotificationErrorNilObject ||
1004 | code == MRLocalNotificationErrorInvalidObject ||
1005 | code == MRLocalNotificationErrorInvalidDate ||
1006 | code == MRLocalNotificationErrorMissingDate ||
1007 | code == MRLocalNotificationErrorAlreadyScheduled ||
1008 | code == MRLocalNotificationErrorMissingAlertBody );
1009 | }
1010 |
1011 | - (NSString *)mr_localizedFailureReasonForCode:(MRLocalNotificationErrorCode const)code
1012 | {
1013 | NSString *failureReason;
1014 | if (code == MRLocalNotificationErrorNoneAllowed) {
1015 | failureReason = NSLocalizedString(@"Notifications will not work if you do not enable them in Settings.", nil);
1016 | } else if (code == MRLocalNotificationErrorSoundNotAllowed) {
1017 | failureReason = NSLocalizedString(@"The sounds of the notifications will not be played if you do not allow them in Settings.", nil);
1018 | } else if (code == MRLocalNotificationErrorAlertNotAllowed) {
1019 | failureReason = NSLocalizedString(@"Notifications will not display any alert dialog if you do not allow them in Settings.", nil);
1020 | } else if (code == MRLocalNotificationErrorBadgeNotAllowed) {
1021 | failureReason = NSLocalizedString(@"Notifications will not update application's badge number if you do not allow them in Settings.", nil);
1022 | } else if (code == MRLocalNotificationErrorNilObject) {
1023 | failureReason = NSLocalizedString(@"No notification provided.", nil);
1024 | } else if (code == MRLocalNotificationErrorInvalidDate) {
1025 | failureReason = NSLocalizedString(@"Notification fire date is not valid.", nil);
1026 | } else if (code == MRLocalNotificationErrorMissingDate) {
1027 | failureReason = NSLocalizedString(@"Notification is missing a fire date or region.", nil);
1028 | } else if (code == MRLocalNotificationErrorAlreadyScheduled) {
1029 | failureReason = NSLocalizedString(@"Notification is already scheduled.", nil);
1030 | } else if (code == MRLocalNotificationErrorInvalidObject) {
1031 | failureReason = NSLocalizedString(@"Notification provided is not valid.", nil);
1032 | } else if (code == MRLocalNotificationErrorCategoryNotRegistered) {
1033 | failureReason = NSLocalizedString(@"Notification scheduled, but its actions group is not registered.", nil);
1034 | } else if (code == MRLocalNotificationErrorMissingAlertBody) {
1035 | failureReason = NSLocalizedString(@"Notification is missing an alert body or icon badge number.", nil);
1036 | } else {
1037 | NSAssert(code == MRLocalNotificationErrorUnknown, @"unhandled code");
1038 | failureReason = NSLocalizedString(@"Unknown error.", nil);
1039 | }
1040 | return failureReason;
1041 | }
1042 |
1043 | #pragma mark - NSErrorRecoveryAttempting
1044 |
1045 | - (void)setContactSupportURLWithEmailAddress:(NSString *const)emailAddress
1046 | subject:(NSString *const)subject
1047 | body:(NSString *const)body
1048 | {
1049 | NSParameterAssert(emailAddress);
1050 | NSString *const scheme = @"mailto";
1051 | NSMutableString *const url = [NSMutableString stringWithFormat:@"%@:%@", scheme, emailAddress];
1052 | NSMutableArray *const queryComponents = [NSMutableArray arrayWithCapacity:2];
1053 | if (subject.length > 0) {
1054 | [queryComponents addObject:[NSString stringWithFormat:@"subject=%@", subject]];
1055 | }
1056 | if (body.length > 0) {
1057 | [queryComponents addObject:[NSString stringWithFormat:@"body=%@", body]];
1058 | }
1059 | if (queryComponents.count > 0) {
1060 | NSCharacterSet *const charSet = NSCharacterSet.URLQueryAllowedCharacterSet;
1061 | NSString *const query = [queryComponents componentsJoinedByString:@"&"];
1062 | NSString *encodedQuery = [query stringByAddingPercentEncodingWithAllowedCharacters:charSet];
1063 | [url appendFormat:@"?%@", encodedQuery];
1064 | }
1065 | self.contactSupportURL = [NSURL URLWithString:url];
1066 | }
1067 |
1068 | - (void)attemptRecoveryFromError:(NSError *const)error
1069 | optionIndex:(NSUInteger const)recoveryOptionIndex
1070 | delegate:(id const)target
1071 | didRecoverSelector:(SEL const)didRecoverSelector
1072 | contextInfo:(void *)contextInfo
1073 | {
1074 | NSParameterAssert(error);
1075 | NSParameterAssert(target == nil || didRecoverSelector);
1076 | BOOL const didRecover = [self attemptRecoveryFromError:error optionIndex:recoveryOptionIndex];
1077 | if (target) {
1078 | NSMethodSignature *const signature = [target methodSignatureForSelector:didRecoverSelector];
1079 | NSInvocation *const invocation = [NSInvocation invocationWithMethodSignature:signature];
1080 | invocation.selector = didRecoverSelector;
1081 | [invocation setArgument:(void *)&didRecover atIndex:2];
1082 | [invocation setArgument:&contextInfo atIndex:3];
1083 | [invocation invokeWithTarget:target];
1084 | }
1085 | }
1086 |
1087 | - (BOOL)attemptRecoveryFromError:(NSError *const)error
1088 | optionIndex:(NSUInteger const)recoveryOptionIndex
1089 | {
1090 |
1091 | NSParameterAssert(error);
1092 | BOOL completed = NO;
1093 | NSURL *const URL = error.userInfo[MRRecoveryURLErrorKey];
1094 | UIApplication *const application = self.defaultApplication;
1095 | if (URL && [application canOpenURL:URL]) {
1096 | completed = [application openURL:URL];
1097 | }
1098 | return completed;
1099 | }
1100 |
1101 | @end
1102 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](http://cocoapods.org/pods/MRLocalNotificationFacade)
2 | [](http://cocoapods.org/pods/MRLocalNotificationFacade)
3 | [](http://cocoapods.org/pods/MRLocalNotificationFacade)
4 |
5 | Overview
6 | ========
7 |
8 | `MRLocalNotificationFacade` is a class that wraps most of the APIs required for dealing with local notifications in *iOS*:
9 |
10 | - **Registration of user notification settings** without direct manipulation of `UIUserNotificationSettings` objects.
11 |
12 | ```objc
13 | - (void)registerForNotificationWithBadges:(BOOL)badgeType alerts:(BOOL)alertType sounds:(BOOL)soundType categories:(NSSet *)categories;
14 | - (BOOL)isBadgeTypeAllowed;
15 | - (BOOL)isSoundTypeAllowed;
16 | - (BOOL)isAlertTypeAllowed;
17 | // etc.
18 | ```
19 |
20 | - **Error aware notification scheduling** with `NSError` object that you can inspect or display to the user.
21 |
22 | ```objc
23 | - (BOOL)scheduleNotification:(UILocalNotification *)notification withError:(NSError **)errorPtr;
24 | - (UIAlertController *)buildAlertControlForError:(NSError *)error;
25 |
26 | ```
27 |
28 | - **App delegate methods handling**.
29 |
30 | ```objc
31 | // application:didRegisterUserNotificationSettings:
32 | - (void)handleDidRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings;
33 | // application:didReceiveLocalNotification:
34 | - (void)handleDidReceiveLocalNotification:(UILocalNotification *)notification;
35 | // application:handleActionWithIdentifier:forLocalNotification:completionHandler:
36 | - (void)handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:(void (^)())completionHandler;
37 | // etc.
38 | ```
39 |
40 | - **Display of local notifications** when the application is in `UIApplicationStateActive` state.
41 |
42 | ```objc
43 | - (UIAlertController *)buildAlertControlForNotification:(UILocalNotification *)notification;
44 | - (void)showAlertController:(UIAlertController *)alert;
45 | ```
46 |
47 | - **Creation and manipulation of date objects**.
48 |
49 | ```objc
50 | - (NSDate *)buildDateWithDay:(NSInteger)day month:(NSInteger)month year:(NSInteger)year hour:(NSInteger)hour minute:(NSInteger)minute second:(NSInteger)second;
51 | - (NSDate *)getGMTFireDateFromNotification:(UILocalNotification *)notification;
52 | // etc.
53 | ```
54 |
55 | - **Creation and customization of notifications, categories and actions**.
56 |
57 | ```objc
58 | - (UILocalNotification *)buildNotificationWithDate:(NSDate *)fireDate timeZone:(BOOL)timeZone category:(NSString *)category userInfo:(NSDictionary *)userInfo;
59 | - (UILocalNotification *)buildNotificationWithRegion:(CLRegion *)fireRegion triggersOnce:(BOOL)regionTriggersOnce category:(NSString *)category userInfo:(NSDictionary *)userInfo;
60 | - (UIMutableUserNotificationAction *)buildAction:(NSString *)identifier title:(NSString *)title destructive:(BOOL)isDestructive backgroundMode:(BOOL)runsInBackground authentication:(BOOL)authRequired;
61 | - (UIMutableUserNotificationCategory *)buildCategory:(NSString *)identifier minimalActions:(NSArray *)minimalActions defaultActions:(NSArray *)defaultActions;
62 | // etc.
63 | ```
64 |
65 | 
66 |
67 | Getting started
68 | ===============
69 |
70 | Installation
71 | ------------
72 |
73 | ### CocoaPods
74 |
75 | To add **MRLocalNotificationFacade** to your app, add `pod "MRLocalNotificationFacade"` to your *Podfile*.
76 |
77 | ### Manually
78 |
79 | Copy the *MRLocalNotificationFacade* directory into your project.
80 |
81 | Usage
82 | -----
83 |
84 | You do pretty much the same you would do if you were not using *MRLocalNotificationFacade*, but using it ;)
85 |
86 | ### Register for notifications
87 |
88 | First you invoke **MRLocalNotificationFacade** handlers from the app delegate methods:
89 |
90 | ```objc
91 | @implementation AppDelegate
92 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)options {
93 | MRLocalNotificationFacade *notificationFacade = MRLocalNotificationFacade.defaultInstance;
94 | [notificationFacade setContactSuportURLWithEmailAddress:@"support@example.com"];
95 | UILocalNotification *notification = [notificationFacade getNotificationFromLaunchOptions:options];
96 | [notificationFacade handleDidReceiveLocalNotification:notification];
97 | return YES;
98 | }
99 | - (void)application:(UIApplication *)app didReceiveLocalNotification:(UILocalNotification *)notification {
100 | MRLocalNotificationFacade *notificationFacade = MRLocalNotificationFacade.defaultInstance;
101 | [notificationFacade handleDidReceiveLocalNotification:notification];
102 | }
103 | - (void)application:(UIApplication *)app didRegisterUserNotificationSettings:(UIUserNotificationSettings *)settings {
104 | MRLocalNotificationFacade *notificationFacade = MRLocalNotificationFacade.defaultInstance;
105 | [notificationFacade handleDidRegisterUserNotificationSettings:settings];
106 | }
107 | - (void)application:(UIApplication *)app handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:(void (^)())handler {
108 | MRLocalNotificationFacade *notificationFacade = MRLocalNotificationFacade.defaultInstance;
109 | [notificationFacade handleActionWithIdentifier:identifier forLocalNotification:notification completionHandler:handler];
110 | }
111 | @end
112 | ```
113 |
114 | Then you just register your preferred options for notifying the user at your best convenience:
115 |
116 | ```objc
117 | - (IBAction)registerForNotificationsAction:(id)sender {
118 | MRLocalNotificationFacade *notificationFacade = MRLocalNotificationFacade.defaultInstance;
119 | NSSet *categories = ...; // use notificationFacade for creating your action groups
120 | [notificationFacade registerForNotificationWithBadges:YES alerts:YES sounds:YES categories:categories];
121 | }
122 | ```
123 |
124 | ### Schedule notification
125 |
126 | You just proceed to create the notification and schedule it using the several `build*` and `customize*` methods available in `MRLocalNotificationFacade`:
127 |
128 | ```objc
129 | - (void)scheduleNotification:(NSString *)text date:(NSDate *)date category:(NSString *)category {
130 | MRLocalNotificationFacade *notificationFacade = MRLocalNotificationFacade.defaultInstance;
131 | UILocalNotification *notification = [notificationFacade buildNotificationWithDate:date
132 | timeZone:NO
133 | category:category
134 | userInfo:nil];
135 | [notificationFacade customizeNotificationAlert:notification
136 | title:nil
137 | body:text
138 | action:nil
139 | launchImage:nil];
140 | NSError *error;
141 | BOOL scheduled = [notificationFacade scheduleNotification:notification
142 | withError:&error];
143 | if (error && scheduled) {
144 | // if the user needs to change settings, the recovery attempter will handle this
145 | UIAlertController *alert = [notificationFacade buildAlertControlForError:error];
146 | [notificationFacade showAlertController:alert];
147 | } else {
148 | // this is bad, maybe you prefer to do something else...
149 | UIAlertController *alert = [notificationFacade buildAlertControlForError:error];
150 | [notificationFacade showAlertController:alert];
151 | }
152 | }
153 | ```
154 |
155 | ### Check notification validity
156 |
157 | Even if you are already handling your app's local notifications, you can still use *MRLocalNotificationFacade* for checking your local notification objects validity:
158 |
159 | ```objc
160 | NSError *error;
161 | BOOL canSchedule = [notificationFacade canScheduleNotification:notification
162 | withRecovery:YES
163 | error:&error];
164 | if (canSchedule && error) {
165 | // user needs to change settings or the app has to register notification's category
166 | UIAlertController *alert = [notificationFacade buildAlertControlForError:error];
167 | [notificationFacade showAlertController:alert];
168 | } else if (!canSchedule) {
169 | // notification is not valid
170 | NSAssert(NO, @"unhandled error: %@", error);
171 | }
172 | ```
173 |
174 | License
175 | =======
176 |
177 | **MRLocalNotificationFacade** is available under the MIT license. See the *LICENSE* file for more info.
178 |
--------------------------------------------------------------------------------
/Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/notification.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hectr/MRLocalNotificationFacade/453a72202a05bfeb9662529e361dbc4d3ce58fec/notification.jpg
--------------------------------------------------------------------------------