SVG is an awesome image format thats widely used, works in all browsers. SVG graphics make better apps and better games – and automatically “upgrade” themselves for future devices.
This post explains how you can use SVGKit – the open-source SVG implementation for iOS/OS X in your own apps
(NB: basic installation is covered on the GitHub page – this is about how you *use* it)
SVGKit is like Apple’s UIKit
Originally, SVGKit was a bit tricky to use. I re-architected it to be an exact clone of Apple’s UIKit – same classes, same class structure, same method names.
So, we have:
|UI*||SVGK*||Apple’s standard: library names start with abbreviation of the library name|
|UIImage||SVGKImage||SVGKImage.h started as a copy/paste of UIImage.h|
|UIImageView||SVGKImageView||You cannot instantiate it directly, have to use a subclass|
|SVGKFastImageView||Renders your SVG as a single layer, fast.|
|SVGKLayeredImageView||Renders your SVG one CALayer per SVG element; you can hilight individual elements, tap them, animatet hem, etc|
(a side note: We couldn’t use a classname prefix of “SVG” because the SVG spec reserves all classnames beginning “SVG”. Apple had a similar problem when they invented GLKit – the prefix “GL” was already used in the OpenGL library they were extending, so they named their classes “GLK” prefix. Hence … “SVGK”)
Loading an image
You already know how to load a UIImage, the “easy way”:
UIImage* newImage = [UIImage imageNamed:@"myImage.png"];
Well, SVGKit is … the same:
SVGKImage* newImage = [SVGKImage imageNamed:@"myImage.svg"];
Displaying an image: UIImageView
And how do you create a UIImageView?
UIImageView* imageView = [[UIImageView alloc] initWithImage:newImage];
…so, for SVGKit:
SVGKImageView* imageView = [[SVGKFastImageView alloc] initWithImage:newImage]; // ..... or: SVGKImageView* imageView = [[SVGKLayeredImageView alloc] initWithImage:newImage];
Wait – why is this different?
The reference in both cases is an SVGKImageView – but you cannot alloc-init that class directly. Why?
Different people use the library in different ways. Some people need performance, others need detailed aniamtion. SVG’s contain a lot of bonus info, and it’s not possible to support every use case with a single class. So, you get to choose which one fits your needs
So far, so easy. What about when you want more control? And how do you load an SVG over the internet (can you load an SVG directly from the web?)
Time to delve a little deeper…
Loading an SVG: in detail
There are four steps to loading an SVG (all of which are done automatically by SVGKImage above):
- SVGKSource: specify a location (a file, a filename – or an HTTP URL)
- SVGKParser: parse the file (all SVG’s are XML files, so your file can contain any custom XML you want)
- SVG classes: convert to SVG’s custom classes (from the SVG Spec); includes automatic support for CSS styling, cascades, etc
- SVGKImage: export the SVG to something Apple can render: e.g. a flat CALayer, or a hierarchy of CALayers, etc
- Experimental Exporter that saves a .svg file from your SVG file, including any changes you made to the in-memory DOM
Every stage is extensible, so you can either add features, or custommize it
Extend SVGKSource: adding new sources
Easy: create a new subclass of SVGKSource, and implement the two methods.
The method signatures are a little strange, because they have to support fast file-access (a C native library from Apple) as well as modern Objective-C methods.
One example of each: the supplied SVGKSource for loading from disk uses C methods, and the supplied one for loading from a URL uses Objective-C methods.
Extend SVGKParser: custom SVG files and formats
NOTE: do NOT subclass SVGKParser; parsing is very complex, and there’s a special mechanism that makes it easy to extend the parser (read on…)
Parsing is complex, so our parser is modular (based on ideas from a modular XML parser I wrote years ago). The SVGKParser class doesn’t parse SVG directly: instead, it parses raw XML, and converts it to a higher-level version that’s much faster to work with. It also manages error handling, loading bytes from an SVGKSource, etc.
You create a new parser instance with a helper method that includes all the modules you need by default:
SVGKParser* parser = [SVGKParser parserWithDefaultParseExtensions];
SVGKParserExtension: adding your own custom parsing
Our SVG implementation is already split into sub-modules. This makes the code easier to maintain – and it makes it much easier to add support for SVG features one-by-one, in parallel with other developers.
- SVGKParserSVG: parses “Basic” SVG – obvious things that require no special handling. NB: this is the most complex parser extension we have. Most are much simpler
- SVGKParserGradients: parses SVG Gradients – because these were added later by a different developer (Stich) … likewise, if you’re adding a msising SVG feature, feel free to make a new parser-extension for it, it’s easier!
- SVGKParserDefsAndUse: the SVG <defs> and <use> tags are much harder to parse than 99% of SVG, since they require complex cross-referencing and instancing. So, we use a separate parser extension to isolate this code.
For one of my games, I wanted to store gameplay data inside the SVG – attach info to particular SVG tags (e.g. give each SVG path a “bonusPoints” attribute).
To do this in a spec-compliant way, you should:
- Create a new XML namespace (requires no code: you simply invent a URL on a domain that you own)
- Put the namespace in your SVG file, using an <xmlns> tag, and give it a “prefix”, e.g. “my-game”
- Everywhere you want your custom attributes, or custom tags, prefix them, e.g.: “
- Write an SVGKParserExtension, and in the “supportedTags” and “supportedNamespaces” methods, return ONLY your namespace, and either nil, or the set of tags you’ve invented
Once the file is parsed, use the DOM to fetch your data (by definition, SVG parsers are required to be DOM-compliant parsers too)
NodeList* myCustomNodes = [svgImage.DOMDocument getElementsByTagNameNS:@"http://my.custom.namespace" localName:@"my-custom-tag"];
(NB: NodeList is defined by the DOM, and is just a wrapper for an NSArray. If you #import “NodeList+Mutable.h”, you can access the NSArray directly, and use ObjectiveC fast enumeration. This is cheating, it’s not in the core SVG spec, which is why it’s hidden inside a separate header file)
Extend SVG Classes: the core SVG Spec
(the only valid reason for doing this is if you find features in the spec that are missing or broken in SVGKit. All other usages, you should be able to do in some other way, more easily, with less risk of your changes being broken when we upgrade SVGKit)
Extend SVGKImage export: export your SVG into custom rendering or to a new file on disk
All the parsed data for the SVGKImage is available to you directly, for exporting:
- (SVGKImage* image).DOMTree : this property contains the entire parsed SVG-DOM (all the SVG* classes)
- (SVGKImage* image).CALayerTree : this property contains the SVG converted into Apple’s CALayer classes, renderable directly in OS X and iOS
- Additionally: “SVGKImage+CGContext.h” has methods to render the SVG into any CGContextRef you supply (or will autocreate one for you)
- SVGKExporterNSData : creates NSData raw bytes, efficient and fast (good for OpenGL)
- SVGKExporterUIImage : creates a UIImage instance, which you can use anywhere in UIKit
To save memory and CPU, the “CALayerTree” property isn’t created until you request it; if you don’t want the CALayer’s, they’ll never get created.
Improving SVGKit, fixing bugs, and ultra-advanced development
But SVGKit isn’t perfect: there are still missing features, bugs, etc.\
If you find a bug but can’t understand / fix it, please create a simple-as-possible SVG file and send it to us (create an Issue on the GitHub page, and include a link to the file). If you give us permission, we’ll add it to the suite of “test” images we use to verify each version of SVGKit, and that way it will always work, even with future versions.
If you fix bugs, or have bits you want to fix, please have a look at the SVGKit Development guide.
If you want support or help with SVGKit, the very best thing to do is create an Issue on the SVGKit page. That way, any of the contributors can see your problem and help you out. If you email me directly for help, you’re less likely to get a response (I’m busy, and I work on SVGKit entirely unpaid).