Skip to content

Maps

This section contains SYMapView manipulations and offline maps management using SYMapInstaller class.

Related topics:

  • Adding SYMapView to iOS application is covered in Getting Started.
  • Showing current location on the map is covered in Navigation.

Map Handling

SYMapView.camera provides an interface to get/set the visible part of the map. Key attributes of SYMapCamera are geoCenter (geographical center), zoom (zoom level), tilt, rotation, boundingBox.

As an example, try showing the location of Sygic headquarters with street level zoom by adding these adjustments:

let mapView = SYMapView(frame: view.bounds)
mapView.camera.zoom = 17
mapView.camera.tilt = 0
mapView.camera.geoCenter = SYGeoCoordinate(latitude: 48.145810, longitude: 17.126420)

Zoom Level

Zoom level determines how "close" the map view is to the surface of Earth. Higher zoom levels give a closer view. Zoom 1 is the view on Earth, zoom 10 is approximately view on city, 15 on street, etc.

Rotation

The rotation determines which cardinal direction corresponds with which screen direction of the SYMapView. The valid range is from 0 to 360 degrees. The default value of 0 orients the map so that true north is toward the top of the view. The following code shows how to set the orientation to south-up:

mapView.camera.rotation = 180

Tilt

The tilt value represents the angle at which the map is viewed. You can set tilt using SYMapView.camera.tilt. Value of 0 provides a top-down two-dimensional view of the map. Note that the valid tilt range depends on the current zoom level, the map will apply the closest possible value from the valid range.

// set tilt for 2D to 0, for 3D to 60
let tiltFor2D: SYAngle = 0
let tiltFor3D: SYAngle = 60

// ...
mapView.camera.tilt = self.tiltFor2D
// ...

2D tilt  3D tilt

FPS Limiter

In order to save battery, SYMapView.setFpsLimitMode method allows you to set the maximum FPS performance of your App.

There are 2 modes available:

  • SYFpsLimitModePerformance - Always try to run at maximum FPS.
  • SYFpsLimitModeBalanced - Try to achieve smooth camera movement with minimum performance requirements.
mapView.setFpsLimitMode(.balanced, withFpsLimit: 15.0)

Handling map rendering

Another way to save battery is to turn map rendering off. This can be useful when the map is fully covered by another view (e.g. menu).

Remember to turn it back on when the map is visible again.

mapView.renderEnabled = false

Animations

The SYMapView supports the following animation settings to be used while changing properties, defined by the SYAnimation:

rotateView

  • angle - Camera rotation offset in degrees.
  • duration - Animation duration.
  • curve - Animation curve.
  • completion - Animation completion block.
mapView.camera.rotateView(90.0, withDuration: 1.0, curve: .decelerate, completion: nil)

dragView

  • drag - Camera move offset in screen points.
  • duration - Animation duration.
  • curve - Animation curve.
  • completion - Animation completion block.
mapView.camera.dragView(CGPoint(x: 50.0, y: 100.0), withDuration: 1.0, curve: .decelerate, completion: nil)

dragViewFrom

  • from - Screen from point.
  • to - Screen to point.
  • duration - Animation duration.
  • curve - Animation curve.
  • completion - Animation completion block.
mapView.camera.dragView(from: CGPoint(x: 0.0, y: 0.0), to: CGPoint(x: 50.0, y: 50.0), withDuration: 1.0, curve: .linear, completion: nil)

zoomView

  • zoom - Zoom scale,
  • center - Screen point to zoom at. Usually represents center of pinch gesture touches. Ignored if SYMapView.cameraMovementMode is not SYCameraMovementFree,
  • duration - Animation duration,
  • curve - Animation curve,
  • completion - Animation completion block.
mapView.camera.zoomView(2.0, at: CGPoint(x: 250.0, y: 250.0), withDuration: 2.0, curve: .accelerate, completion: nil)

animate

  • animations - Animation block containing properties changes (animatable properties are geoCenter, transformCenter, rotation, tilt, and zoom),
  • duration - Animation duration,
  • curve - Animation curve,
  • completion - Animation completion block,
