SwiffCore is a Mac OS X and iOS framework that renders vector shapes and animations stored in the SWF format. It also provides basic support for bitmaps, fonts, text, and MP3 streams.
It isn't a Flash runtime. It doesn't enable you to run your interactive Flash games on iOS. It will, however, accurately render your existing vector graphics and animations.
SwiffCore is open source and freely available via a BSD-style license.
I needed a solution for Theory Lessons, the iOS version of my music theory lessons (http://www.musictheory.net/lessons). Each lesson contains several hundred frames of vector graphics and accompanying music examples.
During initial development, I explored several options:
-
Use Adobe Flash Professional's export-to-iOS feature
This creates a standalone .ipa file of your entire Flash project. I couldn't find a safe, supported way of accessing these from my app. Also, I would rather not inject a blob of Adobe-quality binary code into my product. -
Generate PNG files for each frame, in various resolutions
I had already written a SWF->PNG converter for Mac OS X . It used WebKit to load the Flash Player plug-in, seek to a specific frame, then capture the frame using the screenshot APIs). While the resulting images were accurate, they also used a large amount of space on disk (~45MB). -
Generate a movie file for each lesson
While iOS supports both the H.264 and MPEG-4 formats, neither is well suited for text and simple vector graphic content. -
Use as3swf's Shape Export to Objective-C to generate classes for each lesson.
This resulted in a very large binary size. -
Write my own shape exported into a proprietery data format, then render it
This is basically an abstraction layer on #4. At some point, the data format begins to look like SWF. Why create a new data format when I can just: -
Read the SWF file and render it myself.
Using the framework is fairly simple:
- Create an NSData instance containing your SWF file
- Load it into a SwiffMovie instance using
-[SwiffMovie initWithData:]
- Make an accompanying SwiffView instance using
-[SwiffView initWithFrame:movie:]
- Play it using the SwiffPlayhead returned by
-[SwiffView playhead]
Under the hood, SwiffCore uses Core Graphics to draw the vector shapes and static text, Core Text to draw dynamic text fields, and Core Audio to playback sounds and music. It composites all graphics into a Core Animation CALayer, contained in a UIView or NSView.
Optionally, multiple CALayers can be used (via -[SwiffPlacedObject setWantsLayer:YES]
) to reduce redrawing.
Placed objects promoted to layers also animate at a full 60fps (even when the source movie is less than 60fps).
For more information, read the SwiffCore Overview wiki article.
(outstanding issues in parentheses)
- Sprites / Movie Clips
- Shapes
- Scenes and frame labels
- Animation / Tweens (both motion and classic)
- Text fields (both dynamic and static)
- Embedded fonts
- Color effects
- Event sounds (MP3 only)
- Stream sounds (MP3 only)
- Basic layer blend modes (#7)
Ultimately, performance depends on the source movie. If SwiffCore has to redraw several objects per frame, and those frames contain gradients and/or complex paths, it's easy to saturate the CPU and drop frames (even on A5 devices). After a few migraine-inducing Instruments sessions, I am very grateful that Apple never allowed Flash on the original iPhone.
Redrawing is reduced when SwiffPlacedObject.wantsLayer
is set to YES, but memory footprint increases. Also, several moving CALayers can saturate the GPU. Ultimately, you will want to keep some movie clips in the main content layer, while promoting frequently-moving ones to their own layers.
For Theory Lessons on an iPhone 3GS, SwiffCore rendered all of my movies at a full 20fps (the original frame rate) without using wantsLayer. I then promoted specific SwiffPlacedObject instances to have their own layer (wantsLayer=YES) to create fluid 60fps animations.
I did this by running UpgradeToLayers.jsfl on my source movies. This assigns an instance name of _layer_X
(X increments) to each movie clip involved in a motion tween. At runtime, in the -swiffView:willUpdateCurrentFrame:
delegate callback, I promote these placed objects to wantsLayer=YES:
- (void) swiffView:(SwiffView *)swiffView willUpdateCurrentFrame:(SwiffFrame *)frame
{
for (SwiffPlacedObject *placedObject in [frame placedObjects]) {
NSString *name = [placedObject name];
if ([name hasPrefix:@"_layer"]) {
[placedObject setWantsLayer:YES];
[placedObject setLayerIdentifier:name];
}
}
}
An alternate approach would be to promote movie clips that have "Cache as bitmap" checked:
- (void) swiffView:(SwiffView *)swiffView willUpdateCurrentFrame:(SwiffFrame *)frame
{
for (SwiffPlacedObject *placedObject in [frame placedObjects]) {
[placedObject setWantsLayer:[placedObject cachesAsBitmap]];
}
}
Here are some resources that were helpful during SwiffCore development:
- SWF File Format Specification (version 10, PDF)
- Converting Flash Shapes to WPF - An article on SWF shape parsing
- Hacking SWF ... Shapes - Another article on SWF shape parsing
- Adobe Flex SDK Source - Includes a .swf parsing implementation in Java
- as3swf - A .swf parsing implementation in ActionScript
- gameswf - An older public domain .swf parser in C++