From 931e949de6a57e0375524ca3ed14638b45c30798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Sun, 15 May 2016 16:40:22 -0700 Subject: [PATCH 1/3] Added support for bridging to Objective-C MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All public structs are now classes that inherit from NSObject and have prefixed Objective-C class names so they can bridge to Objective-C. Changed let properties to var properties because mutability of classes is determined on a property-by-property basis. Enums have numeric types so they can bridge too; instead of backing enum values with strings, use switch statements. Added multiple convenience initializers to Snapshot because optional parameters in the designated initializer aren’t getting elided when bridging to Objective-C. Added multiple convenience initializers to Marker that take non-variant types, so that Marker.Label can be taken private. Removed colors from overlay initializers because inner type aliases don’t bridge and duplicating initializers would be unmaintainable. Also removed other parameters from Path’s initializer since they make less sense without color parameters. Added members to Path that traffic in raw pointers instead of arrays to enable bridging to Objective-C with C arrays. GeoJSON’s initializer is now failable instead of throwable, because an error type mismatch between the standard thrown error type and the error parameter of NSJSONSerialization.dataWithJSONObject(_:options:). Added Objective-C examples to the readme. --- MapboxStatic.xcodeproj/project.pbxproj | 170 +++++++++++++-- MapboxStatic/MapboxStatic.h | 1 + MapboxStatic/Overlay.swift | 240 +++++++++++++++------- MapboxStatic/Snapshot.swift | 107 +++++++--- MapboxStaticTests/MapboxStaticTests.swift | 72 ++++--- OS X.playground/Contents.swift | 14 +- OS X.playground/contents.xcplayground | 2 +- README.md | 114 +++++++++- iOS.playground/Contents.swift | 14 +- iOS.playground/contents.xcplayground | 2 +- iOS.playground/timeline.xctimeline | 35 +++- 11 files changed, 596 insertions(+), 175 deletions(-) diff --git a/MapboxStatic.xcodeproj/project.pbxproj b/MapboxStatic.xcodeproj/project.pbxproj index a00d8b2..04253e6 100644 --- a/MapboxStatic.xcodeproj/project.pbxproj +++ b/MapboxStatic.xcodeproj/project.pbxproj @@ -33,6 +33,11 @@ DAF15A441CE8FBBC0040E86C /* Snapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAD19871BD9B3970057AC9F /* Snapshot.swift */; }; DAF15A451CE8FBBC0040E86C /* Overlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA20FBAB1CE4026B00B07762 /* Overlay.swift */; }; DAF15A461CE8FBBC0040E86C /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA20FBA91CE401E800B07762 /* Color.swift */; }; + DAF15A4F1CE90A6C0040E86C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DAF15A4E1CE90A6C0040E86C /* main.m */; }; + DAF15A521CE90A6C0040E86C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DAF15A511CE90A6C0040E86C /* AppDelegate.m */; }; + DAF15A551CE90A6C0040E86C /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAF15A541CE90A6C0040E86C /* ViewController.m */; }; + DAF15A631CE90CE70040E86C /* MapboxStatic.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA20FB9B1CE3DEBB00B07762 /* MapboxStatic.framework */; }; + DAF15A641CE90CE70040E86C /* MapboxStatic.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DA20FB9B1CE3DEBB00B07762 /* MapboxStatic.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DD0C88181BE1A9E100606E9F /* polyline.geojson in Resources */ = {isa = PBXBuildFile; fileRef = DD0C88161BE1A9D400606E9F /* polyline.geojson */; }; DD685BA91BDB14C1002E2BB2 /* MapboxStaticTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAD19831BD9B2F80057AC9F /* MapboxStaticTests.swift */; }; DDAD19711BD9B1A50057AC9F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAD196E1BD9B1A50057AC9F /* AppDelegate.swift */; }; @@ -62,6 +67,13 @@ remoteGlobalIDString = DA4B4B911CE8F83100296A52; remoteInfo = MapboxStaticTV; }; + DAF15A651CE90CE70040E86C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DDAD19511BD9B1780057AC9F /* Project object */; + proxyType = 1; + remoteGlobalIDString = DA20FB9A1CE3DEBB00B07762; + remoteInfo = MapboxStatic; + }; DDAD197D1BD9B24F0057AC9F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = DDAD19511BD9B1780057AC9F /* Project object */; @@ -83,6 +95,17 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + DAF15A671CE90CE70040E86C /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + DAF15A641CE90CE70040E86C /* MapboxStatic.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -105,8 +128,14 @@ DA4B4B921CE8F83100296A52 /* MapboxStatic.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MapboxStatic.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DA4B4B9B1CE8F83100296A52 /* MapboxStaticTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MapboxStaticTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; DAF15A3B1CE8FB8D0040E86C /* MapboxStatic.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MapboxStatic.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DAF15A4B1CE90A6C0040E86C /* MapboxStatic (Objective-C).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MapboxStatic (Objective-C).app"; sourceTree = BUILT_PRODUCTS_DIR; }; + DAF15A4E1CE90A6C0040E86C /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + DAF15A501CE90A6C0040E86C /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + DAF15A511CE90A6C0040E86C /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + DAF15A531CE90A6C0040E86C /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + DAF15A541CE90A6C0040E86C /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; DD0C88161BE1A9D400606E9F /* polyline.geojson */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = polyline.geojson; sourceTree = ""; }; - DDAD19591BD9B1780057AC9F /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + DDAD19591BD9B1780057AC9F /* MapboxStatic (Swift).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MapboxStatic (Swift).app"; sourceTree = BUILT_PRODUCTS_DIR; }; DDAD196E1BD9B1A50057AC9F /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; DDAD196F1BD9B1A50057AC9F /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DDAD19701BD9B1A50057AC9F /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -165,6 +194,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DAF15A481CE90A6C0040E86C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DAF15A631CE90CE70040E86C /* MapboxStatic.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; DDAD19561BD9B1780057AC9F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -219,6 +256,27 @@ path = MapboxStatic; sourceTree = ""; }; + DAF15A4C1CE90A6C0040E86C /* Objective-C */ = { + isa = PBXGroup; + children = ( + DAF15A501CE90A6C0040E86C /* AppDelegate.h */, + DAF15A511CE90A6C0040E86C /* AppDelegate.m */, + DAF15A531CE90A6C0040E86C /* ViewController.h */, + DAF15A541CE90A6C0040E86C /* ViewController.m */, + DAF15A4E1CE90A6C0040E86C /* main.m */, + ); + name = "Objective-C"; + sourceTree = ""; + }; + DAF15A621CE90AA60040E86C /* Swift */ = { + isa = PBXGroup; + children = ( + DDAD196E1BD9B1A50057AC9F /* AppDelegate.swift */, + DDAD19701BD9B1A50057AC9F /* ViewController.swift */, + ); + name = Swift; + sourceTree = ""; + }; DD0C88151BE1A9CC00606E9F /* Fixtures */ = { isa = PBXGroup; children = ( @@ -245,7 +303,7 @@ DDAD195A1BD9B1780057AC9F /* Products */ = { isa = PBXGroup; children = ( - DDAD19591BD9B1780057AC9F /* Example.app */, + DDAD19591BD9B1780057AC9F /* MapboxStatic (Swift).app */, DDAD19781BD9B24F0057AC9F /* MapboxStaticTests.xctest */, DA20FB9B1CE3DEBB00B07762 /* MapboxStatic.framework */, DA20FBB91CE4757E00B07762 /* MapboxStatic.framework */, @@ -253,6 +311,7 @@ DA4B4B921CE8F83100296A52 /* MapboxStatic.framework */, DA4B4B9B1CE8F83100296A52 /* MapboxStaticTests.xctest */, DAF15A3B1CE8FB8D0040E86C /* MapboxStatic.framework */, + DAF15A4B1CE90A6C0040E86C /* MapboxStatic (Objective-C).app */, ); name = Products; sourceTree = ""; @@ -260,8 +319,8 @@ DDAD195B1BD9B1780057AC9F /* Example */ = { isa = PBXGroup; children = ( - DDAD196E1BD9B1A50057AC9F /* AppDelegate.swift */, - DDAD19701BD9B1A50057AC9F /* ViewController.swift */, + DAF15A621CE90AA60040E86C /* Swift */, + DAF15A4C1CE90A6C0040E86C /* Objective-C */, DDAD196F1BD9B1A50057AC9F /* Info.plist */, ); path = Example; @@ -429,9 +488,28 @@ productReference = DAF15A3B1CE8FB8D0040E86C /* MapboxStatic.framework */; productType = "com.apple.product-type.framework"; }; - DDAD19581BD9B1780057AC9F /* Example */ = { + DAF15A4A1CE90A6C0040E86C /* Example (Objective-C) */ = { + isa = PBXNativeTarget; + buildConfigurationList = DAF15A5F1CE90A6C0040E86C /* Build configuration list for PBXNativeTarget "Example (Objective-C)" */; + buildPhases = ( + DAF15A471CE90A6C0040E86C /* Sources */, + DAF15A481CE90A6C0040E86C /* Frameworks */, + DAF15A491CE90A6C0040E86C /* Resources */, + DAF15A671CE90CE70040E86C /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + DAF15A661CE90CE70040E86C /* PBXTargetDependency */, + ); + name = "Example (Objective-C)"; + productName = ExampleObjC; + productReference = DAF15A4B1CE90A6C0040E86C /* MapboxStatic (Objective-C).app */; + productType = "com.apple.product-type.application"; + }; + DDAD19581BD9B1780057AC9F /* Example (Swift) */ = { isa = PBXNativeTarget; - buildConfigurationList = DDAD196B1BD9B1780057AC9F /* Build configuration list for PBXNativeTarget "Example" */; + buildConfigurationList = DDAD196B1BD9B1780057AC9F /* Build configuration list for PBXNativeTarget "Example (Swift)" */; buildPhases = ( DDAD19551BD9B1780057AC9F /* Sources */, DDAD19561BD9B1780057AC9F /* Frameworks */, @@ -443,9 +521,9 @@ dependencies = ( DA20FBA11CE3DEBB00B07762 /* PBXTargetDependency */, ); - name = Example; + name = "Example (Swift)"; productName = "Sample App"; - productReference = DDAD19591BD9B1780057AC9F /* Example.app */; + productReference = DDAD19591BD9B1780057AC9F /* MapboxStatic (Swift).app */; productType = "com.apple.product-type.application"; }; DDAD19771BD9B24F0057AC9F /* MapboxStaticTests */ = { @@ -497,6 +575,9 @@ DAF15A3A1CE8FB8D0040E86C = { CreatedOnToolsVersion = 7.3.1; }; + DAF15A4A1CE90A6C0040E86C = { + CreatedOnToolsVersion = 7.3.1; + }; DDAD19581BD9B1780057AC9F = { CreatedOnToolsVersion = 7.1; }; @@ -518,7 +599,8 @@ projectDirPath = ""; projectRoot = ""; targets = ( - DDAD19581BD9B1780057AC9F /* Example */, + DDAD19581BD9B1780057AC9F /* Example (Swift) */, + DAF15A4A1CE90A6C0040E86C /* Example (Objective-C) */, DA20FB9A1CE3DEBB00B07762 /* MapboxStatic */, DDAD19771BD9B24F0057AC9F /* MapboxStaticTests */, DA20FBB81CE4757E00B07762 /* MapboxStaticMac */, @@ -575,6 +657,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DAF15A491CE90A6C0040E86C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; DDAD19571BD9B1780057AC9F /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -787,6 +876,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DAF15A471CE90A6C0040E86C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DAF15A551CE90A6C0040E86C /* ViewController.m in Sources */, + DAF15A521CE90A6C0040E86C /* AppDelegate.m in Sources */, + DAF15A4F1CE90A6C0040E86C /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; DDAD19551BD9B1780057AC9F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -822,9 +921,14 @@ target = DA4B4B911CE8F83100296A52 /* MapboxStaticTV */; targetProxy = DA4B4B9D1CE8F83100296A52 /* PBXContainerItemProxy */; }; + DAF15A661CE90CE70040E86C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DA20FB9A1CE3DEBB00B07762 /* MapboxStatic */; + targetProxy = DAF15A651CE90CE70040E86C /* PBXContainerItemProxy */; + }; DDAD197E1BD9B24F0057AC9F /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = DDAD19581BD9B1780057AC9F /* Example */; + target = DDAD19581BD9B1780057AC9F /* Example (Swift) */; targetProxy = DDAD197D1BD9B24F0057AC9F /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ @@ -1072,6 +1176,32 @@ }; name = Release; }; + DAF15A601CE90A6C0040E86C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; + INFOPLIST_FILE = Example/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.MapboxStaticExample.ObjC; + PRODUCT_NAME = "MapboxStatic (Objective-C)"; + }; + name = Debug; + }; + DAF15A611CE90A6C0040E86C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; + INFOPLIST_FILE = Example/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.MapboxStaticExample.ObjC; + PRODUCT_NAME = "MapboxStatic (Objective-C)"; + }; + name = Release; + }; DDAD19691BD9B1780057AC9F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1165,8 +1295,8 @@ EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; INFOPLIST_FILE = "$(SRCROOT)/Example/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.StaticExample; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.MapboxStaticExample.Swift; + PRODUCT_NAME = "MapboxStatic (Swift)"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -1179,8 +1309,8 @@ EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; INFOPLIST_FILE = "$(SRCROOT)/Example/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.StaticExample; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.MapboxStaticExample.Swift; + PRODUCT_NAME = "MapboxStatic (Swift)"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; @@ -1267,6 +1397,16 @@ DAF15A411CE8FB8D0040E86C /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DAF15A5F1CE90A6C0040E86C /* Build configuration list for PBXNativeTarget "Example (Objective-C)" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DAF15A601CE90A6C0040E86C /* Debug */, + DAF15A611CE90A6C0040E86C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; DDAD19541BD9B1780057AC9F /* Build configuration list for PBXProject "MapboxStatic" */ = { isa = XCConfigurationList; @@ -1277,7 +1417,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - DDAD196B1BD9B1780057AC9F /* Build configuration list for PBXNativeTarget "Example" */ = { + DDAD196B1BD9B1780057AC9F /* Build configuration list for PBXNativeTarget "Example (Swift)" */ = { isa = XCConfigurationList; buildConfigurations = ( DDAD196C1BD9B1780057AC9F /* Debug */, diff --git a/MapboxStatic/MapboxStatic.h b/MapboxStatic/MapboxStatic.h index 34c1cfd..463e807 100644 --- a/MapboxStatic/MapboxStatic.h +++ b/MapboxStatic/MapboxStatic.h @@ -1,4 +1,5 @@ #import +#import //! Project version number for MapboxStatic. FOUNDATION_EXPORT double MapboxStaticVersionNumber; diff --git a/MapboxStatic/Overlay.swift b/MapboxStatic/Overlay.swift index b37d466..45b994d 100644 --- a/MapboxStatic/Overlay.swift +++ b/MapboxStatic/Overlay.swift @@ -1,4 +1,3 @@ -import CoreLocation #if os(OSX) import Cocoa #else @@ -14,13 +13,13 @@ let allowedCharacterSet: NSCharacterSet = { /** A feature that can be drawn atop the map. */ -public protocol Overlay: CustomStringConvertible { - var description: String { get } -} +@objc(MBOverlay) +public protocol Overlay: NSObjectProtocol {} /** A feature centered over a specific geographic coordinate. */ +@objc(MBPoint) public protocol Point: Overlay { /// The geographic coordinate to place the point at. var coordinate: CLLocationCoordinate2D { get } @@ -31,23 +30,18 @@ public protocol Point: Overlay { The Maki icon set is [open source](https://github.com/mapbox/maki/) and [dedicated to the public domain](https://creativecommons.org/publicdomain/zero/1.0/). */ -public struct Marker: Point { - #if os(OSX) - public typealias Color = NSColor - #else - public typealias Color = UIColor - #endif - +@objc(MBMarker) +public class Marker: NSObject, Point { /** The size of a marker. */ - public enum Size: String { + @objc(MBMarkerSize) public enum Size: Int { /// Small. - case Small = "s" + case Small /// Medium. - case Medium = "m" + case Medium /// Large. - case Large = "l" + case Large } /// Something simple that can be placed atop a marker. @@ -75,54 +69,112 @@ public struct Marker: Point { } /// The geographic coordinate to place the marker at. - public let coordinate: CLLocationCoordinate2D + public var coordinate: CLLocationCoordinate2D /** The size of the marker. By default, the marker is small. */ - public let size: Size + public var size: Size /** A label or Maki icon to place atop the pin. By default, the marker has no label. */ - public let label: Label? + public var label: Label? + #if os(OSX) + /** + The color of the pin part of the marker. + + By default, the marker is red. + */ + public var color: NSColor = .redColor() + #else /** The color of the pin part of the marker. By default, the marker is red. */ - public let color: Color + public var color: UIColor = .redColor() + #endif /** - Initializes a marker with the given options. + Initializes a red marker with the given options. - parameter coordinate: The geographic coordinate to place the marker at. - parameter size: The size of the marker. - parameter label: A label or Maki icon to place atop the pin. */ - public init(coordinate: CLLocationCoordinate2D, - size: Size = .Small, - label: Label? = nil, - color: Color = .redColor()) { + private init(coordinate: CLLocationCoordinate2D, + size: Size = .Small, + label: Label? = nil) { self.coordinate = coordinate self.size = size self.label = label - self.color = color } - public var description: String { + /** + Initializes a red marker labeled with an English letter. + + - parameter coordinate: The geographic coordinate to place the marker at. + - parameter size: The size of the marker. + - parameter letter: An English letter from A through Z to place atop the pin. + */ + public convenience init(coordinate: CLLocationCoordinate2D, + size: Size = .Small, + letter: String) { + assert(letter.characters.count == 1, "A marker can only fit one letter.") + self.init(coordinate: coordinate, size: size, label: .Letter(letter.characters.first!)) + } + + /** + Initializes a red marker labeled with a one- or two-digit number. + + - parameter coordinate: The geographic coordinate to place the marker at. + - parameter size: The size of the marker. + - parameter number: A number from 0 through 99 to place atop the pin. + */ + public convenience init(coordinate: CLLocationCoordinate2D, + size: Size = .Small, + number: Int) { + self.init(coordinate: coordinate, size: size, label: .Number(number)) + } + + /** + Initializes a red marker with a Maki icon. + + - parameter coordinate: The geographic coordinate to place the marker at. + - parameter size: The size of the marker. + - parameter iconName: The name of a [Maki](https://www.mapbox.com/maki-icons/) icon to place atop the pin. + */ + public convenience init(coordinate: CLLocationCoordinate2D, + size: Size = .Small, + iconName: String) { + self.init(coordinate: coordinate, size: size, label: .IconName(iconName)) + } + + public override var description: String { + let sizeComponent: String + switch size { + case .Small: + sizeComponent = "s" + case .Medium: + sizeComponent = "m" + case .Large: + sizeComponent = "l" + } + let labelComponent: String if let label = label { labelComponent = "-\(label)" } else { labelComponent = "" } - return "pin-\(size.rawValue)\(labelComponent)+\(color.toHexString())(\(coordinate.longitude),\(coordinate.latitude))" + + return "pin-\(sizeComponent)\(labelComponent)+\(color.toHexString())(\(coordinate.longitude),\(coordinate.latitude))" } } @@ -131,16 +183,17 @@ public struct Marker: Point { The marker image is always centered on the specified location. When creating an asymmetric marker like a pin, make sure that the tip of the pin is at the center of the image. */ -public struct CustomMarker: Overlay { +@objc(MBCustomMarker) +public class CustomMarker: NSObject, Overlay { /// The geographic coordinate to place the marker at. - public let coordinate: CLLocationCoordinate2D + public var coordinate: CLLocationCoordinate2D /** The HTTP or HTTPS URL of the image. The API caches custom marker images according to the `Expires` and `Cache-Control` headers. If you host the image on your own server, make sure that at least one of these headers is set to an proper value to prevent repeated requests for the image. */ - public let URL: NSURL + public var URL: NSURL /** Initializes a marker with the given coordinate and image URL. @@ -153,7 +206,7 @@ public struct CustomMarker: Overlay { self.URL = URL } - public var description: String { + public override var description: String { let escapedURL = URL.absoluteString.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacterSet)! return "url-\(escapedURL)(\(coordinate.longitude),\(coordinate.latitude))" } @@ -164,11 +217,12 @@ public struct CustomMarker: Overlay { GeoJSON features may be styled according to the [simplestyle specification](https://github.com/mapbox/simplestyle-spec). */ -public struct GeoJSON: Overlay { +@objc(MBGeoJSON) +public class GeoJSON: NSObject, Overlay { /// String representation of the GeoJSON object to display. - public let objectString: String + public var objectString: String - public var description: String { + public override var description: String { let escapedObjectString = objectString.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacterSet)! return "geojson(\(escapedObjectString))" } @@ -177,10 +231,13 @@ public struct GeoJSON: Overlay { Initializes a [GeoJSON](https://www.mapbox.com/help/define-geojson/) overlay with the given GeoJSON object. - parameter object: A valid GeoJSON object. - - throws: If the given object is not a valid JSON object. This initializer does not check whether the object is valid GeoJSON, but invalid GeoJSON will cause the request to fail. + - returns: A GeoJSON overlay, or `nil` if the given object is not a valid JSON object. This initializer does not check whether the object is valid GeoJSON, but invalid GeoJSON will cause the request to fail. */ - public init(object: [String: AnyObject]) throws { - let data = try NSJSONSerialization.dataWithJSONObject(object, options: []) + public init?(object: [String: AnyObject]) { + // This should be a throwing initializer rather than a failiable initializer, but inheriting from Objective-C triggers a warning: no calls to throwing functions occur within 'try' expression + guard let data = try? NSJSONSerialization.dataWithJSONObject(object, options: []) else { + return nil + } objectString = String(data: data, encoding: NSUTF8StringEncoding)! } @@ -199,75 +256,118 @@ public struct GeoJSON: Overlay { /** A polyline or polygon placed along a path atop the map. */ -public struct Path: Overlay { - #if os(OSX) - public typealias Color = NSColor - #else - public typealias Color = UIColor - #endif - +@objc(MBPath) +public class Path: NSObject, Overlay { /** An array of geographic coordinates defining the path of the overlay. */ - public let coordinates: [CLLocationCoordinate2D] + public var coordinates: [CLLocationCoordinate2D] /** The stroke width of the overlay, measured in points. By default, the overlay is 1 point wide. */ - public let strokeWidth: Int + public var strokeWidth: Int = 1 + #if os(OSX) /** The stroke color of the overlay. By default, the overlay is stroked with Davy’s gray (33% white). */ - public let strokeColor: Color + public var strokeColor = NSColor(hexString: "555") /** - The stroke opacity of the overlay, expressed as a percentage such that 0.0 is completely transparent and 1.0 is completely opaque. + The fill color of the overlay. - By default, the overlay’s stroke is completely opaque. + By default, the overlay is filled with Davy’s gray (33% white). */ - public let strokeOpacity: Double + public var fillColor = NSColor(hexString: "555") + #else + /** + The stroke color of the overlay. + + By default, the overlay is stroked with Davy’s gray (33% white). + */ + public var strokeColor = UIColor(hexString: "555") /** The fill color of the overlay. By default, the overlay is filled with Davy’s gray (33% white). */ - public let fillColor: Color + public var fillColor = UIColor(hexString: "555") + #endif + + /** + The stroke opacity of the overlay, expressed as a percentage such that 0.0 is completely transparent and 1.0 is completely opaque. + + By default, the overlay’s stroke is completely opaque. + */ + public var strokeOpacity: Double = 1 /** The fill opacity of the overlay, expressed as a percentage such that 0.0 is completely transparent and 1.0 is completely opaque. By default, the overlay’s fill is completely transparent. */ - public let fillOpacity: Double + public var fillOpacity: Double = 0 /** - Initializes a polyline or polygon overlay with the given options. + Initializes a polyline overlay with the given vertices. + + The polyline is 1 point wide and stroked with Davy’s gray (33% white). + + To turn the overlay into a polygon, close the path by ensuring that the first and last coordinates are the same. To fill the polygon, set the `fillOpacity` property to a value greater than 0.0. - parameter coordinates: An array of geographic coordinates defining the path of the overlay. - - parameter strokeWidth: The stroke width of the overlay, measured in points. - - parameter strokeColor: The stroke color of the overlay. - - parameter strokeOpacity: The stroke opacity of the overlay, expressed as a percentage such that 0.0 is completely transparent and 1.0 is completely opaque. - - parameter fillColor: The fill color of the overlay. - - parameter fillOpacity: The fill opacity of the overlay, expressed as a percentage such that 0.0 is completely transparent and 1.0 is completely opaque. */ - public init(coordinates: [CLLocationCoordinate2D], - strokeWidth: Int = 1, - strokeColor: Color = Color(hexString: "555"), - strokeOpacity: Double = 1.0, - fillColor: Color = Color(hexString: "555"), - fillOpacity: Double = 0) { + public init(coordinates: [CLLocationCoordinate2D]) { self.coordinates = coordinates - self.strokeWidth = strokeWidth - self.strokeColor = strokeColor - self.strokeOpacity = strokeOpacity - self.fillColor = fillColor - self.fillOpacity = fillOpacity + } + + /** + Initializes a polyline overlay with the given vertices, stored in a C array. + + The polyline is 1 point wide and stroked with Davy’s gray (33% white). + + To turn the overlay into a polygon, close the path by ensuring that the first and last coordinates are the same. To fill the polygon, set the `fillOpacity` property to a value greater than 0.0. + + - parameter coordinates: An array of geographic coordinates defining the path of the overlay. + + - note: This initializer is intended for Objective-C usage. In Swift code, use the `init(coordinates:)` initializer. + */ + public init(coordinates: UnsafePointer, count: UInt) { + var convertedCoordinates: [CLLocationCoordinate2D] = [] + for i in 0..) { + for i in 0.. 1 ? "@2x" : "").\(format.rawValue)" + let formatComponent: String + switch format { + case .PNG: + formatComponent = "png" + case .PNG32: + formatComponent = "png32" + case .PNG64: + formatComponent = "png64" + case .PNG128: + formatComponent = "png128" + case .PNG256: + formatComponent = "png256" + case .JPEG: + formatComponent = "jpg" + case .JPEG70: + formatComponent = "jpg70" + case .JPEG80: + formatComponent = "jpg80" + case .JPEG90: + formatComponent = "jpg90" + } + + return "/v4/\(tileSetComponent)\(overlaysComponent)/\(position)/\(Int(round(size.width)))x\(Int(round(size.height)))\(scale > 1 ? "@2x" : "").\(formatComponent)" } /** @@ -195,7 +219,8 @@ public struct SnapshotOptions { If you use `Snapshot` to display a [vector tile set](https://www.mapbox.com/help/define-tileset/#vector-tilesets), the snapshot image will depict a wireframe representation of the tile set. To generate a static, styled image of a vector tile set, use the [vector Mapbox Static API](https://www.mapbox.com/api-documentation/?language=Swift#static). */ -public struct Snapshot { +@objc(MBSnapshot) +public class Snapshot: NSObject { #if os(OSX) public typealias Image = NSImage #else @@ -214,32 +239,52 @@ public struct Snapshot { public let options: SnapshotOptions /// The API endpoint to request the image from. - private var apiEndpoint: String = "https://api.mapbox.com" + private var apiEndpoint: String /// The Mapbox access token to associate the request with. private let accessToken: String - /// The Mapbox access token specified in the main application bundle’s Info.plist. - private static let defaultAccessToken = NSBundle.mainBundle().objectForInfoDictionaryKey("MGLMapboxAccessToken") as? String - /** - Initializes a newly created snapshot instance with the given options and an optional access token. + Initializes a newly created snapshot instance with the given options and an optional access token and host. - parameter options: Options that determine the contents and format of the output image. - parameter accessToken: A Mapbox [access token](https://www.mapbox.com/help/define-access-token/). If an access token is not specified when initializing the snapshot object, it should be specified in the `MGLMapboxAccessToken` key in the main application bundle’s Info.plist. - parameter host: An optional hostname to the server API. The classic Mapbox Static API endpoint is used by default. */ - public init(options: SnapshotOptions, accessToken: String = defaultAccessToken ?? "", host: String? = nil) { - assert(!accessToken.isEmpty, "A Mapbox access token is required. Go to . In Info.plist, set the MGLMapboxAccessToken key to your access token, or use the Snapshot(options:accessToken:host:) initializer.") + public init(options: SnapshotOptions, accessToken: String?, host: String?) { + let accessToken = accessToken ?? defaultAccessToken + assert(accessToken != nil && !accessToken!.isEmpty, "A Mapbox access token is required. Go to . In Info.plist, set the MGLMapboxAccessToken key to your access token, or use the Snapshot(options:accessToken:host:) initializer.") self.options = options - self.accessToken = accessToken - if let host = host { - let baseURLComponents = NSURLComponents() - baseURLComponents.scheme = "https" - baseURLComponents.host = host - apiEndpoint = baseURLComponents.string ?? apiEndpoint - } + self.accessToken = accessToken! + + let baseURLComponents = NSURLComponents() + baseURLComponents.scheme = "https" + baseURLComponents.host = host ?? "api.mapbox.com" + self.apiEndpoint = baseURLComponents.string! + } + + /** + Initializes a newly created snapshot instance with the given options and an optional access token. + + The snapshot instance sends requests to the classic Mapbox Static API endpoint. + + - parameter options: Options that determine the contents and format of the output image. + - parameter accessToken: A Mapbox [access token](https://www.mapbox.com/help/define-access-token/). If an access token is not specified when initializing the snapshot object, it should be specified in the `MGLMapboxAccessToken` key in the main application bundle’s Info.plist. + */ + public convenience init(options: SnapshotOptions, accessToken: String? = nil) { + self.init(options: options, accessToken: accessToken, host: nil) + } + + /** + Initializes a newly created snapshot instance with the given options and the default access token. + + The snapshot instance sends requests to the classic Mapbox Static API endpoint. + + - parameter options: Options that determine the contents and format of the output image. + */ + public convenience init(options: SnapshotOptions) { + self.init(options: options, accessToken: nil) } /** diff --git a/MapboxStaticTests/MapboxStaticTests.swift b/MapboxStaticTests/MapboxStaticTests.swift index df1bb2c..cf5f99f 100644 --- a/MapboxStaticTests/MapboxStaticTests.swift +++ b/MapboxStaticTests/MapboxStaticTests.swift @@ -155,7 +155,7 @@ class MapboxStaticTests: XCTestCase { let sizeExp = expectationWithDescription("size should pass intact for non-retina") - var options = SnapshotOptions( + let options = SnapshotOptions( mapIdentifiers: mapIdentifiers, size: CGSize(width: CGFloat(width), height: CGFloat(height))) options.scale = 1 @@ -184,23 +184,39 @@ class MapboxStaticTests: XCTestCase { .PNG, .PNG32, .PNG64, .PNG128, .PNG256, .JPEG, .JPEG70, .JPEG80, .JPEG90, ] - switch allFormats[0] { - case .PNG, .PNG32, .PNG64, .PNG128, .PNG256, - .JPEG, .JPEG70, .JPEG80, .JPEG90: - break - // If you get a “Switch must be exhaustive, consider adding a default clause” error here, allFormats is missing a format. - } for format in allFormats { let exp = expectationWithDescription("\(format.rawValue) extension should be requested") expectations.append(exp) - stub(isExtension(format.rawValue)) { request in + let pathExtension: String + switch format { + case .PNG: + pathExtension = "png" + case .PNG32: + pathExtension = "png32" + case .PNG64: + pathExtension = "png64" + case .PNG128: + pathExtension = "png128" + case .PNG256: + pathExtension = "png256" + case .JPEG: + pathExtension = "jpg" + case .JPEG70: + pathExtension = "jpg70" + case .JPEG80: + pathExtension = "jpg80" + case .JPEG90: + pathExtension = "jpg90" + // If you get a “Switch must be exhaustive, consider adding a default clause” error here, allFormats is also missing a format. + } + stub(isExtension(pathExtension)) { request in exp.fulfill() return OHHTTPStubsResponse() } - var options = SnapshotOptions( + let options = SnapshotOptions( mapIdentifiers: mapIdentifiers, size: CGSize(width: 200, height: 200)) options.format = format @@ -217,7 +233,7 @@ class MapboxStaticTests: XCTestCase { func testRetina() { let retinaExp = expectationWithDescription("retina should request @2x asset") - var options = SnapshotOptions( + let options = SnapshotOptions( mapIdentifiers: mapIdentifiers, size: CGSize(width: 200, height: 200)) options.scale = 2 @@ -242,7 +258,7 @@ class MapboxStaticTests: XCTestCase { func testOverlayBuiltinMarker() { let lat = 45.52 let lon = -122.681944 - let size = Marker.Size.Medium + let size = "m" let label = "cafe" let color = Color.brownColor() let colorRaw = "996633" @@ -251,18 +267,18 @@ class MapboxStaticTests: XCTestCase { let markerOverlay = Marker( coordinate: CLLocationCoordinate2D(latitude: lat, longitude: lon), - size: size, - label: .IconName("cafe"), - color: color) + size: .Medium, + iconName: "cafe") + markerOverlay.color = color - var options = SnapshotOptions( + let options = SnapshotOptions( mapIdentifiers: mapIdentifiers, size: CGSize(width: 200, height: 200)) options.overlays = [markerOverlay] stub(isHost(serviceHost)) { request in if let p = request.URL?.pathComponents - where p[3] == "pin-" + size.rawValue + "-" + label + + where p[3] == "pin-" + size + "-" + label + "+" + colorRaw + "(\(lon),\(lat))" { markerExp.fulfill() } @@ -286,7 +302,7 @@ class MapboxStaticTests: XCTestCase { coordinate: coordinate, URL: markerURL) - var options = SnapshotOptions( + let options = SnapshotOptions( mapIdentifiers: mapIdentifiers, size: CGSize(width: 200, height: 200)) options.overlays = [customMarker] @@ -323,7 +339,7 @@ class MapboxStaticTests: XCTestCase { let geojsonString = try! NSString(contentsOfURL: geojsonURL, encoding: NSUTF8StringEncoding) let geojsonOverlay = GeoJSON(objectString: geojsonString as String) - var options = SnapshotOptions( + let options = SnapshotOptions( mapIdentifiers: mapIdentifiers, size: CGSize(width: 200, height: 200)) options.overlays = [geojsonOverlay] @@ -376,16 +392,16 @@ class MapboxStaticTests: XCTestCase { CLLocationCoordinate2D( latitude: 45.52475063103141, longitude: -122.68209457397461 ) - ], - strokeWidth: strokeWidth, - strokeColor: strokeColor, - strokeOpacity: strokeOpacity, - fillColor: fillColor, - fillOpacity: fillOpacity) + ]) + path.strokeWidth = strokeWidth + path.strokeColor = strokeColor + path.strokeOpacity = strokeOpacity + path.fillColor = fillColor + path.fillOpacity = fillOpacity let pathExp = expectationWithDescription("raw path argument should properly encode request") - var options = SnapshotOptions( + let options = SnapshotOptions( mapIdentifiers: mapIdentifiers, size: CGSize(width: 200, height: 200)) options.overlays = [path] @@ -415,10 +431,10 @@ class MapboxStaticTests: XCTestCase { let markerOverlay = Marker( coordinate: CLLocationCoordinate2D(latitude: 45.52, longitude: -122.681944), size: .Medium, - label: .IconName("cafe"), - color: .brownColor()) + iconName: "cafe") + markerOverlay.color = .brownColor() - var options = SnapshotOptions( + let options = SnapshotOptions( mapIdentifiers: mapIdentifiers, size: CGSize(width: 200, height: 200)) options.overlays = [markerOverlay] diff --git a/OS X.playground/Contents.swift b/OS X.playground/Contents.swift index 6011de6..34bea9f 100644 --- a/OS X.playground/Contents.swift +++ b/OS X.playground/Contents.swift @@ -64,8 +64,8 @@ snapshot.requestURL let markerOverlay = Marker( coordinate: CLLocationCoordinate2D(latitude: 45.52, longitude: -122.681944), size: .Medium, - label: .IconName("cafe"), - color: .brownColor()) + iconName: "cafe") +markerOverlay.color = .brownColor() options.overlays = [markerOverlay] snapshot = Snapshot( options: options, @@ -122,11 +122,11 @@ let path = Path( CLLocationCoordinate2D( latitude: 45.52475063103141, longitude: -122.68209457397461), - ], - strokeWidth: 2, - strokeColor: .blackColor(), - fillColor: .redColor(), - fillOpacity: 0.25) + ]) +path.strokeWidth = 2 +path.strokeColor = .blackColor() +path.fillColor = .redColor() +path.fillOpacity = 0.25 options.overlays = [path] snapshot = Snapshot( options: options, diff --git a/OS X.playground/contents.xcplayground b/OS X.playground/contents.xcplayground index fd676d5..3de2b51 100644 --- a/OS X.playground/contents.xcplayground +++ b/OS X.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/README.md b/README.md index 2bd734c..50ef2fd 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [📺 ![tvOS Build Status](https://www.bitrise.io/app/76cb1d11414a5b80.svg?token=zz77y14EcDGj5ZbKBidJXw&branch=master)](https://www.bitrise.io/app/76cb1d11414a5b80)     [⌚️ ![watchOS Build Status](https://www.bitrise.io/app/cd7bcec99edcea34.svg?token=ayBsg-HC9sXmqiFlMDYK0A&branch=master)](https://www.bitrise.io/app/cd7bcec99edcea34) -MapboxStatic.swift makes it easy to connect your iOS, OS X, tvOS, or watchOS application to the [classic Mapbox Static API](https://www.mapbox.com/api-documentation/#static-classic). Quickly generate a static map image with overlays, asynchronous imagery fetching, and first-class Swift data types. +MapboxStatic.swift makes it easy to connect your iOS, OS X, tvOS, or watchOS application to the [classic Mapbox Static API](https://www.mapbox.com/api-documentation/#static-classic). Quickly generate a static map image with overlays by fetching it synchronously or asynchronously over the Web using first-class Swift or Objective-C data types. Static maps are flattened PNG or JPG images, ideal for use in table views, image views, and anyplace else you'd like a quick, custom map without the overhead of an interactive view. They are created in one HTTP request, so overlays are all added *server-side*. @@ -25,9 +25,10 @@ You will need a [map ID](https://www.mapbox.com/help/define-map-id/) from a [cus ### Basics -The main static map class is `Snapshot`. To create a basic snapshot, create a `SnapshotOptions` object, specifying the center coordinates, [zoom level](https://www.mapbox.com/help/how-web-maps-work/#tiles-and-zoom-levels), and point size: +The main static map class is `Snapshot` in Swift or `MBSnapshot` in Objective-C. To create a basic snapshot, create a `SnapshotOptions` or `MBSnapshotOptions` object, specifying the center coordinates, [zoom level](https://www.mapbox.com/help/how-web-maps-work/#tiles-and-zoom-levels), and point size: ```swift +// main.swift import MapboxStatic let options = SnapshotOptions( @@ -40,43 +41,87 @@ let snapshot = Snapshot( accessToken: "<#your access token#>") ``` +```objc +// main.m +@import MapboxStatic; + +MBSnapshotOptions *options = [[MBSnapshotOptions alloc] initWithMapIdentifiers:@[@"<#your map ID#>"] + centerCoordinate:CLLocationCoordinate2DMake(45.52, -122.681944) + zoomLevel:13 + size:CGSizeMake(200, 200)]; +MBSnapshot *snapshot = [[MBSnapshot alloc] initWithOptions:options accessToken:@"<#your access token#>"]; +``` + Then, you can either retrieve an image synchronously (blocking the calling thread): ```swift +// main.swift imageView.image = snapshot.image ``` +```objc +// main.m +imageView.image = snapshot.image; +``` + ![](./screenshots/map.png) Or you can pass a completion handler to update the UI thread after the image is retrieved: ```swift +// main.swift snapshot.image { (image, error) in imageView.image = image } ``` +```objc +// main.m +[snapshot imageWithCompletionHandler:^(UIImage * _Nullable image, NSError * _Nullable error) { + imageView.image = image; +}]; +``` + If you're using your own HTTP library or routines, you can also retrieve a snapshot’s `requestURL` property. ```swift +// main.swift let requestURLToFetch = snapshot.requestURL ``` +```objc +// main.m +NSURL *requestURLToFetch = snapshot.requestURL; +``` + ### Overlays Overlays are where things get interesting! You can add [Maki markers](https://www.mapbox.com/maki/), custom marker imagery, GeoJSON geometries, and even paths made of bare coordinates. -You add overlays to the `overlays` field in the `SnapshotOptions` object. Here are some versions of our snapshot with various overlays added. +You add overlays to the `overlays` field in the `SnapshotOptions` or `MBSnapshotOptions` object. Here are some versions of our snapshot with various overlays added. #### Marker ```swift +// main.swift let markerOverlay = Marker( coordinate: CLLocationCoordinate2D(latitude: 45.52, longitude: -122.681944), size: .Medium, - label: .IconName("cafe"), - color: .brownColor() + iconName: "cafe" ) +markerOverlay.color = .brownColor() +``` + +```objc +// main.m +MBMarker *markerOverlay = [[MBMarker alloc] initWithCoordinate:CLLocationCoordinate2DMake(45.52, -122.681944) + size:MBMarkerSizeMedium + iconName:@"cafe"]; +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH + markerOverlay.color = [UIColor brownColor]; +#elif TARGET_OS_MAC + markerOverlay.color = [NSColor brownColor]; +#endif ``` ![](./screenshots/marker.png) @@ -84,17 +129,25 @@ let markerOverlay = Marker( #### Custom marker ```swift +// main.swift let customMarker = CustomMarker( coordinate: CLLocationCoordinate2D(latitude: 45.522, longitude: -122.69), URL: NSURL(string: "https://www.mapbox.com/help/img/screenshots/rocket.png")! ) ``` +```objc +// main.m +MBCustomMarker *customMarker = [[MBCustomMarker alloc] initWithCoordinate:CLLocationCoordinate2DMake(45.522, -122.69) + URL:[NSURL URLWithString:@"https://www.mapbox.com/help/img/screenshots/rocket.png"]]; +``` + ![](./screenshots/custom.png) #### GeoJSON ```swift +// main.swift let geojsonOverlay: GeoJSON do { @@ -104,11 +157,21 @@ do { } ``` +```objc +// main.m +NSURL *geojsonURL = [NSURL URLWithString:@"http://git.io/vCv9U"]; +NSString *geojsonString = [[NSString alloc] initWithContentsOfURL:geojsonURL + encoding:NSUTF8StringEncoding + error:NULL]; +MBGeoJSON *geojsonOverlay = [[MBGeoJSON alloc] initWithObjectString:geojsonString]; +``` + ![](./screenshots/geojson.png) #### Path ```swift +// main.swift let path = Path( coordinates: [ CLLocationCoordinate2D( @@ -129,12 +192,35 @@ let path = Path( CLLocationCoordinate2D( latitude: 45.52475063103141, longitude: -122.68209457397461 ) - ], - strokeWidth: 2, - strokeColor: .blackColor(), - fillColor: .redColor(), - fillOpacity: 0.25 + ] ) +path.strokeWidth = 2 +path.strokeColor = .blackColor() +path.fillColor = .redColor() +path.fillOpacity = 0.25 +``` + +```objc +// main.m +CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(45.52475063103141, -122.68209457397461), + CLLocationCoordinate2DMake(45.52451009822193, -122.67488479614258), + CLLocationCoordinate2DMake(45.51681250530043, -122.67608642578126), + CLLocationCoordinate2DMake(45.51693278828882, -122.68999099731445), + CLLocationCoordinate2DMake(45.520300607576864, -122.68964767456055), + CLLocationCoordinate2DMake(45.52475063103141, -122.68209457397461), +}; +MBPath *path = [[MBPath alloc] initWithCoordinates:coordinates + count:sizeof(coordinates) / sizeof(coordinates[0])]; +path.strokeWidth = 2; +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH + path.strokeColor = [UIColor blackColor]; + path.fillColor = [UIColor redColor]; +#elif TARGET_OS_MAC + path.strokeColor = [NSColor blackColor]; + path.fillColor = [NSColor redColor]; +#endif +path.fillOpacity = 0.25; ``` ![](./screenshots/path.png) @@ -146,12 +232,20 @@ let path = Path( If you’re adding overlays to your map, leave out the center coordinate and zoom level to automatically calculate the center and zoom level that best shows them off. ```swift +// main.swift var options = SnapshotOptions( mapIdentifiers: ["<#your map ID#>"], size: CGSize(width: 500, height: 300)) options.overlays = [path, geojsonOverlay, markerOverlay, customMarker] ``` +```objc +// main.m +MBSnapshotOptions *options = [[MBSnapshotOptions alloc] initWithMapIdentifiers:@[@"<#your map ID#>"] + size:CGSizeMake(500, 300)]; +options.overlays = @[path, geojsonOverlay, markerOverlay, customMarker]; +``` + ![](screenshots/autofit.png) #### File format and quality diff --git a/iOS.playground/Contents.swift b/iOS.playground/Contents.swift index 4980b99..b6fd664 100644 --- a/iOS.playground/Contents.swift +++ b/iOS.playground/Contents.swift @@ -64,8 +64,8 @@ snapshot.requestURL let markerOverlay = Marker( coordinate: CLLocationCoordinate2D(latitude: 45.52, longitude: -122.681944), size: .Medium, - label: .IconName("cafe"), - color: .brownColor()) + iconName: "cafe") +markerOverlay.color = .brownColor() options.overlays = [markerOverlay] snapshot = Snapshot( options: options, @@ -122,11 +122,11 @@ let path = Path( CLLocationCoordinate2D( latitude: 45.52475063103141, longitude: -122.68209457397461), - ], - strokeWidth: 2, - strokeColor: .blackColor(), - fillColor: .redColor(), - fillOpacity: 0.25) + ]) +path.strokeWidth = 2 +path.strokeColor = .blackColor() +path.fillColor = .redColor() +path.fillOpacity = 0.25 options.overlays = [path] snapshot = Snapshot( options: options, diff --git a/iOS.playground/contents.xcplayground b/iOS.playground/contents.xcplayground index 3596865..89da2d4 100644 --- a/iOS.playground/contents.xcplayground +++ b/iOS.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/iOS.playground/timeline.xctimeline b/iOS.playground/timeline.xctimeline index 94b90e9..486ca01 100644 --- a/iOS.playground/timeline.xctimeline +++ b/iOS.playground/timeline.xctimeline @@ -8,27 +8,52 @@ shouldTrackSuperviewWidth = "NO"> + + + + + + + + + + From 62daa1b471e9b46125069d88fc7d724e8c9f6335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Sun, 15 May 2016 16:42:02 -0700 Subject: [PATCH 2/3] Ported example application to Objective-C Added a new example application in Objective-C that has the same minimalist feature set as the Swift example application, plus a little code to exercise round-tripping of coordinates for Path. Fixes #17. --- Example/AppDelegate.h | 6 ++ Example/AppDelegate.m | 16 ++++ Example/ViewController.h | 4 + Example/ViewController.m | 46 ++++++++++ Example/main.m | 8 ++ .../xcschemes/Example (Objective-C).xcscheme | 91 +++++++++++++++++++ ...mple.xcscheme => Example (Swift).xcscheme} | 16 ++-- 7 files changed, 179 insertions(+), 8 deletions(-) create mode 100644 Example/AppDelegate.h create mode 100644 Example/AppDelegate.m create mode 100644 Example/ViewController.h create mode 100644 Example/ViewController.m create mode 100644 Example/main.m create mode 100644 MapboxStatic.xcodeproj/xcshareddata/xcschemes/Example (Objective-C).xcscheme rename MapboxStatic.xcodeproj/xcshareddata/xcschemes/{Example.xcscheme => Example (Swift).xcscheme} (88%) diff --git a/Example/AppDelegate.h b/Example/AppDelegate.h new file mode 100644 index 0000000..98e4875 --- /dev/null +++ b/Example/AppDelegate.h @@ -0,0 +1,6 @@ +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; +@end diff --git a/Example/AppDelegate.m b/Example/AppDelegate.m new file mode 100644 index 0000000..96a4766 --- /dev/null +++ b/Example/AppDelegate.m @@ -0,0 +1,16 @@ +#import "AppDelegate.h" +#import "ViewController.h" + +@interface AppDelegate () +@end + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + self.window.rootViewController = [[ViewController alloc] init]; + [self.window makeKeyAndVisible]; + return YES; +} + +@end diff --git a/Example/ViewController.h b/Example/ViewController.h new file mode 100644 index 0000000..d2fc081 --- /dev/null +++ b/Example/ViewController.h @@ -0,0 +1,4 @@ +#import + +@interface ViewController : UIViewController +@end diff --git a/Example/ViewController.m b/Example/ViewController.m new file mode 100644 index 0000000..f254afe --- /dev/null +++ b/Example/ViewController.m @@ -0,0 +1,46 @@ +#import "ViewController.h" + +@import CoreLocation; +@import MapboxStatic; + +// You can also specify the access token with the `MGLMapboxAccessToken` key in Info.plist. +static NSString * const AccessToken = @"pk.eyJ1IjoianVzdGluIiwiYSI6IlpDbUJLSUEifQ.4mG8vhelFMju6HpIY-Hi5A"; + +@interface ViewController () + +@property (nonatomic, strong) UIImageView *imageView; + +@end + +@implementation ViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; + self.imageView.backgroundColor = [UIColor blackColor]; + [self.view addSubview:self.imageView]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + MBSnapshotOptions *options = [[MBSnapshotOptions alloc] initWithMapIdentifiers:@[@"justin.tm2-basemap"] + centerCoordinate:CLLocationCoordinate2DMake(45, -122) + zoomLevel:6 + size:self.imageView.bounds.size]; + CLLocationCoordinate2D coords[] = { + CLLocationCoordinate2DMake(45, -122), + CLLocationCoordinate2DMake(45, -124), + }; + MBPath *path = [[MBPath alloc] initWithCoordinates:coords count:sizeof(coords) / sizeof(coords[0])]; + options.overlays = @[path]; + MBSnapshot *snapshot = [[MBSnapshot alloc] initWithOptions:options accessToken:AccessToken]; + __weak typeof(self) weakSelf = self; + [snapshot imageWithCompletionHandler:^(UIImage * _Nullable image, NSError * _Nullable error) { + typeof(weakSelf) strongSelf = weakSelf; + strongSelf.imageView.image = image; + }]; +} + +@end diff --git a/Example/main.m b/Example/main.m new file mode 100644 index 0000000..81e84cb --- /dev/null +++ b/Example/main.m @@ -0,0 +1,8 @@ +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/MapboxStatic.xcodeproj/xcshareddata/xcschemes/Example (Objective-C).xcscheme b/MapboxStatic.xcodeproj/xcshareddata/xcschemes/Example (Objective-C).xcscheme new file mode 100644 index 0000000..80bc350 --- /dev/null +++ b/MapboxStatic.xcodeproj/xcshareddata/xcschemes/Example (Objective-C).xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MapboxStatic.xcodeproj/xcshareddata/xcschemes/Example.xcscheme b/MapboxStatic.xcodeproj/xcshareddata/xcschemes/Example (Swift).xcscheme similarity index 88% rename from MapboxStatic.xcodeproj/xcshareddata/xcschemes/Example.xcscheme rename to MapboxStatic.xcodeproj/xcshareddata/xcschemes/Example (Swift).xcscheme index 62f57c0..d1e8588 100644 --- a/MapboxStatic.xcodeproj/xcshareddata/xcschemes/Example.xcscheme +++ b/MapboxStatic.xcodeproj/xcshareddata/xcschemes/Example (Swift).xcscheme @@ -15,8 +15,8 @@ @@ -34,8 +34,8 @@ @@ -57,8 +57,8 @@ @@ -76,8 +76,8 @@ From 75ccda542cf7e49777f358c6ad2b63929e9e074e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Sun, 15 May 2016 16:58:38 -0700 Subject: [PATCH 3/3] Fixed crash initializing color with 3-digit hex string When the hex string is an NSString under the hood, indexing into an array literal can wind up getting garbage if the NSString is initialized with UTF-16 or other storage under the hood. --- MapboxStatic/Color.swift | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/MapboxStatic/Color.swift b/MapboxStatic/Color.swift index c649638..831309f 100644 --- a/MapboxStatic/Color.swift +++ b/MapboxStatic/Color.swift @@ -31,12 +31,9 @@ internal extension Color { convenience init(hexString: String) { var hexString = hexString.stringByReplacingOccurrencesOfString("#", withString: "") - if hexString.lengthOfBytesUsingEncoding(NSUTF8StringEncoding) == 3 { - let r = Array(arrayLiteral: hexString)[0] - let g = Array(arrayLiteral: hexString)[1] - let b = Array(arrayLiteral: hexString)[2] - - hexString = r + r + g + g + b + b + if hexString.characters.count == 3 { + let digits = Array(hexString.characters) + hexString = "\(digits[0])\(digits[0])\(digits[1])\(digits[1])\(digits[2])\(digits[2])" } var r: CGFloat = 0