Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Runtime styling API for iOS/macOS #5626

Closed
1ec5 opened this issue Jul 8, 2016 · 8 comments
Closed

Runtime styling API for iOS/macOS #5626

1ec5 opened this issue Jul 8, 2016 · 8 comments
Assignees
Labels
beta blocker Blocks the next beta release feature iOS Mapbox Maps SDK for iOS macOS Mapbox Maps SDK for macOS runtime styling
Milestone

Comments

@1ec5
Copy link
Contributor

1ec5 commented Jul 8, 2016

#837 tracks the overall work to bring a runtime styling API to all the Mapbox GL Native libraries. This ticket tracks the work to design and implement the public interface in the iOS and macOS SDKs. (We’re planning similar APIs for Android in #5610 and for Qt in #5642.)

Conceptually, the runtime styling API brings all the settings found in Mapbox Studio into the SDK, allowing the developer to manipulate the style and its sources dynamically at runtime rather than at design time, without having to switch styles. The runtime styling API consists of:

  • Classes to represent different kinds of sources
  • Methods for adding and removing sources from a map’s style, including adding and removing raw data
  • Classes to represent different kinds of layers
  • Methods for adding and removing layers
  • Methods for modifying a layer’s feature filter
  • Methods for modifying a layer’s layout properties
  • Methods for modifying a layer’s paint properties

Design goals

Here are some proposed goals for the Cocoa API’s design and possible approaches to meeting those design goals:

  • Use standard Foundation types wherever possible:
    • The developer can set a paint property to an NSString, NSNumber, or NSArray as appropriate. UIColor/NSColor would be an exception to the Foundation rule but easily typedef’d.
    • The developer can filter features using an NSPredicate.
  • Balance type safety and dynamicism
  • Minimize the surface area of MGLMapView:
    • MGLMapView’s sprawling API could get far larger if we require all style and layer operations to go through MGLMapView (-setString:forPaintProperty:forLayerWithIdentifier: or, worse, -setFillOutlineColor:forPaintProperty:forLayerWithIdentifier:). Instead, we should take the approach described in Android - Runtime styling Api #5610 (comment): MGLMapView vends instances of MGLSource and MGLStyleLayer that have backpointers to MGLMapView; you set properties on MGLSource and MGLStyleLayer rather than MGLMapView.

Open questions

  • How specific should property methods be?
    • Less type-safe, dictionary-style methods (-setBool:forPaintProperty:) feel more appropriate to Objective-C, where such methods are common for open-ended APIs. We can make this feel a lot more Swift-like in Swift by adopting object subscripting. Validation is shifted to runtime, making it more difficult to spot basic errors. Documentation for these methods would refer developers to the online style specification documentation. Changes to the style specification won’t necessarily require backwards-incompatible changes to the SDK’s formal API. It’s easier to vary property names by values determined at runtime without resorting to KVO or invocations.
    • Strongly-typed, per-property methods (-setFillOutlineColor:) feel more appropriate to Swift, where they minimize type conversion when accessing properties. Validation occurs at compile-time, so it’s easy to spot basic errors. Per-property methods are natural places to hang per-property documentation. Enumerations are a lot more convenient than strings. Even with this approach, Objective-C code can continue to use KVO methods like -setValue:forKey:.
    • Normally I’d prefer strong typing, but this API has the potential to be so large and fluid that the dictionary style looks very tempting. We could try that approach first, then add per-property methods in a future release if we think it’ll address common pain points.
  • Should there be APIs specific to common sources like Mapbox Streets?
  • How to prevent MGLMapView from vending two distinct MGLStyleLayer instances for the same identifier?
  • Should we go a step further and have MGLMapView vend an instance of MGLStyle that in turn vends MGLSource and MGLStyleLayer instances? This would give us a natural place to get and set properties of the style itself, such as transitions and metadata.

Sketch

Here’s a rough cut of what the API might look like at a high level. Included in this sketch are a couple points that would get our feature querying API closer to GL JS’s.

  • Source classes are named MGL*Source and inherit from or conform to MGLSource:
    • MGLGeoJSONSource
    • MGLRasterSource
    • MGLVectorSource
    • (eventually MGLImageSource and MGLVideoSource)
    • MGLAnnotationSource – Do we need this for representing the source that a GL annotation comes from?
  • MGLSource instances may have some or all of the following members:
    • -URL (NSURL)
    • -tileSize (CGSize/NSSize)
    • -data (NSData), -objectValue (id)
    • -initWithData: (NSData), -initWithJSONObject: (id)
  • Layer classes are named MGL*StyleLayer and inherit from or conform to MGLStyleLayer (not MGLLayer, because *Layer usually refers to subclasses of CALayer, including our own MGLOpenGLLayer):
    • MGLBackgroundStyleLayer
    • MGLCircleStyleLayer
    • MGLFillStyleLayer
    • MGLLineStyleLayer
    • MGLRasterStyleLayer
    • MGLSymbolStyleLayer
    • MGLCustomStyleLayer (?)
  • Filters are expressed as nested NSComparisonPredicate and NSCompoundPredicate objects. Formally, the API will accept any NSPredicate, but we’ll do validation at runtime to ensure that only supported predicate expressions go through (and not, say, NSBlockPredicate). This is entirely consistent with how Core Data uses NSPredicate, and the documentation goes out of its way to remind developers that each backend has its own set of supported predicate operators.
  • Additional methods on MGLMapView:
    • -sources, -setSources: (?)
    • -addSource:
    • -sourceWithIdentifier:
    • -removeSource: (?)
    • -removeSourceWithIdentifier:
    • -visibleFeaturesAtPoint:inStyleLayers: (?) – to complement -visibleFeaturesAtPoint:inStyleLayersWithIdentifiers:
    • -visibleFeaturesInRect:inStyleLayers: (?) – to complement -visibleFeaturesInRect:inStyleLayersWithIdentifiers:
    • -styleLayers, -setStyleLayers: (?)
    • -insertStyleLayer:beforeLayer: (?)
    • -insertStyleLayer:beforeLayerWithIdentifier:
    • -layerWithIdentifier:
    • -removeLayer: (?)
    • -removeLayerWithIdentifier:
  • Additional methods on classes conforming to MGLFeature:
    • -layer (or just -layerIdentifier?)

