From 0625d4b6b2d6f852964a3a8bfd449b3d40b1d363 Mon Sep 17 00:00:00 2001 From: Fredrik Karlsson Date: Mon, 3 Oct 2016 11:33:38 +0200 Subject: [PATCH] [ios, macos] features and annotations now conforms to NSSecureCoding --- platform/darwin/src/MGLFeature.mm | 29 ++ platform/darwin/src/MGLFeature_Private.h | 28 ++ platform/darwin/src/MGLMultiPoint.mm | 38 +- platform/darwin/src/MGLPointAnnotation.mm | 36 ++ platform/darwin/src/MGLPointCollection.mm | 42 +- platform/darwin/src/MGLPolygon.mm | 55 ++ platform/darwin/src/MGLPolyline.mm | 30 ++ platform/darwin/src/MGLShape.h | 2 +- platform/darwin/src/MGLShape.mm | 68 ++- platform/darwin/src/MGLShapeCollection.mm | 29 ++ platform/darwin/src/MGLShape_Private.h | 3 + platform/darwin/src/NSArray+MGLAdditions.h | 15 + platform/darwin/src/NSArray+MGLAdditions.mm | 24 + platform/darwin/src/NSCoder+MGLAdditions.h | 16 + platform/darwin/src/NSCoder+MGLAdditions.mm | 26 + platform/darwin/test/MGLCodingTests.m | 469 ++++++++++++++++++ platform/ios/ios.xcodeproj/project.pbxproj | 16 + platform/ios/src/MGLAnnotationImage.h | 2 +- platform/ios/src/MGLAnnotationImage.m | 38 ++ platform/ios/src/MGLAnnotationView.h | 2 +- platform/ios/src/MGLAnnotationView.mm | 28 ++ platform/ios/src/MGLUserLocation.h | 2 +- platform/ios/src/MGLUserLocation.m | 38 ++ .../macos/macos.xcodeproj/project.pbxproj | 12 + platform/macos/src/MGLAnnotationImage.h | 2 +- platform/macos/src/MGLAnnotationImage.m | 41 ++ 26 files changed, 1072 insertions(+), 19 deletions(-) create mode 100644 platform/darwin/src/NSCoder+MGLAdditions.h create mode 100644 platform/darwin/src/NSCoder+MGLAdditions.mm create mode 100644 platform/darwin/test/MGLCodingTests.m diff --git a/platform/darwin/src/MGLFeature.mm b/platform/darwin/src/MGLFeature.mm index c1e0c312a04..5b63beae872 100644 --- a/platform/darwin/src/MGLFeature.mm +++ b/platform/darwin/src/MGLFeature.mm @@ -10,6 +10,7 @@ #import "MGLPolyline+MGLAdditions.h" #import "MGLPolygon+MGLAdditions.h" #import "NSDictionary+MGLAdditions.h" +#import "NSArray+MGLAdditions.h" #import "NSExpression+MGLAdditions.h" @@ -25,6 +26,10 @@ @implementation MGLPointFeature @synthesize identifier; @synthesize attributes; +MGL_DEFINE_FEATURE_INIT_WITH_CODER(); +MGL_DEFINE_FEATURE_ENCODE(); +MGL_DEFINE_FEATURE_IS_EQUAL(); + - (id)attributeForKey:(NSString *)key { return self.attributes[key]; } @@ -47,6 +52,10 @@ @implementation MGLPolylineFeature @synthesize identifier; @synthesize attributes; +MGL_DEFINE_FEATURE_INIT_WITH_CODER(); +MGL_DEFINE_FEATURE_ENCODE(); +MGL_DEFINE_FEATURE_IS_EQUAL(); + - (id)attributeForKey:(NSString *)key { return self.attributes[key]; } @@ -69,6 +78,10 @@ @implementation MGLPolygonFeature @synthesize identifier; @synthesize attributes; +MGL_DEFINE_FEATURE_INIT_WITH_CODER(); +MGL_DEFINE_FEATURE_ENCODE(); +MGL_DEFINE_FEATURE_IS_EQUAL(); + - (id)attributeForKey:(NSString *)key { return self.attributes[key]; } @@ -91,6 +104,10 @@ @implementation MGLPointCollectionFeature @synthesize identifier; @synthesize attributes; +MGL_DEFINE_FEATURE_INIT_WITH_CODER(); +MGL_DEFINE_FEATURE_ENCODE(); +MGL_DEFINE_FEATURE_IS_EQUAL(); + - (id)attributeForKey:(NSString *)key { return self.attributes[key]; } @@ -113,6 +130,10 @@ @implementation MGLMultiPolylineFeature @synthesize identifier; @synthesize attributes; +MGL_DEFINE_FEATURE_INIT_WITH_CODER(); +MGL_DEFINE_FEATURE_ENCODE(); +MGL_DEFINE_FEATURE_IS_EQUAL(); + - (id)attributeForKey:(NSString *)key { return self.attributes[key]; } @@ -135,6 +156,10 @@ @implementation MGLMultiPolygonFeature @synthesize identifier; @synthesize attributes; +MGL_DEFINE_FEATURE_INIT_WITH_CODER(); +MGL_DEFINE_FEATURE_ENCODE(); +MGL_DEFINE_FEATURE_IS_EQUAL(); + - (id)attributeForKey:(NSString *)key { return self.attributes[key]; } @@ -163,6 +188,10 @@ + (instancetype)shapeCollectionWithShapes:(NSArray *)shapes { return [super shapeCollectionWithShapes:shapes]; } +MGL_DEFINE_FEATURE_INIT_WITH_CODER(); +MGL_DEFINE_FEATURE_ENCODE(); +MGL_DEFINE_FEATURE_IS_EQUAL(); + - (id)attributeForKey:(NSString *)key { return self.attributes[key]; } diff --git a/platform/darwin/src/MGLFeature_Private.h b/platform/darwin/src/MGLFeature_Private.h index 97af509893a..3277a94d5bc 100644 --- a/platform/darwin/src/MGLFeature_Private.h +++ b/platform/darwin/src/MGLFeature_Private.h @@ -37,3 +37,31 @@ mbgl::Feature mbglFeature(mbgl::Feature feature, id identifier, NSDictionary *at NS_DICTIONARY_OF(NSString *, id) *NSDictionaryFeatureForGeometry(NSDictionary *geometry, NSDictionary *attributes, id identifier); NS_ASSUME_NONNULL_END + +#define MGL_DEFINE_FEATURE_INIT_WITH_CODER() \ + - (instancetype)initWithCoder:(NSCoder *)decoder { \ + if (self = [super initWithCoder:decoder]) { \ + NSSet *identifierClasses = [NSSet setWithArray:@[[NSString class], [NSNumber class]]]; \ + identifier = [decoder decodeObjectOfClasses:identifierClasses forKey:@"identifier"]; \ + attributes = [decoder decodeObjectOfClass:[NSDictionary class] forKey:@"attributes"]; \ + } \ + return self; \ + } + +#define MGL_DEFINE_FEATURE_ENCODE() \ + - (void)encodeWithCoder:(NSCoder *)coder { \ + [super encodeWithCoder:coder]; \ + [coder encodeObject:identifier forKey:@"identifier"]; \ + [coder encodeObject:attributes forKey:@"attributes"]; \ + } + +#define MGL_DEFINE_FEATURE_IS_EQUAL() \ + - (BOOL)isEqual:(id)other { \ + if (other == self) return YES; \ + if (![other isKindOfClass:[self class]]) return NO; \ + __typeof(self) otherFeature = other; \ + return [super isEqual:other] && [self geoJSONObject] == [otherFeature geoJSONObject]; \ + } \ + - (NSUInteger)hash { \ + return [super hash] + [[self geoJSONDictionary] hash]; \ + } diff --git a/platform/darwin/src/MGLMultiPoint.mm b/platform/darwin/src/MGLMultiPoint.mm index c49e970c6b0..3b03b78ca62 100644 --- a/platform/darwin/src/MGLMultiPoint.mm +++ b/platform/darwin/src/MGLMultiPoint.mm @@ -1,10 +1,9 @@ #import "MGLMultiPoint_Private.h" #import "MGLGeometry_Private.h" +#import "MGLShape_Private.h" +#import "NSCoder+MGLAdditions.h" #import "MGLTypes.h" -#include -#include - @implementation MGLMultiPoint { mbgl::optional _bounds; @@ -27,6 +26,39 @@ - (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords count return self; } +- (instancetype)initWithCoder:(NSCoder *)decoder +{ + if (self = [super initWithCoder:decoder]) { + _coordinates = [decoder mgl_decodeLocationCoordinates2DForKey:@"coordinates"]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder +{ + [super encodeWithCoder:coder]; + [coder mgl_encodeLocationCoordinates2D:_coordinates forKey:@"coordinates"]; +} + +- (BOOL)isEqual:(id)other +{ + if (self == other) return YES; + if (![other isKindOfClass:[MGLMultiPoint class]]) return NO; + + MGLMultiPoint *otherMultipoint = other; + return ([super isEqual:otherMultipoint] + && _coordinates == otherMultipoint->_coordinates); +} + +- (NSUInteger)hash +{ + NSUInteger hash = [super hash]; + for (auto coord : _coordinates) { + hash += @(coord.latitude+coord.longitude).hash; + } + return hash; +} + - (CLLocationCoordinate2D)coordinate { NSAssert([self pointCount] > 0, @"A multipoint must have coordinates"); diff --git a/platform/darwin/src/MGLPointAnnotation.mm b/platform/darwin/src/MGLPointAnnotation.mm index d2e87f07d12..a2108a9e3be 100644 --- a/platform/darwin/src/MGLPointAnnotation.mm +++ b/platform/darwin/src/MGLPointAnnotation.mm @@ -1,6 +1,7 @@ #import "MGLPointAnnotation.h" #import "MGLShape_Private.h" +#import "NSCoder+MGLAdditions.h" #import @@ -9,6 +10,41 @@ @implementation MGLPointAnnotation @synthesize coordinate; ++ (BOOL)supportsSecureCoding +{ + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)coder +{ + if (self = [super initWithCoder:coder]) { + self.coordinate = [coder decodeMGLCoordinateForKey:@"coordinate"]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder +{ + [super encodeWithCoder:coder]; + [coder encodeMGLCoordinate:coordinate forKey:@"coordinate"]; +} + +- (BOOL)isEqual:(id)other +{ + if (other == self) return YES; + if (![other isKindOfClass:[MGLPointAnnotation class]]) return NO; + + MGLPointAnnotation *otherAnnotation = other; + return ([super isEqual:other] + && self.coordinate.latitude == otherAnnotation.coordinate.latitude + && self.coordinate.longitude == otherAnnotation.coordinate.longitude); +} + +- (NSUInteger)hash +{ + return [super hash] + @(self.coordinate.latitude).hash + @(self.coordinate.longitude).hash; +} + - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p; title = %@; subtitle = %@; coordinate = %f, %f>", diff --git a/platform/darwin/src/MGLPointCollection.mm b/platform/darwin/src/MGLPointCollection.mm index 387a575b2d8..acd78b8b336 100644 --- a/platform/darwin/src/MGLPointCollection.mm +++ b/platform/darwin/src/MGLPointCollection.mm @@ -1,5 +1,6 @@ #import "MGLPointCollection_Private.h" #import "MGLGeometry_Private.h" +#import "NSArray+MGLAdditions.h" #import #import @@ -8,12 +9,10 @@ @implementation MGLPointCollection { - MGLCoordinateBounds _overlayBounds; + mbgl::optional _bounds; std::vector _coordinates; } -@synthesize overlayBounds = _overlayBounds; - + (instancetype)pointCollectionWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count { return [[self alloc] initWithCoordinates:coords count:count]; @@ -25,14 +24,41 @@ - (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords count if (self) { _coordinates = { coords, coords + count }; + } + return self; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)decoder { + if (self = [super initWithCoder:decoder]) { + NSArray *coordinates = [decoder decodeObjectOfClass:[NSArray class] forKey:@"coordinates"]; + _coordinates = [coordinates mgl_coordinates]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [super encodeWithCoder:coder]; + [coder encodeObject:[NSArray mgl_coordinatesFromCoordinates:_coordinates] forKey:@"coordinates"]; +} + +- (BOOL)isEqual:(id)other { + if (self == other) return YES; + if (![other isKindOfClass:[MGLPointCollection class]]) return NO; + + MGLPointCollection *otherCollection = (MGLPointCollection *)other; + return ([super isEqual:other] + && ((![self geoJSONDictionary] && ![otherCollection geoJSONDictionary]) || [[self geoJSONDictionary] isEqualToDictionary:[otherCollection geoJSONDictionary]])); +} + +- (MGLCoordinateBounds)overlayBounds { + if (!_bounds) { mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); - for (auto coordinate : _coordinates) - { + for (auto coordinate : _coordinates) { bounds.extend(mbgl::LatLng(coordinate.latitude, coordinate.longitude)); } - _overlayBounds = MGLCoordinateBoundsFromLatLngBounds(bounds); + _bounds = bounds; } - return self; + return MGLCoordinateBoundsFromLatLngBounds(*_bounds); } - (NSUInteger)pointCount @@ -65,7 +91,7 @@ - (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range - (BOOL)intersectsOverlayBounds:(MGLCoordinateBounds)overlayBounds { - return MGLCoordinateBoundsIntersectsCoordinateBounds(_overlayBounds, overlayBounds); + return MGLCoordinateBoundsIntersectsCoordinateBounds(self.overlayBounds, overlayBounds); } - (mbgl::Geometry)geometryObject diff --git a/platform/darwin/src/MGLPolygon.mm b/platform/darwin/src/MGLPolygon.mm index 393bd31d0dc..565de017ccd 100644 --- a/platform/darwin/src/MGLPolygon.mm +++ b/platform/darwin/src/MGLPolygon.mm @@ -28,6 +28,32 @@ - (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords count return self; } +- (instancetype)initWithCoder:(NSCoder *)decoder { + self = [super initWithCoder:decoder]; + if (self) { + _interiorPolygons = [decoder decodeObjectOfClass:[NSArray class] forKey:@"interiorPolygons"]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [super encodeWithCoder:coder]; + [coder encodeObject:self.interiorPolygons forKey:@"interiorPolygons"]; +} + +- (BOOL)isEqual:(id)other { + if (self == other) return YES; + if (![other isKindOfClass:[MGLPolygon class]]) return NO; + + MGLPolygon *otherPolygon = (MGLPolygon *)other; + return ([super isEqual:otherPolygon] && + [[self geoJSONDictionary] isEqualToDictionary:[otherPolygon geoJSONDictionary]]); +} + +- (NSUInteger)hash { + return [super hash] + [[self geoJSONDictionary] hash]; +} + - (mbgl::LinearRing)ring { NSUInteger count = self.pointCount; CLLocationCoordinate2D *coordinates = self.coordinates; @@ -100,6 +126,35 @@ - (instancetype)initWithPolygons:(NS_ARRAY_OF(MGLPolygon *) *)polygons { return self; } +- (instancetype)initWithCoder:(NSCoder *)decoder { + if (self = [super initWithCoder:decoder]) { + _polygons = [decoder decodeObjectOfClass:[NSArray class] forKey:@"polygons"]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [super encodeWithCoder:coder]; + [coder encodeObject:_polygons forKey:@"polygons"]; +} + +- (BOOL)isEqual:(id)other { + if (self == other) return YES; + if (![other isKindOfClass:[MGLMultiPolygon class]]) return NO; + + MGLMultiPolygon *otherMultiPolygon = other; + return [super isEqual:other] + && [self.polygons isEqualToArray:otherMultiPolygon.polygons]; +} + +- (NSUInteger)hash { + NSUInteger hash = [super hash]; + for (MGLPolygon *polygon in self.polygons) { + hash += [polygon hash]; + } + return hash; +} + - (BOOL)intersectsOverlayBounds:(MGLCoordinateBounds)overlayBounds { return MGLCoordinateBoundsIntersectsCoordinateBounds(_overlayBounds, overlayBounds); } diff --git a/platform/darwin/src/MGLPolyline.mm b/platform/darwin/src/MGLPolyline.mm index 0baeb68e1ac..e6b1cdebf6c 100644 --- a/platform/darwin/src/MGLPolyline.mm +++ b/platform/darwin/src/MGLPolyline.mm @@ -80,6 +80,36 @@ - (instancetype)initWithPolylines:(NS_ARRAY_OF(MGLPolyline *) *)polylines { return self; } +- (instancetype)initWithCoder:(NSCoder *)decoder { + if (self = [super initWithCoder:decoder]) { + _polylines = [decoder decodeObjectOfClass:[NSArray class] forKey:@"polylines"]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [super encodeWithCoder:coder]; + [coder encodeObject:_polylines forKey:@"polylines"]; +} + +- (BOOL)isEqual:(id)other +{ + if (self == other) return YES; + if (![other isKindOfClass:[MGLMultiPolyline class]]) return NO; + + MGLMultiPolyline *otherMultipoline = other; + return ([super isEqual:otherMultipoline] + && [self.polylines isEqualToArray:otherMultipoline.polylines]); +} + +- (NSUInteger)hash { + NSUInteger hash = [super hash]; + for (MGLPolyline *polyline in self.polylines) { + hash += [polyline hash]; + } + return hash; +} + - (BOOL)intersectsOverlayBounds:(MGLCoordinateBounds)overlayBounds { return MGLCoordinateBoundsIntersectsCoordinateBounds(_overlayBounds, overlayBounds); } diff --git a/platform/darwin/src/MGLShape.h b/platform/darwin/src/MGLShape.h index ecb66118a1d..4063ab39eaa 100644 --- a/platform/darwin/src/MGLShape.h +++ b/platform/darwin/src/MGLShape.h @@ -23,7 +23,7 @@ NS_ASSUME_NONNULL_BEGIN you can add some kinds of shapes directly to a map view as annotations or overlays. */ -@interface MGLShape : NSObject +@interface MGLShape : NSObject #pragma mark Creating a Shape diff --git a/platform/darwin/src/MGLShape.mm b/platform/darwin/src/MGLShape.mm index 889ef8b3d7e..4ddf7c9df71 100644 --- a/platform/darwin/src/MGLShape.mm +++ b/platform/darwin/src/MGLShape.mm @@ -2,6 +2,15 @@ #import "MGLFeature_Private.h" +#import "NSString+MGLAdditions.h" +#import "MGLTypes.h" + +#import + +bool operator==(const CLLocationCoordinate2D lhs, const CLLocationCoordinate2D rhs) { + return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude; +} + @implementation MGLShape + (nullable MGLShape *)shapeWithData:(NSData *)data encoding:(NSStringEncoding)encoding error:(NSError * _Nullable *)outError { @@ -42,10 +51,63 @@ - (NSData *)geoJSONDataUsingEncoding:(NSStringEncoding)encoding { return [string dataUsingEncoding:NSUTF8StringEncoding]; } -- (CLLocationCoordinate2D)coordinate { - [NSException raise:@"MGLAbstractClassException" - format:@"MGLShape is an abstract class"]; ++ (BOOL)supportsSecureCoding +{ + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)coder +{ + if (self = [super init]) { + _title = [coder decodeObjectOfClass:[NSString class] forKey:@"title"]; + _subtitle = [coder decodeObjectOfClass:[NSString class] forKey:@"subtitle"]; +#if !TARGET_OS_IPHONE + _toolTip = [coder decodeObjectOfClass:[NSString class] forKey:@"toolTip"]; +#endif + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_title forKey:@"title"]; + [coder encodeObject:_subtitle forKey:@"subtitle"]; +#if !TARGET_OS_IPHONE + [coder encodeObject:_toolTip forKey:@"toolTip"]; +#endif +} + +- (BOOL)isEqual:(id)other +{ + if (other == self) { return YES; } + id annotation = other; + +#if TARGET_OS_IPHONE + return ((!_title && ![annotation title]) || [_title isEqualToString:[annotation title]]) + && ((!_subtitle && ![annotation subtitle]) || [_subtitle isEqualToString:[annotation subtitle]]); +#else + return ((!_title && ![annotation title]) || [_title isEqualToString:[annotation title]]) + && ((!_subtitle && ![annotation subtitle]) || [_subtitle isEqualToString:[annotation subtitle]]) + && ((!_toolTip && ![annotation toolTip]) || [_toolTip isEqualToString:[annotation toolTip]]); +#endif +} + +- (NSUInteger)hash +{ + NSUInteger hash; + hash += _title.hash; + hash += _subtitle.hash; +#if !TARGET_OS_IPHONE + hash += _toolTip.hash; +#endif + return hash; +} +- (CLLocationCoordinate2D)coordinate +{ + [[NSException exceptionWithName:@"MGLAbstractClassException" + reason:@"MGLShape is an abstract class" + userInfo:nil] raise]; return kCLLocationCoordinate2DInvalid; } diff --git a/platform/darwin/src/MGLShapeCollection.mm b/platform/darwin/src/MGLShapeCollection.mm index e317a443fe3..c8f0d09f9a9 100644 --- a/platform/darwin/src/MGLShapeCollection.mm +++ b/platform/darwin/src/MGLShapeCollection.mm @@ -18,6 +18,35 @@ - (instancetype)initWithShapes:(NS_ARRAY_OF(MGLShape *) *)shapes { return self; } +- (instancetype)initWithCoder:(NSCoder *)decoder { + if (self = [super initWithCoder:decoder]) { + _shapes = [decoder decodeObjectOfClass:[NSArray class] forKey:@"shapes"]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [super encodeWithCoder:coder]; + [coder encodeObject:_shapes forKey:@"shapes"]; +} + +- (BOOL)isEqual:(id)other { + if (self == other) return YES; + if (![other isKindOfClass:[MGLShapeCollection class]]) return NO; + + MGLShapeCollection *otherShapeCollection = other; + return [super isEqual:otherShapeCollection] + && [_shapes isEqualToArray:otherShapeCollection.shapes]; +} + +- (NSUInteger)hash { + NSUInteger hash = [super hash]; + for (MGLShape *shape in _shapes) { + hash += [shape hash]; + } + return hash; +} + - (CLLocationCoordinate2D)coordinate { return _shapes.firstObject.coordinate; } diff --git a/platform/darwin/src/MGLShape_Private.h b/platform/darwin/src/MGLShape_Private.h index 15d1c1eb97d..9821d491769 100644 --- a/platform/darwin/src/MGLShape_Private.h +++ b/platform/darwin/src/MGLShape_Private.h @@ -2,6 +2,9 @@ #import #import +#import + +bool operator==(const CLLocationCoordinate2D lhs, const CLLocationCoordinate2D rhs); @interface MGLShape (Private) diff --git a/platform/darwin/src/NSArray+MGLAdditions.h b/platform/darwin/src/NSArray+MGLAdditions.h index eb1cfb7c476..c4dfd8207b8 100644 --- a/platform/darwin/src/NSArray+MGLAdditions.h +++ b/platform/darwin/src/NSArray+MGLAdditions.h @@ -1,4 +1,5 @@ #import +#import #import @@ -9,4 +10,18 @@ /** Returns a string resulting from inserting a separator between each attributed string in the array */ - (NSAttributedString *)mgl_attributedComponentsJoinedByString:(NSString *)separator; +/** + Converts std::vector into an NSArray containing dictionary + representations of coordinates with the following structure: + [{"latitude": lat, "longitude": lng}] + */ ++ (NSArray *)mgl_coordinatesFromCoordinates:(std::vector)coords; + +/** + Converts the receiver into a std::vector. + Receiver must conform to the following structure: + [{"latitude": lat, "longitude": lng}] + */ +- (std::vector)mgl_coordinates; + @end diff --git a/platform/darwin/src/NSArray+MGLAdditions.mm b/platform/darwin/src/NSArray+MGLAdditions.mm index b2799c46e1f..f2c5a096cc9 100644 --- a/platform/darwin/src/NSArray+MGLAdditions.mm +++ b/platform/darwin/src/NSArray+MGLAdditions.mm @@ -38,4 +38,28 @@ - (NSAttributedString *)mgl_attributedComponentsJoinedByString:(NSString *)separ return attributedString; } ++ (NSArray *)mgl_coordinatesFromCoordinates:(std::vector)coords { + NSMutableArray *coordinates = [NSMutableArray array]; + for (auto coord : coords) { + [coordinates addObject:@{@"latitude": @(coord.latitude), + @"longitude": @(coord.longitude)}]; + } + return coordinates; +} + +- (std::vector)mgl_coordinates { + NSUInteger numberOfCoordinates = [self count]; + CLLocationCoordinate2D *coords = (CLLocationCoordinate2D *)malloc(numberOfCoordinates * sizeof(CLLocationCoordinate2D)); + + for (NSUInteger i = 0; i < numberOfCoordinates; i++) { + coords[i] = CLLocationCoordinate2DMake([self[i][@"latitude"] doubleValue], + [self[i][@"longitude"] doubleValue]); + } + + std::vector coordinates = { coords, coords + numberOfCoordinates }; + free(coords); + + return coordinates; +} + @end diff --git a/platform/darwin/src/NSCoder+MGLAdditions.h b/platform/darwin/src/NSCoder+MGLAdditions.h new file mode 100644 index 00000000000..036a99c5afb --- /dev/null +++ b/platform/darwin/src/NSCoder+MGLAdditions.h @@ -0,0 +1,16 @@ +#import +#import + +#import + +@interface NSCoder (MGLAdditions) + +- (void)encodeMGLCoordinate:(CLLocationCoordinate2D)coordinate forKey:(NSString *)key; + +- (CLLocationCoordinate2D)decodeMGLCoordinateForKey:(NSString *)key; + +- (void)mgl_encodeLocationCoordinates2D:(std::vector)coordinates forKey:(NSString *)key; + +- (std::vector)mgl_decodeLocationCoordinates2DForKey:(NSString *)key; + +@end diff --git a/platform/darwin/src/NSCoder+MGLAdditions.mm b/platform/darwin/src/NSCoder+MGLAdditions.mm new file mode 100644 index 00000000000..4af6c7588b5 --- /dev/null +++ b/platform/darwin/src/NSCoder+MGLAdditions.mm @@ -0,0 +1,26 @@ +#import "NSCoder+MGLAdditions.h" + +#import "NSArray+MGLAdditions.h" +#import "NSValue+MGLAdditions.h" + +@implementation NSCoder (MGLAdditions) + +- (void)mgl_encodeLocationCoordinates2D:(std::vector)coordinates forKey:(NSString *)key { + [self encodeObject:[NSArray mgl_coordinatesFromCoordinates:coordinates] forKey:key]; +} + +- (std::vector)mgl_decodeLocationCoordinates2DForKey:(NSString *)key { + NSArray *coordinates = [self decodeObjectOfClass:[NSArray class] forKey:key]; + return [coordinates mgl_coordinates]; +} + +- (void)encodeMGLCoordinate:(CLLocationCoordinate2D)coordinate forKey:(NSString *)key { + [self encodeObject:@{@"latitude": @(coordinate.latitude), @"longitude": @(coordinate.longitude)} forKey:key]; +} + +- (CLLocationCoordinate2D)decodeMGLCoordinateForKey:(NSString *)key { + NSDictionary *coordinate = [self decodeObjectForKey:key]; + return CLLocationCoordinate2DMake([coordinate[@"latitude"] doubleValue], [coordinate[@"longitude"] doubleValue]); +} + +@end diff --git a/platform/darwin/test/MGLCodingTests.m b/platform/darwin/test/MGLCodingTests.m new file mode 100644 index 00000000000..b9b299d50f9 --- /dev/null +++ b/platform/darwin/test/MGLCodingTests.m @@ -0,0 +1,469 @@ +#import +#import + +#if TARGET_OS_IPHONE +#import "MGLUserLocation_Private.h" +#endif + +@interface MGLCodingTests : XCTestCase +@end + +@implementation MGLCodingTests + +- (NSString *)temporaryFilePathForClass:(Class)clazz { + return [NSTemporaryDirectory() stringByAppendingPathComponent:NSStringFromClass(clazz)]; +} + +- (void)testPointAnnotation { + MGLPointAnnotation *annotation = [[MGLPointAnnotation alloc] init]; + annotation.coordinate = CLLocationCoordinate2DMake(0.5, 0.5); + annotation.title = @"title"; + annotation.subtitle = @"subtitle"; + + NSString *filePath = [self temporaryFilePathForClass:MGLPointAnnotation.class]; + [NSKeyedArchiver archiveRootObject:annotation toFile:filePath]; + MGLPointAnnotation *unarchivedAnnotation = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(annotation, unarchivedAnnotation); +} + +- (void)testPointFeature { + MGLPointFeature *pointFeature = [[MGLPointFeature alloc] init]; + pointFeature.title = @"title"; + pointFeature.subtitle = @"subtitle"; + pointFeature.identifier = @(123); + pointFeature.attributes = @{@"bbox": @[@1, @2, @3, @4]}; + + NSString *filePath = [self temporaryFilePathForClass:MGLPointFeature.class]; + [NSKeyedArchiver archiveRootObject:pointFeature toFile:filePath]; + MGLPointFeature *unarchivedPointFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(pointFeature, unarchivedPointFeature); +} + +- (void)testPolyline { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0.129631234123, 1.7812739312551), + CLLocationCoordinate2DMake(2.532083092342, 3.5216418292392) + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + MGLPolyline *polyline = [MGLPolyline polylineWithCoordinates:coordinates count:numberOfCoordinates]; + polyline.title = @"title"; + polyline.subtitle = @"subtitle"; + + NSString *filePath = [self temporaryFilePathForClass:[MGLPolyline class]]; + [NSKeyedArchiver archiveRootObject:polyline toFile:filePath]; + MGLPolyline *unarchivedPolyline = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(polyline, unarchivedPolyline); + + CLLocationCoordinate2D otherCoordinates[] = { + CLLocationCoordinate2DMake(-1, -2) + }; + + [unarchivedPolyline replaceCoordinatesInRange:NSMakeRange(0, 1) withCoordinates:otherCoordinates]; + + XCTAssertNotEqualObjects(polyline, unarchivedPolyline); +} + +- (void)testPolygon { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0.664482398, 1.8865675), + CLLocationCoordinate2DMake(2.13224687, 3.9984632) + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates]; + polygon.title = nil; + polygon.subtitle = @"subtitle"; + + NSString *filePath = [self temporaryFilePathForClass:[MGLPolygon class]]; + [NSKeyedArchiver archiveRootObject:polygon toFile:filePath]; + + MGLPolygon *unarchivedPolygon = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(polygon, unarchivedPolygon); +} + +- (void)testPolygonWithInteriorPolygons { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 20) + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + CLLocationCoordinate2D interiorCoordinates[] = { + CLLocationCoordinate2DMake(4, 4), + CLLocationCoordinate2DMake(6, 6) + }; + + NSUInteger numberOfInteriorCoordinates = sizeof(interiorCoordinates) / sizeof(CLLocationCoordinate2D); + + MGLPolygon *interiorPolygon = [MGLPolygon polygonWithCoordinates:interiorCoordinates count:numberOfInteriorCoordinates]; + MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates interiorPolygons:@[interiorPolygon]]; + + NSString *filePath = [self temporaryFilePathForClass:[MGLPolygon class]]; + [NSKeyedArchiver archiveRootObject:polygon toFile:filePath]; + + MGLPolygon *unarchivedPolygon = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(polygon, unarchivedPolygon); +} + +- (void)testPolylineFeature { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 20) + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + MGLPolylineFeature *polylineFeature = [MGLPolylineFeature polylineWithCoordinates:coordinates count:numberOfCoordinates]; + polylineFeature.attributes = @{@"bbox": @[@0, @1, @2, @3]}; + polylineFeature.identifier = @"identifier"; + + NSString *filePath = [self temporaryFilePathForClass:[MGLPolylineFeature class]]; + [NSKeyedArchiver archiveRootObject:polylineFeature toFile:filePath]; + + MGLPolylineFeature *unarchivedPolylineFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(polylineFeature, unarchivedPolylineFeature); + + unarchivedPolylineFeature.attributes = @{@"bbox": @[@4, @3, @2, @1]}; + + XCTAssertNotEqualObjects(polylineFeature, unarchivedPolylineFeature); +} + +- (void)testPolygonFeature { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 20) + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + MGLPolygonFeature *polygonFeature = [MGLPolygonFeature polygonWithCoordinates:coordinates count:numberOfCoordinates]; + + NSString *filePath = [self temporaryFilePathForClass:[MGLPolygonFeature class]]; + [NSKeyedArchiver archiveRootObject:polygonFeature toFile:filePath]; + + MGLPolygonFeature *unarchivedPolygonFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(polygonFeature, unarchivedPolygonFeature); + + unarchivedPolygonFeature.identifier = @"test"; + + XCTAssertNotEqualObjects(polygonFeature, unarchivedPolygonFeature); +} + +- (void)testPointCollection { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 11), + CLLocationCoordinate2DMake(20, 21), + CLLocationCoordinate2DMake(30, 31), + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + MGLPointCollection *pointCollection = [MGLPointCollection pointCollectionWithCoordinates:coordinates count:numberOfCoordinates]; + NSString *filePath = [self temporaryFilePathForClass:[MGLPointCollection class]]; + [NSKeyedArchiver archiveRootObject:pointCollection toFile:filePath]; + + MGLPointCollection *unarchivedPointCollection = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(pointCollection, unarchivedPointCollection); +} + +- (void)testPointCollectionFeature { + NSMutableArray *features = [NSMutableArray array]; + for (NSUInteger i = 0; i < 100; i++) { + MGLPointFeature *feature = [[MGLPointFeature alloc] init]; + feature.coordinate = CLLocationCoordinate2DMake(arc4random() % 90, arc4random() % 180); + [features addObject:feature]; + } + + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 11), + CLLocationCoordinate2DMake(20, 21), + CLLocationCoordinate2DMake(30, 31), + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + MGLPointCollectionFeature *collection = [MGLPointCollectionFeature pointCollectionWithCoordinates:coordinates count:numberOfCoordinates]; + collection.identifier = @"identifier"; + collection.attributes = @{@"bbox": @[@1, @2, @3, @4]}; + + NSString *filePath = [self temporaryFilePathForClass:[MGLPointCollectionFeature class]]; + [NSKeyedArchiver archiveRootObject:collection toFile:filePath]; + + MGLPointCollectionFeature *unarchivedCollection = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(collection, unarchivedCollection); + + unarchivedCollection.identifier = @"newIdentifier"; + + XCTAssertNotEqualObjects(collection, unarchivedCollection); +} + +- (void)testMultiPolyline { + + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 11), + CLLocationCoordinate2DMake(20, 21), + CLLocationCoordinate2DMake(30, 31), + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + NSMutableArray *polylines = [NSMutableArray array]; + + for (NSUInteger i = 0; i < 100; i++) { + MGLPolyline *polyline = [MGLPolyline polylineWithCoordinates:coordinates count:numberOfCoordinates]; + [polylines addObject:polyline]; + } + + MGLMultiPolyline *multiPolyline = [MGLMultiPolyline multiPolylineWithPolylines:polylines]; + + NSString *filePath = [self temporaryFilePathForClass:[MGLMultiPolyline class]]; + [NSKeyedArchiver archiveRootObject:multiPolyline toFile:filePath]; + + MGLMultiPolyline *unarchivedMultiPolyline = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MGLMultiPolyline *anotherMultipolyline = [MGLMultiPolyline multiPolylineWithPolylines:[polylines subarrayWithRange:NSMakeRange(0, polylines.count/2)]]; + + XCTAssertEqualObjects(multiPolyline, unarchivedMultiPolyline); + XCTAssertNotEqualObjects(unarchivedMultiPolyline, anotherMultipolyline); +} + +- (void)testMultiPolygon { + + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 11), + CLLocationCoordinate2DMake(20, 21), + CLLocationCoordinate2DMake(30, 31), + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + NSMutableArray *polygons = [NSMutableArray array]; + + for (NSUInteger i = 0; i < 100; i++) { + MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates]; + [polygons addObject:polygon]; + } + + MGLMultiPolygon *multiPolygon = [MGLMultiPolygon multiPolygonWithPolygons:polygons]; + + NSString *filePath = [self temporaryFilePathForClass:[MGLMultiPolygon class]]; + [NSKeyedArchiver archiveRootObject:multiPolygon toFile:filePath]; + + MGLMultiPolygon *unarchivedMultiPolygon = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MGLMultiPolygon *anotherMultiPolygon = [MGLMultiPolygon multiPolygonWithPolygons:[polygons subarrayWithRange:NSMakeRange(0, polygons.count/2)]]; + + XCTAssertEqualObjects(multiPolygon, unarchivedMultiPolygon); + XCTAssertNotEqualObjects(anotherMultiPolygon, unarchivedMultiPolygon); +} + +- (void)testShapeCollection { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(10.12315786, 11.23451186), + CLLocationCoordinate2DMake(20.91836515, 21.93689215), + CLLocationCoordinate2DMake(30.55697246, 31.33988123), + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + MGLPolyline *polyline = [MGLPolyline polylineWithCoordinates:coordinates count:numberOfCoordinates]; + MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates]; + + MGLShapeCollection *shapeCollection = [MGLShapeCollection shapeCollectionWithShapes:@[polyline, polygon]]; + + NSString *filePath = [self temporaryFilePathForClass:[MGLShapeCollection class]]; + [NSKeyedArchiver archiveRootObject:shapeCollection toFile:filePath]; + + MGLShapeCollection *unarchivedShapeCollection = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MGLShapeCollection *anotherShapeCollection = [MGLShapeCollection shapeCollectionWithShapes:@[polygon]]; + + XCTAssertEqualObjects(shapeCollection, unarchivedShapeCollection); + XCTAssertNotEqualObjects(shapeCollection, anotherShapeCollection); +} + +- (void)testMultiPolylineFeature { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(10.12315786, 11.23451186), + CLLocationCoordinate2DMake(20.91836515, 21.93689215), + CLLocationCoordinate2DMake(30.55697246, 31.33988123), + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + NSMutableArray *polylines = [NSMutableArray array]; + for (NSUInteger i = 0; i < 100; i++) { + MGLPolylineFeature *polylineFeature = [MGLPolylineFeature polylineWithCoordinates:coordinates count:numberOfCoordinates]; + polylineFeature.identifier = @(arc4random() % 100).stringValue; + [polylines addObject:polylineFeature]; + } + + MGLMultiPolylineFeature *multiPolylineFeature = [MGLMultiPolylineFeature multiPolylineWithPolylines:polylines]; + multiPolylineFeature.attributes = @{@"bbox": @[@4, @3, @2, @1]}; + + NSString *filePath = [self temporaryFilePathForClass:[MGLMultiPolylineFeature class]]; + [NSKeyedArchiver archiveRootObject:multiPolylineFeature toFile:filePath]; + + MGLMultiPolylineFeature *unarchivedMultiPolylineFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MGLMultiPolylineFeature *anotherMultiPolylineFeature = [MGLMultiPolylineFeature multiPolylineWithPolylines:[polylines subarrayWithRange:NSMakeRange(0, polylines.count/2)]]; + + XCTAssertEqualObjects(multiPolylineFeature, unarchivedMultiPolylineFeature); + XCTAssertNotEqualObjects(unarchivedMultiPolylineFeature, anotherMultiPolylineFeature); +} + +- (void)testMultiPolygonFeature { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(10.12315786, 11.23451185), + CLLocationCoordinate2DMake(20.88471238, 21.93684215), + CLLocationCoordinate2DMake(30.15697236, 31.32988123), + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + NSMutableArray *polygons = [NSMutableArray array]; + for (NSUInteger i = 0; i < 100; i++ ) { + MGLPolygonFeature *polygonFeature = [MGLPolygonFeature polygonWithCoordinates:coordinates count:numberOfCoordinates]; + polygonFeature.identifier = @(arc4random_uniform(100)).stringValue; + [polygons addObject:polygonFeature]; + } + + MGLMultiPolygonFeature *multiPolygonFeature = [MGLMultiPolygonFeature multiPolygonWithPolygons:polygons]; + multiPolygonFeature.attributes = @{@"bbox": @[@(arc4random_uniform(100)), + @(arc4random_uniform(100)), + @(arc4random_uniform(100)), + @(arc4random_uniform(100))]}; + + NSString *filePath = [self temporaryFilePathForClass:[MGLMultiPolylineFeature class]]; + [NSKeyedArchiver archiveRootObject:multiPolygonFeature toFile:filePath]; + + MGLMultiPolygonFeature *unarchivedMultiPolygonFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MGLMultiPolygonFeature *anotherMultiPolygonFeature = [MGLMultiPolygonFeature multiPolygonWithPolygons:[polygons subarrayWithRange:NSMakeRange(0, polygons.count/2)]]; + + XCTAssertEqualObjects(multiPolygonFeature, unarchivedMultiPolygonFeature); + XCTAssertNotEqualObjects(anotherMultiPolygonFeature, unarchivedMultiPolygonFeature); +} + +- (void)testShapeCollectionFeature { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(10.12315786, 11.23451186), + CLLocationCoordinate2DMake(20.91836515, 21.93689215), + CLLocationCoordinate2DMake(30.55697246, 31.33988123), + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + MGLPolylineFeature *polyline = [MGLPolylineFeature polylineWithCoordinates:coordinates count:numberOfCoordinates]; + MGLPolygonFeature *polygon = [MGLPolygonFeature polygonWithCoordinates:coordinates count:numberOfCoordinates]; + + MGLShapeCollectionFeature *shapeCollectionFeature = [MGLShapeCollectionFeature shapeCollectionWithShapes:@[polyline, polygon]]; + shapeCollectionFeature.identifier = @(arc4random_uniform(100)).stringValue; + shapeCollectionFeature.attributes = @{@"bbox":@[@(arc4random_uniform(100)), + @(arc4random_uniform(100)), + @(arc4random_uniform(100)), + @(arc4random_uniform(100))]}; + + NSString *filePath = [self temporaryFilePathForClass:[MGLShapeCollectionFeature class]]; + [NSKeyedArchiver archiveRootObject:shapeCollectionFeature toFile:filePath]; + + MGLShapeCollectionFeature *unarchivedShapeCollectionFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(shapeCollectionFeature, unarchivedShapeCollectionFeature); +} + +- (void)testAnnotationImage { +#if TARGET_OS_IPHONE + UIGraphicsBeginImageContext(CGSizeMake(10, 10)); + [[UIColor redColor] setFill]; + UIRectFill(CGRectMake(0, 0, 10, 10)); + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); +#else + NSImage *image = [[NSImage alloc] initWithSize:CGSizeMake(10, 10)]; + [image lockFocus]; + [[NSColor redColor] drawSwatchInRect:CGRectMake(0, 0, 10, 10)]; + [image unlockFocus]; +#endif + + MGLAnnotationImage *annotationImage = [MGLAnnotationImage annotationImageWithImage:image reuseIdentifier:@(arc4random_uniform(100)).stringValue]; + + NSString *filePath = [self temporaryFilePathForClass:[MGLAnnotationImage class]]; + [NSKeyedArchiver archiveRootObject:annotationImage toFile:filePath]; + + MGLAnnotationImage *unarchivedAnnotationImage = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(annotationImage, unarchivedAnnotationImage); +} + +#if TARGET_OS_IPHONE +- (void)testAnnotationView { + MGLAnnotationView *annotationView = [[MGLAnnotationView alloc] initWithReuseIdentifier:@"id"]; + annotationView.enabled = NO; + annotationView.selected = YES; + annotationView.draggable = YES; + annotationView.centerOffset = CGVectorMake(10, 10); + annotationView.scalesWithViewingDistance = NO; + + NSString *filePath = [self temporaryFilePathForClass:[MGLAnnotationView class]]; + [NSKeyedArchiver archiveRootObject:annotationView toFile:filePath]; + + MGLAnnotationView *unarchivedAnnotationView = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqual(annotationView.enabled, unarchivedAnnotationView.enabled); + XCTAssertEqual(annotationView.selected, unarchivedAnnotationView.selected); + XCTAssertEqual(annotationView.draggable, unarchivedAnnotationView.draggable); + XCTAssertEqualObjects(NSStringFromCGVector(annotationView.centerOffset), NSStringFromCGVector(unarchivedAnnotationView.centerOffset)); + XCTAssertEqual(annotationView.scalesWithViewingDistance, unarchivedAnnotationView.scalesWithViewingDistance); +} +#endif + +#if TARGET_OS_IPHONE +- (void)testUserLocation { + MGLUserLocation *userLocation = [[MGLUserLocation alloc] init]; + userLocation.location = [[CLLocation alloc] initWithLatitude:1 longitude:1]; + + NSString *filePath = [self temporaryFilePathForClass:[MGLUserLocation class]]; + [NSKeyedArchiver archiveRootObject:userLocation toFile:filePath]; + + MGLUserLocation *unarchivedUserLocation = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(userLocation, unarchivedUserLocation); + unarchivedUserLocation.location = [[CLLocation alloc] initWithLatitude:10 longitude:10]; + XCTAssertNotEqualObjects(userLocation, unarchivedUserLocation); +} +#endif + +#if TARGET_OS_IPHONE +- (void)testUserLocationAnnotationView { + MGLUserLocationAnnotationView *annotationView = [[MGLUserLocationAnnotationView alloc] init]; + annotationView.enabled = NO; + annotationView.selected = YES; + annotationView.draggable = YES; + annotationView.centerOffset = CGVectorMake(10, 10); + annotationView.scalesWithViewingDistance = NO; + + NSString *filePath = [self temporaryFilePathForClass:[MGLUserLocationAnnotationView class]]; + [NSKeyedArchiver archiveRootObject:annotationView toFile:filePath]; + + MGLUserLocationAnnotationView *unarchivedAnnotationView = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqual(annotationView.enabled, unarchivedAnnotationView.enabled); + XCTAssertEqual(annotationView.selected, unarchivedAnnotationView.selected); + XCTAssertEqual(annotationView.draggable, unarchivedAnnotationView.draggable); + XCTAssertEqualObjects(NSStringFromCGVector(annotationView.centerOffset), NSStringFromCGVector(unarchivedAnnotationView.centerOffset)); + XCTAssertEqual(annotationView.scalesWithViewingDistance, unarchivedAnnotationView.scalesWithViewingDistance); +} +#endif + +@end diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index c9cf5e7b1a6..139e2b014b5 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -93,6 +93,10 @@ 357579891D502B06000B822E /* MGLCircleStyleLayerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 357579881D502B06000B822E /* MGLCircleStyleLayerTests.m */; }; 3575798B1D502B0C000B822E /* MGLBackgroundStyleLayerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3575798A1D502B0C000B822E /* MGLBackgroundStyleLayerTests.m */; }; 3575798E1D502EC7000B822E /* MGLRuntimeStylingHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 3575798D1D502EC7000B822E /* MGLRuntimeStylingHelper.m */; }; + 357FE2DD1E02D2B20068B753 /* NSCoder+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 357FE2DB1E02D2B20068B753 /* NSCoder+MGLAdditions.h */; }; + 357FE2DE1E02D2B20068B753 /* NSCoder+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 357FE2DB1E02D2B20068B753 /* NSCoder+MGLAdditions.h */; }; + 357FE2DF1E02D2B20068B753 /* NSCoder+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 357FE2DC1E02D2B20068B753 /* NSCoder+MGLAdditions.mm */; }; + 357FE2E01E02D2B20068B753 /* NSCoder+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 357FE2DC1E02D2B20068B753 /* NSCoder+MGLAdditions.mm */; }; 3599A3E61DF708BC00E77FB2 /* MGLStyleValueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3599A3E51DF708BC00E77FB2 /* MGLStyleValueTests.m */; }; 359F57461D2FDDA6005217F1 /* MGLUserLocationAnnotationView_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 359F57451D2FDBD5005217F1 /* MGLUserLocationAnnotationView_Private.h */; }; 35B82BF81D6C5F8400B1B721 /* NSPredicate+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 35B82BF61D6C5F8400B1B721 /* NSPredicate+MGLAdditions.h */; }; @@ -112,6 +116,7 @@ 35D13AC41D3D19DD00AFB4E0 /* MGLFillStyleLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 35D13AC11D3D19DD00AFB4E0 /* MGLFillStyleLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 35D13AC51D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35D13AC21D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm */; }; 35D13AC61D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35D13AC21D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm */; }; + 35D9DDE21DA25EEC00DAAD69 /* MGLCodingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 35D9DDE11DA25EEC00DAAD69 /* MGLCodingTests.m */; }; 35E0CFE61D3E501500188327 /* MGLStyle_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 35E0CFE51D3E501500188327 /* MGLStyle_Private.h */; }; 35E0CFE71D3E501500188327 /* MGLStyle_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 35E0CFE51D3E501500188327 /* MGLStyle_Private.h */; }; 35E1A4D81D74336F007AA97F /* MGLValueEvaluator.h in Headers */ = {isa = PBXBuildFile; fileRef = 35E1A4D71D74336F007AA97F /* MGLValueEvaluator.h */; }; @@ -559,6 +564,8 @@ 3575798C1D502EC7000B822E /* MGLRuntimeStylingHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MGLRuntimeStylingHelper.h; path = ../../darwin/test/MGLRuntimeStylingHelper.h; sourceTree = ""; }; 3575798D1D502EC7000B822E /* MGLRuntimeStylingHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLRuntimeStylingHelper.m; path = ../../darwin/test/MGLRuntimeStylingHelper.m; sourceTree = ""; }; 357F09091DF84F3800941873 /* MGLStyleValueTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MGLStyleValueTests.h; path = ../../darwin/test/MGLStyleValueTests.h; sourceTree = ""; }; + 357FE2DB1E02D2B20068B753 /* NSCoder+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSCoder+MGLAdditions.h"; path = "../../darwin/src/NSCoder+MGLAdditions.h"; sourceTree = ""; }; + 357FE2DC1E02D2B20068B753 /* NSCoder+MGLAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "NSCoder+MGLAdditions.mm"; path = "../../darwin/src/NSCoder+MGLAdditions.mm"; sourceTree = ""; }; 3599A3E51DF708BC00E77FB2 /* MGLStyleValueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLStyleValueTests.m; path = ../../darwin/test/MGLStyleValueTests.m; sourceTree = ""; }; 359F57451D2FDBD5005217F1 /* MGLUserLocationAnnotationView_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLUserLocationAnnotationView_Private.h; sourceTree = ""; }; 35B82BF61D6C5F8400B1B721 /* NSPredicate+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSPredicate+MGLAdditions.h"; sourceTree = ""; }; @@ -570,6 +577,7 @@ 35D13AB61D3D15E300AFB4E0 /* MGLStyleLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLStyleLayer.mm; sourceTree = ""; }; 35D13AC11D3D19DD00AFB4E0 /* MGLFillStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLFillStyleLayer.h; sourceTree = ""; }; 35D13AC21D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLFillStyleLayer.mm; sourceTree = ""; }; + 35D9DDE11DA25EEC00DAAD69 /* MGLCodingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLCodingTests.m; path = ../../darwin/test/MGLCodingTests.m; sourceTree = ""; }; 35E0CFE51D3E501500188327 /* MGLStyle_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLStyle_Private.h; sourceTree = ""; }; 35E1A4D71D74336F007AA97F /* MGLValueEvaluator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLValueEvaluator.h; sourceTree = ""; }; 35E208A61D24210F00EC9A46 /* MGLNSDataAdditionsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLNSDataAdditionsTests.m; sourceTree = ""; }; @@ -960,6 +968,8 @@ 35CE61811D4165D9004F2359 /* UIColor+MGLAdditions.mm */, 30E578111DAA7D690050F07E /* UIImage+MGLAdditions.h */, 30E578121DAA7D690050F07E /* UIImage+MGLAdditions.mm */, + 357FE2DB1E02D2B20068B753 /* NSCoder+MGLAdditions.h */, + 357FE2DC1E02D2B20068B753 /* NSCoder+MGLAdditions.mm */, ); name = Categories; sourceTree = ""; @@ -1095,6 +1105,7 @@ DA2E885D1CC0382C00F24E7B /* MGLOfflinePackTests.m */, DA2E885E1CC0382C00F24E7B /* MGLOfflineRegionTests.m */, DA2E885F1CC0382C00F24E7B /* MGLOfflineStorageTests.m */, + 35D9DDE11DA25EEC00DAAD69 /* MGLCodingTests.m */, DA2E88601CC0382C00F24E7B /* MGLStyleTests.mm */, DD58A4C51D822BD000E1F038 /* MGLExpressionTests.mm */, DA2E88551CC036F400F24E7B /* Info.plist */, @@ -1454,6 +1465,7 @@ 4018B1C91CDC288A00F666AF /* MGLAnnotationView_Private.h in Headers */, 35E1A4D81D74336F007AA97F /* MGLValueEvaluator.h in Headers */, DA88482C1CBAFA6200AB86E3 /* NSBundle+MGLAdditions.h in Headers */, + 357FE2DD1E02D2B20068B753 /* NSCoder+MGLAdditions.h in Headers */, 7E016D7E1D9E86BE00A29A21 /* MGLPolyline+MGLAdditions.h in Headers */, 35D13AB71D3D15E300AFB4E0 /* MGLStyleLayer.h in Headers */, DA88488E1CBB047F00AB86E3 /* reachability.h in Headers */, @@ -1608,6 +1620,7 @@ DA35A2B21CCA141D00E826B2 /* MGLCompassDirectionFormatter.h in Headers */, DAF0D8141DFE0EC500B28378 /* MGLVectorSource_Private.h in Headers */, DABFB8731CBE9A9900D62B32 /* Mapbox.h in Headers */, + 357FE2DE1E02D2B20068B753 /* NSCoder+MGLAdditions.h in Headers */, 354B83971D2E873E005D9406 /* MGLUserLocationAnnotationView.h in Headers */, DAF0D8111DFE0EA000B28378 /* MGLRasterSource_Private.h in Headers */, DABFB86B1CBE99E500D62B32 /* MGLTilePyramidOfflineRegion.h in Headers */, @@ -1976,6 +1989,7 @@ DA2E88651CC0382C00F24E7B /* MGLStyleTests.mm in Sources */, DA2E88611CC0382C00F24E7B /* MGLGeometryTests.mm in Sources */, 357579801D501E09000B822E /* MGLFillStyleLayerTests.m in Sources */, + 35D9DDE21DA25EEC00DAAD69 /* MGLCodingTests.m in Sources */, DA2E88641CC0382C00F24E7B /* MGLOfflineStorageTests.m in Sources */, DA2DBBCE1D51E80400D38FF9 /* MGLStyleLayerTests.m in Sources */, DA35A2C61CCA9F8300E826B2 /* MGLCompassDirectionFormatterTests.m in Sources */, @@ -2046,6 +2060,7 @@ DA35A2A11CC9E95F00E826B2 /* MGLCoordinateFormatter.m in Sources */, 35305D481D22AA680007D005 /* NSData+MGLAdditions.mm in Sources */, DA8848291CBAFA6200AB86E3 /* MGLStyle.mm in Sources */, + 357FE2DF1E02D2B20068B753 /* NSCoder+MGLAdditions.mm in Sources */, DA88481C1CBAFA6200AB86E3 /* MGLGeometry.mm in Sources */, 3510FFF21D6D9D8C00F413B2 /* NSExpression+MGLAdditions.mm in Sources */, DA88481F1CBAFA6200AB86E3 /* MGLMultiPoint.mm in Sources */, @@ -2121,6 +2136,7 @@ DAA4E4281CBB730400178DFB /* MGLTypes.m in Sources */, DA35A2A21CC9E95F00E826B2 /* MGLCoordinateFormatter.m in Sources */, 35305D491D22AA680007D005 /* NSData+MGLAdditions.mm in Sources */, + 357FE2E01E02D2B20068B753 /* NSCoder+MGLAdditions.mm in Sources */, DAA4E42D1CBB730400178DFB /* MGLAnnotationImage.m in Sources */, 3510FFF31D6D9D8C00F413B2 /* NSExpression+MGLAdditions.mm in Sources */, DAA4E4301CBB730400178DFB /* MGLLocationManager.m in Sources */, diff --git a/platform/ios/src/MGLAnnotationImage.h b/platform/ios/src/MGLAnnotationImage.h index a7003d7f911..95bce21f51e 100644 --- a/platform/ios/src/MGLAnnotationImage.h +++ b/platform/ios/src/MGLAnnotationImage.h @@ -8,7 +8,7 @@ NS_ASSUME_NONNULL_BEGIN objects and may be recycled later and put into a reuse queue that is maintained by the map view. */ -@interface MGLAnnotationImage : NSObject +@interface MGLAnnotationImage : NSObject #pragma mark Initializing and Preparing the Image Object diff --git a/platform/ios/src/MGLAnnotationImage.m b/platform/ios/src/MGLAnnotationImage.m index e1085be98da..c753e9e54ef 100644 --- a/platform/ios/src/MGLAnnotationImage.m +++ b/platform/ios/src/MGLAnnotationImage.m @@ -30,6 +30,44 @@ - (instancetype)initWithImage:(UIImage *)image reuseIdentifier:(NSString *)reuse return self; } ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)decoder { + if (self = [super init]) { + _image = [decoder decodeObjectOfClass:[UIImage class] forKey:@"image"]; + _reuseIdentifier = [decoder decodeObjectOfClass:[NSString class] forKey:@"reuseIdentifier"]; + _enabled = [decoder decodeBoolForKey:@"enabled"]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [coder encodeObject:_image forKey:@"image"]; + [coder encodeObject:_reuseIdentifier forKey:@"reuseIdentifier"]; + [coder encodeBool:_enabled forKey:@"enabled"]; +} + +- (BOOL)isEqual:(id)other { + if (self == other) return YES; + if (![other isKindOfClass:[MGLAnnotationImage class]]) return NO; + + MGLAnnotationImage *otherAnnotationImage = other; + + return ((!_reuseIdentifier && !otherAnnotationImage.reuseIdentifier) || [_reuseIdentifier isEqualToString:otherAnnotationImage.reuseIdentifier]) + && _enabled == otherAnnotationImage.enabled + && ((!_image && !otherAnnotationImage.image) || [UIImagePNGRepresentation(_image) isEqualToData:UIImagePNGRepresentation(otherAnnotationImage.image)]); +} + +- (NSUInteger)hash { + NSUInteger hash; + hash += [_reuseIdentifier hash]; + hash += _enabled; + hash += [_image hash]; + return hash; +} + - (void)setImage:(UIImage *)image { _image = image; [self.delegate annotationImageNeedsRedisplay:self]; diff --git a/platform/ios/src/MGLAnnotationView.h b/platform/ios/src/MGLAnnotationView.h index 634e9ad7234..d159976a4c9 100644 --- a/platform/ios/src/MGLAnnotationView.h +++ b/platform/ios/src/MGLAnnotationView.h @@ -50,7 +50,7 @@ typedef NS_ENUM(NSUInteger, MGLAnnotationViewDragState) { interactivity such as dragging, you can use an `MGLAnnotationImage` instead to conserve memory and optimize drawing performance. */ -@interface MGLAnnotationView : UIView +@interface MGLAnnotationView : UIView #pragma mark Initializing and Preparing the View diff --git a/platform/ios/src/MGLAnnotationView.mm b/platform/ios/src/MGLAnnotationView.mm index 96ed8c733e6..d2243bdf23d 100644 --- a/platform/ios/src/MGLAnnotationView.mm +++ b/platform/ios/src/MGLAnnotationView.mm @@ -33,6 +33,34 @@ - (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier return self; } ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)decoder { + if (self = [super initWithCoder:decoder]) { + _reuseIdentifier = [decoder decodeObjectOfClass:[NSString class] forKey:@"reuseIdentifier"]; + _annotation = [decoder decodeObjectOfClass:[NSObject class] forKey:@"annotation"]; + _centerOffset = [decoder decodeCGVectorForKey:@"centerOffset"]; + _scalesWithViewingDistance = [decoder decodeBoolForKey:@"scalesWithViewingDistance"]; + _selected = [decoder decodeBoolForKey:@"selected"]; + _enabled = [decoder decodeBoolForKey:@"enabled"]; + self.draggable = [decoder decodeBoolForKey:@"draggable"]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [super encodeWithCoder:coder]; + [coder encodeObject:_reuseIdentifier forKey:@"reuseIdentifier"]; + [coder encodeObject:_annotation forKey:@"annotation"]; + [coder encodeCGVector:_centerOffset forKey:@"centerOffset"]; + [coder encodeBool:_scalesWithViewingDistance forKey:@"scalesWithViewingDistance"]; + [coder encodeBool:_selected forKey:@"selected"]; + [coder encodeBool:_enabled forKey:@"enabled"]; + [coder encodeBool:_draggable forKey:@"draggable"]; +} + - (void)prepareForReuse { // Intentionally left blank. The default implementation of this method does nothing. diff --git a/platform/ios/src/MGLUserLocation.h b/platform/ios/src/MGLUserLocation.h index f2243815cf2..1a27d31dd4f 100644 --- a/platform/ios/src/MGLUserLocation.h +++ b/platform/ios/src/MGLUserLocation.h @@ -11,7 +11,7 @@ NS_ASSUME_NONNULL_BEGIN directly. Instead, you retrieve an existing MGLUserLocation object from the `userLocation` property of the map view displayed in your application. */ -@interface MGLUserLocation : NSObject +@interface MGLUserLocation : NSObject #pragma mark Determining the User’s Position diff --git a/platform/ios/src/MGLUserLocation.m b/platform/ios/src/MGLUserLocation.m index a568ec8be1e..97e3f740fc7 100644 --- a/platform/ios/src/MGLUserLocation.m +++ b/platform/ios/src/MGLUserLocation.m @@ -26,6 +26,44 @@ - (instancetype)initWithMapView:(MGLMapView *)mapView return self; } ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)decoder { + if (self = [super init]) { + _location = [decoder decodeObjectOfClass:[CLLocation class] forKey:@"location"]; + _title = [decoder decodeObjectOfClass:[NSString class] forKey:@"title"]; + _subtitle = [decoder decodeObjectOfClass:[NSString class] forKey:@"subtitle"]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [coder encodeObject:_location forKey:@"location"]; + [coder encodeObject:_title forKey:@"title"]; + [coder encodeObject:_subtitle forKey:@"subtitle"]; +} + +- (BOOL)isEqual:(id)other { + if (self == other) return YES; + if (![other isKindOfClass:[MGLUserLocation class]]) return NO; + + MGLUserLocation *otherUserLocation = other; + return ((!self.location && !otherUserLocation.location) || [self.location distanceFromLocation:otherUserLocation.location] == 0) + && ((!self.title && !otherUserLocation.title) || [self.title isEqualToString:otherUserLocation.title]) + && ((!self.subtitle && !otherUserLocation.subtitle) || [self.subtitle isEqualToString:otherUserLocation.subtitle]); +} + +- (NSUInteger)hash { + NSUInteger hash = [super hash]; + hash += [_location hash]; + hash += [_heading hash]; + hash += [_title hash]; + hash += [_subtitle hash]; + return hash; +} + + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { return ! [key isEqualToString:@"location"] && ! [key isEqualToString:@"heading"]; diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj index e68d5f1bf50..951ae1bd371 100644 --- a/platform/macos/macos.xcodeproj/project.pbxproj +++ b/platform/macos/macos.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 30E5781B1DAA857E0050F07E /* NSImage+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 30E578141DAA7D920050F07E /* NSImage+MGLAdditions.h */; }; 3508EC641D749D39009B0EE4 /* NSExpression+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 3508EC621D749D39009B0EE4 /* NSExpression+MGLAdditions.h */; }; 3508EC651D749D39009B0EE4 /* NSExpression+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3508EC631D749D39009B0EE4 /* NSExpression+MGLAdditions.mm */; }; + 3526EABD1DF9B19800006B43 /* MGLCodingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3526EABC1DF9B19800006B43 /* MGLCodingTests.m */; }; 352742781D4C220900A1ECE6 /* MGLStyleValue.h in Headers */ = {isa = PBXBuildFile; fileRef = 352742771D4C220900A1ECE6 /* MGLStyleValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; 352742811D4C243B00A1ECE6 /* MGLSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 3527427F1D4C243B00A1ECE6 /* MGLSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; 352742821D4C243B00A1ECE6 /* MGLSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 352742801D4C243B00A1ECE6 /* MGLSource.mm */; }; @@ -34,6 +35,8 @@ 35602C001D3EA9B40050646F /* MGLForegroundStyleLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 35602BFD1D3EA9B40050646F /* MGLForegroundStyleLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 35602C011D3EA9B40050646F /* MGLForegroundStyleLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 35602BFE1D3EA9B40050646F /* MGLForegroundStyleLayer.m */; }; 35724FC41D630502002A4AB4 /* amsterdam.geojson in Resources */ = {isa = PBXBuildFile; fileRef = 358EB3AE1D61F0DB00E46D9C /* amsterdam.geojson */; }; + 359819591E02F611008FC139 /* NSCoder+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 359819571E02F611008FC139 /* NSCoder+MGLAdditions.h */; }; + 3598195A1E02F611008FC139 /* NSCoder+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 359819581E02F611008FC139 /* NSCoder+MGLAdditions.mm */; }; 3599A3E81DF70E2000E77FB2 /* MGLStyleValueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3599A3E71DF70E2000E77FB2 /* MGLStyleValueTests.m */; }; 35C5D8471D6DD66D00E95907 /* NSComparisonPredicate+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 35C5D8431D6DD66D00E95907 /* NSComparisonPredicate+MGLAdditions.h */; }; 35C5D8481D6DD66D00E95907 /* NSComparisonPredicate+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35C5D8441D6DD66D00E95907 /* NSComparisonPredicate+MGLAdditions.mm */; }; @@ -252,6 +255,7 @@ 30E578141DAA7D920050F07E /* NSImage+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSImage+MGLAdditions.h"; path = "src/NSImage+MGLAdditions.h"; sourceTree = SOURCE_ROOT; }; 3508EC621D749D39009B0EE4 /* NSExpression+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSExpression+MGLAdditions.h"; sourceTree = ""; }; 3508EC631D749D39009B0EE4 /* NSExpression+MGLAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSExpression+MGLAdditions.mm"; sourceTree = ""; }; + 3526EABC1DF9B19800006B43 /* MGLCodingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLCodingTests.m; path = ../../darwin/test/MGLCodingTests.m; sourceTree = ""; }; 352742771D4C220900A1ECE6 /* MGLStyleValue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLStyleValue.h; sourceTree = ""; }; 3527427F1D4C243B00A1ECE6 /* MGLSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLSource.h; sourceTree = ""; }; 352742801D4C243B00A1ECE6 /* MGLSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLSource.mm; sourceTree = ""; }; @@ -276,6 +280,8 @@ 35602BFD1D3EA9B40050646F /* MGLForegroundStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLForegroundStyleLayer.h; sourceTree = ""; }; 35602BFE1D3EA9B40050646F /* MGLForegroundStyleLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLForegroundStyleLayer.m; sourceTree = ""; }; 358EB3AE1D61F0DB00E46D9C /* amsterdam.geojson */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = amsterdam.geojson; path = ../../darwin/test/amsterdam.geojson; sourceTree = ""; }; + 359819571E02F611008FC139 /* NSCoder+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSCoder+MGLAdditions.h"; sourceTree = ""; }; + 359819581E02F611008FC139 /* NSCoder+MGLAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSCoder+MGLAdditions.mm"; sourceTree = ""; }; 3599A3E71DF70E2000E77FB2 /* MGLStyleValueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLStyleValueTests.m; sourceTree = ""; }; 35C5D8431D6DD66D00E95907 /* NSComparisonPredicate+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSComparisonPredicate+MGLAdditions.h"; sourceTree = ""; }; 35C5D8441D6DD66D00E95907 /* NSComparisonPredicate+MGLAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSComparisonPredicate+MGLAdditions.mm"; sourceTree = ""; }; @@ -814,6 +820,8 @@ 40B77E421DB11BB0003DA2FE /* NSArray+MGLAdditions.mm */, DAE6C37D1CC31E2A00DB3429 /* NSBundle+MGLAdditions.h */, DAE6C37E1CC31E2A00DB3429 /* NSBundle+MGLAdditions.m */, + 359819571E02F611008FC139 /* NSCoder+MGLAdditions.h */, + 359819581E02F611008FC139 /* NSCoder+MGLAdditions.mm */, 35C5D8431D6DD66D00E95907 /* NSComparisonPredicate+MGLAdditions.h */, 35C5D8441D6DD66D00E95907 /* NSComparisonPredicate+MGLAdditions.mm */, 35C5D8451D6DD66D00E95907 /* NSCompoundPredicate+MGLAdditions.h */, @@ -875,6 +883,7 @@ DA8F257D1D51C5F40010E6B5 /* Styling */, DAEDC4311D6033F1000224FF /* MGLAttributionInfoTests.m */, DAEDC4361D606291000224FF /* MGLAttributionButtonTests.m */, + 3526EABC1DF9B19800006B43 /* MGLCodingTests.m */, DA35A2C11CCA9F4A00E826B2 /* MGLClockDirectionFormatterTests.m */, DA35A2B51CCA14D700E826B2 /* MGLCompassDirectionFormatterTests.m */, DA35A2A71CC9F41600E826B2 /* MGLCoordinateFormatterTests.m */, @@ -1015,6 +1024,7 @@ DAE6C39A1CC31E2A00DB3429 /* NSProcessInfo+MGLAdditions.h in Headers */, DA8F258B1D51CA540010E6B5 /* MGLLineStyleLayer.h in Headers */, DA8F25B21D51CB270010E6B5 /* NSValue+MGLStyleAttributeAdditions.h in Headers */, + 359819591E02F611008FC139 /* NSCoder+MGLAdditions.h in Headers */, DAE6C38E1CC31E2A00DB3429 /* MGLOfflineStorage_Private.h in Headers */, 408AA8661DAEEE3600022900 /* MGLPolyline+MGLAdditions.h in Headers */, DA87A9A01DC9DC6200810D09 /* MGLValueEvaluator.h in Headers */, @@ -1252,6 +1262,7 @@ 352742861D4C244700A1ECE6 /* MGLRasterSource.mm in Sources */, DAE6C39D1CC31E2A00DB3429 /* NSString+MGLAdditions.m in Sources */, 4032C5C61DE1FE9B0062E8BD /* NSValue+MGLStyleEnumAttributeAdditions.mm in Sources */, + 3598195A1E02F611008FC139 /* NSCoder+MGLAdditions.mm in Sources */, DAE6C3941CC31E2A00DB3429 /* MGLStyle.mm in Sources */, DAE6C3871CC31E2A00DB3429 /* MGLGeometry.mm in Sources */, 3527428E1D4C24AB00A1ECE6 /* MGLCircleStyleLayer.mm in Sources */, @@ -1321,6 +1332,7 @@ DA87A9991DC9D88400810D09 /* MGLTileSetTests.mm in Sources */, DA35A2A81CC9F41600E826B2 /* MGLCoordinateFormatterTests.m in Sources */, DA87A9981DC9D88400810D09 /* MGLShapeSourceTests.mm in Sources */, + 3526EABD1DF9B19800006B43 /* MGLCodingTests.m in Sources */, DA87A9A21DC9DCF100810D09 /* MGLFillStyleLayerTests.m in Sources */, 3599A3E81DF70E2000E77FB2 /* MGLStyleValueTests.m in Sources */, DAEDC4321D6033F1000224FF /* MGLAttributionInfoTests.m in Sources */, diff --git a/platform/macos/src/MGLAnnotationImage.h b/platform/macos/src/MGLAnnotationImage.h index 37d43d7277d..2738ea654cf 100644 --- a/platform/macos/src/MGLAnnotationImage.h +++ b/platform/macos/src/MGLAnnotationImage.h @@ -8,7 +8,7 @@ NS_ASSUME_NONNULL_BEGIN `NSImage` objects with annotation-related metadata. They may be recycled later and put into a reuse queue that is maintained by the map view. */ -@interface MGLAnnotationImage : NSObject +@interface MGLAnnotationImage : NSObject #pragma mark Initializing and Preparing the Image Object diff --git a/platform/macos/src/MGLAnnotationImage.m b/platform/macos/src/MGLAnnotationImage.m index 1b545651d2a..f9f900344a1 100644 --- a/platform/macos/src/MGLAnnotationImage.m +++ b/platform/macos/src/MGLAnnotationImage.m @@ -23,4 +23,45 @@ - (instancetype)initWithImage:(NSImage *)image reuseIdentifier:(NSString *)reuse return self; } ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)decoder { + if (self = [super init]) { + _image = [decoder decodeObjectOfClass:[NSImage class] forKey:@"image"]; + _reuseIdentifier = [decoder decodeObjectOfClass:[NSString class] forKey:@"reuseIdentifier"]; + _cursor = [decoder decodeObjectOfClass:[NSCursor class] forKey:@"cursor"]; + _selectable = [decoder decodeBoolForKey:@"selectable"]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [coder encodeObject:_image forKey:@"image"]; + [coder encodeObject:_reuseIdentifier forKey:@"reuseIdentifier"]; + [coder encodeObject:_cursor forKey:@"cursor"]; + [coder encodeBool:_selectable forKey:@"selectable"]; +} + +- (BOOL)isEqual:(id)other { + if (self == other) return YES; + if (![other isKindOfClass:[MGLAnnotationImage class]]) return NO; + + MGLAnnotationImage *otherAnnotationImage = other; + + return ((!_reuseIdentifier && !otherAnnotationImage.reuseIdentifier) || [_reuseIdentifier isEqualToString:otherAnnotationImage.reuseIdentifier]) + && _selectable == otherAnnotationImage.selectable + && ((!_cursor && !otherAnnotationImage.cursor) || [_cursor isEqual:otherAnnotationImage.cursor]) + && ((!_image && !otherAnnotationImage.image) || [[_image TIFFRepresentation] isEqualToData:[otherAnnotationImage.image TIFFRepresentation]]); +} + +- (NSUInteger)hash { + NSUInteger hash; + hash += [_reuseIdentifier hash]; + hash += _selectable; + hash += [_image hash]; + return hash; +} + @end