Bill, our User Experience Lead, traveled to Malmö, Sweden, in November of last year to give a couple of talks at Øredev, a conference focused on “the whole software development process.”
There are a few talks from Øredev that we’d especially like to pass along:
The first talk from Bill explores development lessons learned going from the Mac, to the iPhone, to the iPad, and back. Watch it here!
His second talk, Designing Graceful, Gracious Interfaces for iPad, has been given a few different times at the Voices That Matter conferences. You can find it over at Vimeo.
And Robby Ingebretsen, another Seattleite and Pixel Lab person, gave two awesome talks on Fonts, Form, and Function and an introduction to Design Composition.
They’re all insightful and worth throwing on to your ?tv when you have a spare hour or four.
Add all four videos to OmniFocus by tapping here.
“Wow, your customers are nerdy!”
That was a friend’s response recently when I mentioned that logarithmic axes are the number-one feature request for OmniGraphSketcher.
The way I see it, our customers understand that logarithmic scales are the best way to present many types of data and ideas. Stock prices, advances in technology, and many other phenomena tend to change by multiples rather than additions. Logarithmic scales show each doubling as a constant distance, so you can compare percent changes without large differences in absolute size getting in the way.
So I’m very excited to announce that OmniGraphSketcher 1.2 for Mac and OmniGraphSketcher 1.5 for iPad are now available, with full support for logarithmic axes!
.png)
You don’t even need to know anything about logarithms to use this feature. You just turn it on via the axis inspector, for either or both axes. (The resulting charts are sometimes called lin-log and log-log.) There is no step two!
These logarithmic axes are designed to follow best practices in information visualization, and they work seamlessly with all the other features of the app, such as dragging, nudging, snapping, sketch recognition, axis manipulation, and scale-to-fit. And because logarithmic scales are more likely to span many orders of magnitude, we now support much larger and smaller numbers (up to 10300 and down to 10-300), more decimal precision (up to 13 digits), and scientific notation (so you can use numbers like 3 x 10200 without typing 200 zeroes).
Given that the known sizes of physics only range from about 10-35 meters (the Planck distance in quantum theory) up to 1026 meters (the size of the observable universe), we figure that +/- 300 orders of magnitude should be plenty.
At least for now.
As part of these updates, we’ve also refined the algorithms that draw axis tick marks and tick labels. When there is not enough room to label every tick mark, we now consistently label every other tick mark, or every 5th, or every 10th, etc. If we skip a lot, we’ll automatically use major/minor tick marks to make it easier to see which tick marks are getting labeled.

On logarithmic axes, we show just the first five numbers between each power of ten when possible, then only the powers of ten themselves, and then evenly-spaced powers of ten. OmniGraphSketcher makes all of these decisions for you, so you never have to think about it.

And did I mention that your axis ranges don’t have to end on powers of ten? Suppose your data values fall between 8 and 200. In many charting programs, the best you can do is this:

But we think logarithmic axes should be just as flexible as linear ones, and we want you to be able to switch between linear and logarithmic scales seamlessly. Again, we’ve done the work so you can get what you’d expect:

Last but not least, we’ve added a really nifty new feature called line interpolation. As you know, OmniGraphSketcher lets you draw lines freehand even if you don’t have exact data to back them up. This is great if you have a rough idea of a trend or want to visualize several possible scenarios. But wouldn’t it be cool if you could also turn your sketched lines into sampled data points for analysis or re-plotting in another program? That’s exactly what line interpolation does. It samples at each horizontal tick mark (x-value) to convert your line into a data series.
The reason we’re introducing this at the same time as logarithmic axes is because it lets you see how the shape of a line differs in linear vs. logarithmic space. Regular lines in OmniGraphSketcher simply connect two or more data points as smoothly as possible, so intermediate values do not necessarily stay the same when you convert between linear and logarithmic scales. Line interpolation solves this by letting you anchor some of the intermediate points. Now you can easily demonstrate, for example, how a straight line in logarithmic space becomes an exponential curve in linear space:

Download the latest versions of OmniGraphSketcher from the App Store (Mac, iPad) or from our online store (Mac); or use the built-in software update to download automatically.
And let us know what you think!
(If you want all the details, check out the release notes for the Mac and iPad versions.)
At Omni we've noticed a trend that would make Sigfried and Roy jealous: it seems everyone is rushing to get their very own king-of-the-jungle. Y'see, Apple made it so easy to adopt their brand new Lion operating system—by offering it as an upgrade through the Mac App Store—that folks are clamoring to get a Simba to call their own.
Alright, pop culture references aside, it's really cool to see so many of our customers taking up a new operating system so quickly. As you can see from the following OmniGraphSketcher file, the percentage of Omni customers using Lion within the first 10 days of its release is nearly the same as those who began using Leopard within the first 100 days of its release. That's quite a curve, if you ask us.

