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 Aug 19, 2019
1 parent c1b5b6c commit 9cdf001
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 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
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 @@ -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) {
Expand Down Expand Up @@ -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)
}
}

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 9cdf001

Please sign in to comment.