Categories
programming

SVGKit 2013 – Recipes

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 gives some simple 1-line / few lines of code recipes for using some of the main features of SVGKit – SVG implementation for iOS/OS X.

NOTE: this post refers to the 1.0.x version of SVGKit

Basic usage / installation

Install instructions are on the main GitHub page.

Basic usage / first-time usage info is on SVGKit 2013 – Usage.

Recipes

Load an SVG file like loading a PNG file

[objc]
SVGKImage* newImage = [SVGKImage imageNamed:@"imagename"];
[/objc]

Display an SVG file on-screen using an ImageView

[objc]
[self.view addSubView: [[SVGKFastImageView alloc] initWithImage:newImage];
[/objc]

…or an ImageView that supports CoreAnimation for every element

NB: the “Fast” imageview above saves everything as a single layer. Good for rendering, but bad for interaction. The “Layered” imageview here allows you to tap on any layer, rotate/animate/fade/drop-shadow/glow any element, etc.

[objc]
[self.view addSubView: [[SVGKLayeredImageView alloc] initWithImage:newImage];
[/objc]

Use a URL to load an SVG (open an SVG file *directly* from the web)

[objc]
// Splitting URL to multiple lines to make blogpost
// easier to read…
NSString* longURL = [NSString stringWithFormat:@"%@%@%@",
@"http://upload.wikimedia.org/",
@"wikipedia/commons/f/fd/",
@"Ghostscript_Tiger.svg";
NSURL* url = [NSURL urlWithString:longURL];
SVGKImage* newImage = [SVGKImage imageWithContentsOfURL:url];
[/objc]

Open an SVG from a custom source (maybe an in-memory SVG creation method)

First, create a class that conforms to SVGKSourceReader, i.e.:

[objc]
@interface MyNewClass : NSObject <SVGKSourceReader>

[/objc]

…then subclass SVGKSource, and over-ride the method:

[objc]
-(NSObject<SVGKSourceReader>*) newReader:(NSError**) error
[/objc]

Finally, use your customized SVGKSource to load your SVG:

[objc]
SVGKSource* myCustomSource = [[[MyCustomClass alloc] init] newReader:nil];

/** Other examples, using the default SVGKSource class:
SVGKSource* urlSource = [SVGKSource sourceFromURL: [NSURL …];
SVGKSource* fileSource = [SVGKSource sourceFromFilename: @"monkey.svg"];
*/

SVGKImage* newImage = [SVGKImage imageWithSource:myCustomSource];
[/objc]

Search an SVG file for particular tags / nodes / elements

SVG is an XML file, containing XML tags/nodes such as: “<svg>”, “<g>”, “<path>”, “<linearGradient>”, etc.

[objc]
NSString* tagToFind = @"linearGradient";
NodeList* result = [svgImage.DOMDocument getElementsByTagName:tagToFind];

for( Element* domElement in result )
{
// You can use the Element object directly:
domElement.tagName;

// …or, if it was parsed by the SVG parser, you can convert it to an SVGElement:
SVGElement* svgElement = (SVGElement*) domElement;

}
[/objc]

Search for sub-tags / sub-nodes of a particular tag/node

If you already have an SVGElement reference, you can search all its descendants:

[objc]
// To search the entire document, use:

// SVGElement* startPoint = newImage.DOMTree;
SVGElement* startPoint = … // from your code

NSString* tagToFind = @"linearGradient";
NodeList* result = [startPoint getElementsByTagName:tagToFind];

for( Element* domElement in result )
{
SVGElement* svgElement = (SVGElement*) domElement; // if your tags are all supported by SVGKit, they will be converted to SVGElement instances

}
[/objc]

Get a list of ALL descendant tags (from a particular node down)

[objc]
SVGElement* startPoint = … // e.g. for whole SVG doc, use: newImage.DOMDocument;
NodeList* allElements = [startPoint getElementsByTagName:@"*"];
[/objc]

Render one small part of the SVG file on its own

E.g. if you want to display a subset of the SVG, or want to export a single element:
[objc]
NSString* idInSVGFile = … // assuming your SVG file has an "id" attribute for this node
CALayer* absoluteLayer = [newImage newCopyPositionedAbsoluteLayerWithIdentifier:isInSVGFile];

// NB: "absoluteLayer" is now positioned in absolute space;
// if you add it to your window using e.g.:
[self.view.layer addSublayer: absoluteLayer];
// …it will appear in the same place as it appeared before,
// keeping all the offsets, rotations, etc
[/objc]

NOTE: there are several “newCopy…” methods, and each has different effects and outcomes. Read the method docs for each to decide which one you want to use. In a future version of SVGKit, we’d like to move these methods into a class of their own, and make it easier to see which one does what

Customise the parsing, using your own parser extensions

Create your custom class that adheres to SVGKParserExtension:
[objc]
@interface MyCustomSVGParserExtension : NSObject <SVGKParserExtension>

[/objc]
Then create a parser, INCLUDE THE DEFAULT extensions, and add your one on the end:
[objc]
MyCustomSVGParserExtension* myCustomExtension = [[MyCustomSVGParserExtension alloc] init];

SVGKParser* parser = [[SVGKParser alloc] init];
[parser addDefaultSVGParserExtensions]; // HIGHLY RECOMMENDED
[parser addParserExtension:myCustomExtension];

SVGKParseResult* result = [parser parseSynchronously];

SVGKImage* newImage = [[SVGKImage alloc] initWithParsedSVG:result];
[/objc]

Get help on why parsing failed (and warnings and line numbers!)

[objc]
SVGKImage* newImage = … // use methods above

// EITHER: parse using default parser:
SVGKParseResult* parseResult1 = newImage.parseErrorsAndWarnings; // this is a convenience pointer to (SVGKParser*).currentParseRun

// OR: use a custom parser:
SVGKParser* parser = … // use methods above
SVGKParseResult* parseResult2 = parser.currentParseRun;
[/objc]

And then you have the following info:
[objc]
/* array of NSError objects, each one a "WARNING" from the parser */
parseResult.warnings;

/* array of NSError objects, each one a "FATAL ERROR" from the parser – if your SVG didn’t render at all, this is why! */
parseResult.errorsFatal;

/* array of NSError objects, each one a "RECOVERABLE ERROR" from the parser – if your SVG didn’t render correctly, this is why! (although you probably still got to see something) */
parseResult.errorsRecoverable;
[/objc]

UPDATE: more Recipes!

For more recipes, see SVGKit Recipes part 2

(this covers answers to some of the common questions asked in the Comments below)

37 replies on “SVGKit 2013 – Recipes”

I want to extract UIBezierPath from SVG. I noticed that the earlier project had a SVGPathView class that processes touches event to handle a closed path on touch. Can somebody guide me on to extract the closed path/s from SVG and detect one it is touched?

Touch is now built-in, using Apple’s code. Call Apple’s hitTest etc methods on the CALayerTree.

If it doesnt work, re-read Apple’s docs – they are easy to misread.

Thanks for asking forthis one – it should hae a recipe of ots own. I’ll add one in future.

Could you help with the buit-in touch functionality.
I would really appreciate any help with trying to understand how to make a path from SVG detectable.

Thanks

If you’re having trouble, look in the demo project that’s included – “Demo-iOS” – it has detailed examples with full source.

Hi,

I want to change some shapes after rendering. For example if i click a shape i want it to become red.
I tried to use style.cssText but the changes don’t get propagated into the image.
Is this the proper way to change and rendered image using DOM?

Thanks

@John

CURRENTLY:
1. make your change to the CSS
2. call “CALayer * myNewRootLayer = [SVGKImage newCALayerTree]” = this will re-generate the entire image using the current CSS etc
3. …do something with “myNewRootLayer” (e.g. add it to an on-screen view: [(UIView*).layer addSublayer:myNewRootLayer];)

IN FUTURE:
– … I’d like to add a method to SVGKLayeredImageView and/or SVGKFastImageView that “refreshes” from the (changed) in-memory DOM.

Hi Adam,

Thanks for your answer.
I was digging into the sources.. and i found this solution:
i take the element that i want to change (i’m using SVGKLayeredImageView because i want to “detect” hit element), i modify the style.cssText
– then i generate a new layer from that element
– add this newly created layer to the parent of the hit layer
– remove the hit layer.
I know it’s kind of clumsy and “layer consuming” but it works :)

Maybe it will be useful to have a method on SVGPathElement that will regenerate the content of the layer reusing exiting one, avoiding recreation of the layers.

John

I don’t think that’s clumsy – I think that’s a very good way of doing it.

Re: re-using layers – Apple makes this way more difficult than it ought to be (I think: because they stopped updating / supporting their Quartz / CoreAnimation systems about 5 years ago, judging by how little the API’s have been changed, and the bugs that have gone unfixed).

Some changes are possible, but … in most cases it would be the same as *or slower than* the technique you’ve used. TECHNICALLY THIS SHOULD NOT BE THE CASE, but Apple hasn’t exposed the API calls we’d need to do this “the right way” – it’s all “secret” and “Apple only”, and so … only Apple can do this efficiently :(.

Hi Adam,

I was looking at the SVGKLayer dealloc and a saw your comment regarding the crash.
After some digging i observed that the dealloc-ated instance is not the one initialized by [init], but one initialized with -(id)initWithLayer:(id)layer. If i register the observer there everything works ok. i don’t know why the initWithLayer method get’s called, probable is something “internal” to CA.

John

Ah!

Initwithlayer is called implicitly by apple if you do implicit CALayer animations

(Which happens auotmatically a lot)

Hey,

I have a SCGKImage displaying correctly, but I want to change the graphical effects on it, preferably with core graphics. Changing the fill color would be the first step, so I know what I’m doing from there on. How can I get core graphics to work with my vector shape?

The image’s “.CALayerTree” is the raw CALayers for your SVG image.

You can use anything from CoreAnimation on them.

CoreGraphics is incompatible, but you can write a CALayer that uses CoreGraphics if you like

Is there code you could provide to get it to the point where I can customise it using core graphics? Been trying this all yesterday but without any luck.

Sorry, i dont really understand why you want to use CG. Thats a low level library for bitmap data and rasters.

All i can suggest is to read apples user guides on CG and COreAnimation – they are very detailed and clear on all this.

SVGkit is almost entirely standard in this respect – it does almost zero deviation from core CA and CG

Basically, I want to do things such as add gradients and drop shadows and other effects to my SVG’s. Basically use their path instead of drawing each one by code inside core graphics.

Hi!
I have the same as John mentioned before, which is “I want to change some shapes after rendering. For example if i click a shape i want it to become red.”
But as a new programmer of iOS, could you give more specific answer about this?
Thank you!

Hi!
Thanks for your last answer !
I change one tag’s fill attribute and it works, but I wonder how could it be shown on the screen ?
I take the steps you have provided for John, but I can not get what I want , can you give me more direction?

NSString* tagToFind = @”LWPOLYLINE”;
Element * element = [buildingImage.DOMDocument getElementById:tagToFind];
NodeList *elementList = [element getElementsByTagName:@”rect”];
for( Element* domElement in elementList )
{
SVGElement* svgElement = (SVGElement*) domElement;
NSLog(@”before change:%@”,[svgElement getAttributeNode:@”fill”].nodeValue);
[[svgElement getAttributeNode:@”fill”] setNodeValue:@”#000000″];
NSLog(@”after change:%@”,[svgElement getAttributeNode:@”fill”].nodeValue);
}

CALayer * myNewRootLayer = [buildingImage newCALayerTree];

[[self.view layer] addSublayer:myNewRootLayer];

That might be a bug for the “fill” attribute, sorry. DOM attributes are a recent addition, so they may have bugs that haven’t been discovered yet.

In many cases, it’s easier to leave the DOM alone, and just edit the CALayer objects directly. For each SVGElement there is a CALayer object in the “newCALayerTree” that should have the same ID. If you look at the demo project, and search the source code for “monkey”, the bit of code that animates the monkey shows how to find a specific CALayer, I think

Hi Adam!
I have encountered some problems while using SVGKit for a map in GIS.
I have a SVGKLayeredImage displayed correctly. Now I want to edit the CALayer objects directly , as you mentioned before, but CALayer frame is a rectangle while what I need to edit probably is a polygon, how can I do this while using CALayer?
Thanks!

Cast it to CAShapeLayer, which has a .path property. Thats a CGPath with all the points, lines, curves.

Apple’s CGPath cant be edited directly – you have to write your own code to clone it / convert it to a sensible format. I’ve got code for that but its commercial/private , not part of SVGKit.

Not sure if this is a work around for Kevin’s question regarding changing the fill color, but I was running into a similar issue, and ended up using a mask:

SVGKImage *corn = [SVGKImage imageNamed:@”Corn.svg”];
CALayer *layer = [corn layerWithIdentifier:@”Layer_1″];

UIView *viewToMask = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
viewToMask.layer.backgroundColor = [UIColor darkGrayColor].CGColor;
viewToMask.layer.mask = layer;
[self.view addSubview:viewToMask];

I tried changing the “fillColor” of the corn’s CALayer, but only CAShapeLayer has this attribute.

Is there a better way of changing the fill color for the image without using the mask?

@Yehuda

I’m not sure what you’re trying to achieve here. If a layer is not CAShapeLayer, then it has no “shape” and cannot be filled anyway – setting a fill colour would be impossible. Have you checked what class your CALayer actually is ? CALayer is merely the “root” class of all CA…something…Layer classes.

More generally … the answer to all these problems is “read the Apple docs”. Apple’s CoreAnimation library (c.f. the docs links I’ve already posted) has extensive docs and features on pretty much everything you could ever want to do.

Adam,
can you share a simple code to change the color of a SVG element like a Path or Circle after the user click on it, it’s not easy for me to understand how to do it from the samples…
After I get the CALayer from the Hit Test how can I get a reference to the selected SVG element?
Thanks

Hi Adam,

Can you please tell me how can I change the “textContent” of any Element. Right now it is readonly property. Is there any way to change this text in SVG file.

Here is my sample code :
SVGKParser* parser = [SVGKImage imageAsynchronouslyNamed:[name stringByAppendingPathExtension:@”svg”]
onCompletion:^(SVGKImage *svgImage)
{
dispatch_async(dispatch_get_main_queue(), ^{

Element *element = [svgImage.DOMDocument getElementById:@”UserID”];
NSLog(@”Result = %@”,element.textContent); // I need to change this text content over here before displaying SVG Image
});
}];

@Shardul – if you read the DOM spec, you’ll see that nodeValue is settable directly. This works fine if your Element is a Text node with no children.

Otherwise … changing textContent is a complex feature, requires you to write a custom setter (in Node.m) to support it. It doesn’t simply “change some text”: c.f. http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#Node3-textContent

IMHO, edit Node.m, add a setter for the property, and implement it as they describe (adding, removing/replacing child Nodes).

in IOS,I want to load a svg file ,and edit the svg document ,add some “text” tag. how can i do?
this is my code,but it not display.
SVGTextElement *element = [[SVGTextElement alloc ] initType:DOMNodeType_ELEMENT_NODE name:@”text” inNamespace:@”http://www.w3.org/2000/svg”];

NSMutableDictionary *attributeDic = [[NSMutableDictionary alloc] initWithCapacity:0];
[attributeDic setObject:@”50″ forKey:@”x”];
[attributeDic setObject:@”50″ forKey:@”y”];
[attributeDic setObject:@”#000000″ forKey:@”fill”];
[attributeDic setObject:@”50″ forKey:@”font-size”];
Text *text = [[Text alloc] initType:DOMNodeType_TEXT_NODE name:@”text” value:@”123123″];
[element appendChild:text];
// element.x = 100;
// element.y = 40;
[docSVG.rootElement appendChild:element];

I can’t set the x,y property.

I don’t know how to change the SVG via DOM, sorry. Possibly some methods are missing? You might have to add methods to write to the DOM.

But … in your code, you never do anything with “attributeDic” ?

I want to set textelement’s x,y property use below code,but it’s running wrong! why?
[element setAttribute:@”x” value:@”50″];

Hello there, i’m making a demo create SVG file, edit and re-write it. So i just want to ask you do is it possible or not. Btw, do the SVGKit supported those method?
Thank you for taking your time to answer.

Hi Adam,

Thanks for you quick respone. I’ve read that article >3 times. But i’m still confuse about write svg to disk when we edited. CreAte svg file, btw!

Best regard,
Bao

Hi Adam,

I’m trying to render .svgz in our ios app.
I’m facing few issues.

Code :

dispatch_async(kBgQueue, ^{
NSURL *url = [NSURL URLWithString:coursesDict[@”image_url”]];
SVGKImage* svgImage = [SVGKImage imageWithContentsOfURL:url];

NSLog(@”newImage : %@”,svgImage);

if (svgImage) {
dispatch_async(dispatch_get_main_queue(), ^{

cell.svgImageView.image = svgImage;

});
}
});

Error : *** Terminating app due to uncaught exception ‘NSRangeException’, reason: ‘Cannot remove an observer for the key path “DomTree.viewport” from because it is not registered as an observer.’

1) I’m loading data from url and rendering in SVGKFastImageView using SVGKimage but i get this error . Some of the svg images get rendered.
2) When i call svg from my local project, i don’t get these issues.

Also, how can i add width attribute to my svg xml .
Code :

SVGKImage* newImage = [SVGKImage imageNamed:@”android.svg”];
NSString* tagToFind = @”svg”;
NodeList* result = [newImage.DOMDocument getElementsByTagName:tagToFind];

for( Element* domElement in result )
{
SVGElement* svgElement = (SVGElement*) domElement;
[[svgElement getAttributeNode:@”width”] setNodeValue:@”200px”];
}

cell.svgImageView.image = newImage;

this works only if i have a “width” attribute predefined.
I have to add that attribute.

Will be great if anyone can help out with this.

thanks,
Nilabh

There is an entire support forum (“Issues”) for that project. Use it. I will not respond to general support queries here.

Comments are closed.