mapbox / MapboxStatic.swift

Static map snapshots with overlays in Swift or Objective-C on iOS, macOS, tvOS, and watchOS
https://www.mapbox.com/api-documentation/?language=Swift#static
Other
189 stars 31 forks source link

Crashes at URL Construction #65

Closed pkluz closed 7 years ago

pkluz commented 7 years ago

Given the following code:

let preferredBounds = viewModel.preferredBounds
let camera = SnapshotCamera(lookingAtCenter: preferredBounds.midpoint, zoomLevel: 12.0)

guard let url = URL(string: Constants.mapboxTheme) else { return .init(value: nil) }

let options = SnapshotOptions(styleURL: url, camera: camera, size: contentSize)

let path = Path(coordinates: [
    CLLocationCoordinate2D(latitude: 35.012579, longitude: 135.778078),
    CLLocationCoordinate2D(latitude: 35.012853, longitude: 135.773099),
    CLLocationCoordinate2D(latitude: 35.014601, longitude: 135.769167)
])
path.strokeWidth = 2
path.strokeColor = .black
options.overlays = [ path ]

let snapshot = Snapshot(options: options, accessToken: Constants.mapboxAPIKey)
...

When attempting to load the image asynchronously, the application crashes in Snapshot.swift here:

open var url: URL {
    var components = URLComponents()
    components.queryItems = params
    return URL(string: "\(options.path)?\(components.percentEncodedQuery!)", relativeTo: apiEndpoint)!
}

The URL(...) that is being force unwrapped is nil.

The values used are all present and (except for the API key) look as follows:

options.path =

/styles/v1/USER_NAME/cilwvi5hp007dcglv13toc5zd/static/path-2+#000000-1.0+#000555-1.0(skutE_dv%7BXu@b%5E%7DIpW)/135.7849645615,35.01171633755,12.0/375x240@2x

components.percentEncodedQuery! =

access_token=xx.eyXXXXXXXXN9ubWlsXXXXXXXXXXXNpbHdzcjAXXXXXXXXXXXXXXXXNTXXXXXXXXNoVBXXXXXXXXNwmaQ

(key is valid and works elsewhere, replaced characters with X's)

apiEndpoint =

https://api.mapbox.com

Do you have any guidance? Running on Xcode 8.3.2, and deploying to iOS 10.2

1ec5 commented 7 years ago

The path should contain 000000, not #000000.

/cc @frederoni

frederoni commented 7 years ago

I can't reproduce this using v0.9. @pkluz, what version are you using?

pkluz commented 7 years ago

Straight from the Podfile:pod 'MapboxStatic.swift', '~> 0.9.0'

This works perfectly:

let preferredBounds = viewModel.preferredBounds
let camera = SnapshotCamera(lookingAtCenter: preferredBounds.midpoint, zoomLevel: 12.25)

guard let url = URL(string: Constants.mapboxTheme) else { return }

let options = SnapshotOptions(styleURL: url, camera: camera, size: contentSize)
let snapshot = Snapshot(options: options, accessToken: Constants.mapboxAPIKey)

snapshot.image { [weak self] image, error in
    imageView.image = image
}

This does not:

let preferredBounds = viewModel.preferredBounds
let camera = SnapshotCamera(lookingAtCenter: preferredBounds.midpoint, zoomLevel: 12.25)

guard let url = URL(string: Constants.mapboxTheme) else { return }

let options = SnapshotOptions(styleURL: url, camera: camera, size: contentSize)
let snapshot = Snapshot(options: options, accessToken: Constants.mapboxAPIKey)

let path = Path(coordinates: [
    CLLocationCoordinate2D(latitude: 35.012579, longitude: 135.778078),
    CLLocationCoordinate2D(latitude: 35.012853, longitude: 135.773099),
    CLLocationCoordinate2D(latitude: 35.014601, longitude: 135.769167)
])
path.strokeWidth = 2
path.strokeColor = .black
options.overlays = [ path ]

snapshot.image { [weak self] image, error in
    imageView.image = image
}

Have not yet tried this with different coordinates, although I'm not sure how this'd relate to a stray # in the URL. I did have MapBox issues in the past though where US coordinates worked perfectly, but it had issues with rendering pins in say Japan correctly... although as said, I doubt that's related to the crash here

frederoni commented 7 years ago

# in the URL is most likely the issue but I can't reproduce using your code.

```swift let preferredBounds = view.bounds let camera = SnapshotCamera(lookingAtCenter: CLLocationCoordinate2D(latitude: 45, longitude: -122), zoomLevel: 12.25) guard let url = URL(string: "mapbox://styles/mapbox/streets-v9") else { return } let options = SnapshotOptions(styleURL: url, camera: camera, size: preferredBounds.size) let snapshot = Snapshot(options: options, accessToken: accessToken) let path = Path(coordinates: [ CLLocationCoordinate2D(latitude: 35.012579, longitude: 135.778078), CLLocationCoordinate2D(latitude: 35.012853, longitude: 135.773099), CLLocationCoordinate2D(latitude: 35.014601, longitude: 135.769167) ]) path.strokeWidth = 2 path.strokeColor = .black options.overlays = [ path ] snapshot.image { [weak self] image, error in self?.imageView.image = image } ```

This seems to work perfectly fine. Can you check your Podfile.lock to make sure you're actually running v0.9.0? If you have an outdated version you might have to run pod repo update prior to pod update.

pkluz commented 7 years ago

Locked to 0.9.0:

 - MapboxStatic.swift (~> 0.9.0)
pkluz commented 7 years ago

So, I found the issue and it's as follows:

You define a typealias in Color.swift:

#if os(OSX)
    import Cocoa
    typealias Color = NSColor
#else
    import UIKit
    typealias Color = UIColor
#endif

Then you define an extension on that typealias here:

internal extension Color { ... }

A different dependency of mine however, literally implements a platform independent type called Color. That type is concrete, namely a struct. And it also happens to provide a method toHexString(), which happens to return the string with a prepended #. Your extension implementation is never called. Swift seems to prefer concrete types while looking up the methods to dispatch to.

I think this can be mitigated by defining a more uniquely named typealias on your end or scoping it inside a namespace... What do you think?

frederoni commented 7 years ago

Adding final to MapboxStatic's toHexString() would prevent this issue.