mapView.camera.animate({
    mapView.camera.tilt = 20.0
    mapView.camera.zoom = 11
    mapView.camera.rotation = 180.0
    mapView.camera.geoCenter = SYGeoCoordinate(latitude: 48.147, longitude: 17.101878)
}, withDuration: 1.0, curve: .decelerate, completion: nil)

Here are the available SYMapAnimationCurve types:

  • SYMapAnimationCurveLinear ( Swift .linear) - A linear animation curve causes an animation to occur evenly over its duration.
  • SYMapAnimationCurveAccelerate ( Swift .accelerate) - This curve causes the animation to accelerate.
  • SYMapAnimationCurveDecelerate ( Swift .decelerate) - This curve causes the animation to slow down.
  • SYMapAnimationCurveAccelerateDecelerate ( Swift .accelerateDecelerate) - This curve causes the animation to accelerate first and decelerate at the end.
  • SYMapAnimationCurveBounce ( Swift .bounce) - This curve will toggle the animation to bounce.

Screen Points to Coordinates

Conversion of screen points to geographical coordinates and vice versa is provided by SYMapView methods: geoCoordinates(fromScreenPoints:), screenPoints(from:).

let point = NSValue(cgPoint: CGPoint(x: 100, y: 200))
let coordinate = mapView.geoCoordinates(fromScreenPoints: [point])[0]
print("coordinate: \(coordinate.latitude), \(coordinate.longitude)")

Map Language

Our maps (except OSM) have multilang tables which contain translations of city/street/POI names for several languages. Please note that it is our map providers who provide us with the translations and we cannot modify the translations.

The language is set globally using a language code and can not be set for specific countries. We recommend changing the map language according to the language of your application, or the system's current Locale.

Setting the language is as easy as this:

let frLocale = Locale(identifier: "fr")
mapView.displayLanguage = frLocale.languageCode

For example, Belgium mostly has street names translated to Dutch and French. If you set the French language, the names will appear as "Rue de l'église". If you change that to Dutch, the map will reload and the same text will be shown as "Kerkstraat". If you set a language that does not have a translation for that specific name, the default language will be used, in case of Belgium it would be Dutch.

Unfortunately, as of now there is no method to get the available languages or check what is the default language. We hope to add this possibility in the future.

Map Skins

Specific map skins are available to offer your application users a choice among different kinds of map appearance. Currently we support day and night themes alongside with car and pedestrian navigation types. You can combine your skin as theme + navigation type.

// To get available skins:
let activeSkins = mapView.availableSkins() 
// returns ["browse", "car", "car_custom", "car_no_signal", "day", "default", "low_performance", "navigation",
// "night", "pedestrian", "pedestrian_no_signal", "realview", "scout"]

// For example, to set a night theme for car navigation type:
mapView.activeSkins = ["night", "car"]

Day skin & Night skin

Gestures

The SYMapView class responds to a number of predefined touch gestures. The default behavior of the map for each gesture type may be used as-is, supplemented, or replaced entirely.

To disable automatic gesture handling, set SYMapView.mapInteractionEnabled property to false.

The following table is a summary of the available gestures and their default behavior.

Gesture Description Following gestures
Rotate Rotate: Two-finger press, simultaneously orbit both fingers around the center point, lift Pinch, Spread, Move
Zoom out Pinch: Two-finger press, move inwards, lift Spread, Rotate, Move
Zoom in Spread: Two-finger press, move outwards, lift
Move Swipe: One-finger press, move, lift/don't lift
Tilt Tilt: Two fingers press, move up (increase the tilt angle) or move down (decrease the tilt angle)
Short touch Short touch: One-finger press, lift

Map Objects

The Sygic Maps SDK allows customization and extensibility by adding variety of objects to a map view. The types of available objects include map markers, routes, polylines, circles. To see full list of objects available, check SYMapObject API reference. Some objects are described in more detail below.

