Looking for our open source frameworks?

Ken Case Joins 58keys to Talk About OmniOutliner

by Omni on December 22, 2020

Ken Case (our CEO) was invited onto the 58Keys podcast to discuss how he uses OmniOutliner for writing with William Gallagher, a bona fide writer’s writer, and Deputy Chair of the Writers’ Guild of Great Britain.

About recording the podcast, Mr. Gallagher said, “I think Ken is so interesting in the interview. He is a joyous treat.” He further explained that OmniOutliner is “the outlining app for writers whether they like outlining or not. OmniOutliner is an ideas manager, a way to massage the mess in our writing minds out into a message, whether that’s a novel, script or an event. Ken explains what the app brings for writers, reveals why the Omni Group created it, and gives just a little hint about where it’s going next.”

And here’s what Mr. Gallagher wrote about the podcast in advance of it:

Speaking of software, Ken Case from the Omni Group agreed to talk about his firm’s major writing app, OmniOutliner. Today is the first day in months I haven’t opened OmniOutliner, but only because it’s early. I know for certain that later today I will be planning out two complicated articles in it, for instance.

Actually, that might be the moment in next week’s more than two hours of interviews that tickled me the most. Ken confessed that he’d prepared for the interview by making some notes in OmniOutliner –– and I had to confess right back that so had I. We both had this app on our screens throughout. Love that software.

You can watch the entire episode right here:

And you can try OmniOutliner for 14-days or learn more about OmniOutliner here.

Ken Case on Apple’s M1 and the future of the Mac

by Omni on November 27, 2020

Ken (our co-founder and CEO) was invited on The Changelog podcast in the wake of Apple’s “One More Thing” event to share his early experience with M1-powered Macs and what this means for the future of the Omni Group on the Mac platform.

Ken and The Changelog guys discussed:

  • The Omni Group’s many platform transitions over the years
  • What it took to get our apps ready for Big Sur and the M1
  • How Apple can optimize their chips in ways Intel won’t
  • Ken’s early benchmarks of building our apps with the M1
  • And much more 😉

On that last point, Ken had this to say:

So what I’m finding – one of our apps takes about ten minutes to build on an Intel MacBook Pro; the latest one that you could get, maxed out with 32 GB of RAM, and so on. And when I pulled the M1 Air and I tried doing the same build, it took five minutes and fifty seconds.

You can listen to the entire episode (which includes a great interview with Apple’s Tim Triemstra as well) right here:

The Changelog 421: The future of Mac – Listen on Changelog.com

Or, if you’d prefer reading to listening, there is a great transcript of the episode right here.

The Difference Between a Prototype and Wireframe

by Omni on June 25, 2020

You’ve got an idea—you did the hard work of coming up with an app, website, or brand-new product. Now it’s time to figure out how to share it. Wireframes and prototypes are tools that are relatively easy to create and can help you visualize the functions and possibilities for your concept. Here, we’ll explain what wireframes and prototypes are, the uses for both, and how they differentiate—so you can determine which is best for your project and build a plan to share your idea with the world.

What is a wireframe?

A wireframe is a static, basic sketch of your website, which makes it the perfect visual starting point during the brainstorming process. Rectangles or squares are used as placeholders to show the location of potential graphics, buttons, or text, and the accompanying lines connecting these shapes indicate the order of information shared on each page.

There are two different types of wireframes: low-fidelity (lo-fi) and high-fidelity (hi-fi). A lo-fi wireframe will help you map out the basic schematics of your site in black and white. In contrast, a hi-fi wireframe is typically constructed after a lo-fi wireframe and presented in grayscale—this simulates actual color tones to help you better visualize the final version. Think of the hi-fi wireframe as a more detailed blueprint of your concept that will give viewers a better understanding of the look, feel, and functionality of your site. Hi-fi wireframes often contain no content or some placeholder content, such as lorem ipsum, typeface preferences, and specific dimensions for images.

Both types of wireframes are useful during the creative process and can be produced using design tools like OmniGraffle. If you’re starting from scratch, sketch a lo-fi wireframe to establish the basic layout of your site and hone in on the essential hierography of information. Then, create a hi-fi wireframe to explore more of the UI and UX details. Keep in mind that simplicity is key when creating a wireframe—don’t worry about aesthetics. Your goal is to quickly communicate your idea by constructing a simplified visual representation of your design.

Wireframe for the Difference Between a Prototype and Wireframe

What is a prototype?

