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

791 waypoint removal #806

Merged
merged 35 commits into from
Nov 13, 2017
Merged

791 waypoint removal #806

merged 35 commits into from
Nov 13, 2017

Conversation

JThramer
Copy link
Contributor

@JThramer JThramer commented Nov 7, 2017

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.

✅ :

  • Combine UITapGestureRecognizers into one that run both (waypoint and route) hit tests
  • Fix bug where alternate route disappears after selecting other route
  • Figure 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 route
  • Create delegate method that notifies client that routes have changed
  • Create delegate method that notifies client that route request failed.
  • Create Extension for RouteOptions
  • Refactor touch calculation methods to take CGPoints instead of UITapGestureRecognizers
  • Extract directions call in ViewController into its own function.
  • Move all inappropriate NavigationMapView logic over to implementation (example) side.
  • Create optional navigationMapView(_:didTapWaypoint:) for generic waypoint handling
  • Fix didTap:
  • Clean it up so it doesn't look so crufty
  • Add logic to ViewController that prompts user to delete waypoint

/cc @bsudekum @frederoni @ericrwolfe

@JThramer JThramer added feature New feature request. wip ⚠️ DO NOT MERGE PR should not be merged! labels Nov 7, 2017

//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)
Copy link
Contributor

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.

Copy link
Contributor Author

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...

Copy link
Contributor

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()
Copy link
Contributor

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)
Copy link
Contributor

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.

Copy link
Contributor

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?

@JThramer JThramer changed the base branch from select-alts to master November 7, 2017 19:29
Jerrad Thramer added 2 commits November 7, 2017 12:32
# 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 */,
Copy link
Contributor Author

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.

Jerrad Thramer added 2 commits November 8, 2017 11:42
1ec5
1ec5 previously requested changes Nov 8, 2017
Copy link
Contributor

@1ec5 1ec5 left a 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
Copy link
Contributor

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.

Copy link
Contributor Author

@JThramer JThramer Nov 8, 2017

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]? {
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

@1ec5 1ec5 Nov 8, 2017

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.

Copy link
Contributor Author

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)
Copy link
Contributor

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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)
Copy link
Contributor

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?

@bsudekum
Copy link
Contributor

bsudekum commented Nov 8, 2017

@JThramer I agree with @1ec5 here. Let's expose the delegate method and leave it up to the developer to do the rest. Anything related to making a new request can be removed.

@@ -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 }
Copy link
Contributor

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.

@JThramer JThramer added ✓ ready for review and removed wip ⚠️ DO NOT MERGE PR should not be merged! labels Nov 9, 2017
@JThramer JThramer dismissed 1ec5’s stale review November 9, 2017 21:14

Requested changes implemented.

@@ -1,12 +1,19 @@
import MapboxDirections

extension RouteOptions {
var activityType: CLActivityType {
public extension RouteOptions {
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree. Willfix.

return routes?.first
}
set {
self.startButton.isEnabled = (newValue != nil)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: drop self ×4

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree. Willfix.

Copy link
Contributor Author

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.

@bsudekum bsudekum mentioned this pull request Nov 10, 2017
@bsudekum
Copy link
Contributor

bsudekum commented Nov 11, 2017

@JThramer now that #828 is in, it might make sense to rebase this PR since this needs MapboxDirections.swift >= v0.12. This will help with reviewing the pr locally.

switch self.profileIdentifier {
case MBDirectionsProfileIdentifier.cycling, MBDirectionsProfileIdentifier.walking:
return .fitness
default:
return .automotiveNavigation
}
}
public func without(waypoint: Waypoint) -> RouteOptions {
Copy link
Contributor

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?

@bsudekum
Copy link
Contributor

bsudekum commented Nov 11, 2017

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.

return routes?.first
}
set {
startButton.isEnabled = (newValue != nil)
Copy link
Contributor

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.

Copy link
Contributor

@bsudekum bsudekum left a 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.

@JThramer
Copy link
Contributor Author

@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.

Jerrad Thramer added 2 commits November 13, 2017 09:57
@JThramer JThramer merged commit 2bfaa59 into master Nov 13, 2017
@JThramer JThramer deleted the jerrad/791-waypoint-removal branch November 13, 2017 17:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature request.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants