-
Notifications
You must be signed in to change notification settings - Fork 319
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
791 waypoint removal #806
791 waypoint removal #806
Conversation
…rom route, but it's a buggy implementation, will rework by combining recognizers so they don't step on each-other
|
||
//take the best one, if existant, and prompt to delete | ||
guard let best = candidates.1.first, let multipoint = candidates.0.first else { return } | ||
requestNew(route: multipoint, without: best) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be a good place to slip in the delegate method. If the user implements it, give them the waypoint they selected. Otherwise, call provided boilerplate functionality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should 'boilerplate functionality' just be a navigationViewController(_:didSelectWaypoint:)
delegate method or something? Perhaps the implementer doesn't want to delete a waypoint when the user selects one...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, so if they do not want this to happen, they implement navigationViewController(_:didSelectWaypoint:)
and do something custom. Otherwise, we show the action sheet, and re-request the route.
options.waypoints.remove(at: index) | ||
|
||
removeRoutes() | ||
removeWaypoints() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to remove these; if you call showRoute/waypoint, we update routes/waypoints if they are already on the map.
_ = Directions.shared.calculate(options) { (waypoints, routes, error) in | ||
guard let routes = routes else { return } | ||
self.routes = routes | ||
self.showRoutes(routes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the event of an error, we should keep the previous right and gently notify the user the request failed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the view controller hosting the map view already has a way of notifying the user of a routing failure?
# Conflicts: # MapboxNavigation/NavigationMapView.swift
…h cases, however bug where an alternative route disappears is still persisting
@@ -913,6 +916,7 @@ | |||
C5D9800E1EFBCDAD006DBF2E /* Date.swift */, | |||
C51DF8651F38C31C006C6A15 /* Locale.swift */, | |||
C578DA071EFD0FFF0052079F /* ProcessInfo.swift */, | |||
8DF49B2F1FB2778A00B2F932 /* Route.swift */, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shouldn't be here. Investigate.
…blown away at inappropriate times.
…ion annotation to not be added to the map.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We’re trying too hard to cram view controller logic into this view class. The separations of responsibility in MVC exist for a reason. Inevitably, we’re going to find that the map view doesn’t have enough context for these features, and we’ll end up having to pull more and more view controller logic into this view, which will reduce its versatility and reusability.
Think of NavigationMapView as a renderer of data. Yes, some gesture functionality can be built into it, but anything that could possibly depend on the surrounding application’s state (such as whether we’re in a preview or not) should go in the delegate.
|
||
removeRoutes() | ||
removeWaypoints() | ||
_ = Directions.shared.calculate(options) { (waypoints, routes, error) in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It’s contrary to the MVC pattern for a view to kick off network requests. It should be the responsibility of the map view’s delegate (typically a view controller) to issue this request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. @bsudekum's idea of just putting this burden on the implementation side will suffice for now, but yeah, it would be nice if the NavigationMapView
got its own controller (which would become the parent of all our other NavigationMapView
-related controllers) that could provide default implementations for some of this behavior. It's time will come :)
return (multipointRoutes, candidates) | ||
} | ||
|
||
private func routes(closeTo tap: UITapGestureRecognizer) -> [Route]? { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method should take a point (in screen coordinates), rather than a gesture recognizer. That’ll make it useful for other purposes too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem with this is that we do our distance calculation based upon CGPoint
distance, not CLLocationDistance
. I need the recognizer so I can convert coordinates back into screen geometry, and then test if the tap was within 50 points of the closest route coordinate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, my suggestion is to have the method take a CGPoint, not a CLLocationCoordinate2D.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies, I misread. Will Do.
return requestNew(route: activeRoute, without: waypointToRemove) | ||
} else if let routes = self.routes(closeTo: sender) { | ||
guard let selectedRoute = routes.first else { return } | ||
select(route: selectedRoute) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic should be moved to the delegate implementation. We can make the helper methods public to facilitate that implementation. After all, the developer may want to programmatically select a route, or they may want to determine the route closest to a view that’s floating above the map view.
If implementing this logic represents an undue burden on the developer, we should vend a prefabricated view controller (#808). Alternatively, we could explore supplementing the existing Objective-C delegate protocol with a Swift protocol that can come with its own default implementations.
navigationMapDelegate?.navigationMapView?(self, didTap: route) | ||
} | ||
|
||
private func requestNew(route: Route, without waypoint: Waypoint) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extend the RouteOptions class with a method deleting(_:)
that takes a waypoint and returns a new RouteOptions object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. Will implement.
_ = Directions.shared.calculate(options) { (waypoints, routes, error) in | ||
guard let routes = routes else { return } | ||
self.routes = routes | ||
self.showRoutes(routes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the view controller hosting the map view already has a way of notifying the user of a routing failure?
Examples/Swift/ViewController.swift
Outdated
@@ -241,7 +258,14 @@ class ViewController: UIViewController, MGLMapViewDelegate, CLLocationManagerDel | |||
return false | |||
} | |||
|
|||
func navigationMapView(_ mapView: NavigationMapView, didTap route: Route) { | |||
func navigationMapView(_ mapView: NavigationMapView, didSelect waypoint: Waypoint) { | |||
guard let opts = currentRoute?.routeOptions else { return } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you call this routeOptions
? Slightly unclear what it is at first glance.
…tation side of things
@@ -1,12 +1,19 @@ | |||
import MapboxDirections | |||
|
|||
extension RouteOptions { | |||
var activityType: CLActivityType { | |||
public extension RouteOptions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: avoid setting access level for the entire scope of an extension. It's safer to publicize methods individually. Especially when using Swift < 3.2.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree. Willfix.
Examples/Swift/ViewController.swift
Outdated
return routes?.first | ||
} | ||
set { | ||
self.startButton.isEnabled = (newValue != nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: drop self ×4
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree. Willfix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As it turns out, the last two are required, but the first two are redundant and have been removed.
switch self.profileIdentifier { | ||
case MBDirectionsProfileIdentifier.cycling, MBDirectionsProfileIdentifier.walking: | ||
return .fitness | ||
default: | ||
return .automotiveNavigation | ||
} | ||
} | ||
public func without(waypoint: Waypoint) -> RouteOptions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you document this function now that it is public?
In the example app, I'm getting a prompt to remove a waypoint after selecting it, it removes the waypoint from the map, but the route is not recalculated. Updated- huh, it seems to have only happened once, on the first load. Update 2 - Can't reproduce now. |
Examples/Swift/ViewController.swift
Outdated
return routes?.first | ||
} | ||
set { | ||
startButton.isEnabled = (newValue != nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for parens here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work, I think this ready to go after a few minor comments are addressed.
@bsudekum The unreproducible issue you were seeing is a Race Condition, with an observed probability of about 1/20 of repro-ing. I've got a fix ready, just want to test it a bit more to make sure. |
…ute` (both changing the `routes` property), can cause a race condition.
Implementing #791. This is based off of @bsudekum's #789, so I'll change bases to
master
once that gets merged in.This is definitely still a WIP... it's a working implementation, but buggy, as the two gesture recognizers like to interfere with one another. Still some work to do.
✅ :
UITapGestureRecognizer
s into one that run both (waypoint and route) hit testsFigure out why destination waypoint is failing waypoint tap recognizer logic(determined out of scope)Create delegate method that asks client if map should remove waypoint from routeCreate delegate method that notifies client that routes have changedCreate delegate method that notifies client that route request failed.RouteOptions
CGPoint
s instead ofUITapGestureRecognizer
sViewController
into its own function.NavigationMapView
logic over to implementation (example) side.navigationMapView(_:didTapWaypoint:)
for generic waypoint handlingdidTap:
ViewController
that prompts user to delete waypoint/cc @bsudekum @frederoni @ericrwolfe