A prototype is an interactive model or simulation that demonstrates how the finished website or app will work. The primary goal of a prototype is to test the design and functionality of your concept before moving on to the next phase of development. Creating a prototype might seem like a waste of time, but it’s a crucial step in discovering potential flaws and can save you time and money in the long run.

Like wireframes, prototypes also differ in complexity—lo-fi or hi-fi—based on the level of interactivity that’s possible (e.g., how many buttons can be clicked), visual design, and content. Lo-fi prototypes are often paper sketches with some basic visual attributes (rectangles, boxes, and buttons) representing the order of information, and the interactivity is simulated by a person. More realistic in appearance and with a higher degree of functionality, hi-fi prototypes are computer-generated simulations that look and feel like the final product—all interface elements (animations, graphics, colors, and content) are included with interactive clickable hotspots for users to experience the site.

Creating a prototype allows you to test and tweak the functionality of your design and discover any features you may want to add to your product before entering the final phase of design. Prototypes take more time to construct than wireframes, but the feedback gained through user interaction can be invaluable. The more realistic your prototype is, the more in-depth feedback you’ll gain for future iterations.

Prototype

Understanding the difference between a wireframe and a prototype will help you determine which one best suits your needs. Using these design tools to map out and communicate the usability and functionality of your concept—and test your product—will save you time and resources and take you one step closer to production.

Team Subscriptions Now Available for OmniPlan, Other Apps Coming Soon

by Omni on March 2, 2020

Team subscriptions are a great new purchasing option that dramatically simplifies licensing for teams—and it’s available now in OmniPlan 3.14 for Mac (the pi release!) and OmniPlan 3.13 for iOS.

It’s not just for OmniPlan: Team subscriptions will be coming soon to the rest of our apps.

Update 6 Mar 2020: team subscriptions are now available for OmniFocus too!

Introducing Team Subscriptions

A team is a group of people subscribing to and paying for an app using a single subscription, rather than as individuals.

The team subscriptions option has several benefits:

  • It simplifies payment—a single bill covers all team members
  • It simplifies management—no need to track individual licenses
  • It reduces up-front investment—recurring costs are predictable

For some teams this is perfect, but it’s not for everybody. It’s important to remember that this is an option.

You can read more about purchasing and using team subscriptions on our support site.

If you have further questions about team subscriptions or the best way to purchase a license or multiple licenses, please email.

OmniPlan Changes

OmniPlan has addressed a compatibility issue importing some Microsoft Project files and added support for team subscriptions. Other fixes include enhanced stability, Omni Automation, Siri Shortcuts fixes, and more.

Read the OmniPlan for Mac change notes and OmniPlan for iOS change notes for the full scoop.

Implementing Custom Columns Layout in OmniFocus for Mac

by Curt Clifton on December 10, 2015

By William Van Hecke and Curt Clifton

In our previous post we discussed the design of Custom Columns layout for OmniFocus for Mac. In particular, we wanted to add this feature while keeping the improved approachability that is a hallmark of OmniFocus 2.

In this post, we’ll discuss how we implemented this design in our existing app.

Implementation

OmniFocus 2 uses view-based table views and Auto Layout for its sidebar and main outlines. In adding Custom Columns layout we had several problems to solve.

  • What class should be responsible for determining the current layout? Recall that the current layout is a function of the user’s app-wide default settings, whether they have upgraded to Pro, the current perspective, and whether that perspective has layout customizations.
  • If we’re using Custom Columns layout, which columns should be shown?
  • How wide should each column be given the current window width?
  • How do we position the fields for each column?
  • How do we decide when to elide columns because the window is too narrow or the hierarchy indentation is too deep?

Choosing the Current Layout

Here’s a diagram showing the structure of controllers and views in an OmniFocus window before we added Custom Columns layout.

Class diagram of OmniFocus Fluid layout

When switching tabs, the selected sidebar tab extracts the layout mode and other settings from the tab’s perspective. It then uses our window state restoration machinery to pass these settings down to the ContentViewController. The ContentViewController forwards the layout settings to the OFIContentOutlineViewController.

The outline view controller considers this per-perspective layout setting, the current Pro upgrade or trial status, and the value of the app-wide layout preference to determine the current layout mode. For each row in the main outline, the outline view controller uses the corresponding model object along with the current layout mode to vend the correct table cell view.

Sharing Code for Table Cell Views

