Skip to content

Commit

Permalink
Upgraded notifications to UserNotifications framework
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
1ec5 committed Sep 17, 2019
1 parent f7409f3 commit d6768e9
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 28 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 manually incrementing `RouteProgress.legIndex` could lead to undefined behavior. ([#2229](https://github.com/mapbox/mapbox-navigation-ios/pull/2229))

## v0.37.0
Expand Down
49 changes: 29 additions & 20 deletions MapboxNavigation/NavigationViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import MapboxCoreNavigation
import MapboxDirections
import MapboxSpeech
import AVFoundation
import UserNotifications
import MobileCoreServices
import Mapbox

/**
Expand Down Expand Up @@ -323,28 +325,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) {
Expand Down Expand Up @@ -504,10 +509,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)
}
}

Expand Down
20 changes: 12 additions & 8 deletions MapboxNavigation/VisualInstruction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,25 @@ 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.
@available(iOS 12.0, *)
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])
Expand Down

0 comments on commit d6768e9

Please sign in to comment.