Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve FloatingButton styling. #4060

Merged
merged 2 commits into from Sep 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,11 @@

* `NavigationMapView.removeAlternativeRoutes()` and `NavigationMapView.removeContinuousAlternativeRoutesDurations()` were made public to provide a way to remove previously shown alternative routes and alternative routes duration annotations, respectively. ([#4134](https://github.com/mapbox/mapbox-navigation-ios/pull/4134))

### Other changes

* Additional parameters were added to `FloatingButton.rounded(image:selectedImage:size:type:cornerRadius)` to be able to provide button type and corner radius. ([#4060](https://github.com/mapbox/mapbox-navigation-ios/pull/4060))
* `FloatingButton` no longer contains corner radius shadow, border is applied instead. ([#4060](https://github.com/mapbox/mapbox-navigation-ios/pull/4060))

## v2.8.0

### Packaging
Expand Down
3 changes: 3 additions & 0 deletions Example/ViewController.swift
Expand Up @@ -642,6 +642,7 @@ class ViewController: UIViewController {
// Hide `WayNameView` and `FloatingStackView` to smoothly present them.
navigationViewController.navigationView.wayNameView.alpha = 0.0
navigationViewController.navigationView.floatingStackView.alpha = 0.0
navigationViewController.navigationView.speedLimitView.alpha = 0.0

present(navigationViewController, animated: animated) {
completion?()
Expand All @@ -655,6 +656,7 @@ class ViewController: UIViewController {
animations: {
navigationViewController.navigationView.wayNameView.alpha = 1.0
navigationViewController.navigationView.floatingStackView.alpha = 1.0
navigationViewController.navigationView.speedLimitView.alpha = 1.0
Copy link
Contributor Author

@MaximAlien MaximAlien Aug 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just improves overall animation when presenting and dismissing NavigationViewController.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this compatible with SpeedLimitView’s built-in behavior to hide when there’s no content and to blink when the speed limit updates? The blinking operates on the view’s layer’s opacity.

func update() {
let wasHidden = isHidden
isHidden = !canDraw
setNeedsDisplay()
if canDraw && !wasHidden {
blinkIn()
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I did a couple of tests and SpeedLimitView blinking occurs as expected whenever speed limit is changed (after showing or dismissing NavigationViewController).

})
navigationViewController.navigationView.topBannerContainerView.show(duration: 1.0)
}
Expand All @@ -674,6 +676,7 @@ class ViewController: UIViewController {
animations: {
activeNavigationViewController.navigationView.wayNameView.alpha = 0.0
activeNavigationViewController.navigationView.floatingStackView.alpha = 0.0
activeNavigationViewController.navigationView.speedLimitView.alpha = 0.0
},
completion: { _ in
activeNavigationViewController.dismiss(animated: animated) {
Expand Down
19 changes: 14 additions & 5 deletions Sources/MapboxNavigation/CameraController.swift
Expand Up @@ -105,20 +105,23 @@ class CameraController: NavigationComponent, NavigationComponentDelegate {
}

@objc func navigationCameraStateDidChange(_ notification: Notification) {
guard let navigationCameraState = notification.userInfo?[NavigationCamera.NotificationUserInfoKey.state] as? NavigationCameraState else { return }
guard let navigationViewController = navigationViewData.containerViewController as? NavigationViewController,
let navigationCameraState = notification.userInfo?[NavigationCamera.NotificationUserInfoKey.state] as? NavigationCameraState else {
return
}

updateNavigationCameraViewport()

switch navigationCameraState {
case .transitionToFollowing, .following:
navigationViewData.navigationView.overviewButton.isHidden = false
navigationViewController.overviewButton.isHidden = false
navigationViewData.navigationView.resumeButton.isHidden = true
if let _ = navigationViewData.navigationView.wayNameView.text?.nonEmptyString {
navigationViewData.navigationView.wayNameView.containerView.isHidden = false
}
break
case .idle, .transitionToOverview, .overview:
navigationViewData.navigationView.overviewButton.isHidden = true
navigationViewController.overviewButton.isHidden = true
navigationViewData.navigationView.resumeButton.isHidden = false
navigationViewData.navigationView.wayNameView.containerView.isHidden = true
break
Expand Down Expand Up @@ -165,8 +168,14 @@ class CameraController: NavigationComponent, NavigationComponentDelegate {
// MARK: NavigationComponentDelegate Implementation

func navigationViewDidLoad(_: UIView) {
navigationViewData.navigationView.overviewButton.addTarget(self, action: #selector(overview(_:)), for: .touchUpInside)
navigationViewData.navigationView.resumeButton.addTarget(self, action: #selector(recenter(_:)), for: .touchUpInside)
let navigationViewController = navigationViewData.containerViewController as? NavigationViewController
navigationViewController?.overviewButton.addTarget(self,
action: #selector(overview(_:)),
for: .touchUpInside)

navigationViewData.navigationView.resumeButton.addTarget(self,
action: #selector(recenter(_:)),
for: .touchUpInside)

navigationMapView.userLocationStyle = .courseView()
navigationViewData.navigationView.resumeButton.isHidden = true
Expand Down
4 changes: 3 additions & 1 deletion Sources/MapboxNavigation/DayStyle.swift
Expand Up @@ -247,6 +247,8 @@ open class DayStyle: Style {

FloatingButton.appearance(for: traitCollection).backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
FloatingButton.appearance(for: traitCollection).tintColor = tintColor
FloatingButton.appearance(for: traitCollection).borderWidth = Style.defaultBorderWidth
FloatingButton.appearance(for: traitCollection).borderColor = #colorLiteral(red: 0.737254902, green: 0.7960784314, blue: 0.8705882353, alpha: 1)

DistanceRemainingLabel.appearance(for: traitCollection).normalFont = UIFont.systemFont(ofSize: 18.0, weight: .medium).adjustedFont
DistanceRemainingLabel.appearance(for: traitCollection).normalTextColor = #colorLiteral(red: 0.431372549, green: 0.431372549, blue: 0.431372549, alpha: 1)
Expand All @@ -262,7 +264,7 @@ open class DayStyle: Style {
ResumeButton.appearance(for: traitCollection).backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
ResumeButton.appearance(for: traitCollection).tintColor = .defaultPrimaryText
ResumeButton.appearance(for: traitCollection).borderColor = #colorLiteral(red: 0.737254902, green: 0.7960784314, blue: 0.8705882353, alpha: 1)
ResumeButton.appearance(for: traitCollection).borderWidth = 1 / UIScreen.main.scale
ResumeButton.appearance(for: traitCollection).borderWidth = Style.defaultBorderWidth
ResumeButton.appearance(for: traitCollection).cornerRadius = 5.0

NextBannerView.appearance(for: traitCollection).backgroundColor = #colorLiteral(red: 0.9675388083, green: 0.9675388083, blue: 0.9675388083, alpha: 1)
Expand Down
24 changes: 19 additions & 5 deletions Sources/MapboxNavigation/FloatingButton.swift
Expand Up @@ -44,16 +44,30 @@ open class FloatingButton: Button {
- parameter image: The `UIImage` of this button.
- parameter selectedImage: The `UIImage` of this button when selected.
- parameter size: The size of this button, or `FloatingButton.buttonSize` if this argument is not specified.
- parameter type: `UIButton` type. Defaults to `.custom`.
- parameter cornerRadius: Corner radius of the button.

- returns: `FloatingButton` instance.
*/
public class func rounded<T: FloatingButton>(image: UIImage? = nil,
selectedImage: UIImage? = nil,
size: CGSize = FloatingButton.buttonSize) -> T {
let button = T.init(type: .custom)
size: CGSize = FloatingButton.buttonSize,
type: UIButton.ButtonType = .custom,
cornerRadius: CGFloat = FloatingButton.buttonSize.width / 2.0) -> T {
let button = T.init(type: type)
button.translatesAutoresizingMaskIntoConstraints = false
button.constrainedSize = size
button.setImage(image, for: .normal)
if let selected = selectedImage { button.setImage(selected, for: .selected) }
button.applyDefaultCornerRadiusShadow(cornerRadius: size.width / 2)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is removing shadow completely, right? Would users expect that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a tradeoff for performance as noted above. I think it could look like an intentional design change, as opposed to a bug – not dissimilar to changes in Apple software over the years. But the main thing is to make sure the border is sufficiently discernible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think two major reasons for this change is consistency and only partly performance. Previously we've shown shadow, border or no border in views where consistency is expected (top and bottom banners, resume button, floating button). With shadow removal we make look more consistent and customizable to bring new UI updates for next iteration of drop-in.

button.layer.cornerRadius = cornerRadius
button.imageView?.contentMode = .scaleAspectFit

if let image = image {
button.setImage(image, for: .normal)
}

if let selectedImage = selectedImage {
button.setImage(selectedImage, for: .selected)
}

return button
}
}
Expand Up @@ -90,7 +90,8 @@ open class InstructionsCardViewController: UIViewController {
lazy var junctionView: JunctionView = {
let view: JunctionView = .forAutoLayout()
view.isHidden = true
view.applyDefaultCornerRadiusShadow(cornerRadius: 4, shadowOpacity: 0.4)
view.layer.cornerRadius = Style.defaultCornerRadius

return view
}()

Expand Down
14 changes: 1 addition & 13 deletions Sources/MapboxNavigation/NavigationView.swift
Expand Up @@ -44,13 +44,6 @@ open class NavigationView: UIView {
static let buttonSpacing: CGFloat = 8.0
}

private enum Images {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was moved to UIImage extension.

static let overview = UIImage(named: "overview", in: .mapboxNavigation, compatibleWith: nil)!.withRenderingMode(.alwaysTemplate)
static let volumeUp = UIImage(named: "volume_up", in: .mapboxNavigation, compatibleWith: nil)!.withRenderingMode(.alwaysTemplate)
static let volumeOff = UIImage(named: "volume_off", in: .mapboxNavigation, compatibleWith: nil)!.withRenderingMode(.alwaysTemplate)
static let feedback = UIImage(named: "feedback", in: .mapboxNavigation, compatibleWith: nil)!.withRenderingMode(.alwaysTemplate)
}

var compactConstraints = [NSLayoutConstraint]()
var regularConstraints = [NSLayoutConstraint]()

Expand Down Expand Up @@ -125,10 +118,6 @@ open class NavigationView: UIView {
return stackView
}()

lazy var overviewButton = FloatingButton.rounded(image: Images.overview)
Copy link
Contributor Author

@MaximAlien MaximAlien Aug 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO these floating buttons should not be in NavigationView anymore as it can be used not only in active navigation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we then move its management (showing, hiding, etc.) to the view controller? Seems like an extra knowledge for the view, if it doesn't contain the buttons.

Also, could it be a breaking change for the users of NavigationView?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not consider this as a breaking change because all components are internal and not exposed to the user.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is actually another ResumeButton that currently resides in NavigationView, I haven't made any changes to it in scope of this PR, but it'd make sense to move it outside of NavigationView as well, because it's only active-navigation related UI component, and NavigationView is meant to be used in free-drive as well.

The reason I keep it here is because it's likely to be removed/deprecated altogether to match Android behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated style:

Screen Shot 2022-09-06 at 2 51 43 PM

Screen Shot 2022-09-06 at 3 00 31 PM

lazy var muteButton = FloatingButton.rounded(image: Images.volumeUp, selectedImage: Images.volumeOff)
lazy var reportButton = FloatingButton.rounded(image: Images.feedback)

var floatingButtonsPosition: MapOrnamentPosition = .topTrailing {
didSet {
setupConstraints()
Expand All @@ -143,7 +132,7 @@ open class NavigationView: UIView {
}
}

lazy var resumeButton: ResumeButton = .forAutoLayout()
lazy var resumeButton: ResumeButton = .forAutoLayout(hidden: true)

var wayNameViewLayoutGuide: UILayoutGuide? {
didSet {
Expand Down Expand Up @@ -226,7 +215,6 @@ open class NavigationView: UIView {

func commonInit() {
DayStyle().apply()
floatingButtons = [overviewButton, muteButton, reportButton]
setupViews()
setupConstraints()
}
Expand Down
37 changes: 34 additions & 3 deletions Sources/MapboxNavigation/NavigationViewController.swift
Expand Up @@ -235,6 +235,28 @@ open class NavigationViewController: UIViewController, NavigationStatusPresenter
navigationService.router
}

lazy var overviewButton: FloatingButton = {
let floatingButton = FloatingButton.rounded(image: .overview)
floatingButton.borderWidth = Style.defaultBorderWidth

return floatingButton
}()

lazy var muteButton: FloatingButton = {
let floatingButton = FloatingButton.rounded(image: .volumeUp,
selectedImage: .volumeOff)
floatingButton.borderWidth = Style.defaultBorderWidth

return floatingButton
}()

lazy var reportButton: FloatingButton = {
let floatingButton = FloatingButton.rounded(image: .feedback)
floatingButton.borderWidth = Style.defaultBorderWidth

return floatingButton
}()

func setupNavigationService() {
guard let routeResponse = _routeResponse,
let routeIndex = _routeIndex,
Expand Down Expand Up @@ -365,7 +387,16 @@ open class NavigationViewController: UIViewController, NavigationStatusPresenter

open override func loadView() {
let frame = parent?.view.bounds ?? UIScreen.main.bounds
view = NavigationView(delegate: self, frame: frame, tileStoreLocation: mapTileStore, navigationMapView: self.navigationOptions?.navigationMapView)
view = NavigationView(delegate: self,
frame: frame,
tileStoreLocation: mapTileStore,
navigationMapView: self.navigationOptions?.navigationMapView)

navigationView.floatingButtons = [
overviewButton,
muteButton,
reportButton
]
}

/**
Expand Down Expand Up @@ -503,7 +534,7 @@ open class NavigationViewController: UIViewController, NavigationStatusPresenter
public var showsReportFeedback: Bool = true {
didSet {
loadViewIfNeeded()
ornamentsController?.reportButton.isHidden = !showsReportFeedback
reportButton.isHidden = !showsReportFeedback
showsEndOfRouteFeedback = showsReportFeedback
}
}
Expand Down Expand Up @@ -603,7 +634,7 @@ open class NavigationViewController: UIViewController, NavigationStatusPresenter
subviewInits.removeAll()

arrivalController?.destination = route?.legs.last?.destination
ornamentsController?.reportButton.isHidden = !showsReportFeedback
reportButton.isHidden = !showsReportFeedback
}

func addTopBanner(_ navigationOptions: NavigationOptions?) -> ContainerViewController {
Expand Down
1 change: 1 addition & 0 deletions Sources/MapboxNavigation/NightStyle.swift
Expand Up @@ -26,6 +26,7 @@ open class NightStyle: DayStyle {

FloatingButton.appearance(for: traitCollection).backgroundColor = .defaultDarkAppearanceBackgroundColor
FloatingButton.appearance(for: traitCollection).tintColor = #colorLiteral(red: 0.9842069745, green: 0.9843751788, blue: 0.9841964841, alpha: 1)
FloatingButton.appearance(for: traitCollection).borderColor = #colorLiteral(red: 0.3764705882, green: 0.4901960784, blue: 0.6117647059, alpha: 0.796599912)

InstructionsCardContainerView.appearance(for: traitCollection, whenContainedInInstancesOf: [InstructionsCardCell.self]).customBackgroundColor = .defaultDarkAppearanceBackgroundColor
InstructionsCardContainerView.appearance(for: traitCollection, whenContainedInInstancesOf: [InstructionsCardCell.self]).separatorColor = #colorLiteral(red: 0.3764705882, green: 0.4901960784, blue: 0.6117647059, alpha: 0.796599912)
Expand Down
21 changes: 14 additions & 7 deletions Sources/MapboxNavigation/OrnamentsController.swift
Expand Up @@ -118,10 +118,6 @@ class OrnamentsController: NavigationComponent, NavigationComponentDelegate {
}
}

var reportButton: FloatingButton {
return navigationView.reportButton
}

@objc func toggleMute(_ sender: UIButton) {
sender.isSelected = !sender.isSelected

Expand Down Expand Up @@ -211,13 +207,24 @@ class OrnamentsController: NavigationComponent, NavigationComponentDelegate {
// MARK: NavigationComponentDelegate implementation

func navigationViewDidLoad(_: UIView) {
navigationView.muteButton.addTarget(self, action: #selector(toggleMute(_:)), for: .touchUpInside)
navigationView.reportButton.addTarget(self, action: #selector(feedback(_:)), for: .touchUpInside)
guard let navigationViewController = navigationViewData.containerViewController as? NavigationViewController else {
return
}

navigationViewController.muteButton.addTarget(self,
action: #selector(toggleMute(_:)),
for: .touchUpInside)

navigationViewController.reportButton.addTarget(self,
action: #selector(feedback(_:)),
for: .touchUpInside)
}

func navigationViewWillAppear(_: Bool) {
resumeNotifications()
navigationView.muteButton.isSelected = NavigationSettings.shared.voiceMuted

let navigationViewController = navigationViewData.containerViewController as? NavigationViewController
navigationViewController?.muteButton.isSelected = NavigationSettings.shared.voiceMuted
}

func navigationViewDidDisappear(_: Bool) {
Expand Down
3 changes: 1 addition & 2 deletions Sources/MapboxNavigation/ReportButton.swift
Expand Up @@ -6,7 +6,6 @@ import UIKit
public class ReportButton: Button {

static let defaultInsets: UIEdgeInsets = 10.0
static let defaultCornerRadius: CGFloat = 4.0

public required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
Expand All @@ -22,6 +21,6 @@ public class ReportButton: Button {

private func commonInit() {
contentEdgeInsets = ReportButton.defaultInsets
applyDefaultCornerRadiusShadow(cornerRadius: ReportButton.defaultCornerRadius)
layer.cornerRadius = Style.defaultCornerRadius
}
}
8 changes: 8 additions & 0 deletions Sources/MapboxNavigation/Style.swift
Expand Up @@ -49,6 +49,14 @@ open class Style: NSObject {
UITraitCollection(userInterfaceIdiom: .pad),
])

class var defaultBorderWidth: CGFloat {
1 / UIScreen.main.scale
}
Comment on lines +52 to +54
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original purpose of the shadow was to ensure contrast with a map background that could be the exact same color. A border can accomplish the same contrast, though we should do some field-testing to verify that the border is thick and dark enough to make the button discernible in a sunny, washed-out lighting condition.


class var defaultCornerRadius: CGFloat {
10.0
}

/**
Applies the style for all changed properties.
*/
Expand Down
16 changes: 16 additions & 0 deletions Sources/MapboxNavigation/UIImage.swift
Expand Up @@ -2,6 +2,22 @@ import UIKit

extension UIImage {

static let overview = UIImage(named: "overview",
in: .mapboxNavigation,
compatibleWith: nil)!.withRenderingMode(.alwaysTemplate)

static let volumeUp = UIImage(named: "volume_up",
in: .mapboxNavigation,
compatibleWith: nil)!.withRenderingMode(.alwaysTemplate)

static let volumeOff = UIImage(named: "volume_off",
in: .mapboxNavigation,
compatibleWith: nil)!.withRenderingMode(.alwaysTemplate)

static let feedback = UIImage(named: "feedback",
in: .mapboxNavigation,
compatibleWith: nil)!.withRenderingMode(.alwaysTemplate)

convenience init?(color: UIColor, size: CGSize = CGSize(width: 1.0, height: 1.0)) {
let rect = CGRect(origin: .zero, size: size)
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
Expand Down
7 changes: 0 additions & 7 deletions Sources/MapboxNavigation/UIView.swift
Expand Up @@ -48,13 +48,6 @@ extension UIView {

// MARK: Layer Styling

func applyDefaultCornerRadiusShadow(cornerRadius: CGFloat? = 4, shadowOpacity: CGFloat? = 0.1) {
layer.cornerRadius = cornerRadius!
layer.shadowOffset = CGSize(width: 0, height: 0)
layer.shadowRadius = 4
layer.shadowOpacity = Float(shadowOpacity!)
}

func applyGradient(colors: [UIColor], locations: [NSNumber]? = nil) {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
Expand Down
Expand Up @@ -488,13 +488,13 @@ class NavigationViewControllerTests: TestCase {
3,
"There should be three floating buttons by default.")
XCTAssertEqual(navigationViewController.floatingButtons?[0],
navigationViewController.navigationView.overviewButton,
navigationViewController.overviewButton,
"Unexpected floating button.")
XCTAssertEqual(navigationViewController.floatingButtons?[1],
navigationViewController.navigationView.muteButton,
navigationViewController.muteButton,
"Unexpected floating button.")
XCTAssertEqual(navigationViewController.floatingButtons?[2],
navigationViewController.navigationView.reportButton,
navigationViewController.reportButton,
"Unexpected floating button.")

navigationViewController.floatingButtons = []
Expand Down