Merge branch 'release-ios-3.2.0-android-4.0.0'

This commit is contained in:
John Firebaugh
2016-03-30 17:01:54 -07:00
101 changed files with 2268 additions and 1077 deletions

1
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

@@ -199,6 +199,7 @@ private:
};
RenderState renderState = RenderState::never;
bool loading = false;
};
} // namespace mbgl

View File

@@ -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) {

View File

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

View File

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

View File

@@ -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);

View File

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

View File

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

View File

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

View File

@@ -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();
}
/**

View File

@@ -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();
@@ -481,22 +493,6 @@ public class MapView extends FrameLayout {
mNativeMapView = null;
}
/**
* 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()}.
*/
@@ -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.
*

View File

@@ -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());
@@ -83,6 +87,78 @@ public class MapboxMap {
mInfoWindows = new ArrayList<>();
}
//
// 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);
}
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 (count > 0) {
MarkerOptions markerOptions;
Marker marker;
for (int i = 0; i < count; i++) {
markerOptions = markerOptionsList.get(i);
marker = prepareMarker(markerOptions);
markers.add(marker);
}
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);
}
}
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++;
if(count>0) {
for (PolylineOptions options : polylineOptionsList) {
polyline = options.getPolyline();
if (!polyline.getPoints().isEmpty()) {
polylines.add(polyline);
}
}
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;
}

