Skip to content

Commit

Permalink
[google_maps_flutter] Implement polyline patterns in google maps ios (f…
Browse files Browse the repository at this point in the history
…lutter#5757)

This PR Implements patterns in google maps ios polylines. Currently the patterns param is simply ignored on ios, despite the official google maps SDK for ios having instructions on how to achieve repeated patterns: https://developers.google.com/maps/documentation/ios-sdk/shapes#add-a-repeating-color-pattern-to-a-polyline

*List which issues are fixed by this PR. You must list at least one issue.*
flutter#60083

*If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*
Nil
  • Loading branch information
Hari-07 authored May 28, 2024
1 parent b16a7e3 commit a61103b
Show file tree
Hide file tree
Showing 14 changed files with 213 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -291,10 +291,10 @@ class PlacePolylineBodyState extends State<PlacePolylineBody> {
child: const Text('change joint type [Android only]'),
),
TextButton(
onPressed: isIOS || (selectedId == null)
onPressed: (selectedId == null)
? null
: () => _changePattern(selectedId),
child: const Text('change pattern [Android only]'),
child: const Text('change pattern'),
),
],
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,10 +292,10 @@ class PlacePolylineBodyState extends State<PlacePolylineBody> {
child: const Text('change joint type [Android only]'),
),
TextButton(
onPressed: isIOS || (selectedId == null)
onPressed: (selectedId == null)
? null
: () => _changePattern(selectedId),
child: const Text('change pattern [Android only]'),
child: const Text('change pattern'),
),
],
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.6.1

* Adds support for patterns in polylines.

## 2.6.0

