mapbox / mapbox-gl-native

Interactive, thoroughly customizable maps in native Android, iOS, macOS, Node.js, and Qt applications, powered by vector tiles and OpenGL
https://mapbox.com/mobile
Other
4.36k stars 1.33k forks source link

Move callout customization away from MGLMapViewDelegate towards custom views #4392

Closed 1ec5 closed 5 years ago

1ec5 commented 8 years ago

The current annotation callout API is implemented as a series of delegate methods in MGLMapViewDelegate that configure SMCalloutView. SMCalloutView provides a lot of customization options, but we’re only exposing a few basic ones.

SMCalloutView is great for callout views that need a certain layout, but many developers need more flexibility and don’t mind setting up the layout and drawing themselves. The current API for custom callout views can be very confusing for a developer who wants something more freeform – “Why do I have to implement accessory views?” And you’re basically on your own in terms of styling as soon as you go the custom callout route.

Let’s drop the dependency on SMCalloutView and deprecate the current callout API – both the delegate methods and the protocol – in favor of a more deliberate, coherent callout API. The proposal below is aimed at aligning the iOS SDK’s annotations API with that of the OS X SDK, which manages callouts with NSPopoverControllers and allows you to specify your own.


You should be able to drag a custom view into a storyboard, turn it into an MGLMapView, and design the custom callout views directly inside the storyboard. You’d drag a custom view into the scene dock above the view controller and optionally set the custom class to MGLCompactCalloutView, which provides the standard bubble look and feel (surfaced in the storyboard via a designable). You’d specify a reuse identifier for the callout view, then drag a connection from the callout view to the map view’s prototypeCalloutViews outlet collection. The callout view can be customized extensively without any work on our part:

When configuring an MGLAnnotationView (#1784) or MGLAnnotationImage programmatically, you’d choose which callout view to use with the annotation, identifying one of the prototype callout views either by indexing into the outlet collection or by specifying a reuse identifier. This part differs from how MapKit’s MKAnnotationView API works, but I think it’s worth diverging to offer better storyboard support and more flexibility. Notably, setting up the annotation model object is the only part of the workflow that absolutely requires code.


If possible, we’d provide a compatibility shim for the old API that could get removed in a future major version. But if you want to work with SMCalloutView specifically, then you’d be able to pull in SMCalloutView and customize it to your liking without depending on SDK changes.

The entire API would still be usable programmatically for more advanced use cases. But the goal is that the “First steps” guide, which is intended for beginners, would have only a few lines of code and the rest would be in Interface Builder. Some future directions, somewhat out of scope for this issue, would further reduce the need for code:

A lot of the code in this custom callout view example can be reused to implement a snazzy-looking default callout view.

/cc @friedbunny @boundsj @tmcw

1ec5 commented 8 years ago

Some addenda:

You’d drag a custom view into the scene dock above the view controller and optionally set the custom class to MGLCompactCalloutView… You’d specify a reuse identifier for the callout view

Providing a reuse identifier inspectable for ordinary UIViews would be tricky. We can’t add a stored property to UIView via a category. However, we could have the category-defined getter and setter work with an associated object on the view.

If the developer wants the standard bubble look but forgets to set MGLCompactCalloutView as the custom class, should the callout really be transparent and borderless?

You could define an annotation view itself by dragging a UIImageView into the scene dock, connecting it to the map view’s prototypeAnnotationViews outlet collection, and optionally connecting a callout view to the annotation view’s calloutView outlet.

There are two likely models here:

What happens when we add support for showing callouts on shape and line overlays? It may make sense to have prototype callout views be independent of any prototype annotation view.

Where should the callout’s anchor position or anchor edge be exposed? As a property of the callout view or the annotation view?

friedbunny commented 8 years ago

Working in the fb-callouts-4392 branch. So far, the only commits are the removal of SMCalloutView and the interim conversion of MGLCompactCalloutView to the demo custom callout.

I’m currently exploring how to deprecate the old delegate-based callouts without removing them outright.

1ec5 commented 8 years ago

Noting also that the callout view’s title and subtitle labels should update (and the callout view itself should resize) when the represented annotation object’s title and subtitle properties change. It’s currently trivial to observe property changes using KVO, as we do with the coordinate property, but SMCalloutView lacks support for updating its contents dynamically.

It also doesn’t look straightforward to add dynamic content updates to SMCalloutView, because it doesn’t expose its labels publicly and lays itself out manually. SMCalloutView is written to support iOS 5.0 and above, while Auto Layout was introduced in iOS 6.0. We only support iOS 7.0 and above, so any replacement for SMCalloutView should use constraints to avoid a similar mess.

incanus commented 8 years ago

I'd love to discuss this more, especially if we are anticipating having MapKit parity with some sort of similar callout view as part of this more abstract solution. SMCalloutView is mature, we have a good relationship with the developer (👋 @nfarina), and it'd take a lot of work to replicate.

1ec5 commented 8 years ago

SMCalloutView is a great library, but the current situation is unfortunate: we only make use of a tiny fraction of SMCalloutView, so there’s plenty of dead code around iOS 6 support and various customization options. Due to the way we’ve integrated SMCalloutView, developers can’t even bring in their own copy of SMCalloutView that they can use more fully (#1757).

Meanwhile, many developers are eschewing the built-in callout API for custom callout views, either because they want more flexibility with the bubble callout design (#3950) or because they want to imitate the bottom panel affordance found in Google Maps and (now in iOS 10) Apple Maps. We need the ability to dynamically update the callout’s title and subtitle through KVO, for parity with the Android SDK (#5353) and macOS SDK, but that’s currently impossible because of how SMCalloutView is laid out. Instead of hacking around that limitation, I think we should move to an implementation that uses Auto Layout.

The existing custom callout API requires a lot of code, because it’s trying to fit within the paradigm of SMCalloutView. I think we can greatly simplify the API in a way that allows developers to bring in SMCalloutView if that’s what they want but get a reasonable default that matches MapKit if they don’t bring in SMCalloutView. The implementation in #4948 already replicates much of the SMCalloutView functionality we’ve been using, but with far less code. I think we can also consider it fairly mature for what it does, given that it’s based on the custom callout example.

friedbunny commented 8 years ago

My plan so far in #4948 is to minimally reproduce SMCalloutView’s features, but do so in a way that’s as efficient as possible for this project. Primarily, I want to reduce the number of delegate methods and more closely align to MapKit’s property-based callout↔annotation connections. Our current custom callout API is entirely too complicated and this effort is about improving that.

SMCalloutView itself is great and the changes we’re discussing here should actually make it easier for developers to more fully use it. For those developers who don’t need SMCalloutView, unbundling it from our SDK will make their projects marginally lighter weight.

(I do think, though, that iOS 10’s Maps.app move away from bubble-style callouts is the beginning of the end for SMCalloutView — the bubble is still a useful paradigm, but separate floating views will probably at least enjoy some faddish popularity.)

ghost commented 6 years ago

I think this is an excellent idea.

stale[bot] commented 5 years ago

This issue has been automatically detected as stale because it has not had recent activity and will be archived. Thank you for your contributions.

stale[bot] commented 5 years ago

This issue has been automatically detected as stale because it has not had recent activity and will be archived. Thank you for your contributions.