The SYMapObject class provides a generic base class from which most of the specialized map object types inherit. The following is a list of important properties and methods in SYMapObject.

  • type - contains the type of the map object, such as marker, polyline, circle, route, or other.
  • zIndex - determines the objects stacking order, which controls how the object is displayed on the map relative to other objects that may overlap it. Objects with the highest value are placed at the top of the view.
  • visible - determines whether or not the object is drawn on the map view.

Info

In order to display the map object on SYMapView, call add(_:) in Swift or addMapObject: in Objective-C.

SYMapMarker

Displays an image at a fixed geographical position on the map. Provides adjustable image anchor, visible zoom levels, clickable area (see class reference).

Assuming we've already added an image with name "your_image_resource" to our project (this way), the marker can be added as follows:

let marker = SYMapMarker(
    coordinate: SYGeoCoordinate(latitude: 48.145810, longitude: 17.126420),
    image: UIImage(named: "your_image_resource")!
)
mapView.add(marker)

Marker on the map

SYMapCircle

Draws a circle on the map at a fixed geographical location. Color, radius, line color, and other parameters are adjustable (see class reference).

let circle = SYMapCircle(geoCenter: SYGeoCoordinate(latitude: 48.145810, longitude: 17.126420), radius: 40)
circle.fillColor = UIColor(hue: 236, saturation: 27, brightness: 46, alpha: 0.5)
mapView.add(circle)

Circle on the map

SYMapPolyline

Draws one or more connected line segments on the map. The segment vertices are specified by a series of SYGeoCoordinates. The visual appearance of the polyline can be customized (see class reference).

let polyline = SYMapPolyline(coordinates: [
    SYGeoCoordinate(latitude: 48.1418807, longitude: 17.099226),
    SYGeoCoordinate(latitude: 48.1551798, longitude: 17.1251751),
    SYGeoCoordinate(latitude: 48.1458952, longitude: 17.1246556)
])
mapView.add(polyline)

Polyline on the map

SYMapPolygon

Draws a polygon on the map. It is possible to choose its center color, border color, border width.

// create the center point of the polygon
let polygonCenterCoords = SYGeoCoordinate(latitude: 51.52553410146105, longitude: -0.0874850560665021)

// create a list of vertices of the polygon
var vertices = [SYGeoCoordinate]()
vertices.append(SYGeoCoordinate(latitude: 51.5244670244131, longitude: -0.09403177994637854))
vertices.append(SYGeoCoordinate(latitude: 51.52077097665752, longitude: -0.08904422414612727))
vertices.append(SYGeoCoordinate(latitude: 51.52337451319931, longitude: -0.07890099336773757))
vertices.append(SYGeoCoordinate(latitude: 51.52801169404006, longitude: -0.08521482781958561))
vertices.append(SYGeoCoordinate(latitude: 51.52702834633725, longitude: -0.09122055003020424))

// create the polygon and choose its colors
let polygon = SYMapPolygon(geoCenter: polygonCenterCoords, coordinates: vertices)
polygon.borderColor = UIColor.blue
polygon.centerColor = UIColor.red.withAlphaComponent(0.3)

// add the polygon to map
mapView.add(polygon)

Polygon on the map

Interaction

Map object selection can be detected in 2 ways:

  1. listening to SYMapViewDelegate.mapView(_:didSelect:), SYMapView notifies its delegate any time it recognizes a tap gesture
  2. calling SYMapView.objects(at point:withCompletion:)

Both approaches deliver an array of map objects hit by the tap gesture. Each object can be SYViewObject or its subclass.

Info

SYViewObject instances can be categorized this way:

  • object.baseType == .screen - it's the map itself, object.coordinate contains a geographical coordinate matching the point of tap;
  • object.baseType == .map - it's a map object represented by a subclass of SYMapObject, may be SYMapMarker, SYMapRoute, SYMapRouteLabel;
  • object.baseType == .proxy - it's a place of interest represented by a subclass of SYProxyObject, may be SYProxyPlace, SYProxyCity, SYProxyIncident, use SYProxyObjectsManager to retrieve the object details.