* Updates the minimum allowed verison of the Google Maps SDK to 8.4, for privacy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
4510D964F3B1259FEDD3ABA6 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */; };
478116522BEF8F47002F593E /* GoogleMapsPolylinesControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 478116512BEF8F47002F593E /* GoogleMapsPolylinesControllerTests.m */; };
6851F3562835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6851F3552835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m */; };
68E4726A2836FF0C00BDDDAC /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 68E472692836FF0C00BDDDAC /* MapKit.framework */; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
F269303B2BB389BF00BF17C4 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = F269303A2BB389BF00BF17C4 /* assets */; };
982F2A6C27BADE17003C81F4 /* PartiallyMockedMapView.m in Sources */ = {isa = PBXBuildFile; fileRef = 982F2A6B27BADE17003C81F4 /* PartiallyMockedMapView.m */; };
F269303B2BB389BF00BF17C4 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = F269303A2BB389BF00BF17C4 /* assets */; };
F7151F13265D7ED70028CB91 /* GoogleMapsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F12265D7ED70028CB91 /* GoogleMapsTests.m */; };
F7151F21265D7EE50028CB91 /* GoogleMapsUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F20265D7EE50028CB91 /* GoogleMapsUITests.m */; };
FC8F35FC8CD533B128950487 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F267F68029D1A4E2E4C572A7 /* libPods-RunnerTests.a */; };
Expand Down Expand Up @@ -60,6 +61,7 @@
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
478116512BEF8F47002F593E /* GoogleMapsPolylinesControllerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoogleMapsPolylinesControllerTests.m; sourceTree = "<group>"; };
6851F3552835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTGoogleMapJSONConversionsConversionTests.m; sourceTree = "<group>"; };
68E472692836FF0C00BDDDAC /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk/System/iOSSupport/System/Library/Frameworks/MapKit.framework; sourceTree = DEVELOPER_DIR; };
733AFAB37683A9DA7512F09C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -203,6 +205,7 @@
F269303A2BB389BF00BF17C4 /* assets */,
6851F3552835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m */,
F7151F12265D7ED70028CB91 /* GoogleMapsTests.m */,
478116512BEF8F47002F593E /* GoogleMapsPolylinesControllerTests.m */,
982F2A6A27BADE17003C81F4 /* PartiallyMockedMapView.h */,
982F2A6B27BADE17003C81F4 /* PartiallyMockedMapView.m */,
F7151F14265D7ED70028CB91 /* Info.plist */,
Expand Down Expand Up @@ -467,6 +470,7 @@
F7151F13265D7ED70028CB91 /* GoogleMapsTests.m in Sources */,
6851F3562835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m in Sources */,
982F2A6C27BADE17003C81F4 /* PartiallyMockedMapView.m in Sources */,
478116522BEF8F47002F593E /* GoogleMapsPolylinesControllerTests.m in Sources */,
0DD7B6C32B744EEF00E857FD /* FLTTileProviderControllerTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,4 +288,18 @@ - (void)testCameraUpdateFromChannelValueZoomTo {
[classMockCameraUpdate stopMocking];
}

- (void)testLengthsFromPatterns {
NSArray<NSArray<id> *> *patterns = @[ @[ @"gap", @10 ], @[ @"dash", @6.4 ] ];

NSArray<NSNumber *> *spanLengths = [FLTGoogleMapJSONConversions spanLengthsFromPatterns:patterns];

XCTAssertEqual([spanLengths count], 2);

NSNumber *firstSpanLength = spanLengths[0];
NSNumber *secondSpanLength = spanLengths[1];

XCTAssertEqual(firstSpanLength.doubleValue, 10);
XCTAssertEqual(secondSpanLength.doubleValue, 6.4);
}

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

@import google_maps_flutter_ios;
@import google_maps_flutter_ios.Test;
@import XCTest;
@import GoogleMaps;

#import <OCMock/OCMock.h>
#import <google_maps_flutter_ios/GoogleMapPolylineController_Test.h>
#import "PartiallyMockedMapView.h"

@interface GoogleMapsPolylinesControllerTests : XCTestCase
@end

@implementation GoogleMapsPolylinesControllerTests

/// Returns GoogleMapPolylineController object instantiated with a mocked map instance
///
/// @return An object of FLTGoogleMapPolylineController
- (FLTGoogleMapPolylineController *)polylineControllerWithMockedMap {
NSDictionary<NSString *, id> *polyline = @{
@"points" : @[
@[ @(52.4816), @(-3.1791) ], @[ @(54.043), @(-2.9925) ], @[ @(54.1396), @(-4.2739) ],
@[ @(53.4153), @(-4.0829) ]
],
@"polylineId" : @"polyline_id_0",
};

CGRect frame = CGRectMake(0, 0, 100, 100);
GMSCameraPosition *camera = [[GMSCameraPosition alloc] initWithLatitude:0 longitude:0 zoom:0];

GMSMapViewOptions *mapViewOptions = [[GMSMapViewOptions alloc] init];
mapViewOptions.frame = frame;
mapViewOptions.camera = camera;

PartiallyMockedMapView *mapView = [[PartiallyMockedMapView alloc] initWithOptions:mapViewOptions];

GMSMutablePath *path = [FLTPolylinesController pathForPolyline:polyline];
NSString *identifier = polyline[@"polylineId"];

FLTGoogleMapPolylineController *polylineControllerWithMockedMap =
[[FLTGoogleMapPolylineController alloc] initPolylineWithPath:path
identifier:identifier
mapView:mapView];

return polylineControllerWithMockedMap;
}

- (void)testSetPatterns {
NSArray<GMSStrokeStyle *> *styles = @[
[GMSStrokeStyle solidColor:[UIColor clearColor]], [GMSStrokeStyle solidColor:[UIColor redColor]]
];

NSArray<NSNumber *> *lengths = @[ @10, @10 ];

FLTGoogleMapPolylineController *polylineController = [self polylineControllerWithMockedMap];

XCTAssertNil(polylineController.polyline.spans);

[polylineController setPattern:styles lengths:lengths];

// `GMSStyleSpan` doesn't implement `isEqual` so cannot be compared by value at present.
XCTAssertNotNil(polylineController.polyline.spans);
}

- (void)testStrokeStylesFromPatterns {
NSArray<NSArray<id> *> *patterns = @[ @[ @"gap", @10 ], @[ @"dash", @10 ] ];
UIColor *strokeColor = [UIColor redColor];

NSArray<GMSStrokeStyle *> *patternStrokeStyle =
[FLTGoogleMapJSONConversions strokeStylesFromPatterns:patterns strokeColor:strokeColor];

XCTAssertEqual([patternStrokeStyle count], 2);

// None of the parameters of `patternStrokeStyle` is observable, so we limit to testing
// the length of this output array.
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -292,10 +292,10 @@ class PlacePolylineBodyState extends State<PlacePolylineBody> {
child: const Text('change joint type [Android only]'),
),
TextButton(
onPressed: isIOS || (selectedId == null)
onPressed: (selectedId == null)
? null
: () => _changePattern(selectedId),
child: const Text('change pattern [Android only]'),
child: const Text('change pattern'),
),
],
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,21 @@ NS_ASSUME_NONNULL_BEGIN
+ (GMSMapViewType)mapViewTypeFromTypeValue:(NSNumber *)value;
+ (nullable GMSCameraUpdate *)cameraUpdateFromChannelValue:(NSArray *)channelValue;

/// Return GMS strokestyle object array populated using the patterns and stroke colors passed in.
///
/// @param patterns An array of patterns for each stroke in the polyline.
/// @param strokeColor An array of color for each stroke in the polyline.
/// @return An array of GMSStrokeStyle.
+ (NSArray<GMSStrokeStyle *> *)strokeStylesFromPatterns:(NSArray<NSArray<NSObject *> *> *)patterns
strokeColor:(UIColor *)strokeColor;

/// Return GMS strokestyle object array populated using the patterns and stroke colors passed in.
/// Extracts the lengths of each stroke in the polyline from patterns input
///
/// @param patterns An array of object representing the pattern params in the polyline.
/// @return Array of lengths.
+ (NSArray<NSNumber *> *)spanLengthsFromPatterns:(NSArray<NSArray<NSObject *> *> *)patterns;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,26 @@ + (nullable GMSCameraUpdate *)cameraUpdateFromChannelValue:(NSArray *)channelVal
}
return nil;
}

+ (NSArray<GMSStrokeStyle *> *)strokeStylesFromPatterns:(NSArray<NSArray<NSObject *> *> *)patterns
strokeColor:(UIColor *)strokeColor {
NSMutableArray *strokeStyles = [[NSMutableArray alloc] initWithCapacity:[patterns count]];
for (NSArray *pattern in patterns) {
NSString *patternType = pattern[0];
UIColor *color = [patternType isEqualToString:@"gap"] ? [UIColor clearColor] : strokeColor;
[strokeStyles addObject:[GMSStrokeStyle solidColor:color]];
}

return strokeStyles;
}

+ (NSArray<NSNumber *> *)spanLengthsFromPatterns:(NSArray<NSArray<NSObject *> *> *)patterns {
NSMutableArray *lengths = [[NSMutableArray alloc] initWithCapacity:[patterns count]];
for (NSArray *pattern in patterns) {
NSNumber *length = [pattern count] > 1 ? pattern[1] : @0;
[lengths addObject:length];
}

return lengths;
}
@end
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
identifier:(NSString *)identifier
mapView:(GMSMapView *)mapView;
- (void)removePolyline;

/// Sets the pattern on polyline controller
///
/// @param styles The styles for repeating pattern sections.
/// @param lengths The lengths for repeating pattern sections.
- (void)setPattern:(NSArray<GMSStrokeStyle *> *)styles lengths:(NSArray<NSNumber *> *)lengths;
@end

@interface FLTPolylinesController : NSObject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ @interface FLTGoogleMapPolylineController ()

@end

/// Returns dict[key], or nil if dict[key] is NSNull.
static id GetValueOrNilFromDict(NSDictionary *dict, NSString *key) {
id value = dict[key];
return value == [NSNull null] ? nil : value;
}

@implementation FLTGoogleMapPolylineController

- (instancetype)initPolylineWithPath:(GMSMutablePath *)path
Expand Down Expand Up @@ -59,42 +65,54 @@ - (void)setGeodesic:(BOOL)isGeodesic {
self.polyline.geodesic = isGeodesic;
}

- (void)setPattern:(NSArray<GMSStrokeStyle *> *)styles lengths:(NSArray<NSNumber *> *)lengths {
self.polyline.spans = GMSStyleSpans(self.polyline.path, styles, lengths, kGMSLengthRhumb);
}

- (void)interpretPolylineOptions:(NSDictionary *)data
registrar:(NSObject<FlutterPluginRegistrar> *)registrar {
NSNumber *consumeTapEvents = data[@"consumeTapEvents"];
if (consumeTapEvents && consumeTapEvents != (id)[NSNull null]) {
NSNumber *consumeTapEvents = GetValueOrNilFromDict(data, @"consumeTapEvents");
if (consumeTapEvents) {
[self setConsumeTapEvents:[consumeTapEvents boolValue]];
}

NSNumber *visible = data[@"visible"];
if (visible && visible != (id)[NSNull null]) {
NSNumber *visible = GetValueOrNilFromDict(data, @"visible");
if (visible) {
[self setVisible:[visible boolValue]];
}

NSNumber *zIndex = data[@"zIndex"];
if (zIndex && zIndex != (id)[NSNull null]) {
NSNumber *zIndex = GetValueOrNilFromDict(data, @"zIndex");
if (zIndex) {
[self setZIndex:[zIndex intValue]];
}

NSArray *points = data[@"points"];
if (points && points != (id)[NSNull null]) {
NSArray *points = GetValueOrNilFromDict(data, @"points");
if (points) {
[self setPoints:[FLTGoogleMapJSONConversions pointsFromLatLongs:points]];
}

NSNumber *strokeColor = data[@"color"];
if (strokeColor && strokeColor != (id)[NSNull null]) {
NSNumber *strokeColor = GetValueOrNilFromDict(data, @"color");
if (strokeColor) {
[self setColor:[FLTGoogleMapJSONConversions colorFromRGBA:strokeColor]];
}

NSNumber *strokeWidth = data[@"width"];
if (strokeWidth && strokeWidth != (id)[NSNull null]) {
NSNumber *strokeWidth = GetValueOrNilFromDict(data, @"width");
if (strokeWidth) {
[self setStrokeWidth:[strokeWidth intValue]];
}

NSNumber *geodesic = data[@"geodesic"];
if (geodesic && geodesic != (id)[NSNull null]) {
NSNumber *geodesic = GetValueOrNilFromDict(data, @"geodesic");
if (geodesic) {
[self setGeodesic:geodesic.boolValue];
}

NSArray *patterns = GetValueOrNilFromDict(data, @"pattern");
if (patterns) {
[self
setPattern:[FLTGoogleMapJSONConversions strokeStylesFromPatterns:patterns
strokeColor:self.polyline.strokeColor]
lengths:[FLTGoogleMapJSONConversions spanLengthsFromPatterns:patterns]];
}
}

@end
Expand Down Expand Up @@ -125,7 +143,7 @@ - (instancetype)init:(FlutterMethodChannel *)methodChannel
}
- (void)addPolylines:(NSArray *)polylinesToAdd {
for (NSDictionary *polyline in polylinesToAdd) {
GMSMutablePath *path = [FLTPolylinesController getPath:polyline];
GMSMutablePath *path = [FLTPolylinesController pathForPolyline:polyline];
NSString *identifier = polyline[@"polylineId"];
FLTGoogleMapPolylineController *controller =
[[FLTGoogleMapPolylineController alloc] initPolylineWithPath:path
Expand Down Expand Up @@ -171,7 +189,7 @@ - (bool)hasPolylineWithIdentifier:(NSString *)identifier {
}
return self.polylineIdentifierToController[identifier] != nil;
}
+ (GMSMutablePath *)getPath:(NSDictionary *)polyline {
+ (GMSMutablePath *)pathForPolyline:(NSDictionary *)polyline {
NSArray *pointArray = polyline[@"points"];
NSArray<CLLocation *> *points = [FLTGoogleMapJSONConversions pointsFromLatLongs:pointArray];
GMSMutablePath *path = [GMSMutablePath path];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "GoogleMapPolylineController.h"

/// Internal APIs exposed for unit testing
@interface FLTGoogleMapPolylineController (Test)

/// Polyline instance the controller is attached to
@property(strong, nonatomic) GMSPolyline *polyline;

@end

/// Internal APIs explosed for unit testing
@interface FLTPolylinesController (Test)

/// Returns the path for polyline based on the points(locations) the polyline has.
///
/// @param polyline The polyline instance for which path is calculated.
/// @return An instance of GMSMutablePath.
+ (GMSMutablePath *)pathForPolyline:(NSDictionary *)polyline;

@end
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#import <google_maps_flutter_ios/FLTGoogleMapJSONConversions.h>
#import <google_maps_flutter_ios/FLTGoogleMapTileOverlayController.h>
#import <google_maps_flutter_ios/FLTGoogleMapsPlugin.h>
#import <google_maps_flutter_ios/GoogleMapPolylineController_Test.h>

FOUNDATION_EXPORT double google_maps_flutterVersionNumber;
FOUNDATION_EXPORT const unsigned char google_maps_flutterVersionString[];
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: google_maps_flutter_ios
description: iOS implementation of the google_maps_flutter plugin.
repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter_ios
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22
version: 2.6.0
version: 2.6.1

environment:
sdk: ^3.2.3
Expand Down

0 comments on commit a61103b

Please sign in to comment.