summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--platform/darwin/src/MGLAttributionInfo.h58
-rw-r--r--platform/darwin/src/MGLAttributionInfo.mm178
-rw-r--r--platform/darwin/src/MGLGeoJSONSource.mm6
-rw-r--r--platform/darwin/src/MGLGeoJSONSource_Private.h3
-rw-r--r--platform/darwin/src/MGLRasterSource.mm14
-rw-r--r--platform/darwin/src/MGLRasterSource_Private.h13
-rw-r--r--platform/darwin/src/MGLSource.mm8
-rw-r--r--platform/darwin/src/MGLSource_Private.h12
-rw-r--r--platform/darwin/src/MGLStyle.mm47
-rw-r--r--platform/darwin/src/MGLStyle_Private.h5
-rw-r--r--platform/darwin/src/MGLTileSet.h48
-rw-r--r--platform/darwin/src/MGLTileSet.mm8
-rw-r--r--platform/darwin/src/MGLTileSet_Private.h17
-rw-r--r--platform/darwin/src/MGLVectorSource.mm14
-rw-r--r--platform/darwin/src/MGLVectorSource_Private.h13
-rw-r--r--platform/darwin/src/NSString+MGLAdditions.h13
-rw-r--r--platform/darwin/src/NSString+MGLAdditions.m25
-rw-r--r--platform/darwin/test/MGLAttributionInfoTests.m115
-rw-r--r--platform/ios/CHANGELOG.md1
-rw-r--r--platform/ios/ios.xcodeproj/project.pbxproj16
-rw-r--r--platform/ios/resources/Base.lproj/Localizable.strings9
-rw-r--r--platform/ios/src/MGLMapView.mm65
-rw-r--r--platform/macos/CHANGELOG.md1
-rw-r--r--platform/macos/INSTALL.md1
-rw-r--r--platform/macos/app/MapDocument.m9
-rw-r--r--platform/macos/macos.xcodeproj/project.pbxproj32
-rw-r--r--platform/macos/sdk/Base.lproj/Localizable.strings15
-rw-r--r--platform/macos/src/MGLAttributionButton.h16
-rw-r--r--platform/macos/src/MGLAttributionButton.m50
-rw-r--r--platform/macos/src/MGLAttributionButton.mm55
-rw-r--r--platform/macos/src/MGLMapView.h19
-rw-r--r--platform/macos/src/MGLMapView.mm119
-rw-r--r--platform/macos/test/MGLAttributionButtonTests.m31
33 files changed, 824 insertions, 212 deletions
diff --git a/platform/darwin/src/MGLAttributionInfo.h b/platform/darwin/src/MGLAttributionInfo.h
new file mode 100644
index 0000000000..c0cb1578b5
--- /dev/null
+++ b/platform/darwin/src/MGLAttributionInfo.h
@@ -0,0 +1,58 @@
+#import <Foundation/Foundation.h>
+#import <CoreGraphics/CoreGraphics.h>
+#import <CoreLocation/CoreLocation.h>
+
+#import "MGLTypes.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ Information about an attribution statement, usually a copyright or trademark
+ statement, associated with a map source.
+ */
+@interface MGLAttributionInfo : NSObject
+
+/**
+ Parses and returns the attribution infos contained in the given HTML source
+ code string.
+
+ @param htmlString The HTML source code to parse.
+ @param fontSize The default text size in points.
+ @param linkColor The default link color.
+ */
++ (NS_ARRAY_OF(MGLAttributionInfo *) *)attributionInfosFromHTMLString:(NSString *)htmlString fontSize:(CGFloat)fontSize linkColor:(nullable MGLColor *)linkColor;
+
+- (instancetype)initWithTitle:(NSAttributedString *)title URL:(nullable NSURL *)URL;
+
+@property (nonatomic) NSAttributedString *title;
+@property (nonatomic, nullable) NSURL *URL;
+@property (nonatomic, getter=isFeedbackLink) BOOL feedbackLink;
+
+- (nullable NSURL *)feedbackURLAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel;
+
+@end
+
+@interface NSMutableArray (MGLAttributionInfoAdditions)
+
+/**
+ Adds the given attribution info object to the receiver as long as it isn’t
+ redundant to any object already in the receiver. Any existing object that is
+ redundant to the given object is replaced by the given object.
+
+ @param info The info object to add to the receiver.
+ @return True if the given info object was added to the receiver.
+ */
+- (void)growArrayByAddingAttributionInfo:(MGLAttributionInfo *)info;
+
+/**
+ Adds each of the given attribution info objects to the receiver as long as it
+ isn’t redundant to any object already in the receiver. Any existing object that
+ is redundant to the given object is replaced by the given object.
+
+ @param infos An array of info objects to add to the receiver.
+ */
+- (void)growArrayByAddingAttributionInfosFromArray:(NS_ARRAY_OF(MGLAttributionInfo *) *)infos;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/darwin/src/MGLAttributionInfo.mm b/platform/darwin/src/MGLAttributionInfo.mm
new file mode 100644
index 0000000000..2719aef7ca
--- /dev/null
+++ b/platform/darwin/src/MGLAttributionInfo.mm
@@ -0,0 +1,178 @@
+#import "MGLAttributionInfo.h"
+
+#if TARGET_OS_IPHONE
+ #import <UIKit/UIKit.h>
+#else
+ #import <Cocoa/Cocoa.h>
+#endif
+
+#import "MGLMapCamera.h"
+#import "NSString+MGLAdditions.h"
+
+#include <string>
+
+@implementation MGLAttributionInfo
+
++ (NS_ARRAY_OF(MGLAttributionInfo *) *)attributionInfosFromHTMLString:(NSString *)htmlString fontSize:(CGFloat)fontSize linkColor:(nullable MGLColor *)linkColor {
+ NSDictionary *options = @{
+ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
+ NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding),
+ };
+ // Apply a bogus, easily detectable style rule to any feedback link, since
+ // NSAttributedString doesn’t preserve the class attribute.
+ NSMutableString *css = [NSMutableString stringWithString:
+ @".mapbox-improve-map { -webkit-text-stroke-width: 1000px; }"];
+ if (fontSize) {
+ [css appendFormat:@"html { font-size: %.1fpx; }", fontSize];
+ }
+ if (linkColor) {
+ CGFloat red;
+ CGFloat green;
+ CGFloat blue;
+ CGFloat alpha;
+#if !TARGET_OS_IPHONE
+ linkColor = [linkColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
+#endif
+ [linkColor getRed:&red green:&green blue:&blue alpha:&alpha];
+ [css appendFormat:
+ @"a:link { color: rgba(%f%%, %f%%, %f%%, %f); }",
+ red * 100, green * 100, blue * 100, alpha];
+ }
+ NSString *styledHTML = [NSString stringWithFormat:@"<style type='text/css'>%@</style>%@", css, htmlString];
+ NSData *htmlData = [styledHTML dataUsingEncoding:NSUTF8StringEncoding];
+
+#if TARGET_OS_IPHONE
+ NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithData:htmlData
+ options:options
+ documentAttributes:nil
+ error:NULL];
+#else
+ NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithHTML:htmlData
+ options:options
+ documentAttributes:nil];
+#endif
+
+ NSMutableArray *infos = [NSMutableArray array];
+ [attributedString enumerateAttribute:NSLinkAttributeName
+ inRange:attributedString.mgl_wholeRange
+ options:0
+ usingBlock:
+ ^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
+ NSCAssert(!value || [value isKindOfClass:[NSURL class]], @"If present, URL attribute must be an NSURL.");
+
+ // Detect feedback links by the bogus style rule applied above.
+ NSNumber *strokeWidth = [attributedString attribute:NSStrokeWidthAttributeName
+ atIndex:range.location
+ effectiveRange:NULL];
+ BOOL isFeedbackLink = NO;
+ if ([strokeWidth floatValue] > 100) {
+ isFeedbackLink = YES;
+ [attributedString removeAttribute:NSStrokeWidthAttributeName range:range];
+ }
+
+ // Omit whitespace-only strings.
+ NSAttributedString *title = [[attributedString attributedSubstringFromRange:range]
+ mgl_attributedStringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+ if (!title.length) {
+ return;
+ }
+
+ MGLAttributionInfo *info = [[MGLAttributionInfo alloc] initWithTitle:title URL:value];
+ info.feedbackLink = isFeedbackLink;
+ [infos addObject:info];
+ }];
+ return infos;
+}
+
+- (instancetype)initWithTitle:(NSAttributedString *)title URL:(NSURL *)URL {
+ if (self = [super init]) {
+ _title = title;
+ _URL = URL;
+ }
+ return self;
+}
+
+- (nullable NSURL *)feedbackURLAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel {
+ if (!self.feedbackLink) {
+ return nil;
+ }
+
+ NSURLComponents *components = [NSURLComponents componentsWithURL:self.URL resolvingAgainstBaseURL:NO];
+ components.fragment = [NSString stringWithFormat:@"/%.5f/%.5f/%i",
+ centerCoordinate.longitude, centerCoordinate.latitude, (int)round(zoomLevel + 1)];
+ return components.URL;
+}
+
+- (BOOL)isEqual:(id)object {
+ return [object isKindOfClass:[self class]] && [[object title] isEqual:self.title] && [[object URL] isEqual:self.URL];
+}
+
+- (NSUInteger)hash {
+ return self.title.hash + self.URL.hash;
+}
+
+/**
+ Returns whether the given attribution info object overlaps with the receiver by
+ its plain text title.
+
+ @return `NSOrderedAscending` if the given object is a superset of the receiver,
+ `NSOrderedDescending` if it is a subset of the receiver, or `NSOrderedSame`
+ if there is no overlap.
+ */
+- (NSComparisonResult)subsetCompare:(MGLAttributionInfo *)otherInfo {
+ NSString *title = self.title.string;
+ NSString *otherTitle = otherInfo.title.string;
+ if ([title containsString:otherTitle]) {
+ return NSOrderedDescending;
+ }
+ if ([otherTitle containsString:title]) {
+ return NSOrderedAscending;
+ }
+ return NSOrderedSame;
+}
+
+@end
+
+@implementation NSMutableArray (MGLAttributionInfoAdditions)
+
+- (void)growArrayByAddingAttributionInfo:(MGLAttributionInfo *)info {
+ __block BOOL didInsertInfo = NO;
+ __block BOOL shouldAddInfo = YES;
+ [self enumerateObjectsUsingBlock:^(MGLAttributionInfo * _Nonnull existingInfo, NSUInteger idx, BOOL * _Nonnull stop) {
+ switch ([info subsetCompare:existingInfo]) {
+ case NSOrderedDescending:
+ // The existing info object is a subset of the one we’re adding.
+ // Replace the existing object the first time we find a subset;
+ // remove the existing object every time after that.
+ if (didInsertInfo) {
+ [self removeObjectAtIndex:idx];
+ } else {
+ [self replaceObjectAtIndex:idx withObject:info];
+ didInsertInfo = YES;
+ }
+ break;
+
+ case NSOrderedAscending:
+ // The info object we’re adding is a subset of the existing one.
+ // Don’t add the object and stop looking.
+ shouldAddInfo = NO;
+ *stop = YES;
+ break;
+
+ default:
+ break;
+ }
+ }];
+ if (shouldAddInfo && !didInsertInfo) {
+ // No overlapping infos were found, so append the info object.
+ [self addObject:info];
+ }
+}
+
+- (void)growArrayByAddingAttributionInfosFromArray:(NS_ARRAY_OF(MGLAttributionInfo *) *)infos {
+ for (MGLAttributionInfo *info in infos) {
+ [self growArrayByAddingAttributionInfo:info];
+ }
+}
+
+@end
diff --git a/platform/darwin/src/MGLGeoJSONSource.mm b/platform/darwin/src/MGLGeoJSONSource.mm
index 7fd89ddc74..570b884149 100644
--- a/platform/darwin/src/MGLGeoJSONSource.mm
+++ b/platform/darwin/src/MGLGeoJSONSource.mm
@@ -18,6 +18,8 @@ const MGLGeoJSONSourceOption MGLGeoJSONSourceOptionSimplificationTolerance = @"M
@interface MGLGeoJSONSource ()
+- (instancetype)initWithRawSource:(mbgl::style::GeoJSONSource *)rawSource NS_DESIGNATED_INITIALIZER;
+
@property (nonatomic, readwrite) NSDictionary *options;
@property (nonatomic) mbgl::style::GeoJSONSource *rawSource;
@@ -60,6 +62,10 @@ const MGLGeoJSONSourceOption MGLGeoJSONSourceOptionSimplificationTolerance = @"M
return self;
}
+- (instancetype)initWithRawSource:(mbgl::style::GeoJSONSource *)rawSource {
+ return [super initWithRawSource:rawSource];
+}
+
- (void)addToMapView:(MGLMapView *)mapView
{
if (_pendingSource == nullptr) {
diff --git a/platform/darwin/src/MGLGeoJSONSource_Private.h b/platform/darwin/src/MGLGeoJSONSource_Private.h
index de5bb10fac..9d67deee34 100644
--- a/platform/darwin/src/MGLGeoJSONSource_Private.h
+++ b/platform/darwin/src/MGLGeoJSONSource_Private.h
@@ -1,10 +1,11 @@
#import "MGLGeoJSONSource.h"
-#import "MGLGeoJSONSource_Private.h"
#include <mbgl/style/sources/geojson_source.hpp>
@interface MGLGeoJSONSource (Private)
+- (instancetype)initWithRawSource:(mbgl::style::GeoJSONSource *)rawSource;
+
- (mbgl::style::GeoJSONOptions)geoJSONOptions;
@end
diff --git a/platform/darwin/src/MGLRasterSource.mm b/platform/darwin/src/MGLRasterSource.mm
index 62472050e3..1671e1decd 100644
--- a/platform/darwin/src/MGLRasterSource.mm
+++ b/platform/darwin/src/MGLRasterSource.mm
@@ -1,4 +1,4 @@
-#import "MGLRasterSource.h"
+#import "MGLRasterSource_Private.h"
#import "MGLMapView_Private.h"
#import "MGLSource_Private.h"
@@ -9,6 +9,8 @@
@interface MGLRasterSource ()
+- (instancetype)initWithRawSource:(mbgl::style::RasterSource *)rawSource NS_DESIGNATED_INITIALIZER;
+
@property (nonatomic) mbgl::style::RasterSource *rawSource;
@end
@@ -39,6 +41,16 @@
return self;
}
+- (instancetype)initWithRawSource:(mbgl::style::RasterSource *)rawSource {
+ if (self = [super initWithRawSource:rawSource]) {
+ if (auto attribution = rawSource->getAttribution()) {
+ _tileSet = [[MGLTileSet alloc] initWithTileURLTemplates:@[]];
+ _tileSet.attribution = @(attribution->c_str());
+ }
+ }
+ return self;
+}
+
- (void)commonInit
{
std::unique_ptr<mbgl::style::RasterSource> source;
diff --git a/platform/darwin/src/MGLRasterSource_Private.h b/platform/darwin/src/MGLRasterSource_Private.h
new file mode 100644
index 0000000000..4a367cf8f8
--- /dev/null
+++ b/platform/darwin/src/MGLRasterSource_Private.h
@@ -0,0 +1,13 @@
+#import "MGLRasterSource.h"
+
+namespace mbgl {
+ namespace style {
+ class RasterSource;
+ }
+}
+
+@interface MGLRasterSource (Private)
+
+- (instancetype)initWithRawSource:(mbgl::style::RasterSource *)rawSource;
+
+@end
diff --git a/platform/darwin/src/MGLSource.mm b/platform/darwin/src/MGLSource.mm
index 2fa580df89..c96b6c41c6 100644
--- a/platform/darwin/src/MGLSource.mm
+++ b/platform/darwin/src/MGLSource.mm
@@ -20,6 +20,14 @@
return self;
}
+- (instancetype)initWithRawSource:(mbgl::style::Source *)rawSource {
+ NSString *identifier = @(rawSource->getID().c_str());
+ if (self = [self initWithIdentifier:identifier]) {
+ _rawSource = rawSource;
+ }
+ return self;
+}
+
- (void)addToMapView:(MGLMapView *)mapView {
[NSException raise:NSInvalidArgumentException format:
@"The source %@ cannot be added to the style. "
diff --git a/platform/darwin/src/MGLSource_Private.h b/platform/darwin/src/MGLSource_Private.h
index d360e71f3c..3100e0ae6e 100644
--- a/platform/darwin/src/MGLSource_Private.h
+++ b/platform/darwin/src/MGLSource_Private.h
@@ -1,13 +1,21 @@
#import "MGLSource.h"
-#include <mbgl/mbgl.hpp>
-#include <mbgl/style/source.hpp>
+namespace mbgl {
+ namespace style {
+ class Source;
+ }
+}
@class MGLMapView;
@interface MGLSource (Private)
/**
+ Initializes and returns a source with a raw pointer to the backing store.
+ */
+- (instancetype)initWithRawSource:(mbgl::style::Source *)rawSource;
+
+/**
A raw pointer to the mbgl object, which is always initialized, either to the
value returned by `mbgl::Map getSource`, or for independently created objects,
to the pointer value held in `pendingSource`. In the latter case, this raw
diff --git a/platform/darwin/src/MGLStyle.mm b/platform/darwin/src/MGLStyle.mm
index a6de4e798d..ee0bb286ba 100644
--- a/platform/darwin/src/MGLStyle.mm
+++ b/platform/darwin/src/MGLStyle.mm
@@ -17,10 +17,13 @@
#import "NSDate+MGLAdditions.h"
#import "MGLSource.h"
-#import "MGLVectorSource.h"
+#import "MGLVectorSource_Private.h"
#import "MGLRasterSource.h"
#import "MGLGeoJSONSource.h"
+#import "MGLAttributionInfo.h"
+#import "MGLTileSet_Private.h"
+
#include <mbgl/util/default_styles.hpp>
#include <mbgl/sprite/sprite_image.hpp>
#include <mbgl/style/layers/fill_layer.hpp>
@@ -158,25 +161,18 @@ static NSURL *MGLStyleURL_emerald;
return rawSource ? [self sourceFromMBGLSource:rawSource] : nil;
}
-- (MGLSource *)sourceFromMBGLSource:(mbgl::style::Source *)mbglSource {
- NSString *identifier = @(mbglSource->getID().c_str());
-
+- (MGLSource *)sourceFromMBGLSource:(mbgl::style::Source *)source {
// TODO: Fill in options specific to the respective source classes
// https://github.com/mapbox/mapbox-gl-native/issues/6584
- MGLSource *source;
- if (mbglSource->is<mbgl::style::VectorSource>()) {
- source = [[MGLVectorSource alloc] initWithIdentifier:identifier];
- } else if (mbglSource->is<mbgl::style::GeoJSONSource>()) {
- source = [[MGLGeoJSONSource alloc] initWithIdentifier:identifier];
- } else if (mbglSource->is<mbgl::style::RasterSource>()) {
- source = [[MGLRasterSource alloc] initWithIdentifier:identifier];
+ if (auto vectorSource = source->as<mbgl::style::VectorSource>()) {
+ return [[MGLVectorSource alloc] initWithRawSource:vectorSource];
+ } else if (auto geoJSONSource = source->as<mbgl::style::GeoJSONSource>()) {
+ return [[MGLGeoJSONSource alloc] initWithRawSource:geoJSONSource];
+ } else if (auto rasterSource = source->as<mbgl::style::RasterSource>()) {
+ return [[MGLRasterSource alloc] initWithRawSource:rasterSource];
} else {
- source = [[MGLSource alloc] initWithIdentifier:identifier];
+ return [[MGLSource alloc] initWithRawSource:source];
}
-
- source.rawSource = mbglSource;
-
- return source;
}
- (void)addSource:(MGLSource *)source
@@ -206,6 +202,25 @@ static NSURL *MGLStyleURL_emerald;
[source removeFromMapView:self.mapView];
}
+- (nullable NS_ARRAY_OF(MGLAttributionInfo *) *)attributionInfosWithFontSize:(CGFloat)fontSize linkColor:(nullable MGLColor *)linkColor {
+ // It’d be incredibly convenient to use -sources here, but this operation
+ // depends on the sources being sorted in ascending order by creation, as
+ // with the std::vector used in mbgl.
+ auto rawSources = self.mapView.mbglMap->getSources();
+ NSMutableArray *infos = [NSMutableArray arrayWithCapacity:rawSources.size()];
+ for (auto rawSource = rawSources.begin(); rawSource != rawSources.end(); ++rawSource) {
+ MGLSource *source = [self sourceFromMBGLSource:*rawSource];
+ if (![source isKindOfClass:[MGLVectorSource class]]
+ && ![source isKindOfClass:[MGLRasterSource class]]) {
+ continue;
+ }
+
+ NSArray *tileSetInfos = [[(id)source tileSet] attributionInfosWithFontSize:fontSize linkColor:linkColor];
+ [infos growArrayByAddingAttributionInfosFromArray:tileSetInfos];
+ }
+ return infos;
+}
+
#pragma mark Style layers
- (NS_MUTABLE_ARRAY_OF(MGLStyleLayer *) *)layers
diff --git a/platform/darwin/src/MGLStyle_Private.h b/platform/darwin/src/MGLStyle_Private.h
index ee4a30c887..23ce8fbee0 100644
--- a/platform/darwin/src/MGLStyle_Private.h
+++ b/platform/darwin/src/MGLStyle_Private.h
@@ -2,11 +2,10 @@
#import "MGLStyleLayer.h"
#import "MGLFillStyleLayer.h"
-#import <mbgl/util/default_styles.hpp>
-#include <mbgl/mbgl.hpp>
NS_ASSUME_NONNULL_BEGIN
+@class MGLAttributionInfo;
@class MGLMapView;
@class MGLOpenGLStyleLayer;
@@ -16,6 +15,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly, weak) MGLMapView *mapView;
+- (nullable NS_ARRAY_OF(MGLAttributionInfo *) *)attributionInfosWithFontSize:(CGFloat)fontSize linkColor:(nullable MGLColor *)linkColor;
+
@property (nonatomic, readonly, strong) NS_MUTABLE_DICTIONARY_OF(NSString *, MGLOpenGLStyleLayer *) *openGLLayers;
- (void)setStyleClasses:(NS_ARRAY_OF(NSString *) *)appliedClasses transitionDuration:(NSTimeInterval)transitionDuration;
diff --git a/platform/darwin/src/MGLTileSet.h b/platform/darwin/src/MGLTileSet.h
index 08a34338b1..88bc7e4ae0 100644
--- a/platform/darwin/src/MGLTileSet.h
+++ b/platform/darwin/src/MGLTileSet.h
@@ -17,6 +17,32 @@ typedef NS_ENUM(NSUInteger, MGLTileSetScheme) {
*/
@interface MGLTileSet : NSObject
+#pragma mark Creating a Tile Set
+
+/**
+ Initializes and returns a new tile set object.
+
+ @param tileURLTemplates An `NSArray` of `NSString` objects that represent the
+ tile templates.
+ @return The initialized tile set object.
+ */
+- (instancetype)initWithTileURLTemplates:(NS_ARRAY_OF(NSString *) *)tileURLTemplates;
+
+/**
+ Initializes and returns a new tile set object.
+
+ @param tileURLTemplates An `NSArray` of `NSString` objects that represent the
+ tile templates.
+ @param minimumZoomLevel An `NSUInteger`; specifies the minimum zoom level at
+ which to display tiles.
+ @param maximumZoomLevel An `NSUInteger`; specifies the maximum zoom level at
+ which to display tiles.
+ @return The initialized tile set object.
+ */
+- (instancetype)initWithTileURLTemplates:(NS_ARRAY_OF(NSString *) *)tileURLTemplates minimumZoomLevel:(NSUInteger)minimumZoomLevel maximumZoomLevel:(NSUInteger)maximumZoomLevel;
+
+#pragma mark Accessing Tile Set Metadata
+
/**
An `NSArray` of `NSString` objects that represent the tile templates.
*/
@@ -51,28 +77,6 @@ typedef NS_ENUM(NSUInteger, MGLTileSetScheme) {
*/
@property (nonatomic) MGLTileSetScheme scheme;
-/**
- Initializes and returns a new tile set object.
-
- @param tileURLTemplates An `NSArray` of `NSString` objects that represent the
- tile templates.
- @return The initialized tile set object.
- */
-- (instancetype)initWithTileURLTemplates:(NS_ARRAY_OF(NSString *) *)tileURLTemplates;
-
-/**
- Initializes and returns a new tile set object.
-
- @param tileURLTemplates An `NSArray` of `NSString` objects that represent the
- tile templates.
- @param minimumZoomLevel An `NSUInteger`; specifies the minimum zoom level at
- which to display tiles.
- @param maximumZoomLevel An `NSUInteger`; specifies the maximum zoom level at
- which to display tiles.
- @return The initialized tile set object.
- */
-- (instancetype)initWithTileURLTemplates:(NS_ARRAY_OF(NSString *) *)tileURLTemplates minimumZoomLevel:(NSUInteger)minimumZoomLevel maximumZoomLevel:(NSUInteger)maximumZoomLevel;
-
@end
NS_ASSUME_NONNULL_END
diff --git a/platform/darwin/src/MGLTileSet.mm b/platform/darwin/src/MGLTileSet.mm
index f795545eed..6afc6c19af 100644
--- a/platform/darwin/src/MGLTileSet.mm
+++ b/platform/darwin/src/MGLTileSet.mm
@@ -1,5 +1,7 @@
#import "MGLTileSet.h"
+#import "MGLAttributionInfo.h"
+
#include <mbgl/util/tileset.hpp>
@implementation MGLTileSet
@@ -57,6 +59,12 @@
_maximumZoomLevel = maximumZoomLevel;
}
+- (nullable NS_ARRAY_OF(MGLAttributionInfo *) *)attributionInfosWithFontSize:(CGFloat)fontSize linkColor:(nullable MGLColor *)linkColor {
+ return [MGLAttributionInfo attributionInfosFromHTMLString:self.attribution
+ fontSize:fontSize
+ linkColor:linkColor];
+}
+
- (mbgl::Tileset)mbglTileset
{
mbgl::Tileset tileset;
diff --git a/platform/darwin/src/MGLTileSet_Private.h b/platform/darwin/src/MGLTileSet_Private.h
index 6a14d428db..038fe57fa2 100644
--- a/platform/darwin/src/MGLTileSet_Private.h
+++ b/platform/darwin/src/MGLTileSet_Private.h
@@ -2,8 +2,23 @@
#include <mbgl/util/tileset.hpp>
+NS_ASSUME_NONNULL_BEGIN
+
+@class MGLAttributionInfo;
+
@interface MGLTileSet (Private)
+/**
+ A structured representation of the `attribution` property. The default value is
+ `nil`.
+
+ @param fontSize The default text size in points.
+ @param linkColor The default link color.
+ */
+- (nullable NS_ARRAY_OF(MGLAttributionInfo *) *)attributionInfosWithFontSize:(CGFloat)fontSize linkColor:(nullable MGLColor *)linkColor;
+
- (mbgl::Tileset)mbglTileset;
-@end \ No newline at end of file
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/darwin/src/MGLVectorSource.mm b/platform/darwin/src/MGLVectorSource.mm
index ab68d45ba1..b5ec0b33be 100644
--- a/platform/darwin/src/MGLVectorSource.mm
+++ b/platform/darwin/src/MGLVectorSource.mm
@@ -1,4 +1,4 @@
-#import "MGLVectorSource.h"
+#import "MGLVectorSource_Private.h"
#import "MGLMapView_Private.h"
#import "MGLSource_Private.h"
@@ -9,6 +9,8 @@
@interface MGLVectorSource ()
+- (instancetype)initWithRawSource:(mbgl::style::VectorSource *)rawSource NS_DESIGNATED_INITIALIZER;
+
@property (nonatomic) mbgl::style::VectorSource *rawSource;
@end
@@ -38,6 +40,16 @@
return self;
}
+- (instancetype)initWithRawSource:(mbgl::style::VectorSource *)rawSource {
+ if (self = [super initWithRawSource:rawSource]) {
+ if (auto attribution = rawSource->getAttribution()) {
+ _tileSet = [[MGLTileSet alloc] initWithTileURLTemplates:@[]];
+ _tileSet.attribution = @(attribution->c_str());
+ }
+ }
+ return self;
+}
+
- (void)commonInit
{
std::unique_ptr<mbgl::style::VectorSource> source;
diff --git a/platform/darwin/src/MGLVectorSource_Private.h b/platform/darwin/src/MGLVectorSource_Private.h
new file mode 100644
index 0000000000..ce6bccdbae
--- /dev/null
+++ b/platform/darwin/src/MGLVectorSource_Private.h
@@ -0,0 +1,13 @@
+#import "MGLVectorSource.h"
+
+namespace mbgl {
+ namespace style {
+ class VectorSource;
+ }
+}
+
+@interface MGLVectorSource (Private)
+
+- (instancetype)initWithRawSource:(mbgl::style::VectorSource *)rawSource;
+
+@end
diff --git a/platform/darwin/src/NSString+MGLAdditions.h b/platform/darwin/src/NSString+MGLAdditions.h
index 5b549affd5..45fea25588 100644
--- a/platform/darwin/src/NSString+MGLAdditions.h
+++ b/platform/darwin/src/NSString+MGLAdditions.h
@@ -4,9 +4,22 @@ NS_ASSUME_NONNULL_BEGIN
@interface NSString (MGLAdditions)
+/** Returns the range spanning the entire receiver. */
+- (NSRange)mgl_wholeRange;
+
/** Returns the receiver if non-empty or nil if empty. */
- (nullable NSString *)mgl_stringOrNilIfEmpty;
@end
+@interface NSAttributedString (MGLAdditions)
+
+/** Returns the range spanning the entire receiver. */
+- (NSRange)mgl_wholeRange;
+
+/** Returns a copy of the receiver with leading and trailing members of the given set removed. */
+- (NSAttributedString *)mgl_attributedStringByTrimmingCharactersInSet:(NSCharacterSet *)set;
+
+@end
+
NS_ASSUME_NONNULL_END
diff --git a/platform/darwin/src/NSString+MGLAdditions.m b/platform/darwin/src/NSString+MGLAdditions.m
index 969886651b..04a65dc5e2 100644
--- a/platform/darwin/src/NSString+MGLAdditions.m
+++ b/platform/darwin/src/NSString+MGLAdditions.m
@@ -2,9 +2,30 @@
@implementation NSString (MGLAdditions)
-- (nullable NSString *)mgl_stringOrNilIfEmpty
-{
+- (NSRange)mgl_wholeRange {
+ return NSMakeRange(0, self.length);
+}
+
+- (nullable NSString *)mgl_stringOrNilIfEmpty {
return self.length ? self : nil;
}
@end
+
+@implementation NSAttributedString (MGLAdditions)
+
+- (NSRange)mgl_wholeRange {
+ return NSMakeRange(0, self.length);
+}
+
+- (NSAttributedString *)mgl_attributedStringByTrimmingCharactersInSet:(NSCharacterSet *)set {
+ NSScanner *scanner = [NSScanner scannerWithString:self.string];
+ scanner.charactersToBeSkipped = nil;
+ NSString *prefix;
+ [scanner scanCharactersFromSet:set intoString:&prefix];
+
+ NSString *trimmedString = [self.string stringByTrimmingCharactersInSet:set];
+ return [self attributedSubstringFromRange:NSMakeRange(prefix.length, trimmedString.length)];
+}
+
+@end
diff --git a/platform/darwin/test/MGLAttributionInfoTests.m b/platform/darwin/test/MGLAttributionInfoTests.m
new file mode 100644
index 0000000000..003637bf1b
--- /dev/null
+++ b/platform/darwin/test/MGLAttributionInfoTests.m
@@ -0,0 +1,115 @@
+#import <Mapbox/Mapbox.h>
+#import <XCTest/XCTest.h>
+
+#import "MGLAttributionInfo.h"
+
+@interface MGLAttributionInfoTests : XCTestCase
+
+@end
+
+@implementation MGLAttributionInfoTests
+
+- (void)testParsing {
+ static NSString * const htmlStrings[] = {
+ @"<a href=\"https://www.mapbox.com/about/maps/\" target=\"_blank\">&copy; Mapbox</a> "
+ @"<a href=\"http://www.openstreetmap.org/about/\" target=\"_blank\">©️ OpenStreetMap</a> "
+ @"CC&nbsp;BY-SA "
+ @"<a class=\"mapbox-improve-map\" href=\"https://www.mapbox.com/map-feedback/\" target=\"_blank\">Improve this map</a>",
+ };
+
+ NS_MUTABLE_ARRAY_OF(MGLAttributionInfo *) *infos = [NSMutableArray array];
+ for (NSUInteger i = 0; i < sizeof(htmlStrings) / sizeof(htmlStrings[0]); i++) {
+ NSArray *subinfos = [MGLAttributionInfo attributionInfosFromHTMLString:htmlStrings[i]
+ fontSize:0
+ linkColor:nil];
+ [infos growArrayByAddingAttributionInfosFromArray:subinfos];
+ }
+
+ XCTAssertEqual(infos.count, 4);
+
+ CLLocationCoordinate2D mapbox = CLLocationCoordinate2DMake(12.9810816, 77.6368034);
+ XCTAssertEqualObjects(infos[0].title.string, @"© Mapbox");
+ XCTAssertEqualObjects(infos[0].URL, [NSURL URLWithString:@"https://www.mapbox.com/about/maps/"]);
+ XCTAssertFalse(infos[0].feedbackLink);
+ XCTAssertNil([infos[0] feedbackURLAtCenterCoordinate:mapbox zoomLevel:14]);
+
+ XCTAssertEqualObjects(infos[1].title.string, @"©️ OpenStreetMap");
+ XCTAssertEqualObjects(infos[1].URL, [NSURL URLWithString:@"http://www.openstreetmap.org/about/"]);
+ XCTAssertFalse(infos[1].feedbackLink);
+ XCTAssertNil([infos[1] feedbackURLAtCenterCoordinate:mapbox zoomLevel:14]);
+
+ XCTAssertEqualObjects(infos[2].title.string, @"CC\u00a0BY-SA");
+ XCTAssertNil(infos[2].URL);
+ XCTAssertFalse(infos[2].feedbackLink);
+ XCTAssertNil([infos[2] feedbackURLAtCenterCoordinate:mapbox zoomLevel:14]);
+
+ XCTAssertEqualObjects(infos[3].title.string, @"Improve this map");
+ XCTAssertEqualObjects(infos[3].URL, [NSURL URLWithString:@"https://www.mapbox.com/map-feedback/"]);
+ XCTAssertTrue(infos[3].feedbackLink);
+ XCTAssertEqualObjects([infos[3] feedbackURLAtCenterCoordinate:mapbox zoomLevel:14],
+ [NSURL URLWithString:@"https://www.mapbox.com/map-feedback/#/77.63680/12.98108/15"]);
+}
+
+- (void)testStyle {
+ static NSString * const htmlStrings[] = {
+ @"<a href=\"https://www.mapbox.com/\">Mapbox</a>",
+ };
+
+ CGFloat fontSize = 72;
+ MGLColor *color = [MGLColor redColor];
+ NS_MUTABLE_ARRAY_OF(MGLAttributionInfo *) *infos = [NSMutableArray array];
+ for (NSUInteger i = 0; i < sizeof(htmlStrings) / sizeof(htmlStrings[0]); i++) {
+ NSArray *subinfos = [MGLAttributionInfo attributionInfosFromHTMLString:htmlStrings[i]
+ fontSize:72
+ linkColor:color];
+ [infos growArrayByAddingAttributionInfosFromArray:subinfos];
+ }
+
+ XCTAssertEqual(infos.count, 1);
+
+ XCTAssertEqualObjects(infos[0].title.string, @"Mapbox");
+ XCTAssertEqualObjects([infos[0].title attribute:NSLinkAttributeName atIndex:0 effectiveRange:nil], [NSURL URLWithString:@"https://www.mapbox.com/"]);
+ XCTAssertEqualObjects([infos[0].title attribute:NSUnderlineStyleAttributeName atIndex:0 effectiveRange:nil], @(NSUnderlineStyleSingle));
+
+#if TARGET_OS_IPHONE
+ UIFont *font;
+#else
+ NSFont *font;
+#endif
+ font = [infos[0].title attribute:NSFontAttributeName atIndex:0 effectiveRange:nil];
+ XCTAssertEqual(font.pointSize, fontSize);
+
+ CGFloat r, g, b, a;
+ [color getRed:&r green:&g blue:&b alpha:&a];
+ MGLColor *linkColor = [infos[0].title attribute:NSForegroundColorAttributeName atIndex:0 effectiveRange:nil];
+ CGFloat linkR, linkG, linkB, linkA;
+ [linkColor getRed:&linkR green:&linkG blue:&linkB alpha:&linkA];
+ XCTAssertEqual(r, linkR);
+ XCTAssertEqual(g, linkG);
+ XCTAssertEqual(b, linkB);
+ XCTAssertEqual(a, linkA);
+}
+
+- (void)testDedupe {
+ static NSString * const htmlStrings[] = {
+ @"World",
+ @"Hello World",
+ @"Another Source",
+ @"Hello",
+ @"Hello World",
+ };
+
+ NS_MUTABLE_ARRAY_OF(MGLAttributionInfo *) *infos = [NSMutableArray array];
+ for (NSUInteger i = 0; i < sizeof(htmlStrings) / sizeof(htmlStrings[0]); i++) {
+ NSArray *subinfos = [MGLAttributionInfo attributionInfosFromHTMLString:htmlStrings[i]
+ fontSize:0
+ linkColor:nil];
+ [infos growArrayByAddingAttributionInfosFromArray:subinfos];
+ }
+
+ XCTAssertEqual(infos.count, 2);
+ XCTAssertEqualObjects(infos[0].title.string, @"Hello World");
+ XCTAssertEqualObjects(infos[1].title.string, @"Another Source");
+}
+
+@end
diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md
index 894c33510a..5c63bdd2f5 100644
--- a/platform/ios/CHANGELOG.md
+++ b/platform/ios/CHANGELOG.md
@@ -22,6 +22,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT
* `-[MGLMapView resetPosition]` now resets to the current style’s default center coordinates, zoom level, direction, and pitch, if specified. ([#6127](https://github.com/mapbox/mapbox-gl-native/pull/6127))
* Fixed an issue where feature querying sometimes failed to return the expected features when the map was tilted. ([#6773](https://github.com/mapbox/mapbox-gl-native/pull/6773))
* MGLFeature’s `attributes` and `identifier` properties are now writable. ([#6728](https://github.com/mapbox/mapbox-gl-native/pull/6728))
+* The action sheet that appears when tapping the information button in the bottom-right corner now lists the correct attribution for the current style. ([#5999](https://github.com/mapbox/mapbox-gl-native/pull/5999))
* The `text-pitch-alignment` property is now supported in stylesheets for improved street label legibility on a tilted map. ([#5288](https://github.com/mapbox/mapbox-gl-native/pull/5288))
* The `icon-text-fit` and `icon-text-fit-padding` properties are now supported in stylesheets, allowing the background of a shield to automatically resize to fit the shield’s text. ([#5334](https://github.com/mapbox/mapbox-gl-native/pull/5334))
* The `circle-pitch-scale` property is now supported in stylesheets, allowing circle features in a tilted base map to scale or remain the same size as the viewing distance changes. ([#5576](https://github.com/mapbox/mapbox-gl-native/pull/5576))
diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj
index e469c82ba5..30ed5556b2 100644
--- a/platform/ios/ios.xcodeproj/project.pbxproj
+++ b/platform/ios/ios.xcodeproj/project.pbxproj
@@ -172,6 +172,10 @@
7E016D851D9E890300A29A21 /* MGLPolygon+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 7E016D821D9E890300A29A21 /* MGLPolygon+MGLAdditions.h */; };
7E016D861D9E890300A29A21 /* MGLPolygon+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E016D831D9E890300A29A21 /* MGLPolygon+MGLAdditions.m */; };
7E016D871D9E890300A29A21 /* MGLPolygon+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E016D831D9E890300A29A21 /* MGLPolygon+MGLAdditions.m */; };
+ DA00FC8E1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */; };
+ DA00FC8F1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */; };
+ DA00FC901D5EEB0D009AABC8 /* MGLAttributionInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA00FC8D1D5EEB0D009AABC8 /* MGLAttributionInfo.mm */; };
+ DA00FC911D5EEB0D009AABC8 /* MGLAttributionInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA00FC8D1D5EEB0D009AABC8 /* MGLAttributionInfo.mm */; };
DA0CD5901CF56F6A00A5F5A5 /* MGLFeatureTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA0CD58F1CF56F6A00A5F5A5 /* MGLFeatureTests.mm */; };
DA17BE301CC4BAC300402C41 /* MGLMapView_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DA17BE2F1CC4BAC300402C41 /* MGLMapView_Private.h */; };
DA17BE311CC4BDAA00402C41 /* MGLMapView_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DA17BE2F1CC4BAC300402C41 /* MGLMapView_Private.h */; };
@@ -407,6 +411,7 @@
DAED38641D62D0FC00D7640F /* NSURL+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = DAED38611D62D0FC00D7640F /* NSURL+MGLAdditions.h */; };
DAED38651D62D0FC00D7640F /* NSURL+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = DAED38621D62D0FC00D7640F /* NSURL+MGLAdditions.m */; };
DAED38661D62D0FC00D7640F /* NSURL+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = DAED38621D62D0FC00D7640F /* NSURL+MGLAdditions.m */; };
+ DAEDC4341D603417000224FF /* MGLAttributionInfoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DAEDC4331D603417000224FF /* MGLAttributionInfoTests.m */; };
DD0902A91DB1929D00C5BDCE /* MGLNetworkConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = DD0902A21DB18DE700C5BDCE /* MGLNetworkConfiguration.m */; };
DD0902AA1DB1929D00C5BDCE /* MGLNetworkConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = DD0902A21DB18DE700C5BDCE /* MGLNetworkConfiguration.m */; };
DD0902AB1DB192A800C5BDCE /* MGLNetworkConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = DD0902A41DB18F1B00C5BDCE /* MGLNetworkConfiguration.h */; };
@@ -601,6 +606,8 @@
7E016D7D1D9E86BE00A29A21 /* MGLPolyline+MGLAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MGLPolyline+MGLAdditions.m"; sourceTree = "<group>"; };
7E016D821D9E890300A29A21 /* MGLPolygon+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MGLPolygon+MGLAdditions.h"; sourceTree = "<group>"; };
7E016D831D9E890300A29A21 /* MGLPolygon+MGLAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MGLPolygon+MGLAdditions.m"; sourceTree = "<group>"; };
+ DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAttributionInfo.h; sourceTree = "<group>"; };
+ DA00FC8D1D5EEB0D009AABC8 /* MGLAttributionInfo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAttributionInfo.mm; sourceTree = "<group>"; };
DA0CD58F1CF56F6A00A5F5A5 /* MGLFeatureTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLFeatureTests.mm; path = ../../darwin/test/MGLFeatureTests.mm; sourceTree = "<group>"; };
DA17BE2F1CC4BAC300402C41 /* MGLMapView_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMapView_Private.h; sourceTree = "<group>"; };
DA1DC94A1CB6C1C2006E619F /* Mapbox GL.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Mapbox GL.app"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -775,6 +782,7 @@
DAD165771CF4CDFF001FF4B9 /* MGLShapeCollection.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLShapeCollection.mm; sourceTree = "<group>"; };
DAED38611D62D0FC00D7640F /* NSURL+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURL+MGLAdditions.h"; sourceTree = "<group>"; };
DAED38621D62D0FC00D7640F /* NSURL+MGLAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURL+MGLAdditions.m"; sourceTree = "<group>"; };
+ DAEDC4331D603417000224FF /* MGLAttributionInfoTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLAttributionInfoTests.m; path = ../../darwin/test/MGLAttributionInfoTests.m; sourceTree = "<group>"; };
DD0902A21DB18DE700C5BDCE /* MGLNetworkConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLNetworkConfiguration.m; sourceTree = "<group>"; };
DD0902A41DB18F1B00C5BDCE /* MGLNetworkConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLNetworkConfiguration.h; sourceTree = "<group>"; };
DD4823721D94AE6C00EB71B7 /* fill_filter_style.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = fill_filter_style.json; sourceTree = "<group>"; };
@@ -1060,6 +1068,7 @@
isa = PBXGroup;
children = (
357579811D502AD4000B822E /* Styling */,
+ DAEDC4331D603417000224FF /* MGLAttributionInfoTests.m */,
353D23951D0B0DFE002BE09D /* MGLAnnotationViewTests.m */,
DA35A2C31CCA9F8300E826B2 /* MGLClockDirectionFormatterTests.m */,
DA35A2C41CCA9F8300E826B2 /* MGLCompassDirectionFormatterTests.m */,
@@ -1111,6 +1120,8 @@
DA8848001CBAFA6200AB86E3 /* MGLAccountManager.m */,
DD0902A41DB18F1B00C5BDCE /* MGLNetworkConfiguration.h */,
DD0902A21DB18DE700C5BDCE /* MGLNetworkConfiguration.m */,
+ DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */,
+ DA00FC8D1D5EEB0D009AABC8 /* MGLAttributionInfo.mm */,
DA8847E21CBAFA5100AB86E3 /* MGLMapCamera.h */,
DA8848031CBAFA6200AB86E3 /* MGLMapCamera.mm */,
DA8847EC1CBAFA5100AB86E3 /* MGLStyle.h */,
@@ -1422,6 +1433,7 @@
353933FE1D3FB7DD003F57D7 /* MGLSymbolStyleLayer.h in Headers */,
DA8848861CBB033F00AB86E3 /* Fabric+FABKits.h in Headers */,
DA8848201CBAFA6200AB86E3 /* MGLOfflinePack_Private.h in Headers */,
+ DA00FC8E1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */,
DA8847FA1CBAFA5100AB86E3 /* MGLPolyline.h in Headers */,
3566C7711D4A9198008152BC /* MGLSource_Private.h in Headers */,
4018B1C91CDC288A00F666AF /* MGLAnnotationView_Private.h in Headers */,
@@ -1593,6 +1605,7 @@
DABFB8601CBE99E500D62B32 /* MGLMapCamera.h in Headers */,
DA737EE21D056A4E005BDA16 /* MGLMapViewDelegate.h in Headers */,
DABFB86A1CBE99E500D62B32 /* MGLStyle.h in Headers */,
+ DA00FC8F1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1911,6 +1924,7 @@
DA35A2C61CCA9F8300E826B2 /* MGLCompassDirectionFormatterTests.m in Sources */,
3575798E1D502EC7000B822E /* MGLRuntimeStylingHelper.m in Sources */,
4085AF091D933DEA00F11B22 /* MGLTileSetTests.mm in Sources */,
+ DAEDC4341D603417000224FF /* MGLAttributionInfoTests.m in Sources */,
357579851D502AF5000B822E /* MGLSymbolStyleLayerTests.m in Sources */,
357579871D502AFE000B822E /* MGLLineStyleLayerTests.m in Sources */,
357579891D502B06000B822E /* MGLCircleStyleLayerTests.m in Sources */,
@@ -1951,6 +1965,7 @@
400533021DB0862B0069F638 /* NSArray+MGLAdditions.mm in Sources */,
35136D421D42274500C20EFD /* MGLRasterStyleLayer.mm in Sources */,
3538AA1F1D542239008EC33D /* MGLForegroundStyleLayer.m in Sources */,
+ DA00FC901D5EEB0D009AABC8 /* MGLAttributionInfo.mm in Sources */,
DA88482D1CBAFA6200AB86E3 /* NSBundle+MGLAdditions.m in Sources */,
DA88485B1CBAFB9800AB86E3 /* MGLUserLocation.m in Sources */,
350098BD1D480108004B2AF0 /* MGLVectorSource.mm in Sources */,
@@ -2025,6 +2040,7 @@
400533031DB086490069F638 /* NSArray+MGLAdditions.mm in Sources */,
35136D431D42274500C20EFD /* MGLRasterStyleLayer.mm in Sources */,
3538AA201D542239008EC33D /* MGLForegroundStyleLayer.m in Sources */,
+ DA00FC911D5EEB0D009AABC8 /* MGLAttributionInfo.mm in Sources */,
DAA4E4201CBB730400178DFB /* MGLOfflinePack.mm in Sources */,
DAA4E4331CBB730400178DFB /* MGLUserLocation.m in Sources */,
350098BE1D480108004B2AF0 /* MGLVectorSource.mm in Sources */,
diff --git a/platform/ios/resources/Base.lproj/Localizable.strings b/platform/ios/resources/Base.lproj/Localizable.strings
index c4569fe239..63bed7e326 100644
--- a/platform/ios/resources/Base.lproj/Localizable.strings
+++ b/platform/ios/resources/Base.lproj/Localizable.strings
@@ -19,12 +19,6 @@
/* Compass abbreviation for north */
"COMPASS_NORTH" = "N";
-/* Copyright notice in attribution sheet */
-"COPY_MAPBOX" = "© Mapbox";
-
-/* Copyright notice in attribution sheet */
-"COPY_OSM" = "© OpenStreetMap";
-
/* Instructions in Interface Builder designable; {key}, {plist file name} */
"DESIGNABLE" = "To display a Mapbox-hosted map here, set %1$@ to your access token in %2$@\n\nFor detailed instructions, see:";
@@ -46,9 +40,6 @@
/* Map accessibility value */
"MAP_A11Y_VALUE" = "Zoom %1$dx\n%2$ld annotation(s) visible";
-/* Action in attribution sheet */
-"MAP_FEEDBACK" = "Improve This Map";
-
/* Action sheet title */
"SDK_NAME" = "Mapbox iOS SDK";
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
index 3ee03182a8..c67105b9e7 100644
--- a/platform/ios/src/MGLMapView.mm
+++ b/platform/ios/src/MGLMapView.mm
@@ -55,6 +55,7 @@
#import "MGLCompactCalloutView.h"
#import "MGLAnnotationContainerView.h"
#import "MGLAnnotationContainerView_Private.h"
+#import "MGLAttributionInfo.h"
#include <algorithm>
#include <cstdlib>
@@ -310,6 +311,8 @@ public:
BOOL _delegateHasLineWidthsForShapeAnnotations;
MGLCompassDirectionFormatter *_accessibilityCompassFormatter;
+
+ NS_ARRAY_OF(MGLAttributionInfo *) *_attributionInfos;
}
#pragma mark - Setup & Teardown -
@@ -1714,44 +1717,27 @@ public:
- (void)showAttribution
{
- if ( ! self.attributionSheet)
+ self.attributionSheet = [[UIActionSheet alloc] initWithTitle:NSLocalizedStringWithDefaultValue(@"SDK_NAME", nil, nil, @"Mapbox iOS SDK", @"Action sheet title")
+ delegate:self
+ cancelButtonTitle:NSLocalizedStringWithDefaultValue(@"CANCEL", nil, nil, @"Cancel", @"")
+ destructiveButtonTitle:nil
+ otherButtonTitles:nil];
+
+ _attributionInfos = [self.style attributionInfosWithFontSize:[UIFont buttonFontSize] linkColor:nil];
+ for (MGLAttributionInfo *info in _attributionInfos)
{
- self.attributionSheet = [[UIActionSheet alloc] initWithTitle:NSLocalizedStringWithDefaultValue(@"SDK_NAME", nil, nil, @"Mapbox iOS SDK", @"Action sheet title")
- delegate:self
- cancelButtonTitle:NSLocalizedStringWithDefaultValue(@"CANCEL", nil, nil, @"Cancel", @"")
- destructiveButtonTitle:nil
- otherButtonTitles:
- NSLocalizedStringWithDefaultValue(@"COPY_MAPBOX", nil, nil, @"© Mapbox", @"Copyright notice in attribution sheet"),
- NSLocalizedStringWithDefaultValue(@"COPY_OSM", nil, nil, @"© OpenStreetMap", @"Copyright notice in attribution sheet"),
- NSLocalizedStringWithDefaultValue(@"MAP_FEEDBACK", nil, nil, @"Improve This Map", @"Action in attribution sheet"),
- NSLocalizedStringWithDefaultValue(@"TELEMETRY_NAME", nil, nil, @"Mapbox Telemetry", @"Action in attribution sheet"),
- nil];
-
+ NSString *title = [info.title.string capitalizedStringWithLocale:[NSLocale currentLocale]];
+ [self.attributionSheet addButtonWithTitle:title];
}
-
+
+ [self.attributionSheet addButtonWithTitle:NSLocalizedStringWithDefaultValue(@"TELEMETRY_NAME", nil, nil, @"Mapbox Telemetry", @"Action in attribution sheet")];
+
[self.attributionSheet showFromRect:self.attributionButton.frame inView:self animated:YES];
}
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
{
- if (buttonIndex == actionSheet.firstOtherButtonIndex)
- {
- [[UIApplication sharedApplication] openURL:
- [NSURL URLWithString:@"https://www.mapbox.com/about/maps/"]];
- }
- else if (buttonIndex == actionSheet.firstOtherButtonIndex + 1)
- {
- [[UIApplication sharedApplication] openURL:
- [NSURL URLWithString:@"http://www.openstreetmap.org/about/"]];
- }
- else if (buttonIndex == actionSheet.firstOtherButtonIndex + 2)
- {
- NSString *feedbackURL = [NSString stringWithFormat:@"https://www.mapbox.com/map-feedback/#/%.5f/%.5f/%i",
- self.longitude, self.latitude, (int)round(self.zoomLevel + 1)];
- [[UIApplication sharedApplication] openURL:
- [NSURL URLWithString:feedbackURL]];
- }
- else if (buttonIndex == actionSheet.firstOtherButtonIndex + 3)
+ if (buttonIndex == actionSheet.numberOfButtons - 1)
{
NSString *message;
NSString *participate;
@@ -1777,6 +1763,19 @@ public:
otherButtonTitles:NSLocalizedStringWithDefaultValue(@"TELEMETRY_MORE", nil, nil, @"Tell Me More", @"Telemetry prompt button"), optOut, nil];
[alert show];
}
+ else if (buttonIndex > 0)
+ {
+ MGLAttributionInfo *info = _attributionInfos[buttonIndex + actionSheet.firstOtherButtonIndex];
+ NSURL *url = info.URL;
+ if (url)
+ {
+ if (info.feedbackLink)
+ {
+ url = [info feedbackURLAtCenterCoordinate:self.centerCoordinate zoomLevel:self.zoomLevel];
+ }
+ [[UIApplication sharedApplication] openURL:url];
+ }
+ }
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
@@ -4694,6 +4693,10 @@ public:
}
break;
}
+ case mbgl::MapChangeSourceDidChange:
+ {
+ break;
+ }
}
}
diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md
index 1d5aa93638..ab85890edb 100644
--- a/platform/macos/CHANGELOG.md
+++ b/platform/macos/CHANGELOG.md
@@ -20,6 +20,7 @@
* Fixed an issue where the style zoom levels were not respected when deciding when to render a layer. ([#5811](https://github.com/mapbox/mapbox-gl-native/issues/5811))
* Fixed an issue where feature querying sometimes failed to return the expected features when the map was tilted. ([#6773](https://github.com/mapbox/mapbox-gl-native/pull/6773))
* MGLFeature’s `attributes` and `identifier` properties are now writable. ([#6728](https://github.com/mapbox/mapbox-gl-native/pull/6728))
+* Attribution views now display the correct attribution for the current style. ([#5999](https://github.com/mapbox/mapbox-gl-native/pull/5999))
* If MGLMapView is unable to obtain or parse a style, it now calls its delegate’s `-mapViewDidFailLoadingMap:withError:` method. ([#6145](https://github.com/mapbox/mapbox-gl-native/pull/6145))
* Added the `-[MGLMapViewDelegate mapView:didFinishLoadingStyle:]` delegate method, which offers the earliest opportunity to modify the layout or appearance of the current style before the map view is displayed to the user. ([#6636](https://github.com/mapbox/mapbox-gl-native/pull/6636))
* Fixed an issue causing stepwise zoom functions to be misinterpreted. ([#6328](https://github.com/mapbox/mapbox-gl-native/pull/6328))
diff --git a/platform/macos/INSTALL.md b/platform/macos/INSTALL.md
index 665af128c5..a4b944611d 100644
--- a/platform/macos/INSTALL.md
+++ b/platform/macos/INSTALL.md
@@ -28,6 +28,7 @@ In a storyboard or XIB:
3. MGLMapView needs to be layer-backed:
* You can make the window layer-backed by selecting the window and checking Full Size Content View in the Attributes inspector. This allows the map view to underlap the title bar and toolbar.
* Alternatively, if you don’t want the entire window to be layer-backed, you can make just the map view layer-backed by selecting it and checking its entry under the View Effects inspector’s Core Animation Layer section.
+4. Add a map feedback item to your Help menu. (Drag Menu Item from the Object library into Main Menu ‣ Help ‣ Menu.) Title it “Improve This Map” or similar, and connect it to the `giveFeedback:` action of First Responder.
If you need to manipulate the map view programmatically:
diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m
index 6c36d00d73..c742440e84 100644
--- a/platform/macos/app/MapDocument.m
+++ b/platform/macos/app/MapDocument.m
@@ -666,15 +666,6 @@ NS_ARRAY_OF(id <MGLAnnotation>) *MBXFlattenedShapes(NS_ARRAY_OF(id <MGLAnnotatio
}];
}
-#pragma mark Help methods
-
-- (IBAction)giveFeedback:(id)sender {
- CLLocationCoordinate2D centerCoordinate = self.mapView.centerCoordinate;
- NSURL *feedbackURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://www.mapbox.com/map-feedback/#/%.5f/%.5f/%.0f",
- centerCoordinate.longitude, centerCoordinate.latitude, round(self.mapView.zoomLevel + 1)]];
- [[NSWorkspace sharedWorkspace] openURL:feedbackURL];
-}
-
#pragma mark Mouse events
- (void)handlePressGesture:(NSPressGestureRecognizer *)gestureRecognizer {
diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj
index 9a264f0bb7..2f7ef1c2df 100644
--- a/platform/macos/macos.xcodeproj/project.pbxproj
+++ b/platform/macos/macos.xcodeproj/project.pbxproj
@@ -60,6 +60,8 @@
5548BE781D09E718005DDE81 /* libmbgl-core.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAE6C3451CC31D1200DB3429 /* libmbgl-core.a */; };
558F18221D0B13B100123F46 /* libmbgl-loop.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 558F18211D0B13B000123F46 /* libmbgl-loop.a */; };
55D9B4B11D005D3900C1CCE2 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 55D9B4B01D005D3900C1CCE2 /* libz.tbd */; };
+ DA00FC8A1D5EEAC3009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC881D5EEAC3009AABC8 /* MGLAttributionInfo.h */; };
+ DA00FC8B1D5EEAC3009AABC8 /* MGLAttributionInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA00FC891D5EEAC3009AABC8 /* MGLAttributionInfo.mm */; };
DA0CD58E1CF56F5800A5F5A5 /* MGLFeatureTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA0CD58D1CF56F5800A5F5A5 /* MGLFeatureTests.mm */; };
DA2207BC1DC076940002F84D /* MGLStyleValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2207BB1DC076940002F84D /* MGLStyleValueTests.swift */; };
DA2784FE1DF03060001D5B8D /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA2784FD1DF03060001D5B8D /* Media.xcassets */; };
@@ -83,6 +85,8 @@
DA6408D81DA4E5DA00908C90 /* MGLVectorStyleLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = DA6408D61DA4E5DA00908C90 /* MGLVectorStyleLayer.m */; };
DA7262071DEEDD460043BB89 /* MGLOpenGLStyleLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = DA7262051DEEDD460043BB89 /* MGLOpenGLStyleLayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
DA7262081DEEDD460043BB89 /* MGLOpenGLStyleLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA7262061DEEDD460043BB89 /* MGLOpenGLStyleLayer.mm */; };
+ DA7DC9811DED5F5C0027472F /* MGLVectorSource_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DA7DC9801DED5F5C0027472F /* MGLVectorSource_Private.h */; };
+ DA7DC9831DED647F0027472F /* MGLRasterSource_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DA7DC9821DED647F0027472F /* MGLRasterSource_Private.h */; };
DA839E971CC2E3400062CAFB /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DA839E961CC2E3400062CAFB /* AppDelegate.m */; };
DA839E9A1CC2E3400062CAFB /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DA839E991CC2E3400062CAFB /* main.m */; };
DA839E9D1CC2E3400062CAFB /* MapDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = DA839E9C1CC2E3400062CAFB /* MapDocument.m */; };
@@ -184,7 +188,7 @@
DAE6C3A61CC31E9400DB3429 /* MGLMapViewDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3A21CC31E9400DB3429 /* MGLMapViewDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
DAE6C3B11CC31EF300DB3429 /* MGLAnnotationImage.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3A71CC31EF300DB3429 /* MGLAnnotationImage.m */; };
DAE6C3B21CC31EF300DB3429 /* MGLAttributionButton.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3A81CC31EF300DB3429 /* MGLAttributionButton.h */; };
- DAE6C3B31CC31EF300DB3429 /* MGLAttributionButton.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3A91CC31EF300DB3429 /* MGLAttributionButton.m */; };
+ DAE6C3B31CC31EF300DB3429 /* MGLAttributionButton.mm in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3A91CC31EF300DB3429 /* MGLAttributionButton.mm */; };
DAE6C3B41CC31EF300DB3429 /* MGLCompassCell.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3AA1CC31EF300DB3429 /* MGLCompassCell.h */; };
DAE6C3B51CC31EF300DB3429 /* MGLCompassCell.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3AB1CC31EF300DB3429 /* MGLCompassCell.m */; };
DAE6C3B61CC31EF300DB3429 /* MGLMapView_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DAE6C3AC1CC31EF300DB3429 /* MGLMapView_Private.h */; };
@@ -203,6 +207,8 @@
DAE6C3D61CC34C9900DB3429 /* MGLStyleTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DAE6C3CC1CC34BD800DB3429 /* MGLStyleTests.mm */; };
DAED385F1D62CED700D7640F /* NSURL+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = DAED385D1D62CED700D7640F /* NSURL+MGLAdditions.h */; };
DAED38601D62CED700D7640F /* NSURL+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = DAED385E1D62CED700D7640F /* NSURL+MGLAdditions.m */; };
+ DAEDC4321D6033F1000224FF /* MGLAttributionInfoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DAEDC4311D6033F1000224FF /* MGLAttributionInfoTests.m */; };
+ DAEDC4371D606291000224FF /* MGLAttributionButtonTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DAEDC4361D606291000224FF /* MGLAttributionButtonTests.m */; };
DD0902B21DB1AC6400C5BDCE /* MGLNetworkConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = DD0902AF1DB1AC6400C5BDCE /* MGLNetworkConfiguration.m */; };
DD0902B31DB1AC6400C5BDCE /* MGLNetworkConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = DD0902B01DB1AC6400C5BDCE /* MGLNetworkConfiguration.h */; };
DD58A4C91D822C6700E1F038 /* MGLExpressionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DD58A4C71D822C6200E1F038 /* MGLExpressionTests.mm */; };
@@ -300,6 +306,8 @@
558F18211D0B13B000123F46 /* libmbgl-loop.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libmbgl-loop.a"; path = "../../build/osx/Debug/libmbgl-loop.a"; sourceTree = "<group>"; };
55D9B4B01D005D3900C1CCE2 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
55FE0E8D1D100A0900FD240B /* config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = config.xcconfig; path = ../../build/macos/config.xcconfig; sourceTree = "<group>"; };
+ DA00FC881D5EEAC3009AABC8 /* MGLAttributionInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAttributionInfo.h; sourceTree = "<group>"; };
+ DA00FC891D5EEAC3009AABC8 /* MGLAttributionInfo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAttributionInfo.mm; sourceTree = "<group>"; };
DA0CD58D1CF56F5800A5F5A5 /* MGLFeatureTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLFeatureTests.mm; path = ../../darwin/test/MGLFeatureTests.mm; sourceTree = "<group>"; };
DA2207BA1DC076930002F84D /* test-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "test-Bridging-Header.h"; sourceTree = "<group>"; };
DA2207BB1DC076940002F84D /* MGLStyleValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MGLStyleValueTests.swift; sourceTree = "<group>"; };
@@ -324,6 +332,8 @@
DA6408D61DA4E5DA00908C90 /* MGLVectorStyleLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLVectorStyleLayer.m; sourceTree = "<group>"; };
DA7262051DEEDD460043BB89 /* MGLOpenGLStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLOpenGLStyleLayer.h; sourceTree = "<group>"; };
DA7262061DEEDD460043BB89 /* MGLOpenGLStyleLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLOpenGLStyleLayer.mm; sourceTree = "<group>"; };
+ DA7DC9801DED5F5C0027472F /* MGLVectorSource_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLVectorSource_Private.h; sourceTree = "<group>"; };
+ DA7DC9821DED647F0027472F /* MGLRasterSource_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLRasterSource_Private.h; sourceTree = "<group>"; };
DA839E921CC2E3400062CAFB /* Mapbox GL.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Mapbox GL.app"; sourceTree = BUILT_PRODUCTS_DIR; };
DA839E951CC2E3400062CAFB /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
DA839E961CC2E3400062CAFB /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
@@ -440,7 +450,7 @@
DAE6C3A21CC31E9400DB3429 /* MGLMapViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMapViewDelegate.h; sourceTree = "<group>"; };
DAE6C3A71CC31EF300DB3429 /* MGLAnnotationImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLAnnotationImage.m; sourceTree = "<group>"; };
DAE6C3A81CC31EF300DB3429 /* MGLAttributionButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAttributionButton.h; sourceTree = "<group>"; };
- DAE6C3A91CC31EF300DB3429 /* MGLAttributionButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLAttributionButton.m; sourceTree = "<group>"; };
+ DAE6C3A91CC31EF300DB3429 /* MGLAttributionButton.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAttributionButton.mm; sourceTree = "<group>"; };
DAE6C3AA1CC31EF300DB3429 /* MGLCompassCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLCompassCell.h; sourceTree = "<group>"; };
DAE6C3AB1CC31EF300DB3429 /* MGLCompassCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLCompassCell.m; sourceTree = "<group>"; };
DAE6C3AC1CC31EF300DB3429 /* MGLMapView_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMapView_Private.h; sourceTree = "<group>"; };
@@ -459,6 +469,8 @@
DAE6C3CC1CC34BD800DB3429 /* MGLStyleTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLStyleTests.mm; path = ../../darwin/test/MGLStyleTests.mm; sourceTree = "<group>"; };
DAED385D1D62CED700D7640F /* NSURL+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURL+MGLAdditions.h"; sourceTree = "<group>"; };
DAED385E1D62CED700D7640F /* NSURL+MGLAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURL+MGLAdditions.m"; sourceTree = "<group>"; };
+ DAEDC4311D6033F1000224FF /* MGLAttributionInfoTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLAttributionInfoTests.m; path = ../../darwin/test/MGLAttributionInfoTests.m; sourceTree = "<group>"; };
+ DAEDC4361D606291000224FF /* MGLAttributionButtonTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLAttributionButtonTests.m; sourceTree = "<group>"; };
DD0902AF1DB1AC6400C5BDCE /* MGLNetworkConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLNetworkConfiguration.m; sourceTree = "<group>"; };
DD0902B01DB1AC6400C5BDCE /* MGLNetworkConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLNetworkConfiguration.h; sourceTree = "<group>"; };
DD58A4C71D822C6200E1F038 /* MGLExpressionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLExpressionTests.mm; path = ../../darwin/test/MGLExpressionTests.mm; sourceTree = "<group>"; };
@@ -543,11 +555,13 @@
DA8F25991D51CAD00010E6B5 /* MGLSource_Private.h */,
352742801D4C243B00A1ECE6 /* MGLSource.mm */,
DA8F25951D51CAC70010E6B5 /* MGLVectorSource.h */,
+ DA7DC9801DED5F5C0027472F /* MGLVectorSource_Private.h */,
DA8F25961D51CAC70010E6B5 /* MGLVectorSource.mm */,
352742871D4C245800A1ECE6 /* MGLGeoJSONSource.h */,
DA87A99B1DC9D8DD00810D09 /* MGLGeoJSONSource_Private.h */,
352742881D4C245800A1ECE6 /* MGLGeoJSONSource.mm */,
352742831D4C244700A1ECE6 /* MGLRasterSource.h */,
+ DA7DC9821DED647F0027472F /* MGLRasterSource_Private.h */,
352742841D4C244700A1ECE6 /* MGLRasterSource.mm */,
DA551B7F1DB496AC0009AFAF /* MGLTileSet.h */,
DA551B801DB496AC0009AFAF /* MGLTileSet_Private.h */,
@@ -849,6 +863,8 @@
isa = PBXGroup;
children = (
DA8F257D1D51C5F40010E6B5 /* Styling */,
+ DAEDC4311D6033F1000224FF /* MGLAttributionInfoTests.m */,
+ DAEDC4361D606291000224FF /* MGLAttributionButtonTests.m */,
DA35A2C11CCA9F4A00E826B2 /* MGLClockDirectionFormatterTests.m */,
DA35A2B51CCA14D700E826B2 /* MGLCompassDirectionFormatterTests.m */,
DA35A2A71CC9F41600E826B2 /* MGLCoordinateFormatterTests.m */,
@@ -879,6 +895,8 @@
DAE6C36B1CC31E2A00DB3429 /* MGLAccountManager.m */,
DD0902B01DB1AC6400C5BDCE /* MGLNetworkConfiguration.h */,
DD0902AF1DB1AC6400C5BDCE /* MGLNetworkConfiguration.m */,
+ DA00FC881D5EEAC3009AABC8 /* MGLAttributionInfo.h */,
+ DA00FC891D5EEAC3009AABC8 /* MGLAttributionInfo.mm */,
DAE6C34D1CC31E0400DB3429 /* MGLMapCamera.h */,
DAE6C36E1CC31E2A00DB3429 /* MGLMapCamera.mm */,
DAE6C3571CC31E0400DB3429 /* MGLStyle.h */,
@@ -900,7 +918,7 @@
DAC2ABC41CC6D343006D18C4 /* MGLAnnotationImage_Private.h */,
DAE6C3A71CC31EF300DB3429 /* MGLAnnotationImage.m */,
DAE6C3A81CC31EF300DB3429 /* MGLAttributionButton.h */,
- DAE6C3A91CC31EF300DB3429 /* MGLAttributionButton.m */,
+ DAE6C3A91CC31EF300DB3429 /* MGLAttributionButton.mm */,
DAE6C3AA1CC31EF300DB3429 /* MGLCompassCell.h */,
DAE6C3AB1CC31EF300DB3429 /* MGLCompassCell.m */,
DAE6C3A01CC31E9400DB3429 /* MGLMapView.h */,
@@ -934,11 +952,13 @@
DA8F258F1D51CA600010E6B5 /* MGLRasterStyleLayer.h in Headers */,
3508EC641D749D39009B0EE4 /* NSExpression+MGLAdditions.h in Headers */,
DAE6C38D1CC31E2A00DB3429 /* MGLOfflineRegion_Private.h in Headers */,
+ DA7DC9831DED647F0027472F /* MGLRasterSource_Private.h in Headers */,
408AA8651DAEEE3400022900 /* MGLPolygon+MGLAdditions.h in Headers */,
DA8F259C1D51CB000010E6B5 /* MGLStyleValue_Private.h in Headers */,
DAE6C35B1CC31E0400DB3429 /* MGLAnnotation.h in Headers */,
DAE6C3B61CC31EF300DB3429 /* MGLMapView_Private.h in Headers */,
3527428D1D4C24AB00A1ECE6 /* MGLCircleStyleLayer.h in Headers */,
+ DA00FC8A1D5EEAC3009AABC8 /* MGLAttributionInfo.h in Headers */,
DAE6C3B21CC31EF300DB3429 /* MGLAttributionButton.h in Headers */,
40B77E451DB11BC9003DA2FE /* NSArray+MGLAdditions.h in Headers */,
35C5D8471D6DD66D00E95907 /* NSComparisonPredicate+MGLAdditions.h in Headers */,
@@ -959,6 +979,7 @@
DAE6C39C1CC31E2A00DB3429 /* NSString+MGLAdditions.h in Headers */,
3529039B1D6C63B80002C7DF /* NSPredicate+MGLAdditions.h in Headers */,
DA8F25971D51CAC70010E6B5 /* MGLVectorSource.h in Headers */,
+ DA7DC9811DED5F5C0027472F /* MGLVectorSource_Private.h in Headers */,
DAE6C3861CC31E2A00DB3429 /* MGLGeometry_Private.h in Headers */,
DAE6C3841CC31E2A00DB3429 /* MGLAccountManager_Private.h in Headers */,
DAE6C3691CC31E0400DB3429 /* MGLTypes.h in Headers */,
@@ -1211,7 +1232,7 @@
DACC22151CF3D3E200D220D9 /* MGLFeature.mm in Sources */,
DA7262081DEEDD460043BB89 /* MGLOpenGLStyleLayer.mm in Sources */,
355BA4EE1D41633E00CCC6D5 /* NSColor+MGLAdditions.mm in Sources */,
- DAE6C3B31CC31EF300DB3429 /* MGLAttributionButton.m in Sources */,
+ DAE6C3B31CC31EF300DB3429 /* MGLAttributionButton.mm in Sources */,
35602BFB1D3EA99F0050646F /* MGLFillStyleLayer.mm in Sources */,
DAE6C3931CC31E2A00DB3429 /* MGLShape.mm in Sources */,
352742861D4C244700A1ECE6 /* MGLRasterSource.mm in Sources */,
@@ -1245,6 +1266,7 @@
408AA8681DAEEE5200022900 /* MGLPolygon+MGLAdditions.m in Sources */,
DAE6C3951CC31E2A00DB3429 /* MGLTilePyramidOfflineRegion.mm in Sources */,
DAE6C3851CC31E2A00DB3429 /* MGLAccountManager.m in Sources */,
+ DA00FC8B1D5EEAC3009AABC8 /* MGLAttributionInfo.mm in Sources */,
DAE6C3921CC31E2A00DB3429 /* MGLPolyline.mm in Sources */,
3527428A1D4C245800A1ECE6 /* MGLGeoJSONSource.mm in Sources */,
DAE6C3B51CC31EF300DB3429 /* MGLCompassCell.m in Sources */,
@@ -1269,6 +1291,7 @@
DAE6C3D41CC34C9900DB3429 /* MGLOfflineRegionTests.m in Sources */,
DA87A9A11DC9DCB400810D09 /* MGLRuntimeStylingHelper.m in Sources */,
DAE6C3D61CC34C9900DB3429 /* MGLStyleTests.mm in Sources */,
+ DAEDC4371D606291000224FF /* MGLAttributionButtonTests.m in Sources */,
DA35A2B61CCA14D700E826B2 /* MGLCompassDirectionFormatterTests.m in Sources */,
DAE6C3D21CC34C9900DB3429 /* MGLGeometryTests.mm in Sources */,
DA87A9A41DCACC5000810D09 /* MGLSymbolStyleLayerTests.m in Sources */,
@@ -1286,6 +1309,7 @@
DA87A9981DC9D88400810D09 /* MGLGeoJSONSourceTests.mm in Sources */,
DA87A9A21DC9DCF100810D09 /* MGLFillStyleLayerTests.m in Sources */,
3599A3E81DF70E2000E77FB2 /* MGLStyleValueTests.m in Sources */,
+ DAEDC4321D6033F1000224FF /* MGLAttributionInfoTests.m in Sources */,
DA0CD58E1CF56F5800A5F5A5 /* MGLFeatureTests.mm in Sources */,
DA2207BC1DC076940002F84D /* MGLStyleValueTests.swift in Sources */,
);
diff --git a/platform/macos/sdk/Base.lproj/Localizable.strings b/platform/macos/sdk/Base.lproj/Localizable.strings
index 818c82b2ec..b7a4a21173 100644
--- a/platform/macos/sdk/Base.lproj/Localizable.strings
+++ b/platform/macos/sdk/Base.lproj/Localizable.strings
@@ -1,18 +1,3 @@
-/* Linked part of copyright notice */
-"COPYRIGHT_MAPBOX" = "Mapbox";
-
-/* Copyright notice link */
-"COPYRIGHT_MAPBOX_LINK" = "https://www.mapbox.com/about/maps/";
-
-/* Linked part of copyright notice */
-"COPYRIGHT_OSM" = "OpenStreetMap";
-
-/* Copyright notice link */
-"COPYRIGHT_OSM_LINK" = "http://www.openstreetmap.org/about/";
-
-/* Copyright notice prefix */
-"COPYRIGHT_PREFIX" = "© ";
-
/* Accessibility title */
"MAP_A11Y_TITLE" = "Mapbox";
diff --git a/platform/macos/src/MGLAttributionButton.h b/platform/macos/src/MGLAttributionButton.h
index 9ff3137849..88fcdadf78 100644
--- a/platform/macos/src/MGLAttributionButton.h
+++ b/platform/macos/src/MGLAttributionButton.h
@@ -1,15 +1,23 @@
#import <Cocoa/Cocoa.h>
+#import "MGLTypes.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class MGLAttributionInfo;
+
/// Button that looks like a hyperlink and opens a URL.
@interface MGLAttributionButton : NSButton
-/// Returns an `MGLAttributionButton` instance with the given title and URL.
-- (instancetype)initWithTitle:(NSString *)title URL:(NSURL *)url;
+/// Returns an `MGLAttributionButton` instance with the given info.
+- (instancetype)initWithAttributionInfo:(MGLAttributionInfo *)info;
/// The URL to open and display as a tooltip.
-@property (nonatomic) NSURL *URL;
+@property (nonatomic, readonly, nullable) NSURL *URL;
/// Opens the URL.
-- (IBAction)openURL:(id)sender;
+- (IBAction)openURL:(nullable id)sender;
@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/macos/src/MGLAttributionButton.m b/platform/macos/src/MGLAttributionButton.m
deleted file mode 100644
index e21b860794..0000000000
--- a/platform/macos/src/MGLAttributionButton.m
+++ /dev/null
@@ -1,50 +0,0 @@
-#import "MGLAttributionButton.h"
-
-#import "NSBundle+MGLAdditions.h"
-
-@implementation MGLAttributionButton {
- NSTrackingRectTag _trackingAreaTag;
-}
-
-- (instancetype)initWithTitle:(NSString *)title URL:(NSURL *)url {
- if (self = [super initWithFrame:NSZeroRect]) {
- self.bordered = NO;
- self.bezelStyle = NSRegularSquareBezelStyle;
-
- // Start with a copyright symbol. The whole string will be mini.
- NSMutableAttributedString *attributedTitle = [[NSMutableAttributedString alloc] initWithString:NSLocalizedStringWithDefaultValue(@"COPYRIGHT_PREFIX", nil, nil, @"© ", @"Copyright notice prefix") attributes:@{
- NSFontAttributeName: [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSMiniControlSize]],
- }];
- // Append the specified title, underlining it like a hyperlink.
- [attributedTitle appendAttributedString:
- [[NSAttributedString alloc] initWithString:title
- attributes:@{
- NSFontAttributeName: [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSMiniControlSize]],
- NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle),
- }]];
- self.attributedTitle = attributedTitle;
- [self sizeToFit];
-
- _URL = url;
- self.toolTip = _URL.absoluteString;
-
- self.target = self;
- self.action = @selector(openURL:);
- }
- return self;
-}
-
-- (BOOL)wantsLayer {
- return YES;
-}
-
-- (void)resetCursorRects {
- // The whole button gets a pointing hand cursor, just like a hyperlink.
- [self addCursorRect:self.bounds cursor:[NSCursor pointingHandCursor]];
-}
-
-- (IBAction)openURL:(__unused id)sender {
- [[NSWorkspace sharedWorkspace] openURL:self.URL];
-}
-
-@end
diff --git a/platform/macos/src/MGLAttributionButton.mm b/platform/macos/src/MGLAttributionButton.mm
new file mode 100644
index 0000000000..ed8bb18a66
--- /dev/null
+++ b/platform/macos/src/MGLAttributionButton.mm
@@ -0,0 +1,55 @@
+#import "MGLAttributionButton.h"
+#import "MGLAttributionInfo.h"
+
+#import "NSBundle+MGLAdditions.h"
+#import "NSString+MGLAdditions.h"
+
+@implementation MGLAttributionButton
+
+- (instancetype)initWithAttributionInfo:(MGLAttributionInfo *)info {
+ if (self = [super initWithFrame:NSZeroRect]) {
+ self.bordered = NO;
+ self.bezelStyle = NSRegularSquareBezelStyle;
+
+ // Extract any prefix consisting of intellectual property symbols.
+ NSScanner *scanner = [NSScanner scannerWithString:info.title.string];
+ NSCharacterSet *symbolSet = [NSCharacterSet characterSetWithCharactersInString:@"©℗®℠™ &"];
+ NSString *symbol;
+ [scanner scanCharactersFromSet:symbolSet intoString:&symbol];
+
+ // Remove the underline from the symbol for aesthetic reasons.
+ NSMutableAttributedString *title = info.title.mutableCopy;
+ [title removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, symbol.length)];
+
+ self.attributedTitle = title;
+ [self sizeToFit];
+
+ _URL = info.URL;
+ if (_URL) {
+ self.toolTip = _URL.absoluteString;
+ }
+
+ self.target = self;
+ self.action = @selector(openURL:);
+ }
+ return self;
+}
+
+- (BOOL)wantsLayer {
+ return YES;
+}
+
+- (void)resetCursorRects {
+ if (self.URL) {
+ // The whole button gets a pointing hand cursor, just like a hyperlink.
+ [self addCursorRect:self.bounds cursor:[NSCursor pointingHandCursor]];
+ }
+}
+
+- (IBAction)openURL:(__unused id)sender {
+ if (self.URL) {
+ [[NSWorkspace sharedWorkspace] openURL:self.URL];
+ }
+}
+
+@end
diff --git a/platform/macos/src/MGLMapView.h b/platform/macos/src/MGLMapView.h
index 88745db212..ea87f3b338 100644
--- a/platform/macos/src/MGLMapView.h
+++ b/platform/macos/src/MGLMapView.h
@@ -950,6 +950,25 @@ IB_DESIGNABLE
*/
- (CLLocationDistance)metersPerPointAtLatitude:(CLLocationDegrees)latitude;
+#pragma mark Giving Feedback to Improve the Map
+
+/**
+ Opens one or more webpages in the default Web browser in which the user can
+ provide feedback about the map data.
+
+ You should add a menu item to the Help menu of your application that invokes
+ this method. Title it “Improve This Map” or similar. Set its target to the
+ first responder and its action to `giveFeedback:`.
+
+ This map view searches the current style’s sources for webpages to open.
+ Specifically, each source’s tile set has an `attribution` property containing
+ HTML code; if an <code>&lt;a></code> tag (link) within that code has an
+ <code>class</code> attribute set to <code>mapbox-improve-map</code>, its
+ <code>href</code> attribute defines the URL to open. Such links are omitted
+ from the attribution view.
+ */
+- (IBAction)giveFeedback:(id)sender;
+
#pragma mark Debugging the Map
/**
diff --git a/platform/macos/src/MGLMapView.mm b/platform/macos/src/MGLMapView.mm
index a2fe7acbda..bf31daffad 100644
--- a/platform/macos/src/MGLMapView.mm
+++ b/platform/macos/src/MGLMapView.mm
@@ -1,6 +1,7 @@
#import "MGLMapView_Private.h"
#import "MGLAnnotationImage_Private.h"
#import "MGLAttributionButton.h"
+#import "MGLAttributionInfo.h"
#import "MGLCompassCell.h"
#import "MGLOpenGLLayer.h"
#import "MGLStyle.h"
@@ -84,23 +85,6 @@ const CGFloat MGLAnnotationImagePaddingForHitTest = 4;
/// Distance from the callout’s anchor point to the annotation it points to.
const CGFloat MGLAnnotationImagePaddingForCallout = 4;
-/// Copyright notices displayed in the attribution view.
-struct MGLAttribution {
- /// Attribution button label text. A copyright symbol is prepended to this string.
- NSString *title;
- /// URL to open when the attribution button is clicked.
- NSString *urlString;
-} MGLAttributions[] = {
- {
- .title = NSLocalizedStringWithDefaultValue(@"COPYRIGHT_MAPBOX", nil, nil, @"Mapbox", @"Linked part of copyright notice"),
- .urlString = NSLocalizedStringWithDefaultValue(@"COPYRIGHT_MAPBOX_LINK", nil, nil, @"https://www.mapbox.com/about/maps/", @"Copyright notice link"),
- },
- {
- .title = NSLocalizedStringWithDefaultValue(@"COPYRIGHT_OSM", nil, nil, @"OpenStreetMap", @"Linked part of copyright notice"),
- .urlString = NSLocalizedStringWithDefaultValue(@"COPYRIGHT_OSM_LINK", nil, nil, @"http://www.openstreetmap.org/about/", @"Copyright notice link"),
- },
-};
-
/// Unique identifier representing a single annotation in mbgl.
typedef uint32_t MGLAnnotationTag;
@@ -371,6 +355,7 @@ public:
/// Adds legally required map attribution to the lower-left corner.
- (void)installAttributionView {
+ [_attributionView removeFromSuperview];
_attributionView = [[NSView alloc] initWithFrame:NSZeroRect];
_attributionView.wantsLayer = YES;
@@ -433,37 +418,69 @@ public:
/// Updates the attribution view to reflect the sources used. For now, this is
/// hard-coded to the standard Mapbox and OpenStreetMap attribution.
- (void)updateAttributionView {
- self.attributionView.subviews = @[];
-
- for (NSUInteger i = 0; i < sizeof(MGLAttributions) / sizeof(MGLAttributions[0]); i++) {
+ NSView *attributionView = self.attributionView;
+ for (NSView *button in attributionView.subviews) {
+ [button removeConstraints:button.constraints];
+ }
+ attributionView.subviews = @[];
+ [attributionView removeConstraints:attributionView.constraints];
+
+ // Make the whole string mini by default.
+ // Force links to be black, because the default blue is distracting.
+ CGFloat miniSize = [NSFont systemFontSizeForControlSize:NSMiniControlSize];
+ NSArray *attributionInfos = [self.style attributionInfosWithFontSize:miniSize linkColor:[NSColor blackColor]];
+ for (MGLAttributionInfo *info in attributionInfos) {
+ // Feedback links are added to the Help menu.
+ if (info.feedbackLink) {
+ continue;
+ }
+
// For each attribution, add a borderless button that responds to clicks
// and feels like a hyperlink.
- NSURL *url = [NSURL URLWithString:MGLAttributions[i].urlString];
- NSButton *button = [[MGLAttributionButton alloc] initWithTitle:MGLAttributions[i].title URL:url];
+ NSButton *button = [[MGLAttributionButton alloc] initWithAttributionInfo:info];
button.controlSize = NSMiniControlSize;
button.translatesAutoresizingMaskIntoConstraints = NO;
// Set the new button flush with the buttom of the container and to the
// right of the previous button, with standard spacing. If there is no
// previous button, align to the container instead.
- NSView *previousView = self.attributionView.subviews.lastObject;
- [self.attributionView addSubview:button];
- [_attributionView addConstraint:
+ NSView *previousView = attributionView.subviews.lastObject;
+ [attributionView addSubview:button];
+ [attributionView addConstraint:
[NSLayoutConstraint constraintWithItem:button
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
- toItem:_attributionView
+ toItem:attributionView
attribute:NSLayoutAttributeBottom
multiplier:1
constant:0]];
- [_attributionView addConstraint:
+ [attributionView addConstraint:
[NSLayoutConstraint constraintWithItem:button
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
- toItem:previousView ? previousView : _attributionView
+ toItem:previousView ? previousView : attributionView
attribute:previousView ? NSLayoutAttributeTrailing : NSLayoutAttributeLeading
multiplier:1
constant:8]];
+ [attributionView addConstraint:
+ [NSLayoutConstraint constraintWithItem:button
+ attribute:NSLayoutAttributeTop
+ relatedBy:NSLayoutRelationEqual
+ toItem:attributionView
+ attribute:NSLayoutAttributeTop
+ multiplier:1
+ constant:0]];
+ }
+
+ if (attributionInfos.count) {
+ [attributionView addConstraint:
+ [NSLayoutConstraint constraintWithItem:attributionView
+ attribute:NSLayoutAttributeTrailing
+ relatedBy:NSLayoutRelationEqual
+ toItem:attributionView.subviews.lastObject
+ attribute:NSLayoutAttributeTrailing
+ multiplier:1
+ constant:8]];
}
}
@@ -732,20 +749,6 @@ public:
attribute:NSLayoutAttributeTrailing
multiplier:1
constant:8]];
- [self addConstraint:[NSLayoutConstraint constraintWithItem:_attributionView.subviews.firstObject
- attribute:NSLayoutAttributeTop
- relatedBy:NSLayoutRelationEqual
- toItem:_attributionView
- attribute:NSLayoutAttributeTop
- multiplier:1
- constant:0]];
- [self addConstraint:[NSLayoutConstraint constraintWithItem:_attributionView
- attribute:NSLayoutAttributeTrailing
- relatedBy:NSLayoutRelationEqual
- toItem:_attributionView.subviews.lastObject
- attribute:NSLayoutAttributeTrailing
- multiplier:1
- constant:8]];
[super updateConstraints];
}
@@ -924,6 +927,12 @@ public:
}
break;
}
+ case mbgl::MapChangeSourceDidChange:
+ {
+ [self installAttributionView];
+ self.needsUpdateConstraints = YES;
+ break;
+ }
}
}
@@ -1640,6 +1649,23 @@ public:
[self setDirection:-sender.doubleValue animated:YES];
}
+- (IBAction)giveFeedback:(id)sender {
+ CLLocationCoordinate2D centerCoordinate = self.centerCoordinate;
+ double zoomLevel = self.zoomLevel;
+ NSMutableArray *urls = [NSMutableArray array];
+ for (MGLAttributionInfo *info in [self.style attributionInfosWithFontSize:0 linkColor:nil]) {
+ NSURL *url = [info feedbackURLAtCenterCoordinate:centerCoordinate zoomLevel:zoomLevel];
+ if (url) {
+ [urls addObject:url];
+ }
+ }
+ [[NSWorkspace sharedWorkspace] openURLs:urls
+ withAppBundleIdentifier:nil
+ options:0
+ additionalEventParamDescriptor:nil
+ launchIdentifiers:nil];
+}
+
#pragma mark Annotations
- (nullable NS_ARRAY_OF(id <MGLAnnotation>) *)annotations {
@@ -2454,6 +2480,15 @@ public:
return MGLFeaturesFromMBGLFeatures(features);
}
+#pragma mark User interface validation
+
+- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
+ if (menuItem.action == @selector(giveFeedback:)) {
+ return YES;
+ }
+ return [super validateMenuItem:menuItem];
+}
+
#pragma mark Interface Builder methods
- (void)prepareForInterfaceBuilder {
diff --git a/platform/macos/test/MGLAttributionButtonTests.m b/platform/macos/test/MGLAttributionButtonTests.m
new file mode 100644
index 0000000000..f5c0aac856
--- /dev/null
+++ b/platform/macos/test/MGLAttributionButtonTests.m
@@ -0,0 +1,31 @@
+#import <Mapbox/Mapbox.h>
+#import <XCTest/XCTest.h>
+
+#import "MGLAttributionButton.h"
+#import "MGLAttributionInfo.h"
+
+@interface MGLAttributionButtonTests : XCTestCase
+
+@end
+
+@implementation MGLAttributionButtonTests
+
+- (void)testPlainSymbol {
+ NSAttributedString *title = [[NSAttributedString alloc] initWithString:@"® & ™ Mapbox" attributes:@{
+ NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle),
+ }];
+ MGLAttributionInfo *info = [[MGLAttributionInfo alloc] initWithTitle:title URL:nil];
+ MGLAttributionButton *button = [[MGLAttributionButton alloc] initWithAttributionInfo:info];
+
+ NSRange symbolUnderlineRange;
+ NSNumber *symbolUnderline = [button.attributedTitle attribute:NSUnderlineStyleAttributeName atIndex:0 effectiveRange:&symbolUnderlineRange];
+ XCTAssertNil(symbolUnderline);
+ XCTAssertEqual(symbolUnderlineRange.length, 6);
+
+ NSRange wordUnderlineRange;
+ NSNumber *wordUnderline = [button.attributedTitle attribute:NSUnderlineStyleAttributeName atIndex:6 effectiveRange:&wordUnderlineRange];
+ XCTAssertEqualObjects(wordUnderline, @(NSUnderlineStyleSingle));
+ XCTAssertEqual(wordUnderlineRange.length, 6);
+}
+
+@end