For Custom Columns layout we needed to use different table cell views, but wanted to retain as much of the existing, working code as possible. As the figure above shows, both OFIProjectTableCellView and OFIActionTableCellView are backed by .xib files and have a common superclass, OFIActionProxyTableCellView. We wanted to add two more leaf nodes and .xibs for Custom Columns layout: OFIProjectColumnarTableCellView and OFIActionColumnarTableCellView. These classes needed to share some behavior with each other, but also needed to share some behavior with their corresponding Fluid variant. For example, both OFIActionTableCellView and OFIActionColumnarTableCellView needed to manage their status circles. This is a classic example of the diamond inheritance problem.

Since Objective-C (and Swift) don’t have multiple inheritance, we chose to solve this diamond inheritance by introducing assistant classes for the action and project table cell views. Here’s a diagram showing that design.

Class diagram showing code sharing between Fluid and Custom Columns layout

Shared code for the Custom Columns table cell views lives in OFIActionProxyColumnarTableCellView as shown on the left side of the diagram. The existing shared code for regular table cell views moved to OFIActionProxyFluidTableCell, shown on the right side of the diagram. The shared code across all these table cell views, primarily for managing notes, lives in OFIActionProxyTableCellView at the top of the diagram. Finally, the two assistant classes, shown at the bottom of the diagram, do the chores that are shared by all project rows (like displaying action counts) and by all action rows (like updating status circles). This design works well. The concrete table cell views have very little code. That code is primarily devoted to forwarding messages to the appropriate assistant. The .xib for each concrete table cell view handles instantiating the assistant at the same time the table cell view itself is instantiated.

With this code sharing problem solved, the basic structure when using Custom Columns layout is shown in the diagram below. The highlights show the changes from the original Fluid layout.

Class diagram of OmniFocus Custom Columns layout

Sharing Column Info with Table Cell views

Once we had the correct table cell views for Custom Columns layout, the next challenge was letting cells know which columns should be visible. Recall that the OFIContentOutlineViewController knows about the current layout settings. Conveniently, our table cell views already had a delegate pointer to the outline view controller. The outline view controller implemented an OFITableCellViewDelegate protocol. To get column information, we add a new method to this protocol:

- (OFIColumnLayoutManager *)columnLayoutManagerForCellView:(OFITableCellView *)cellView;

Our table cell views call this delegate method. The outline view controller, as the delegate, decides which columns should be visible and instantiates an OFIColumnLayoutManager. This column layout manager is immutable and exposes all the information about columns necessary for the table cell view to decide which columns to show and where to position them. Here’s what the header looks like:

/// Instances of this class are immutable. We rely on that fact to do identity comparisons.
@interface OFIColumnLayoutManager : NSObject

- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithColumnSpecifications:(NSArray <OFIColumnSpecification *> *)columnSpecifications NS_DESIGNATED_INITIALIZER;

// Width management
@property (nonatomic, readonly) CGFloat minimumTitleWidth;
@property (nonatomic, readonly) CGFloat maximumTitleWidth;
@property (nonatomic, readonly) CGFloat minimumMetadataColumnsWidth;
@property (nonatomic, readonly) CGFloat maximumMetadataColumnsWidth;
@property (nonatomic, readonly) BOOL areAllMetadataColumnsFixedWidth;
@property (nonatomic, readonly) NSArray <NSNumber *> *minimumColumnWidths;

/// Returns an array of NSNumbers, the CGFloat of which gives the column width.
- (NSArray *)columnsWidthsForTotalWidth:(CGFloat)totalWidth;

/// Calculates the multiplier and constant for a constraint relating the width of the title text field to the width of all the other columns combined.
/// - Parameter outMultiplier: must be non-NULL
/// - Parameter outConstant: must be non-NULL
/// - Returns: whether the out parameters were set
- (BOOL)titleWidthToOtherColumnsWidthConstraintMultiplier:(CGFloat *)outMultiplier constant:(CGFloat *)outConstant;

// Metadata column metadata
@property (nonatomic, readonly) NSInteger numberOfColumns;
@property (nonatomic, readonly) NSArray <NSNumber *> *columnKinds;

@end

We instantiate a column layout manager with an array of OFIColumnSpecification instances. These encapsulate the column kind and its minimum and maximum width.

Now we have enough information in the table cell view to layout out the columns. How should we do that?

Calculating Column Widths

Recall our goals:

  • Automatically choose the width for each column.
  • Resize variable-width columns proportionally with the width of the window.
  • Make indented child actions steal space from the title column until they reach a minimum width.
  • Beyond that, omit columns from left to right so that the existing columns remain aligned.

