diff --git a/ios-sdkTests/MarkerTests.swift b/ios-sdkTests/MarkerTests.swift index 9757caa..115a993 100644 --- a/ios-sdkTests/MarkerTests.swift +++ b/ios-sdkTests/MarkerTests.swift @@ -10,9 +10,9 @@ import XCTest @testable import ios_sdk import TangramMap -class MarkerTests: XCTestCase { +class PointMarkerTests: XCTestCase { - let marker = Marker() + let marker = PointMarker() func testPoint() { let point = TGGeoPoint(longitude: 70.0, latitude: 40.0) @@ -21,26 +21,10 @@ class MarkerTests: XCTestCase { XCTAssertEqual(marker.point.longitude, point.longitude) XCTAssertEqual(marker.tgMarker.point.latitude, point.latitude) XCTAssertEqual(marker.tgMarker.point.longitude, point.longitude) + XCTAssertTrue(marker.tgMarker.stylingString.isEmpty) + marker.size = .zero XCTAssertTrue(marker.tgMarker.stylingString.contains("style: 'points'")) - XCTAssertFalse(marker.tgMarker.stylingString.contains("size:")) - } - - func testPolyline() { - let polyline = TGGeoPolyline() - marker.polyline = polyline - XCTAssertEqual(marker.polyline, polyline) - XCTAssertEqual(marker.tgMarker.polyline, polyline) - XCTAssertTrue(marker.tgMarker.stylingString.contains("style: 'lines'")) - XCTAssertFalse(marker.tgMarker.stylingString.contains("size:")) - } - - func testPolygon() { - let polygon = TGGeoPolygon() - marker.polygon = polygon - XCTAssertEqual(marker.polygon, polygon) - XCTAssertEqual(marker.tgMarker.polygon, polygon) - XCTAssertTrue(marker.tgMarker.stylingString.contains("style: 'polygons'")) - XCTAssertFalse(marker.tgMarker.stylingString.contains("size:")) + XCTAssertTrue(marker.tgMarker.stylingString.contains("size:")) } //TODO: failing, fix @@ -100,7 +84,7 @@ class MarkerTests: XCTestCase { func testInitWithSize() { let size = CGSize(width: 30, height: 30) - let m = Marker.init(size: size) + let m = PointMarker.init(size: size) XCTAssertEqual(m.size, size) XCTAssertEqual(m.backgroundColor, UIColor.white) XCTAssertTrue(m.interactive) @@ -108,7 +92,7 @@ class MarkerTests: XCTestCase { } func testInit() { - let m = Marker.init() + let m = PointMarker.init() XCTAssertEqual(m.backgroundColor, UIColor.white) XCTAssertTrue(m.interactive) XCTAssertFalse(m.tgMarker.stylingString.contains("size:")) @@ -126,54 +110,274 @@ class MarkerTests: XCTestCase { func testSetPointEased() { let point = TGGeoPoint(longitude: 70.0, latitude: 40.0) let tgMarker = TestTGMarker() - let m = Marker(tgMarker: tgMarker) + let m = PointMarker(tgMarker: tgMarker) _ = m.setPointEased(point, seconds: 4, easeType: .linear) XCTAssertEqual(tgMarker.coordinates.latitude, 40.0) XCTAssertEqual(tgMarker.coordinates.longitude, 70.0) XCTAssertEqual(tgMarker.seconds, 4) XCTAssertEqual(tgMarker.ease, .linear) } +} + +class PolylineMarkerTests: XCTestCase { + + let marker = PolylineMarker() + + func testPolyline() { + let polyline = TGGeoPolyline() + marker.polyline = polyline + XCTAssertEqual(marker.polyline, polyline) + XCTAssertEqual(marker.tgMarker.polyline, polyline) + XCTAssertTrue(marker.tgMarker.stylingString.contains("style: 'lines'")) + XCTAssertFalse(marker.tgMarker.stylingString.contains("size:")) + } + + func testVisible() { + XCTAssertTrue(marker.visible) + XCTAssertTrue(marker.tgMarker.visible) + marker.visible = false + XCTAssertFalse(marker.visible) + XCTAssertFalse(marker.tgMarker.visible) + } + + func testDrawOrder() { + XCTAssertEqual(marker.drawOrder, 0) + XCTAssertEqual(marker.tgMarker.drawOrder, 0) + marker.drawOrder = 8 + XCTAssertEqual(marker.drawOrder, 8) + XCTAssertEqual(marker.tgMarker.drawOrder, 8) + } + + func testInteractive() { + XCTAssertTrue(marker.interactive) + marker.interactive = false + XCTAssertFalse(marker.interactive) + XCTAssertTrue(marker.tgMarker.stylingString.contains("interactive: false")) + } + + func testOrder() { + XCTAssertEqual(marker.order, 1000) + marker.order = 8 + XCTAssertEqual(marker.order, 8) + XCTAssertTrue(marker.tgMarker.stylingString.contains("order: 8")) + } + + func testStrokeWidth() { + XCTAssertEqual(marker.strokeWidth, 10) + marker.strokeWidth = 8 + XCTAssertEqual(marker.strokeWidth, 8) + XCTAssertTrue(marker.tgMarker.stylingString.contains("width: 8")) + } +} + +class PolygonMarkerTests: XCTestCase { + + let marker = PolygonMarker() + + func testPolygon() { + let polygon = TGGeoPolygon() + marker.polygon = polygon + XCTAssertEqual(marker.polygon, polygon) + XCTAssertEqual(marker.tgMarker.polygon, polygon) + XCTAssertTrue(marker.tgMarker.stylingString.contains("style: 'polygons'")) + XCTAssertFalse(marker.tgMarker.stylingString.contains("size:")) + } + + func testVisible() { + XCTAssertTrue(marker.visible) + XCTAssertTrue(marker.tgMarker.visible) + marker.visible = false + XCTAssertFalse(marker.visible) + XCTAssertFalse(marker.tgMarker.visible) + } + + func testDrawOrder() { + XCTAssertEqual(marker.drawOrder, 0) + XCTAssertEqual(marker.tgMarker.drawOrder, 0) + marker.drawOrder = 8 + XCTAssertEqual(marker.drawOrder, 8) + XCTAssertEqual(marker.tgMarker.drawOrder, 8) + } + + func testInteractive() { + XCTAssertTrue(marker.interactive) + marker.interactive = false + XCTAssertFalse(marker.interactive) + XCTAssertTrue(marker.tgMarker.stylingString.contains("interactive: false")) + } + + func testOrder() { + XCTAssertEqual(marker.order, 1000) + marker.order = 8 + XCTAssertEqual(marker.order, 8) + XCTAssertTrue(marker.tgMarker.stylingString.contains("order: 8")) + } +} + +class SystemPointMarkerTests: XCTestCase { + + let marker = SystemPointMarker(markerType: .currentLocation) func testTypeCurrentLocation() { - let m = Marker.initWithMarkerType(.currentLocation) - XCTAssertEqual(m.tgMarker.stylingPath, "layers.mz_current_location_gem.draw.ux-location-gem-overlay") + XCTAssertEqual(marker.tgMarker.stylingPath, "layers.mz_current_location_gem.draw.ux-location-gem-overlay") + XCTAssertTrue(marker.tgMarker.stylingString.isEmpty) + } + + func testTypeRouteLocation() { + let m = SystemPointMarker.initWithMarkerType(.routeLocation) + XCTAssertEqual(m.tgMarker.stylingPath, "layers.mz_route_location.draw.ux-location-gem-overlay") XCTAssertTrue(m.tgMarker.stylingString.isEmpty) } - func testTypeSearchPin() { - let m = Marker.initWithMarkerType(.searchPin) - XCTAssertEqual(m.tgMarker.stylingPath, "layers.mz_search_result.draw.ux-icons-overlay") + func testTypeDroppedPin() { + let m = SystemPointMarker.initWithMarkerType(.droppedPin) + XCTAssertEqual(m.tgMarker.stylingPath, "layers.mz_dropped_pin.draw.ux-icons-overlay") XCTAssertTrue(m.tgMarker.stylingString.isEmpty) } + + func testVisible() { + XCTAssertTrue(marker.visible) + XCTAssertTrue(marker.tgMarker.visible) + marker.visible = false + XCTAssertFalse(marker.visible) + XCTAssertFalse(marker.tgMarker.visible) + } - func testTypeRouteLine() { - let m = Marker.initWithMarkerType(.routeLine) - XCTAssertEqual(m.tgMarker.stylingPath, "layers.mz_route_line.draw.ux-route-line-overlay") + func testDrawOrder() { + XCTAssertEqual(marker.drawOrder, 0) + XCTAssertEqual(marker.tgMarker.drawOrder, 0) + marker.drawOrder = 8 + XCTAssertEqual(marker.drawOrder, 8) + XCTAssertEqual(marker.tgMarker.drawOrder, 8) + } + + func testPoint() { + let point = TGGeoPoint(longitude: 70.0, latitude: 40.0) + marker.point = point + XCTAssertEqual(marker.point.latitude, point.latitude) + XCTAssertEqual(marker.point.longitude, point.longitude) + XCTAssertEqual(marker.tgMarker.point.latitude, point.latitude) + XCTAssertEqual(marker.tgMarker.point.longitude, point.longitude) + } + + func testSetPointEased() { + let point = TGGeoPoint(longitude: 70.0, latitude: 40.0) + let tgMarker = TestTGMarker() + let m = PointMarker(tgMarker: tgMarker) + _ = m.setPointEased(point, seconds: 4, easeType: .linear) + XCTAssertEqual(tgMarker.coordinates.latitude, 40.0) + XCTAssertEqual(tgMarker.coordinates.longitude, 70.0) + XCTAssertEqual(tgMarker.seconds, 4) + XCTAssertEqual(tgMarker.ease, .linear) + } + +} + +class SelectableSystemPointMarkerTests: XCTestCase { + + let marker = SelectableSystemPointMarker(markerType: .routeStart) + + func testTypeRouteStart() { + XCTAssertEqual(marker.tgMarker.stylingPath, "layers.mz_route_start.draw.ux-icons-overlay") + XCTAssertTrue(marker.tgMarker.stylingString.isEmpty) + } + + func testTypeRouteDestination() { + let m = SelectableSystemPointMarker.initWithMarkerType(.routeDestination) + XCTAssertEqual(m.tgMarker.stylingPath, "layers.mz_route_destination.draw.ux-icons-overlay") XCTAssertTrue(m.tgMarker.stylingString.isEmpty) } - func testTypeCurrentLocationInactive() { - let m = Marker.initWithMarkerType(.currentLocation) - m.active = false - // there is no inactive draw rule for curr location so its same as active state - XCTAssertEqual(m.tgMarker.stylingPath, "layers.mz_current_location_gem.draw.ux-location-gem-overlay") + func testTypeSearchPin() { + let m = SelectableSystemPointMarker.initWithMarkerType(.searchPin) + XCTAssertEqual(m.tgMarker.stylingPath, "layers.mz_search_result.draw.ux-icons-overlay") XCTAssertTrue(m.tgMarker.stylingString.isEmpty) } + func testTypeRouteStartInactive() { + marker.active = false + // there is no inactive draw rule for route start so its same as active state + XCTAssertEqual(marker.tgMarker.stylingPath, "layers.mz_route_start.draw.ux-icons-overlay") + XCTAssertTrue(marker.tgMarker.stylingString.isEmpty) + } + + func testTypeRouteDestinationInactive() { + let m = SelectableSystemPointMarker.initWithMarkerType(.routeDestination) + marker.active = false + // there is no inactive draw rule for route start so its same as active state + XCTAssertEqual(m.tgMarker.stylingPath, "layers.mz_route_destination.draw.ux-icons-overlay") + XCTAssertTrue(m.tgMarker.stylingString.isEmpty) + } func testTypeSearchPinInactive() { - let m = Marker.initWithMarkerType(.searchPin) + let m = SelectableSystemPointMarker.initWithMarkerType(.searchPin) m.active = false XCTAssertEqual(m.tgMarker.stylingPath, "layers.mz_search_result.inactive.draw.ux-icons-overlay") XCTAssertTrue(m.tgMarker.stylingString.isEmpty) } - func testTypeRouteLineInactive() { - let m = Marker.initWithMarkerType(.routeLine) - m.active = false - // there is no inactive draw rule for route line so its same as active state - XCTAssertEqual(m.tgMarker.stylingPath, "layers.mz_route_line.draw.ux-route-line-overlay") - XCTAssertTrue(m.tgMarker.stylingString.isEmpty) + func testVisible() { + XCTAssertTrue(marker.visible) + XCTAssertTrue(marker.tgMarker.visible) + marker.visible = false + XCTAssertFalse(marker.visible) + XCTAssertFalse(marker.tgMarker.visible) + } + + func testDrawOrder() { + XCTAssertEqual(marker.drawOrder, 0) + XCTAssertEqual(marker.tgMarker.drawOrder, 0) + marker.drawOrder = 8 + XCTAssertEqual(marker.drawOrder, 8) + XCTAssertEqual(marker.tgMarker.drawOrder, 8) + } + + func testPoint() { + let point = TGGeoPoint(longitude: 70.0, latitude: 40.0) + marker.point = point + XCTAssertEqual(marker.point.latitude, point.latitude) + XCTAssertEqual(marker.point.longitude, point.longitude) + XCTAssertEqual(marker.tgMarker.point.latitude, point.latitude) + XCTAssertEqual(marker.tgMarker.point.longitude, point.longitude) + } + + func testSetPointEased() { + let point = TGGeoPoint(longitude: 70.0, latitude: 40.0) + let tgMarker = TestTGMarker() + let m = PointMarker(tgMarker: tgMarker) + _ = m.setPointEased(point, seconds: 4, easeType: .linear) + XCTAssertEqual(tgMarker.coordinates.latitude, 40.0) + XCTAssertEqual(tgMarker.coordinates.longitude, 70.0) + XCTAssertEqual(tgMarker.seconds, 4) + XCTAssertEqual(tgMarker.ease, .linear) + } + +} + +class SystemPolylineMarkerTests: XCTestCase { + + let marker = SystemPolylineMarker() + + func testTypeRouteLine() { + XCTAssertEqual(marker.tgMarker.stylingPath, "layers.mz_route_line.draw.ux-route-line-overlay") + XCTAssertTrue(marker.tgMarker.stylingString.isEmpty) + } + + func testVisible() { + XCTAssertTrue(marker.visible) + XCTAssertTrue(marker.tgMarker.visible) + marker.visible = false + XCTAssertFalse(marker.visible) + XCTAssertFalse(marker.tgMarker.visible) } + + func testDrawOrder() { + XCTAssertEqual(marker.drawOrder, 0) + XCTAssertEqual(marker.tgMarker.drawOrder, 0) + marker.drawOrder = 8 + XCTAssertEqual(marker.drawOrder, 8) + XCTAssertEqual(marker.tgMarker.drawOrder, 8) + } + } class TestTGMarker : TGMarker { diff --git a/src/MapViewController.swift b/src/MapViewController.swift index fc50aca..689c13e 100644 --- a/src/MapViewController.swift +++ b/src/MapViewController.swift @@ -215,7 +215,7 @@ open class MapViewController: UIViewController, LocationManagerDelegate { let application : ApplicationProtocol open var tgViewController: TGMapViewController = TGMapViewController() - var currentLocationGem: GenericMarker? + var currentLocationGem: GenericSystemPointMarker? var lastSetPoint: TGGeoPoint? var shouldShowCurrentLocation = false var currentRouteMarker: GenericMarker? @@ -704,7 +704,7 @@ open class MapViewController: UIViewController, LocationManagerDelegate { guard let marker = currentLocationGem else { if !shouldShowCurrentLocation { return false } //TODO: handle error adding to map? - let marker = Marker.initWithMarkerType(.currentLocation) + let marker = SystemPointMarker.initWithMarkerType(.currentLocation) addMarker(marker) currentLocationGem = marker; locationManager.requestWhenInUseAuthorization() @@ -738,7 +738,7 @@ open class MapViewController: UIViewController, LocationManagerDelegate { open func add(_ annotations: [PeliasMapkitAnnotation]) throws { for annotation in annotations { //TODO: handle error adding to map? - let marker = Marker.initWithMarkerType(.searchPin) + let marker = SelectableSystemPointMarker.initWithMarkerType(.searchPin) addMarker(marker) // if newMarker == nil { // //TODO: Once TG integrates better error codes, we need to integrate that here. @@ -812,7 +812,7 @@ open class MapViewController: UIViewController, LocationManagerDelegate { print("Next Point: \(point)") polyLine.add(TGGeoPoint(coordinate: point!)) } - let marker = Marker.initWithMarkerType(.routeLine) + let marker = SystemPolylineMarker.init() addMarker(marker) marker.polyline = polyLine currentRouteMarker = marker @@ -867,7 +867,7 @@ open class MapViewController: UIViewController, LocationManagerDelegate { let oldAnnotations = self.currentAnnotations self.currentAnnotations = Dictionary() for (annotation, marker) in oldAnnotations { - let newMarker = Marker.init() + let newMarker = PointMarker.init() addMarker(newMarker) //TODO: also set polyline, polygon etc newMarker.point = marker.point diff --git a/src/Marker.swift b/src/Marker.swift index 80cb768..6708ff6 100644 --- a/src/Marker.swift +++ b/src/Marker.swift @@ -9,81 +9,106 @@ import Foundation import TangramMap -/// Enum for common marker types supported by all the house styles. -@objc public enum MarkerType : Int { - case currentLocation, searchPin, routeLine -} - /// Generic marker protocol definition. @objc(MZGenericMarker) public protocol GenericMarker { /// The underlying Tangram marker object. Use this only for advanced cases where features of the Marker class aren't supported. var tgMarker: TGMarker { get } - /// The coordinates that the marker should be placed at on the map. Note that point, polyline, and polygon are mutually exclusive. Setting one will overwrite the other values - var point: TGGeoPoint { get set } - /// The polyline that should be displayed on the map. Note that point, polyline, and polygon are mutually exclusive. Setting one will overwrite the other values - var polyline: TGGeoPolyline? { get set } - /// The polygon that should be displayed on the map. Note that point, polyline, and polygon are mutually exclusive. Setting one will overwrite the other values - var polygon: TGGeoPolygon? { get set } - /// The image that should be displayed on the marker. This cannot be used with polylines or polygons, only points. - var icon: UIImage? { get set } /// Toggles the marker visibility. var visible: Bool { get set } /// The marker draw order relative to other markers. Note that higher values are drawn above lower ones. var drawOrder: Int { get set } - /// Sets the size of a point marker. Does nothing when a polyline or polygon is set. - var size: CGSize { get set } - /// Sets the marker background color. Default value is white. - var backgroundColor: UIColor { get set } +} + +/// Generic geometric marker protocol definition. +@objc(MZGenericGeometricMarker) +public protocol GenericGeometricMarker: GenericMarker { /// If the marker is interactive, it is able to be selected and delegates can receive callbacks for these events. Default value is true. var interactive: Bool { get set } - /// Should only be used when a marker is initialized with a MarkerType. Updates the visual properties to indicate active status (ie. updates search pin to be gray when inactive). - var active: Bool { get set } + /// Sets the marker background color. Default value is white. + var backgroundColor: UIColor { get set } + /// Default initializer. + init() +} + +/// Generic point marker protocol definition. +@objc(MZGenericPointMarker) +public protocol GenericPointMarker: GenericMarker { + /// The coordinates that the marker should be placed at on the map. + var point: TGGeoPoint { get set } + /** Animates the marker from its current coordinates to the ones given. - + - parameter coordinates: Coordinates to animate the marker to - parameter seconds: Duration in seconds of the animation. - parameter easeType: Easing to use for animation. - */ + */ func setPointEased(_ coordinates: TGGeoPoint, seconds: Float, easeType ease: TGEaseType) -> Bool - /// Returns a marker whose visual properties have been defined by a house style. Do not try to update the background color, size or other visual aspects of this marker. - static func initWithMarkerType(_ markerType: MarkerType) -> GenericMarker - /// Default initializer. - init() + +} + +@objc(MZGenericPointIconMarker) +public protocol GenericPointIconMarker: GenericGeometricMarker, GenericPointMarker { + /// The image that should be displayed on the marker. + var icon: UIImage? { get set } + /// Sets the size of the marker. + var size: CGSize { get set } /** Initializes a marker with a given size. - + - parameter size: The size the marker should be. */ init(size s: CGSize) } -/** - Base implementation for the GenericMarker protocol - */ -public class Marker : NSObject, GenericMarker { +/// Generic polyline marker protocol definition. +@objc(MZGenericPolylineMarker) +public protocol GenericPolylineMarker: GenericGeometricMarker { + /// The polyline that should be displayed on the map. + var polyline: TGGeoPolyline? { get set } + /// The width of the stroke to draw the polyline. + var strokeWidth: Int { get set } +/// The drawing order of the polyline relative to other polylines and polygons. Higher values will be drawn above lower values. Default is 1000. + var order: Int { get set } +} - private static let kPointStyle = "points" - private static let kLineStyle = "lines" - private static let kPolygonStyle = "polygons" - private static let kDefaultBackgroundColor = UIColor.white - private static let kDefaultInteractive = true - private static let kDefaultSize = CGSize.zero - private static let kDefaultActive = true +/// Generic polygon marker protocol definition. +@objc(MZGenericPolygonMarker) +public protocol GenericPolygonMarker: GenericGeometricMarker { + /// The polygon that should be displayed on the map. + var polygon: TGGeoPolygon? { get set } +/// The drawing order of the polygon relative to other polylines and polygons. Higher values will be drawn above lower values. Default is 1000. + var order: Int { get set } +} - var styleType = Marker.kPointStyle - var markerType: MarkerType? +/// Generic system point marker protocol definition. +@objc(MZGenericSystemPointMarker) +public protocol GenericSystemPointMarker: GenericPointMarker { + /// Returns a marker whose visual properties have been defined by a house style. + static func initWithMarkerType(_ markerType: PointMarkerType) -> GenericSystemPointMarker +} - // all marker types have an associated styling path - private let typeToStylingPath = [MarkerType.currentLocation : "layers.mz_current_location_gem.draw.ux-location-gem-overlay", - MarkerType.searchPin : "layers.mz_search_result.draw.ux-icons-overlay", - MarkerType.routeLine : "layers.mz_route_line.draw.ux-route-line-overlay"] - //currently only search results have an inactive state - private let typeToInactiveStylingPath = [MarkerType.searchPin : "layers.mz_search_result.inactive.draw.ux-icons-overlay"] +/// Generic selectable system point marker protocol definition. +@objc(MZGenericSelectableSystemPointMarker) +public protocol GenericSelectableSystemPointMarker: GenericPointMarker { + /// Updates the visual properties to indicate active status (ie. updates search pin to be gray when inactive). + var active: Bool { get set } + /// Returns a marker whose visual properties have been defined by a house style. + static func initWithMarkerType(_ markerType: SelectablePointMarkerType) -> GenericSelectableSystemPointMarker +} + +/// Generic system geometric marker protocol definition. +@objc(MZGenericSystemPolylineMarker) +public protocol GenericSystemPolylineMarker: GenericMarker { + /// The polyline that should be displayed on the map. + var polyline: TGGeoPolyline? { get set } +} + +/// Base class for generic markers. Do not instantiate this class directly, use one of the more meaningful subclasses. +public class Marker : NSObject, GenericMarker { private let internalTgMarker: TGMarker - private var userUpdatedSize = false /// The underlying Tangram marker object. Use this only for advanced cases where features of the Marker class aren't supported. public var tgMarker: TGMarker { @@ -92,58 +117,6 @@ public class Marker : NSObject, GenericMarker { } } - /// The coordinates that the marker should be placed at on the map. Note that point, polyline, and polygon are mutually exclusive. Setting one will overwrite the other values - public var point: TGGeoPoint { - set { - tgMarker.point = newValue - styleType = Marker.kPointStyle - updateStyleString() - } - get { - return tgMarker.point - } - } - - /// The polyline that should be displayed on the map. Note that point, polyline, and polygon are mutually exclusive. Setting one will overwrite the other values - public var polyline: TGGeoPolyline? { - set { - guard let l = newValue else { return } - tgMarker.polyline = l - styleType = Marker.kLineStyle - updateStyleString() - } - get { - return tgMarker.polyline - } - } - - /// The polygon that should be displayed on the map. Note that point, polyline, and polygon are mutually exclusive. Setting one will overwrite the other values - public var polygon: TGGeoPolygon? { - set { - guard let p = newValue else { return } - tgMarker.polygon = p - styleType = Marker.kPolygonStyle - updateStyleString() - } - get { - return tgMarker.polygon - } - } - - /// The image that should be displayed on the marker. Updates the size of the marker to be the intrinsic size of the image. This cannot be used with polylines or polygons, only points. - public var icon: UIImage? { - set { - guard let i = newValue else { return } - tgMarker.icon = i - size = i.size - styleType = Marker.kPointStyle - updateStyleString() - } - get { - return tgMarker.icon - } - } - /// Toggles the marker visibility. public var visible: Bool { set { @@ -164,14 +137,22 @@ public class Marker : NSObject, GenericMarker { } } - /// Sets the size of a point marker. Does nothing when a polyline or polygon is set. - public var size: CGSize { - didSet { - userUpdatedSize = true - updateStyleString() - } + public override init() { + internalTgMarker = TGMarker.init() + } + + init(tgMarker tgM: TGMarker) { + internalTgMarker = tgM } +} + +/// Base class for geometric markers. Do not instantiate this class directly, use one of the more meaningful subclasses. +public class GeometricMarker : Marker, GenericGeometricMarker { + + private static let kDefaultBackgroundColor = UIColor.white + private static let kDefaultInteractive = true + /// Sets the marker background color. Default value is white. public var backgroundColor: UIColor { didSet { @@ -186,10 +167,62 @@ public class Marker : NSObject, GenericMarker { } } - /// Should only be used when a marker is initialized with a MarkerType. Updates the visual properties to indicate active status (ie. updates search pin to be gray when inactive). - public var active: Bool { + public required override init() { + backgroundColor = GeometricMarker.kDefaultBackgroundColor + interactive = GeometricMarker.kDefaultInteractive + super.init() + } + + override init(tgMarker tgM: TGMarker) { + backgroundColor = GeometricMarker.kDefaultBackgroundColor + interactive = GeometricMarker.kDefaultInteractive + super.init(tgMarker: tgM) + } + + func updateStyleString() { + // override in subclass + } +} + +/** + Base class for point markers. + */ +@objc(MZPointMarker) +public class PointMarker : GeometricMarker, GenericPointIconMarker { + + private static let kPointStyle = "points" + private static let kDefaultSize = CGSize.zero + + private var userUpdatedSize = false + + /// The coordinates that the marker should be placed at on the map. + public var point: TGGeoPoint { + set { + tgMarker.point = newValue + } + get { + return tgMarker.point + } + } + + /// The image that should be displayed on the marker. Updates the size of the marker to be the intrinsic size of the image. + public var icon: UIImage? { + set { + guard let i = newValue else { return } + tgMarker.icon = i + size = i.size + updateStyleString() + } + get { + return tgMarker.icon + } + } + + /// Sets the size of a point marker. Does nothing when a polyline or polygon is set. + public var size: CGSize { didSet { - updateStylePath() + userUpdatedSize = true + updateStyleString() } } @@ -204,19 +237,10 @@ public class Marker : NSObject, GenericMarker { return tgMarker.setPointEased(coordinates, seconds: seconds, easeType: ease) } - /// Returns a marker whose visual properties have been defined by a house style. Do not try to update the background color, size or other visual aspects of this marker. - public static func initWithMarkerType(_ markerType: MarkerType) -> GenericMarker { - let marker = Marker(markerType: markerType) - return marker - } - /// Default initializer. - public required override init() { - internalTgMarker = TGMarker.init() - size = Marker.kDefaultSize - backgroundColor = Marker.kDefaultBackgroundColor - interactive = Marker.kDefaultInteractive - active = Marker.kDefaultActive + public required init() { + size = PointMarker.kDefaultSize + super.init() } //TODO: add back when https://github.com/tangrams/tangram-es/issues/1394 is fixed @@ -235,59 +259,224 @@ public class Marker : NSObject, GenericMarker { - parameter size: The size the marker should be. */ public required init(size s: CGSize) { - internalTgMarker = TGMarker.init() size = s - backgroundColor = Marker.kDefaultBackgroundColor - interactive = Marker.kDefaultInteractive - active = Marker.kDefaultActive super.init() defer { size = s } } - init(tgMarker tgM: TGMarker) { - internalTgMarker = tgM - size = Marker.kDefaultSize - backgroundColor = Marker.kDefaultBackgroundColor - interactive = Marker.kDefaultInteractive - active = Marker.kDefaultActive + override init(tgMarker tgM: TGMarker) { + size = PointMarker.kDefaultSize + super.init(tgMarker: tgM) + } + + // MARK : private + override func updateStyleString() { + tgMarker.stylingString = generateStyleStringWithSize() + } + + private func generateBasicStyleString() -> String { + return "{ style: '\(PointMarker.kPointStyle)', color: '\(backgroundColor.hexValue())', collide: false, interactive: \(interactive) }" + } + + private func generateStyleStringWithSize() -> String { + if !userUpdatedSize { return generateBasicStyleString() } + return "{ style: '\(PointMarker.kPointStyle)', color: '\(backgroundColor.hexValue())', size: [\(size.width)px, \(size.height)px], collide: false, interactive: \(interactive) }" + } +} + +/// Base class for polyline markers. +@objc(MZPolylineMarker) +public class PolylineMarker : GeometricMarker, GenericPolylineMarker { + + private static let kLineStyle = "lines" + private static let kDefaultPolylineWidth = 10 + private static let kDefaultOrder = 1000 + + /// The polyline that should be displayed on the map. + public var polyline: TGGeoPolyline? { + set { + guard let l = newValue else { return } + tgMarker.polyline = l + } + get { + return tgMarker.polyline + } + } + + /// The width of the stroke to draw the polyline. + public var strokeWidth: Int { + didSet { + updateStyleString() + } + } + + /// The drawing order of the polyline relative to other polylines and polygons. Higher values will be drawn above lower values. Default is 1000. + public var order: Int { + didSet { + updateStyleString() + } + } + + // Default initializer. + public required init() { + strokeWidth = PolylineMarker.kDefaultPolylineWidth + order = PolylineMarker.kDefaultOrder + super.init() + updateStyleString() + } + + override func updateStyleString() { + tgMarker.stylingString = "{ style: '\(PolylineMarker.kLineStyle)', color: '\(backgroundColor.hexValue())', collide: false, interactive: \(interactive), width: \(strokeWidth)px, order: \(order) }" + } +} + +/// Base class for polygon markers. +@objc(MZPolygonMarker) +public class PolygonMarker : GeometricMarker, GenericPolygonMarker { + + private static let kPolygonStyle = "polygons" + private static let kDefaultOrder = 1000 + + /// The polygon that should be displayed on the map. + public var polygon: TGGeoPolygon? { + set { + guard let p = newValue else { return } + tgMarker.polygon = p + } + get { + return tgMarker.polygon + } + } + + /// The drawing order of the polygon relative to other polygons and polylines. Higher values will be drawn above lower values. Default is 1000. + public var order: Int { + didSet { + updateStyleString() + } + } + + // Default initializer. + public required init() { + order = PolygonMarker.kDefaultOrder + super.init() + updateStyleString() } - convenience init(markerType mt: MarkerType) { - self.init(size: Marker.kDefaultSize) + override func updateStyleString() { + tgMarker.stylingString = "{ style: '\(PolygonMarker.kPolygonStyle)', color: '\(backgroundColor.hexValue())', collide: false, interactive: \(interactive), order: \(order) }" + } +} + +/// Enum for common point marker types supported by all the house styles. +@objc public enum PointMarkerType : Int { + case currentLocation, routeLocation, droppedPin +} + +/// Enum for common selectable point marker types supported by all the house styles. +@objc public enum SelectablePointMarkerType : Int { + case searchPin, routeStart, routeDestination +} + +/// Base class for system point markers. +@objc(MZSystemPointMarker) +public class SystemPointMarker : Marker, GenericSystemPointMarker { + + var markerType: PointMarkerType? + + // all marker types have an associated styling path + private let typeToStylingPath = [PointMarkerType.currentLocation : "layers.mz_current_location_gem.draw.ux-location-gem-overlay", + PointMarkerType.routeLocation : "layers.mz_route_location.draw.ux-location-gem-overlay", + PointMarkerType.droppedPin : "layers.mz_dropped_pin.draw.ux-icons-overlay",] + + /// The coordinates that the marker should be placed at on the map. + public var point: TGGeoPoint { + set { + tgMarker.point = newValue + } + get { + return tgMarker.point + } + } + + /// Returns a marker whose visual properties have been defined by a house style. + public static func initWithMarkerType(_ markerType: PointMarkerType) -> GenericSystemPointMarker { + return SystemPointMarker(markerType: markerType) + } + + /** + Animates the marker from its current coordinates to the ones given. + + - parameter coordinates: Coordinates to animate the marker to + - parameter seconds: Duration in seconds of the animation. + - parameter easeType: Easing to use for animation. + */ + public func setPointEased(_ coordinates: TGGeoPoint, seconds: Float, easeType ease: TGEaseType) -> Bool { + return tgMarker.setPointEased(coordinates, seconds: seconds, easeType: ease) + } + + init(markerType mt: PointMarkerType) { + super.init() markerType = mt tgMarker.stylingPath = typeToStylingPath[mt]! //there is always a styling string for a given MarkerType so force unwrap } - // MARK : private - private func updateStyleString() { - if !tgMarker.stylingPath.isEmpty { - return +} + +/// Base class for system point markers. +@objc(MZSelectableSystemPointMarker) +public class SelectableSystemPointMarker : Marker, GenericSelectableSystemPointMarker { + + var markerType: SelectablePointMarkerType? + + private let typeToStylingPath = [SelectablePointMarkerType.searchPin : "layers.mz_search_result.draw.ux-icons-overlay", + SelectablePointMarkerType.routeStart : "layers.mz_route_start.draw.ux-icons-overlay", + SelectablePointMarkerType.routeDestination : "layers.mz_route_destination.draw.ux-icons-overlay"] + + //currently only search results have an inactive state + private let typeToInactiveStylingPath = [SelectablePointMarkerType.searchPin : "layers.mz_search_result.inactive.draw.ux-icons-overlay"] + + private static let kDefaultActive = true + + /// Updates the visual properties to indicate active status (ie. updates search pin to be gray when inactive). + public var active: Bool { + didSet { + updateStylePath() } + } - var str: String - switch styleType { - case Marker.kPointStyle: - str = generateStyleStringWithSize() - break; - case Marker.kLineStyle, - Marker.kPolygonStyle: - str = generateBasicStyleString() - break; - default: - str = generateStyleStringWithSize() + /// The coordinates that the marker should be placed at on the map. + public var point: TGGeoPoint { + set { + tgMarker.point = newValue + } + get { + return tgMarker.point } - tgMarker.stylingString = str } - private func generateBasicStyleString() -> String { - return "{ style: '\(styleType)', color: '\(backgroundColor.hexValue())', collide: false, interactive: \(interactive) }" + /// Returns a marker whose visual properties have been defined by a house style. + public static func initWithMarkerType(_ markerType: SelectablePointMarkerType) -> GenericSelectableSystemPointMarker { + return SelectableSystemPointMarker(markerType: markerType) } - private func generateStyleStringWithSize() -> String { - if !userUpdatedSize { return generateBasicStyleString() } - return "{ style: '\(styleType)', color: '\(backgroundColor.hexValue())', size: [\(size.width)px, \(size.height)px], collide: false, interactive: \(interactive) }" + /** + Animates the marker from its current coordinates to the ones given. + + - parameter coordinates: Coordinates to animate the marker to + - parameter seconds: Duration in seconds of the animation. + - parameter easeType: Easing to use for animation. + */ + public func setPointEased(_ coordinates: TGGeoPoint, seconds: Float, easeType ease: TGEaseType) -> Bool { + return tgMarker.setPointEased(coordinates, seconds: seconds, easeType: ease) + } + + init(markerType mt: SelectablePointMarkerType) { + active = SelectableSystemPointMarker.kDefaultActive + super.init() + markerType = mt + tgMarker.stylingPath = typeToStylingPath[mt]! //there is always a styling string for a given MarkerType so force unwrap } private func updateStylePath() { @@ -304,4 +493,29 @@ public class Marker : NSObject, GenericMarker { tgMarker.stylingPath = currPath } } + } + +/// Base class for system polyline markers. +@objc(MZSystemPolylineMarker) +public class SystemPolylineMarker : Marker, GenericSystemPolylineMarker { + + private static let kSystemPolylineStylingPath = "layers.mz_route_line.draw.ux-route-line-overlay" + + /// The polyline that should be displayed on the map. + public var polyline: TGGeoPolyline? { + set { + guard let l = newValue else { return } + tgMarker.polyline = l + } + get { + return tgMarker.polyline + } + } + + public override init() { + super.init() + tgMarker.stylingPath = SystemPolylineMarker.kSystemPolylineStylingPath + } +} +