summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.jazzy.yaml12
-rw-r--r--CHANGELOG.md246
-rw-r--r--Makefile6
-rw-r--r--include/mbgl/map/map.hpp1
-rw-r--r--include/mbgl/util/string.hpp6
-rw-r--r--platform/android/CHANGELOG.md82
-rw-r--r--platform/android/MapboxGLAndroidSDK/gradle.properties2
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdateFactory.java2
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java2
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLng.java17
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngBounds.java2
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapFragment.java2
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java64
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java229
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java4
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java7
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/OnMapReadyCallback.java10
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/SupportMapFragment.java2
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java67
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/UserLocationView.java24
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java37
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java8
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEventManager.java13
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/MathUtils.java19
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml13
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties2
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/AnimatedMarkerActivity.java12
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/BulkMarkerActivity.java12
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/CameraAnimationTypeActivity.java12
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/CameraPositionActivity.java12
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/DirectionsActivity.java12
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/DynamicMarkerChangeActivity.java13
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/GeocoderActivity.java12
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/InfoWindowActivity.java12
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/InfoWindowAdapterActivity.java12
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/LatLngBoundsActivity.java12
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/ManualZoomActivity.java12
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/MapFragmentActivity.java6
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/MapPaddingActivity.java12
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/MaxMinZoomActivity.java17
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/MyLocationTrackingModeActivity.java12
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/OfflineActivity.java15
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/PolylineActivity.java12
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/ScrollByActivity.java12
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/annotations/MarkerTest.java2
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/camera/CameraPositionTest.java2
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java5
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngTest.java23
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/geometry/VisibleRegionTest.java8
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.java88
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/maps/UiSettingsTest.java24
-rwxr-xr-xplatform/android/src/native_map_view.cpp2
-rw-r--r--platform/darwin/include/MGLOfflinePack.h119
-rw-r--r--platform/darwin/include/MGLOfflineStorage.h166
-rw-r--r--platform/darwin/include/MGLTilePyramidOfflineRegion.h15
-rw-r--r--platform/darwin/include/MGLTypes.h2
-rw-r--r--platform/darwin/src/MGLOfflinePack.mm45
-rw-r--r--platform/darwin/src/MGLOfflinePack_Private.h58
-rw-r--r--platform/darwin/src/MGLOfflineRegion_Private.h11
-rw-r--r--platform/darwin/src/MGLOfflineStorage.mm142
-rw-r--r--platform/darwin/src/MGLOfflineStorage_Private.h3
-rw-r--r--platform/darwin/src/MGLTilePyramidOfflineRegion.mm54
-rw-r--r--platform/default/default_file_source.cpp1
-rw-r--r--platform/default/mbgl/storage/offline_database.cpp67
-rw-r--r--platform/default/mbgl/storage/offline_database.hpp2
-rw-r--r--platform/default/mbgl/storage/offline_download.cpp24
-rw-r--r--platform/default/mbgl/storage/offline_download.hpp3
-rw-r--r--platform/ios/CHANGELOG.md204
-rw-r--r--platform/ios/INSTALL.md12
-rw-r--r--platform/ios/Mapbox-iOS-SDK.podspec2
-rw-r--r--platform/ios/app/MBXOfflinePacksTableViewController.m147
-rw-r--r--platform/ios/docs/pod-README.md10
-rw-r--r--platform/ios/include/Mapbox.h4
-rwxr-xr-xplatform/ios/scripts/document.sh2
-rwxr-xr-xplatform/ios/scripts/package.sh36
-rw-r--r--platform/ios/src/MGLAPIClient.m2
-rw-r--r--platform/ios/src/MGLLocationManager.m17
-rw-r--r--platform/ios/src/MGLMapView.mm79
-rw-r--r--platform/ios/src/MGLMapboxEvents.m63
-rw-r--r--platform/ios/test/MGLTViewController.h1
-rw-r--r--platform/ios/test/MGLTViewController.m5
-rw-r--r--platform/ios/test/MapViewTests.m23
-rw-r--r--platform/osx/app/AppDelegate.h2
-rw-r--r--platform/osx/app/AppDelegate.m108
-rw-r--r--platform/osx/app/MainMenu.xib203
-rw-r--r--platform/osx/app/MapDocument.m38
-rw-r--r--platform/osx/app/OfflinePackNameValueTransformer.h5
-rw-r--r--platform/osx/app/OfflinePackNameValueTransformer.m33
-rw-r--r--platform/osx/app/mapboxgl-app.gypi2
-rw-r--r--platform/osx/src/MGLMapView.mm62
-rw-r--r--platform/osx/test/MGLOfflinePackTests.m40
-rw-r--r--platform/osx/test/MGLOfflineRegionTests.m35
-rw-r--r--platform/osx/test/MGLOfflineStorageTests.m126
-rw-r--r--platform/osx/test/osxtest.gypi3
-rw-r--r--src/mbgl/map/map.cpp6
-rw-r--r--src/mbgl/map/map_context.cpp5
-rw-r--r--src/mbgl/map/map_data.hpp1
-rw-r--r--test/fixtures/offline/v2.dbbin0 -> 43008 bytes
-rw-r--r--test/storage/offline_database.cpp36
-rw-r--r--test/storage/offline_download.cpp64
101 files changed, 2266 insertions, 1075 deletions
diff --git a/.gitignore b/.gitignore
index 1b6ed5386b..68e242a31b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,7 @@ offline.db
/test/fixtures/api/1.png
/test/fixtures/api/2.png
/test/fixtures/database/*.db
+/test/fixtures/offline/v3.db
/test/fixtures/**/actual.png
/test/fixtures/**/diff.png
/test/output
diff --git a/.jazzy.yaml b/.jazzy.yaml
index 430bfbeba5..602e8cdb0d 100644
--- a/.jazzy.yaml
+++ b/.jazzy.yaml
@@ -37,18 +37,24 @@ custom_categories:
- MGLOverlay
- MGLShape
- MGLUserLocation
- - name: Offline Viewing
+ - name: Offline Storage
children:
- MGLOfflineRegion
- MGLOfflineStorage
- MGLOfflinePack
- MGLOfflinePackAdditionCompletionHandler
- - MGLOfflinePackDelegate
- - MGLOfflinePackListingCompletionHandler
+ - MGLOfflinePackErrorNotification
+ - MGLOfflinePackErrorUserInfoKey
+ - MGLOfflinePackMaximumCountUserInfoKey
+ - MGLOfflinePackMaximumMapboxTilesReachedNotification
- MGLOfflinePackProgress
+ - MGLOfflinePackProgressChangedNotification
+ - MGLOfflinePackProgressUserInfoKey
- MGLOfflinePackRemovalCompletionHandler
- MGLOfflinePackState
+ - MGLOfflinePackStateUserInfoKey
- MGLTilePyramidOfflineRegion
+ - NSValue(MGLOfflinePackAdditions)
- name: Geometry
children:
- MGLCoordinateBounds
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e041e6ec69..6b8572f54e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,245 +1,7 @@
# Changelog
-## Android
+As of iOS 3.2.0 and Android 4.0.0, we've split the CHANGELOG into platform specific CHANGELOGS to make it easier to follow the progress of a particular project. Their new locations are as follows:
-## 3.2.0
-
-* Fixed crash caused by annotation image with non-integer width or height ([#3031](https://github.com/mapbox/mapbox-gl-native/issues/3031))
-* Tracking Mode Reverses Bearing Fix ([#3664](https://github.com/mapbox/mapbox-gl-native/issues/3664))
-* GPS Extra Rotation Fix ([#3661](https://github.com/mapbox/mapbox-gl-native/issues/3661))
-* Added new methods for getting and setting the min and max zoom levels: `getMinZoom`, `setMinZoom`, `getMaxZoom`, `setMaxZoom`. ([#509](https://github.com/mapbox/mapbox-gl-native/issues/509))
-
-## 3.1.0
-
-* Camera API Callback Improvements ([#3412](https://github.com/mapbox/mapbox-gl-native/issues/3412))
-* Coordinate Deprecated For LatLng ([#3309](https://github.com/mapbox/mapbox-gl-native/issues/3309))
-* Responsive User Dot Location Tracking ([#2049](https://github.com/mapbox/mapbox-gl-native/issues/2049))
-
-## 3.0.0
-
-* Added Camera API ([#3244](https://github.com/mapbox/mapbox-gl-native/issues/3244))
-* Custom Layer Support ([#3248](https://github.com/mapbox/mapbox-gl-native/issues/3348))
-* Reverse Tilt Gesutre Detection ([#3245](https://github.com/mapbox/mapbox-gl-native/issues/3245))
-* Decoupled Location Provider ([#2954](https://github.com/mapbox/mapbox-gl-native/issues/2954))
-
-## 2.3.0
-
-* Added Tilt / Pitch Support ([#2805](https://github.com/mapbox/mapbox-gl-native/issues/2805))
-* Keep InfoWindow Open When Panning ([#3121](https://github.com/mapbox/mapbox-gl-native/issues/3121))
-* Concurrent Multiple Open InfoWindows ([#3115](https://github.com/mapbox/mapbox-gl-native/issues/3115))
-* OkHttp Replace curl ([#2856](https://github.com/mapbox/mapbox-gl-native/issues/2856))
-* GPS and Compass Customization Support ([#2858](https://github.com/mapbox/mapbox-gl-native/issues/2858))
-
-## 2.2.0
-
-- New User Dot location graphics ([#2732](https://github.com/mapbox/mapbox-gl-native/issues/2732))
-- Custom Drawable Markers ([#2744](https://github.com/mapbox/mapbox-gl-native/issues/2744))
-- `MapView.setOnInfoWindowClickListener()` support ([#2448](https://github.com/mapbox/mapbox-gl-native/issues/2448))
-- Completed Annotations API ([#1716](https://github.com/mapbox/mapbox-gl-native/issues/1716))
-- Satellite Streets Style ([#2739](https://github.com/mapbox/mapbox-gl-native/issues/2739))
-- **RESOLVED** Black Screen On Ice Cream Sandwich and Jelly Bean devices ([#2802](https://github.com/mapbox/mapbox-gl-native/issues/2802))
-
-
-## 2.1.0
-
-- Initial Android release.
-
-Known issues:
-
-- Black Screen On Ice Cream Sandwich and Jelly Bean devices ([#2802](https://github.com/mapbox/mapbox-gl-native/issues/2802))
- - Resolved in 2.2.0
-
-## iOS master
-
-- Offline packs can now be downloaded to allow users to view specific regions of the map offline. A new MGLOfflineStorage class provides APIs for managing MGLOfflinePacks. ([#4221](https://github.com/mapbox/mapbox-gl-native/pull/4221))
-- The user dot no longer disappears after panning the map across the antimeridian at low zoom levels. ([#4275](https://github.com/mapbox/mapbox-gl-native/pull/4275))
-- The map no longer recoils when panning quickly at low zoom levels. ([#4214](https://github.com/mapbox/mapbox-gl-native/pull/4214))
-- An icon laid out along a line no longer appears if it would extend past the end of the line. Some one-way arrows no longer point the wrong way. ([#3839](https://github.com/mapbox/mapbox-gl-native/pull/3839))
-- Fixed warping of dashed lines near sharp corners. ([#3914](https://github.com/mapbox/mapbox-gl-native/pull/3914))
-- Telemetry location gathering now occurs only when the device is in motion. ([#4115](https://github.com/mapbox/mapbox-gl-native/pull/4115))
-- An account’s monthly active users metric only counts a user once a map view is displayed to that user. ([#3713](https://github.com/mapbox/mapbox-gl-native/pull/3713))
-- Updated documentation to reflect the requirement that you must embed the framework in the “Embedded Binaries” section in Xcode. ([#4011](https://github.com/mapbox/mapbox-gl-native/issues/4011))
-- Polygons and polylines now default to using the map view’s tint color. ([#4028](https://github.com/mapbox/mapbox-gl-native/pull/4028))
-- The user dot no longer lags when panning the map. ([#3683](https://github.com/mapbox/mapbox-gl-native/pull/3683))
-- The Improve This Map tool now uses the same zoom level that is currently being shown in the map view. ([#4068](https://github.com/mapbox/mapbox-gl-native/pull/4068))
-- Fixed a formatting issue in the documentation for `MGLCoordinateBoundsIsEmpty()`. ([#3958](https://github.com/mapbox/mapbox-gl-native/pull/3958))
-
-## iOS 3.1.2
-
-- You can once again install the static framework without manually linking several framework and library dependencies. ([#4029](https://github.com/mapbox/mapbox-gl-native/pull/4029))
-- The location manager used by MGLMapView to show the user’s location is now paused when the application is sent to the background. ([#4034](https://github.com/mapbox/mapbox-gl-native/pull/4034))
-
-## iOS 3.1.1
-
-- Corrected the dynamic framework’s minimum deployment target to iOS 8.0. ([#3872](https://github.com/mapbox/mapbox-gl-native/pull/3872))
-- Fixed Fabric compatibility. ([#3847](https://github.com/mapbox/mapbox-gl-native/pull/3847))
-- Fixed a crash that can occur when reselecting an annotation. ([#3881](https://github.com/mapbox/mapbox-gl-native/pull/3881))
-- Fixed an issue preventing the Latitude inspectable from working when it is set before setting the Zoom Level inspectable in Interface Builder. ([#3886](https://github.com/mapbox/mapbox-gl-native/pull/3886))
-- Fixed an issue that incorrectly expanded the tappable area of an annotation and prevented the annotation’s alignment rect insets from having any effect on the tappable area. ([#3898](https://github.com/mapbox/mapbox-gl-native/pull/3898))
-- Fixed an issue preventing `-[MGLMapViewDelegate mapView:tapOnCalloutForAnnotation:]` from being called when a non-custom callout view is tapped. ([#3875](https://github.com/mapbox/mapbox-gl-native/pull/3875))
-
-## iOS 3.1.0
-
-- The SDK is now distributed as a dynamic framework instead of a static library, resulting in a simpler installation workflow and significantly reduced download size. The framework contains both simulator and device content. If you install the dynamic framework manually, you’ll need to strip out the simulator content before submitting your application to the App Store due to [an Xcode bug](http://www.openradar.me/radar?id=6409498411401216); see the installation instructions included with the framework for details. ([#3183](https://github.com/mapbox/mapbox-gl-native/pull/3183))
-- Fixed an issue causing the entire MGLMapView to leak. ([#3448](https://github.com/mapbox/mapbox-gl-native/pull/3448))
-- `MGLMapView` methods that alter the viewport now accept optional completion handlers. ([#3090](https://github.com/mapbox/mapbox-gl-native/pull/3090))
-- You can now modify an annotation’s image after adding the annotation to the map. ([#3146](https://github.com/mapbox/mapbox-gl-native/pull/3146))
-- Tapping now selects annotations more reliably. Tapping near the top of a large annotation image now selects that annotation. An annotation image’s alignment insets influence how far away the user can tap and still select the annotation. For example, if your annotation image has a large shadow, you can keep that shadow from being tappable by excluding it from the image’s alignment rect. ([#3261](https://github.com/mapbox/mapbox-gl-native/pull/3261))
-- Annotations remain visible after switching to a different style. ([#3049](https://github.com/mapbox/mapbox-gl-native/pull/3049))
-- The minimum and maximum zoom levels can now be configured using the `minimumZoomLevel` and `maximumZoomLevel` properties, respectively. The map is no longer limited to zoom level 18: by default, the maximum zoom level is now 20, allowing for a more detailed map in urban areas. ([#3712](https://github.com/mapbox/mapbox-gl-native/pull/3712))
-- A new method on MGLMapView, `-flyToCamera:withDuration:completionHandler:`, lets you transition between viewpoints along an arc as if by aircraft. ([#3171](https://github.com/mapbox/mapbox-gl-native/pull/3171), [#3301](https://github.com/mapbox/mapbox-gl-native/pull/3301))
-- MGLMapCamera’s `altitude` values now match those of MKMapCamera. ([#3362](https://github.com/mapbox/mapbox-gl-native/pull/3362))
-- MGLMapView properties like `centerCoordinate` and `camera` now offset the center to account for any translucent top or bottom bar. As a result, when user tracking is enabled and the map view is an immediate child of a view controller, the user dot is centered in the unobscured portion of the map view. To override this offset, modify the `contentInset` property; you may also need to set the containing view controller’s `automaticallyAdjustsScrollViewInsets` property to `NO`. ([#3583](https://github.com/mapbox/mapbox-gl-native/pull/3583))
-- In user tracking mode, the user dot stays in a fixed position within MGLMapView while the map pans smoothly. A new property, `userLocationVerticalAlignment`, determines the user dot’s fixed position. ([#3589](https://github.com/mapbox/mapbox-gl-native/pull/3589))
-- When the user tracking mode is set to `MGLUserTrackingModeFollowWithCourse`, an optional `targetCoordinate` is kept within sight at all times as the user changes location. This property, in conjunction with the `userLocationVerticalAlignment` property, may be useful for displaying the user’s progress toward a waypoint. ([#3680](https://github.com/mapbox/mapbox-gl-native/pull/3680))
-- Heading or course tracking mode can now be enabled as soon as an MGLMapView is initialized. ([#3680](https://github.com/mapbox/mapbox-gl-native/pull/3680))
-- Zooming and rotation gestures no longer disable user tracking mode. ([#3589](https://github.com/mapbox/mapbox-gl-native/pull/3589))
-- User tracking mode starts out at a lower zoom level by default. ([#3589](https://github.com/mapbox/mapbox-gl-native/pull/3589))
-- Fixed an issue with small map views not properly fitting annotations within bounds. (#[3407](https://github.com/mapbox/mapbox-gl-native/pull/3407))
-- When the user rotates the map to within 7° of true north, the map view now snaps to true north. ([#3403](https://github.com/mapbox/mapbox-gl-native/pull/3403))
-- The map view’s background can now be transparent or translucent, as long as the style’s background layer is transparent or translucent and `MGLMapView.opaque` is set to `NO`. ([#3096](https://github.com/mapbox/mapbox-gl-native/pull/3096))
-- Documentation is now generated by [jazzy](https://github.com/realm/jazzy) instead of appledoc. ♪♫ ([#3203](https://github.com/mapbox/mapbox-gl-native/pull/3203))
-- New API to provide a custom callout view to the map for annotations. ([#3456](https://github.com/mapbox/mapbox-gl-native/pull/3456))
-- Made telemetry on/off setting available in-app. ([#3445](https://github.com/mapbox/mapbox-gl-native/pull/3445))
-- Fixed an issue with users not being counted by Mapbox if they had disabled telemetry. ([#3495](https://github.com/mapbox/mapbox-gl-native/pull/3495))
-- Fixed crash caused by MGLAnnotationImage with non-integer width or height ([#2198](https://github.com/mapbox/mapbox-gl-native/issues/2198))
-- Fixed “include of non-modular header” errors in Swift projects managed by CocoaPods. ([#3679](https://github.com/mapbox/mapbox-gl-native/pull/3679))
-- Avoids triggering the blue background location status bar when user has granted "when in use" permission. ([#3671](https://github.com/mapbox/mapbox-gl-native/issues/3671))
-- Deprecated the `debugActive` property and `-toggleDebug` method on MGLMapView in favor of a new `debugMask` property that exposes individual style debugging options. ([#3742](https://github.com/mapbox/mapbox-gl-native/pull/3742))
-
-## iOS 3.0.1
-
-- Fixed CoreTelephony.framework crash. ([#3170](https://github.com/mapbox/mapbox-gl-native/pull/3170))
-- Fixed an issue preventing the compass from responding to taps after the compass is moved programmatically. ([#3117](https://github.com/mapbox/mapbox-gl-native/pull/3117))
-- CocoaPods is now distributed via a (static) framework. ([#3181](https://github.com/mapbox/mapbox-gl-native/issues/3181))
-
-## iOS 3.0.0
-
-- If you install this SDK via CocoaPods, CocoaPods version 0.38.0 or above is required. ([#2132](https://github.com/mapbox/mapbox-gl-native/pull/2132))
-- The `styleID` property has been removed from MGLMapView. Instead, set the `styleURL` property to an NSURL in the form `mapbox://styles/STYLE_ID`. If you previously set the style ID in Interface Builder’s Attributes inspector, delete the `styleID` entry from the User Defined Runtime Attributes section of the Identity inspector, then set the new “Style URL” inspectable to a value in the form `mapbox://styles/STYLE_ID`. ([#2632](https://github.com/mapbox/mapbox-gl-native/pull/2632))
-- Default styles such as Streets are no longer bundled with the SDK; instead, they are loaded at runtime from the style API on mapbox.com. As always, you can use these default styles with any valid access token, and Streets continues to be `MGLMapView`’s initial style. The `bundledStyleURLs` property on `MGLMapView` has been deprecated in favor of several class methods on `MGLStyle` that provide direct access to the default styles. ([#2746](https://github.com/mapbox/mapbox-gl-native/pull/2746))
-- The SDK now builds with Bitcode enabled. A version of libMapbox.a with Bitcode disabled is also available. ([#2332](https://github.com/mapbox/mapbox-gl-native/issues/2332), [#3003](https://github.com/mapbox/mapbox-gl-native/pull/3003))
-- The style URL can be set to a local resource: `asset://local-color.json` and `local-color.json` both resolve to a file named `local-color.json` in the application’s root folder. ([#3087](https://github.com/mapbox/mapbox-gl-native/pull/3087))
-- The double-tap-drag gesture for zooming in and out is now consistent with the Google Maps SDK. ([#2153](https://github.com/mapbox/mapbox-gl-native/pull/2153))
-- A new `MGLAnnotationImage.enabled` property allows you to disable touch events on individual annotations. ([#2501](https://github.com/mapbox/mapbox-gl-native/pull/2501))
-- Fixed a rendering issue that caused one-way arrows along tile boundaries to point due east instead of in the direction of travel. ([#2530](https://github.com/mapbox/mapbox-gl-native/pull/2530))
-- Fixed an issue that prevented zoom level–dependent style properties from updating after zooming programmatically with animation. ([#2951](https://github.com/mapbox/mapbox-gl-native/pull/2951))
-- Performance and appearance improvements during annotation adds & removes. ([#1688](https://github.com/mapbox/mapbox-gl-native/issues/1688))
-- Overall improved performance during renders by not rendering faster than necessary. ([#1975](https://github.com/mapbox/mapbox-gl-native/issues/1975))
-- Fixed a rendering issue with styles that use the `background-pattern` property. ([#2531](https://github.com/mapbox/mapbox-gl-native/pull/2531))
-- Fixed a crash when reusing a single `MGLMapView` across multiple `UIViewController`s. ([#2969](https://github.com/mapbox/mapbox-gl-native/pull/2969))
-- Fixed a crash on iPod touch and other devices or simulators without a cell carrier. ([#2687](https://github.com/mapbox/mapbox-gl-native/issues/2687))
-- Eliminated flickering when opening and closing an overlay, such as an alert or action sheet. ([#2309](https://github.com/mapbox/mapbox-gl-native/pull/2309))
-- Labels can now line wrap on hyphens and other punctuation. ([#2598](https://github.com/mapbox/mapbox-gl-native/pull/2598))
-- A new delegate callback was added for observing taps to annotation callout views. ([#2596](https://github.com/mapbox/mapbox-gl-native/pull/2596))
-- `-mapViewRegionIsChanging:` is now sent to the map view’s delegate during gestures. ([#2700](https://github.com/mapbox/mapbox-gl-native/pull/2700))
-- Improved gesture recognition while the map is tilted. ([#2770](https://github.com/mapbox/mapbox-gl-native/pull/2770))
-- `-mapViewWillStartLoadingMap:` and `-mapViewDidFinishLoadingMap:` delegate methods now work. ([#2706](https://github.com/mapbox/mapbox-gl-native/pull/2706))
-- Removed CoreTelephony.framework dependency. ([#2581](https://github.com/mapbox/mapbox-gl-native/pull/2581))
-- Improved user location annotation responsiveness. ([#2643](https://github.com/mapbox/mapbox-gl-native/pull/2643))
-
-## iOS 2.1.2
-
-- Built with Xcode 6.4 to not yet trigger Bitcode compatibility until Xcode 7 stabilizes. ([#2332](https://github.com/mapbox/mapbox-gl-native/issues/2332))
-
-## iOS 2.1.1
-
-- Fixes for Xcode 7 and Bitcode. ([#2238](https://github.com/mapbox/mapbox-gl-native/pull/2238))
-
-## iOS 2.1.0
-
-- A two-finger vertical swipe now tilts the map into perspective mode. ([#2116](https://github.com/mapbox/mapbox-gl-native/pull/2116))
-- A new `MGLMapCamera` API allows you to transition multiple viewpoint properties, including rotation and pitch, simultaneously with an optional custom duration and timing function. ([#2193](https://github.com/mapbox/mapbox-gl-native/pull/2193))
-- A new user tracking mode, `MGLUserTrackingModeFollowWithCourse`, has been added for indicating the current direction of travel. ([#2068](https://github.com/mapbox/mapbox-gl-native/pull/2068))
-- Version 8 (`v8`) of the [Mapbox GL style spec](https://www.mapbox.com/mapbox-gl-style-spec/) is now required. If you are using a custom `v7` style, it needs to be upgraded using [this migrator script](https://github.com/mapbox/mapbox-gl-style-spec/blob/mb-pages/migrations/v7.js). ([#2052](https://github.com/mapbox/mapbox-gl-native/pull/2052))
-- Applications built with Mapbox GL no longer crash when Location Services launches them in background mode. ([#1821](https://github.com/mapbox/mapbox-gl-native/pull/1821), [#1869](https://github.com/mapbox/mapbox-gl-native/pull/1869))
-- Fixed a crash when adding annotations to an `MGLMapView` inside `-viewDidLoad`. ([#1874](https://github.com/mapbox/mapbox-gl-native/pull/1874))
-- The user location annotation view now indicates the location reading’s accuracy and the device’s heading. ([#2010](https://github.com/mapbox/mapbox-gl-native/pull/2010))
-- Eliminated linker warnings and errors when building against the iOS 9.0 SDK in Xcode 7. ([#1962](https://github.com/mapbox/mapbox-gl-native/pull/1962))
-- Worked around a bug in the iOS 9.0 SDK that caused a crash on launch. ([#1958](https://github.com/mapbox/mapbox-gl-native/pull/1958))
-- User location tracking no longer sends `MGLMapView` into an invalid region on iOS 9. ([#1925](https://github.com/mapbox/mapbox-gl-native/pull/1925))
-- Eliminated console spew in the iOS demo application that was related to Mapbox Metrics HTTP requests. ([#1937](https://github.com/mapbox/mapbox-gl-native/issues/1937))
-- Implemented `-[MGLMapView showAnnotations:animated:]`. ([#2050](https://github.com/mapbox/mapbox-gl-native/pull/2050))
-- Fixed a crash adding a shape annotation with zero points. ([#2098](https://github.com/mapbox/mapbox-gl-native/pull/2098))
-- Debug mode now displays information useful for debugging the label collision algorithm. ([#1808](https://github.com/mapbox/mapbox-gl-native/pull/1808))
-- Minor style updates. ([#1910](https://github.com/mapbox/mapbox-gl-native/pull/1910))
-- The CocoaPods pod now contains a `README.md` file. ([#1886](https://github.com/mapbox/mapbox-gl-native/pull/1886))
-
-## iOS 2.0.0
-
-Repackaging 2.0.0-pre.1 as it contained no issues.
-
-## iOS 2.0.0-pre.1
-
-Repackaging 0.5.1 as the Mapbox iOS SDK 2.0.0 series.
-
-## 0.5.1
-
-### iOS
-
-- Added support for CocoaPods 0.38.0. ([#1876](https://github.com/mapbox/mapbox-gl-native/pull/1876))
-
-## 0.5.0
-
-### Core
-
-- Support for runtime marker imagery. ([#941](https://github.com/mapbox/mapbox-gl-native/pull/941))
-- Added `Map::fitBounds()` for region-based viewport setting. ([#1092](https://github.com/mapbox/mapbox-gl-native/issues/1092))
-- Added a raster satellite bundled style and improved raster rendering. ([#963](https://github.com/mapbox/mapbox-gl-native/issues/963))
-- Improved round line joins for semi-transparent lines. ([#1839](https://github.com/mapbox/mapbox-gl-native/pull/1839))
-- Improved map render lifecycle notifications. ([#1026](https://github.com/mapbox/mapbox-gl-native/issues/1026))
-- Fixed a bug that caused annotations not to show at zoom level zero. ([#1279](https://github.com/mapbox/mapbox-gl-native/issues/1279))
-- Fixed a bug with the ordering of shape layers. ([#1866](https://github.com/mapbox/mapbox-gl-native/pull/1866))
-- Other bug fixes and performance improvements.
-
-### iOS
-
-- **Breaking:** Headers now make use of lightweight generics, eliminating many unnecessary casts when working with annotations in Swift 2.0 in Xcode 7. ([#1711](https://github.com/mapbox/mapbox-gl-native/pull/1711))
-- **Breaking:** `-mapView:symbolNameForAnnotation:` has been removed from the `MGLMapViewDelegate` protocol. Implement `-mapView:imageForAnnotation:` instead, which accepts images at runtime. ([#941](https://github.com/mapbox/mapbox-gl-native/pull/941))
-- **Breaking:** `MGLMapView.direction` is now expressed in terms of degrees clockwise from true north, as indicated in the documentation, rather than counterclockwise. ([#1789](https://github.com/mapbox/mapbox-gl-native/pull/1789))
-- A Satellite style showing Mapbox Satellite imagery is now bundled with Mapbox GL. ([#1845](https://github.com/mapbox/mapbox-gl-native/pull/1845))
-- Improved `UIView` tracking to the map. ([#1813](https://github.com/mapbox/mapbox-gl-native/pull/1813))
-- Delegate method `-[MGLMapViewDelegate mapView:didFailToLocateUserWithError:]` now works. ([#1608](https://github.com/mapbox/mapbox-gl-native/pull/1608))
-- It is now possible to fit the map’s viewport to a coordinate bounding box via `-[MGLMapView setVisibleCoordinateBounds:animated:]` or to a specific set of coordinates via `-[MGLMapView setVisibleCoordinates:count:edgePadding:animated:]`. ([#1783](https://github.com/mapbox/mapbox-gl-native/pull/1783), [#1795](https://github.com/mapbox/mapbox-gl-native/pull/1795))
-- The logo and ℹ️ no longer disappear or get distorted after embedding MGLMapView in a different view, and you can now access these subviews directly via properties on MGLMapView. ([#1779](https://github.com/mapbox/mapbox-gl-native/pull/1779), [#1815](https://github.com/mapbox/mapbox-gl-native/pull/1815))
-- Raster tiles now look sharper midway between two zoom levels. ([#1843](https://github.com/mapbox/mapbox-gl-native/pull/1843))
-- Resetting the map rotation to north no longer also resets the user location tracking mode. ([#1809](https://github.com/mapbox/mapbox-gl-native/pull/1809))
-- `-[MGLMapView convertPoint:toCoordinateFromView:]` now returns accurate coordinates on iPhone 6. ([#1827](https://github.com/mapbox/mapbox-gl-native/pull/1827))
-- Fixed an issue in which `-[MGLMapView direction]` would sometimes return 360 instead of 0. ([#1829](https://github.com/mapbox/mapbox-gl-native/pull/1829))
-- Build against iOS 8.4. ([#1868](https://github.com/mapbox/mapbox-gl-native/pull/1868))
-
-## 0.4.0
-
-### Core
-
-- Support for polyline and polygon shape annotations. ([#1655](https://github.com/mapbox/mapbox-gl-native/issues/1655))
-- Improved placement and density of labels. ([#1666](https://github.com/mapbox/mapbox-gl-native/issues/1666), [blog](https://www.mapbox.com/blog/better-label-placement-mapbox-mobile/))
-- Improved z-ordering appearance of point markers. ([#988](https://github.com/mapbox/mapbox-gl-native/issues/988))
-- Fixed an issue in which certain features, such as roundabouts, were not rendered completely. ([#1725](https://github.com/mapbox/mapbox-gl-native/issues/1725))
-- Many bug fixes and performance and stability improvements.
-- Improved tests.
-
-### iOS
-
-- **Breaking:** `MGLMapView` no longer manages Mapbox access tokens directly; an access token cannot be passed in when initializing the map view. Instead, set `MGLMapboxAccessToken` to your access token in your app’s `Info.plist` file, or call `+[MGLAccountManager setAccessToken:]` before initializing the map view. If you were setting the access token inside an Interface Builder inspectable, also remove it from the User Defined Runtime Attributes section of the Identity inspector. ([#1553](https://github.com/mapbox/mapbox-gl-native/issues/1553))
-- **Breaking:** `MGLAccountManager`'s `-setMapboxMetricsEnabledSettingShownInApp:` has been removed. If you implement a Mapbox Metrics switch inside your app, instead of inside a Settings bundle, set `MGLMapboxMetricsEnabledSettingShownInApp` to `YES` in the `Info.plist` file. ([#1553](https://github.com/mapbox/mapbox-gl-native/issues/1553))
-- **Breaking:** `MGLMapView`'s `-mapID` has been renamed to `-styleID`. ([#1561](https://github.com/mapbox/mapbox-gl-native/issues/1561))
-- Headers have been audited for nullability, improving type safety in both Objective-C and Swift 1.2 when compiling with Xcode 6.3 or above. ([#1578](https://github.com/mapbox/mapbox-gl-native/issues/1578))
-- Fixed an issue in which the map would sometimes spin 180° while rotating the map with two fingers. ([#1453](https://github.com/mapbox/mapbox-gl-native/issues/1453))
-- Added a shortcut to the Mapbox Metrics switch in `MGLMapView`'s action sheet that is attached to the ℹ️ button. ([#1611](https://github.com/mapbox/mapbox-gl-native/issues/1611))
-- `MGLMapView` now supports Interface Builder designables. When you add an `MGLMapView` to a storyboard, it displays instructions for getting set up directly on the storyboard canvas. ([#1573](https://github.com/mapbox/mapbox-gl-native/issues/1573))
-- The default title for the user location annotation is now “You Are Here”. You can customize the title by setting `mapView.userAnnotation.title`. ([#1559](https://github.com/mapbox/mapbox-gl-native/issues/1559))
-- Internal use of the Reachability library has been cleaned up so that your app can include its own copy of Reachability. ([#1718](https://github.com/mapbox/mapbox-gl-native/issues/1718))
-- Now distribute a binary stripped of debugging symbols by default with an optional, secondary symbols build. ([#1650](https://github.com/mapbox/mapbox-gl-native/issues/1650))
-
-## 0.3.1
-
-- Temporarily removed `IBDesignable` support on iOS.
-
-## 0.3.0
-
-- Initial iOS beta release.
-
-Known issues:
-
-- None.
+* Android --> [`/platform/android/CHANGELOG.md`](platform/android/CHANGELOG.md)
+* iOS --> [`/platform/ios/CHANGELOG.md`](platform/ios/CHANGELOG.md)
+* Node.js --> [`/platform/node/CHANGELOG.md`](platform/node/CHANGELOG.md)
diff --git a/Makefile b/Makefile
index 317a40ef5a..395ab5f78b 100644
--- a/Makefile
+++ b/Makefile
@@ -51,9 +51,9 @@ ibench: export XCODEBUILD_ARGS += -sdk iphoneos ARCHS="arm64"
ibench: ; $(RUN) HOST=ios Xcode/ios-bench
.PHONY: ipackage ipackage-strip ipackage-sim itest
-ipackage: Xcode/ios ; @JOBS=$(JOBS) BITCODE=$(BITCODE) FORMAT=$(FORMAT) BUILD_DEVICE=$(BUILD_DEVICE) SYMBOLS=$(SYMBOLS) ./platform/ios/scripts/package.sh
-ipackage-strip: Xcode/ios ; @JOBS=$(JOBS) BITCODE=$(BITCODE) FORMAT=$(FORMAT) BUILD_DEVICE=$(BUILD_DEVICE) SYMBOLS=NO ./platform/ios/scripts/package.sh
-ipackage-sim: Xcode/ios ; @JOBS=$(JOBS) BUILDTYPE=Debug BITCODE=$(BITCODE) FORMAT=dynamic BUILD_DEVICE=false SYMBOLS=$(SYMBOLS) ./platform/ios/scripts/package.sh
+ipackage: Xcode/ios ; @JOBS=$(JOBS) BITCODE=$(BITCODE) FORMAT=$(FORMAT) BUILD_DEVICE=$(BUILD_DEVICE) SYMBOLS=$(SYMBOLS) BUNDLE_RESOURCES=YES PLACE_RESOURCE_BUNDLES_OUTSIDE_FRAMEWORK=YES ./platform/ios/scripts/package.sh
+ipackage-strip: Xcode/ios ; @JOBS=$(JOBS) BITCODE=$(BITCODE) FORMAT=$(FORMAT) BUILD_DEVICE=$(BUILD_DEVICE) SYMBOLS=NO BUNDLE_RESOURCES=YES PLACE_RESOURCE_BUNDLES_OUTSIDE_FRAMEWORK=YES ./platform/ios/scripts/package.sh
+ipackage-sim: Xcode/ios ; @JOBS=$(JOBS) BUILDTYPE=Debug BITCODE=$(BITCODE) FORMAT=dynamic BUILD_DEVICE=false SYMBOLS=$(SYMBOLS) BUNDLE_RESOURCES=YES PLACE_RESOURCE_BUNDLES_OUTSIDE_FRAMEWORK=YES ./platform/ios/scripts/package.sh
iframework: Xcode/ios ; @JOBS=$(JOBS) BITCODE=$(BITCODE) FORMAT=dynamic BUILD_DEVICE=$(BUILD_DEVICE) SYMBOLS=$(SYMBOLS) ./platform/ios/scripts/package.sh
ifabric: Xcode/ios ; @JOBS=$(JOBS) BITCODE=$(BITCODE) FORMAT=$(FORMAT) BUILD_DEVICE=$(BUILD_DEVICE) SYMBOLS=NO BUNDLE_RESOURCES=YES ./platform/ios/scripts/package.sh
itest: ipackage-sim ; ./platform/ios/scripts/test.sh
diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp
index c85e7eae38..9d586d8b8a 100644
--- a/include/mbgl/map/map.hpp
+++ b/include/mbgl/map/map.hpp
@@ -199,6 +199,7 @@ private:
};
RenderState renderState = RenderState::never;
+ bool loading = false;
};
} // namespace mbgl
diff --git a/include/mbgl/util/string.hpp b/include/mbgl/util/string.hpp
index 6c2433534e..51ee848f22 100644
--- a/include/mbgl/util/string.hpp
+++ b/include/mbgl/util/string.hpp
@@ -28,6 +28,12 @@ inline std::string toString(uint8_t num) {
}
inline std::string toString(std::exception_ptr error) {
+ assert(error);
+
+ if (!error) {
+ return "(null)";
+ }
+
try {
std::rethrow_exception(error);
} catch (const std::exception& ex) {
diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md
new file mode 100644
index 0000000000..7ba51fd368
--- /dev/null
+++ b/platform/android/CHANGELOG.md
@@ -0,0 +1,82 @@
+# Changelog for Mapbox Android SDK
+
+Mapbox welcomes participation and contributions from everyone. If you'd like to do so please see the [`Contributing Guide`](https://github.com/mapbox/mapbox-gl-native/blob/master/CONTRIBUTING.md) first to get started.
+
+## 4.0.0
+
+Mapbox Android 4.0.0 contains the following 3 major new features.
+
+* MapboxMap API Change ([#3145](https://github.com/mapbox/mapbox-gl-native/issues/3145))
+* Offline Maps ([#3891](https://github.com/mapbox/mapbox-gl-native/issues/3891))
+* Telemetry ([#2421](https://github.com/mapbox/mapbox-gl-native/issues/2421))
+
+## 4.0.0-rc.1
+
+* Default Value Bug Fix for MapboxMapOptions ([#4398](https://github.com/mapbox/mapbox-gl-native/issues/4398))
+* NullPointerException When Scrolling ([#4424](https://github.com/mapbox/mapbox-gl-native/issues/4424))
+* Platform Specific CHANGELOGS ([#4432](https://github.com/mapbox/mapbox-gl-native/issues/4432))
+* Introduce LatLng.wrap() ([#4475](https://github.com/mapbox/mapbox-gl-native/issues/4475))
+
+## 4.0.0-beta.2
+
+* arm64 ABI Support ([#3128](https://github.com/mapbox/mapbox-gl-native/issues/3128))
+* Unify Offline And Cache Databases ([#4362](https://github.com/mapbox/mapbox-gl-native/issues/4362))
+* Offline Database Vacuuming ([#4342](https://github.com/mapbox/mapbox-gl-native/pull/4342))
+* Telemetry User Agent Fix ([#4328](https://github.com/mapbox/mapbox-gl-native/issues/4328))
+* OnCameraChange Bug Fix ([#4327](https://github.com/mapbox/mapbox-gl-native/issues/4327))
+* OnCameraChangeListener vs getCameraPosition ([#4326](https://github.com/mapbox/mapbox-gl-native/issues/4326))
+
+## 4.0.0-beta.1
+
+Mapbox Android 4.0.0 is the most ambitious Android release to date with 3 major new features being released. To help us produce the highest quality SDK possible we're releasing an official Beta release first so that everyone has time to explore it and help hardened it before the official 4.0.0 Final Release.
+
+* MapboxMap API Change ([#3145](https://github.com/mapbox/mapbox-gl-native/issues/3145))
+* Offline Maps ([#3891](https://github.com/mapbox/mapbox-gl-native/issues/3891))
+* Telemetry ([#2421](https://github.com/mapbox/mapbox-gl-native/issues/2421))
+
+## 3.2.0
+
+* Fixed crash caused by annotation image with non-integer width or height ([#3031](https://github.com/mapbox/mapbox-gl-native/issues/3031))
+* Tracking Mode Reverses Bearing Fix ([#3664](https://github.com/mapbox/mapbox-gl-native/issues/3664))
+* GPS Extra Rotation Fix ([#3661](https://github.com/mapbox/mapbox-gl-native/issues/3661))
+* Added new methods for getting and setting the min and max zoom levels: `getMinZoom`, `setMinZoom`, `getMaxZoom`, `setMaxZoom`. ([#509](https://github.com/mapbox/mapbox-gl-native/issues/509))
+
+## 3.1.0
+
+* Camera API Callback Improvements ([#3412](https://github.com/mapbox/mapbox-gl-native/issues/3412))
+* Coordinate Deprecated For LatLng ([#3309](https://github.com/mapbox/mapbox-gl-native/issues/3309))
+* Responsive User Dot Location Tracking ([#2049](https://github.com/mapbox/mapbox-gl-native/issues/2049))
+
+## 3.0.0
+
+* Added Camera API ([#3244](https://github.com/mapbox/mapbox-gl-native/issues/3244))
+* Custom Layer Support ([#3248](https://github.com/mapbox/mapbox-gl-native/issues/3348))
+* Reverse Tilt Gesutre Detection ([#3245](https://github.com/mapbox/mapbox-gl-native/issues/3245))
+* Decoupled Location Provider ([#2954](https://github.com/mapbox/mapbox-gl-native/issues/2954))
+
+## 2.3.0
+
+* Added Tilt / Pitch Support ([#2805](https://github.com/mapbox/mapbox-gl-native/issues/2805))
+* Keep InfoWindow Open When Panning ([#3121](https://github.com/mapbox/mapbox-gl-native/issues/3121))
+* Concurrent Multiple Open InfoWindows ([#3115](https://github.com/mapbox/mapbox-gl-native/issues/3115))
+* OkHttp Replace curl ([#2856](https://github.com/mapbox/mapbox-gl-native/issues/2856))
+* GPS and Compass Customization Support ([#2858](https://github.com/mapbox/mapbox-gl-native/issues/2858))
+
+## 2.2.0
+
+- New User Dot location graphics ([#2732](https://github.com/mapbox/mapbox-gl-native/issues/2732))
+- Custom Drawable Markers ([#2744](https://github.com/mapbox/mapbox-gl-native/issues/2744))
+- `MapView.setOnInfoWindowClickListener()` support ([#2448](https://github.com/mapbox/mapbox-gl-native/issues/2448))
+- Completed Annotations API ([#1716](https://github.com/mapbox/mapbox-gl-native/issues/1716))
+- Satellite Streets Style ([#2739](https://github.com/mapbox/mapbox-gl-native/issues/2739))
+- **RESOLVED** Black Screen On Ice Cream Sandwich and Jelly Bean devices ([#2802](https://github.com/mapbox/mapbox-gl-native/issues/2802))
+
+
+## 2.1.0
+
+- Initial Android release.
+
+Known issues:
+
+- Black Screen On Ice Cream Sandwich and Jelly Bean devices ([#2802](https://github.com/mapbox/mapbox-gl-native/issues/2802))
+ - Resolved in 2.2.0
diff --git a/platform/android/MapboxGLAndroidSDK/gradle.properties b/platform/android/MapboxGLAndroidSDK/gradle.properties
index 0e38586447..56cb4324fb 100644
--- a/platform/android/MapboxGLAndroidSDK/gradle.properties
+++ b/platform/android/MapboxGLAndroidSDK/gradle.properties
@@ -1,5 +1,5 @@
GROUP=com.mapbox.mapboxsdk
-VERSION_NAME=4.0.0-SNAPSHOT
+VERSION_NAME=4.1.0-SNAPSHOT
POM_DESCRIPTION=Mapbox GL Android SDK
POM_URL=https://github.com/mapbox/mapbox-gl-native
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdateFactory.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdateFactory.java
index b3da298c06..8c6591f9a0 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdateFactory.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdateFactory.java
@@ -249,7 +249,7 @@ public final class CameraUpdateFactory {
float scaleY = (uiSettings.getHeight() - padding.top - padding.bottom) / height;
float minScale = scaleX < scaleY ? scaleX : scaleY;
double zoom = projection.calculateZoom(minScale);
- zoom = MathUtils.clamp(zoom, (float) uiSettings.getMinZoom(), (float) uiSettings.getMaxZoom());
+ zoom = MathUtils.clamp(zoom, (float) mapboxMap.getMinZoom(), (float) mapboxMap.getMaxZoom());
// Calculate the center point
PointF paddedNEPixel = new PointF(nePixel.x + padding.right / minScale, nePixel.y + padding.top / minScale);
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java
index 00add0ec67..80d9a06890 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java
@@ -40,7 +40,7 @@ public class MapboxConstants {
/**
* The currently supported maximum zoom level.
*/
- public static final float MAXIMUM_ZOOM = 25.5f;
+ public static final float MAXIMUM_ZOOM = 21.0f;
/**
* The currently supported maximum tilt value.
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLng.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLng.java
index f24c2ed431..1ba2a14e94 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLng.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLng.java
@@ -6,6 +6,7 @@ import android.os.Parcelable;
import com.mapbox.mapboxsdk.constants.GeoConstants;
import com.mapbox.mapboxsdk.constants.MathConstants;
+import com.mapbox.mapboxsdk.utils.MathUtils;
import java.io.Serializable;
@@ -118,6 +119,20 @@ public class LatLng implements ILatLng, Parcelable {
return altitude;
}
+ /**
+ * Return a new LatLng object with a wrapped Longitude. This allows original data object
+ * to remain unchanged.
+ * @return New LatLng object with wrapped Longitude
+ */
+ public LatLng wrap(){
+ LatLng wrappedVersion = new LatLng(this);
+ double lon = wrappedVersion.getLongitude();
+ if (lon < GeoConstants.MIN_LONGITUDE || lon > GeoConstants.MAX_LONGITUDE) {
+ wrappedVersion.setLongitude(MathUtils.wrap(wrappedVersion.getLongitude(), GeoConstants.MIN_LONGITUDE, GeoConstants.MAX_LONGITUDE));
+ }
+ return wrappedVersion;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -147,7 +162,7 @@ public class LatLng implements ILatLng, Parcelable {
@Override
public String toString() {
- return "LatLng [longitude=" + longitude + ", latitude=" + latitude + ", altitude=" + altitude + "]";
+ return "LatLng [latitude=" + latitude + ", longitude=" + longitude + ", altitude=" + altitude + "]";
}
@Override
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngBounds.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngBounds.java
index 3b63871382..5251ab4d6d 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngBounds.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngBounds.java
@@ -162,7 +162,7 @@ public class LatLngBounds implements Parcelable {
* @param latLng the point which may be contained
* @return true, if the point is contained within the box.
*/
- public boolean including(final ILatLng latLng) {
+ public boolean contains(final ILatLng latLng) {
final double latitude = latLng.getLatitude();
final double longitude = latLng.getLongitude();
return ((latitude < this.mLatNorth)
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapFragment.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapFragment.java
index dd056ff9b7..2fefd805ea 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapFragment.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapFragment.java
@@ -76,7 +76,6 @@ public final class MapFragment extends Fragment {
@Override
public void onStart() {
super.onStart();
- mMap.onStart();
mMap.getMapAsync(mOnMapReadyCallback);
}
@@ -115,7 +114,6 @@ public final class MapFragment extends Fragment {
@Override
public void onStop() {
super.onStop();
- mMap.onStop();
}
/**
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
index 1f2b2bd48a..60c79a1828 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
@@ -13,6 +13,7 @@ import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PointF;
@@ -265,8 +266,8 @@ public class MapView extends FrameLayout {
uiSettings.setZoomControlsEnabled(options.getZoomControlsEnabled());
// Zoom
- uiSettings.setMaxZoom(options.getMaxZoom());
- uiSettings.setMinZoom(options.getMinZoom());
+ mMapboxMap.setMaxZoom(options.getMaxZoom());
+ mMapboxMap.setMinZoom(options.getMinZoom());
// Compass
uiSettings.setCompassEnabled(options.getCompassEnabled());
@@ -274,6 +275,9 @@ public class MapView extends FrameLayout {
int[] compassMargins = options.getCompassMargins();
if (compassMargins != null) {
uiSettings.setCompassMargins(compassMargins[0], compassMargins[1], compassMargins[2], compassMargins[3]);
+ } else {
+ int tenDp = (int) getResources().getDimension(R.dimen.ten_dp);
+ uiSettings.setCompassMargins(tenDp, tenDp, tenDp, tenDp);
}
// Logo
@@ -282,6 +286,9 @@ public class MapView extends FrameLayout {
int[] logoMargins = options.getLogoMargins();
if (logoMargins != null) {
uiSettings.setLogoMargins(logoMargins[0], logoMargins[1], logoMargins[2], logoMargins[3]);
+ } else {
+ int sixteenDp = (int) getResources().getDimension(R.dimen.sixteen_dp);
+ uiSettings.setLogoMargins(sixteenDp, sixteenDp, sixteenDp, sixteenDp);
}
// Attribution
@@ -290,6 +297,11 @@ public class MapView extends FrameLayout {
int[] attributionMargins = options.getAttributionMargins();
if (attributionMargins != null) {
uiSettings.setAttributionMargins(attributionMargins[0], attributionMargins[1], attributionMargins[2], attributionMargins[3]);
+ } else {
+ Resources resources = getResources();
+ int sevenDp = (int) resources.getDimension(R.dimen.seven_dp);
+ int seventySixDp = (int) resources.getDimension(R.dimen.seventy_six_dp);
+ uiSettings.setAttributionMargins(seventySixDp, sevenDp, sevenDp, sevenDp);
}
}
@@ -386,7 +398,7 @@ public class MapView extends FrameLayout {
addOnMapChangedListener(new OnMapChangedListener() {
@Override
public void onMapChanged(@MapChange int change) {
- if (change == DID_FINISH_LOADING_MAP) {
+ if (change == DID_FINISH_RENDERING_MAP_FULLY_RENDERED) {
reloadIcons();
reloadMarkers();
adjustTopOffsetPixels();
@@ -482,22 +494,6 @@ public class MapView extends FrameLayout {
}
/**
- * You must call this method from the parent's {@link Activity#onStart()} or {@link Fragment#onStart()}.
- */
- @UiThread
- public void onStart() {
- mUserLocationView.onStart();
- }
-
- /**
- * You must call this method from the parent's {@link Activity#onStop()} or {@link Fragment#onStop()}
- */
- @UiThread
- public void onStop() {
- mUserLocationView.onStop();
- }
-
- /**
* You must call this method from the parent's {@link Activity#onPause()} or {@link Fragment#onPause()}.
*/
@UiThread
@@ -581,6 +577,10 @@ public class MapView extends FrameLayout {
//
double getDirection() {
+ if(mDestroyed){
+ return 0;
+ }
+
double direction = -mNativeMapView.getBearing();
while (direction > 360) {
@@ -691,6 +691,9 @@ public class MapView extends FrameLayout {
} else {
mNativeMapView.scaleBy(0.5, x / mScreenDensity, y / mScreenDensity, MapboxConstants.ANIMATION_DURATION);
}
+
+ // work around to invalidate camera position
+ postDelayed(new ZoomInvalidator(mMapboxMap), MapboxConstants.ANIMATION_DURATION);
}
//
@@ -1268,6 +1271,10 @@ public class MapView extends FrameLayout {
// Must handle window resizing here.
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+ if(mDestroyed){
+ return;
+ }
+
mNativeMapView.resizeFramebuffer(width, height);
}
@@ -1275,6 +1282,10 @@ public class MapView extends FrameLayout {
// Must sync with UI here
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ if(mDestroyed){
+ return;
+ }
+
mCompassView.update(getDirection());
mUserLocationView.update();
for (InfoWindow infoWindow : mMapboxMap.getInfoWindows()) {
@@ -2540,6 +2551,21 @@ public class MapView extends FrameLayout {
}
}
+ private static class ZoomInvalidator implements Runnable {
+
+ private MapboxMap mapboxMap;
+
+ public ZoomInvalidator(MapboxMap mapboxMap) {
+ this.mapboxMap = mapboxMap;
+ }
+
+ @Override
+ public void run() {
+ // invalidate camera position
+ mapboxMap.getCameraPosition();
+ }
+ }
+
/**
* Definition of a map change event.
*
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java
index fbeefd6ef6..e1a9cd8cdf 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java
@@ -2,6 +2,7 @@ package com.mapbox.mapboxsdk.maps;
import android.location.Location;
import android.os.SystemClock;
+import android.support.annotation.FloatRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
@@ -72,6 +73,9 @@ public class MapboxMap {
private MapboxMap.OnFpsChangedListener mOnFpsChangedListener;
private MapboxMap.OnCameraChangeListener mOnCameraChangeListener;
+ private double mMaxZoomLevel = -1;
+ private double mMinZoomLevel = -1;
+
MapboxMap(@NonNull MapView mapView) {
mMapView = mapView;
mMapView.addOnMapChangedListener(new MapChangeCameraPositionListener());
@@ -84,6 +88,78 @@ public class MapboxMap {
}
//
+ // MinZoom
+ //
+
+ /**
+ * <p>
+ * Sets the minimum zoom level the map can be displayed at.
+ * </p>
+ *
+ * @param minZoom The new minimum zoom level.
+ */
+ @UiThread
+ public void setMinZoom(@FloatRange(from = MapboxConstants.MINIMUM_ZOOM, to = MapboxConstants.MAXIMUM_ZOOM) double minZoom) {
+ if ((minZoom < MapboxConstants.MINIMUM_ZOOM) || (minZoom > MapboxConstants.MAXIMUM_ZOOM)) {
+ Log.e(MapboxConstants.TAG, "Not setting minZoom, value is in unsupported range: " + minZoom);
+ return;
+ }
+ mMinZoomLevel = minZoom;
+ mMapView.setMinZoom(minZoom);
+ }
+
+ /**
+ * <p>
+ * Gets the maximum zoom level the map can be displayed at.
+ * </p>
+ *
+ * @return The minimum zoom level.
+ */
+ @UiThread
+ public double getMinZoom() {
+ if (mMinZoomLevel == -1) {
+ return mMinZoomLevel = mMapView.getMinZoom();
+ }
+ return mMinZoomLevel;
+ }
+
+ //
+ // MaxZoom
+ //
+
+ /**
+ * <p>
+ * Sets the maximum zoom level the map can be displayed at.
+ * </p>
+ *
+ * @param maxZoom The new maximum zoom level.
+ */
+ @UiThread
+ public void setMaxZoom(@FloatRange(from = MapboxConstants.MINIMUM_ZOOM, to = MapboxConstants.MAXIMUM_ZOOM) double maxZoom) {
+ if ((maxZoom < MapboxConstants.MINIMUM_ZOOM) || (maxZoom > MapboxConstants.MAXIMUM_ZOOM)) {
+ Log.e(MapboxConstants.TAG, "Not setting maxZoom, value is in unsupported range: " + maxZoom);
+ return;
+ }
+ mMaxZoomLevel = maxZoom;
+ mMapView.setMaxZoom(maxZoom);
+ }
+
+ /**
+ * <p>
+ * Gets the maximum zoom level the map can be displayed at.
+ * </p>
+ *
+ * @return The maximum zoom level.
+ */
+ @UiThread
+ public double getMaxZoom() {
+ if (mMaxZoomLevel == -1) {
+ return mMaxZoomLevel = mMapView.getMaxZoom();
+ }
+ return mMaxZoomLevel;
+ }
+
+ //
// UiSettings
//
@@ -171,12 +247,10 @@ public class MapboxMap {
public final void moveCamera(CameraUpdate update, MapboxMap.CancelableCallback callback) {
mCameraPosition = update.getCameraPosition(this);
mMapView.jumpTo(mCameraPosition.bearing, mCameraPosition.target, mCameraPosition.tilt, mCameraPosition.zoom);
- if (mOnCameraChangeListener != null) {
- mOnCameraChangeListener.onCameraChange(mCameraPosition);
- }
if (callback != null) {
callback.onFinish();
}
+ invalidateCameraPosition();
}
/**
@@ -219,17 +293,15 @@ public class MapboxMap {
if (callback != null) {
callback.onCancel();
}
+ invalidateCameraPosition();
}
@Override
public void onFinish() {
- if (mOnCameraChangeListener != null) {
- mOnCameraChangeListener.onCameraChange(mCameraPosition);
- }
-
if (callback != null) {
callback.onFinish();
}
+ invalidateCameraPosition();
}
});
}
@@ -289,6 +361,7 @@ public class MapboxMap {
if (callback != null) {
callback.onCancel();
}
+ invalidateCameraPosition();
}
@Override
@@ -300,6 +373,7 @@ public class MapboxMap {
if (callback != null) {
callback.onFinish();
}
+ invalidateCameraPosition();
}
});
}
@@ -319,7 +393,12 @@ public class MapboxMap {
*/
private void invalidateCameraPosition() {
mInvalidCameraPosition = false;
- mCameraPosition = mMapView.invalidateCameraPosition();
+
+ CameraPosition cameraPosition = mMapView.invalidateCameraPosition();
+ if (cameraPosition != null) {
+ mCameraPosition = cameraPosition;
+ }
+
if (mOnCameraChangeListener != null) {
mOnCameraChangeListener.onCameraChange(mCameraPosition);
}
@@ -531,29 +610,36 @@ public class MapboxMap {
public List<Marker> addMarkers(@NonNull List<MarkerOptions> markerOptionsList) {
int count = markerOptionsList.size();
List<Marker> markers = new ArrayList<>(count);
- MarkerOptions markerOptions;
- Marker marker;
- for (int i = 0; i < count; i++) {
- markerOptions = markerOptionsList.get(i);
- marker = prepareMarker(markerOptions);
- markers.add(marker);
- }
+ if (count > 0) {
+ MarkerOptions markerOptions;
+ Marker marker;
+ for (int i = 0; i < count; i++) {
+ markerOptions = markerOptionsList.get(i);
+ marker = prepareMarker(markerOptions);
+ markers.add(marker);
+ }
- long[] ids = mMapView.addMarkers(markers);
- long id = 0;
- Marker m;
-
- for (int i = 0; i < markers.size(); i++) {
- m = markers.get(i);
- m.setMapboxMap(this);
- if (ids != null) {
- id = ids[i];
- } else {
- //unit test
- id++;
+ if (markers.size() > 0) {
+ long[] ids = mMapView.addMarkers(markers);
+
+ // if unittests or markers are correctly added to map
+ if (ids == null || ids.length == markers.size()) {
+ long id = 0;
+ Marker m;
+ for (int i = 0; i < markers.size(); i++) {
+ m = markers.get(i);
+ m.setMapboxMap(this);
+ if (ids != null) {
+ id = ids[i];
+ } else {
+ //unit test
+ id++;
+ }
+ m.setId(id);
+ mAnnotations.put(id, m);
+ }
+ }
}
- m.setId(id);
- mAnnotations.put(id, m);
}
return markers;
}
@@ -606,28 +692,35 @@ public class MapboxMap {
int count = polylineOptionsList.size();
Polyline polyline;
List<Polyline> polylines = new ArrayList<>(count);
- for (PolylineOptions options : polylineOptionsList) {
- polyline = options.getPolyline();
- if (!polyline.getPoints().isEmpty()) {
- polylines.add(polyline);
+
+ if(count>0) {
+ for (PolylineOptions options : polylineOptionsList) {
+ polyline = options.getPolyline();
+ if (!polyline.getPoints().isEmpty()) {
+ polylines.add(polyline);
+ }
}
- }
- long[] ids = mMapView.addPolylines(polylines);
- long id = 0;
- Polyline p;
-
- for (int i = 0; i < polylines.size(); i++) {
- p = polylines.get(i);
- p.setMapboxMap(this);
- if (ids != null) {
- id = ids[i];
- } else {
- // unit test
- id++;
+ long[] ids = mMapView.addPolylines(polylines);
+
+ // if unit tests or polylines are correctly added to map
+ if (ids == null || ids.length == polylines.size()) {
+ long id = 0;
+ Polyline p;
+
+ for (int i = 0; i < polylines.size(); i++) {
+ p = polylines.get(i);
+ p.setMapboxMap(this);
+ if (ids != null) {
+ id = ids[i];
+ } else {
+ // unit test
+ id++;
+ }
+ p.setId(id);
+ mAnnotations.put(id, p);
+ }
}
- p.setId(id);
- mAnnotations.put(id, p);
}
return polylines;
}
@@ -664,26 +757,32 @@ public class MapboxMap {
Polygon polygon;
List<Polygon> polygons = new ArrayList<>(count);
- for (PolygonOptions polygonOptions : polygonOptionsList) {
- polygon = polygonOptions.getPolygon();
- if (!polygon.getPoints().isEmpty()) {
- polygons.add(polygon);
+ if(count>0) {
+ for (PolygonOptions polygonOptions : polygonOptionsList) {
+ polygon = polygonOptions.getPolygon();
+ if (!polygon.getPoints().isEmpty()) {
+ polygons.add(polygon);
+ }
}
- }
- long[] ids = mMapView.addPolygons(polygons);
- long id = 0;
- for (int i = 0; i < polygons.size(); i++) {
- polygon = polygons.get(i);
- polygon.setMapboxMap(this);
- if (ids != null) {
- id = ids[i];
- } else {
- // unit test
- id++;
+ long[] ids = mMapView.addPolygons(polygons);
+
+ // if unit tests or polygons correcly added to map
+ if(ids==null || ids.length==polygons.size()) {
+ long id = 0;
+ for (int i = 0; i < polygons.size(); i++) {
+ polygon = polygons.get(i);
+ polygon.setMapboxMap(this);
+ if (ids != null) {
+ id = ids[i];
+ } else {
+ // unit test
+ id++;
+ }
+ polygon.setId(id);
+ mAnnotations.put(id, polygon);
+ }
}
- polygon.setId(id);
- mAnnotations.put(id, polygon);
}
return polygons;
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java
index 941d2f1c60..6b7a0db8bd 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java
@@ -130,14 +130,14 @@ public class MapboxMapOptions implements Parcelable {
, ((int) typedArray.getDimension(R.styleable.MapView_compass_margin_right, DIMENSION_TEN_DP * screenDensity))
, ((int) typedArray.getDimension(R.styleable.MapView_compass_margin_bottom, DIMENSION_TEN_DP * screenDensity))});
- mapboxMapOptions.logoEnabled(typedArray.getBoolean(R.styleable.MapView_logo_visibility, true));
+ mapboxMapOptions.logoEnabled(typedArray.getBoolean(R.styleable.MapView_logo_enabled, true));
mapboxMapOptions.logoGravity(typedArray.getInt(R.styleable.MapView_logo_gravity, Gravity.BOTTOM | Gravity.START));
mapboxMapOptions.logoMargins(new int[]{(int) (typedArray.getDimension(R.styleable.MapView_logo_margin_left, DIMENSION_SIXTEEN_DP) * screenDensity)
, (int) (typedArray.getDimension(R.styleable.MapView_logo_margin_top, DIMENSION_SIXTEEN_DP) * screenDensity)
, (int) (typedArray.getDimension(R.styleable.MapView_logo_margin_right, DIMENSION_SIXTEEN_DP) * screenDensity)
, (int) (typedArray.getDimension(R.styleable.MapView_logo_margin_bottom, DIMENSION_SIXTEEN_DP) * screenDensity)});
- mapboxMapOptions.attributionEnabled(typedArray.getBoolean(R.styleable.MapView_attribution_visibility, true));
+ mapboxMapOptions.attributionEnabled(typedArray.getBoolean(R.styleable.MapView_attribution_enabled, true));
mapboxMapOptions.attributionGravity(typedArray.getInt(R.styleable.MapView_attribution_gravity, Gravity.BOTTOM));
mapboxMapOptions.attributionMargins(new int[]{(int) (typedArray.getDimension(R.styleable.MapView_attribution_margin_left, DIMENSION_SEVENTY_SIX_DP) * screenDensity)
, (int) (typedArray.getDimension(R.styleable.MapView_attribution_margin_top, DIMENSION_SEVEN_DP) * screenDensity)
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java
index 63e4f1923b..6b5a02cf06 100755
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java
@@ -50,8 +50,13 @@ final class NativeMapView {
public NativeMapView(MapView mapView) {
Context context = mapView.getContext();
- String cachePath = context.getCacheDir().getAbsolutePath();
String dataPath = context.getFilesDir().getAbsolutePath();
+
+ // With the availability of offline, we're unifying the ambient (cache) and the offline
+ // databases to be in the same folder, outside cache, to avoid automatic deletion from
+ // the system
+ String cachePath = dataPath;
+
float pixelRatio = context.getResources().getDisplayMetrics().density;
String apkPath = context.getPackageCodePath();
int availableProcessors = Runtime.getRuntime().availableProcessors();
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/OnMapReadyCallback.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/OnMapReadyCallback.java
index 255ca1e5f9..7ace9ec2d3 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/OnMapReadyCallback.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/OnMapReadyCallback.java
@@ -1,12 +1,10 @@
package com.mapbox.mapboxsdk.maps;
-import android.support.annotation.NonNull;
-
/**
* Interface definition for a callback to be invoked when the map is ready to be used.
* <p>
* Once an instance of this interface is set on a {@link MapFragment} or {@link MapView} object,
- * the onMapReady(MapboxMap) method is triggered when the map is ready to be used and provides a non-null instance of {@link MapboxMap}.
+ * the onMapReady(MapboxMap) method is triggered when the map is ready to be used and provides an instance of {@link MapboxMap}.
* </p>
*/
public interface OnMapReadyCallback {
@@ -14,8 +12,8 @@ public interface OnMapReadyCallback {
/**
* Called when the map is ready to be used.
*
- * @param mapboxMap A non-null instance of a MapboxMap associated with the {@link MapFragment}
- * or {@link MapView} that defines the callback.
+ * @param mapboxMap An instance of MapboxMap associated with the {@link MapFragment} or
+ * {@link MapView} that defines the callback.
*/
- void onMapReady(@NonNull MapboxMap mapboxMap);
+ void onMapReady(MapboxMap mapboxMap);
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/SupportMapFragment.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/SupportMapFragment.java
index 9cf946758f..ffdb57de8c 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/SupportMapFragment.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/SupportMapFragment.java
@@ -76,7 +76,6 @@ public class SupportMapFragment extends Fragment {
@Override
public void onStart() {
super.onStart();
- mMap.onStart();
mMap.getMapAsync(mOnMapReadyCallback);
}
@@ -115,7 +114,6 @@ public class SupportMapFragment extends Fragment {
@Override
public void onStop() {
super.onStop();
- mMap.onStop();
}
/**
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java
index 4c2502cdb8..f87ddb4ca1 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java
@@ -26,9 +26,6 @@ public class UiSettings {
private boolean zoomControlsEnabled;
private boolean scrollGesturesEnabled;
- private double maxZoomLevel = -1;
- private double minZoomLevel = -1;
-
UiSettings(@NonNull MapView mapView) {
this.mapView = mapView;
this.compassSettings = new ViewSettings();
@@ -38,70 +35,6 @@ public class UiSettings {
/**
* <p>
- * Sets the minimum zoom level the map can be displayed at.
- * </p>
- *
- * @param minZoom The new minimum zoom level.
- */
- @UiThread
- public void setMinZoom(@FloatRange(from = MapboxConstants.MINIMUM_ZOOM, to = MapboxConstants.MAXIMUM_ZOOM) double minZoom) {
- if ((minZoom < MapboxConstants.MINIMUM_ZOOM) || (minZoom > MapboxConstants.MAXIMUM_ZOOM)) {
- Log.e(MapboxConstants.TAG, "Not setting minZoom, value is in unsupported range: " + minZoom);
- return;
- }
- minZoomLevel = minZoom;
- mapView.setMinZoom(minZoom);
- }
-
- /**
- * <p>
- * Gets the maximum zoom level the map can be displayed at.
- * </p>
- *
- * @return The minimum zoom level.
- */
- @UiThread
- public double getMinZoom() {
- if (minZoomLevel == -1) {
- return minZoomLevel = mapView.getMinZoom();
- }
- return minZoomLevel;
- }
-
- /**
- * <p>
- * Sets the maximum zoom level the map can be displayed at.
- * </p>
- *
- * @param maxZoom The new maximum zoom level.
- */
- @UiThread
- public void setMaxZoom(@FloatRange(from = MapboxConstants.MINIMUM_ZOOM, to = MapboxConstants.MAXIMUM_ZOOM) double maxZoom) {
- if ((maxZoom < MapboxConstants.MINIMUM_ZOOM) || (maxZoom > MapboxConstants.MAXIMUM_ZOOM)) {
- Log.e(MapboxConstants.TAG, "Not setting maxZoom, value is in unsupported range: " + maxZoom);
- return;
- }
- maxZoomLevel = maxZoom;
- mapView.setMaxZoom(maxZoom);
- }
-
- /**
- * <p>
- * Gets the maximum zoom level the map can be displayed at.
- * </p>
- *
- * @return The maximum zoom level.
- */
- @UiThread
- public double getMaxZoom() {
- if (maxZoomLevel == -1) {
- return maxZoomLevel = mapView.getMaxZoom();
- }
- return maxZoomLevel;
- }
-
- /**
- * <p>
* Enables or disables the compass. The compass is an icon on the map that indicates the
* direction of north on the map. When a user clicks
* the compass, the camera orients itself to its default orientation and fades away shortly
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/UserLocationView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/UserLocationView.java
index 37a88e2ff3..36e48488fa 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/UserLocationView.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/UserLocationView.java
@@ -198,17 +198,6 @@ public final class UserLocationView extends View {
mProjection = mapboxMap.getProjection();
}
- public void onStart() {
- if (mMyBearingTrackingMode == MyBearingTracking.COMPASS) {
- mBearingChangeListener.onStart(getContext());
- }
- }
-
- public void onStop() {
- mBearingChangeListener.onStop();
- cancelAnimations();
- }
-
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
@@ -395,9 +384,9 @@ public final class UserLocationView extends View {
if (myBearingTrackingMode == MyBearingTracking.COMPASS) {
mShowAccuracy = false;
mShowDirection = true;
- mBearingChangeListener.onStart(getContext());
+ mBearingChangeListener.onResume();
} else {
- mBearingChangeListener.onStop();
+ mBearingChangeListener.onPause();
if (myBearingTrackingMode == MyBearingTracking.GPS) {
mShowDirection = (mUserLocation != null) && mUserLocation.hasBearing();
} else {
@@ -433,12 +422,12 @@ public final class UserLocationView extends View {
mMagnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
}
- public void onStart(Context context) {
+ public void onResume() {
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME);
mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_GAME);
}
- public void onStop() {
+ public void onPause() {
mSensorManager.unregisterListener(this, mAccelerometer);
mSensorManager.unregisterListener(this, mMagnetometer);
}
@@ -661,11 +650,16 @@ public final class UserLocationView extends View {
public void onPause() {
mPaused = true;
+ mBearingChangeListener.onPause();
+ cancelAnimations();
toggleGps(false);
}
public void onResume() {
mPaused = false;
+ if (mMyBearingTrackingMode == MyBearingTracking.COMPASS) {
+ mBearingChangeListener.onResume();
+ }
if (isEnabled()) {
toggleGps(true);
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java
index 726cac63b2..85b3d619b8 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineManager.java
@@ -4,6 +4,7 @@ import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
+import android.util.Log;
import java.io.File;
@@ -13,8 +14,18 @@ import java.io.File;
*/
public class OfflineManager {
+ private final static String LOG_TAG = "OfflineManager";
+
+ //
+ // Static methods
+ //
+
+ static {
+ System.loadLibrary("mapbox-gl");
+ }
+
// Default database name
- private final static String OFFLINE_DATABASE_NAME = "mbgl-offline.db";
+ private final static String DATABASE_NAME = "mbgl-offline.db";
/*
* The maximumCacheSize parameter is a limit applied to non-offline resources only,
@@ -78,8 +89,30 @@ public class OfflineManager {
private OfflineManager(Context context) {
// Get a pointer to the DefaultFileSource instance
String assetRoot = context.getFilesDir().getAbsolutePath();
- String cachePath = assetRoot + File.separator + OFFLINE_DATABASE_NAME;
+ String cachePath = assetRoot + File.separator + DATABASE_NAME;
mDefaultFileSourcePtr = createDefaultFileSource(cachePath, assetRoot, DEFAULT_MAX_CACHE_SIZE);
+
+ // Delete any existing previous ambient cache database
+ deleteAmbientDatabase(context);
+ }
+
+ private void deleteAmbientDatabase(final Context context) {
+ // Delete the file in a separate thread to avoid affecting the UI
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ String path = context.getCacheDir().getAbsolutePath() + File.separator + "mbgl-cache.db";
+ File file = new File(path);
+ if (file.exists()) {
+ file.delete();
+ Log.d(LOG_TAG, "Old ambient cache database deleted to save space: " + path);
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Failed to delete old ambient cache database: " + e.getMessage());
+ }
+ }
+ }).start();
}
public static synchronized OfflineManager getInstance(Context context) {
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java
index 0f192cde4c..7f066e74d1 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java
@@ -18,6 +18,14 @@ public class OfflineRegion {
private final static String LOG_TAG = "OfflineRegion";
+ //
+ // Static methods
+ //
+
+ static {
+ System.loadLibrary("mapbox-gl");
+ }
+
// Parent OfflineManager
private OfflineManager offlineManager;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEventManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEventManager.java
index fb98903c8f..736247bbac 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEventManager.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEventManager.java
@@ -24,8 +24,10 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowManager;
import com.mapbox.mapboxsdk.BuildConfig;
+import com.mapbox.mapboxsdk.constants.GeoConstants;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.location.LocationServices;
+import com.mapbox.mapboxsdk.utils.MathUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import java.security.MessageDigest;
@@ -551,7 +553,16 @@ public class MapboxEventManager {
jsonObject.putOpt(MapboxEvent.ATTRIBUTE_SOURCE, evt.get(MapboxEvent.ATTRIBUTE_SOURCE));
jsonObject.putOpt(MapboxEvent.ATTRIBUTE_SESSION_ID, evt.get(MapboxEvent.ATTRIBUTE_SESSION_ID));
jsonObject.putOpt(MapboxEvent.KEY_LATITUDE, evt.get(MapboxEvent.KEY_LATITUDE));
- jsonObject.putOpt(MapboxEvent.KEY_LONGITUDE, evt.get(MapboxEvent.KEY_LONGITUDE));
+
+ // Make sure Longitude Is Wrapped
+ if (evt.containsKey(MapboxEvent.KEY_LONGITUDE)) {
+ double lon = (double)evt.get(MapboxEvent.KEY_LONGITUDE);
+ if ((lon < GeoConstants.MIN_LONGITUDE) || (lon > GeoConstants.MAX_LONGITUDE)) {
+ lon = MathUtils.wrap(lon, GeoConstants.MIN_LONGITUDE, GeoConstants.MAX_LONGITUDE);
+ }
+ jsonObject.put(MapboxEvent.KEY_LONGITUDE, lon);
+ }
+
jsonObject.putOpt(MapboxEvent.KEY_ALTITUDE, evt.get(MapboxEvent.KEY_ALTITUDE));
jsonObject.putOpt(MapboxEvent.KEY_ZOOM, evt.get(MapboxEvent.KEY_ZOOM));
jsonObject.putOpt(MapboxEvent.ATTRIBUTE_OPERATING_SYSTEM, evt.get(MapboxEvent.ATTRIBUTE_OPERATING_SYSTEM));
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/MathUtils.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/MathUtils.java
index ff1b56422b..922fb11868 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/MathUtils.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/MathUtils.java
@@ -24,4 +24,23 @@ public class MathUtils {
return Math.max(min, Math.min(max, value));
}
+ /**
+ * Constrains value to the given range (including min, excluding max) via modular arithmetic.
+ *
+ * Same formula as used in Core GL (math.hpp)
+ * std::fmod((std::fmod((value - min), d) + d), d) + min;
+ *
+ * @param value Value to wrap
+ * @param min Minimum value
+ * @param max Maximum value
+ * @return Wrapped value
+ */
+ public static double wrap(double value, double min, double max) {
+ double delta = max - min;
+
+ double firstMod = (value - min) % delta;
+ double secondMod = (firstMod + delta) % delta;
+
+ return secondMod + min;
+ }
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml
index f516e98dd8..a6e1f044c8 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml
@@ -59,11 +59,8 @@
<attr name="logo_margin_top" format="dimension" />
<attr name="logo_margin_right" format="dimension" />
<attr name="logo_margin_bottom" format="dimension" />
- <attr name="logo_visibility">
- <enum name="visible" value="0x0" />
- <enum name="invisible" value="0x4" />
- <enum name="gone" value="0x8" />
- </attr>
+ <attr name="logo_enabled" format="boolean" />
+
<attr name="attribution_gravity">
<flag name="top" value="0x30" />
<flag name="bottom" value="0x50" />
@@ -84,10 +81,6 @@
<attr name="attribution_margin_top" format="dimension" />
<attr name="attribution_margin_right" format="dimension" />
<attr name="attribution_margin_bottom" format="dimension" />
- <attr name="attribution_visibility">
- <enum name="visible" value="0x0" />
- <enum name="invisible" value="0x4" />
- <enum name="gone" value="0x8" />
- </attr>
+ <attr name="attribution_enabled" format="boolean" />
</declare-styleable>
</resources>
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties b/platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties
index 3539430594..8c1dcc38ce 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties
+++ b/platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties
@@ -1,3 +1,3 @@
fabric-identifier=com.mapbox.mapboxsdk.mapbox-android-sdk
-fabric-version=3.2.0
+fabric-version=4.0.0
fabric-build-type=binary
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/AnimatedMarkerActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/AnimatedMarkerActivity.java
index c7b8fd619f..cf61a220b1 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/AnimatedMarkerActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/AnimatedMarkerActivity.java
@@ -86,12 +86,6 @@ public class AnimatedMarkerActivity extends AppCompatActivity {
}
@Override
- protected void onStart() {
- super.onStart();
- mMapView.onStart();
- }
-
- @Override
public void onResume() {
super.onResume();
mMapView.onResume();
@@ -104,12 +98,6 @@ public class AnimatedMarkerActivity extends AppCompatActivity {
}
@Override
- protected void onStop() {
- super.onStop();
- mMapView.onStop();
- }
-
- @Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mMapView.onSaveInstanceState(outState);
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/BulkMarkerActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/BulkMarkerActivity.java
index 9a14b5c58a..1330f48469 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/BulkMarkerActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/BulkMarkerActivity.java
@@ -85,12 +85,6 @@ public class BulkMarkerActivity extends AppCompatActivity implements AdapterView
}
@Override
- protected void onStart() {
- super.onStart();
- mMapView.onStart();
- }
-
- @Override
public void onResume() {
super.onResume();
mMapView.onResume();
@@ -103,12 +97,6 @@ public class BulkMarkerActivity extends AppCompatActivity implements AdapterView
}
@Override
- protected void onStop() {
- super.onStop();
- mMapView.onStop();
- }
-
- @Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mMapView.onSaveInstanceState(outState);
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/CameraAnimationTypeActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/CameraAnimationTypeActivity.java
index 7185335939..3b220a046f 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/CameraAnimationTypeActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/CameraAnimationTypeActivity.java
@@ -140,12 +140,6 @@ public class CameraAnimationTypeActivity extends AppCompatActivity {
}
@Override
- protected void onStart() {
- super.onStart();
- mapView.onStart();
- }
-
- @Override
public void onResume() {
super.onResume();
mapView.onResume();
@@ -158,12 +152,6 @@ public class CameraAnimationTypeActivity extends AppCompatActivity {
}
@Override
- protected void onStop() {
- super.onStop();
- mapView.onStop();
- }
-
- @Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/CameraPositionActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/CameraPositionActivity.java
index 77fda5261f..dea87079e2 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/CameraPositionActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/CameraPositionActivity.java
@@ -96,18 +96,6 @@ public class CameraPositionActivity extends AppCompatActivity implements OnMapRe
}
@Override
- protected void onStart() {
- super.onStart();
- mapView.onStart();
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- mapView.onStop();
- }
-
- @Override
public void onPause() {
super.onPause();
mapView.onPause();
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/DirectionsActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/DirectionsActivity.java
index b126370720..4895b8fcef 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/DirectionsActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/DirectionsActivity.java
@@ -142,12 +142,6 @@ public class DirectionsActivity extends AppCompatActivity {
}
@Override
- protected void onStart() {
- super.onStart();
- mMapView.onStart();
- }
-
- @Override
public void onResume() {
super.onResume();
mMapView.onResume();
@@ -160,12 +154,6 @@ public class DirectionsActivity extends AppCompatActivity {
}
@Override
- protected void onStop() {
- super.onStop();
- mMapView.onStop();
- }
-
- @Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mMapView.onSaveInstanceState(outState);
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/DynamicMarkerChangeActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/DynamicMarkerChangeActivity.java
index ceade78fc1..2d485fc966 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/DynamicMarkerChangeActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/DynamicMarkerChangeActivity.java
@@ -88,13 +88,6 @@ public class DynamicMarkerChangeActivity extends AppCompatActivity {
mMarker.setIcon(mIconFactory.fromResource(first ? R.drawable.ic_chelsea : R.drawable.ic_arsenal));
}
-
- @Override
- protected void onStart() {
- super.onStart();
- mMapView.onStart();
- }
-
@Override
public void onResume() {
super.onResume();
@@ -108,12 +101,6 @@ public class DynamicMarkerChangeActivity extends AppCompatActivity {
}
@Override
- protected void onStop() {
- super.onStop();
- mMapView.onStop();
- }
-
- @Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mMapView.onSaveInstanceState(outState);
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/GeocoderActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/GeocoderActivity.java
index 4c8bd3b700..29fbc53380 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/GeocoderActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/GeocoderActivity.java
@@ -98,18 +98,6 @@ public class GeocoderActivity extends AppCompatActivity {
}
@Override
- protected void onStart() {
- super.onStart();
- mapView.onStart();
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- mapView.onStop();
- }
-
- @Override
public void onPause() {
super.onPause();
mapView.onPause();
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/InfoWindowActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/InfoWindowActivity.java
index af0bdee6e7..5e1b368266 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/InfoWindowActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/InfoWindowActivity.java
@@ -117,12 +117,6 @@ public class InfoWindowActivity extends AppCompatActivity implements OnMapReadyC
}
@Override
- protected void onStart() {
- super.onStart();
- mapView.onStart();
- }
-
- @Override
public void onResume() {
super.onResume();
mapView.onResume();
@@ -135,12 +129,6 @@ public class InfoWindowActivity extends AppCompatActivity implements OnMapReadyC
}
@Override
- protected void onStop() {
- super.onStop();
- mapView.onStop();
- }
-
- @Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/InfoWindowAdapterActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/InfoWindowAdapterActivity.java
index 26aab52284..54deb1a073 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/InfoWindowAdapterActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/InfoWindowAdapterActivity.java
@@ -104,12 +104,6 @@ public class InfoWindowAdapterActivity extends AppCompatActivity {
}
@Override
- protected void onStart() {
- super.onStart();
- mapView.onStart();
- }
-
- @Override
public void onResume() {
super.onResume();
mapView.onResume();
@@ -122,12 +116,6 @@ public class InfoWindowAdapterActivity extends AppCompatActivity {
}
@Override
- protected void onStop() {
- super.onStop();
- mapView.onStop();
- }
-
- @Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/LatLngBoundsActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/LatLngBoundsActivity.java
index f321da28aa..2888f2170a 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/LatLngBoundsActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/LatLngBoundsActivity.java
@@ -84,12 +84,6 @@ public class LatLngBoundsActivity extends AppCompatActivity {
}
@Override
- protected void onStart() {
- super.onStart();
- mMapView.onStart();
- }
-
- @Override
public void onResume() {
super.onResume();
mMapView.onResume();
@@ -102,12 +96,6 @@ public class LatLngBoundsActivity extends AppCompatActivity {
}
@Override
- protected void onStop() {
- super.onStop();
- mMapView.onStop();
- }
-
- @Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mMapView.onSaveInstanceState(outState);
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/ManualZoomActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/ManualZoomActivity.java
index 02459b00d1..cce6d0585e 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/ManualZoomActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/ManualZoomActivity.java
@@ -93,12 +93,6 @@ public class ManualZoomActivity extends AppCompatActivity {
}
@Override
- protected void onStart() {
- super.onStart();
- mMapView.onStart();
- }
-
- @Override
public void onResume() {
super.onResume();
mMapView.onResume();
@@ -111,12 +105,6 @@ public class ManualZoomActivity extends AppCompatActivity {
}
@Override
- protected void onStop() {
- super.onStop();
- mMapView.onStop();
- }
-
- @Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mMapView.onSaveInstanceState(outState);
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/MapFragmentActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/MapFragmentActivity.java
index 29ef545c10..fbc69bfd5d 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/MapFragmentActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/MapFragmentActivity.java
@@ -49,9 +49,9 @@ public class MapFragmentActivity extends AppCompatActivity {
options.rotateGesturesEnabled(false);
options.debugActive(false);
- options.compassEnabled(false);
- options.attributionEnabled(false);
- options.logoEnabled(false);
+// options.compassEnabled(false);
+// options.attributionEnabled(false);
+// options.logoEnabled(false);
options.minZoom(9);
options.maxZoom(11);
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/MapPaddingActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/MapPaddingActivity.java
index f5c2011e5d..91b2793bd4 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/MapPaddingActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/MapPaddingActivity.java
@@ -60,12 +60,6 @@ public class MapPaddingActivity extends AppCompatActivity {
}
@Override
- protected void onStart() {
- super.onStart();
- mMapView.onStart();
- }
-
- @Override
public void onResume() {
super.onResume();
mMapView.onResume();
@@ -78,12 +72,6 @@ public class MapPaddingActivity extends AppCompatActivity {
}
@Override
- protected void onStop() {
- super.onStop();
- mMapView.onStop();
- }
-
- @Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mMapView.onSaveInstanceState(outState);
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/MaxMinZoomActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/MaxMinZoomActivity.java
index 6adbfe3002..28fad94ef5 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/MaxMinZoomActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/MaxMinZoomActivity.java
@@ -36,9 +36,8 @@ public class MaxMinZoomActivity extends AppCompatActivity {
mMapView.getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(@NonNull final MapboxMap mapboxMap) {
- UiSettings uiSettings = mapboxMap.getUiSettings();
- uiSettings.setMinZoom(3);
- uiSettings.setMaxZoom(5);
+ mapboxMap.setMinZoom(3);
+ mapboxMap.setMaxZoom(5);
}
});
}
@@ -54,12 +53,6 @@ public class MaxMinZoomActivity extends AppCompatActivity {
}
@Override
- protected void onStart() {
- super.onStart();
- mMapView.onStart();
- }
-
- @Override
public void onResume() {
super.onResume();
mMapView.onResume();
@@ -72,12 +65,6 @@ public class MaxMinZoomActivity extends AppCompatActivity {
}
@Override
- protected void onStop() {
- super.onStop();
- mMapView.onStop();
- }
-
- @Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mMapView.onSaveInstanceState(outState);
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/MyLocationTrackingModeActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/MyLocationTrackingModeActivity.java
index e15ffda61e..3b848d3483 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/MyLocationTrackingModeActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/MyLocationTrackingModeActivity.java
@@ -171,12 +171,6 @@ public class MyLocationTrackingModeActivity extends AppCompatActivity implements
}
@Override
- protected void onStart() {
- super.onStart();
- mMapView.onStart();
- }
-
- @Override
public void onResume() {
super.onResume();
mMapView.onResume();
@@ -195,12 +189,6 @@ public class MyLocationTrackingModeActivity extends AppCompatActivity implements
}
@Override
- protected void onStop() {
- super.onStop();
- mMapView.onStop();
- }
-
- @Override
protected void onDestroy() {
super.onDestroy();
mMapView.onDestroy();
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/OfflineActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/OfflineActivity.java
index d12536a35b..3d16902532 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/OfflineActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/OfflineActivity.java
@@ -123,12 +123,6 @@ public class OfflineActivity extends AppCompatActivity
}
@Override
- protected void onStart() {
- super.onStart();
- mMapView.onStart();
- }
-
- @Override
public void onResume() {
super.onResume();
mMapView.onResume();
@@ -141,12 +135,6 @@ public class OfflineActivity extends AppCompatActivity
}
@Override
- protected void onStop() {
- super.onStop();
- mMapView.onStop();
- }
-
- @Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mMapView.onSaveInstanceState(outState);
@@ -258,7 +246,7 @@ public class OfflineActivity extends AppCompatActivity
String styleURL = mMapboxMap.getStyleUrl();
LatLngBounds bounds = mMapboxMap.getProjection().getVisibleRegion().latLngBounds;
double minZoom = mMapboxMap.getCameraPosition().zoom;
- double maxZoom = mMapboxMap.getUiSettings().getMaxZoom();
+ double maxZoom = mMapboxMap.getMaxZoom();
float pixelRatio = this.getResources().getDisplayMetrics().density;
OfflineTilePyramidRegionDefinition definition = new OfflineTilePyramidRegionDefinition(
styleURL, bounds, minZoom, maxZoom, pixelRatio);
@@ -347,6 +335,7 @@ public class OfflineActivity extends AppCompatActivity
mProgressBar.setIndeterminate(true);
mProgressBar.setVisibility(View.VISIBLE);
}
+
private void setPercentage(final int percentage) {
mProgressBar.setIndeterminate(false);
mProgressBar.setProgress(percentage);
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/PolylineActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/PolylineActivity.java
index 10d6771dd7..2212299a61 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/PolylineActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/PolylineActivity.java
@@ -118,12 +118,6 @@ public class PolylineActivity extends AppCompatActivity {
}
@Override
- protected void onStart() {
- super.onStart();
- mMapView.onStart();
- }
-
- @Override
public void onResume() {
super.onResume();
mMapView.onResume();
@@ -136,12 +130,6 @@ public class PolylineActivity extends AppCompatActivity {
}
@Override
- protected void onStop() {
- super.onStop();
- mMapView.onStop();
- }
-
- @Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mMapView.onSaveInstanceState(outState);
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/ScrollByActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/ScrollByActivity.java
index 40a84e5f30..8eba00bb79 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/ScrollByActivity.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/ScrollByActivity.java
@@ -77,12 +77,6 @@ public class ScrollByActivity extends AppCompatActivity {
}
@Override
- protected void onStart() {
- super.onStart();
- mMapView.onStart();
- }
-
- @Override
public void onResume() {
super.onResume();
mMapView.onResume();
@@ -95,12 +89,6 @@ public class ScrollByActivity extends AppCompatActivity {
}
@Override
- protected void onStop() {
- super.onStop();
- mMapView.onStop();
- }
-
- @Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mMapView.onSaveInstanceState(outState);
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/annotations/MarkerTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/annotations/MarkerTest.java
index 76e29c208b..cc38ca40b8 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/annotations/MarkerTest.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/annotations/MarkerTest.java
@@ -113,7 +113,7 @@ public class MarkerTest {
@Test
public void testToString() {
Marker marker = new MarkerOptions().position(new LatLng(0, 0)).getMarker();
- assertEquals(marker.toString(), "Marker [position[" + "LatLng [longitude=0.0, latitude=0.0, altitude=0.0]" + "]]");
+ assertEquals(marker.toString(), "Marker [position[" + "LatLng [latitude=0.0, longitude=0.0, altitude=0.0]" + "]]");
}
}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/camera/CameraPositionTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/camera/CameraPositionTest.java
index e2109adf47..222007df79 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/camera/CameraPositionTest.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/camera/CameraPositionTest.java
@@ -23,7 +23,7 @@ public class CameraPositionTest {
public void testToString() {
LatLng latLng = new LatLng(1, 2);
CameraPosition cameraPosition = new CameraPosition(latLng, 3, 4, 5);
- assertEquals("toString should match", "Target: LatLng [longitude=2.0, latitude=1.0, altitude=0.0], Zoom:3.0, Bearing:5.0, Tilt:4.0", cameraPosition.toString());
+ assertEquals("toString should match", "Target: LatLng [latitude=1.0, longitude=2.0, altitude=0.0], Zoom:3.0, Bearing:5.0, Tilt:4.0", cameraPosition.toString());
}
@Test
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java
index e3870b63e2..f9cdf29b81 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java
@@ -2,7 +2,6 @@ package com.mapbox.mapboxsdk.geometry;
import com.mapbox.mapboxsdk.exceptions.InvalidLatLngBoundsException;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -100,12 +99,12 @@ public class LatLngBoundsTest {
@Test
public void testIncluding() {
- assertTrue("LatLng should be included", mLatLngBounds.including(new LatLng(1, 1)));
+ assertTrue("LatLng should be included", mLatLngBounds.contains(new LatLng(1, 1)));
}
@Test
public void testNoIncluding() {
- assertFalse("LatLng should not be included", mLatLngBounds.including(new LatLng(3, 1)));
+ assertFalse("LatLng should not be included", mLatLngBounds.contains(new LatLng(3, 1)));
}
@Test
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngTest.java
index ecce08b17e..e13efb9708 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngTest.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngTest.java
@@ -2,19 +2,17 @@ package com.mapbox.mapboxsdk.geometry;
import android.location.Location;
import android.os.Parcel;
-
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.utils.MockParcel;
-
import org.junit.Test;
-
import java.util.Objects;
-
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -151,7 +149,7 @@ public class LatLngTest {
LatLng latLng = new LatLng(latitude, longitude, altitude);
assertEquals("string should match",
latLng.toString(),
- "LatLng [longitude=3.4, latitude=1.2, altitude=5.6]");
+ "LatLng [latitude=1.2, longitude=3.4, altitude=5.6]");
}
@Test
@@ -202,4 +200,19 @@ public class LatLngTest {
assertEquals("contents should be 0", 0, latLng.describeContents(), DELTA);
}
+ @Test
+ public void testWrapped() {
+ LatLng latLng = new LatLng(45.0, -185.0);
+ LatLng wrapped = latLng.wrap();
+ assertNotSame(latLng, wrapped);
+ assertEquals("longitude wrapped value", wrapped.getLongitude(), 175.0, DELTA);
+ }
+
+ @Test
+ public void testUnnecessaryWrapped() {
+ LatLng latLng = new LatLng(45.0, 50.0);
+ LatLng wrapped = latLng.wrap();
+ assertNotSame(latLng, wrapped);
+ assertEquals("longitude wrapped value", wrapped.getLongitude(), 50.0, DELTA);
+ }
}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/geometry/VisibleRegionTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/geometry/VisibleRegionTest.java
index 83cfa37841..0682d0878e 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/geometry/VisibleRegionTest.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/geometry/VisibleRegionTest.java
@@ -73,10 +73,10 @@ public class VisibleRegionTest {
public void testToString() {
VisibleRegion region = new VisibleRegion(FAR_LEFT, FAR_RIGHT, NEAR_LEFT, NEAR_RIGHT, BOUNDS);
assertEquals("string should match",
- "[farLeft [LatLng [longitude=-12.0, latitude=52.0, altitude=0.0]], " +
- "farRight [LatLng [longitude=26.0, latitude=52.0, altitude=0.0]], " +
- "nearLeft [LatLng [longitude=-12.0, latitude=34.0, altitude=0.0]], " +
- "nearRight [LatLng [longitude=26.0, latitude=34.0, altitude=0.0]], " +
+ "[farLeft [LatLng [latitude=52.0, longitude=-12.0, altitude=0.0]], " +
+ "farRight [LatLng [latitude=52.0, longitude=26.0, altitude=0.0]], " +
+ "nearLeft [LatLng [latitude=34.0, longitude=-12.0, altitude=0.0]], " +
+ "nearRight [LatLng [latitude=34.0, longitude=26.0, altitude=0.0]], " +
"latLngBounds [N:52.0; E:26.0; S:34.0; W:-12.0]]"
, region.toString());
}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.java
index 1bf49823c8..1705f44fa4 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.java
@@ -10,9 +10,7 @@ import com.mapbox.mapboxsdk.annotations.PolygonOptions;
import com.mapbox.mapboxsdk.annotations.Polyline;
import com.mapbox.mapboxsdk.annotations.PolylineOptions;
import com.mapbox.mapboxsdk.camera.CameraPosition;
-
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
-import com.mapbox.mapboxsdk.constants.Style;
import com.mapbox.mapboxsdk.geometry.LatLng;
import org.junit.Before;
@@ -26,11 +24,9 @@ import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.matches;
import static org.mockito.Mockito.mock;
-
-import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -109,6 +105,30 @@ public class MapboxMapTest {
}
//
+ // MinZoomLevel
+ //
+
+ @Test
+ public void testMinZoom() {
+ double zoom = 10;
+ mMapboxMap.setMinZoom(zoom);
+ assertEquals("MinZoom should match", zoom, mMapboxMap.getMinZoom(), 0);
+ }
+
+ @Test
+ public void testMaxZoom() {
+ double zoom = 10;
+ mMapboxMap.setMaxZoom(zoom);
+ assertEquals("MaxZoom should match", zoom, mMapboxMap.getMaxZoom(), 0);
+ }
+
+ @Test
+ public void testInitialZoomLevels() {
+ assertEquals("MaxZoom should match", 0, mMapboxMap.getMaxZoom(), 0);
+ assertEquals("MinZoom should match", 0, mMapboxMap.getMinZoom(), 0);
+ }
+
+ //
// TrackingSettings
//
@@ -221,15 +241,15 @@ public class MapboxMapTest {
}
@Test
- public void testOnBearingTrackingModeChangeListener(){
+ public void testOnBearingTrackingModeChangeListener() {
mMapboxMap.setOnMyBearingTrackingModeChangeListener(mMyBearingTrackingModeChangeListener);
- assertEquals("MyBearingTrackingChangeListerner should match",mMyBearingTrackingModeChangeListener, mMapboxMap.getOnMyBearingTrackingModeChangeListener());
+ assertEquals("MyBearingTrackingChangeListerner should match", mMyBearingTrackingModeChangeListener, mMapboxMap.getOnMyBearingTrackingModeChangeListener());
}
@Test
- public void testOnLocationTrackingModeChangeListener(){
+ public void testOnLocationTrackingModeChangeListener() {
mMapboxMap.setOnMyLocationTrackingModeChangeListener(mMyLocationTrackingModeChangeListener);
- assertEquals("MyLocationTrackigChangeListener should match",mMyLocationTrackingModeChangeListener, mMapboxMap.getOnMyLocationTrackingModeChangeListener());
+ assertEquals("MyLocationTrackigChangeListener should match", mMyLocationTrackingModeChangeListener, mMapboxMap.getOnMyLocationTrackingModeChangeListener());
}
//
@@ -515,6 +535,23 @@ public class MapboxMapTest {
}
@Test
+ public void testAddMarkersEmpty() {
+ List<MarkerOptions> markerList = new ArrayList<>();
+ mMapboxMap.addMarkers(markerList);
+ assertEquals("Markers size should be 0", 0, mMapboxMap.getMarkers().size());
+ }
+
+ @Test
+ public void testAddMarkersSingleMarker() {
+ List<MarkerOptions> markerList = new ArrayList<>();
+ MarkerOptions markerOptions = new MarkerOptions().title("a");
+ markerList.add(markerOptions);
+ mMapboxMap.addMarkers(markerList);
+ assertEquals("Markers size should be 1", 1, mMapboxMap.getMarkers().size());
+ assertTrue(mMapboxMap.getMarkers().contains(markerOptions.getMarker()));
+ }
+
+ @Test
public void testAddPolygon() {
PolygonOptions polygonOptions = new PolygonOptions().add(new LatLng());
Polygon polygon = mMapboxMap.addPolygon(polygonOptions);
@@ -545,6 +582,22 @@ public class MapboxMapTest {
}
@Test
+ public void addPolygonsEmpty() {
+ mMapboxMap.addPolygons(new ArrayList<PolygonOptions>());
+ assertEquals("Polygons size should be 0", 0, mMapboxMap.getPolygons().size());
+ }
+
+ @Test
+ public void addPolygonsSingle() {
+ List<PolygonOptions> polygonList = new ArrayList<>();
+ PolygonOptions polygonOptions = new PolygonOptions().fillColor(Color.BLACK).add(new LatLng());
+ polygonList.add(polygonOptions);
+ mMapboxMap.addPolygons(polygonList);
+ assertEquals("Polygons size should be 1", 1, mMapboxMap.getPolygons().size());
+ assertTrue(mMapboxMap.getPolygons().contains(polygonOptions.getPolygon()));
+ }
+
+ @Test
public void testAddPolyline() {
PolylineOptions polylineOptions = new PolylineOptions().add(new LatLng());
Polyline polyline = mMapboxMap.addPolyline(polylineOptions);
@@ -575,6 +628,22 @@ public class MapboxMapTest {
}
@Test
+ public void testAddPolylinesEmpty() {
+ mMapboxMap.addPolylines(new ArrayList<PolylineOptions>());
+ assertEquals("Polygons size should be 0", 0, mMapboxMap.getPolylines().size());
+ }
+
+ @Test
+ public void testAddPolylinesSingle() {
+ List<PolylineOptions> polylineList = new ArrayList<>();
+ PolylineOptions polygonOptions = new PolylineOptions().color(Color.BLACK).add(new LatLng());
+ polylineList.add(polygonOptions);
+ mMapboxMap.addPolylines(polylineList);
+ assertEquals("Polygons size should be 1", 1, mMapboxMap.getPolylines().size());
+ assertTrue(mMapboxMap.getPolylines().contains(polygonOptions.getPolyline()));
+ }
+
+ @Test
public void testRemoveMarker() {
MarkerOptions markerOptions = new MarkerOptions();
Marker marker = mMapboxMap.addMarker(markerOptions);
@@ -709,7 +778,6 @@ public class MapboxMapTest {
assertTrue("Selected markers should be empty", mMapboxMap.getSelectedMarkers().isEmpty());
}
-
//
// OnMarkerClick interface
//
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/maps/UiSettingsTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/maps/UiSettingsTest.java
index cb9031c66e..4af44a3f49 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/maps/UiSettingsTest.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/test/java/com/mapbox/mapboxsdk/maps/UiSettingsTest.java
@@ -22,30 +22,6 @@ public class UiSettingsTest {
}
@Test
- public void testMinZoom() {
- double zoom = 10;
- UiSettings uiSettings = new UiSettings(mMapView);
- uiSettings.setMinZoom(zoom);
- assertEquals("MinZoom should match", zoom, uiSettings.getMinZoom(), 0);
- }
-
- @Test
- public void testMaxZoom() {
- double zoom = 10;
- UiSettings uiSettings = new UiSettings(mMapView);
- uiSettings.setMaxZoom(zoom);
- assertEquals("MaxZoom should match", zoom, uiSettings.getMaxZoom(), 0);
- }
-
- @Test
- public void testInitialZoomLevels() {
- //we are mocking MapView we expect a value of 0 to be returned
- UiSettings uiSettings = new UiSettings(mMapView);
- assertEquals("MaxZoom should match", 0, uiSettings.getMaxZoom(), 0);
- assertEquals("MinZoom should match", 0, uiSettings.getMinZoom(), 0);
- }
-
- @Test
public void testCompassEnabled() {
UiSettings uiSettings = new UiSettings(mMapView);
uiSettings.setCompassEnabled(true);
diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp
index f8318c7006..a645d86d4b 100755
--- a/platform/android/src/native_map_view.cpp
+++ b/platform/android/src/native_map_view.cpp
@@ -76,7 +76,7 @@ NativeMapView::NativeMapView(JNIEnv *env, jobject obj_, float pixelRatio_, int a
}
fileSource = std::make_unique<mbgl::DefaultFileSource>(
- mbgl::android::cachePath + "/mbgl-cache.db",
+ mbgl::android::cachePath + "/mbgl-offline.db",
mbgl::android::apkPath);
map = std::make_unique<mbgl::Map>(*this, *fileSource, MapMode::Continuous);
diff --git a/platform/darwin/include/MGLOfflinePack.h b/platform/darwin/include/MGLOfflinePack.h
index d1f5a3ed53..783650590a 100644
--- a/platform/darwin/include/MGLOfflinePack.h
+++ b/platform/darwin/include/MGLOfflinePack.h
@@ -4,8 +4,6 @@
NS_ASSUME_NONNULL_BEGIN
-@protocol MGLOfflinePackDelegate;
-
/**
The state an offline pack is currently in.
*/
@@ -13,11 +11,11 @@ typedef NS_ENUM (NSInteger, MGLOfflinePackState) {
/**
It is unknown whether the pack is inactive, active, or complete.
- This is the initial state of a pack that is obtained using the
- `-[MGLOfflineStorage getPacksWithCompletionHandler:]` method. The state
- becomes known by the time the pack’s delegate receives its first progress
- update. For inactive packs, you must explicitly request a progress update
- using the `-[MGLOfflinePack requestProgress]` method.
+ This is the initial state of a pack. The state of a pack becomes known by
+ the time the shared `MGLOfflineStorage` object sends the first
+ `MGLOfflinePackProgressChangedNotification` about the pack. For inactive
+ packs, you must explicitly request a progress update using the
+ `-[MGLOfflinePack requestProgress]` method.
An invalid pack always has a state of `MGLOfflinePackStateInvalid`, never
`MGLOfflinePackStateUnknown`.
@@ -86,9 +84,11 @@ typedef struct MGLOfflinePackProgress {
/**
An `MGLOfflinePack` represents a collection of resources necessary for viewing
- a region offline to a local database. It provides an optional
- `MGLOfflinePackDelegate` object with progress updates as data or errors arrive
- from the server.
+ a region offline to a local database.
+
+ To create an instance of `MGLOfflinePack`, use the
+ `+[MGLOfflineStorage addPackForRegion:withContext:completionHandler:]` method.
+ A pack created using `-[MGLOfflinePack init]` is immediately invalid.
*/
@interface MGLOfflinePack : NSObject
@@ -109,9 +109,12 @@ typedef struct MGLOfflinePackProgress {
The pack’s current state.
The state of an inactive or completed pack is computed lazily and is set to
- `MGLOfflinePackStateUnknown` by default. If you need the state of a pack
- inside an `MGLOfflinePackListingCompletionHandler`, set the `delegate` property
- then call the `-requestProgress` method.
+ `MGLOfflinePackStateUnknown` by default. To request the pack’s status, use the
+ `-requestProgress` method. To get notified when the state becomes known and
+ when it changes, observe KVO change notifications on this pack’s `state` key
+ path. Alternatively, you can add an observer for
+ `MGLOfflinePackProgressChangedNotification`s about this pack that come from the
+ default notification center.
*/
@property (nonatomic, readonly) MGLOfflinePackState state;
@@ -119,31 +122,43 @@ typedef struct MGLOfflinePackProgress {
The pack’s current progress.
The progress of an inactive or completed pack is computed lazily, and all its
- fields are set to 0 by default. If you need the progress of a pack inside an
- `MGLOfflinePackListingCompletionHandler`, set the `delegate` property then call
- the `-requestProgress` method.
+ fields are set to 0 by default. To request the pack’s progress, use the
+ `-requestProgress` method. To get notified when the progress becomes
+ known and when it changes, observe KVO change notifications on this pack’s
+ `state` key path. Alternatively, you can add an observer for
+ `MGLOfflinePackProgressChangedNotification`s about this pack that come from the
+ default notification center.
*/
@property (nonatomic, readonly) MGLOfflinePackProgress progress;
/**
- The pack’s delegate.
-
- You can use the offline pack delegate to be notified of any changes in the
- pack’s progress and of any errors while downloading. For more information, see
- the `MGLOfflinePackDelegate` documentation.
- */
-@property (nonatomic, weak, nullable) id <MGLOfflinePackDelegate> delegate;
-
-- (instancetype)init NS_UNAVAILABLE;
-
-/**
Resumes downloading if the pack is inactive.
+
+ A pack resumes asynchronously. To get notified when this pack resumes, observe
+ KVO change notifications on this pack’s `state` key path. Alternatively, you
+ can add an observer for `MGLOfflinePackProgressChangedNotification`s about this
+ pack that come from the default notification center.
+
+ When a pack resumes after being suspended, it may begin by iterating over the
+ already downloaded resources. As a result, the `progress` structure’s
+ `countOfResourcesCompleted` field may revert to 0 before rapidly returning to
+ the level of progress at the time the pack was suspended.
+
+ To temporarily suspend downloading, call the `-suspend` method.
*/
- (void)resume;
/**
Temporarily stops downloading if the pack is active.
+ A pack suspends asynchronously. To get notified when this pack resumes, observe
+ KVO change notifications on this pack’s `state` key path. Alternatively, you
+ can add an observer for `MGLOfflinePackProgressChangedNotification` about this
+ pack that come from the default notification center.
+
+ If the pack previously reached a higher level of progress before being
+ suspended, it may wait to suspend until it returns to that level.
+
To resume downloading, call the `-resume` method.
*/
- (void)suspend;
@@ -152,56 +167,34 @@ typedef struct MGLOfflinePackProgress {
Request an asynchronous update to the pack’s `state` and `progress` properties.
The state and progress of an inactive or completed pack are computed lazily. If
- you need the state or progress of a pack inside an
- `MGLOfflinePackListingCompletionHandler`, set the `delegate` property then call
- this method.
+ you need the state or progress of a pack whose `state` property is currently
+ set to `MGLOfflinePackStateUnknown`, observe KVO change notifications on this
+ pack’s `state` key path, then call this method. Alternatively, you can add an
+ observer for `MGLOfflinePackProgressChangedNotification` about this pack that
+ come from the default notification center.
*/
- (void)requestProgress;
@end
/**
- The `MGLOfflinePackDelegate` protocol defines methods that a delegate of an
- `MGLOfflinePack` object can optionally implement to be notified of any changes
- in the pack’s download progress and of any errors while downloading.
+ Methods for round-tripping `MGLOfflinePackProgress` values.
*/
-@protocol MGLOfflinePackDelegate <NSObject>
-
-@optional
+@interface NSValue (MGLOfflinePackAdditions)
/**
- Sent whenever the pack’s state or download progress changes. Every change to a
- field in the `progress` property corresponds to an invocation of this method.
+ Creates a new value object containing the given `MGLOfflinePackProgress`
+ structure.
- @param pack The pack whose state of progress changed.
- @param progress The updated progress. To get the updated state, refer to the
- `state` property.
+ @param progress The value for the new object.
+ @return A new value object that contains the offline pack progress information.
*/
-- (void)offlinePack:(MGLOfflinePack *)pack progressDidChange:(MGLOfflinePackProgress)progress;
++ (NSValue *)valueWithMGLOfflinePackProgress:(MGLOfflinePackProgress)progress;
/**
- Sent whenever the pack encounters an error while downloading.
-
- Download errors may be recoverable. For example, this pack’s implementation may
- attempt to re-request failed resources based on an exponential backoff
- strategy or upon the restoration of network access.
-
- @param pack The pack that encountered an error.
- @param error A download error. For a list of possible error codes, see
- `MGLErrorCode`.
- */
-- (void)offlinePack:(MGLOfflinePack *)pack didReceiveError:(NSError *)error;
-
-/**
- Sent when the maximum number of Mapbox-hosted tiles has been downloaded and
- stored on the current device.
-
- Once this limit is reached, no instance of `MGLOfflinePack` can download
- additional tiles from Mapbox APIs until already downloaded tiles are removed by
- calling the `-[MGLOfflineStorage removePack:withCompletionHandler:]` method.
- Contact your Mapbox sales representative to have the limit raised.
+ The `MGLOfflinePackProgress` structure representation of the value.
*/
-- (void)offlinePack:(MGLOfflinePack *)pack didReceiveMaximumAllowedMapboxTiles:(uint64_t)maximumCount;
+@property (readonly) MGLOfflinePackProgress MGLOfflinePackProgressValue;
@end
diff --git a/platform/darwin/include/MGLOfflineStorage.h b/platform/darwin/include/MGLOfflineStorage.h
index 4f3432163b..5544663d3e 100644
--- a/platform/darwin/include/MGLOfflineStorage.h
+++ b/platform/darwin/include/MGLOfflineStorage.h
@@ -8,14 +8,98 @@ NS_ASSUME_NONNULL_BEGIN
@protocol MGLOfflineRegion;
/**
+ Posted by the shared `MGLOfflineStorage` object when an `MGLOfflinePack`
+ object’s progress changes. The progress may change due to a resource being
+ downloaded or because the pack discovers during the download that more
+ resources are required for offline viewing. This notification is posted
+ whenever any field in the `progress` property changes.
+
+ The `object` is the `MGLOfflinePack` object whose progress changed. The
+ `userInfo` dictionary contains the pack’s current state in the
+ `MGLOfflinePackStateUserInfoKey` key and details about the pack’s current
+ progress in the `MGLOfflinePackProgressUserInfoKey` key. You may also consult
+ the pack’s `state` and `progress` properties, which provide the same values.
+
+ If you only need to observe changes in a particular pack’s progress, you can
+ alternatively observe KVO change notifications to the pack’s `progress` key
+ path.
+ */
+extern NSString * const MGLOfflinePackProgressChangedNotification;
+
+/**
+ Posted by the shared `MGLOfflineStorage` object whenever an `MGLOfflinePack`
+ object encounters an error while downloading. The error may be recoverable and
+ may not warrant the user’s attention. For example, the pack’s implementation
+ may attempt to re-request failed resources based on an exponential backoff
+ strategy or upon the restoration of network access.
+
+ The `object` is the `MGLOfflinePack` object that encountered the error. The
+ `userInfo` dictionary contains the error object in the
+ `MGLOfflinePackErrorUserInfoKey` key.
+ */
+extern NSString * const MGLOfflinePackErrorNotification;
+
+/**
+ Posted by the shared `MGLOfflineStorage` object when the maximum number of
+ Mapbox-hosted tiles has been downloaded and stored on the current device.
+
+ The `object` is the `MGLOfflinePack` object that reached the tile limit in the
+ course of downloading. The `userInfo` dictionary contains the tile limit in the
+ `MGLOfflinePackMaximumCountUserInfoKey` key.
+
+ Once this limit is reached, no instance of `MGLOfflinePack` can download
+ additional tiles from Mapbox APIs until already downloaded tiles are removed by
+ calling the `-[MGLOfflineStorage removePack:withCompletionHandler:]` method.
+ Contact your Mapbox sales representative to have the limit raised.
+ */
+extern NSString * const MGLOfflinePackMaximumMapboxTilesReachedNotification;
+
+/**
+ The key for an `NSNumber` object that indicates an offline pack’s current
+ state. This key is used in the `userInfo` dictionary of an
+ `MGLOfflinePackProgressChangedNotification` notification. Call `-integerValue`
+ on the object to receive the `MGLOfflinePackState`-typed state.
+ */
+extern NSString * const MGLOfflinePackStateUserInfoKey;
+
+/**
+ The key for an `NSValue` object that indicates an offline pack’s current
+ progress. This key is used in the `userInfo` dictionary of an
+ `MGLOfflinePackProgressChangedNotification` notification. Call
+ `-MGLOfflinePackProgressValue` on the object to receive the
+ `MGLOfflinePackProgress`-typed progress.
+ */
+extern NSString * const MGLOfflinePackProgressUserInfoKey;
+
+/**
+ The key for an `NSError` object that is encountered in the course of
+ downloading an offline pack. This key is used in the `userInfo` dictionary of
+ an `MGLOfflinePackErrorNotification` notification. The error’s domain is
+ `MGLErrorDomain`. See `MGLErrorCode` for possible error codes.
+ */
+extern NSString * const MGLOfflinePackErrorUserInfoKey;
+
+/**
+ The key for an `NSNumber` object that indicates the maximum number of
+ Mapbox-hosted tiles that may be downloaded and stored on the current device.
+ This key is used in the `userInfo` dictionary of an
+ `MGLOfflinePackMaximumMapboxTilesReachedNotification` notification. Call
+ `-unsignedLongLongValue` on the object to receive the `uint64_t`-typed tile
+ limit.
+ */
+extern NSString * const MGLOfflinePackMaximumCountUserInfoKey;
+
+/**
A block to be called once an offline pack has been completely created and
added.
+ An application typically calls the `-resume` method on the pack inside this
+ completion handler to begin the download.
+
@param pack Contains a pointer to the newly added pack, or `nil` if there was
an error creating or adding the pack.
@param error Contains a pointer to an error object (if any) indicating why the
- pack could not be created or added. For a list of possible error codes, see
- `MGLErrorCode`.
+ pack could not be created or added.
*/
typedef void (^MGLOfflinePackAdditionCompletionHandler)(MGLOfflinePack * _Nullable pack, NSError * _Nullable error);
@@ -23,25 +107,20 @@ typedef void (^MGLOfflinePackAdditionCompletionHandler)(MGLOfflinePack * _Nullab
A block to be called once an offline pack has been completely invalidated and
removed.
+ Avoid any references to the pack inside this completion handler: by the time
+ this completion handler is executed, the pack has become invalid, and any
+ messages passed to it will raise an exception.
+
@param error Contains a pointer to an error object (if any) indicating why the
pack could not be invalidated or removed.
*/
typedef void (^MGLOfflinePackRemovalCompletionHandler)(NSError * _Nullable error);
/**
- A block to be called with a complete list of offline packs.
-
- @param pack Contains a pointer an array of packs, or `nil` if there was an
- error obtaining the packs.
- @param error Contains a pointer to an error object (if any) indicating why the
- list of packs could not be obtained.
- */
-typedef void (^MGLOfflinePackListingCompletionHandler)(NS_ARRAY_OF(MGLOfflinePack *) *packs, NSError * _Nullable error);
-
-/**
MGLOfflineStorage implements a singleton (shared object) that manages offline
packs. All of this class’s instance methods are asynchronous, reflecting the
- fact that offline resources are stored in a database.
+ fact that offline resources are stored in a database. The shared object
+ maintains a canonical collection of offline packs in its `packs` property.
*/
@interface MGLOfflineStorage : NSObject
@@ -50,16 +129,36 @@ typedef void (^MGLOfflinePackListingCompletionHandler)(NS_ARRAY_OF(MGLOfflinePac
*/
+ (instancetype)sharedOfflineStorage;
-- (instancetype)init NS_UNAVAILABLE;
+/**
+ An array of all known offline packs, in the order in which they were created.
+
+ This property is set to `nil`, indicating that the receiver does not yet know
+ the existing packs, for an undefined amount of time starting from the moment
+ the shared offline storage object is initialized until the packs are fetched
+ from the database. After that point, this property is always non-nil, but it
+ may be empty to indicate that no packs are present.
+
+ To detect when the shared offline storage object has finished loading its
+ `packs` property, observe KVO change notifications on the `packs` key path.
+ The initial load results in an `NSKeyValueChangeSetting` change.
+ */
+@property (nonatomic, strong, readonly, nullable) NS_ARRAY_OF(MGLOfflinePack *) *packs;
/**
Creates and registers an offline pack that downloads the resources needed to
use the given region offline.
- The resulting pack starts out with a state of `MGLOfflinePackStateInactive`. To
- begin downloading resources, call `-[MGLOfflinePack resume]`. To monitor
- download progress, set the pack’s `delegate` property to an object that
- conforms to the `MGLOfflinePackDelegate` protocol.
+ The resulting pack is added to the shared offline storage object’s `packs`
+ property, then the `completion` block is executed with that pack passed in.
+
+ The pack has an initial state of `MGLOfflinePackStateInactive`. To begin
+ downloading resources, call `-[MGLOfflinePack resume]` on the pack from within
+ the completion handler. To monitor download progress, add an observer for
+ `MGLOfflinePackProgressChangedNotification`s about that pack.
+
+ To detect when any call to this method results in a new pack, observe KVO
+ change notifications on the shared offline storage object’s `packs` key path.
+ Additions to that array result in an `NSKeyValueChangeInsertion` change.
@param region A region to download.
@param context Arbitrary data to store alongside the downloaded resources.
@@ -75,8 +174,12 @@ typedef void (^MGLOfflinePackListingCompletionHandler)(NS_ARRAY_OF(MGLOfflinePac
As soon as this method is called on a pack, the pack becomes invalid; any
attempt to send it a message will result in an exception being thrown. If an
error occurs and the pack cannot be removed, do not attempt to reuse the pack
- object. Instead, use the `-getPacksWithCompletionHandler:` method to obtain a
- valid pointer to the pack object.
+ object. Instead, if you need continued access to the pack, suspend all packs
+ and use the `-reloadPacks` method to obtain valid pointers to all the packs.
+
+ To detect when any call to this method results in a pack being removed, observe
+ KVO change notifications on the shared offline storage object’s `packs` key
+ path. Removals from that array result in an `NSKeyValueChangeRemoval` change.
@param pack The offline pack to remove.
@param completion The completion handler to call once the pack has been
@@ -85,21 +188,28 @@ typedef void (^MGLOfflinePackListingCompletionHandler)(NS_ARRAY_OF(MGLOfflinePac
- (void)removePack:(MGLOfflinePack *)pack withCompletionHandler:(nullable MGLOfflinePackRemovalCompletionHandler)completion;
/**
- Asynchronously calls a completion callback with all existing offline packs.
+ Forcibly, asynchronously reloads the `packs` property. At some point after this
+ method is called, the pointer values of the `MGLOfflinePack` objects in the
+ `packs` property change, even if the underlying data for these packs has not
+ changed. If this method is called while a pack is actively downloading, the
+ behavior is undefined.
+
+ You typically do not need to call this method.
- @param completion The completion handler to call with the list of packs. This
- handler is executed asynchronously on the main queue.
+ To detect when the shared offline storage object has finished reloading its
+ `packs` property, observe KVO change notifications on the `packs` key path.
+ A reload results in an `NSKeyValueChangeSetting` change.
*/
-- (void)getPacksWithCompletionHandler:(MGLOfflinePackListingCompletionHandler)completion;
+- (void)reloadPacks;
/**
Sets the maximum number of Mapbox-hosted tiles that may be downloaded and
stored on the current device.
- Once this limit is reached,
- `-[MGLOfflinePackDelegate offlinePack:didReceiveMaximumAllowedMapboxTiles:]` is
- called on every delegate of `MGLOfflinePack` until already downloaded tiles are
- removed by calling the `-removePack:withCompletionHandler:` method.
+ Once this limit is reached, an
+ `MGLOfflinePackMaximumMapboxTilesReachedNotification` is posted for every
+ attempt to download additional tiles until already downloaded tiles are removed
+ by calling the `-removePack:withCompletionHandler:` method.
@note The [Mapbox Terms of Service](https://www.mapbox.com/tos/) prohibits
changing or bypassing this limit without permission from Mapbox. Contact
diff --git a/platform/darwin/include/MGLTilePyramidOfflineRegion.h b/platform/darwin/include/MGLTilePyramidOfflineRegion.h
index 89920adcc5..4e9f394e74 100644
--- a/platform/darwin/include/MGLTilePyramidOfflineRegion.h
+++ b/platform/darwin/include/MGLTilePyramidOfflineRegion.h
@@ -9,7 +9,7 @@ NS_ASSUME_NONNULL_BEGIN
An offline region defined by a style URL, geographic coordinate bounds, and
range of zoom levels.
*/
-@interface MGLTilePyramidOfflineRegion : NSObject <MGLOfflineRegion>
+@interface MGLTilePyramidOfflineRegion : NSObject <MGLOfflineRegion, NSSecureCoding, NSCopying>
/**
URL of the style whose resources are required for offline viewing.
@@ -17,9 +17,8 @@ NS_ASSUME_NONNULL_BEGIN
In addition to the JSON stylesheet, different styles may require different font
glyphs, sprite sheets, and other resources.
- The URL may be a full HTTP or HTTPS URL, a Mapbox URL indicating the style’s
- map ID (`mapbox://styles/{user}/{style}`), or a path to a local file
- relative to the application’s resource path.
+ The URL may be a full HTTP or HTTPS URL or a Mapbox URL indicating the style’s
+ map ID (`mapbox://styles/{user}/{style}`).
*/
@property (nonatomic, readonly) NSURL *styleURL;
@@ -52,9 +51,11 @@ NS_ASSUME_NONNULL_BEGIN
This is the designated initializer for `MGLTilePyramidOfflineRegion`.
@param styleURL URL of the map style for which to download resources. The URL
- may be a full HTTP or HTTPS URL, a Mapbox URL indicating the style’s map ID
- (`mapbox://styles/{user}/{style}`), or a path to a local file relative to
- the application’s resource path. Specify `nil` for the default style.
+ may be a full HTTP or HTTPS URL or a Mapbox URL indicating the style’s map
+ ID (`mapbox://styles/{user}/{style}`). Specify `nil` for the default style.
+ Relative file URLs cannot be used as offline style URLs. To download the
+ online resources required by a local style, specify a URL to an online copy
+ of the style.
@param bounds The coordinate bounds for the geographic region to be covered by
the downloaded tiles.
@param minimumZoomLevel The minimum zoom level to be covered by the downloaded
diff --git a/platform/darwin/include/MGLTypes.h b/platform/darwin/include/MGLTypes.h
index 213b90670e..ccb496816a 100644
--- a/platform/darwin/include/MGLTypes.h
+++ b/platform/darwin/include/MGLTypes.h
@@ -8,6 +8,8 @@
#define nullable
#define nonnull
#define null_resettable
+ #define _Nullable
+ #define _Nonnull
#endif
NS_ASSUME_NONNULL_BEGIN
diff --git a/platform/darwin/src/MGLOfflinePack.mm b/platform/darwin/src/MGLOfflinePack.mm
index 42bd871e54..9775f1cfca 100644
--- a/platform/darwin/src/MGLOfflinePack.mm
+++ b/platform/darwin/src/MGLOfflinePack.mm
@@ -38,8 +38,8 @@ private:
@interface MGLOfflinePack ()
+@property (nonatomic, weak, nullable) id <MGLOfflinePackDelegate> delegate;
@property (nonatomic, nullable, readwrite) mbgl::OfflineRegion *mbglOfflineRegion;
-@property (nonatomic, readwrite) MGLOfflinePackState state;
@property (nonatomic, readwrite) MGLOfflinePackProgress progress;
@end
@@ -47,11 +47,11 @@ private:
@implementation MGLOfflinePack
- (instancetype)init {
- [NSException raise:@"Method unavailable"
- format:
- @"-[MGLOfflinePack init] is unavailable. "
- @"Use +[MGLOfflineStorage addPackForRegion:withContext:completionHandler:] instead."];
- return nil;
+ if (self = [super init]) {
+ _state = MGLOfflinePackStateInvalid;
+ NSLog(@"%s called; did you mean to call +[MGLOfflineStorage addPackForRegion:withContext:completionHandler:] instead?", __PRETTY_FUNCTION__);
+ }
+ return self;
}
- (instancetype)initWithMBGLRegion:(mbgl::OfflineRegion *)region {
@@ -66,10 +66,7 @@ private:
}
- (void)dealloc {
- if (_mbglOfflineRegion && _state != MGLOfflinePackStateInvalid) {
- mbgl::DefaultFileSource *mbglFileSource = [[MGLOfflineStorage sharedOfflineStorage] mbglFileSource];
- mbglFileSource->setOfflineRegionObserver(*_mbglOfflineRegion, nullptr);
- }
+ NSAssert(_state == MGLOfflinePackStateInvalid, @"MGLOfflinePack was not invalided prior to deallocation.");
}
- (id <MGLOfflineRegion>)region {
@@ -105,6 +102,8 @@ private:
NSAssert(_state != MGLOfflinePackStateInvalid, @"Cannot invalidate an already invalid offline pack.");
self.state = MGLOfflinePackStateInvalid;
+ mbgl::DefaultFileSource *mbglFileSource = [[MGLOfflineStorage sharedOfflineStorage] mbglFileSource];
+ mbglFileSource->setOfflineRegionObserver(*self.mbglOfflineRegion, nullptr);
self.mbglOfflineRegion = nil;
}
@@ -159,9 +158,7 @@ private:
progress.maximumResourcesExpected = status.requiredResourceCountIsPrecise ? status.requiredResourceCount : UINT64_MAX;
self.progress = progress;
- if ([self.delegate respondsToSelector:@selector(offlinePack:progressDidChange:)]) {
- [self.delegate offlinePack:self progressDidChange:progress];
- }
+ [self.delegate offlinePack:self progressDidChange:progress];
}
NSError *MGLErrorFromResponseError(mbgl::Response::Error error) {
@@ -187,6 +184,8 @@ NSError *MGLErrorFromResponseError(mbgl::Response::Error error) {
}];
}
+@end
+
void MBGLOfflineRegionObserver::statusChanged(mbgl::OfflineRegionStatus status) {
dispatch_async(dispatch_get_main_queue(), ^{
[pack offlineRegionStatusDidChange:status];
@@ -195,18 +194,26 @@ void MBGLOfflineRegionObserver::statusChanged(mbgl::OfflineRegionStatus status)
void MBGLOfflineRegionObserver::responseError(mbgl::Response::Error error) {
dispatch_async(dispatch_get_main_queue(), ^{
- if ([pack.delegate respondsToSelector:@selector(offlinePack:didReceiveError:)]) {
- [pack.delegate offlinePack:pack didReceiveError:MGLErrorFromResponseError(error)];
- }
+ [pack.delegate offlinePack:pack didReceiveError:MGLErrorFromResponseError(error)];
});
}
void MBGLOfflineRegionObserver::mapboxTileCountLimitExceeded(uint64_t limit) {
dispatch_async(dispatch_get_main_queue(), ^{
- if ([pack.delegate respondsToSelector:@selector(offlinePack:didReceiveMaximumAllowedMapboxTiles:)]) {
- [pack.delegate offlinePack:pack didReceiveMaximumAllowedMapboxTiles:limit];
- }
+ [pack.delegate offlinePack:pack didReceiveMaximumAllowedMapboxTiles:limit];
});
}
+@implementation NSValue (MGLOfflinePackAdditions)
+
++ (NSValue *)valueWithMGLOfflinePackProgress:(MGLOfflinePackProgress)progress {
+ return [NSValue value:&progress withObjCType:@encode(MGLOfflinePackProgress)];
+}
+
+- (MGLOfflinePackProgress)MGLOfflinePackProgressValue {
+ MGLOfflinePackProgress progress;
+ [self getValue:&progress];
+ return progress;
+}
+
@end
diff --git a/platform/darwin/src/MGLOfflinePack_Private.h b/platform/darwin/src/MGLOfflinePack_Private.h
index e29018fc36..95d8ba4323 100644
--- a/platform/darwin/src/MGLOfflinePack_Private.h
+++ b/platform/darwin/src/MGLOfflinePack_Private.h
@@ -4,18 +4,74 @@
NS_ASSUME_NONNULL_BEGIN
+@protocol MGLOfflinePackDelegate;
+
@interface MGLOfflinePack (Private)
+/**
+ The pack’s delegate.
+
+ You can use the offline pack delegate to be notified of any changes in the
+ pack’s progress and of any errors while downloading. For more information, see
+ the `MGLOfflinePackDelegate` documentation.
+ */
+@property (nonatomic, weak, nullable) id <MGLOfflinePackDelegate> delegate;
+
@property (nonatomic, nullable) mbgl::OfflineRegion *mbglOfflineRegion;
+@property (nonatomic, readwrite) MGLOfflinePackState state;
+
- (instancetype)initWithMBGLRegion:(mbgl::OfflineRegion *)region;
/**
Invalidates the pack and ensures that no future progress update can ever
- revalidate it.
+ revalidate it. This method must be called before the pack is deallocated.
*/
- (void)invalidate;
@end
+/**
+ The `MGLOfflinePackDelegate` protocol defines methods that a delegate of an
+ `MGLOfflinePack` object can optionally implement to be notified of any changes
+ in the pack’s download progress and of any errors while downloading.
+ */
+@protocol MGLOfflinePackDelegate <NSObject>
+
+/**
+ Sent whenever the pack’s state or download progress changes. Every change to a
+ field in the `progress` property corresponds to an invocation of this method.
+
+ @param pack The pack whose state of progress changed.
+ @param progress The updated progress. To get the updated state, refer to the
+ `state` property.
+ */
+- (void)offlinePack:(MGLOfflinePack *)pack progressDidChange:(MGLOfflinePackProgress)progress;
+
+/**
+ Sent whenever the pack encounters an error while downloading.
+
+ Download errors may be recoverable. For example, this pack’s implementation may
+ attempt to re-request failed resources based on an exponential backoff
+ strategy or upon the restoration of network access.
+
+ @param pack The pack that encountered an error.
+ @param error A download error. For a list of possible error codes, see
+ `MGLErrorCode`.
+ */
+- (void)offlinePack:(MGLOfflinePack *)pack didReceiveError:(NSError *)error;
+
+/**
+ Sent when the maximum number of Mapbox-hosted tiles has been downloaded and
+ stored on the current device.
+
+ Once this limit is reached, no instance of `MGLOfflinePack` can download
+ additional tiles from Mapbox APIs until already downloaded tiles are removed by
+ calling the `-[MGLOfflineStorage removePack:withCompletionHandler:]` method.
+ Contact your Mapbox sales representative to have the limit raised.
+ */
+- (void)offlinePack:(MGLOfflinePack *)pack didReceiveMaximumAllowedMapboxTiles:(uint64_t)maximumCount;
+
+@end
+
NS_ASSUME_NONNULL_END
diff --git a/platform/darwin/src/MGLOfflineRegion_Private.h b/platform/darwin/src/MGLOfflineRegion_Private.h
index 41cd5843f6..22106987d0 100644
--- a/platform/darwin/src/MGLOfflineRegion_Private.h
+++ b/platform/darwin/src/MGLOfflineRegion_Private.h
@@ -8,8 +8,19 @@ NS_ASSUME_NONNULL_BEGIN
@protocol MGLOfflineRegion_Private <MGLOfflineRegion>
+/**
+ Initializes and returns an offline region backed by the given C++ region
+ definition object.
+
+ @param definition A reference to an offline region definition backing the
+ offline region.
+ */
- (instancetype)initWithOfflineRegionDefinition:(const mbgl::OfflineRegionDefinition &)definition;
+/**
+ Creates and returns a C++ offline region definition corresponding to the
+ receiver.
+ */
- (const mbgl::OfflineRegionDefinition)offlineRegionDefinition;
@end
diff --git a/platform/darwin/src/MGLOfflineStorage.mm b/platform/darwin/src/MGLOfflineStorage.mm
index 3eac28ca78..89bf050249 100644
--- a/platform/darwin/src/MGLOfflineStorage.mm
+++ b/platform/darwin/src/MGLOfflineStorage.mm
@@ -8,11 +8,22 @@
#include <mbgl/util/string.hpp>
-@interface MGLOfflineStorage ()
+static NSString * const MGLOfflineStorageFileName = @"cache.db";
+static NSString * const MGLOfflineStorageFileName3_2_0_beta_1 = @"offline.db";
-@property (nonatomic) mbgl::DefaultFileSource *mbglFileSource;
+NSString * const MGLOfflinePackProgressChangedNotification = @"MGLOfflinePackProgressChanged";
+NSString * const MGLOfflinePackErrorNotification = @"MGLOfflinePackError";
+NSString * const MGLOfflinePackMaximumMapboxTilesReachedNotification = @"MGLOfflinePackMaximumMapboxTilesReached";
+
+NSString * const MGLOfflinePackStateUserInfoKey = @"State";
+NSString * const MGLOfflinePackProgressUserInfoKey = @"Progress";
+NSString * const MGLOfflinePackErrorUserInfoKey = @"Error";
+NSString * const MGLOfflinePackMaximumCountUserInfoKey = @"MaximumCount";
-- (instancetype)initWithFileName:(NSString *)fileName NS_DESIGNATED_INITIALIZER;
+@interface MGLOfflineStorage () <MGLOfflinePackDelegate>
+
+@property (nonatomic, strong, readwrite) NS_MUTABLE_ARRAY_OF(MGLOfflinePack *) *packs;
+@property (nonatomic) mbgl::DefaultFileSource *mbglFileSource;
@end
@@ -22,31 +33,63 @@
static dispatch_once_t onceToken;
static MGLOfflineStorage *sharedOfflineStorage;
dispatch_once(&onceToken, ^{
- sharedOfflineStorage = [[self alloc] initWithFileName:@"offline.db"];
+ sharedOfflineStorage = [[self alloc] init];
+ [sharedOfflineStorage reloadPacks];
});
return sharedOfflineStorage;
}
-- (instancetype)initWithFileName:(NSString *)fileName {
+- (instancetype)init {
if (self = [super init]) {
-#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
- NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
- NSString *cachePath = [paths.firstObject stringByAppendingPathComponent:fileName];
-#elif TARGET_OS_MAC
- NSURL *cacheDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory
+ // Place the cache in a location specific to the application, so that
+ // packs downloaded by other applications don’t count toward this
+ // application’s limits.
+ // ~/Library/Application Support/tld.app.bundle.id/cache.db
+ NSURL *cacheDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:YES
error:nil];
- cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:
- [NSBundle mainBundle].bundleIdentifier];
+ NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
+ if (!bundleIdentifier) {
+ // There’s no main bundle identifier when running in a unit test bundle.
+ bundleIdentifier = [NSBundle bundleForClass:[self class]].bundleIdentifier;
+ }
+ cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:bundleIdentifier];
[[NSFileManager defaultManager] createDirectoryAtURL:cacheDirectoryURL
withIntermediateDirectories:YES
attributes:nil
error:nil];
- NSURL *cacheURL = [cacheDirectoryURL URLByAppendingPathComponent:fileName];
+ NSURL *cacheURL = [cacheDirectoryURL URLByAppendingPathComponent:MGLOfflineStorageFileName];
NSString *cachePath = cacheURL ? cacheURL.path : @"";
+
+ // Avoid backing up the offline cache onto iCloud, because it can be
+ // redownloaded. Ideally, we’d even put the ambient cache in Caches, so
+ // it can be reclaimed by the system when disk space runs low. But
+ // unfortunately it has to live in the same file as offline resources.
+ [cacheURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:NULL];
+
+ // Move the offline cache from v3.2.0-beta.1 to a location that can also
+ // be used for ambient caching.
+#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
+ // ~/Documents/offline.db
+ NSArray *legacyPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
+ NSString *legacyCachePath = [legacyPaths.firstObject stringByAppendingPathComponent:MGLOfflineStorageFileName3_2_0_beta_1];
+#elif TARGET_OS_MAC
+ // ~/Library/Caches/tld.app.bundle.id/offline.db
+ NSURL *legacyCacheDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory
+ inDomain:NSUserDomainMask
+ appropriateForURL:nil
+ create:NO
+ error:nil];
+ legacyCacheDirectoryURL = [legacyCacheDirectoryURL URLByAppendingPathComponent:bundleIdentifier];
+ NSURL *legacyCacheURL = [legacyCacheDirectoryURL URLByAppendingPathComponent:MGLOfflineStorageFileName3_2_0_beta_1];
+ NSString *legacyCachePath = legacyCacheURL ? legacyCacheURL.path : @"";
#endif
+ if (![[NSFileManager defaultManager] fileExistsAtPath:cachePath]) {
+ [[NSFileManager defaultManager] moveItemAtPath:legacyCachePath toPath:cachePath error:NULL];
+ }
+
_mbglFileSource = new mbgl::DefaultFileSource(cachePath.UTF8String, [NSBundle mainBundle].resourceURL.path.UTF8String);
// Observe for changes to the global access token (and find out the current one).
@@ -62,21 +105,42 @@
- (void)dealloc {
[[MGLAccountManager sharedManager] removeObserver:self forKeyPath:@"accessToken"];
+ for (MGLOfflinePack *pack in self.packs) {
+ [pack invalidate];
+ }
+
delete _mbglFileSource;
_mbglFileSource = nullptr;
}
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NS_DICTIONARY_OF(NSString *, id) *)change context:(__unused void *)context {
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NS_DICTIONARY_OF(NSString *, id) *)change context:(void *)context {
// Synchronize the file source’s access token with the global one in MGLAccountManager.
if ([keyPath isEqualToString:@"accessToken"] && object == [MGLAccountManager sharedManager]) {
NSString *accessToken = change[NSKeyValueChangeNewKey];
if (![accessToken isKindOfClass:[NSNull class]]) {
self.mbglFileSource->setAccessToken(accessToken.UTF8String);
}
+ } else {
+ [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
+#pragma mark Pack management methods
+
- (void)addPackForRegion:(id <MGLOfflineRegion>)region withContext:(NSData *)context completionHandler:(MGLOfflinePackAdditionCompletionHandler)completion {
+ __weak MGLOfflineStorage *weakSelf = self;
+ [self _addPackForRegion:region withContext:context completionHandler:^(MGLOfflinePack * _Nullable pack, NSError * _Nullable error) {
+ pack.state = MGLOfflinePackStateInactive;
+ MGLOfflineStorage *strongSelf = weakSelf;
+ [[strongSelf mutableArrayValueForKey:@"packs"] addObject:pack];
+ pack.delegate = strongSelf;
+ if (completion) {
+ completion(pack, error);
+ }
+ }];
+}
+
+- (void)_addPackForRegion:(id <MGLOfflineRegion>)region withContext:(NSData *)context completionHandler:(MGLOfflinePackAdditionCompletionHandler)completion {
if (![region conformsToProtocol:@protocol(MGLOfflineRegion_Private)]) {
[NSException raise:@"Unsupported region type" format:
@"Regions of type %@ are unsupported.", NSStringFromClass([region class])];
@@ -104,8 +168,22 @@
}
- (void)removePack:(MGLOfflinePack *)pack withCompletionHandler:(MGLOfflinePackRemovalCompletionHandler)completion {
+ [[self mutableArrayValueForKey:@"packs"] removeObject:pack];
+ [self _removePack:pack withCompletionHandler:^(NSError * _Nullable error) {
+ if (completion) {
+ completion(error);
+ }
+ }];
+}
+
+- (void)_removePack:(MGLOfflinePack *)pack withCompletionHandler:(MGLOfflinePackRemovalCompletionHandler)completion {
mbgl::OfflineRegion *mbglOfflineRegion = pack.mbglOfflineRegion;
[pack invalidate];
+ if (!mbglOfflineRegion) {
+ completion(nil);
+ return;
+ }
+
self.mbglFileSource->deleteOfflineRegion(std::move(*mbglOfflineRegion), [&, completion](std::exception_ptr exception) {
NSError *error;
if (exception) {
@@ -121,7 +199,20 @@
});
}
-- (void)getPacksWithCompletionHandler:(MGLOfflinePackListingCompletionHandler)completion {
+- (void)reloadPacks {
+ [self getPacksWithCompletionHandler:^(NS_ARRAY_OF(MGLOfflinePack *) *packs, __unused NSError * _Nullable error) {
+ for (MGLOfflinePack *pack in self.packs) {
+ [pack invalidate];
+ }
+ self.packs = [packs mutableCopy];
+
+ for (MGLOfflinePack *pack in packs) {
+ pack.delegate = self;
+ }
+ }];
+}
+
+- (void)getPacksWithCompletionHandler:(void (^)(NS_ARRAY_OF(MGLOfflinePack *) *packs, NSError * _Nullable error))completion {
self.mbglFileSource->listOfflineRegions([&, completion](std::exception_ptr exception, mbgl::optional<std::vector<mbgl::OfflineRegion>> regions) {
NSError *error;
if (exception) {
@@ -149,4 +240,25 @@
_mbglFileSource->setOfflineMapboxTileCountLimit(maximumCount);
}
+#pragma mark MGLOfflinePackDelegate methods
+
+- (void)offlinePack:(MGLOfflinePack *)pack progressDidChange:(__unused MGLOfflinePackProgress)progress {
+ [[NSNotificationCenter defaultCenter] postNotificationName:MGLOfflinePackProgressChangedNotification object:pack userInfo:@{
+ MGLOfflinePackStateUserInfoKey: @(pack.state),
+ MGLOfflinePackProgressUserInfoKey: [NSValue valueWithMGLOfflinePackProgress:progress],
+ }];
+}
+
+- (void)offlinePack:(MGLOfflinePack *)pack didReceiveError:(NSError *)error {
+ [[NSNotificationCenter defaultCenter] postNotificationName:MGLOfflinePackErrorNotification object:pack userInfo:@{
+ MGLOfflinePackErrorUserInfoKey: error,
+ }];
+}
+
+- (void)offlinePack:(MGLOfflinePack *)pack didReceiveMaximumAllowedMapboxTiles:(uint64_t)maximumCount {
+ [[NSNotificationCenter defaultCenter] postNotificationName:MGLOfflinePackMaximumMapboxTilesReachedNotification object:pack userInfo:@{
+ MGLOfflinePackMaximumCountUserInfoKey: @(maximumCount),
+ }];
+}
+
@end
diff --git a/platform/darwin/src/MGLOfflineStorage_Private.h b/platform/darwin/src/MGLOfflineStorage_Private.h
index 05c3188bdb..7c7b80dc46 100644
--- a/platform/darwin/src/MGLOfflineStorage_Private.h
+++ b/platform/darwin/src/MGLOfflineStorage_Private.h
@@ -8,6 +8,9 @@ NS_ASSUME_NONNULL_BEGIN
@interface MGLOfflineStorage (Private)
+/**
+ The shared file source object owned by the shared offline storage object.
+ */
@property (nonatomic) mbgl::DefaultFileSource *mbglFileSource;
@end
diff --git a/platform/darwin/src/MGLTilePyramidOfflineRegion.mm b/platform/darwin/src/MGLTilePyramidOfflineRegion.mm
index 6507ceef77..d1151de094 100644
--- a/platform/darwin/src/MGLTilePyramidOfflineRegion.mm
+++ b/platform/darwin/src/MGLTilePyramidOfflineRegion.mm
@@ -18,6 +18,10 @@
@synthesize styleURL = _styleURL;
++ (BOOL)supportsSecureCoding {
+ return YES;
+}
+
- (instancetype)init {
[NSException raise:@"Method unavailable"
format:
@@ -67,4 +71,54 @@
scaleFactor);
}
+- (nullable instancetype)initWithCoder:(NSCoder *)coder {
+ NSURL *styleURL = [coder decodeObjectForKey:@"styleURL"];
+ CLLocationCoordinate2D sw = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"southWestLatitude"],
+ [coder decodeDoubleForKey:@"southWestLongitude"]);
+ CLLocationCoordinate2D ne = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"northEastLatitude"],
+ [coder decodeDoubleForKey:@"northEastLongitude"]);
+ MGLCoordinateBounds bounds = MGLCoordinateBoundsMake(sw, ne);
+ double minimumZoomLevel = [coder decodeDoubleForKey:@"minimumZoomLevel"];
+ double maximumZoomLevel = [coder decodeDoubleForKey:@"maximumZoomLevel"];
+
+ return [self initWithStyleURL:styleURL bounds:bounds fromZoomLevel:minimumZoomLevel toZoomLevel:maximumZoomLevel];
+}
+
+- (void)encodeWithCoder:(NSCoder *)coder
+{
+ [coder encodeObject:_styleURL forKey:@"styleURL"];
+ [coder encodeDouble:_bounds.sw.latitude forKey:@"southWestLatitude"];
+ [coder encodeDouble:_bounds.sw.longitude forKey:@"southWestLongitude"];
+ [coder encodeDouble:_bounds.ne.latitude forKey:@"northEastLatitude"];
+ [coder encodeDouble:_bounds.ne.longitude forKey:@"northEastLongitude"];
+ [coder encodeDouble:_maximumZoomLevel forKey:@"maximumZoomLevel"];
+ [coder encodeDouble:_minimumZoomLevel forKey:@"minimumZoomLevel"];
+}
+
+- (id)copyWithZone:(nullable NSZone *)zone {
+ return [[[self class] allocWithZone:zone] initWithStyleURL:_styleURL bounds:_bounds fromZoomLevel:_minimumZoomLevel toZoomLevel:_maximumZoomLevel];
+}
+
+- (BOOL)isEqual:(id)other {
+ if (other == self) {
+ return YES;
+ }
+ if (![other isKindOfClass:[self class]]) {
+ return NO;
+ }
+
+ MGLTilePyramidOfflineRegion *otherRegion = other;
+ return (_minimumZoomLevel == otherRegion->_minimumZoomLevel
+ && _maximumZoomLevel == otherRegion->_maximumZoomLevel
+ && MGLCoordinateBoundsEqualToCoordinateBounds(_bounds, otherRegion->_bounds)
+ && [_styleURL isEqual:otherRegion->_styleURL]);
+}
+
+- (NSUInteger)hash {
+ return (_styleURL.hash
+ + @(_bounds.sw.latitude).hash + @(_bounds.sw.longitude).hash
+ + @(_bounds.ne.latitude).hash + @(_bounds.ne.longitude).hash
+ + @(_minimumZoomLevel).hash + @(_maximumZoomLevel).hash);
+}
+
@end
diff --git a/platform/default/default_file_source.cpp b/platform/default/default_file_source.cpp
index d79d7877d1..11944e40c7 100644
--- a/platform/default/default_file_source.cpp
+++ b/platform/default/default_file_source.cpp
@@ -88,6 +88,7 @@ public:
void deleteRegion(OfflineRegion&& region, std::function<void (std::exception_ptr)> callback) {
try {
+ downloads.erase(region.getID());
offlineDatabase.deleteRegion(std::move(region));
callback({});
} catch (...) {
diff --git a/platform/default/mbgl/storage/offline_database.cpp b/platform/default/mbgl/storage/offline_database.cpp
index a42591d60e..0b8dec01bf 100644
--- a/platform/default/mbgl/storage/offline_database.cpp
+++ b/platform/default/mbgl/storage/offline_database.cpp
@@ -14,9 +14,6 @@ namespace mbgl {
using namespace mapbox::sqlite;
-// If you change the schema you must write a migration from the previous version.
-static const uint32_t schemaVersion = 2;
-
OfflineDatabase::Statement::~Statement() {
stmt.reset();
stmt.clearBindings();
@@ -50,34 +47,53 @@ void OfflineDatabase::ensureSchema() {
try {
connect(ReadWrite);
- {
- auto userVersionStmt = db->prepare("PRAGMA user_version");
- userVersionStmt.run();
- switch (userVersionStmt.get<int>(0)) {
- case 0: break; // cache-only database; ok to delete
- case 1: break; // cache-only database; ok to delete
- case 2: return;
- default: throw std::runtime_error("unknown schema version");
- }
+ switch (userVersion()) {
+ case 0: break; // cache-only database; ok to delete
+ case 1: break; // cache-only database; ok to delete
+ case 2: migrateToVersion3(); // fall through
+ case 3: return;
+ default: throw std::runtime_error("unknown schema version");
}
removeExisting();
connect(ReadWrite | Create);
} catch (mapbox::sqlite::Exception& ex) {
- if (ex.code == SQLITE_CANTOPEN) {
- connect(ReadWrite | Create);
- } else if (ex.code == SQLITE_NOTADB) {
- removeExisting();
+ if (ex.code != SQLITE_CANTOPEN && ex.code != SQLITE_NOTADB) {
+ Log::Error(Event::Database, "Unexpected error connecting to database: %s", ex.what());
+ throw;
+ }
+
+ try {
+ if (ex.code == SQLITE_NOTADB) {
+ removeExisting();
+ }
connect(ReadWrite | Create);
+ } catch (...) {
+ Log::Error(Event::Database, "Unexpected error creating database: %s", util::toString(std::current_exception()).c_str());
+ throw;
}
}
}
- #include "offline_schema.cpp.include"
+ try {
+ #include "offline_schema.cpp.include"
+
+ connect(ReadWrite | Create);
- connect(ReadWrite | Create);
- db->exec(schema);
- db->exec("PRAGMA user_version = " + util::toString(schemaVersion));
+ // If you change the schema you must write a migration from the previous version.
+ db->exec("PRAGMA auto_vacuum = INCREMENTAL");
+ db->exec(schema);
+ db->exec("PRAGMA user_version = 3");
+ } catch (...) {
+ Log::Error(Event::Database, "Unexpected error creating database schema: %s", util::toString(std::current_exception()).c_str());
+ throw;
+ }
+}
+
+int OfflineDatabase::userVersion() {
+ auto stmt = db->prepare("PRAGMA user_version");
+ stmt.run();
+ return stmt.get<int>(0);
}
void OfflineDatabase::removeExisting() {
@@ -92,6 +108,12 @@ void OfflineDatabase::removeExisting() {
}
}
+void OfflineDatabase::migrateToVersion3() {
+ db->exec("PRAGMA auto_vacuum = INCREMENTAL");
+ db->exec("VACUUM");
+ db->exec("PRAGMA user_version = 3");
+}
+
OfflineDatabase::Statement OfflineDatabase::getStatement(const char * sql) {
auto it = statements.find(sql);
@@ -458,6 +480,7 @@ void OfflineDatabase::deleteRegion(OfflineRegion&& region) {
stmt->run();
evict(0);
+ db->exec("PRAGMA incremental_vacuum");
// Ensure that the cached offlineTileCount value is recalculated.
offlineMapboxTileCount = {};
@@ -614,10 +637,6 @@ bool OfflineDatabase::evict(uint64_t neededFreeSize) {
uint64_t pageSize = getPragma<int64_t>("PRAGMA page_size");
uint64_t pageCount = getPragma<int64_t>("PRAGMA page_count");
- if (pageSize * pageCount > maximumCacheSize) {
- Log::Warning(mbgl::Event::Database, "Current size is larger than the maximum size. Database won't get truncated.");
- }
-
auto usedSize = [&] {
return pageSize * (pageCount - getPragma<int64_t>("PRAGMA freelist_count"));
};
diff --git a/platform/default/mbgl/storage/offline_database.hpp b/platform/default/mbgl/storage/offline_database.hpp
index eb18cc18d2..1e77d560d4 100644
--- a/platform/default/mbgl/storage/offline_database.hpp
+++ b/platform/default/mbgl/storage/offline_database.hpp
@@ -58,8 +58,10 @@ public:
private:
void connect(int flags);
+ int userVersion();
void ensureSchema();
void removeExisting();
+ void migrateToVersion3();
class Statement {
public:
diff --git a/platform/default/mbgl/storage/offline_download.cpp b/platform/default/mbgl/storage/offline_download.cpp
index 0dcffbd9cb..e3fd10503d 100644
--- a/platform/default/mbgl/storage/offline_download.cpp
+++ b/platform/default/mbgl/storage/offline_download.cpp
@@ -228,12 +228,8 @@ void OfflineDownload::ensureResource(const Resource& resource, std::function<voi
return;
}
-
- if (resource.kind == Resource::Kind::Tile
- && util::mapbox::isMapboxURL(resource.url)
- && offlineDatabase.offlineMapboxTileCountLimitExceeded()) {
- observer->mapboxTileCountLimitExceeded(offlineDatabase.getOfflineMapboxTileCountLimit());
- setState(OfflineRegionDownloadState::Inactive);
+
+ if (checkTileCountLimit(resource)) {
return;
}
@@ -255,6 +251,10 @@ void OfflineDownload::ensureResource(const Resource& resource, std::function<voi
observer->statusChanged(status);
+ if (checkTileCountLimit(resource)) {
+ return;
+ }
+
if (status.complete()) {
setState(OfflineRegionDownloadState::Inactive);
}
@@ -262,4 +262,16 @@ void OfflineDownload::ensureResource(const Resource& resource, std::function<voi
});
}
+bool OfflineDownload::checkTileCountLimit(const Resource& resource) {
+ if (resource.kind == Resource::Kind::Tile
+ && util::mapbox::isMapboxURL(resource.url)
+ && offlineDatabase.offlineMapboxTileCountLimitExceeded()) {
+ observer->mapboxTileCountLimitExceeded(offlineDatabase.getOfflineMapboxTileCountLimit());
+ setState(OfflineRegionDownloadState::Inactive);
+ return true;
+ }
+
+ return false;
+}
+
} // namespace mbgl
diff --git a/platform/default/mbgl/storage/offline_download.hpp b/platform/default/mbgl/storage/offline_download.hpp
index 70c486d945..706cdf98d8 100644
--- a/platform/default/mbgl/storage/offline_download.hpp
+++ b/platform/default/mbgl/storage/offline_download.hpp
@@ -48,7 +48,8 @@ private:
*/
void ensureResource(const Resource&, std::function<void (Response)> = {});
void ensureTiles(SourceType, uint16_t, const SourceInfo&);
-
+ bool checkTileCountLimit(const Resource& resource);
+
int64_t id;
OfflineRegionDefinition definition;
OfflineDatabase& offlineDatabase;
diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md
new file mode 100644
index 0000000000..68049be4cc
--- /dev/null
+++ b/platform/ios/CHANGELOG.md
@@ -0,0 +1,204 @@
+# Changelog for Mapbox iOS SDK
+
+Mapbox welcomes participation and contributions from everyone. If you’d like to do so please see [CONTRIBUTING.md](https://github.com/mapbox/mapbox-gl-native/blob/master/CONTRIBUTING.md) first to get started.
+
+## 3.2.0
+
+- If you’ve previously installed the SDK as a static framework, the installation workflow has changed to address issues when submitting your application to the App Store or installing it on a device. Upon upgrading to this version of the SDK, you’ll need to add Mapbox.bundle to the Copy Bundle Resources build phase and remove Mapbox.framework from the Embed Frameworks build phase. ([#4455](https://github.com/mapbox/mapbox-gl-native/pull/4455))
+- Offline packs can now be downloaded to allow users to view specific regions of the map offline. A new MGLOfflineStorage class provides APIs for managing MGLOfflinePacks. ([#4221](https://github.com/mapbox/mapbox-gl-native/pull/4221))
+- Tiles and other resources are cached in the same file that holds offline resources. The combined cache file is located in a subdirectory of the user’s Application Support directory, which means iOS will not delete the file when disk space runs low. ([#4377](https://github.com/mapbox/mapbox-gl-native/pull/4377))
+- Fixed an issue where the map view’s center would always be calculated as if the view occupied the entire screen. ([#4504](https://github.com/mapbox/mapbox-gl-native/issues/4504))
+- The user dot no longer disappears after panning the map across the antimeridian at low zoom levels. ([#4275](https://github.com/mapbox/mapbox-gl-native/pull/4275))
+- The map no longer recoils when panning quickly at low zoom levels. ([#4214](https://github.com/mapbox/mapbox-gl-native/pull/4214))
+- Fixed an issue causing the map to pan the wrong way when the user pinches unevenly. ([#4427](https://github.com/mapbox/mapbox-gl-native/pull/4427))
+- The maximum zoom level is capped at 21 due to rendering issues at higher zoom levels. ([#4417](https://github.com/mapbox/mapbox-gl-native/pull/4417))
+- An icon laid out along a line no longer appears if it would extend past the end of the line. Some one-way arrows no longer point the wrong way. ([#3839](https://github.com/mapbox/mapbox-gl-native/pull/3839))
+- Fixed warping of dashed lines near sharp corners. ([#3914](https://github.com/mapbox/mapbox-gl-native/pull/3914))
+- Telemetry location gathering now occurs only when the device is in motion. ([#4115](https://github.com/mapbox/mapbox-gl-native/pull/4115))
+- An account’s monthly active users metric only counts a user once a map view is displayed to that user. ([#3713](https://github.com/mapbox/mapbox-gl-native/pull/3713))
+- A .dSYM bundle is included with the dynamic framework to facilitate symbolication of crash logs.
+- Updated documentation to reflect the requirement that you must embed the dynamic framework in the “Embedded Binaries” section in Xcode. ([#4011](https://github.com/mapbox/mapbox-gl-native/issues/4011))
+- Polygons and polylines now default to using the map view’s tint color. ([#4028](https://github.com/mapbox/mapbox-gl-native/pull/4028))
+- The user dot no longer lags when panning the map. ([#3683](https://github.com/mapbox/mapbox-gl-native/pull/3683))
+- The Improve This Map tool now uses the same zoom level that is currently being shown in the map view. ([#4068](https://github.com/mapbox/mapbox-gl-native/pull/4068))
+- Fixed a formatting issue in the documentation for `MGLCoordinateBoundsIsEmpty()`. ([#3958](https://github.com/mapbox/mapbox-gl-native/pull/3958))
+
+## 3.1.2
+
+- You can once again install the static framework without manually linking several framework and library dependencies. ([#4029](https://github.com/mapbox/mapbox-gl-native/pull/4029))
+- The location manager used by MGLMapView to show the user’s location is now paused when the application is sent to the background. ([#4034](https://github.com/mapbox/mapbox-gl-native/pull/4034))
+
+## 3.1.1
+
+- Corrected the dynamic framework’s minimum deployment target to iOS 8.0. ([#3872](https://github.com/mapbox/mapbox-gl-native/pull/3872))
+- Fixed Fabric compatibility. ([#3847](https://github.com/mapbox/mapbox-gl-native/pull/3847))
+- Fixed a crash that can occur when reselecting an annotation. ([#3881](https://github.com/mapbox/mapbox-gl-native/pull/3881))
+- Fixed an issue preventing the Latitude inspectable from working when it is set before setting the Zoom Level inspectable in Interface Builder. ([#3886](https://github.com/mapbox/mapbox-gl-native/pull/3886))
+- Fixed an issue that incorrectly expanded the tappable area of an annotation and prevented the annotation’s alignment rect insets from having any effect on the tappable area. ([#3898](https://github.com/mapbox/mapbox-gl-native/pull/3898))
+- Fixed an issue preventing `-[MGLMapViewDelegate mapView:tapOnCalloutForAnnotation:]` from being called when a non-custom callout view is tapped. ([#3875](https://github.com/mapbox/mapbox-gl-native/pull/3875))
+
+## 3.1.0
+
+- The SDK is now distributed as a dynamic framework instead of a static library, resulting in a simpler installation workflow and significantly reduced download size. The framework contains both simulator and device content. If you install the dynamic framework manually, you’ll need to strip out the simulator content before submitting your application to the App Store due to [an Xcode bug](http://www.openradar.me/radar?id=6409498411401216); see the installation instructions included with the framework for details. ([#3183](https://github.com/mapbox/mapbox-gl-native/pull/3183))
+- Fixed an issue causing the entire MGLMapView to leak. ([#3448](https://github.com/mapbox/mapbox-gl-native/pull/3448))
+- `MGLMapView` methods that alter the viewport now accept optional completion handlers. ([#3090](https://github.com/mapbox/mapbox-gl-native/pull/3090))
+- You can now modify an annotation’s image after adding the annotation to the map. ([#3146](https://github.com/mapbox/mapbox-gl-native/pull/3146))
+- Tapping now selects annotations more reliably. Tapping near the top of a large annotation image now selects that annotation. An annotation image’s alignment insets influence how far away the user can tap and still select the annotation. For example, if your annotation image has a large shadow, you can keep that shadow from being tappable by excluding it from the image’s alignment rect. ([#3261](https://github.com/mapbox/mapbox-gl-native/pull/3261))
+- Annotations remain visible after switching to a different style. ([#3049](https://github.com/mapbox/mapbox-gl-native/pull/3049))
+- The minimum and maximum zoom levels can now be configured using the `minimumZoomLevel` and `maximumZoomLevel` properties, respectively. The map is no longer limited to zoom level 18: by default, the maximum zoom level is now 20, allowing for a more detailed map in urban areas. ([#3712](https://github.com/mapbox/mapbox-gl-native/pull/3712))
+- A new method on MGLMapView, `-flyToCamera:withDuration:completionHandler:`, lets you transition between viewpoints along an arc as if by aircraft. ([#3171](https://github.com/mapbox/mapbox-gl-native/pull/3171), [#3301](https://github.com/mapbox/mapbox-gl-native/pull/3301))
+- MGLMapCamera’s `altitude` values now match those of MKMapCamera. ([#3362](https://github.com/mapbox/mapbox-gl-native/pull/3362))
+- MGLMapView properties like `centerCoordinate` and `camera` now offset the center to account for any translucent top or bottom bar. As a result, when user tracking is enabled and the map view is an immediate child of a view controller, the user dot is centered in the unobscured portion of the map view. To override this offset, modify the `contentInset` property; you may also need to set the containing view controller’s `automaticallyAdjustsScrollViewInsets` property to `NO`. ([#3583](https://github.com/mapbox/mapbox-gl-native/pull/3583))
+- In user tracking mode, the user dot stays in a fixed position within MGLMapView while the map pans smoothly. A new property, `userLocationVerticalAlignment`, determines the user dot’s fixed position. ([#3589](https://github.com/mapbox/mapbox-gl-native/pull/3589))
+- When the user tracking mode is set to `MGLUserTrackingModeFollowWithCourse`, an optional `targetCoordinate` is kept within sight at all times as the user changes location. This property, in conjunction with the `userLocationVerticalAlignment` property, may be useful for displaying the user’s progress toward a waypoint. ([#3680](https://github.com/mapbox/mapbox-gl-native/pull/3680))
+- Heading or course tracking mode can now be enabled as soon as an MGLMapView is initialized. ([#3680](https://github.com/mapbox/mapbox-gl-native/pull/3680))
+- Zooming and rotation gestures no longer disable user tracking mode. ([#3589](https://github.com/mapbox/mapbox-gl-native/pull/3589))
+- User tracking mode starts out at a lower zoom level by default. ([#3589](https://github.com/mapbox/mapbox-gl-native/pull/3589))
+- Fixed an issue with small map views not properly fitting annotations within bounds. (#[3407](https://github.com/mapbox/mapbox-gl-native/pull/3407))
+- When the user rotates the map to within 7° of true north, the map view now snaps to true north. ([#3403](https://github.com/mapbox/mapbox-gl-native/pull/3403))
+- The map view’s background can now be transparent or translucent, as long as the style’s background layer is transparent or translucent and `MGLMapView.opaque` is set to `NO`. ([#3096](https://github.com/mapbox/mapbox-gl-native/pull/3096))
+- Documentation is now generated by [jazzy](https://github.com/realm/jazzy) instead of appledoc. ♪♫ ([#3203](https://github.com/mapbox/mapbox-gl-native/pull/3203))
+- New API to provide a custom callout view to the map for annotations. ([#3456](https://github.com/mapbox/mapbox-gl-native/pull/3456))
+- Made telemetry on/off setting available in-app. ([#3445](https://github.com/mapbox/mapbox-gl-native/pull/3445))
+- Fixed an issue with users not being counted by Mapbox if they had disabled telemetry. ([#3495](https://github.com/mapbox/mapbox-gl-native/pull/3495))
+- Fixed crash caused by MGLAnnotationImage with non-integer width or height ([#2198](https://github.com/mapbox/mapbox-gl-native/issues/2198))
+- Fixed “include of non-modular header” errors in Swift projects managed by CocoaPods. ([#3679](https://github.com/mapbox/mapbox-gl-native/pull/3679))
+- Avoids triggering the blue background location status bar when user has granted "when in use" permission. ([#3671](https://github.com/mapbox/mapbox-gl-native/issues/3671))
+- Deprecated the `debugActive` property and `-toggleDebug` method on MGLMapView in favor of a new `debugMask` property that exposes individual style debugging options. ([#3742](https://github.com/mapbox/mapbox-gl-native/pull/3742))
+
+## 3.0.1
+
+- Fixed CoreTelephony.framework crash. ([#3170](https://github.com/mapbox/mapbox-gl-native/pull/3170))
+- Fixed an issue preventing the compass from responding to taps after the compass is moved programmatically. ([#3117](https://github.com/mapbox/mapbox-gl-native/pull/3117))
+- CocoaPods is now distributed via a (static) framework. ([#3181](https://github.com/mapbox/mapbox-gl-native/issues/3181))
+
+## 3.0.0
+
+- If you install this SDK via CocoaPods, CocoaPods version 0.38.0 or above is required. ([#2132](https://github.com/mapbox/mapbox-gl-native/pull/2132))
+- The `styleID` property has been removed from MGLMapView. Instead, set the `styleURL` property to an NSURL in the form `mapbox://styles/STYLE_ID`. If you previously set the style ID in Interface Builder’s Attributes inspector, delete the `styleID` entry from the User Defined Runtime Attributes section of the Identity inspector, then set the new “Style URL” inspectable to a value in the form `mapbox://styles/STYLE_ID`. ([#2632](https://github.com/mapbox/mapbox-gl-native/pull/2632))
+- Default styles such as Streets are no longer bundled with the SDK; instead, they are loaded at runtime from the style API on mapbox.com. As always, you can use these default styles with any valid access token, and Streets continues to be `MGLMapView`’s initial style. The `bundledStyleURLs` property on `MGLMapView` has been deprecated in favor of several class methods on `MGLStyle` that provide direct access to the default styles. ([#2746](https://github.com/mapbox/mapbox-gl-native/pull/2746))
+- The SDK now builds with Bitcode enabled. A version of libMapbox.a with Bitcode disabled is also available. ([#2332](https://github.com/mapbox/mapbox-gl-native/issues/2332), [#3003](https://github.com/mapbox/mapbox-gl-native/pull/3003))
+- The style URL can be set to a local resource: `asset://local-color.json` and `local-color.json` both resolve to a file named `local-color.json` in the application’s root folder. ([#3087](https://github.com/mapbox/mapbox-gl-native/pull/3087))
+- The double-tap-drag gesture for zooming in and out is now consistent with the Google Maps SDK. ([#2153](https://github.com/mapbox/mapbox-gl-native/pull/2153))
+- A new `MGLAnnotationImage.enabled` property allows you to disable touch events on individual annotations. ([#2501](https://github.com/mapbox/mapbox-gl-native/pull/2501))
+- Fixed a rendering issue that caused one-way arrows along tile boundaries to point due east instead of in the direction of travel. ([#2530](https://github.com/mapbox/mapbox-gl-native/pull/2530))
+- Fixed an issue that prevented zoom level–dependent style properties from updating after zooming programmatically with animation. ([#2951](https://github.com/mapbox/mapbox-gl-native/pull/2951))
+- Performance and appearance improvements during annotation adds & removes. ([#1688](https://github.com/mapbox/mapbox-gl-native/issues/1688))
+- Overall improved performance during renders by not rendering faster than necessary. ([#1975](https://github.com/mapbox/mapbox-gl-native/issues/1975))
+- Fixed a rendering issue with styles that use the `background-pattern` property. ([#2531](https://github.com/mapbox/mapbox-gl-native/pull/2531))
+- Fixed a crash when reusing a single `MGLMapView` across multiple `UIViewController`s. ([#2969](https://github.com/mapbox/mapbox-gl-native/pull/2969))
+- Fixed a crash on iPod touch and other devices or simulators without a cell carrier. ([#2687](https://github.com/mapbox/mapbox-gl-native/issues/2687))
+- Eliminated flickering when opening and closing an overlay, such as an alert or action sheet. ([#2309](https://github.com/mapbox/mapbox-gl-native/pull/2309))
+- Labels can now line wrap on hyphens and other punctuation. ([#2598](https://github.com/mapbox/mapbox-gl-native/pull/2598))
+- A new delegate callback was added for observing taps to annotation callout views. ([#2596](https://github.com/mapbox/mapbox-gl-native/pull/2596))
+- `-mapViewRegionIsChanging:` is now sent to the map view’s delegate during gestures. ([#2700](https://github.com/mapbox/mapbox-gl-native/pull/2700))
+- Improved gesture recognition while the map is tilted. ([#2770](https://github.com/mapbox/mapbox-gl-native/pull/2770))
+- `-mapViewWillStartLoadingMap:` and `-mapViewDidFinishLoadingMap:` delegate methods now work. ([#2706](https://github.com/mapbox/mapbox-gl-native/pull/2706))
+- Removed CoreTelephony.framework dependency. ([#2581](https://github.com/mapbox/mapbox-gl-native/pull/2581))
+- Improved user location annotation responsiveness. ([#2643](https://github.com/mapbox/mapbox-gl-native/pull/2643))
+
+## 2.1.2
+
+- Built with Xcode 6.4 to not yet trigger Bitcode compatibility until Xcode 7 stabilizes. ([#2332](https://github.com/mapbox/mapbox-gl-native/issues/2332))
+
+## 2.1.1
+
+- Fixes for Xcode 7 and Bitcode. ([#2238](https://github.com/mapbox/mapbox-gl-native/pull/2238))
+
+## 2.1.0
+
+- A two-finger vertical swipe now tilts the map into perspective mode. ([#2116](https://github.com/mapbox/mapbox-gl-native/pull/2116))
+- A new `MGLMapCamera` API allows you to transition multiple viewpoint properties, including rotation and pitch, simultaneously with an optional custom duration and timing function. ([#2193](https://github.com/mapbox/mapbox-gl-native/pull/2193))
+- A new user tracking mode, `MGLUserTrackingModeFollowWithCourse`, has been added for indicating the current direction of travel. ([#2068](https://github.com/mapbox/mapbox-gl-native/pull/2068))
+- Version 8 (`v8`) of the [Mapbox GL style spec](https://www.mapbox.com/mapbox-gl-style-spec/) is now required. If you are using a custom `v7` style, it needs to be upgraded using [this migrator script](https://github.com/mapbox/mapbox-gl-style-spec/blob/mb-pages/migrations/v7.js). ([#2052](https://github.com/mapbox/mapbox-gl-native/pull/2052))
+- Applications built with Mapbox GL no longer crash when Location Services launches them in background mode. ([#1821](https://github.com/mapbox/mapbox-gl-native/pull/1821), [#1869](https://github.com/mapbox/mapbox-gl-native/pull/1869))
+- Fixed a crash when adding annotations to an `MGLMapView` inside `-viewDidLoad`. ([#1874](https://github.com/mapbox/mapbox-gl-native/pull/1874))
+- The user location annotation view now indicates the location reading’s accuracy and the device’s heading. ([#2010](https://github.com/mapbox/mapbox-gl-native/pull/2010))
+- Eliminated linker warnings and errors when building against the iOS 9.0 SDK in Xcode 7. ([#1962](https://github.com/mapbox/mapbox-gl-native/pull/1962))
+- Worked around a bug in the iOS 9.0 SDK that caused a crash on launch. ([#1958](https://github.com/mapbox/mapbox-gl-native/pull/1958))
+- User location tracking no longer sends `MGLMapView` into an invalid region on iOS 9. ([#1925](https://github.com/mapbox/mapbox-gl-native/pull/1925))
+- Eliminated console spew in the iOS demo application that was related to Mapbox Metrics HTTP requests. ([#1937](https://github.com/mapbox/mapbox-gl-native/issues/1937))
+- Implemented `-[MGLMapView showAnnotations:animated:]`. ([#2050](https://github.com/mapbox/mapbox-gl-native/pull/2050))
+- Fixed a crash adding a shape annotation with zero points. ([#2098](https://github.com/mapbox/mapbox-gl-native/pull/2098))
+- Debug mode now displays information useful for debugging the label collision algorithm. ([#1808](https://github.com/mapbox/mapbox-gl-native/pull/1808))
+- Minor style updates. ([#1910](https://github.com/mapbox/mapbox-gl-native/pull/1910))
+- The CocoaPods pod now contains a `README.md` file. ([#1886](https://github.com/mapbox/mapbox-gl-native/pull/1886))
+
+## 2.0.0
+
+Repackaging 2.0.0-pre.1 as it contained no issues.
+
+## 2.0.0-pre.1
+
+Repackaging 0.5.1 as the Mapbox iOS SDK 2.0.0 series.
+
+## 0.5.1
+
+### iOS
+
+- Added support for CocoaPods 0.38.0. ([#1876](https://github.com/mapbox/mapbox-gl-native/pull/1876))
+
+## 0.5.0
+
+### Core
+
+- Support for runtime marker imagery. ([#941](https://github.com/mapbox/mapbox-gl-native/pull/941))
+- Added `Map::fitBounds()` for region-based viewport setting. ([#1092](https://github.com/mapbox/mapbox-gl-native/issues/1092))
+- Added a raster satellite bundled style and improved raster rendering. ([#963](https://github.com/mapbox/mapbox-gl-native/issues/963))
+- Improved round line joins for semi-transparent lines. ([#1839](https://github.com/mapbox/mapbox-gl-native/pull/1839))
+- Improved map render lifecycle notifications. ([#1026](https://github.com/mapbox/mapbox-gl-native/issues/1026))
+- Fixed a bug that caused annotations not to show at zoom level zero. ([#1279](https://github.com/mapbox/mapbox-gl-native/issues/1279))
+- Fixed a bug with the ordering of shape layers. ([#1866](https://github.com/mapbox/mapbox-gl-native/pull/1866))
+- Other bug fixes and performance improvements.
+
+### iOS
+
+- **Breaking:** Headers now make use of lightweight generics, eliminating many unnecessary casts when working with annotations in Swift 2.0 in Xcode 7. ([#1711](https://github.com/mapbox/mapbox-gl-native/pull/1711))
+- **Breaking:** `-mapView:symbolNameForAnnotation:` has been removed from the `MGLMapViewDelegate` protocol. Implement `-mapView:imageForAnnotation:` instead, which accepts images at runtime. ([#941](https://github.com/mapbox/mapbox-gl-native/pull/941))
+- **Breaking:** `MGLMapView.direction` is now expressed in terms of degrees clockwise from true north, as indicated in the documentation, rather than counterclockwise. ([#1789](https://github.com/mapbox/mapbox-gl-native/pull/1789))
+- A Satellite style showing Mapbox Satellite imagery is now bundled with Mapbox GL. ([#1845](https://github.com/mapbox/mapbox-gl-native/pull/1845))
+- Improved `UIView` tracking to the map. ([#1813](https://github.com/mapbox/mapbox-gl-native/pull/1813))
+- Delegate method `-[MGLMapViewDelegate mapView:didFailToLocateUserWithError:]` now works. ([#1608](https://github.com/mapbox/mapbox-gl-native/pull/1608))
+- It is now possible to fit the map’s viewport to a coordinate bounding box via `-[MGLMapView setVisibleCoordinateBounds:animated:]` or to a specific set of coordinates via `-[MGLMapView setVisibleCoordinates:count:edgePadding:animated:]`. ([#1783](https://github.com/mapbox/mapbox-gl-native/pull/1783), [#1795](https://github.com/mapbox/mapbox-gl-native/pull/1795))
+- The logo and ℹ️ no longer disappear or get distorted after embedding MGLMapView in a different view, and you can now access these subviews directly via properties on MGLMapView. ([#1779](https://github.com/mapbox/mapbox-gl-native/pull/1779), [#1815](https://github.com/mapbox/mapbox-gl-native/pull/1815))
+- Raster tiles now look sharper midway between two zoom levels. ([#1843](https://github.com/mapbox/mapbox-gl-native/pull/1843))
+- Resetting the map rotation to north no longer also resets the user location tracking mode. ([#1809](https://github.com/mapbox/mapbox-gl-native/pull/1809))
+- `-[MGLMapView convertPoint:toCoordinateFromView:]` now returns accurate coordinates on iPhone 6. ([#1827](https://github.com/mapbox/mapbox-gl-native/pull/1827))
+- Fixed an issue in which `-[MGLMapView direction]` would sometimes return 360 instead of 0. ([#1829](https://github.com/mapbox/mapbox-gl-native/pull/1829))
+- Build against iOS 8.4. ([#1868](https://github.com/mapbox/mapbox-gl-native/pull/1868))
+
+## 0.4.0
+
+### Core
+
+- Support for polyline and polygon shape annotations. ([#1655](https://github.com/mapbox/mapbox-gl-native/issues/1655))
+- Improved placement and density of labels. ([#1666](https://github.com/mapbox/mapbox-gl-native/issues/1666), [blog](https://www.mapbox.com/blog/better-label-placement-mapbox-mobile/))
+- Improved z-ordering appearance of point markers. ([#988](https://github.com/mapbox/mapbox-gl-native/issues/988))
+- Fixed an issue in which certain features, such as roundabouts, were not rendered completely. ([#1725](https://github.com/mapbox/mapbox-gl-native/issues/1725))
+- Many bug fixes and performance and stability improvements.
+- Improved tests.
+
+### iOS
+
+- **Breaking:** `MGLMapView` no longer manages Mapbox access tokens directly; an access token cannot be passed in when initializing the map view. Instead, set `MGLMapboxAccessToken` to your access token in your app’s `Info.plist` file, or call `+[MGLAccountManager setAccessToken:]` before initializing the map view. If you were setting the access token inside an Interface Builder inspectable, also remove it from the User Defined Runtime Attributes section of the Identity inspector. ([#1553](https://github.com/mapbox/mapbox-gl-native/issues/1553))
+- **Breaking:** `MGLAccountManager`'s `-setMapboxMetricsEnabledSettingShownInApp:` has been removed. If you implement a Mapbox Metrics switch inside your app, instead of inside a Settings bundle, set `MGLMapboxMetricsEnabledSettingShownInApp` to `YES` in the `Info.plist` file. ([#1553](https://github.com/mapbox/mapbox-gl-native/issues/1553))
+- **Breaking:** `MGLMapView`'s `-mapID` has been renamed to `-styleID`. ([#1561](https://github.com/mapbox/mapbox-gl-native/issues/1561))
+- Headers have been audited for nullability, improving type safety in both Objective-C and Swift 1.2 when compiling with Xcode 6.3 or above. ([#1578](https://github.com/mapbox/mapbox-gl-native/issues/1578))
+- Fixed an issue in which the map would sometimes spin 180° while rotating the map with two fingers. ([#1453](https://github.com/mapbox/mapbox-gl-native/issues/1453))
+- Added a shortcut to the Mapbox Metrics switch in `MGLMapView`'s action sheet that is attached to the ℹ️ button. ([#1611](https://github.com/mapbox/mapbox-gl-native/issues/1611))
+- `MGLMapView` now supports Interface Builder designables. When you add an `MGLMapView` to a storyboard, it displays instructions for getting set up directly on the storyboard canvas. ([#1573](https://github.com/mapbox/mapbox-gl-native/issues/1573))
+- The default title for the user location annotation is now “You Are Here”. You can customize the title by setting `mapView.userAnnotation.title`. ([#1559](https://github.com/mapbox/mapbox-gl-native/issues/1559))
+- Internal use of the Reachability library has been cleaned up so that your app can include its own copy of Reachability. ([#1718](https://github.com/mapbox/mapbox-gl-native/issues/1718))
+- Now distribute a binary stripped of debugging symbols by default with an optional, secondary symbols build. ([#1650](https://github.com/mapbox/mapbox-gl-native/issues/1650))
+
+## 0.3.1
+
+- Temporarily removed `IBDesignable` support on iOS.
+
+## 0.3.0
+
+- Initial iOS beta release.
+
+Known issues:
+
+- None.
diff --git a/platform/ios/INSTALL.md b/platform/ios/INSTALL.md
index b4e27acdea..416bbc6381 100644
--- a/platform/ios/INSTALL.md
+++ b/platform/ios/INSTALL.md
@@ -95,7 +95,15 @@ If your application targets iOS 7.x, you’ll need to install the static framewo
1. Build from source manually per above.
-1. Open the project editor and select your application target. Drag `build/ios/pkg/static/Mapbox.framework` into the “Embedded Binaries” section of the General tab. (Don’t drag it into the “Linked Frameworks and Libraries” section; Xcode will add it there automatically.) In the sheet that appears, make sure “Copy items if needed” is checked, then click Finish.
+1. Drag the Mapbox.bundle and Mapbox.framework files in `build/ios/pkg/static` into the Project navigator, checking "Copy items if needed". It should happen automatically, but ensure that:
+
+ - `Mapbox.framework` is listed in your `Link Binary With Libraries` build phase.
+ - Your *Framework Search Paths* (`FRAMEWORK_SEARCH_PATHS`) build setting includes the directory that contains `Mapbox.framework`. For most projects, the default value of `$(inherited) $(PROJECT_DIR)` should be sufficient.
+ - `Mapbox.bundle` is in your target's *Copy Bundle Resources* build phase.
+
+1. **Optional** As [noted in the documentation](https://www.mapbox.com/ios-sdk/#telemetry_opt_out) you may use the provided Settings.bundle to provide a Telemetry opt out for users. To use the provided file, drag the `Settings.bundle` file in `build/ios/pkg` into the Project navigator, checking "Copy items if needed". It should happen automatically, but ensure that:
+
+ - `Settings.bundle` is in your target's *Copy Bundle Resources* build phase.
1. Add the following Cocoa Touch frameworks and libraries to the “Linked Frameworks and Libraries” section:
@@ -138,4 +146,4 @@ class ViewController: UIViewController {
### Troubleshooting
-On OS X, you can also try clearing the Xcode cache with `make clear_xcode_cache`.
+On OS X, you can also try clearing the Xcode cache with `make clear_xcode_cache`. \ No newline at end of file
diff --git a/platform/ios/Mapbox-iOS-SDK.podspec b/platform/ios/Mapbox-iOS-SDK.podspec
index 08e27053c9..b226ef780d 100644
--- a/platform/ios/Mapbox-iOS-SDK.podspec
+++ b/platform/ios/Mapbox-iOS-SDK.podspec
@@ -1,7 +1,7 @@
Pod::Spec.new do |m|
m.name = 'Mapbox-iOS-SDK'
- m.version = '3.2.0-pre.2-symbols'
+ m.version = '3.2.0-beta.3-symbols'
m.summary = 'Open source vector map solution for iOS with full styling capabilities.'
m.description = 'Open source, OpenGL-based vector map solution for iOS with full styling capabilities and Cocoa Touch APIs.'
diff --git a/platform/ios/app/MBXOfflinePacksTableViewController.m b/platform/ios/app/MBXOfflinePacksTableViewController.m
index 3410f075a1..8aad4fbbf8 100644
--- a/platform/ios/app/MBXOfflinePacksTableViewController.m
+++ b/platform/ios/app/MBXOfflinePacksTableViewController.m
@@ -28,52 +28,77 @@ static NSString * const MBXOfflinePacksTableViewActiveCellReuseIdentifier = @"Ac
@end
-@interface MBXOfflinePacksTableViewController () <MGLOfflinePackDelegate>
-
-@property (nonatomic, strong) NS_MUTABLE_ARRAY_OF(MGLOfflinePack *) *offlinePacks;
-
-@end
-
-@implementation MBXOfflinePacksTableViewController {
- NSUInteger _untitledRegionCount;
-}
+@implementation MBXOfflinePacksTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
- __weak MBXOfflinePacksTableViewController *weakSelf = self;
- [[MGLOfflineStorage sharedOfflineStorage] getPacksWithCompletionHandler:^(NS_ARRAY_OF(MGLOfflinePack *) *packs, NSError *error) {
- MBXOfflinePacksTableViewController *strongSelf = weakSelf;
- strongSelf.offlinePacks = packs.mutableCopy;
- [strongSelf.tableView reloadData];
-
- for (MGLOfflinePack *pack in strongSelf.offlinePacks) {
- pack.delegate = strongSelf;
- [pack requestProgress];
- }
-
- if (error) {
- UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Can’t Find Offline Packs" message:@"Mapbox GL was unable to find the existing offline packs." preferredStyle:UIAlertControllerStyleAlert];
- [alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]];
- [self presentViewController:alertController animated:YES completion:^{
- [strongSelf dismissViewControllerAnimated:YES completion:nil];
+ [[MGLOfflineStorage sharedOfflineStorage] addObserver:self forKeyPath:@"packs" options:NSKeyValueObservingOptionInitial context:NULL];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(offlinePackProgressDidChange:) name:MGLOfflinePackProgressChangedNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(offlinePackDidReceiveError:) name:MGLOfflinePackErrorNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(offlinePackDidReceiveMaximumAllowedMapboxTiles:) name:MGLOfflinePackMaximumMapboxTilesReachedNotification object:nil];
+}
+
+- (void)dealloc {
+ [[MGLOfflineStorage sharedOfflineStorage] removeObserver:self forKeyPath:@"packs"];
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NS_DICTIONARY_OF(NSString *, id) *)change context:(void *)context {
+ if ([keyPath isEqualToString:@"packs"]) {
+ NSKeyValueChange changeKind = [change[NSKeyValueChangeKindKey] unsignedIntegerValue];
+ NSIndexSet *indices = change[NSKeyValueChangeIndexesKey];
+ NSMutableArray *indexPaths;
+ if (indices) {
+ indexPaths = [NSMutableArray arrayWithCapacity:indices.count];
+ [indices enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
+ [indexPaths addObject:[NSIndexPath indexPathForRow:idx inSection:0]];
}];
}
- }];
+ switch (changeKind) {
+ case NSKeyValueChangeInsertion:
+ [self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
+ break;
+
+ case NSKeyValueChangeRemoval:
+ [self.tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
+ break;
+
+ case NSKeyValueChangeReplacement:
+ [self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
+ break;
+
+ default:
+ [self.tableView reloadData];
+
+ for (MGLOfflinePack *pack in [MGLOfflineStorage sharedOfflineStorage].packs) {
+ if (pack.state == MGLOfflinePackStateUnknown) {
+ [pack requestProgress];
+ }
+ }
+
+ break;
+ }
+ } else {
+ [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
+ }
}
- (IBAction)addCurrentRegion:(id)sender {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Add Offline Pack" message:@"Choose a name for the pack:" preferredStyle:UIAlertControllerStyleAlert];
- [alertController addTextFieldWithConfigurationHandler:nil];
+ [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
+ textField.placeholder = [NSString stringWithFormat:@"%@", MGLStringFromCoordinateBounds(self.mapView.visibleCoordinateBounds)];
+ }];
[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
UIAlertAction *downloadAction = [UIAlertAction actionWithTitle:@"Download" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
MGLMapView *mapView = self.mapView;
NSAssert(mapView, @"No map view to get the current region from.");
- NSString *name = alertController.textFields.firstObject.text;
+ UITextField *nameField = alertController.textFields.firstObject;
+ NSString *name = nameField.text;
if (!name.length) {
- name = [NSString stringWithFormat:@"Untitled %lu", (unsigned long)++_untitledRegionCount];
+ name = nameField.placeholder;
}
MGLTilePyramidOfflineRegion *region = [[MGLTilePyramidOfflineRegion alloc] initWithStyleURL:mapView.styleURL bounds:mapView.visibleCoordinateBounds fromZoomLevel:mapView.zoomLevel toZoomLevel:mapView.maximumZoomLevel];
@@ -81,26 +106,21 @@ static NSString * const MBXOfflinePacksTableViewActiveCellReuseIdentifier = @"Ac
MBXOfflinePackContextNameKey: name,
}];
- __weak MBXOfflinePacksTableViewController *weakSelf = self;
[[MGLOfflineStorage sharedOfflineStorage] addPackForRegion:region withContext:context completionHandler:^(MGLOfflinePack *pack, NSError *error) {
- MBXOfflinePacksTableViewController *strongSelf = weakSelf;
if (error) {
NSString *message = [NSString stringWithFormat:@"Mapbox GL was unable to add the offline pack “%@”.", name];
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Can’t Add Offline Pack" message:message preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
} else {
- pack.delegate = strongSelf;
[pack resume];
-
- NSIndexPath *indexPath = [NSIndexPath indexPathForRow:strongSelf.offlinePacks.count inSection:0];
- [strongSelf.offlinePacks addObject:pack];
- [strongSelf.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
}];
}];
[alertController addAction:downloadAction];
- alertController.preferredAction = downloadAction;
+ if ([alertController respondsToSelector:@selector(setPreferredAction:)]) {
+ alertController.preferredAction = downloadAction;
+ }
[self presentViewController:alertController animated:YES completion:nil];
}
@@ -108,14 +128,20 @@ static NSString * const MBXOfflinePacksTableViewActiveCellReuseIdentifier = @"Ac
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
- return self.offlinePacks.count;
+ return [MGLOfflineStorage sharedOfflineStorage].packs.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
- MGLOfflinePack *pack = self.offlinePacks[indexPath.row];
+ MGLOfflinePack *pack = [MGLOfflineStorage sharedOfflineStorage].packs[indexPath.row];
NSString *reuseIdentifier = pack.state == MGLOfflinePackStateActive ? MBXOfflinePacksTableViewActiveCellReuseIdentifier : MBXOfflinePacksTableViewInactiveCellReuseIdentifier;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier forIndexPath:indexPath];
+ [self updateTableViewCell:cell atIndexPath:indexPath forPack:pack];
+
+ return cell;
+}
+
+- (void)updateTableViewCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath forPack:(MGLOfflinePack *)pack {
cell.textLabel.text = pack.name;
MGLOfflinePackProgress progress = pack.progress;
NSString *completedString = [NSNumberFormatter localizedStringFromNumber:@(progress.countOfResourcesCompleted)
@@ -157,15 +183,11 @@ static NSString * const MBXOfflinePacksTableViewActiveCellReuseIdentifier = @"Ac
break;
}
cell.detailTextLabel.text = statusString;
-
- return cell;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
- MGLOfflinePack *pack = self.offlinePacks[indexPath.row];
- [self.offlinePacks removeObjectAtIndex:indexPath.row];
- [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
+ MGLOfflinePack *pack = [MGLOfflineStorage sharedOfflineStorage].packs[indexPath.row];
[[MGLOfflineStorage sharedOfflineStorage] removePack:pack withCompletionHandler:nil];
}
}
@@ -175,7 +197,7 @@ static NSString * const MBXOfflinePacksTableViewActiveCellReuseIdentifier = @"Ac
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
- MGLOfflinePack *pack = self.offlinePacks[indexPath.row];
+ MGLOfflinePack *pack = [MGLOfflineStorage sharedOfflineStorage].packs[indexPath.row];
switch (pack.state) {
case MGLOfflinePackStateUnknown:
break;
@@ -189,12 +211,10 @@ static NSString * const MBXOfflinePacksTableViewActiveCellReuseIdentifier = @"Ac
case MGLOfflinePackStateInactive:
[pack resume];
- [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case MGLOfflinePackStateActive:
[pack suspend];
- [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case MGLOfflinePackStateInvalid:
@@ -205,21 +225,42 @@ static NSString * const MBXOfflinePacksTableViewActiveCellReuseIdentifier = @"Ac
#pragma mark - Offline pack delegate
-- (void)offlinePack:(MGLOfflinePack *)pack progressDidChange:(MGLOfflinePackProgress)progress {
- NSUInteger index = [self.offlinePacks indexOfObject:pack];
+- (void)offlinePackProgressDidChange:(NSNotification *)notification {
+ MGLOfflinePack *pack = notification.object;
+ NSAssert([pack isKindOfClass:[MGLOfflinePack class]], @"MGLOfflineStorage notification has a non-pack object.");
+
+ NSUInteger index = [[MGLOfflineStorage sharedOfflineStorage].packs indexOfObject:pack];
if (index == NSNotFound) {
return;
}
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
- [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
+ UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
+ [self updateTableViewCell:cell atIndexPath:indexPath forPack:pack];
}
-- (void)offlinePack:(MGLOfflinePack *)pack didReceiveError:(NSError *)error {
- NSLog(@"Offline pack “%@” received error: %@", pack.name, error.localizedFailureReason);
+- (void)offlinePackDidReceiveError:(NSNotification *)notification {
+ MGLOfflinePack *pack = notification.object;
+ NSAssert([pack isKindOfClass:[MGLOfflinePack class]], @"MGLOfflineStorage notification has a non-pack object.");
+
+ NSError *error = notification.userInfo[MGLOfflinePackErrorUserInfoKey];
+ NSAssert([error isKindOfClass:[NSError class]], @"MGLOfflineStorage notification has a non-error error.");
+
+ NSString *message = [NSString stringWithFormat:@"iosapp encountered an error while downloading the offline pack “%@”: %@", pack.name, error.localizedFailureReason];
+ if (error.code == MGLErrorCodeConnectionFailed) {
+ NSLog(@"%@", message);
+ } else {
+ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Error Downloading Offline Pack" message:message preferredStyle:UIAlertControllerStyleAlert];
+ [alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]];
+ [self presentViewController:alertController animated:YES completion:nil];
+ }
}
-- (void)offlinePack:(MGLOfflinePack *)pack didReceiveMaximumAllowedMapboxTiles:(uint64_t)maximumCount {
+- (void)offlinePackDidReceiveMaximumAllowedMapboxTiles:(NSNotification *)notification {
+ MGLOfflinePack *pack = notification.object;
+ NSAssert([pack isKindOfClass:[MGLOfflinePack class]], @"MGLOfflineStorage notification has a non-pack object.");
+
+ uint64_t maximumCount = [notification.userInfo[MGLOfflinePackMaximumCountUserInfoKey] unsignedLongLongValue];
NSLog(@"Offline pack “%@” reached limit of %llu tiles.", pack.name, maximumCount);
}
diff --git a/platform/ios/docs/pod-README.md b/platform/ios/docs/pod-README.md
index e2c8ff813a..d62e23b606 100644
--- a/platform/ios/docs/pod-README.md
+++ b/platform/ios/docs/pod-README.md
@@ -33,7 +33,15 @@ bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Mapbox.framework/strip-fra
If your application targets iOS 7.x, you’ll need to install the static framework instead:
-1. Open the project editor and select your application target. Drag `Mapbox.framework` from the `static` folder into the “Embedded Binaries” section of the General tab. (Don’t drag it into the “Linked Frameworks and Libraries” section; Xcode will add it there automatically.) In the sheet that appears, make sure “Copy items if needed” is checked, then click Finish.
+1. Drag the Mapbox.bundle and Mapbox.framework files in `static` into the Project navigator, checking "Copy items if needed". It should happen automatically, but ensure that:
+
+ - `Mapbox.framework` is listed in your `Link Binary With Libraries` build phase.
+ - Your *Framework Search Paths* (`FRAMEWORK_SEARCH_PATHS`) build setting includes the directory that contains `Mapbox.framework`. For most projects, the default value of `$(inherited) $(PROJECT_DIR)` should be sufficient.
+ - `Mapbox.bundle` is in your target's *Copy Bundle Resources* build phase.
+
+1. **Optional** As [noted in the documentation](https://www.mapbox.com/ios-sdk/#telemetry_opt_out) you may use the provided Settings.bundle to provide a Telemetry opt out for users. To use the provided file, drag the `Settings.bundle` file from the release root folder into the Project navigator, checking "Copy items if needed". It should happen automatically, but ensure that:
+
+ - `Settings.bundle` is in your target's *Copy Bundle Resources* build phase.
1. Add the following Cocoa Touch frameworks and libraries to the “Linked Frameworks and Libraries” section:
diff --git a/platform/ios/include/Mapbox.h b/platform/ios/include/Mapbox.h
index fd4f532a40..fb25a0104e 100644
--- a/platform/ios/include/Mapbox.h
+++ b/platform/ios/include/Mapbox.h
@@ -8,11 +8,15 @@
#import "MGLMapView+IBAdditions.h"
#import "MGLMapView+MGLCustomStyleLayerAdditions.h"
#import "MGLMultiPoint.h"
+#import "MGLOfflinePack.h"
+#import "MGLOfflineRegion.h"
+#import "MGLOfflineStorage.h"
#import "MGLOverlay.h"
#import "MGLPointAnnotation.h"
#import "MGLPolygon.h"
#import "MGLPolyline.h"
#import "MGLShape.h"
#import "MGLStyle.h"
+#import "MGLTilePyramidOfflineRegion.h"
#import "MGLTypes.h"
#import "MGLUserLocation.h"
diff --git a/platform/ios/scripts/document.sh b/platform/ios/scripts/document.sh
index e52176b08b..4890aca15c 100755
--- a/platform/ios/scripts/document.sh
+++ b/platform/ios/scripts/document.sh
@@ -27,7 +27,7 @@ README=/tmp/mbgl/README.md
cp platform/ios/docs/doc-README.md "${README}"
# http://stackoverflow.com/a/4858011/4585461
echo "## Changes in version ${RELEASE_VERSION}" >> "${README}"
-sed -n -e '/^## iOS/{' -e ':a' -e 'n' -e '/^##/q' -e 'p' -e 'ba' -e '}' CHANGELOG.md >> "${README}"
+sed -n -e '/^## /{' -e ':a' -e 'n' -e '/^##/q' -e 'p' -e 'ba' -e '}' platform/ios/CHANGELOG.md >> "${README}"
rm -rf ${OUTPUT}
mkdir -p ${OUTPUT}
diff --git a/platform/ios/scripts/package.sh b/platform/ios/scripts/package.sh
index 5d84ba5e27..137775ef71 100755
--- a/platform/ios/scripts/package.sh
+++ b/platform/ios/scripts/package.sh
@@ -27,13 +27,28 @@ if [[ ${BUNDLE_RESOURCES} ]]; then
BUNDLE_PATH="/${NAME}.bundle"
fi
+PLACE_RESOURCE_BUNDLES_OUTSIDE_FRAMEWORK=${PLACE_RESOURCE_BUNDLES_OUTSIDE_FRAMEWORK:-}
+STATIC_BUNDLE_PATH=
+if [[ ${PLACE_RESOURCE_BUNDLES_OUTSIDE_FRAMEWORK} ]]; then
+ STATIC_BUNDLE_PATH="${OUTPUT}/static${BUNDLE_PATH}"
+else
+ STATIC_BUNDLE_PATH="${OUTPUT}/static/${NAME}.framework${BUNDLE_PATH}"
+fi
+
+STATIC_SETTINGS_DIRECTORY=
+if [[ ${PLACE_RESOURCE_BUNDLES_OUTSIDE_FRAMEWORK} ]]; then
+ STATIC_SETTINGS_DIRECTORY="${OUTPUT}"
+else
+ STATIC_SETTINGS_DIRECTORY="${OUTPUT}/static/${NAME}.framework"
+fi
+
SDK=iphonesimulator
if [[ ${BUILD_FOR_DEVICE} == true ]]; then
SDK=iphoneos
fi
IOS_SDK_VERSION=`xcrun --sdk ${SDK} --show-sdk-version`
-echo "Configuring ${FORMAT:-dynamic and static} ${BUILDTYPE} framework for ${SDK}; symbols: ${GCC_GENERATE_DEBUGGING_SYMBOLS}; Bitcode: ${ENABLE_BITCODE}; Mapbox.bundle: ${BUNDLE_RESOURCES}"
+echo "Configuring ${FORMAT:-dynamic and static} ${BUILDTYPE} framework for ${SDK}; symbols: ${GCC_GENERATE_DEBUGGING_SYMBOLS}; Bitcode: ${ENABLE_BITCODE}; Mapbox.bundle: ${BUNDLE_RESOURCES} bundle.outside: ${PLACE_RESOURCE_BUNDLES_OUTSIDE_FRAMEWORK}"
function step { >&2 echo -e "\033[1m\033[36m* $@\033[0m"; }
function finish { >&2 echo -en "\033[0m"; }
@@ -147,6 +162,8 @@ if [[ "${BUILD_FOR_DEVICE}" == true ]]; then
cp -r \
gyp/build/${BUILDTYPE}-iphoneos/${NAME}.framework \
${OUTPUT}/dynamic/
+ cp -r gyp/build/${BUILDTYPE}-iphoneos/${NAME}.framework.dSYM \
+ ${OUTPUT}/dynamic/
step "Merging simulator dynamic library into device dynamic library…"
lipo \
@@ -170,6 +187,8 @@ else
cp -r \
gyp/build/${BUILDTYPE}-iphonesimulator/${NAME}.framework \
${OUTPUT}/dynamic/${NAME}.framework
+ cp -r gyp/build/${BUILDTYPE}-iphonesimulator/${NAME}.framework.dSYM \
+ ${OUTPUT}/dynamic/
fi
fi
@@ -202,18 +221,19 @@ step "Copying library resources…"
SEM_VERSION=$( git describe --tags --match=ios-v*.*.* --abbrev=0 | sed 's/^ios-v//' )
SHORT_VERSION=${SEM_VERSION%-*}
if [[ ${BUNDLE_RESOURCES} ]]; then
- cp -pv LICENSE.md "${OUTPUT}/static/${NAME}.framework"
- cp -rv platform/ios/app/Settings.bundle "${OUTPUT}/static/${NAME}.framework"
+ cp -pv LICENSE.md ${STATIC_SETTINGS_DIRECTORY}
+ cp -rv platform/ios/app/Settings.bundle ${STATIC_SETTINGS_DIRECTORY}
else
cp -pv LICENSE.md "${OUTPUT}"
cp -rv platform/ios/app/Settings.bundle "${OUTPUT}"
fi
if [[ ${BUILD_STATIC} == true ]]; then
- mkdir -p "${OUTPUT}/static/${NAME}.framework${BUNDLE_PATH}"
- cp -pv platform/ios/resources/* "${OUTPUT}/static/${NAME}.framework${BUNDLE_PATH}"
+ mkdir -p ${STATIC_BUNDLE_PATH}
+ cp -pv platform/{default,ios}/resources/* ${STATIC_BUNDLE_PATH}
INFO_PLIST_PATH="${OUTPUT}/static/${NAME}.framework/Info.plist"
cp -pv platform/ios/framework/Info.plist "${INFO_PLIST_PATH}"
- plutil -replace CFBundleExecutable -string ${NAME} "${INFO_PLIST_PATH}"
+ plutil -remove CFBundleExecutable "${INFO_PLIST_PATH}"
+ plutil -remove CFBundlePackageType "${INFO_PLIST_PATH}"
plutil -replace CFBundleIdentifier -string com.mapbox.sdk.ios "${INFO_PLIST_PATH}"
plutil -replace CFBundleName -string ${NAME} "${INFO_PLIST_PATH}"
plutil -replace CFBundleShortVersionString -string "${SHORT_VERSION}" "${INFO_PLIST_PATH}"
@@ -221,7 +241,7 @@ if [[ ${BUILD_STATIC} == true ]]; then
plutil -replace MGLSemanticVersionString -string "${SEM_VERSION}" "${INFO_PLIST_PATH}"
plutil -replace MGLCommitHash -string "${HASH}" "${INFO_PLIST_PATH}"
if [[ ${BUNDLE_RESOURCES} ]]; then
- cp -pv "${INFO_PLIST_PATH}" "${OUTPUT}/static/${NAME}.framework${BUNDLE_PATH}/Info.plist"
+ cp -pv "${INFO_PLIST_PATH}" "${STATIC_BUNDLE_PATH}/Info.plist"
fi
mkdir "${OUTPUT}/static/${NAME}.framework/Modules"
cp -pv platform/ios/framework/modulemap "${OUTPUT}/static/${NAME}.framework/Modules/module.modulemap"
@@ -233,7 +253,7 @@ if [[ ${BUILD_DYNAMIC} == true ]]; then
plutil -replace MGLCommitHash -string "${HASH}" "${OUTPUT}/dynamic/${NAME}.framework/Info.plist"
cp -pv platform/ios/framework/strip-frameworks.sh "${OUTPUT}/dynamic/${NAME}.framework/strip-frameworks.sh"
fi
-sed -n -e '/^## iOS/,$p' CHANGELOG.md > "${OUTPUT}/CHANGELOG.md"
+sed -n -e '/^## /,$p' platform/ios/CHANGELOG.md > "${OUTPUT}/CHANGELOG.md"
rm -rf /tmp/mbgl
mkdir -p /tmp/mbgl/
diff --git a/platform/ios/src/MGLAPIClient.m b/platform/ios/src/MGLAPIClient.m
index 7a72496ac0..91bc074425 100644
--- a/platform/ios/src/MGLAPIClient.m
+++ b/platform/ios/src/MGLAPIClient.m
@@ -115,7 +115,7 @@ static NSString * const MGLAPIClientHTTPMethodPost = @"POST";
}
- (void)setupUserAgent {
- NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
+ NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"];
NSString *appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
NSString *appBuildNumber = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
NSString *semanticVersion = [NSBundle mgl_frameworkInfoDictionary][@"MGLSemanticVersionString"];
diff --git a/platform/ios/src/MGLLocationManager.m b/platform/ios/src/MGLLocationManager.m
index 7a3f8de4ff..b5740e3547 100644
--- a/platform/ios/src/MGLLocationManager.m
+++ b/platform/ios/src/MGLLocationManager.m
@@ -1,9 +1,10 @@
#import "MGLLocationManager.h"
#import <UIKit/UIKit.h>
-static const NSTimeInterval fiveMinuteTimeInterval = 300.0;
-static const NSTimeInterval fiveSecondTimeInterval = 5.0;
-static const CLLocationDistance regionRadiusLocationDistance = 300.0;
+static const NSTimeInterval MGLLocationManagerHibernationTimeout = 300.0;
+static const NSTimeInterval MGLLocationManagerHibernationPollInterval = 5.0;
+static const CLLocationDistance MGLLocationManagerHibernationRadius = 300.0;
+static const CLLocationDistance MGLLocationManagerDistanceFilter = 5.0;
static NSString * const MGLLocationManagerRegionIdentifier = @"MGLLocationManagerRegionIdentifier.fence.center";
@interface MGLLocationManager ()
@@ -54,7 +55,7 @@ static NSString * const MGLLocationManagerRegionIdentifier = @"MGLLocationManage
CLLocationManager *standardLocationManager = [[CLLocationManager alloc] init];
standardLocationManager.delegate = self;
standardLocationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
- standardLocationManager.distanceFilter = 1;
+ standardLocationManager.distanceFilter = MGLLocationManagerDistanceFilter;
self.standardLocationManager = standardLocationManager;
}
}
@@ -105,12 +106,12 @@ static NSString * const MGLLocationManagerRegionIdentifier = @"MGLLocationManage
- (void)startBackgroundTimeoutTimer {
[self.backgroundLocationServiceTimeoutTimer invalidate];
- self.backgroundLocationServiceTimeoutAllowedDate = [[NSDate date] dateByAddingTimeInterval:fiveMinuteTimeInterval];
- self.backgroundLocationServiceTimeoutTimer = [NSTimer scheduledTimerWithTimeInterval:fiveSecondTimeInterval target:self selector:@selector(timeoutAllowedCheck) userInfo:nil repeats:YES];
+ self.backgroundLocationServiceTimeoutAllowedDate = [[NSDate date] dateByAddingTimeInterval:MGLLocationManagerHibernationTimeout];
+ self.backgroundLocationServiceTimeoutTimer = [NSTimer scheduledTimerWithTimeInterval:MGLLocationManagerHibernationPollInterval target:self selector:@selector(timeoutAllowedCheck) userInfo:nil repeats:YES];
}
- (void)establishRegionMonitoringForLocation:(CLLocation *)location {
- CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:location.coordinate radius:regionRadiusLocationDistance identifier:MGLLocationManagerRegionIdentifier];
+ CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:location.coordinate radius:MGLLocationManagerHibernationRadius identifier:MGLLocationManagerRegionIdentifier];
region.notifyOnEntry = NO;
region.notifyOnExit = YES;
[self.standardLocationManager startMonitoringForRegion:region];
@@ -135,7 +136,7 @@ static NSString * const MGLLocationManagerRegionIdentifier = @"MGLLocationManage
if (location.speed > 0.0) {
[self startBackgroundTimeoutTimer];
}
- if (self.standardLocationManager.monitoredRegions.count == 0 || location.horizontalAccuracy < regionRadiusLocationDistance) {
+ if (self.standardLocationManager.monitoredRegions.count == 0 || location.horizontalAccuracy < MGLLocationManagerHibernationRadius) {
[self establishRegionMonitoringForLocation:location];
}
if ([self.delegate respondsToSelector:@selector(locationManager:didUpdateLocations:)]) {
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
index 935f28b473..101f678e69 100644
--- a/platform/ios/src/MGLMapView.mm
+++ b/platform/ios/src/MGLMapView.mm
@@ -31,6 +31,7 @@
#import "Mapbox.h"
#import "../../darwin/src/MGLGeometry_Private.h"
#import "../../darwin/src/MGLMultiPoint_Private.h"
+#import "../../darwin/src/MGLOfflineStorage_Private.h"
#import "NSBundle+MGLAdditions.h"
#import "NSString+MGLAdditions.h"
@@ -38,7 +39,6 @@
#import "NSException+MGLAdditions.h"
#import "MGLUserLocationAnnotationView.h"
#import "MGLUserLocation_Private.h"
-#import "MGLAccountManager_Private.h"
#import "MGLAnnotationImage_Private.h"
#import "MGLMapboxEvents.h"
#import "MGLCompactCalloutView.h"
@@ -188,7 +188,6 @@ public:
{
mbgl::Map *_mbglMap;
MBGLView *_mbglView;
- mbgl::DefaultFileSource *_mbglFileSource;
BOOL _opaque;
@@ -213,6 +212,10 @@ public:
NSUInteger _changeDelimiterSuppressionDepth;
+ /// Center coordinate of the pinch gesture on the previous iteration of the gesture.
+ CLLocationCoordinate2D _previousPinchCenterCoordinate;
+ NSUInteger _previousPinchNumberOfTouches;
+
BOOL _delegateHasAlphasForShapeAnnotations;
BOOL _delegateHasStrokeColorsForShapeAnnotations;
BOOL _delegateHasFillColorsForShapeAnnotations;
@@ -304,18 +307,15 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
// setup mbgl view
const float scaleFactor = [UIScreen instancesRespondToSelector:@selector(nativeScale)] ? [[UIScreen mainScreen] nativeScale] : [[UIScreen mainScreen] scale];
_mbglView = new MBGLView(self, scaleFactor);
-
- // setup mbgl cache & file source
- NSString *fileCachePath = @"";
+
+ // Delete the pre-offline ambient cache at ~/Library/Caches/cache.db.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
- if ([paths count] != 0) {
- NSString *libraryDirectory = [paths objectAtIndex:0];
- fileCachePath = [libraryDirectory stringByAppendingPathComponent:@"cache.db"];
- }
- _mbglFileSource = new mbgl::DefaultFileSource([fileCachePath UTF8String], [[[[NSBundle mainBundle] resourceURL] path] UTF8String]);
+ NSString *fileCachePath = [paths.firstObject stringByAppendingPathComponent:@"cache.db"];
+ [[NSFileManager defaultManager] removeItemAtPath:fileCachePath error:NULL];
// setup mbgl map
- _mbglMap = new mbgl::Map(*_mbglView, *_mbglFileSource, mbgl::MapMode::Continuous, mbgl::GLContextMode::Unique, mbgl::ConstrainMode::None);
+ mbgl::DefaultFileSource *mbglFileSource = [MGLOfflineStorage sharedOfflineStorage].mbglFileSource;
+ _mbglMap = new mbgl::Map(*_mbglView, *mbglFileSource, mbgl::MapMode::Continuous, mbgl::GLContextMode::Unique, mbgl::ConstrainMode::None);
// start paused if in IB
if (_isTargetingInterfaceBuilder || background) {
@@ -323,13 +323,6 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
_mbglMap->pause();
}
- // Observe for changes to the global access token (and find out the current one).
- [[MGLAccountManager sharedManager] addObserver:self
- forKeyPath:@"accessToken"
- options:(NSKeyValueObservingOptionInitial |
- NSKeyValueObservingOptionNew)
- context:NULL];
-
// Notify map object when network reachability status changes.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reachabilityChanged:)
@@ -451,7 +444,9 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
_pendingLongitude = NAN;
_targetCoordinate = kCLLocationCoordinate2DInvalid;
- [MGLMapboxEvents pushEvent:MGLEventTypeMapLoad withAttributes:@{}];
+ if ([UIApplication sharedApplication].applicationState != UIApplicationStateBackground) {
+ [MGLMapboxEvents pushEvent:MGLEventTypeMapLoad withAttributes:@{}];
+ }
}
- (void)createGLView
@@ -507,7 +502,6 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
- [[MGLAccountManager sharedManager] removeObserver:self forKeyPath:@"accessToken"];
[_attributionButton removeObserver:self forKeyPath:@"hidden"];
[self validateDisplayLink];
@@ -518,12 +512,6 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
_mbglMap = nullptr;
}
- if (_mbglFileSource)
- {
- delete _mbglFileSource;
- _mbglFileSource = nullptr;
- }
-
if (_mbglView)
{
delete _mbglView;
@@ -827,6 +815,11 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
- viewController.bottomLayoutGuide.length);
contentInset.bottom = (CGRectGetMaxY(self.bounds)
- [self convertPoint:bottomPoint fromView:viewController.view].y);
+
+ // Negative insets are invalid, replace with 0.
+ contentInset.top = fmaxf(contentInset.top, 0);
+ contentInset.bottom = fmaxf(contentInset.bottom, 0);
+
self.contentInset = contentInset;
}
@@ -1001,6 +994,8 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
_displayLink.paused = NO;
[self validateLocationServices];
+
+ [MGLMapboxEvents pushEvent:MGLEventTypeMapLoad withAttributes:@{}];
}
}
@@ -1148,6 +1143,17 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
if (log2(newScale) < _mbglMap->getMinZoom()) return;
_mbglMap->setScale(newScale, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
+
+ // The gesture recognizer only reports the gesture’s current center
+ // point, so use the previous center point to anchor the transition.
+ // If the number of touches has changed, the remembered center point is
+ // meaningless.
+ if (self.userTrackingMode == MGLUserTrackingModeNone && pinch.numberOfTouches == _previousPinchNumberOfTouches)
+ {
+ CLLocationCoordinate2D centerCoordinate = _previousPinchCenterCoordinate;
+ _mbglMap->setLatLng(MGLLatLngFromLocationCoordinate2D(centerCoordinate),
+ mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
+ }
[self notifyMapChange:mbgl::MapChangeRegionIsChanging];
}
@@ -1191,6 +1197,9 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
[self unrotateIfNeededForGesture];
}
+
+ _previousPinchCenterCoordinate = [self convertPoint:[pinch locationInView:pinch.view] toCoordinateFromView:self];
+ _previousPinchNumberOfTouches = pinch.numberOfTouches;
}
- (void)handleRotateGesture:(UIRotationGestureRecognizer *)rotate
@@ -1607,15 +1616,7 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(__unused void *)context
{
- // Synchronize mbgl::Map’s access token with the global one in MGLAccountManager.
- if ([keyPath isEqualToString:@"accessToken"] && object == [MGLAccountManager sharedManager])
- {
- NSString *accessToken = change[NSKeyValueChangeNewKey];
- if (![accessToken isKindOfClass:[NSNull class]]) {
- _mbglFileSource->setAccessToken((std::string)[accessToken UTF8String]);
- }
- }
- else if ([keyPath isEqualToString:@"hidden"] && object == _attributionButton)
+ if ([keyPath isEqualToString:@"hidden"] && object == _attributionButton)
{
NSNumber *hiddenNumber = change[NSKeyValueChangeNewKey];
BOOL attributionButtonWasHidden = [hiddenNumber boolValue];
@@ -2155,7 +2156,7 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
- (mbgl::LatLng)convertPoint:(CGPoint)point toLatLngFromView:(nullable UIView *)view
{
CGPoint convertedPoint = [self convertPoint:point fromView:view];
- return _mbglMap->latLngForPixel(mbgl::ScreenCoordinate(convertedPoint.x, convertedPoint.y));
+ return _mbglMap->latLngForPixel(mbgl::ScreenCoordinate(convertedPoint.x, convertedPoint.y)).wrapped();
}
- (CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(nullable UIView *)view
@@ -2969,9 +2970,9 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
- (void)showAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations animated:(BOOL)animated
{
- CGFloat defaultPadding = 100;
- CGFloat yPadding = (self.frame.size.height / 2 <= defaultPadding) ? (self.frame.size.height / 5) : defaultPadding;
- CGFloat xPadding = (self.frame.size.width / 2 <= defaultPadding) ? (self.frame.size.width / 5) : defaultPadding;
+ CGFloat maximumPadding = 100;
+ CGFloat yPadding = (self.frame.size.height / 5 <= maximumPadding) ? (self.frame.size.height / 5) : maximumPadding;
+ CGFloat xPadding = (self.frame.size.width / 5 <= maximumPadding) ? (self.frame.size.width / 5) : maximumPadding;
UIEdgeInsets edgeInsets = UIEdgeInsetsMake(yPadding, xPadding, yPadding, xPadding);
diff --git a/platform/ios/src/MGLMapboxEvents.m b/platform/ios/src/MGLMapboxEvents.m
index f5628bc6fb..13540bfd08 100644
--- a/platform/ios/src/MGLMapboxEvents.m
+++ b/platform/ios/src/MGLMapboxEvents.m
@@ -122,7 +122,8 @@ const NSTimeInterval MGLFlushInterval = 180;
@property (nonatomic) dispatch_queue_t debugLogSerialQueue;
@property (nonatomic) MGLLocationManager *locationManager;
@property (nonatomic) NSTimer *timer;
-@property (nonatomic) NSDate *lastInstanceIDRotationDate;
+@property (nonatomic) NSDate *instanceIDRotationDate;
+@property (nonatomic) NSDate *turnstileSendDate;
@end
@@ -233,13 +234,13 @@ const NSTimeInterval MGLFlushInterval = 180;
}
- (NSString *)instanceID {
- if (self.lastInstanceIDRotationDate && [[NSDate date] timeIntervalSinceDate:self.lastInstanceIDRotationDate] >= 0) {
+ if (self.instanceIDRotationDate && [[NSDate date] timeIntervalSinceDate:self.instanceIDRotationDate] >= 0) {
_instanceID = nil;
}
if (!_instanceID) {
_instanceID = [[NSUUID UUID] UUIDString];
NSTimeInterval twentyFourHourTimeInterval = 24 * 3600;
- self.lastInstanceIDRotationDate = [[NSDate date] dateByAddingTimeInterval:twentyFourHourTimeInterval];
+ self.instanceIDRotationDate = [[NSDate date] dateByAddingTimeInterval:twentyFourHourTimeInterval];
}
return _instanceID;
}
@@ -314,38 +315,36 @@ const NSTimeInterval MGLFlushInterval = 180;
}
- (void)pushTurnstileEvent {
- __weak MGLMapboxEvents *weakSelf = self;
+ if (self.turnstileSendDate && [[NSDate date] timeIntervalSinceDate:self.turnstileSendDate] < 0) {
+ return;
+ }
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- MGLMapboxEvents *strongSelf = weakSelf;
-
- if (!strongSelf) {
- return;
- }
-
- NSString *vendorID = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
- if (!vendorID) {
- return;
- }
-
- NSDictionary *turnstileEventAttributes = @{MGLEventKeyEvent: MGLEventTypeAppUserTurnstile,
- MGLEventKeyCreated: [strongSelf.rfc3339DateFormatter stringFromDate:[NSDate date]],
- MGLEventKeyVendorID: vendorID,
- MGLEventKeyEnabledTelemetry: @([[strongSelf class] isEnabled])};
-
- if ([MGLAccountManager accessToken] == nil) {
+ NSString *vendorID = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
+ if (!vendorID) {
+ return;
+ }
+
+ NSDictionary *turnstileEventAttributes = @{MGLEventKeyEvent: MGLEventTypeAppUserTurnstile,
+ MGLEventKeyCreated: [self.rfc3339DateFormatter stringFromDate:[NSDate date]],
+ MGLEventKeyVendorID: vendorID,
+ MGLEventKeyEnabledTelemetry: @([[self class] isEnabled])};
+
+ if ([MGLAccountManager accessToken] == nil) {
+ return;
+ }
+
+ __weak __typeof__(self) weakSelf = self;
+ [self.apiClient postEvent:turnstileEventAttributes completionHandler:^(NSError * _Nullable error) {
+ __strong __typeof__(weakSelf) strongSelf = weakSelf;
+ if (error) {
+ [strongSelf pushDebugEvent:MGLEventTypeLocalDebug withAttributes:@{MGLEventKeyLocalDebugDescription: @"Network error",
+ @"error": error}];
return;
}
- [strongSelf.apiClient postEvent:turnstileEventAttributes completionHandler:^(NSError * _Nullable error) {
- if (error) {
- [strongSelf pushDebugEvent:MGLEventTypeLocalDebug withAttributes:@{MGLEventKeyLocalDebugDescription: @"Network error",
- @"error": error}];
- return;
- }
- [strongSelf writeEventToLocalDebugLog:turnstileEventAttributes];
- }];
- });
+ [strongSelf writeEventToLocalDebugLog:turnstileEventAttributes];
+ NSTimeInterval twentyFourHourTimeInterval = 24 * 3600;
+ strongSelf.turnstileSendDate = [[NSDate date] dateByAddingTimeInterval:twentyFourHourTimeInterval];
+ }];
}
+ (void)pushEvent:(NSString *)event withAttributes:(MGLMapboxEventAttributes *)attributeDictionary {
diff --git a/platform/ios/test/MGLTViewController.h b/platform/ios/test/MGLTViewController.h
index 0be0e1ff2c..349c216008 100644
--- a/platform/ios/test/MGLTViewController.h
+++ b/platform/ios/test/MGLTViewController.h
@@ -3,6 +3,7 @@
@interface MGLTViewController : UIViewController
- (void)insetMapView;
+- (void)tinyMapView;
- (void)resetMapView;
@end
diff --git a/platform/ios/test/MGLTViewController.m b/platform/ios/test/MGLTViewController.m
index 09c60bf614..451dea9292 100644
--- a/platform/ios/test/MGLTViewController.m
+++ b/platform/ios/test/MGLTViewController.m
@@ -21,6 +21,11 @@
_mapView.frame = CGRectInset(_mapView.frame, 50, 50);
}
+- (void)tinyMapView
+{
+ _mapView.frame = CGRectMake(20, self.topLayoutGuide.length, self.view.frame.size.width / 2, self.view.frame.size.height / 2);
+}
+
- (void)resetMapView
{
_mapView.frame = self.view.bounds;
diff --git a/platform/ios/test/MapViewTests.m b/platform/ios/test/MapViewTests.m
index 40022a1ee5..c8fe862e0a 100644
--- a/platform/ios/test/MapViewTests.m
+++ b/platform/ios/test/MapViewTests.m
@@ -424,6 +424,29 @@
@"compass should lie inside shrunken map view");
}
+- (void)testContentInsetsWithTinyMapView {
+ [tester.viewController tinyMapView];
+ [self keyValueObservingExpectationForObject:tester.mapView keyPath:@"contentInset" handler:^BOOL(id observedObject, NSDictionary *change) {
+ XCTAssertEqual(tester.mapView.contentInset.top,
+ 0,
+ @"map should not have top content inset");
+ XCTAssertEqual(tester.mapView.contentInset.bottom,
+ 0,
+ @"map should not have bottom content inset");
+ return YES;
+ }];
+ [self waitForExpectationsWithTimeout:2.0 handler:nil];
+
+ tester.mapView.frame = CGRectMake(0, 0, tester.mapView.frame.size.width, tester.mapView.frame.size.height);
+ [self keyValueObservingExpectationForObject:tester.mapView keyPath:@"contentInset" handler:^BOOL(id observedObject, NSDictionary *change) {
+ XCTAssertEqual(tester.mapView.contentInset.top,
+ tester.viewController.topLayoutGuide.length,
+ @"map should have top content inset equal to the top layout guide");
+ return YES;
+ }];
+ [self waitForExpectationsWithTimeout:2.0 handler:nil];
+}
+
- (void)testDelegateRegionWillChange {
__block NSUInteger unanimatedCount;
__block NSUInteger animatedCount;
diff --git a/platform/osx/app/AppDelegate.h b/platform/osx/app/AppDelegate.h
index 45d389f546..ca9edab773 100644
--- a/platform/osx/app/AppDelegate.h
+++ b/platform/osx/app/AppDelegate.h
@@ -15,8 +15,8 @@ extern NSString * const MGLMapboxAccessTokenDefaultsKey;
@property (assign) double pendingZoomLevel;
@property (copy) MGLMapCamera *pendingCamera;
+@property (assign) MGLCoordinateBounds pendingVisibleCoordinateBounds;
@property (copy) NSURL *pendingStyleURL;
@property (assign) MGLMapDebugMaskOptions pendingDebugMask;
@end
-
diff --git a/platform/osx/app/AppDelegate.m b/platform/osx/app/AppDelegate.m
index b54ab4fe74..d9fd967410 100644
--- a/platform/osx/app/AppDelegate.m
+++ b/platform/osx/app/AppDelegate.m
@@ -7,8 +7,59 @@ NSString * const MGLLastMapCameraDefaultsKey = @"MGLLastMapCamera";
NSString * const MGLLastMapStyleURLDefaultsKey = @"MGLLastMapStyleURL";
NSString * const MGLLastMapDebugMaskDefaultsKey = @"MGLLastMapDebugMask";
+/**
+ Some convenience methods to make offline pack properties easier to bind to.
+ */
+@implementation MGLOfflinePack (Additions)
+
++ (NSSet *)keyPathsForValuesAffectingStateImage {
+ return [NSSet setWithObjects:@"state", nil];
+}
+
+- (NSImage *)stateImage {
+ switch (self.state) {
+ case MGLOfflinePackStateComplete:
+ return [NSImage imageNamed:@"NSMenuOnStateTemplate"];
+
+ case MGLOfflinePackStateActive:
+ return [NSImage imageNamed:@"NSFollowLinkFreestandingTemplate"];
+
+ default:
+ return nil;
+ }
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCountOfResourcesCompleted {
+ return [NSSet setWithObjects:@"progress", nil];
+}
+
+- (uint64_t)countOfResourcesCompleted {
+ return self.progress.countOfResourcesCompleted;
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCountOfResourcesExpected {
+ return [NSSet setWithObjects:@"progress", nil];
+}
+
+- (uint64_t)countOfResourcesExpected {
+ return self.progress.countOfResourcesExpected;
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCountOfBytesCompleted {
+ return [NSSet setWithObjects:@"progress", nil];
+}
+
+- (uint64_t)countOfBytesCompleted {
+ return self.progress.countOfBytesCompleted;
+}
+
+@end
+
@interface AppDelegate ()
+@property (weak) IBOutlet NSArrayController *offlinePacksArrayController;
+@property (weak) IBOutlet NSPanel *offlinePacksPanel;
+
@end
@implementation AppDelegate
@@ -63,6 +114,8 @@ NSString * const MGLLastMapDebugMaskDefaultsKey = @"MGLLastMapDebugMask";
[alert runModal];
[self showPreferences:nil];
}
+
+ [self.offlinePacksArrayController bind:@"content" toObject:[MGLOfflineStorage sharedOfflineStorage] withKeyPath:@"packs" options:nil];
}
- (void)applicationWillTerminate:(NSNotification *)notification {
@@ -79,6 +132,8 @@ NSString * const MGLLastMapDebugMaskDefaultsKey = @"MGLLastMapDebugMask";
[[NSUserDefaults standardUserDefaults] setInteger:mapView.debugMask forKey:MGLLastMapDebugMaskDefaultsKey];
}
}
+
+ [self.offlinePacksArrayController unbind:@"content"];
}
#pragma mark Services
@@ -121,6 +176,53 @@ NSString * const MGLLastMapDebugMaskDefaultsKey = @"MGLLastMapDebugMask";
[[NSDocumentController sharedDocumentController] openUntitledDocumentAndDisplay:YES error:NULL];
}
+#pragma mark Offline pack management
+
+- (IBAction)showOfflinePacksPanel:(id)sender {
+ [self.offlinePacksPanel makeKeyAndOrderFront:sender];
+
+ for (MGLOfflinePack *pack in self.offlinePacksArrayController.arrangedObjects) {
+ [pack requestProgress];
+ }
+}
+
+- (IBAction)delete:(id)sender {
+ for (MGLOfflinePack *pack in self.offlinePacksArrayController.selectedObjects) {
+ [[MGLOfflineStorage sharedOfflineStorage] removePack:pack withCompletionHandler:^(NSError * _Nullable error) {
+ if (error) {
+ [[NSAlert alertWithError:error] runModal];
+ }
+ }];
+ }
+}
+
+- (IBAction)chooseOfflinePack:(id)sender {
+ for (MGLOfflinePack *pack in self.offlinePacksArrayController.selectedObjects) {
+ switch (pack.state) {
+ case MGLOfflinePackStateComplete:
+ {
+ if ([pack.region isKindOfClass:[MGLTilePyramidOfflineRegion class]]) {
+ MGLTilePyramidOfflineRegion *region = (MGLTilePyramidOfflineRegion *)pack.region;
+ self.pendingVisibleCoordinateBounds = region.bounds;
+ [[NSDocumentController sharedDocumentController] openUntitledDocumentAndDisplay:YES error:NULL];
+ }
+ break;
+ }
+
+ case MGLOfflinePackStateInactive:
+ [pack resume];
+ break;
+
+ case MGLOfflinePackStateActive:
+ [pack suspend];
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
#pragma mark Help methods
- (IBAction)showShortcuts:(id)sender {
@@ -154,6 +256,12 @@ NSString * const MGLLastMapDebugMaskDefaultsKey = @"MGLLastMapDebugMask";
if (menuItem.action == @selector(showPreferences:)) {
return YES;
}
+ if (menuItem.action == @selector(showOfflinePacksPanel:)) {
+ return YES;
+ }
+ if (menuItem.action == @selector(delete:)) {
+ return self.offlinePacksArrayController.selectedObjects.count;
+ }
return NO;
}
diff --git a/platform/osx/app/MainMenu.xib b/platform/osx/app/MainMenu.xib
index 64ff4e550d..646c4ae40d 100644
--- a/platform/osx/app/MainMenu.xib
+++ b/platform/osx/app/MainMenu.xib
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10109" systemVersion="15E39d" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10116" systemVersion="15E65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
- <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10109"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10116"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@@ -17,6 +17,8 @@
</customObject>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate">
<connections>
+ <outlet property="offlinePacksArrayController" destination="dWe-R6-sRz" id="Ar5-xu-ABm"/>
+ <outlet property="offlinePacksPanel" destination="Jjv-gs-Tx6" id="0vK-rR-3ZX"/>
<outlet property="preferencesWindow" destination="UWc-yQ-qda" id="Ota-aT-Mz2"/>
</connections>
</customObject>
@@ -113,6 +115,12 @@
<action selector="saveDocumentAs:" target="-1" id="mDf-zr-I0C"/>
</connections>
</menuItem>
+ <menuItem title="Save Offline Pack…" id="UXB-sj-C7i">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="addOfflinePack:" target="-1" id="Usu-xO-QEx"/>
+ </connections>
+ </menuItem>
<menuItem title="Revert to Saved" id="KaW-ft-85H">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
@@ -516,6 +524,13 @@
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
</connections>
</menuItem>
+ <menuItem isSeparatorItem="YES" id="Uix-g7-fAt"/>
+ <menuItem title="Offline Packs" id="YW3-jR-knj">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="showOfflinePacksPanel:" target="Voe-Tx-rLC" id="kj9-ht-KmF"/>
+ </connections>
+ </menuItem>
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
<modifierMask key="keyEquivalentModifierMask"/>
@@ -551,7 +566,7 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="109" y="131" width="350" height="62"/>
- <rect key="screenRect" x="0.0" y="0.0" width="1680" height="1050"/>
+ <rect key="screenRect" x="0.0" y="0.0" width="1280" height="777"/>
<view key="contentView" id="eA4-n3-qPe">
<rect key="frame" x="0.0" y="0.0" width="350" height="62"/>
<autoresizingMask key="autoresizingMask"/>
@@ -609,8 +624,190 @@
<point key="canvasLocation" x="754" y="210"/>
</window>
<userDefaultsController representsSharedInstance="YES" id="45S-yT-WUN"/>
+ <window title="Offline Packs" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="MBXOfflinePacksPanel" animationBehavior="default" id="Jjv-gs-Tx6" customClass="NSPanel">
+ <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES" utility="YES"/>
+ <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
+ <rect key="contentRect" x="830" y="430" width="400" height="300"/>
+ <rect key="screenRect" x="0.0" y="0.0" width="1280" height="777"/>
+ <view key="contentView" id="8ha-hw-zOD">
+ <rect key="frame" x="0.0" y="0.0" width="400" height="300"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <scrollView autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Q8b-0e-dLv">
+ <rect key="frame" x="-1" y="20" width="402" height="281"/>
+ <clipView key="contentView" id="J9U-Yx-o2S">
+ <rect key="frame" x="1" y="0.0" width="400" height="280"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" autosaveColumns="NO" headerView="MAZ-Iq-hBi" id="Ato-Vu-HYT">
+ <rect key="frame" x="0.0" y="0.0" width="400" height="257"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <size key="intercellSpacing" width="3" height="2"/>
+ <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+ <color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
+ <tableColumns>
+ <tableColumn identifier="" editable="NO" width="16" minWidth="10" maxWidth="3.4028234663852886e+38" id="xtw-hQ-8C5" userLabel="State">
+ <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
+ <font key="font" metaFont="smallSystem"/>
+ <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
+ </tableHeaderCell>
+ <imageCell key="dataCell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="edU-Yw-20f"/>
+ <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+ <connections>
+ <binding destination="dWe-R6-sRz" name="value" keyPath="arrangedObjects.stateImage" id="2wd-1J-TZt"/>
+ </connections>
+ </tableColumn>
+ <tableColumn editable="NO" width="116" minWidth="40" maxWidth="1000" id="2hD-LN-h0L">
+ <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Name">
+ <font key="font" metaFont="smallSystem"/>
+ <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
+ </tableHeaderCell>
+ <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="oys-QZ-34I">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+ <connections>
+ <binding destination="dWe-R6-sRz" name="value" keyPath="arrangedObjects.context" id="NtD-s5-ZUq">
+ <dictionary key="options">
+ <string key="NSValueTransformerName">OfflinePackNameValueTransformer</string>
+ </dictionary>
+ </binding>
+ </connections>
+ </tableColumn>
+ <tableColumn editable="NO" width="50" minWidth="40" maxWidth="1000" id="pkI-c7-xoD">
+ <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Downloaded">
+ <font key="font" metaFont="smallSystem"/>
+ <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
+ </tableHeaderCell>
+ <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="WfC-qb-HsW">
+ <numberFormatter key="formatter" formatterBehavior="default10_4" numberStyle="decimal" usesGroupingSeparator="NO" groupingSize="0" minimumIntegerDigits="0" maximumIntegerDigits="42" id="sNm-Qn-ne6"/>
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+ <connections>
+ <binding destination="dWe-R6-sRz" name="value" keyPath="arrangedObjects.countOfResourcesCompleted" id="mu6-Jg-GiU"/>
+ </connections>
+ </tableColumn>
+ <tableColumn identifier="" editable="NO" width="50" minWidth="10" maxWidth="3.4028234663852886e+38" id="Rrd-A9-jqc">
+ <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Total">
+ <font key="font" metaFont="smallSystem"/>
+ <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
+ </tableHeaderCell>
+ <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="mHy-qJ-rOA">
+ <numberFormatter key="formatter" formatterBehavior="default10_4" numberStyle="decimal" usesGroupingSeparator="NO" groupingSize="0" minimumIntegerDigits="0" maximumIntegerDigits="42" id="kyx-ZP-OBH"/>
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+ <connections>
+ <binding destination="dWe-R6-sRz" name="value" keyPath="arrangedObjects.countOfResourcesExpected" id="mh2-k0-vvB"/>
+ </connections>
+ </tableColumn>
+ <tableColumn identifier="" editable="NO" width="60" minWidth="10" maxWidth="3.4028234663852886e+38" id="h7m-6l-KaS">
+ <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Size">
+ <font key="font" metaFont="smallSystem"/>
+ <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
+ </tableHeaderCell>
+ <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="701-bg-k6L">
+ <byteCountFormatter key="formatter" allowsNonnumericFormatting="NO" id="IXV-J9-sP3"/>
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+ <connections>
+ <binding destination="dWe-R6-sRz" name="value" keyPath="arrangedObjects.countOfBytesCompleted" id="Zsa-Na-yFN"/>
+ </connections>
+ </tableColumn>
+ </tableColumns>
+ <connections>
+ <action trigger="doubleAction" selector="chooseOfflinePack:" target="-1" id="pUN-eT-zRT"/>
+ </connections>
+ </tableView>
+ </subviews>
+ <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </clipView>
+ <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="QLr-6P-Ogs">
+ <rect key="frame" x="1" y="7" width="0.0" height="16"/>
+ <autoresizingMask key="autoresizingMask"/>
+ </scroller>
+ <scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="q0K-eE-mzL">
+ <rect key="frame" x="224" y="17" width="15" height="102"/>
+ <autoresizingMask key="autoresizingMask"/>
+ </scroller>
+ <tableHeaderView key="headerView" id="MAZ-Iq-hBi">
+ <rect key="frame" x="0.0" y="0.0" width="400" height="23"/>
+ <autoresizingMask key="autoresizingMask"/>
+ </tableHeaderView>
+ </scrollView>
+ <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="wzf-ce-Spm">
+ <rect key="frame" x="0.0" y="-1" width="21" height="21"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="21" id="5ST-tY-8Ph"/>
+ </constraints>
+ <buttonCell key="cell" type="smallSquare" bezelStyle="smallSquare" image="NSAddTemplate" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" state="on" imageScaling="proportionallyDown" inset="2" id="sew-F7-i5T">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="system"/>
+ </buttonCell>
+ <connections>
+ <action selector="addOfflinePack:" target="-1" id="SN0-PM-HoU"/>
+ </connections>
+ </button>
+ <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="7L7-hr-zId">
+ <rect key="frame" x="20" y="0.0" width="21" height="19"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="21" id="JYb-AF-8gZ"/>
+ </constraints>
+ <buttonCell key="cell" type="smallSquare" bezelStyle="smallSquare" image="NSRemoveTemplate" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" state="on" imageScaling="proportionallyDown" inset="2" id="oTF-3m-6qT">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="system"/>
+ <string key="keyEquivalent" base64-UTF8="YES">
+CA
+</string>
+ </buttonCell>
+ <connections>
+ <action selector="delete:" target="-1" id="EGL-bf-yUD"/>
+ </connections>
+ </button>
+ </subviews>
+ <constraints>
+ <constraint firstItem="7L7-hr-zId" firstAttribute="centerY" secondItem="wzf-ce-Spm" secondAttribute="centerY" id="7TI-6w-bf1"/>
+ <constraint firstAttribute="bottom" secondItem="Q8b-0e-dLv" secondAttribute="bottom" constant="20" symbolic="YES" id="DZa-ly-bhV"/>
+ <constraint firstItem="wzf-ce-Spm" firstAttribute="top" secondItem="Q8b-0e-dLv" secondAttribute="bottom" id="LhK-5z-CQA"/>
+ <constraint firstItem="Q8b-0e-dLv" firstAttribute="leading" secondItem="8ha-hw-zOD" secondAttribute="leading" constant="-1" id="Oyo-ch-rZo"/>
+ <constraint firstAttribute="bottom" secondItem="7L7-hr-zId" secondAttribute="bottom" id="TtY-j1-T5h"/>
+ <constraint firstItem="Q8b-0e-dLv" firstAttribute="top" secondItem="8ha-hw-zOD" secondAttribute="top" constant="-1" id="WDk-Ig-Grr"/>
+ <constraint firstAttribute="trailing" secondItem="Q8b-0e-dLv" secondAttribute="trailing" constant="-1" id="hHf-rd-Wcv"/>
+ <constraint firstItem="7L7-hr-zId" firstAttribute="leading" secondItem="8ha-hw-zOD" secondAttribute="leading" constant="20" symbolic="YES" id="iKJ-ph-ACS"/>
+ <constraint firstAttribute="bottom" secondItem="wzf-ce-Spm" secondAttribute="bottom" constant="-1" id="jFV-Xi-fWr"/>
+ <constraint firstItem="wzf-ce-Spm" firstAttribute="leading" secondItem="8ha-hw-zOD" secondAttribute="leading" id="kJt-oJ-72R"/>
+ </constraints>
+ </view>
+ <point key="canvasLocation" x="720" y="317"/>
+ </window>
+ <arrayController objectClassName="MGLOfflinePack" editable="NO" avoidsEmptySelection="NO" id="dWe-R6-sRz" userLabel="Offline Packs Array Controller">
+ <declaredKeys>
+ <string>context</string>
+ <string>countOfResourcesCompleted</string>
+ <string>countOfResourcesExpected</string>
+ <string>countOfBytesCompleted</string>
+ <string>stateImage</string>
+ </declaredKeys>
+ </arrayController>
</objects>
<resources>
+ <image name="NSAddTemplate" width="11" height="11"/>
<image name="NSFollowLinkFreestandingTemplate" width="14" height="14"/>
+ <image name="NSRemoveTemplate" width="11" height="11"/>
</resources>
</document>
diff --git a/platform/osx/app/MapDocument.m b/platform/osx/app/MapDocument.m
index 7c42bc9802..16d15c4ffd 100644
--- a/platform/osx/app/MapDocument.m
+++ b/platform/osx/app/MapDocument.m
@@ -204,6 +204,10 @@ static const CLLocationCoordinate2D WorldTourDestinations[] = {
appDelegate.pendingZoomLevel = -1;
appDelegate.pendingCamera = nil;
}
+ if (!MGLCoordinateBoundsIsEmpty(appDelegate.pendingVisibleCoordinateBounds)) {
+ self.mapView.visibleCoordinateBounds = appDelegate.pendingVisibleCoordinateBounds;
+ appDelegate.pendingVisibleCoordinateBounds = (MGLCoordinateBounds){ { 0, 0 }, { 0, 0 } };
+ }
if (appDelegate.pendingDebugMask) {
self.mapView.debugMask = appDelegate.pendingDebugMask;
}
@@ -350,6 +354,36 @@ static const CLLocationCoordinate2D WorldTourDestinations[] = {
[self.mapView addAnnotation:line];
}
+#pragma mark Offline packs
+
+- (IBAction)addOfflinePack:(id)sender {
+ NSAlert *namePrompt = [[NSAlert alloc] init];
+ namePrompt.messageText = @"Add offline pack";
+ namePrompt.informativeText = @"Choose a name for the pack:";
+ NSTextField *nameTextField = [[NSTextField alloc] initWithFrame:NSZeroRect];
+ nameTextField.placeholderString = MGLStringFromCoordinateBounds(self.mapView.visibleCoordinateBounds);
+ [nameTextField sizeToFit];
+ NSRect textFieldFrame = nameTextField.frame;
+ textFieldFrame.size.width = 300;
+ nameTextField.frame = textFieldFrame;
+ namePrompt.accessoryView = nameTextField;
+ [namePrompt addButtonWithTitle:@"Add"];
+ [namePrompt addButtonWithTitle:@"Cancel"];
+ if ([namePrompt runModal] != NSAlertFirstButtonReturn) {
+ return;
+ }
+
+ id <MGLOfflineRegion> region = [[MGLTilePyramidOfflineRegion alloc] initWithStyleURL:self.mapView.styleURL bounds:self.mapView.visibleCoordinateBounds fromZoomLevel:self.mapView.zoomLevel toZoomLevel:self.mapView.maximumZoomLevel];
+ NSData *context = [[NSValueTransformer valueTransformerForName:@"OfflinePackNameValueTransformer"] reverseTransformedValue:nameTextField.stringValue];
+ [[MGLOfflineStorage sharedOfflineStorage] addPackForRegion:region withContext:context completionHandler:^(MGLOfflinePack * _Nullable pack, NSError * _Nullable error) {
+ if (error) {
+ [[NSAlert alertWithError:error] runModal];
+ } else {
+ [pack resume];
+ }
+ }];
+}
+
#pragma mark Help methods
- (IBAction)giveFeedback:(id)sender {
@@ -500,6 +534,10 @@ static const CLLocationCoordinate2D WorldTourDestinations[] = {
if (menuItem.action == @selector(drawPolygonAndPolyLineAnnotations:)) {
return !_isShowingPolygonAndPolylineAnnotations;
}
+ if (menuItem.action == @selector(addOfflinePack:)) {
+ NSURL *styleURL = self.mapView.styleURL;
+ return !styleURL.isFileURL;
+ }
if (menuItem.action == @selector(giveFeedback:)) {
return YES;
}
diff --git a/platform/osx/app/OfflinePackNameValueTransformer.h b/platform/osx/app/OfflinePackNameValueTransformer.h
new file mode 100644
index 0000000000..11fe3ff441
--- /dev/null
+++ b/platform/osx/app/OfflinePackNameValueTransformer.h
@@ -0,0 +1,5 @@
+#import <Foundation/Foundation.h>
+
+@interface OfflinePackNameValueTransformer : NSValueTransformer
+
+@end
diff --git a/platform/osx/app/OfflinePackNameValueTransformer.m b/platform/osx/app/OfflinePackNameValueTransformer.m
new file mode 100644
index 0000000000..2825e48ed3
--- /dev/null
+++ b/platform/osx/app/OfflinePackNameValueTransformer.m
@@ -0,0 +1,33 @@
+#import "OfflinePackNameValueTransformer.h"
+
+static NSString * const MBXOfflinePackContextNameKey = @"Name";
+
+@implementation OfflinePackNameValueTransformer
+
++ (Class)transformedValueClass {
+ return [NSString class];
+}
+
++ (BOOL)allowsReverseTransformation {
+ return YES;
+}
+
+- (NSString *)transformedValue:(NSData *)context {
+ NSAssert([context isKindOfClass:[NSData class]], @"Context should be NSData.");
+
+ NSDictionary *userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:context];
+ NSAssert([userInfo isKindOfClass:[NSDictionary class]], @"Context of offline pack isn’t a dictionary.");
+ NSString *name = userInfo[MBXOfflinePackContextNameKey];
+ NSAssert([name isKindOfClass:[NSString class]], @"Name of offline pack isn’t a string.");
+ return name;
+}
+
+- (NSData *)reverseTransformedValue:(NSString *)name {
+ NSAssert([name isKindOfClass:[NSString class]], @"Name should be a string.");
+
+ return [NSKeyedArchiver archivedDataWithRootObject:@{
+ MBXOfflinePackContextNameKey: name,
+ }];
+}
+
+@end
diff --git a/platform/osx/app/mapboxgl-app.gypi b/platform/osx/app/mapboxgl-app.gypi
index 7b39f5d9eb..8b8cc17276 100644
--- a/platform/osx/app/mapboxgl-app.gypi
+++ b/platform/osx/app/mapboxgl-app.gypi
@@ -29,6 +29,8 @@
'./LocationCoordinate2DTransformer.m',
'./MapDocument.h',
'./MapDocument.m',
+ './OfflinePackNameValueTransformer.h',
+ './OfflinePackNameValueTransformer.m',
'./TimeIntervalTransformer.h',
'./TimeIntervalTransformer.m',
'./NSValue+Additions.h',
diff --git a/platform/osx/src/MGLMapView.mm b/platform/osx/src/MGLMapView.mm
index 6f3cd8e329..f8bbb61d05 100644
--- a/platform/osx/src/MGLMapView.mm
+++ b/platform/osx/src/MGLMapView.mm
@@ -4,10 +4,11 @@
#import "MGLOpenGLLayer.h"
#import "MGLStyle.h"
-#import "../../darwin/src/MGLAccountManager_Private.h"
#import "../../darwin/src/MGLGeometry_Private.h"
#import "../../darwin/src/MGLMultiPoint_Private.h"
+#import "../../darwin/src/MGLOfflineStorage_Private.h"
+#import "MGLAccountManager.h"
#import "MGLMapCamera.h"
#import "MGLPolygon.h"
#import "MGLPolyline.h"
@@ -151,7 +152,6 @@ public:
/// Cross-platform map view controller.
mbgl::Map *_mbglMap;
MGLMapViewImpl *_mbglView;
- mbgl::DefaultFileSource *_mbglFileSource;
NSPanGestureRecognizer *_panGestureRecognizer;
NSMagnificationGestureRecognizer *_magnificationGestureRecognizer;
@@ -232,36 +232,25 @@ public:
// Set up cross-platform controllers and resources.
_mbglView = new MGLMapViewImpl(self, [NSScreen mainScreen].backingScaleFactor);
- // Place the cache in a location that can be shared among all the
- // applications that embed the Mapbox OS X SDK.
- NSURL *cacheDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory
- inDomain:NSUserDomainMask
- appropriateForURL:nil
- create:YES
- error:nil];
- cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:
- [[NSBundle mgl_frameworkBundle] bundleIdentifier]];
- [[NSFileManager defaultManager] createDirectoryAtURL:cacheDirectoryURL
- withIntermediateDirectories:YES
- attributes:nil
- error:nil];
- NSURL *cacheURL = [cacheDirectoryURL URLByAppendingPathComponent:@"cache.db"];
- NSString *cachePath = cacheURL ? cacheURL.path : @"";
- _mbglFileSource = new mbgl::DefaultFileSource(cachePath.UTF8String, [[[[NSBundle mainBundle] resourceURL] path] UTF8String]);
-
- _mbglMap = new mbgl::Map(*_mbglView, *_mbglFileSource, mbgl::MapMode::Continuous, mbgl::GLContextMode::Unique, mbgl::ConstrainMode::None);
+ // Delete the pre-offline ambient cache at
+ // ~/Library/Caches/com.mapbox.sdk.ios/cache.db.
+ NSURL *cachesDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory
+ inDomain:NSUserDomainMask
+ appropriateForURL:nil
+ create:NO
+ error:nil];
+ cachesDirectoryURL = [cachesDirectoryURL URLByAppendingPathComponent:
+ [NSBundle mgl_frameworkBundle].bundleIdentifier];
+ NSURL *legacyCacheURL = [cachesDirectoryURL URLByAppendingPathComponent:@"cache.db"];
+ [[NSFileManager defaultManager] removeItemAtURL:legacyCacheURL error:NULL];
+
+ mbgl::DefaultFileSource *mbglFileSource = [MGLOfflineStorage sharedOfflineStorage].mbglFileSource;
+ _mbglMap = new mbgl::Map(*_mbglView, *mbglFileSource, mbgl::MapMode::Continuous, mbgl::GLContextMode::Unique, mbgl::ConstrainMode::None);
// Install the OpenGL layer. Interface Builder’s synchronous drawing means
// we can’t display a map, so don’t even bother to have a map layer.
self.layer = _isTargetingInterfaceBuilder ? [CALayer layer] : [MGLOpenGLLayer layer];
- // Observe for changes to the global access token (and find out the current one).
- [[MGLAccountManager sharedManager] addObserver:self
- forKeyPath:@"accessToken"
- options:(NSKeyValueObservingOptionInitial |
- NSKeyValueObservingOptionNew)
- context:NULL];
-
// Notify map object when network reachability status changes.
MGLReachability *reachability = [MGLReachability reachabilityForInternetConnection];
reachability.reachableBlock = ^(MGLReachability *) {
@@ -443,7 +432,6 @@ public:
}
- (void)dealloc {
- [[MGLAccountManager sharedManager] removeObserver:self forKeyPath:@"accessToken"];
[self.window removeObserver:self forKeyPath:@"contentLayoutRect"];
[self.window removeObserver:self forKeyPath:@"titlebarAppearsTransparent"];
@@ -455,25 +443,15 @@ public:
delete _mbglMap;
_mbglMap = nullptr;
}
- if (_mbglFileSource) {
- delete _mbglFileSource;
- _mbglFileSource = nullptr;
- }
if (_mbglView) {
delete _mbglView;
_mbglView = nullptr;
}
}
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(__unused void *)context {
- // Synchronize mbgl::Map’s access token with the global one in MGLAccountManager.
- if ([keyPath isEqualToString:@"accessToken"] && object == [MGLAccountManager sharedManager]) {
- NSString *accessToken = change[NSKeyValueChangeNewKey];
- if (![accessToken isKindOfClass:[NSNull class]]) {
- _mbglFileSource->setAccessToken((std::string)accessToken.UTF8String);
- }
- } else if ([keyPath isEqualToString:@"contentLayoutRect"] ||
- [keyPath isEqualToString:@"titlebarAppearsTransparent"]) {
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(__unused id)object change:(__unused NSDictionary *)change context:(__unused void *)context {
+ if ([keyPath isEqualToString:@"contentLayoutRect"] ||
+ [keyPath isEqualToString:@"titlebarAppearsTransparent"]) {
[self adjustContentInsets];
}
}
@@ -2182,7 +2160,7 @@ public:
convertedPoint.x,
// mbgl origin is at the top-left corner.
NSHeight(self.bounds) - convertedPoint.y,
- });
+ }).wrapped();
}
- (NSRect)convertCoordinateBounds:(MGLCoordinateBounds)bounds toRectToView:(nullable NSView *)view {
diff --git a/platform/osx/test/MGLOfflinePackTests.m b/platform/osx/test/MGLOfflinePackTests.m
new file mode 100644
index 0000000000..41262d16c7
--- /dev/null
+++ b/platform/osx/test/MGLOfflinePackTests.m
@@ -0,0 +1,40 @@
+#import <Mapbox/Mapbox.h>
+
+#pragma clang diagnostic ignored "-Wgnu-statement-expression"
+#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
+
+#import <XCTest/XCTest.h>
+
+@interface MGLOfflinePackTests : XCTestCase
+
+@end
+
+@implementation MGLOfflinePackTests
+
+- (void)testInvalidation {
+ MGLOfflinePack *invalidPack = [[MGLOfflinePack alloc] init];
+
+ XCTAssertEqual(invalidPack.state, MGLOfflinePackStateInvalid, @"Offline pack should be invalid when initialized independently of MGLOfflineStorage.");
+
+ XCTAssertThrowsSpecificNamed(invalidPack.region, NSException, @"Invalid offline pack", @"Invalid offline pack should raise an exception when accessing its region.");
+ XCTAssertThrowsSpecificNamed(invalidPack.context, NSException, @"Invalid offline pack", @"Invalid offline pack should raise an exception when accessing its context.");
+ XCTAssertThrowsSpecificNamed([invalidPack resume], NSException, @"Invalid offline pack", @"Invalid offline pack should raise an exception when being resumed.");
+ XCTAssertThrowsSpecificNamed([invalidPack suspend], NSException, @"Invalid offline pack", @"Invalid offline pack should raise an exception when being suspended.");
+}
+
+- (void)testProgressBoxing {
+ MGLOfflinePackProgress progress = {
+ .countOfResourcesCompleted = 1,
+ .countOfResourcesExpected = 2,
+ .countOfBytesCompleted = 7,
+ .maximumResourcesExpected = UINT64_MAX,
+ };
+ MGLOfflinePackProgress roundTrippedProgress = [NSValue valueWithMGLOfflinePackProgress:progress].MGLOfflinePackProgressValue;
+
+ XCTAssertEqual(progress.countOfResourcesCompleted, roundTrippedProgress.countOfResourcesCompleted, @"Completed resources should round-trip.");
+ XCTAssertEqual(progress.countOfResourcesExpected, roundTrippedProgress.countOfResourcesExpected, @"Expected resources should round-trip.");
+ XCTAssertEqual(progress.countOfBytesCompleted, roundTrippedProgress.countOfBytesCompleted, @"Completed bytes should round-trip.");
+ XCTAssertEqual(progress.maximumResourcesExpected, roundTrippedProgress.maximumResourcesExpected, @"Maximum expected resources should round-trip.");
+}
+
+@end
diff --git a/platform/osx/test/MGLOfflineRegionTests.m b/platform/osx/test/MGLOfflineRegionTests.m
new file mode 100644
index 0000000000..63befdf14c
--- /dev/null
+++ b/platform/osx/test/MGLOfflineRegionTests.m
@@ -0,0 +1,35 @@
+#import <Mapbox/Mapbox.h>
+
+#pragma clang diagnostic ignored "-Wgnu-statement-expression"
+#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
+
+#import <XCTest/XCTest.h>
+
+@interface MGLOfflineRegionTests : XCTestCase
+
+@end
+
+@implementation MGLOfflineRegionTests
+
+- (void)testStyleURLs {
+ MGLCoordinateBounds bounds = MGLCoordinateBoundsMake(kCLLocationCoordinate2DInvalid, kCLLocationCoordinate2DInvalid);
+ MGLTilePyramidOfflineRegion *region = [[MGLTilePyramidOfflineRegion alloc] initWithStyleURL:nil bounds:bounds fromZoomLevel:0 toZoomLevel:DBL_MAX];
+ XCTAssertEqualObjects(region.styleURL, [MGLStyle streetsStyleURL], @"Streets isn’t the default style.");
+
+ NSURL *localURL = [NSURL URLWithString:@"beautiful.style"];
+ XCTAssertThrowsSpecificNamed([[MGLTilePyramidOfflineRegion alloc] initWithStyleURL:localURL bounds:bounds fromZoomLevel:0 toZoomLevel:DBL_MAX], NSException, @"Invalid style URL", @"No exception raised when initializing region with a local file URL as the style URL.");
+}
+
+- (void)testEquality {
+ MGLCoordinateBounds bounds = MGLCoordinateBoundsMake(kCLLocationCoordinate2DInvalid, kCLLocationCoordinate2DInvalid);
+ MGLTilePyramidOfflineRegion *original = [[MGLTilePyramidOfflineRegion alloc] initWithStyleURL:[MGLStyle lightStyleURL] bounds:bounds fromZoomLevel:5 toZoomLevel:10];
+ MGLTilePyramidOfflineRegion *copy = [original copy];
+ XCTAssertEqualObjects(original, copy, @"Tile pyramid region should be equal to its copy.");
+
+ XCTAssertEqualObjects(original.styleURL, copy.styleURL, @"Style URL has changed.");
+ XCTAssert(MGLCoordinateBoundsEqualToCoordinateBounds(original.bounds, copy.bounds), @"Bounds have changed.");
+ XCTAssertEqual(original.minimumZoomLevel, original.minimumZoomLevel, @"Minimum zoom level has changed.");
+ XCTAssertEqual(original.maximumZoomLevel, original.maximumZoomLevel, @"Maximum zoom level has changed.");
+}
+
+@end
diff --git a/platform/osx/test/MGLOfflineStorageTests.m b/platform/osx/test/MGLOfflineStorageTests.m
new file mode 100644
index 0000000000..8ffa1207ce
--- /dev/null
+++ b/platform/osx/test/MGLOfflineStorageTests.m
@@ -0,0 +1,126 @@
+#import <Mapbox/Mapbox.h>
+
+#pragma clang diagnostic ignored "-Wgnu-statement-expression"
+#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
+
+#import <XCTest/XCTest.h>
+
+@interface MGLOfflineStorageTests : XCTestCase
+
+@end
+
+@implementation MGLOfflineStorageTests
+
+- (void)testSharedObject {
+ XCTAssertEqual([MGLOfflineStorage sharedOfflineStorage], [MGLOfflineStorage sharedOfflineStorage], @"There should only be one shared offline storage object.");
+}
+
+// This test needs to come first so it can test the initial loading of packs.
+- (void)testAAALoadPacks {
+ XCTestExpectation *kvoExpectation = [self keyValueObservingExpectationForObject:[MGLOfflineStorage sharedOfflineStorage] keyPath:@"packs" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) {
+ NSKeyValueChange changeKind = [change[NSKeyValueChangeKindKey] unsignedIntegerValue];
+ return changeKind = NSKeyValueChangeSetting;
+ }];
+
+ [self waitForExpectationsWithTimeout:1 handler:nil];
+
+ XCTAssertNotNil([MGLOfflineStorage sharedOfflineStorage].packs, @"Shared offline storage object should have a non-nil collection of packs by this point.");
+}
+
+- (void)testAddPack {
+ NSUInteger countOfPacks = [MGLOfflineStorage sharedOfflineStorage].packs.count;
+
+ NSURL *styleURL = [MGLStyle lightStyleURL];
+ /// Somewhere near Grape Grove, Ohio, United States.
+ MGLCoordinateBounds bounds = {
+ { 39.70358155855172, -83.69506472545841 },
+ { 39.703818870225376, -83.69420641857361 },
+ };
+ double zoomLevel = 20;
+ MGLTilePyramidOfflineRegion *region = [[MGLTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL bounds:bounds fromZoomLevel:zoomLevel toZoomLevel:zoomLevel];
+
+ NSString *nameKey = @"Name";
+ NSString *name = @"🍇 Grape Grove";
+
+ NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{
+ nameKey: name,
+ }];
+
+ __block MGLOfflinePack *pack;
+ [self keyValueObservingExpectationForObject:[MGLOfflineStorage sharedOfflineStorage] keyPath:@"packs" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) {
+ NSKeyValueChange changeKind = [change[NSKeyValueChangeKindKey] unsignedIntegerValue];
+ NSIndexSet *indices = change[NSKeyValueChangeIndexesKey];
+ return changeKind == NSKeyValueChangeInsertion && indices.count == 1;
+ }];
+ XCTestExpectation *additionCompletionHandlerExpectation = [self expectationWithDescription:@"add pack completion handler"];
+ [[MGLOfflineStorage sharedOfflineStorage] addPackForRegion:region withContext:context completionHandler:^(MGLOfflinePack * _Nullable completionHandlerPack, NSError * _Nullable error) {
+ XCTAssertNotNil(completionHandlerPack, @"Added pack should exist.");
+ XCTAssertEqual(completionHandlerPack.state, MGLOfflinePackStateInactive, @"New pack should initially have inactive state.");
+ pack = completionHandlerPack;
+ [additionCompletionHandlerExpectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:1 handler:nil];
+
+ XCTAssertEqual([MGLOfflineStorage sharedOfflineStorage].packs.count, countOfPacks + 1, @"Added pack should have been added to the canonical collection of packs owned by the shared offline storage object. This assertion can fail if this test is run before -testAAALoadPacks.");
+
+ XCTAssertEqual(pack, [MGLOfflineStorage sharedOfflineStorage].packs.lastObject, @"Pack should be appended to end of packs array.");
+
+ XCTAssertEqualObjects(pack.region, region, @"Added pack’s region has changed.");
+
+ NSDictionary *userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:pack.context];
+ XCTAssert([userInfo isKindOfClass:[NSDictionary class]], @"Context of offline pack isn’t a dictionary.");
+ XCTAssert([userInfo[nameKey] isKindOfClass:[NSString class]], @"Name of offline pack isn’t a string.");
+ XCTAssertEqualObjects(userInfo[nameKey], name, @"Name of offline pack has changed.");
+
+ XCTAssertEqual(pack.state, MGLOfflinePackStateInactive, @"New pack should initially have inactive state.");
+
+ [self keyValueObservingExpectationForObject:pack keyPath:@"state" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) {
+ NSKeyValueChange changeKind = [change[NSKeyValueChangeKindKey] unsignedIntegerValue];
+ MGLOfflinePackState state = [change[NSKeyValueChangeNewKey] integerValue];
+ return changeKind == NSKeyValueChangeSetting && state == MGLOfflinePackStateInactive;
+ }];
+ [self expectationForNotification:MGLOfflinePackProgressChangedNotification object:pack handler:^BOOL(NSNotification * _Nonnull notification) {
+ MGLOfflinePack *notificationPack = notification.object;
+ XCTAssert([notificationPack isKindOfClass:[MGLOfflinePack class]], @"Object of notification should be an MGLOfflinePack.");
+
+ NSDictionary *userInfo = notification.userInfo;
+ XCTAssertNotNil(userInfo, @"Progress change notification should have a userInfo dictionary.");
+
+ NSNumber *stateNumber = userInfo[MGLOfflinePackStateUserInfoKey];
+ XCTAssert([stateNumber isKindOfClass:[NSNumber class]], @"Progress change notification’s state should be an NSNumber.");
+ XCTAssertEqual(stateNumber.integerValue, pack.state, @"State in a progress change notification should match the pack’s state.");
+
+ NSValue *progressValue = userInfo[MGLOfflinePackProgressUserInfoKey];
+ XCTAssert([progressValue isKindOfClass:[NSValue class]], @"Progress change notification’s progress should be an NSValue.");
+ XCTAssertEqualObjects(progressValue, [NSValue valueWithMGLOfflinePackProgress:pack.progress], @"Progress change notification’s progress should match pack’s progress.");
+
+ return notificationPack == pack && pack.state == MGLOfflinePackStateInactive;
+ }];
+ [pack requestProgress];
+ [self waitForExpectationsWithTimeout:1 handler:nil];
+}
+
+- (void)testRemovePack {
+ NSUInteger countOfPacks = [MGLOfflineStorage sharedOfflineStorage].packs.count;
+
+ MGLOfflinePack *pack = [MGLOfflineStorage sharedOfflineStorage].packs.lastObject;
+ XCTAssertNotNil(pack, @"Added pack should still exist.");
+
+ XCTestExpectation *kvoExpectation = [self keyValueObservingExpectationForObject:[MGLOfflineStorage sharedOfflineStorage] keyPath:@"packs" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) {
+ NSKeyValueChange changeKind = [change[NSKeyValueChangeKindKey] unsignedIntegerValue];
+ NSIndexSet *indices = change[NSKeyValueChangeIndexesKey];
+ return changeKind = NSKeyValueChangeRemoval && indices.count == 1;
+ }];
+ XCTestExpectation *completionHandlerExpectation = [self expectationWithDescription:@"remove pack completion handler"];
+ [[MGLOfflineStorage sharedOfflineStorage] removePack:pack withCompletionHandler:^(NSError * _Nullable error) {
+ XCTAssertEqual(pack.state, MGLOfflinePackStateInvalid, @"Removed pack should be invalid in the completion handler.");
+ [completionHandlerExpectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:1 handler:nil];
+
+ XCTAssertEqual(pack.state, MGLOfflinePackStateInvalid, @"Removed pack should have been invalidated synchronously.");
+
+ XCTAssertEqual([MGLOfflineStorage sharedOfflineStorage].packs.count, countOfPacks - 1, @"Removed pack should have been removed from the canonical collection of packs owned by the shared offline storage object. This assertion can fail if this test is run before -testAAALoadPacks or -testAddPack.");
+}
+
+@end
diff --git a/platform/osx/test/osxtest.gypi b/platform/osx/test/osxtest.gypi
index 30bced31c4..6165b6fa88 100644
--- a/platform/osx/test/osxtest.gypi
+++ b/platform/osx/test/osxtest.gypi
@@ -44,6 +44,9 @@
'sources': [
'./MGLGeometryTests.mm',
+ './MGLOfflinePackTests.m',
+ './MGLOfflineRegionTests.m',
+ './MGLOfflineStorageTests.m',
'./MGLStyleTests.mm',
],
diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp
index 1d9cb1ac74..4024aaee7e 100644
--- a/src/mbgl/map/map.cpp
+++ b/src/mbgl/map/map.cpp
@@ -79,8 +79,8 @@ void Map::renderSync() {
} else if (renderState != RenderState::fully) {
renderState = RenderState::fully;
view.notifyMapChange(MapChangeDidFinishRenderingMapFullyRendered);
- if (data->loading) {
- data->loading = false;
+ if (loading) {
+ loading = false;
view.notifyMapChange(MapChangeDidFinishLoadingMap);
}
}
@@ -100,11 +100,13 @@ void Map::update(Update flags) {
#pragma mark - Style
void Map::setStyleURL(const std::string &url) {
+ loading = true;
view.notifyMapChange(MapChangeWillStartLoadingMap);
context->invoke(&MapContext::setStyleURL, url);
}
void Map::setStyleJSON(const std::string& json, const std::string& base) {
+ loading = true;
view.notifyMapChange(MapChangeWillStartLoadingMap);
context->invoke(&MapContext::setStyleJSON, json, base);
}
diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp
index b4badb20ef..0ea51bf53a 100644
--- a/src/mbgl/map/map_context.cpp
+++ b/src/mbgl/map/map_context.cpp
@@ -113,7 +113,6 @@ void MapContext::setStyleURL(const std::string& url) {
Log::Error(Event::Setup, "style %s could not be found or is an incompatible legacy map or style", styleURL.c_str());
} else {
Log::Error(Event::Setup, "loading style failed: %s", res.error->message.c_str());
- data.loading = false;
}
} else if (res.notModified || res.noContent) {
return;
@@ -146,10 +145,6 @@ void MapContext::loadStyleJSON(const std::string& json, const std::string& base)
// force style cascade, causing all pending transitions to complete.
style->cascade(Clock::now());
- // set loading here so we don't get a false loaded event as soon as map is
- // created but before a style is loaded
- data.loading = true;
-
updateAsync(Update::Classes | Update::RecalculateStyle | Update::Annotations);
}
diff --git a/src/mbgl/map/map_data.hpp b/src/mbgl/map/map_data.hpp
index 4cf3140e91..0513d84f87 100644
--- a/src/mbgl/map/map_data.hpp
+++ b/src/mbgl/map/map_data.hpp
@@ -68,7 +68,6 @@ public:
bool paused = false;
std::mutex mutexPause;
std::condition_variable condPause;
- bool loading = false;
};
} // namespace mbgl
diff --git a/test/fixtures/offline/v2.db b/test/fixtures/offline/v2.db
new file mode 100644
index 0000000000..8fadec4abe
--- /dev/null
+++ b/test/fixtures/offline/v2.db
Binary files differ
diff --git a/test/storage/offline_database.cpp b/test/storage/offline_database.cpp
index 455c564dc9..a9081d2b3f 100644
--- a/test/storage/offline_database.cpp
+++ b/test/storage/offline_database.cpp
@@ -8,6 +8,7 @@
#include <mbgl/util/string.hpp>
#include <gtest/gtest.h>
+#include <sqlite3.hpp>
#include <sqlite3.h>
#include <thread>
#include <random>
@@ -654,3 +655,38 @@ TEST(OfflineDatabase, OfflineMapboxTileCount) {
db.deleteRegion(std::move(region1));
EXPECT_EQ(0, db.getOfflineMapboxTileCount());
}
+
+static int databasePageCount(const std::string& path) {
+ mapbox::sqlite::Database db(path, mapbox::sqlite::ReadOnly);
+ mapbox::sqlite::Statement stmt = db.prepare("pragma page_count");
+ stmt.run();
+ return stmt.get<int>(0);
+}
+
+static int databaseUserVersion(const std::string& path) {
+ mapbox::sqlite::Database db(path, mapbox::sqlite::ReadOnly);
+ mapbox::sqlite::Statement stmt = db.prepare("pragma user_version");
+ stmt.run();
+ return stmt.get<int>(0);
+}
+
+TEST(OfflineDatabase, MigrateFromV2Schema) {
+ using namespace mbgl;
+
+ // v2.db is a v2 database containing a single offline region with a small number of resources.
+
+ deleteFile("test/fixtures/offline/v3.db");
+ writeFile("test/fixtures/offline/v3.db", util::read_file("test/fixtures/offline/v2.db"));
+
+ {
+ OfflineDatabase db("test/fixtures/offline/v3.db", 0);
+ auto regions = db.listRegions();
+ for (auto& region : regions) {
+ db.deleteRegion(std::move(region));
+ }
+ }
+
+ EXPECT_EQ(3, databaseUserVersion("test/fixtures/offline/v3.db"));
+ EXPECT_LT(databasePageCount("test/fixtures/offline/v3.db"),
+ databasePageCount("test/fixtures/offline/v2.db"));
+}
diff --git a/test/storage/offline_download.cpp b/test/storage/offline_download.cpp
index f7fe0b4064..0898043232 100644
--- a/test/storage/offline_download.cpp
+++ b/test/storage/offline_download.cpp
@@ -364,7 +364,7 @@ TEST(OfflineDownload, RequestErrorsAreRetried) {
test.loop.run();
}
-TEST(OfflineDownload, TileCountLimitExceeded) {
+TEST(OfflineDownload, TileCountLimitExceededNoTileResponse) {
OfflineTest test;
OfflineRegion region = test.createRegion();
OfflineDownload download(
@@ -372,7 +372,9 @@ TEST(OfflineDownload, TileCountLimitExceeded) {
OfflineTilePyramidRegionDefinition("http://127.0.0.1:3000/offline/style.json", LatLngBounds::world(), 0.0, 0.0, 1.0),
test.db, test.fileSource);
- test.db.setOfflineMapboxTileCountLimit(0);
+ uint64_t tileLimit = 0;
+
+ test.db.setOfflineMapboxTileCountLimit(tileLimit);
test.fileSource.styleResponse = [&] (const Resource& resource) {
EXPECT_EQ("http://127.0.0.1:3000/offline/style.json", resource.url);
@@ -384,18 +386,72 @@ TEST(OfflineDownload, TileCountLimitExceeded) {
observer->mapboxTileCountLimitExceededFn = [&] (uint64_t limit) {
EXPECT_FALSE(mapboxTileCountLimitExceededCalled);
- EXPECT_EQ(0, limit);
+ EXPECT_EQ(tileLimit, limit);
mapboxTileCountLimitExceededCalled = true;
};
observer->statusChangedFn = [&] (OfflineRegionStatus status) {
- EXPECT_FALSE(status.complete());
+ if (!mapboxTileCountLimitExceededCalled) {
+ EXPECT_FALSE(status.complete());
+ EXPECT_EQ(OfflineRegionDownloadState::Active, status.downloadState);
+ } else {
+ EXPECT_EQ(OfflineRegionDownloadState::Inactive, status.downloadState);
+ test.loop.stop();
+ }
+ };
+
+ download.setObserver(std::move(observer));
+ download.setState(OfflineRegionDownloadState::Active);
+
+ test.loop.run();
+}
+
+TEST(OfflineDownload, TileCountLimitExceededWithTileResponse) {
+ OfflineTest test;
+ OfflineRegion region = test.createRegion();
+ OfflineDownload download(
+ region.getID(),
+ OfflineTilePyramidRegionDefinition("http://127.0.0.1:3000/offline/style.json", LatLngBounds::world(), 0.0, 0.0, 1.0),
+ test.db, test.fileSource);
+
+ uint64_t tileLimit = 1;
+
+ test.db.setOfflineMapboxTileCountLimit(tileLimit);
+
+ test.fileSource.styleResponse = [&] (const Resource& resource) {
+ EXPECT_EQ("http://127.0.0.1:3000/offline/style.json", resource.url);
+ return test.response("offline/mapbox_source.style.json");
+ };
+
+ test.fileSource.tileResponse = [&] (const Resource& resource) {
+ const Resource::TileData& tile = *resource.tileData;
+ EXPECT_EQ("mapbox://{z}-{x}-{y}.vector.pbf", tile.urlTemplate);
+ EXPECT_EQ(1, tile.pixelRatio);
+ EXPECT_EQ(0, tile.x);
+ EXPECT_EQ(0, tile.y);
+ EXPECT_EQ(0, tile.z);
+ return test.response("offline/0-0-0.vector.pbf");
+ };
+
+ auto observer = std::make_unique<MockObserver>();
+ bool mapboxTileCountLimitExceededCalled = false;
+
+ observer->mapboxTileCountLimitExceededFn = [&] (uint64_t limit) {
+ EXPECT_FALSE(mapboxTileCountLimitExceededCalled);
+ EXPECT_EQ(tileLimit, limit);
+ mapboxTileCountLimitExceededCalled = true;
+ };
+
+ observer->statusChangedFn = [&] (OfflineRegionStatus status) {
if (!mapboxTileCountLimitExceededCalled) {
EXPECT_EQ(OfflineRegionDownloadState::Active, status.downloadState);
} else {
EXPECT_EQ(OfflineRegionDownloadState::Inactive, status.downloadState);
test.loop.stop();
}
+ if (status.completedResourceCount > tileLimit) {
+ test.loop.stop();
+ }
};
download.setObserver(std::move(observer));