But some of you might want to ask how we're getting these magical metrics. Well, when you update an Omni app, we get some anonymous system data (if you allow it). This helps us to see the adoption rate of new OS or hardware. It also helps us to see when there are so few users on a particular OS or hardware platform (the PPC architecture, for example) that it might be reasonable for us to stop supporting it. From this type of data we get to confirm our instincts to quickly add Lion features to our apps before the OS ships, and our customers get their favorite apps chock-full of cool Lion features faster than they might if we were just guessing at this stuff.
.png)
Though it may not be as thrilling as a Vegas show with giant ferocious cats, we think this trend is quite encouraging and hopefully you do too.
If you'll pardon the horn-tooting for a moment, we'd like to take the opportunity to flatter one of our teammates. Omni's UX lead Bill recently spoke at the Voices That Matter conference here in Seattle. At last fall's VTM: iPhone Developers conference in Philly, Bill spoke about Designing Graceful, Gracious Interfaces for iPad alongside other notable tech folk. Though he didn't have as cool of a getup as this speaker, we think he put forth a valiant effort and are perfectly comfortable with calling him a hero from time to time.
In recognition of his contributions, not only to our apps, but to the ongoing dialogue about User Experience in software development, we would like to share some of Bill's thoughts with you. For those of you who couldn't attend the conference, here's his keynote presentation (paired with some audio from one of his practice sessions). If you find this sort of stuff as inspiring as we do, perhaps we should all arrange for a party-bus to the next conference - an omnibus, if you will.
Designing Graceful, Gracious Interfaces for iPad from The Omni Group on Vimeo.
I think the Mac App Store is going to be a huge boon for Mac consumer software, and we're looking forward to publishing the same suite of Omni Group apps on the Mac App Store that we've been busy bringing to the iPad App Store: OmniFocus, OmniGraffle, OmniGraphSketcher, OmniOutliner, and OmniPlan.
The Mac App Store will be a much, much better app buying experience than any option consumers currently have: you'll be able to experiment with buying software from developers you don't know without worrying about whether they will be careful with your billing information, or whether they might even be shipping you malware. You won't have to figure out how to install the software or any of its future updates. (The standard mechanisms for distributing Mac software electronically have a poor user experience, whether they're distributed as disk images, zip files, or Installer packages.) The standards Apple will be enforcing for apps listed in the store will set a baseline for overall quality and make it less likely that apps will interfere with each other. And, of course, a central Mac App Store makes it far easier for you to find all sorts of current, supported software in the first place.
The App Store is also great from the independent developer's point of view: we don't have to figure out how to build our own online stores (or find someone else to distribute our software), or how to distribute license keys or scale up our websites and bandwidth to handle lots of downloads if we suddenly get written up by a popular reviewer. Those of us who are already established in the Mac market have already built up a lot of this infrastructure, of course, so this benefit may not be as important to us as it is to new developers. But we'll benefit from a strong, healthy, growing market for Mac apps.
And while it's new to the Mac, we know the App Store works well for consumers: we've sold tens of thousands of copies of our iOS apps in just the last few months.
Not that there aren't plenty of questions and challenges. The App Store doesn't currently have any mechanism for offering discounted pricing to certain customers, so what do we do for our OmniGraffle 5 customers who want to upgrade to OmniGraffle 6 on the App Store? Or for people who want to upgrade from Standard to Pro? (Do we even list Standard and Pro as separate apps on the store, or do we try to combine them?) How do we handle sales to organizations which want a discount for purchasing 100 licenses? How do we take care of customers who have an older system which can't run the latest version of our app, but could run an older version if we could get it to them? How do we handle trial software? Should the product pages on our website point at our own online store or the App Store—or both?
And on top of all these questions, of course, is one I've seen a lot of other developers asking: is all this worth giving Apple 30% of our revenue?
Managed hosting and payment processing are worth something, certainly, but I think the real benefit is that our software is far more likely to reach consumers who otherwise simply wouldn't see it. To date we've tried to reach consumers by placing our software in retail channels, where the split is much worse: you're lucky if you clear 50%. Not to mention that retail is completely impractical for software under $20, since there's so much overhead involved with printing boxes and CDs, warehousing them, shipping them, updating them when you ship new versions, etc. Finally, even once you've resigned yourself to the cost of getting there and you've finally made it onto retail store shelves, it turns out that the retail experience isn't great for finding software anyway—its only benefit is that it's somewhere the average consumer knows to look. (Or at least it's somewhere they used to look; but with cheap software cut out of the picture, limited shelf space, and so on, I'm guessing fewer and fewer people bother!)
But the App Store changes all that: it offers a much more efficient distribution channel, where everyone on the platform will know to look. You can easily browse around, or search for something specific. When you find something you want, you simply click "Buy Now" and the app starts downloading and adds itself on your Dock. No more futzing about with figuring out how to buy something from yet another vendor's website, tracking license keys, and so on. You just find what you want, buy it, and start using it.
That's the experience we'd like all our customers to have, and that's why we're looking forward to publishing our apps in the Mac App Store.
10/24 UPDATE: From the comments, it seems some people are assuming that we're planning to stop selling software directly, i.e. to only offer our software through the Mac App Store. Sorry if that wasn't clear: we do intend to keep selling software from our own site as well, where we're able to offer trial downloads as well as discounts for upgrades, bundles, and volume purchases. We view the Mac App Store as a great alternative to retail stores (which have all those same limitations), not as a replacement for our own site (which doesn't). (Also, to be clear, we plan to charge the same list price both on our store and in the Mac App Store, just as we charge the same list price on our store and in retail.)
Update: I've added some comments about the -[CALayer shadowPath] approach that I missed before.
I've just pushed a new sample to the OmniGroup? source on github which shows several drop shadow approaches, with an eye towards performance, particularly while the shadow casting object's frame is being animated. One of these is a pair of functions vended by OmniUI, OUIViewAddShadowEdges?() and OUIViewLayoutShadowEdges?(), which originate from writing our document picker. In this case we want to be able to smoothly animate between two apparent view sizes as you open and close documents.
Opening up OmniGroup/Frameworks/OmniUI/iPad/Examples/DropShadowOptions? and building, you'll see a set of options that control the testing:

Animation Type
-
Resize: The size of the object changes, possibly leading to it being redrawn.
-
Slide: The object simply move, which may allow optimized screen rendering by compositing a previously cached backing store.
Animation Driver
-
One-time change: Some one-time programatic change is being made to the object that has a starting and ending state. These are driven by UIView animation.
-
User interaction: This simulates multiple successive small changes, as if the user were performing a sequence of dragging touches (like resizing a shape). These are driven by a timer, simulating events from the user.
Shadow Options
-
CoreGraphics, resampled: This renders the shadow into the view's content area with normal CoreGraphics calls. When the view's size changes, the content is not redrawn, but the previously created backing store is just scaled. If the change you are making is temporary (like a small zoom in/out bounce animation), this could be imperceptible to the user. One problem with the CoreGraphics-based approaches is that some of your content area is taken up by the shadow and positioning elements within your view needs to take this into account.
-
CoreGraphics, redrawn: The shadow is drawn into the backing store, as above. But, each time the view's bounds changes, a new backing store is generated. Note however, that UIView animations only generate a backing store image for the starting and ending frame and interpolate between them. There is not a new image generated for each frame of animation. This yields good performance for one-time, animations.
-
CALayer Shadow: CALayer provides shadowing options with the shadows being cast outside your content. This makes performing other geometry calculations simpler (like positioning sublayers) since you can turn on the shadow and forget about it.
-
CALayer Shadow, rasterized: Normally, CALayer shadows are built at the time that the layer is composited to screen. That means, each time you even move a layer, the full shadow needs to be recomputed. But, CALayer provides an option to request rasterization, which is enabled in this case.
-
CALayer, shadow path:(not shown in screenshot above since this was added to the test later) This uses the shadowPath property on CALayer, which improves the performance of shadow rendering by allowing the layer to assume that the interior of the path is opaque (rather than having to convolve the alpha channel of the layer's contents). Sadly, UIView seems to disable implicit animations for this property, and you need to be careful to share or reuse CGPathRefs. With this extra bookkeeping, it is a bit of a pain to use, but nice and speedy.
-
OmniUI Shadow Edge Views: Given a UIView, these OmniUI functions add four thin views along the edges of your view. Each has a three-part stretchable image that renders the shadow. This takes advantage of the CALayer contentsCenter property to avoid even needing to re-fill the shadow edge contents on a resize. Like the CALayer approach, these views lie outside the view itself, simplifying the positioning of the content within the view (or other geometry calculations, in our case, calculating the animation parameters when opening or closing documents).
Performance
Running this app under Instruments with the Core Animation tool, we can check out the relative advantages of each approach. Instruments doesn't give a precise frame rate over time, but really we don't care. In the real world, you'd have other work to do during a live-resize of an object, so anything here less than 60fps (for just the shadowing) is likely too slow.
|
|
Resize/Once
|
Slide/Once
|
Resize/User
|
Slide/User
|
|
CoreGraphics, resampled
|
Fast
|
Fast
|
Fast
|
Fast
|
|
CoreGraphics, redrawn
|
Fast
|
Fast
|
Slowish
|
Fast
|
|
CALayer Shadow
|
Slow
|
Slow
|
Slow
|
Slow
|
|
CALayer Shadow, rasterized
|
Slow
|
Fast
|
Slow
|
Fast
|
|
CALayer, shadow path
|
Fast
|
Fast
|
Fast
|
Fast
|
|
OmniUI Shadow Edge Views
|
Fast
|
Fast
|
Fast
|
Fast
|
I'll call out the one "Slowish" result; this timed at ~45fps on my iPad. CoreGraphics shadowing is amazingly fast — nice work. Still, depending on the rest of your workload during the resize, "pretty fast" may not be good enough.
Conclusions
The default behavior of UIView/CALayer is great for simple situations. If, however, a user is dynamically interacting with an object that casts shadows and you want that object to not get blurry as it resizes, CoreGraphics may not be as fast as you'd like (obviously the rasterization or relayout of the inner content matters too).
Don't turn on CALayer shadows unless either enable rasterization on and aren't going to be changing the view's size, or are willing to do the extra bookkeeping to maintain a shadowPath. Generic CALayer shadows are stunningly slow if all you need is a rectangle, but the shadowPath hint makes them very zippy.
Finally, what does the OmniUI shadow edges approach tell us? This particular hack may not be useful in your app, but the core idea is that you shouldn't do work you don't need to do. When resizing axially aligned, opaque objects, we don't need a fully general shadowing algorithm.
We can deceive the user into thinking a real shadowed object is floating in their view, but we don't have to implement it that way. On a "magical" device like an iPad, you need to act like a magician: as long as it looks like something visually complicated is really happening, then your job as illusionist is complete.
?
We've pushed new versions of our public framework source to github, featuring a bunch of work from our iPad apps, OmniGraffle and OmniGraphSketcher. Some of the highlights include our document picker, inspectors, a bunch of controls, and a start on a CoreText-based text editor, but there is much more.
For a quick iPad example, open up OmniGroup/Frameworks/OmniUI/iPad/Examples/TextEditor and build it. The text editing bit of this sample is a little rough, but this shows off some of the document picker.
Even if you aren't interested in adopting the frameworks directly, there are a bunch of little gems that you can use. One such feature is our Objective-C runtime sanity checker (in OmniGroup/Frameworks/OmniBase/OBRuntimeCheck.m).
The runtime checker helps avoid problems with mismatched method signatures between classes and their superclasses and between classes and conformed protocols. These can creep in as ABI changes from 32- to 64-bit support is added, or as subtle shifts in API happen in the system frameworks or your own class hierarchies. Some of these clang will check for you, but not all.
For example, having built OmniBase (with the Debug configuration, which enables this checking support) we can run Xcode with checking enabled (not to pick on Xcode, lots of apps have these problems):
% OBPerformRuntimeChecksOnLoad=1 DYLD_INSERT_LIBRARIES=/Users/Shared/bungi/Source/PROJ/Products/Debug/OmniBase.framework/OmniBase /Developer/Applications/Xcode.app/Contents/MacOS/Xcode?
...
2010-05-13 13:36:10.205 Xcode[19717:903] Method scriptWorkingDirectory has conflicting type signatures between class and its superclass:
signature i16@0:8 for class XCUserScript has imp -[XCUserScript scriptWorkingDirectory] at 0x100874d40 in DevToolsInterface.framework
signature @16@0:8 for class XCUserScriptNode has imp -[XCUserScriptNode scriptWorkingDirectory] at 0x100c8f870 in DevToolsInterface.framework
?...
?
Here we can see that a method's signature has changed — in one case it is returning an integer and in the other an object. Probably not a good sign.
By default, we silence problems where both sides of the conflict are in system frameworks (since we can't do anything about them). We've logged Radars on the in-system conflicts, but if you'd like to see them, you can also define OBReportWarningsInSystemLibraries in the environment:
2010-05-13 15:19:05.652 Xcode[21608:903] Method hash has conflicting type signatures between class and its superclass:
signature I16@0:8 for class CIVector has imp -[CIVector hash] at 0x7fff81e27e84 in QuartzCore.framework
signature Q16@0:8 for class NSObject has imp -[NSObject(NSObject) hash] at 0x7fff84862e80 in CoreFoundation.framework
First things first, if you haven't already seen this incredible video, may I recommend doing so? You'll want to load it in HD full-screen mode, let it buffer, then just kick back and relax. (Via the always-awesome Kottke.)
Next up: what's Omni been doing lately? Let me break it down for you bullet-style:
• Preparing for Snow Leopard! Engineers are madly adding Snow Leopard updates to all our shipping apps, with the goal of having everything ready the day Snow Leopard is. I don't exactly know how this works, if they just . . . physically stuff an actual leopard in there, or what (aw, who's a snuggly? You are!), but they seem very busy.
• Starting sneaky peeks of OmniFocus 1.7! LOTS of changes in the works, and you can get the rundown here. Warning: running an alpha version isn't for everyone, but if you don't mind, you know, living on the edge a little, the very latest builds are being constantly made available via our sneaky peek page.
• OmniWeb 5.10 sneaky peek updating! Speaking of sneaky peeks (the more I type that term, the goofier it sounds. So SNEAKY, these peeks), the work-in-progress version of OmniWeb, 5.10, has just been updated with the latest WebKit (from Safari 4.0.3) and has several improvements to its built-in software update (including the ability to ignore a particular update).
Questions? Feedback? Cool links to share? Chat us up in the comments.
We're looking for one or two software development interns for this summer! Check out our open positions for more info on internships or full-time employment.
Out of the box, CALayer supports many animatable properties where a change will automatically create a CAAnimation for that property. But, if you look at the list of properties, it is clear that they are all of the form “stuff that OpenGL does while compositing” and have nothing to do with the inner texture representing your content. You are entirely responsible for telling CoreAnimation when your content has changed.
Recently I wanted to write a layer that had its content animate, but as far as I could determine CALayer doesn't support this out of the box, though it is really pretty close. If you add an @dynamic property to a CALayer subclass and set it, your layer will be sent -actionForKey: and the returned animation will be passed to your -addAnimation:forKey:.
The problem is that CALayer doesn't know that this change means it needs to update your contents property too, so the animation will happen but no display change will happen.
The API on CALayer really isn't sufficient for this task, so it isn't too surprising it isn't supported. Since CALayer is a generic KVC container you can squirrel away random keys/value pairs in it and use them for whatever purposes you want. But it has no way of knowing that a particular property change should provoke an update to the content, and updating the content unnecessarily would seriously hurt performance.
I've whipped up some sample code with a superclass that shows one approach to handling this.
The sample subclass looks like:
@interface WaveLayer : ContentAnimatingLayer
@property CGFloat phase, frequency, amplitude;
@end
@implementation WaveLayer
@dynamic phase, frequency, amplitude;
+ (NSSet *)keyPathsForValuesAffectingContent;
{
static NSSet *keys = nil;
if (!keys)
keys = [[NSSet alloc] initWithObjects:@"phase",
@"frequency", @"amplitude", nil];
return keys;
}
- (void)drawInContext:(CGContextRef)ctx;
{
… draw a sine wave …
}
@end
The ContentAnimatingLayer superclass provides some bookkeeping and basic support for updating the content when the content-affecting properties are changed. First, it adds support for -actionFor; like many other key-based Cocoa protocols (Radar #6372335). On top of this, it adds support for determining which properties are content-affecting with +keyPathsForValuesAffectingContent and -isContentAnimation:. Active content-affecting animations are then tracked and an update timer is fired when there is at least one such animation.
This makes it as easy as I'd hoped it would be to write content animating layers; hopefully you'll find this useful too! I've asked Apple to add this to CALayer in the future. If you'd like it too, you can reference my Radar #6372372.
The sample code is just that, my first cut at a sample. Some possible issues/improvements:
- The -actionFor support may or may not be fast enough (or really not even necessary).
- Instead of following the class-based KVO customization pattern, CALayers are often customized by the delegate. So, the NSSet-returning method should maybe be an instance method or maybe the -isContentAnimation: should be split up and call a new -layer:isContentAnimatingKey: delegate method.
- It would be nicer to tie into the normal CoreAnimation timing mechanism instead of creating a per-instance NSTimer. Even without that, it might be a bit more efficient to have a single animation timer for all instances of ContentAnimatingLayer. Also, right now the timer doesn't get registered for the event tracking runloop mode, though it maybe should.
- This approach assumes that a change to a content-affecting key requires the entire content be redrawn. This may or may not be the case for any particular layer. A change to a content-animating property might want to specify an NSRect-based animation that describes the dirty area.
Page 1 of 3 pages 1 2 3 >