Example - select a map object

Assuming we have a project set up similarly as in Getting Started, and added 2 images: marker1_200x200, marker2_200x200 (this way), modify ViewController as follows (recognized object is written to log "didSelectObjects:").

func setupMapView() {
    let mapView = SYMapView(frame: view.bounds)
    mapView.camera.zoom = 17
    mapView.camera.tilt = 0
    mapView.camera.geoCenter = SYGeoCoordinate(latitude: 48.145810, longitude: 17.126420)
    mapView.delegate = self

    let marker1 = SYMapMarker(
        coordinate: SYGeoCoordinate(latitude: 48.1455, longitude: 17.1262),
        image: UIImage(named: "marker1_200x200")!,
        payload: "🔥" as NSString // special symbol is noticeable in log
    )
    let marker2 = SYMapMarker(
        coordinate: SYGeoCoordinate(latitude: 48.1454, longitude: 17.1266),
        image: UIImage(named: "marker2_200x200")!,
        payload: "💧" as NSString // special symbol is noticeable in log
    )

    mapView.add([marker1, marker2])

    // ...
}

// implement SYMapViewDelegate
extension ViewController: SYMapViewDelegate {
    func mapView(_ mapView: SYMapView, didSelect objects: [SYViewObject]) {

        let summary = objects.map {
            if let payload = $0.payload as? String {
                return payload
            } else if $0.baseType == .screen {
                return "map"
            } else {
                return String(describing: type(of: $0))
            }
        }.joined(separator: ", ")

        // result will appear in log
        print("didSelectObjects: \(summary)")
    }
}
// declare protocol conformance
@interface ViewController() <SYMapViewDelegate>
@end

// improve setting up map view
- (void)setupMapView {
    SYMapView *mapView = [[SYMapView alloc] initWithFrame:self.view.bounds];
    mapView.camera.zoom = 17;
    mapView.camera.tilt = 0;
    mapView.camera.geoCenter = [[SYGeoCoordinate alloc] initWithLatitude:48.145810 longitude:17.126420];
    mapView.delegate = self;

    SYGeoCoordinate* geo1 = [[SYGeoCoordinate alloc] initWithLatitude:48.1455 longitude:17.1262];
    SYMapMarker *marker1 = [[SYMapMarker alloc] initWithCoordinate:geo1
                                                             image:[UIImage imageNamed:@"marker1_200x200"]
                                                           payload:@"🔥"];
    SYGeoCoordinate* geo2 = [[SYGeoCoordinate alloc] initWithLatitude:48.1454 longitude:17.1266];
    SYMapMarker *marker2 = [[SYMapMarker alloc] initWithCoordinate:geo2
                                                             image:[UIImage imageNamed:@"marker2_200x200"]
                                                           payload:@"💧"];

    [mapView addMapObjects:@[marker1, marker2]];

    // ...
}

// implement the delegate method
- (void)mapView:(SYMapView *)mapView didSelectObjects:(NSArray<SYViewObject *> *)objects {

    NSMutableArray *descriptions = [[NSMutableArray alloc] init];
    for (SYViewObject *object in objects)
    {
        if ([(NSObject *)object.payload isKindOfClass:[NSString class]])
        {
            [descriptions addObject:object.payload];
        }
        else if (object.baseType == SYViewObjectTypeScreen)
        {
            [descriptions addObject:@"map"];
        }
        else
        {
            [descriptions addObject:NSStringFromClass([object class])];
        }
    }

    // result will appear in log
    NSLog(@"didSelectObjects: %@", [descriptions componentsJoinedByString:@", "]);
}

When you move, Bread Crumbs leave a "trail" behind the vehicle. Please note that you need to turn this function on in the JSON configuration for it to be active.

"Map": {
  "breadcrumbs_enabled": true
},

Let's see how to use BreadCrumbs - at first you need to have a mapView. After that, just call the MapView's breadCrumbs:

