From 9cdf001a8d306dc89bf85e60811d2d0aef9bd28a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Tue, 6 Aug 2019 22:06:57 -0700 Subject: [PATCH] Upgraded notifications to UserNotifications framework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lock screen notifications are presented more reliably and more closely resemble instruction banners. Factored out a method that produces a UIImage representing a visual instruction’s maneuver. --- CHANGELOG.md | 1 + .../NavigationViewController.swift | 49 +++++++++++-------- MapboxNavigation/VisualInstruction.swift | 20 +++++--- 3 files changed, 42 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5b15c5f29..2b20295cd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## master * This library now requires a minimum deployment target of iOS 10.0 or above. iOS 9._x_ is no longer supported. ([#2206](https://github.com/mapbox/mapbox-navigation-ios/pull/2206)) +* Lock screen notifications are presented more reliably and more closely resemble instruction banners. ([#2206](https://github.com/mapbox/mapbox-navigation-ios/pull/2206)) * Fixed an issue where swiping the banner down after the StepsTableViewController has already displayed could put the UI in an unstable state. ([#2197](https://github.com/mapbox/mapbox-navigation-ios/pull/2197)) ## v0.36.0 diff --git a/MapboxNavigation/NavigationViewController.swift b/MapboxNavigation/NavigationViewController.swift index c270139546..a01ad8aba7 100644 --- a/MapboxNavigation/NavigationViewController.swift +++ b/MapboxNavigation/NavigationViewController.swift @@ -3,6 +3,8 @@ import MapboxCoreNavigation import MapboxDirections import MapboxSpeech import AVFoundation +import UserNotifications +import MobileCoreServices import Mapbox /** @@ -324,28 +326,31 @@ open class NavigationViewController: UIViewController, NavigationStatusPresenter // MARK: Route controller notifications - func scheduleLocalNotification(about step: RouteStep) { + func scheduleLocalNotification(about step: RouteStep, identifier: String) { guard sendsNotifications else { return } guard UIApplication.shared.applicationState == .background else { return } - guard let text = step.instructionsSpokenAlongStep?.last?.text else { return } + guard let instruction = step.instructionsDisplayedAlongStep?.last else { return } - let notification = UILocalNotification() - notification.alertBody = text - notification.fireDate = Date() + let content = UNMutableNotificationContent() + if let primaryText = instruction.primaryInstruction.text { + content.title = primaryText + } + if let secondaryText = instruction.secondaryInstruction?.text { + content.subtitle = secondaryText + } - clearStaleNotifications() + if let image = instruction.primaryInstruction.maneuverImage(side: instruction.drivingSide, color: .black, size: CGSize(width: 72, height: 72)), + let imageData = image.pngData() { + let temporaryURL = FileManager.default.temporaryDirectory.appendingPathComponent("com.mapbox.navigation.notification-icon.png") + do { + try imageData.write(to: temporaryURL) + let iconAttachment = try UNNotificationAttachment(identifier: "maneuver", url: temporaryURL, options: [UNNotificationAttachmentOptionsTypeHintKey: kUTTypePNG]) + content.attachments = [iconAttachment] + } catch {} + } - UIApplication.shared.cancelAllLocalNotifications() - UIApplication.shared.scheduleLocalNotification(notification) - } - - func clearStaleNotifications() { - guard sendsNotifications else { return } - // Remove all outstanding notifications from notification center. - // This will only work if it's set to 1 and then back to 0. - // This way, there is always just one notification. - UIApplication.shared.applicationIconBadgeNumber = 1 - UIApplication.shared.applicationIconBadgeNumber = 0 + let notificationRequest = UNNotificationRequest(identifier: identifier, content: content, trigger: nil) + UNUserNotificationCenter.current().add(notificationRequest, withCompletionHandler: nil) } public func showStatus(title: String, spinner: Bool, duration: TimeInterval, animated: Bool, interactive: Bool) { @@ -502,10 +507,14 @@ extension NavigationViewController: NavigationServiceDelegate { component.navigationService?(service, didPassSpokenInstructionPoint: instruction, routeProgress: routeProgress) } - clearStaleNotifications() + // Remove any notification about an already complete maneuver, even if there isn’t another notification to replace it with yet. + let notificationIdentifier = "instruction" + UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [notificationIdentifier]) + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [notificationIdentifier]) - if routeProgress.currentLegProgress.currentStepProgress.durationRemaining <= RouteControllerHighAlertInterval { - scheduleLocalNotification(about: routeProgress.currentLegProgress.currentStep) + let legProgress = routeProgress.currentLegProgress + if legProgress.currentStepProgress.currentSpokenInstruction == legProgress.currentStep.instructionsSpokenAlongStep?.last { + scheduleLocalNotification(about: legProgress.currentStep, identifier: notificationIdentifier) } } diff --git a/MapboxNavigation/VisualInstruction.swift b/MapboxNavigation/VisualInstruction.swift index d7531959a8..15a7a71051 100644 --- a/MapboxNavigation/VisualInstruction.swift +++ b/MapboxNavigation/VisualInstruction.swift @@ -9,6 +9,17 @@ extension VisualInstruction { public var containsLaneIndications: Bool { return components.contains(where: { $0 is LaneIndicationComponent }) } + + func maneuverImage(side: DrivingSide, color: UIColor, size: CGSize) -> UIImage? { + let mv = ManeuverView() + mv.frame = CGRect(origin: .zero, size: size) + mv.primaryColor = color + mv.backgroundColor = .clear + mv.scale = UIScreen.main.scale + mv.visualInstruction = self + let image = mv.imageRepresentation + return shouldFlipImage(side: side) ? image?.withHorizontallyFlippedOrientation() : image + } #if canImport(CarPlay) /// Returns a `CPImageSet` representing the maneuver. @@ -16,14 +27,7 @@ extension VisualInstruction { public func maneuverImageSet(side: DrivingSide) -> CPImageSet? { let colors: [UIColor] = [.black, .white] let blackAndWhiteManeuverIcons: [UIImage] = colors.compactMap { (color) in - let mv = ManeuverView() - mv.frame = CGRect(x: 0, y: 0, width: 30, height: 30) - mv.primaryColor = color - mv.backgroundColor = .clear - mv.scale = UIScreen.main.scale - mv.visualInstruction = self - let image = mv.imageRepresentation - return shouldFlipImage(side: side) ? image?.withHorizontallyFlippedOrientation() : image + return maneuverImage(side: side, color: color, size: CGSize(width: 30, height: 30)) } guard blackAndWhiteManeuverIcons.count == 2 else { return nil } return CPImageSet(lightContentImage: blackAndWhiteManeuverIcons[1], darkContentImage: blackAndWhiteManeuverIcons[0])