Callouts are small, transient popups that display information or user interface controls. As part of my Swift node based calculator experiment, I wanted to add two new features, a numeric dial and a delete node button, which are ideal candidates to be contained in a callout container.
I've used a different approach for both new features and this post describes how I've implemented them.
Popovers
First off, the numeric dial is launched from a UIBarButtonItem in my Toolbar class
The numeric dial is my own component that I blogged about here. In this implementation, I've updated its layers to extend CAShapeLayer and draw its value line immediately through a drawValueCurve() method in the NumericDialTrack class.
The dial itself knows nothing of my presentation model; in keeping with the strategy I've used for years, my low-level UI components are framework and domain agnostic. My NumericDialViewController, which houses the control, does know about the presentation model and has a few observers on it:
nodeChangeHandler() does little more than normalise the current node's value and update the control to reflect the relevant node's value:
When the user changes the dial position, the controller acts on a .ValueChanged control event on the dial and notifies the presentation model:
Up in the Toolbar class, I create an instance of my NumericDialController and an instance of UIPopoverController with the numeric dial controller set as its contentViewController:
I want to know when the dial callout has been closed and to do that my toolbar implements the UIPopoverControllerDelegate and sets itself to the popover controller's delegate:
The user launches the numeric dial callout by clicking a UIBarButtonItem:
The showDial() method actually displays the callout. It does this by checking we have a rootViewControler and then simply invoking presentPopoverFromBarButton():
One thing I found was that although the callout is meant to be modal, the little calculator buttons were still clickable. Changing the toolbar's userInteractionEnabled to false didn't seem to help, so I created a little function that manually enables or disables the individual button bar buttons:
This is invoked when the callout is opened and, because toolbar is the popover controller's delegate, invoked again when the callout is closed through the popoverControllerDidDismissPopover() method:
Action Sheets
Next up is an ActionSheet which is launched from my MenuButton class.
I could have used the same technique for the menu button, but UIKit offers a simpler alternative for creating action sheets, UIAlertController.
The UIController contains a set of UIAlertAction instances. In my case, I have one for deleting the selected node and two others to toggle nodes between numeric and operator types.
My first steps are to instantiate the UIAlertController and add the actions:
Now that the actions have been created, launching them is simply a matter of invoking presentViewController() on the root view controller in the menu button's overridden touchesBegan() method:
A little note on NSTimer and userInfo
When I first plugged the numeric dial into this application, performance was pretty grim. Because of the frequent updates to the screen, the dial was very juddery. One of the approaches I took to help with this was to use an NSTimer inside the recursive nodeUpdate() method inside my presentation model.
Although I didn't end up using this approach, I did have to scratch my head for a few minutes to figure out how to pass data from a timer to its selector. I needed to pass a NodeVO and I did this by creating a NSMutableDictionary containing my node and setting that as the timer's userInfo property:
Then, timerComplete() uses valeForKey() to extract the node from its argument:
All the updated source code is available in my GitHub repository. Please note: this code has been built under Xcode 6.0 and may not work under 6.1.
Add a comment