Apple introduced the Swift programming language in 2014. It has now largely replaced Objective C to become the language of choice for iOS applications. However, CartoType's iOS API is written in Objective C. This article explains how to use the CartoType iOS SDK in a Swift project. You can use CartoType completely from Swift. There is no need to write any Objective C.
Create a Swift project
You'll need to install Xcode 7 on your Macintosh. All versions of Xcode from version 6 onwards support Swift; the following example was created using Xcode 7. Create a project in the normal way, and choose Swift as the language.
Add the CartoType framework to the project
Download the latest evaluation SDK. Double-click on the .dmg file containing the CartoType framework, which mounts the .dmg file as a drive. Drag the file CartoType.framework into your project, and, when prompted, choose to copy the files.
Create a bridging header
You need to create a header file to import all the Objective C headers you need. This is very easy. In Xcode, add a new Objective C file to your file (call it anything you like) and when Xcode asks whether you want to create a bridging header, say yes. Xcode will create a file called <project-name>-Bridging-Header.h, with nothing but some comments at the top. After the comments, insert this text:
#import <CartoType/CartoType.h>
That's everything you need to do to make CartoType available to your Swift code!
What the CartoType API looks like in Swift
Xcode automatically translates the CartoType Objective C API into Swift. For example, the init function for CartoTypeFramework changes from this Objective C declaration
-(id)initWithParam:(CartoTypeFrameworkParam*)aParam;
to this Swift version
public init!(param aParam: CartoTypeFrameworkParam!)
and the setView function, which is declared like this in Objective C
-(CTResult)setView:(CartoTypeRect)aView coordType:(CartoTypeCoordType)aCoordType margin:(int)aMarginInPixels minScale:(int)aMinScaleDenominator;
becomes
public func setView(aView: CartoTypeRect, coordType aCoordType: CartoTypeCoordType, margin aMarginInPixels: Int32, minScale aMinScaleDenominator: Int32) -> CTResult
If you want to examine the Swift version of the API, it's very easy in Xcode. Open one of the CartoType framework headers in the Xcode editor, for example CartoTypeFramework.h, which is the main one. Click on the intersecting-circles icon in the top right corner to open the 'assistant editor' pane. At the top, select Counterparts->CartoTypeFramework.h (Interface). You should see the whole header file, converted into Swfit: this is the interface that's available to you in your Swift code.
Sample code: a UIView class
The following sample code is a simple UIView class for displaying CartoType maps:
//
// CartoTypeDemoView.swift
// CartoTypeSwiftDemo
//
import UIKit
class CartoTypeDemoView : UIView
{
override init(frame aFrame: CGRect)
{
super.init(frame: aFrame);
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder);
}
override func drawRect(rect: CGRect)
{
let c = UIGraphicsGetCurrentContext();
let h = bounds.size.height;
// Apply the transform representing current interactive panning, rotation and zooming.
if (m_scale != 1 || m_rotation != 0)
{
CGContextTranslateCTM(c,m_current_point.x,m_current_point.y);
CGContextScaleCTM(c,m_scale,m_scale);
CGContextRotateCTM(c,m_rotation);
CGContextTranslateCTM(c,-m_current_point.x,-m_current_point.y);
}
CGContextTranslateCTM(c,m_offset.x,m_offset.y);
// Reflect the coordinate system vertically.
CGContextScaleCTM(c,1,-1);
CGContextTranslateCTM(c,0,-h);
if (m_map_image != nil)
{
CGContextDrawImage(c, rect,m_map_image);
}
}
var m_offset : CGPoint = CGPoint(x:0, y:0);
var m_scale : CGFloat = 1;
var m_rotation : CGFloat = 0;
var m_current_point : CGPoint = CGPoint(x:0, y:0);
internal var m_map_image : CGImageRef? ;
}
Sample code: a UIViewController class
This code loads a map, draws it and handles gestures for panning, zooming, rotation and (by means of tapping on two successive locations) creating a route.
//
// ViewController.swift
// CartoTypeSwiftDemo
//
import UIKit
class ViewController: UIViewController, UIGestureRecognizerDelegate
{
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder);
}
override func viewDidLoad()
{
super.viewDidLoad();
let view = m_view!;
// Create a pan gesture recognizer.
let my_pan_recognizer = UIPanGestureRecognizer(target: self,action: #selector(ViewController.handlePanGesture(_:)));
my_pan_recognizer.delegate = self;
view.addGestureRecognizer(my_pan_recognizer);
// Create a pinch gesture recognizer.
let my_pinch_recognizer = UIPinchGestureRecognizer(target: self,action: #selector(ViewController.handlePinchGesture(_:)));
my_pinch_recognizer.delegate = self;
view.addGestureRecognizer(my_pinch_recognizer);
// Create a rotation gesture recognizer.
let my_rotation_recognizer = UIRotationGestureRecognizer(target: self,action: #selector(ViewController.handleRotationGesture(_:)));
my_rotation_recognizer.delegate = self;
view.addGestureRecognizer(my_rotation_recognizer);
// Create a tap gesture recognizer.
let my_tap_recognizer = UITapGestureRecognizer(target: self,action: #selector(ViewController.handleTapGesture(_:)));
my_tap_recognizer.delegate = self;
view.addGestureRecognizer(my_tap_recognizer);
// Create the framework object.
let width = view.frame.size.width * m_ui_scale;
let height = view.frame.size.height * m_ui_scale;
let param = CartoTypeFrameworkParam();
param.mapFileName = "santa-cruz";
param.styleSheetFileName = "standard";
param.fontFileName = "DejaVuSans";
param.viewWidth = Int32(width);
param.viewHeight = Int32(height);
m_framework = CartoTypeFramework(param: param);
// Draw the first map.
view.m_map_image = m_framework!.getMapBitmap().takeUnretainedValue();
view.m_scale = 1;
view.setNeedsDisplay();
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer)->Bool
{
return true;
}
func RadiansToDegrees(aRadians:Double)->Double { return aRadians / M_PI * 180.0; }
func setRouteEnd(aPoint: CGPoint)
{
m_route_start = m_route_end;
m_route_end.x = Double(aPoint.x * m_ui_scale);
m_route_end.y = Double(aPoint.y * m_ui_scale);
m_framework?.convertPoint(&m_route_end, from: ScreenCoordType, to: DegreeCoordType);
if (m_route_start.x != 0 && m_route_start.y != 0)
{
let error = m_framework?.startNavigationFrom(m_route_start, startCoordType: DegreeCoordType, to: m_route_end, endCoordType: DegreeCoordType);
if (error == 0)
{
m_view!.m_map_image = m_framework!.getMapBitmap().takeUnretainedValue();
m_view!.setNeedsDisplay();
}
}
}
func acceptGesture()
{
let framework = m_framework!;
let view = m_view!;
framework.panX(Int32(-view.m_offset.x * m_ui_scale), andY: Int32(-view.m_offset.y * m_ui_scale));
if (view.m_scale != 1 || view.m_rotation != 0)
{
let w = view.bounds.size.width;
let h = view.bounds.size.height;
let cx = view.bounds.origin.x + w / 2;
let cy = view.bounds.origin.y + h / 2;
let x = Int32((cx - view.m_current_point.x) * m_ui_scale);
let y = Int32((cy - view.m_current_point.y) * m_ui_scale);
framework.panX(-x, andY:-y);
framework.zoom(Double(view.m_scale));
framework.rotate(RadiansToDegrees(Double(view.m_rotation)));
framework.panX(x, andY:y);
}
view.m_offset = CGPointZero;
view.m_scale = 1;
view.m_rotation = 0;
view.m_current_point = CGPointZero;
view.m_map_image = m_framework!.getMapBitmap().takeUnretainedValue();
view.setNeedsDisplay();
}
func rejectGesture()
{
let view = m_view!;
view.m_offset = CGPointZero;
view.m_scale = 1;
view.m_rotation = 0;
view.m_current_point = CGPointZero;
view.setNeedsDisplay();
}
func handlePanGesture(aRecognizer:UIPanGestureRecognizer)
{
let view = m_view!;
if (aRecognizer.state == UIGestureRecognizerState.Changed)
{
view.m_offset = aRecognizer.translationInView(nil);
view.m_current_point = aRecognizer.locationInView(nil);
view.setNeedsDisplay();
}
else if (aRecognizer.state == UIGestureRecognizerState.Recognized)
{
acceptGesture();
aRecognizer.setTranslation(CGPointZero, inView: nil);
}
else if (aRecognizer.state == UIGestureRecognizerState.Cancelled)
{
rejectGesture();
}
}
func handlePinchGesture(aRecognizer:UIPinchGestureRecognizer)
{
let view = m_view!;
if (aRecognizer.state == UIGestureRecognizerState.Changed)
{
view.m_scale = aRecognizer.scale;
view.m_current_point = aRecognizer.locationInView(nil);
view.setNeedsDisplay();
}
else if (aRecognizer.state == UIGestureRecognizerState.Recognized)
{
acceptGesture();
aRecognizer.scale = 1;
}
else if (aRecognizer.state == UIGestureRecognizerState.Cancelled)
{
rejectGesture();
}
}
func handleRotationGesture(aRecognizer:UIRotationGestureRecognizer)
{
let view = m_view!;
if (aRecognizer.state == UIGestureRecognizerState.Changed)
{
view.m_rotation = aRecognizer.rotation;
view.m_current_point = aRecognizer.locationInView(nil);
view.setNeedsDisplay();
}
else if (aRecognizer.state == UIGestureRecognizerState.Recognized)
{
acceptGesture();
aRecognizer.rotation = 0;
}
else if (aRecognizer.state == UIGestureRecognizerState.Cancelled)
{
rejectGesture();
}
}
func handleTapGesture(aRecognizer:UITapGestureRecognizer)
{
if (aRecognizer.state == UIGestureRecognizerState.Recognized)
{
setRouteEnd(aRecognizer.locationInView(nil));
}
}
var m_framework: CartoTypeFramework? ;
var m_route_start = CartoTypePoint(x:0, y:0);
var m_route_end = CartoTypePoint(x:0, y:0);
var m_ui_scale: CGFloat = UIScreen.mainScreen().scale;
// MARK: Properties
@IBOutlet weak var m_view: CartoTypeDemoView! ;
}