mapView.breadCrumbs.start() // to start showing the bread crumbs on map
mapView.breadCrumbs.setVisibilityTo(SYBreadCrumbsVisibility.hidden) // to hide the visibility
mapView.breadCrumbs.clear() // to delete the visible bread crumbs path - this does not stop it!
mapView.breadCrumbs.stop() // this stops the logging

Offline Maps

Sygic Maps SDK for iOS is able to pre-load map data, allowing the use of maps, search, routing, and other features without an active data connection. Interfaces involved in providing offline maps functionality include SYMapInstaller, SYMapInstallerDelegate and SYCountryDetails.

Switch online/offline

By default, Sygic Maps SDK initializes with offline maps mode. Online maps can be enabled/disabled by SYOnlineSession methods: setOnlineMapsEnabledWithCompletion(_:), setOnlineMapsDisabledWithCompletion(_:).

SYOnlineSession.shared()?.setOnlineMapsEnabledWithCompletion { _ in }

Also online maps can be enabled during SDK initialization by adding a key to the configuration: "MapReaderSettings": ["startup_online_maps_enabled": true], this approach is demonstrated in Getting Started.

SYMapInstaller

SYMapInstaller identifies a country by it's ISO code (usually, a two-letter string).

Info

Use ISO codes received directly from SYMapInstaller, it's not guaranteed that SYMapInstaller always operates with ISO 3166 alpha-2 standard.

SYMapInstaller API provides such operations with map data stored on the device:

  • getAvailableCountries - returns ISO codes for:
    • SYMapInstallerMapFilterAll - all countries (already downloaded + available for download)
    • SYMapInstallerMapFilterInstalled - only downloaded countries
  • getCountryDetails - get details by a country ISO code
  • getRegionDetails - get details by a region ISO code (some big countries are be split into regions)
  • installMap - download map data for a country ISO code or a region ISO code
  • uninstallMap - remove map data, associated with the specified ISO code, from the device
  • getMapStatus - not installed, installed, loaded, etc.
  • loadMap - after map is downloaded, it must be loaded to make it available for browsing, searching, routing, etc.
  • unloadMap
  • checkForUpdates - check if the new map data is available for the specified ISO code
  • updateMap - download new map data for the specified ISO code
  • detectCurrentCountry - 2 modes:
    • detect current county ISO code automatically by the current IP-address
    • get country ISO code from the region ISO code
  • resumePendingInstallations - resume downloads which were in progress when the app was terminated, if any

Order of installation

The map installer keeps track of maps that were requested to be installed. It keeps a queue with all the requests and downloads them over time. The order of downloads is given by the queue. The request to install a map is asynchronous, so if the install map function is called in a for loop for example, the requests will be added to the download queue in no particular order.

Info

Currently there is no way to ensure a specific download order via the API.

What happens when SDK is destroyed

Map installer supports concurrent downloads. If for any reason the SDK is destroyed (the app that uses the SDK is closed or has crashed), the running and queued map downloads are persistently stored and can be restored on a subsequent initialization of the SDK.

To restore them, the resume pending installations function must be called, which will start the download and installation of the previously persisted map downloads. The restored downloads have priority over new downloads. Learn how to resume pending installations.

In case of a network failure (Internet connection not available, disconnected Wi-Fi, etc.), the download will end with an install error and must be requested again by calling the install map function.

Example - List all available countries

class Example: NSObject, SYMapInstallerDelegate {
    var countries = [SYCountryDetails]()

    func demoGetCountries() {
        let mapLoader = SYMapInstaller()

        // get all countries
        mapLoader.getAvailableCountries(.all) { (isoCodes: [String], result: SYMapInstallerResult) in
            guard result == .success else {
                print("Error getAvailableCountries: \(result.rawValue)")
                return
            }

            var remainingCount = isoCodes.count
            isoCodes.forEach { (iso: String) in

                // get country details
                mapLoader.getCountryDetails(iso, installedOnly: false) { (result: SYMapInstallerResult, details: SYCountryDetails?) in
                    if let details = details {
                        self.countries.append(details)
                    }
                    else {
                        print("Error getCountryDetails '\(iso)' result=\(result.rawValue)")
                    }

                    remainingCount -= 1
                    if remainingCount == 0 {
                        // all countries are processed, data is ready
                        self.printCountries()
                    }
                }
            }
        }
    }