View File

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

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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();
}
/**

View File

@@ -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();
@@ -36,70 +33,6 @@ public class UiSettings {
this.attributionSettings = new ViewSettings();
}
/**
* <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

View File

@@ -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);
}

View File

@@ -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) {

View File

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

View File

@@ -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));

View File

@@ -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;
}
}

View File

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

View File

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

View File

@@ -85,12 +85,6 @@ public class AnimatedMarkerActivity extends AppCompatActivity {
}
}
@Override
protected void onStart() {
super.onStart();
mMapView.onStart();
}
@Override
public void onResume() {
super.onResume();
@@ -103,12 +97,6 @@ public class AnimatedMarkerActivity extends AppCompatActivity {
mMapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mMapView.onStop();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);

View File

@@ -84,12 +84,6 @@ public class BulkMarkerActivity extends AppCompatActivity implements AdapterView
// nothing selected, nothing to do!
}
@Override
protected void onStart() {
super.onStart();
mMapView.onStart();
}
@Override
public void onResume() {
super.onResume();
@@ -102,12 +96,6 @@ public class BulkMarkerActivity extends AppCompatActivity implements AdapterView
mMapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mMapView.onStop();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);

View File

@@ -139,12 +139,6 @@ public class CameraAnimationTypeActivity extends AppCompatActivity {
return cameraState ? LAT_LNG_TOWER_BRIDGE : LAT_LNG_LONDON_EYE;
}
@Override
protected void onStart() {
super.onStart();
mapView.onStart();
}
@Override
public void onResume() {
super.onResume();
@@ -157,12 +151,6 @@ public class CameraAnimationTypeActivity extends AppCompatActivity {
mapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mapView.onStop();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);

View File

@@ -95,18 +95,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();

View File

@@ -141,12 +141,6 @@ public class DirectionsActivity extends AppCompatActivity {
.width(5));
}
@Override
protected void onStart() {
super.onStart();
mMapView.onStart();
}
@Override
public void onResume() {
super.onResume();
@@ -159,12 +153,6 @@ public class DirectionsActivity extends AppCompatActivity {
mMapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mMapView.onStop();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);

View File

@@ -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();
@@ -107,12 +100,6 @@ public class DynamicMarkerChangeActivity extends AppCompatActivity {
mMapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mMapView.onStop();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);

View File

@@ -97,18 +97,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();

View File

@@ -116,12 +116,6 @@ public class InfoWindowActivity extends AppCompatActivity implements OnMapReadyC
.position(point));
}
@Override
protected void onStart() {
super.onStart();
mapView.onStart();
}
@Override
public void onResume() {
super.onResume();
@@ -134,12 +128,6 @@ public class InfoWindowActivity extends AppCompatActivity implements OnMapReadyC
mapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mapView.onStop();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);

View File

@@ -103,12 +103,6 @@ public class InfoWindowAdapterActivity extends AppCompatActivity {
});
}
@Override
protected void onStart() {
super.onStart();
mapView.onStart();
}
@Override
public void onResume() {
super.onResume();
@@ -121,12 +115,6 @@ public class InfoWindowAdapterActivity extends AppCompatActivity {
mapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mapView.onStop();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);

View File

@@ -83,12 +83,6 @@ public class LatLngBoundsActivity extends AppCompatActivity {
}
@Override
protected void onStart() {
super.onStart();
mMapView.onStart();
}
@Override
public void onResume() {
super.onResume();
@@ -101,12 +95,6 @@ public class LatLngBoundsActivity extends AppCompatActivity {
mMapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mMapView.onStop();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);

View File

@@ -92,12 +92,6 @@ public class ManualZoomActivity extends AppCompatActivity {
}
}
@Override
protected void onStart() {
super.onStart();
mMapView.onStart();
}
@Override
public void onResume() {
super.onResume();
@@ -110,12 +104,6 @@ public class ManualZoomActivity extends AppCompatActivity {
mMapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mMapView.onStop();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);

View File

@@ -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);

View File

@@ -59,12 +59,6 @@ public class MapPaddingActivity extends AppCompatActivity {
});
}
@Override
protected void onStart() {
super.onStart();
mMapView.onStart();
}
@Override
public void onResume() {
super.onResume();
@@ -77,12 +71,6 @@ public class MapPaddingActivity extends AppCompatActivity {
mMapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mMapView.onStop();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);

View File

@@ -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);
}
});
}
@@ -53,12 +52,6 @@ public class MaxMinZoomActivity extends AppCompatActivity {
return false;
}
@Override
protected void onStart() {
super.onStart();
mMapView.onStart();
}
@Override
public void onResume() {
super.onResume();
@@ -71,12 +64,6 @@ public class MaxMinZoomActivity extends AppCompatActivity {
mMapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mMapView.onStop();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);

View File

@@ -170,12 +170,6 @@ public class MyLocationTrackingModeActivity extends AppCompatActivity implements
}
@Override
protected void onStart() {
super.onStart();
mMapView.onStart();
}
@Override
public void onResume() {
super.onResume();
@@ -194,12 +188,6 @@ public class MyLocationTrackingModeActivity extends AppCompatActivity implements
mMapView.onSaveInstanceState(outState);
}
@Override
protected void onStop() {
super.onStop();
mMapView.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();

View File

@@ -122,12 +122,6 @@ public class OfflineActivity extends AppCompatActivity
mOfflineManager.setAccessToken(ApiAccess.getToken(this));
}
@Override
protected void onStart() {
super.onStart();
mMapView.onStart();
}
@Override
public void onResume() {
super.onResume();
@@ -140,12 +134,6 @@ public class OfflineActivity extends AppCompatActivity
mMapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mMapView.onStop();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.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);

View File

@@ -117,12 +117,6 @@ public class PolylineActivity extends AppCompatActivity {
}};
}
@Override
protected void onStart() {
super.onStart();
mMapView.onStart();
}
@Override
public void onResume() {
super.onResume();
@@ -135,12 +129,6 @@ public class PolylineActivity extends AppCompatActivity {
mMapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mMapView.onStop();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);

View File

@@ -76,12 +76,6 @@ public class ScrollByActivity extends AppCompatActivity {
});
}
@Override
protected void onStart() {
super.onStart();
mMapView.onStart();
}
@Override
public void onResume() {
super.onResume();
@@ -94,12 +88,6 @@ public class ScrollByActivity extends AppCompatActivity {
mMapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mMapView.onStop();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);

View File

@@ -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]" + "]]");
}
}

View File

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

View File

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

View File

@@ -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);
}
}

View File

@@ -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());
}

View File

@@ -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.assertTrue;
import static org.mockito.Matchers.matches;
import static org.mockito.Mockito.mock;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -108,6 +104,30 @@ public class MapboxMapTest {
assertNotNull("UiSettings should not be null", mMapboxMap.getUiSettings());
}
//
// 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());
}
//
@@ -514,6 +534,23 @@ public class MapboxMapTest {
assertTrue(mMapboxMap.getMarkers().contains(markerOptions2.getMarker()));
}
@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());
@@ -544,6 +581,22 @@ public class MapboxMapTest {
assertTrue("Polygon should be ignored", !mMapboxMap.getPolygons().contains(polygonOptions3.getPolygon()));
}
@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());
@@ -574,6 +627,22 @@ public class MapboxMapTest {
assertTrue("Polyline should be ignored", !mMapboxMap.getPolylines().contains(polygonOptions3.getPolyline()));
}
@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();
@@ -709,7 +778,6 @@ public class MapboxMapTest {
assertTrue("Selected markers should be empty", mMapboxMap.getSelectedMarkers().isEmpty());
}
//
// OnMarkerClick interface
//

View File

@@ -21,30 +21,6 @@ public class UiSettingsTest {
assertNotNull("uiSettings should not be null", uiSettings);
}
@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);

View File

@@ -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);

View File

@@ -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`.
The `MGLOfflinePackProgress` structure representation of the value.
*/
- (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;
@property (readonly) MGLOfflinePackProgress MGLOfflinePackProgressValue;
@end

View File

@@ -7,15 +7,99 @@ NS_ASSUME_NONNULL_BEGIN
@class MGLOfflinePack;
@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.
@param completion The completion handler to call with the list of packs. This
handler is executed asynchronously on the main queue.
You typically do not need to call this method.
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

View File

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

View File

@@ -8,6 +8,8 @@
#define nullable
#define nonnull
#define null_resettable
#define _Nullable
#define _Nonnull
#endif
NS_ASSUME_NONNULL_BEGIN

View File

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

View File

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

View File

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

View File

@@ -8,12 +8,23 @@
#include <mbgl/util/string.hpp>
@interface MGLOfflineStorage ()
static NSString * const MGLOfflineStorageFileName = @"cache.db";
static NSString * const MGLOfflineStorageFileName3_2_0_beta_1 = @"offline.db";
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";
@interface MGLOfflineStorage () <MGLOfflinePackDelegate>
@property (nonatomic, strong, readwrite) NS_MUTABLE_ARRAY_OF(MGLOfflinePack *) *packs;
@property (nonatomic) mbgl::DefaultFileSource *mbglFileSource;
- (instancetype)initWithFileName:(NSString *)fileName NS_DESIGNATED_INITIALIZER;
@end
@implementation MGLOfflineStorage
@@ -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

View File

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

View File

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

View File

@@ -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 (...) {

View File

@@ -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);
db->exec(schema);
db->exec("PRAGMA user_version = " + util::toString(schemaVersion));
connect(ReadWrite | Create);
// 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"));
};