Except for the additions to MGLMapView, the entire implementation should live in platform/darwin/src/ for maximum compatibility and testability.

/cc @mapbox/gl

@1ec5 1ec5 added feature iOS Mapbox Maps SDK for iOS macOS Mapbox Maps SDK for macOS labels Jul 8, 2016
@1ec5 1ec5 added this to the ios-v3.4.0 milestone Jul 8, 2016
@1ec5 1ec5 self-assigned this Jul 8, 2016
@1ec5
Copy link
Contributor Author

1ec5 commented Jul 9, 2016

I think we also need APIs on MGLGeoJSONSource to add, access, and remove individual features by their identifiers. Otherwise, GeoJSON will be far less useful than the existing GL annotation API for common use cases like adding and removing shapes. Browsing through the GL JS examples, it seems like common practice is to blow away all the source’s existing data and add it back with any modifications. With the GL annotation API, even having to remove and readd an individual annotation (in order to relocate it) was seen as a significant pain point (#1980), not to mention a performance bottleneck when attempting to animate data, so having to work with the entire source’s data atomically would be a nonstarter for these common use cases.

@bleege
Copy link
Contributor

bleege commented Jul 11, 2016

For continuity, the Android Styling API is simultaneously being worked on in #5610.

This was referenced Jul 12, 2016
@jfirebaugh
Copy link
Contributor

I think we also need APIs on MGLGeoJSONSource to add, access, and remove individual features by their identifiers.

You would need to implement this entirely at the SDK level. We don't have plans or a timeline for incremental GeoJSON mutations at the core level -- it's quite a difficult problem.

@1ec5
Copy link
Contributor Author

1ec5 commented Jul 14, 2016

I think we also need APIs on MGLGeoJSONSource to add, access, and remove individual features by their identifiers.

You would need to implement this entirely at the SDK level. We don't have plans or a timeline for incremental GeoJSON mutations at the core level -- it's quite a difficult problem.

In light of that, we should treat the existing GL annotation API as the way you’d get full styling capabilities but retain the ability to modify individual shapes without rebuilding the world. In order to expose all the remaining style properties on annotations, I think we’ll end up replacing MGLAnnotationImage with MGLAnnotationLayer and replacing all the existing MGLMapViewDelegate style methods with properties on MGLAnnotationLayer.

@jfirebaugh
Copy link
Contributor

I don't think that approach will pan out -- the annotation API has several behaviors that the runtime styling API does not:

  • Annotations persist across style loads. Mutations via the runtime styling API do not.
  • Symbol annotations support dynamic images. Symbol layers support only what's included in the original sprite.

frederoni added a commit that referenced this issue Jul 19, 2016
frederoni added a commit that referenced this issue Jul 21, 2016
@1ec5
Copy link
Contributor Author

1ec5 commented Aug 11, 2016

A first cut of the runtime styling API landed in #5727, which is in ios-v3.4.0-alpha.1.

Tail work includes:

#5943 #5947 #5948 #5949 #5950 #5951 #5952 #5953 #5954 #5955 #5956 #5957 #5958 #5959 #5960 #5961 #5962 #5965 #5966 #5969 #5970 #5971 #5972 #5973 #5974 #5975 #5989

@1ec5 1ec5 added the beta blocker Blocks the next beta release label Aug 15, 2016
@1ec5
Copy link
Contributor Author

1ec5 commented Aug 18, 2016

There’s lots of work remaining before we can call the runtime styling API complete. We’re tracking it with the runtime styling label now (iOS, macOS), so it’s time to close this issue. If you see anything missing or misbehaving, please open a separate issue so we can track and prioritize that work.

@1ec5
Copy link
Contributor Author

1ec5 commented Aug 26, 2016

Following up on #5626 (comment), we’re tracking the future of the annotation API in #1734 and #6181.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
beta blocker Blocks the next beta release feature iOS Mapbox Maps SDK for iOS macOS Mapbox Maps SDK for macOS runtime styling
Projects
None yet
Development

No branches or pull requests

5 participants