├── .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 | [![Version](https://img.shields.io/cocoapods/v/MRLocalNotificationFacade.svg?style=flat)](http://cocoapods.org/pods/MRLocalNotificationFacade) 2 | [![License](https://img.shields.io/cocoapods/l/MRLocalNotificationFacade.svg?style=flat)](http://cocoapods.org/pods/MRLocalNotificationFacade) 3 | [![Platform](https://img.shields.io/cocoapods/p/MRLocalNotificationFacade.svg?style=flat)](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 | ![Notification example](notification.jpg?raw=true "Notification example") 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 --------------------------------------------------------------------------------