View File

@@ -58,8 +58,10 @@ public:
private:
void connect(int flags);
int userVersion();
void ensureSchema();
void removeExisting();
void migrateToVersion3();
class Statement {
public:

View File

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

View File

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

204
platform/ios/CHANGELOG.md Normal file
View File

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

View File

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

View File

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

View File

@@ -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);
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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"];

View File

@@ -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:)]) {

View File

@@ -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);

View File

@@ -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) {
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;
}
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) {
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 {

View File

@@ -3,6 +3,7 @@
@interface MGLTViewController : UIViewController
- (void)insetMapView;
- (void)tinyMapView;
- (void)resetMapView;
@end

View File

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

View File

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

View File

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

View File

@@ -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;
}

View File

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

View File

@@ -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;
}

View File

@@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>
@interface OfflinePackNameValueTransformer : NSValueTransformer
@end

View File

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

View File

@@ -29,6 +29,8 @@
'./LocationCoordinate2DTransformer.m',
'./MapDocument.h',
'./MapDocument.m',
'./OfflinePackNameValueTransformer.h',
'./OfflinePackNameValueTransformer.m',
'./TimeIntervalTransformer.h',
'./TimeIntervalTransformer.m',
'./NSValue+Additions.h',

View File

@@ -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]);
// 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];
_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);
// 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 {

View File

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

View File

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

View File

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

View File

@@ -44,6 +44,9 @@
'sources': [
'./MGLGeometryTests.mm',
'./MGLOfflinePackTests.m',
'./MGLOfflineRegionTests.m',
'./MGLOfflineStorageTests.m',
'./MGLStyleTests.mm',
],

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -68,7 +68,6 @@ public:
bool paused = false;
std::mutex mutexPause;
std::condition_variable condPause;
bool loading = false;
};
} // namespace mbgl

BIN
test/fixtures/offline/v2.db vendored Normal file

Binary file not shown.

View File

@@ -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"));
}

Some files were not shown because too many files have changed in this diff Show More