    func printCountries() {
        countries.forEach { (country: SYCountryDetails) in
            let continent = country.continentName
            let name = country.name
            let size = country.totalSize
            print("\(continent), '\(country.iso)' \(name) \(size) bytes")
        }
    }
}

Example - Install a map

func demoInstallCountry(_ country: SYCountryDetails) {
    let mapLoader = SYMapInstaller()
    mapLoader.delegate = self // listen to downloading progress

    mapLoader.installMap(country.iso) { (iso: String, result: SYMapInstallerResult) in
        guard result == .success else {
            print("Error installMap \(iso): \(result.rawValue)")
            return
        }

        // load the map to enable rendering, searching, routing
        mapLoader.loadMap(iso) { (result: SYMapInstallerResult) in
            print("loadMap '\(iso)' result=\(result.rawValue)")
        }
    }
}

func mapLoader(_ maploader: SYMapInstaller, didUpdate progress: SYMapInstallProgress, forMap map: String) {
    print("download '\(map)' \(progress.downloadedSize * 100 / progress.totalSize)%")
}

Resuming pending installations

The resumePendingInstallations method checks if some map downloads were interrupted by application exit, and resumes them. If there are no pending installations, the method will finish with completion resumedInstalls = 0, resumedUpdates = 0.

func demoResumePendingInstallations() {
    let mapLoader = SYMapInstaller()
    mapLoader.delegate = self
    mapLoader.resumePendingInstallations { (
      installs: [SYMapInstallerResumedTaskDetails],
      updates: [SYMapInstallerResumedTaskDetails],
      result: SYMapInstallerResult
  ) in
      print("resumePendingInstallations -> \(result.rawValue)")
      print("resumed installs: \(installs.count), resumed updates: \(updates.count)")
  }
}

func mapLoader(_ maploader: SYMapInstaller, didFinishResumedInstallWith result: SYMapInstallerResult, forMap map: String) {
    print("didFinishResumedInstallWith result=\(result.rawValue) map=\(map)")
}

Optional information about SYMapInstaller operations can be received by implementing such methods of SYMapInstallerDelegate:

  • (void)mapLoader:(nonnull SYMapInstaller\*)maploader didFinishResumedInstallWithResult:(SYMapInstallerResult)result forMap:(nonnull SYCountryIso\*)map;
  • (void)mapLoader:(nonnull SYMapInstaller\*)maploader didUpdateMapInstallProgress:(nonnull SYMapInstallProgress\*)progress forMap:(nonnull SYCountryIso\*)map;

Note: these methods will be called on delegates of all SYMapInstaller instances, even if an operation was requested only on one SYMapInstaller.

SYMapInstaller instances can be created only when SYContext is already initialized.

When Sygic Maps SDK is initialized, all installed maps are loaded automatically, but when the new map is installed, SYMapInstaller.loadMap must be called before the map data can be used (for rendering, search, etc.).

All asynchronous operations return SYTask object, it can be used to cancel the operation. If the operation is cancelled, it's completion will be called with result SYMapInstallerResultCancelled. However, it's possible that the operation has already finished internally at the moment of the cancellation request, in this case the completion block will be called with a regular non-cancelled result.

Using Download Options

If you need to limit the download to Wi-Fi connection only or use specific network options, you can create a SYDownloadOptions object and pass it to the specific download. There are three availalbe options defined by Apple:

let options = SYDownloadOptions()
options.allowsCellularAccess = false
options.allowsConstrainedNetworkAccess = true
options.allowsExpensiveNetworkAccess = false

SYMapInstaller.shared()?.installMap(iso, options: options) { iso, result in
}