The graph below, which lived on Curt’s whiteboard for the summer, relates our desired column width to the table cell view width.

Graph relating column widths to table cell width

The graph shows three regions. Let’s consider these right to left, as we’d pass through them if you were making your OmniFocus window narrower. Here’s an example row laid out for the various regions.

Examples of Custom Columns layout at various widths

At the window’s widest, we’re in the spacious region on the right. When a table cell view is wide enough to be in this region, resizing the window directly resizes the title column. All the variable-width columns are at their maximum widths. And, of course, all the fixed-width columns are at their fixed widths.

As we make the window narrower, we enter into the normal region in the middle. Here all the fixed-width columns remain at their fixed widths and we allocate the remaining width proportionally between the item titles and the variable-width columns.

As we make the window narrower still, we enter the cozy region on the left. (We can also get cozy table cell views when you indent tasks inside action groups.) In this region all the columns have reached their minimum widths, so we have no choice but to drop columns. The stair steps in the graph represent the columns being removed.

Our existing table cell views all use Auto Layout. It’s an important part of our mechanism for handling variable-height rows that wrap the title text. We don’t want to lose that capability. On the other hand, in the cozy region, our column widths are non-linear; there’s a stepping down of widths as we elide columns. Auto Layout doesn’t handle non-linearity.

Cocoa’s solution for these cases is NSStackView, which allows you to set priorities on subviews and have them automatically removed as needed. NSStackView is great for many use cases. Unfortunately, we’ve found that it doesn’t yet perform adequately when used in table cell views, many dozens of which can be laying out at once (for example, while you resize a window).

We solved this problem by using a mix of Auto Layout and manual layout.

Mixing Auto Layout and Manual Layout

Our strategy is to use Auto Layout at the top level inside the table cell views, but to put all the column subviews inside a single OFIMetadataColumnsView that manually positions the columns. The title view and columns view are positioned and sized using Auto Layout. This figure shows the horizontal constraints that we use to do that.

Autolayout constraint system for title and columns views

Going from the top of the figure down, we have fixed constraints setting the left edge of the title view, the right edge of the columns view, and the space between them. Next we have a constraint that sets the minimum width of the title field, followed by one that sets the maximum width of the columns. Finally, we have a low priority constraint relating the width of the title view to the width of the columns view. The priority on this ratio constraint is less than the OS-provided window resizing priorities. So, the window can be resized in a way that breaks this ratio constraint. This priority scheme allows different constraints to be active at different table cell widths.

In the spacious region, the maximum width constraint on the metadata columns view is active. The low priority ratio constraint is violated, which is why it’s low priority. Autolayout allocates all extra width to the title view as shown below.

Active Auto Layout constraints in the spacious region

In the normal region, as shown below, the constraint that relates title view width to column view width is active.

Active autolayout constraints in the normal region

The constants for this ratio constraint are calculated based on the minimum and maximum widths of all the columns using a bit of algebra.

(For the curious:

max. title width = m * (max. variable width columns width + fixed columns width) + b

and

min. title width = m * (min. variable width columns width + fixed columns width) + b

Solve for m and b, then plug in the current values for all the other terms from the column layout manager. For child actions, we then adjust b to account for indentation.)

Finally, in the cozy region, the minimum width contraint on the title view becomes active. The ratio constraint is violated once again. Auto Layout steals space from the columns view to maintain the width of the title view.

Active Auto Layout constraints in the cozy region

Manual Column Layout

During a layout pass, the Auto Layout system sets the frame of the columns view, then calls the layout method on OFIMetadataColumnsView. OFIMetadataColumnsView has a delegate pointer that references its host table cell view. We use that to get the information we need to position the column subviews.

First, we ask the delegate to provide an array of views, one for each column you’ve asked us to display. We install these as the subviews of the OFIMetadataColumnsView.

Next we get the desired and minimum width for each column from the delegate. We loop through the columns from right to left. For each we check whether there is enough space to render the column subview. If so, we set the frame of the subview to the desired size. In cases where we’re indenting child actions, we may not have room to render the column subview at its desired size. As long as there is enough space for at least the minimum size, we’ll still show it.

Finally, if we run out of room before positioning all the subviews, then we hide the remaining column subviews and add a mid-elipsis, ⋯, to indicate that we’re eliding some columns.

Conclusion

This implementation is not simple, but we think the experience that we’re providing is. You decide which information you would like to see. When you need more or less space, it should be as easy as resizing the window; OmniFocus and your Mac do the work to display that information in the space given, letting you focus on your own tasks.