Blogs

DejalIntervalPicker: a custom Mac control similar to NSDatePicker, but for time intervals or ranges

Another open source project for developers.

DejalIntervalPicker is a custom Mac control similar to NSDatePicker, but for time intervals or ranges.

This project was started several years ago for Time Out 2, but got put aside while I worked on contract projects. I've just finished it off and released it for others to enjoy. It will be making an appearance in Time Out 2 as of the next alpha release.

Requirements

  • OS X 10.10 or later recommended, but should work back to 10.7.
  • Objective-C language.
  • ARC.
  • Dependency: the DejalObject project.

Features

  • A custom control with an amount or amount range, units, and stepper.
  • Like NSDatePicker, editing components separately, with a stepper.
  • Can set minimum and maximum amounts.
  • Can get/set the interval as a DejalInterval, as individual values, or as a NSTimeInterval.
  • Can have either a single amount or a range of amounts.
  • Can optionally filter the range to ensure the first amount is smaller (or equal to) the second one, or vice versa.
  • Can control which units to include.
  • Can navigate between components via Tab and Shift-Tab and left/right arrow keys, or clicking.
  • Can type amounts just like in the date picker, and units with auto-completion.
  • Can increment and decrement amounts and units via up/down arrow keys, +/- keys, or the stepper.
  • Can increment/decrement in steps of 5 via Shift/Option/Ctrl and up/down arrow keys, or Page Up/Down.
  • Can go to the first/last valid values via Home/End.
  • Can display a drop-down menu of suggested legal amounts or units via the spacebar or clicking on the selected value.
  • Supports regular, small and mini sizes.
  • Supports properties, key-value coding, and bindings.
  • Supports IB_DESIGNABLE and IBInspectable, so the picker can be configured in IB.
  • A demo project is included.

Usage

  1. Include the DejalIntervalPicker.h and DejalIntervalPicker.m files in your project. Also include at least DejalObject.h, DejalObject.m, DejalInterval.h and DejalInterval.m from the DejalObject project.
  2. In Interface Builder for your xib or storyboard, drag a custom view to your view or window.
  3. In the Identity inspector, change the Custom Class of the view to DejalIntervalPicker.
  4. In the Size inspector, add a Placeholder Intrinsic Size of 150 width and 22 height if using Auto Layout, or set the view to that size for auto-resizing.
  5. In the Attributes, configure the desired attributes like the using range, initial amounts, and which units to include.
  6. In your controller, you can also configure the picker via methods like usingRange, includeForever, firstAmount, and others; see the demo project for examples.
  7. Populate the picker value by setting the amount(s) and units, or setting the interval from a DejalInterval instance.
  8. Get the picker value via the same properties: either amounts(s) and units directly, or the interval instance.
  9. See DejalInterval for several useful methods and properties, e.g. to get a string representation of the interval.

You can get the code and more information from the Dejal Open Source page.

DejalObject: an abstract data model class that can represent subclasses as dictionary or JSON data

DejalObject is an abstract data model class that can represent subclasses as dictionary or JSON data for saving to disk or over the network.

Included are DejalColor, DejalDate and DejalInterval concrete subclasses.

They work on both OS X and iOS.

Features

  • DejalObject: This is an abstract subclass of NSObject that adds methods to represent the receiver as a dictionary or JSON data, load default values, track changes, enumerate an array of DejalObject instances, and more.
  • DejalColor: A concrete subclass of DejalObject to represent a color (for OS X or iOS), enabling it to be stored in a DejalObject subclass.
  • DejalDate: Another concrete subclass to represent a date, primarily so it can automatically be represented as JSON.
  • DejalInterval: A subclass to represent a time interval or a range of intervals, including an amount and units, with methods to represent the interval or range in various ways, including as human-readable strings (see also the DejalIntervalPicker project for OS X).

A demo project is included, showing a subclass of DejalObject to store various data types.

Usage

Include at least DejalObject.h and DejalObject.m in your project. Include the DejalColor, DejalDate and/or DejalInterval files if those are needed.

Add a new class that inherits from DejalObject.

In the header, you only need to define properties to store, e.g.:

@interface Demo : DejalObject

@property (nonatomic, strong) NSString *text;  
@property (nonatomic) NSInteger number;  
@property (nonatomic, strong) DejalColor *label;  
@property (nonatomic, strong) DejalDate *when;

@end

In the implementation, override -initWithCoder: and -encodeWithCoder: if you need to support coding (secure coding is supported in DejalObject).

Override -loadDefaultValues to populate default values to each of the properties. A version number can be assigned (via DejalObject’s version property) to enable upgrading later, e.g.:

- (void)loadDefaultValues;  
{
    [super loadDefaultValues];

    self.version = DemoVersion;  
    self.text = @"Foo";  
    self.number = 12345;  
    self.label = [DejalColor colorWithColor:[NSColor blueColor]];  
    self.when = [DejalDate dateWithNow];  
}

Finally, override -savedKeys to indicate which properties should be automatically included in the dictionary or JSON representation, e.g.:

- (NSArray *)savedKeys;  
{
    return [[super savedKeys] arrayByAddingObjectsFromArray:@[@"text", @"number", @"label", @"when"]];  
}

A DejalObject subclass can be represented as a NSDictionary simply by invoking the dictionary property on it, or as JSON via the json property. That can then be saved to disk or the user defaults, or passed over the network.

Those properties can also be set, or a new instance can be created via +objectWithDictionary: or +objectWithJSON:, or an instance with default values via +object.

DejalObject instances automatically track changes, with a hasChanges BOOL property indicating that something has changed (so may need to be saved). There is also a hasAnyChanges property that recursively enumerates the properties and any other DejalObject instances in them, e.g. if the color is changed in the above example.

After saving, you should invoke -clearChanges to reset the change flag.

You can get the code and more information from the Dejal Open Source page.

The importance of a good backup strategy

Today I received a support email from a Simon customer who had a hard drive failure, and lost their data. Worse still, they were in the process of recreating their Time Machine backup at the time, so didn't have a backup.

That prompted me to post about my backup strategy. When you live your life and make your living on computers, there is little more valuable than the data they contain. So it is critical to protect it from a loss that could set you back years.

Fortunately nowadays most important data is in the cloud... various remote servers. For example, if you use iTunes Match, your music collection is safely on Apple's servers (well, hopefully safely). Photos are still at risk, but Apple is rolling out the iCloud Photos service that will keep them offsite. And other services like Dropbox help protect important documents... if you put them in there.

For myself, I have a multi-pronged data management and backup strategy.

In terms of data management, I use cloud services to sync my data between my iMac and MacBook Air, which has the added benefit of keeping offsite copies of the important data:

  • My documents are all stored in Dropbox.
  • My app source code is managed by Subversion (for my app-specific code) and Git (for my open source code).
  • My music, (soon) photos and other data are stored in iCloud.

In fact, I replaced the Documents folder in my home directory with a symbolic link to a Documents folder within the Dropbox folder, so all of my documents are safely in Dropbox. It's not necessary, but you can easily do this via a couple of simple Terminal commands:

sudo mv ~/Documents ~/Dropbox/Documents
sudo ln -s ~/Dropbox/Documents ~/Documents

The first command moves the Documents folder to within Dropbox, and the second one makes a symbolic link to that folder where the old Documents folder was. The sudo is needed as the OS will normally prevent moving the Documents folder; Terminal will prompt you for your password.

But that doesn't mean that backups aren't important too. Backups are useful to get back earlier versions of documents (via Time Machine), or provide redundancy in case a cloud service loses something, or just as a quick way to get back up-and-running. Plus, of course, protecting data like preferences that aren't included in Dropbox or other cloud syncing.

I use multiple services for backups, too:

  • I use Time Machine via a Time Capsule in a different part of the house to do hourly incremental backups of the most important files. Useful to get back earlier versions of documents.
  • I use SuperDuper! to make nightly exact clones of my SSD main drive and spinning media drive onto backup disks. Useful to quickly get back up-to-speed if a hard drive fails, or I need to revert an obscure file.
  • I use Backblaze to make nightly offsite backups of pretty much all of my files. Useful in case of a major disaster like my house burning down, or failure of one of the other backups.

(Full disclosure, if you use the Dropbox link to sign up I'll get more space, not that I need it, and you'll get 500 MB bonus space. And similarly that Backblaze link will give both you and I a free month of service.)

Your data is valuable — don't risk losing it when it is so easy to protect it!

Simon tip: groups

One feature request that I received many times for Simon was the ability to organize tests into folders or groups — especially useful for people with lots of tests, or simply want to collect all tests relating to a particular server or client together.

Previously, the closest you could get to this was to use a common prefix on the name, and sort by name. But that is cumbersome, and loses the benefit of being able to sort by something more useful, like last event date — so recently changed or failed tests appear at the top.

Simon 4 solves this with the new groups feature.

Now, tests can be grouped together however you wish. It's easy to create a group: simply choose the New Group command in the File menu or the + pop-up menu, then drag the tests into the new group. Even easier, you can just select some tests and choose the New Group with Selection command to make a group and move those tests into it in one step.

Groups appear with disclosure triangles, enabling them to be collapsed. The group row shows a summary of the contents, with any common values displayed for easy reference. And similarly, the info pane shows a summary of the contained tests.

Groups can even be nested, if desired — you can have an unlimited number of groups within other groups, if that helps organize them.

But wait, there's more! While grouping tests is perhaps one of the most-requested features, I didn't stop there: you can also group services, filters, notifiers and reports in the same way!

When these items are grouped, they appear indented in the Kind pop-up menu in the test editor, so you can keep related items together:

I hope you enjoy this new feature.

20 years of contract work, looking forward to the next 20 years

Today marks a milestone in my career. 20 years ago today, I started working for the company that would eventually become Intrahealth. Technically, the start date was February 7, 1995, but it was in New Zealand, which is a day ahead of the US, so it counts as today.

I actually started Dejal as a company back in September 20, 1991; see my previous 20 years of Dejal shareware blog post for a retrospective on that.

But 20 years ago today I started working for Mark Matthews, now CEO of Intrahealth, taking over development of his MMAS application: the Macintosh Medical Administration System. It was a pair of server/client apps written in MPW Pascal for classic Mac OS, used by hundreds of medical clinics around New Zealand to manage their practices: everything from appointments, billing, patient medical records, and more. My first assignment was rewriting the prescriptions windows.

An early memory was that Mark actually printed the wedding invites for Jenn and me on his LaserWriter.

A few years later, around April 1997, I converted the project to CodeWarrior Pascal, and 10 years later it moved to Free Pascal (FPC). Being such an ancient product, it has gone through many iterations, but still exists today.

When I started, I was working out of my home office, in a tiny 2-bedroom flat (or apartment as we'd call it in the US). And I mean really tiny — our bed touched three walls of the bedroom.

Later, about a year after the company established the Intrahealth name in 1997, I started working in their office. By that time it had several more employees, but I remained the lead developer of their Mac apps, with part-time coding by another company founder, Andrew Hall, and for about a year another developer. A few years later, in 1999, MMAS was renamed as Profile for Mac, to bring it in line with their new product for the Windows operating system, Profile for Windows.

Big changes

Big changes in 2001: on April 1 my wife and I moved to the US. So at that time I became a contractor again (an international employee would be tricky), and continued working on Profile for Mac, mostly part-time — about half of my time on that, half on Dejal apps, including my first Mac OS X apps, Narrator (since sold to Mariner Software), Simon (which was recently updated to version 4), and others.

I also spent some time on other contract projects, including iData and MindFortress, both snippet utilities similar to Caboodle. But Intrahealth was a big part of my career.

Intrahealth changed countries too: in 2005 they moved their head office to Vancouver, BC, Canada.

After the iPhone was introduced, I created some Dejal apps for it. Naturally, when Intrahealth wanted a mobile edition of Profile, they turned to me. In January 2011 I started work on Profile for iOS, an app designed to sync with the Profile for Windows server, fetch patient information, appointments, tasks, etc from the server, and work offline until the device has a connection again, when it would sync changes back to the server and get updates.

I worked on Profile for iOS mostly full-time since then, as its sole developer, which did mean Dejal suffered a bit — astute readers of my traditional year-end summary blog posts for 2012, 2013 and 2014 will have noticed that I didn't have many releases, and mentioned that I spent most of my time on Profile.

All good things....

This period of my life drifted to an end late last year. Intrahealth has continued to expand, from one employee (me) in 1995 to around 100 staff in four countries today. A few months ago they decided to bring Profile for iOS development in-house, and as I had no interest in moving to Canada, they took on another iOS developer to take over the project. After providing guidance and mentoring to get him up-to-speed for a few months, my Intrahealth hours decreased to practically nothing. Since then, I have just provided occasional advice on issues with the Mac and iOS products. That may continue for quite some time, but it's effectively a negligible amount of time now.

So, ignoring the past few months, I count my time with Intrahealth as about 19 years. I'm a little sad that it didn't make it to 20 years, but even 19 years is an amazingly long length of time to work with one company, even if it was in various employment/contracting capacities, and sometimes part-time. So I thought it was worth taking some time to mark this milestone.

So, what's next?

What's the plan for the next 20 years? Sometimes I wish I could see into the future, so I knew what was ahead.

I know what I want: to continue doing what I've been doing for the past few months — working on Dejal full-time.

After stopping Intrahealth work, I started work on Simon version 4.0. That took a few months, and was released in December as its biggest upgrade ever. Sales of Simon jumped up, thanks in large part to upgrades, which has certainly helped cover the lack of Intrahealth income, though not quite enough to be sustainable; I really need to double my income to be able to afford to do this full-time.

I am currently back working on Time Out version 2.0, which has been in the works for years, but kept getting sidelined by Intrahealth work. It has progressed significantly, and I'm looking forward to releasing it sometime in the coming months. And hoping that I can survive until then, and that it'll do well enough to let me continue.

I've also released a new iPhone app, Pack, to help with packing lists, released several open source projects for other developers, and published many blog posts, including a series of Simon tips.

I am also open to working on contract projects for others, to help refill the coffers. If you know anyone who needs an experienced iOS or Mac developer, please send them to my Consulting page for more information.

And recently I produced a custom build of Simon for a customer who wanted to install it on their clients' machines under their own name. I'm certainly open to doing that for others too, or adding custom features to Simon as paid work.

The upshot of all this is that after about 20 years, I am keen to take Dejal from a part-time, hobby-like business to full-time operation. The Dejal apps are mature, high quality, and useful, and I'm sure could fully support me and enable me to accelerate the rate of improvements, and maybe even release more new apps. I just need to get better at getting the word out about them.

Please help!

If you've read this far, congratulations! You must be sufficiently interested in me and my apps, so can I ask you for a favor? Please tell everyone you know about them! Tweet about the apps, post on Facebook, tell your friends, email bloggers, etc etc. Anything you can do to help others learn about the apps will help me grow Dejal to a full-time, sustainable software business. Which will benefit everybody. Thank you.

Simon tip: hide the Dock icon

Simon 4 added a surprise new feature that many people have asked for over the years: the ability to hide the app from the Dock.

In the past, Simon's app icon was always displayed in the Dock. Now, with version 4, there is a new General preference to control this. By default, it is on (so the icon is shown, as before).

Why might you want to hide it? Maybe you want to keep your Dock as sparse as possible. Simon's Dock icon can display the most interesting status, but maybe you don't need to see that all the time, or you're satisfied with seeing that only in the status menu. Since you'd probably want to keep Simon running all the time, treating it as a background-only app can make a lot of sense. Now you can!

If you turn off the Show the Simon icon in the Dock checkbox, the app icon vanishes from the Dock, and also from the Cmd-Tab app switcher. Note that if you have chosen the Keep in Dock option in the Dock menu, the icon will linger, in an inactive state; you can disable that or drag the icon out of the Dock to remove it.

When Simon is hidden from the Dock, you can still activate the app via the status menu, if you have that enabled — and the app will automatically turn it on when you turn off the Dock icon, as a convenience. If you don't want the status menu, you can turn it off again... in which case the only way to activate the app will be to click on one of its windows, if any are visible, or open it from the Finder.

One thing to note is that as a necessary side-effect of hiding the Dock icon, Simon will no longer have a menubar. It'll truly be a background-only app. When you display the Simon Monitor window, the menus won't change from whatever other app you were using. This isn't a problem for most functions, as the toolbar buttons and sort drop-down menu options cover most menu commands. But for app functions like checking for updates, accessing preferences, etc, when the Dock icon is disabled a special action menu is added to the toolbar. For power users, the keyboard equivalents still work, too — so you can press Ctrl-Cmd-1 to switch to Preview mode, for example.

I know that this is an exciting enhancement for many customers. For anyone who wants Simon to "disappear" into the background, try turning off the Dock icon. You can always turn it back on again. No restart required. What do you think? Do you prefer the Dock icon visible or hidden? Let me know in the comments below.

Simon 4.0.2 released

Here are a bunch of fixes for Simon 4, plus one exciting change: Simon now uses the Sparkle framework, like many other non-App Store apps do, to make updating the app easier. I've resisted using Sparkle for years, as it had various issues that made it incompatible with my apps, but those have been resolved in recent updates.

So it's time to sparkle, finally.

After this update, you should no longer need to download the app from here when there's a new version; Simon will be able to update itself in place, and even do so completely automatically if you wish.

Edit: Note that the update checkbox will be off initially, but the app will ask you if you want to automatically check for updates on the second launch.

Here are the changes in this version:

  • Simon now uses the popular Sparkle framework for app updates, so it can finally download and install updates itself.
  • Changed the Updates preferences for the Sparkle framework, and to add a handy button to show the release notes.
  • Fixed the New pop-up menu being disabled when in full-screen mode.
  • Fixed the app not resuming full-screen mode on launch if it was in full-screen when quit.
  • Fixed a too small icon in the status menu and Dock when a Wi-Fi hotspot is detected.
  • Fixed the auto-pause function, which sometimes wasn't engaging when it should.
  • Fixed the Find filter with regular expressions when not finding a match should be a failure.

Download Simon 4.0.2 now!

DejaUIKitCategories: an open source collection of UIKit-level categories for iOS

The final batch of open source categories: DejalUIKitCategories.

DejalUIKitCategories is a collection of categories for UIKit on iOS, to add useful methods to classes like UIBarButtonItem, UIColor, UIView, and others. The categories include:

  • UIApplication+Dejal: Adds methods to get the first responder and keyboard view of the app.
  • UIBarButtonItem+Dejal: Convenience initializers to make UIBarButton instances based on and image, title, system item, custom view, spacer, or segmented control.
  • UIButton+Dejal: A more convenient title property, and a method to add a gloss effect.
  • UIColor+Dejal: Convenience initializers for more standard colors, or based on a platform-specific image or hex value.
  • UIImage+Dejal: Convenience initializers for tinted images, methods to overlay images with colors, and scaling methods.
  • UIImageView+Dejal: Make a white-background image view, and adjust the background color based on the highlighted state.
  • UILabel+Dejal: Convenience initializers for labels with various text, font, width etc attributes, and sizing methods.
  • UISegmentedControl+Dejal: Convenience initializer for a segmented control with specified items, target, action, and initial selection.
  • UITextField+Dejal: A selected range property, and support for gestures to move the insertion point by swiping.
  • UITextView+Dejal: Attributes of the selection or insertion point, and support for the insertion point swiping gestures.
  • UIView+Dejal: Properties for frame and bounds components and an image representation, methods to hide, add to and remove from the superview with animation.

You can get the code and more information from the Dejal Open Source page.

(Looking for an iOS or Mac developer? I'm available for contract or full-time work. Learn more about me.)

DejalAppKitCategories change: methods for radio buttons

Another developer post.

I recently released my DejalAppKitCategories open source project, which includes categories for OS X UI classes.

I am currently working on Time Out 2, and wanted to modernize the radio buttons in the preferences. In the AppKit release notes for OS X 10.10, Apple wrote:

Use of NSMatrix is informally deprecated. We expect to add the formal deprecation macros in a subsequent release, but its use is discouraged in the mean time. The primary use of NSMatrix is for radio button groups, so recall that for applications linked on 10.8 or later, radio buttons that share the same parent view and action will operate as a group.

Which is all well and good, but managing a radio group is annoying when using standalone buttons, so I added some methods to help.

These methods are part of my DejalAppKitCategories open source project, in the NSButton+Dejal category.

Here's the header:

@interface NSButton (DejalRadios)

@property (nonatomic, setter=dejal_setRadiosEnabled:) BOOL dejal_radiosEnabled;

- (void)dejal_selectRadioWithTag:(NSInteger)tag;
- (NSInteger)dejal_selectedRadioTag;

- (NSButton *)dejal_radioPassingTest:(BOOL (^)(NSButton *radio, BOOL *stop))predicate;
- (void)dejal_enumerateRadiosUsingBlock:(void (^)(NSButton *radio, BOOL *stop))block;

@end

And the implementation:

@implementation NSButton (DejalRadios)

/**
Assuming the receiver is a radio button, finds other radio buttons in the group (i.e. in the same superview and with the same action) and selects the one with the specified tag.  Invoke this on any of the radios in the group.  A replacement for -[NSMatrix selectCellWithTag:].

@param tag The tag value to select.

@author DJS 2015-01.
*/

- (void)dejal_selectRadioWithTag:(NSInteger)tag;
{
    [self dejal_enumerateRadiosUsingBlock:^(NSButton *radio, BOOL *stop)
     {
         radio.state = radio.tag == tag;
     }];
}

/**
Assuming the receiver is a radio button, finds other radio buttons in the group (i.e. in the same superview and with the same action) and returns the tag value of the selected radio.  Invoke this on any of the radios in the group.  A replacement for -[NSMatrix selectedTag].

@returns A tag value integer.

@author DJS 2015-01.
*/

- (NSInteger)dejal_selectedRadioTag;
{
    NSButton *foundRadio = [self dejal_radioPassingTest:^BOOL(NSButton *radio, BOOL *stop)
     {
         return radio.state;
     }];
   
    return foundRadio.tag;
}

/**
Returns YES if the radio group is enabled, or NO if not.  Simply returns the state of the receiver; the others are assumed to be the same.  (If you want to know if they are all enabled or disabled, probably best to use -dejal_enumerateRadiosUsingBlock: to scan the group, and handle a mixed case as needed.)

@author DJS 2015-01.
*/

- (BOOL)dejal_radiosEnabled;
{
    return self.enabled;
}

/**
Sets all of the radios in the group to be enabled or disabled.  A replacement for -[NSMatrix setEnabled:].

@author DJS 2015-01.
*/

- (void)dejal_setRadiosEnabled:(BOOL)enabled;
{
    [self dejal_enumerateRadiosUsingBlock:^(NSButton *radio, BOOL *stop)
     {
         radio.enabled = enabled;
     }];
}

/**
Assuming the receiver is a radio button, finds other radio buttons in the group (i.e. in the same superview and with the same action) and performs the block for each of them, passing the radio to the block.  Returns the one that returns YES, or nil if the block requests to stop before completion, or it completes without the block returning YES.  Invoke this on any of the radios in the group.

@param block A block that takes a radio button and stop boolean reference as parameters and returns a boolean.
@returns The found radio button, or nil if none is found.

@author DJS 2015-01.
*/

- (NSButton *)dejal_radioPassingTest:(BOOL (^)(NSButton *radio, BOOL *stop))predicate;
{
    for (NSButton *radio in self.superview.subviews)
    {
        // There's no reliable way to determine if a button is actually a radio button, but it's reasonable to assume that no non-radio will have the same action (and having the same action is what makes it a member of the group):
        if ([radio isKindOfClass:[NSButton class]] && radio.action == self.action && predicate)
        {
            BOOL stop = NO;
           
            if (predicate(radio, &stop))
            {
                return radio;
            }
           
            if (stop)
            {
                return nil;
            }
        }
    }
   
    return nil;
}

/**
Assuming the receiver is a radio button, finds other radio buttons in the group (i.e. in the same superview and with the same action) and performs the block for each of them, passing the radio to the block.  Invoke this on any of the radios in the group.

@param block A block that takes a radio button and stop boolean reference as parameters and returns void.

@author DJS 2015-01.
*/

- (void)dejal_enumerateRadiosUsingBlock:(void (^)(NSButton *radio, BOOL *stop))block;
{
    for (NSButton *radio in self.superview.subviews)
    {
        // There's no reliable way to determine if a button is actually a radio button, but it's reasonable to assume that no non-radio will have the same action (and having the same action is what makes it a member of the group):
        if ([radio isKindOfClass:[NSButton class]] && radio.action == self.action && block)
        {
            BOOL stop = NO;
           
            block(radio, &stop);
           
            if (stop)
            {
                return;
            }
        }
    }
}

@end

To use these methods, simply invoke on any of the radios in the group, e.g.

    [self.iconNoneRadio dejal_selectRadioWithTag:self.statusIconKind];
    self.iconNoneRadio.dejal_radiosEnabled = use;

And:

- (IBAction)chooseIcon:(id)sender;
{
    self.statusIconKind = self.iconNoneRadio.dejal_selectedRadioTag;
   
    [self maintainControls];
}

I hope this helps others! And, of course, if I'm missing anything obvious, or you have any suggestions or comments, please let me know.

This look useful? Do check out my other open source projects, too.

(Looking for an iOS or Mac developer? I'm available for contract or full-time work. Learn more about me.)

Anniversary of the iPhone launch

I just listened to episode 18 of the Upgrade podcast, where Jason Snell reminisced about first seeing and touching the iPhone. (Yes, I'm about a week behind on my podcast listening.)

That sent me down a memory lane of my experience at that event. I haven't attended many conferences over the years, but Macworld Expo in 2007 was one of them. In fact, that was the only Macworld I ever attended. But yes, I was also in the audience for the historic occasion of Steve Jobs introducing the first iPhone.

My seat was rather far back, but I was there (that's a repeater screen on top, and the live stage at the bottom):

Unlike Jason, I didn't get to touch one, but I did get to see it up close, behind well-guarded glass:

After the show, I wrote a followup blog post with my initial impressions of the iPhone and other news at the event (introduction of the Apple TV, and Apple changing its name). Reading over that post now is somewhat amusing, with concerns over the keyboard and developer access.

I had a bunch of other photos from the show, showing various events and presentations, the show floor, iPhone demos, etc. Ah, memories.

DejalAppKitExtensionCategories: an open source collection of categories to extend Foundation classes for OS X

I just released another open source collection of categories: DejalAppKitExtensionCategories.

DejalAppKitExtensionCategories is a collection of categories to extend Foundation classes with methods specific to OS X. The categories include:

  • NSAttributedString+AppKit+Dejal: Adds convenience initializers to load from a bundle resource, image URL or RTF, and to return a RTF.
  • NSDictionary+AppKit+Dejal: Extends NSDictionary and NSMutableDictionary to support NSColor values.
  • NSFileManager+AppKit+Dejal: Adds a method to determine if a path is a directory but not a package, and to move a file to the Trash.
  • NSObject+AppKit+Dejal: Extends the NSObject base class with an old-style modal did-end perform selector, and modifier key detection.
  • NSSString+AppKit+Dejal: Adds methods that depend on fonts, points and colors.
  • NSUserDefaults+AppKit+Dejal: Adds support for points, sizes and colors in NSUserDefaults.

These categories depend on the previously-published DejalFoundationCategories.

You can get the code and more information from the Dejal Open Source page.

Next up: my UIKit categories.

(Looking for an iOS or Mac developer? I'm available for contract or full-time work. Learn more about me.)

Simon 3.6.4 released

When I added recognition of version 4 groups in version 3.6.2, I did so for tests, but services, filters, notifiers and reports can also have groups in version 4. So here's Simon 3.6.4 to fix an issue with editing those.

If you're using OS X 10.10 (Yosemite), I recommend that you upgrade to version 4, if you haven't already. It includes heaps of great improvements. But if you're on 10.6 (Snow Leopard) to 10.9 (Mavericks), you can download Simon 3.6.4 now.

Simon password protection

One of the many enhancements in Simon 4, which was actually also retrofitted to Simon 3.6.2 and later, was the introduction of an optional password feature. This was requested by a volume purchaser of Simon, who also paid for the unusual step of retrofitting it to Simon 3.

The password feature can be used to require a password when Simon is launched or activated. This provides some level of security to prevent unauthorized people from accessing Simon. It doesn't encrypt data or any other changes, it's just a simple access control.

By default, a password is not required. If you want to require one, open up the Preferences. Notice the new Choose Password... button and the text to the left indicating that a password hasn't been set:

Click the button to display the password sheet. If a password hasn't already been set, the first field will be disabled (and display "None"). If one has been set, enter the existing password there. The next two fields are for the new password; enter the same one in both, or leave them both blank to disable the password feature. If entering a password, you should also enter a hint that will remind you of the password (without being too obvious):

After setting a password, the text in the Preferences window will change to indicate so:

When a password has been set, whenever you activate Simon it will display an unlock sheet, asking for the password. It includes a Quit button to quickly stop Simon, and a Cancel to deactivate Simon. After two failed attempts, it will display the password hint (if any); after two more failed attempts, it'll disallow further attempts until after you quit or cancel:

I expect that most people won't need this feature, but for those who do, it should prove quite useful.

Stay tuned for more blog posts delving into Simon 4 enhancements.

DejaAppKitCategories: an open source collection of AppKit-level categories for OS X

I just released some more open source code: DejalAppKitCategories.

DejalAppKitCategories is a collection of categories for AppKit on OS X, to add useful methods to classes like NSMenu, NSTableView, NSTextView, and others. The categories include:

  • NSButton+Dejal: A text color property and a method to display a menu.
  • NSImage+Dejal: Methods to draw flipped images, apply a badge or tint, or get a PNG representation.
  • NSMenu+Dejal: Methods to add and remove items.
  • NSOutlineView+Dejal: Methods for selected items and displaying a menu.
  • NSPopUpButton+Dejal: Methods to add and select items.
  • NSScreen+Dejal: Screen name methods.
  • NSSplitView+Dejal: Methods for split positions and collapsing and expanding.
  • NSTableView+Dejal: Selection, column and copying methods.
  • NSTextField+Dejal: Methods to set values, synchronize with a slider, and resize the window (using autoresizing).
  • NSTextView+Dejal: Properties for string, attributed string and RTF values, methods for length, range, appending, and selection.
  • NSToolbar+Dejal: Methods for the toolbar height and finding an item by identifier.
  • NSView+Dejal: Add a view as a fully-constrained subview, adjust autoresizing, scale, and set the alpha opacity.
  • NSWindow+Dejal: Methods to force editing to end, fade in a window, and adjust the sizing of the window based on a view.

You can get the code and more information from the Dejal Open Source page.

Stay tuned for more OS X and iOS open source code, coming your way over the next week or so.

(Looking for an iOS or Mac developer? I'm available for contract or full-time work. Learn more about me.)

A Simon 4 case study: the default "Dejal posts" test

When I released the major version 4 upgrade of Dejal Simon, I included a couple of new default tests as examples for new customers. One of them is named "Dejal posts", and actually includes a quite sophisticated set of filters.

The general idea of this test is to look at the Recent Posts page of the Dejal site, which lists all recent blog, forum, FAQ etc posts and their comments, and output some tidy text describing the most recent one, along with a changed state when a new post or comment is added.

I thought it'd be interesting to break down this test as an example and tutorial for new and existing customers — even long-term users might learn something!

Firstly, here's the Service page; nothing remarkable here (the cookies are automatically recorded, and unimportant for this test):

[Service page]

The most interesting page is the Filters one (click to see it full-size; you might want to use the appropriate modifier key for your browser to open in a new window):

[Filters page]

When you check the test and look in the Activity log, you can see the output from each of those filters (from bottom to top) — click to see full-size:

[Activity log]

Another way to view the output is via the Preview pane, which now includes not only the service response and headers, but also the full output of each filter, to help you diagnose each step.

Here's the output of the service; the full HTML of the web page:

[Preview service response]

Let's break down each of the filters, via the Preview filter output.

The first filter, a Block one, takes the service response as its Input, and has Start text of <tbody> and End text of <td class="replies">. This finds the first occurrence of each of those bits of HTML in the service response, which corresponds with the most recent post information:

[Filter configuration]

This filter outputs that:

[Preview filter output]

The second filter is another Block one. It takes the output of the first filter as its input, and narrows it down further to just the title of the post. Notice that it also uses options disclosed on the right-hand-side of the filter configuration: it looks for the second occurrence of the Start text, searching from the beginning of the input:

[Filter configuration]

The output of this filter is the post title:

[Preview filter output]

The third filter is yet another Block (it is one of the most useful filters), but the input is different: this time it uses the output of the first filter, instead of the previous one (as is the default). It also has an option to look for the third occurrence:

[Filter configuration]

It extracts the author information:

[Preview filter output]

Filter number four is different. It uses a Ignore Links filter to extract out just the author name from the previous filter output. The previous filter doesn't do this as when you're not logged in on the Dejal site, only the name is included (in which case this filter has no effect):

[Filter configuration]

The output is just the non-HTML part of the input:

[Preview filter output]

Next we're back to a Block filter again, but this time looking at the original service response text to extract the number of replies to the post:

[Filter configuration]

This should always output a number:

[Preview filter output]

We then use a new filter introduced in version 4, Singular or Plural, to take the number found in the previous filter and output "reply" if it is one, or "replies" for any other number:

[Filter configuration]

As seen in the preview:

[Preview filter output]

The last filter puts it all together: an Override Custom filter uses variables to combine the output of several filters in a nice readable way. In this case all the variables are variations of the filter output, but other variables are available too. Something that isn't immediately obvious is that you can insert numbers to reference specific filters (otherwise it refers to the previous one):

[Filter configuration]

Which results in:

[Preview filter output]

So now that we've got some nice output text, what do we do with it? Of course, you can just see it in the Tests list, if you have the last change and failure displayed:

[Tests list]

But you'll probably want to get a notification. For myself, in addition to some generic speech notifiers, I have a notifier to post to the @SimonBot Twitter account; an account I added just for Simon to tweet about Dejal site changes:

[Notifiers page]

The SimonBot notifier also uses variables to add more information about the test:

[SimonBot notifier]

Which appears like this:

You're welcome to follow @SimonBot to learn about Dejal news and discussions.

I hope this case study is helpful. Most tests don't need a series of filters like this, and there are other ways to achieve similar effects (like writing all the logic in a script), but it can be very useful when you want it. You can use similar techniques in your own tests.

I plan to do more case studies or tips on Simon features in the future; please leave a comment if you like this or find it useful, or have suggestions for other things you'd like me to cover.

BlogAssist Express 2.4.1 released

BlogAssist version 2.4.1 was recently released here on the Dejal site.

Those using the Mac App Store edition, BlogAssist Express, will be pleased to learn that Apple has just approved the update, so version 2.4.1 is now available for that edition too.

The changes again are just:

  • Fixed some issues with the Substitutions preference page.
  • Updated for OS X 10.10 (Yosemite).

The standard edition is available for download here, or you can get BlogAssist Express 2.4.1 from the Mac App Store.

DejalFoundationCategories: an open source collection of Foundation-level categories

Today I released two new open source projects: a tiny one, DejalUtilities, and a significantly larger one, DejalFoundationCategories.

DejalFoundationCategories is a collection of Foundation-level categories, to add useful methods to classes like NSArray, NSDictionary, NSString, and others. They work on both OS X and iOS, and include:

  • NSArray+Dejal: 30+ methods extending NSArray and NSMutableArray, including object matching, reversal, sorting, deep copying, adding and removing.
  • NSAttributedString+Dejal: 10+ methods extending NSAttributedString and NSMutableAttributedString, including convenience initializers, RTF and font methods.
  • NSData+Dejal: A couple of methods to make archiving and unarchiving objects slightly more convenient.
  • NSDate+Dejal: 50+ methods extending NSDate, including convenience initializers, handy date component properties and calculators, JSON date support, string formatting, and relative date output.
  • NSDictionary+Dejal: 25+ methods extending NSDictionary and NSMutableDictionary, including object matching, scalar support, deep copying, and more.
  • NSFileManager+Dejal: 15+ methods extending NSFileManager, including convenient file attributes, file renaming, and path building.
  • NSObject+Dejal: 15+ methods extending the NSObject base class, including key-value conveniences, “equivalent” comparisons, and performSelector methods.
  • NSString+Dejal: 80+ methods extending NSString and NSMutableString, including scalar value formatting, contains evaluation, comparisons, substring and range utilities, reformatting, checksum and encoding utilities, internet utilities, file path methods, and appending and replacing methods.
  • NSUserDefaults+Dejal: 15+ methods extending NSUserDefaults, including support for default values, sanitizing values, time intervals, factory settings, and copying preferences.

DejalUtilities is a single header file with some useful #define macros and static functions, also for both OS X and iOS:

  • Adds DejalClassAvailable() and DejalClassSelectorAvailable() macros, to help determine available APIs.
  • Adds DejalIntervalFromMinutes(), DejalIntervalFromDays(), DejalMinutesFromInterval(), DejalMonthsFromInterval(), and similar macros, to convert seconds to and from other units.
  • Adds DEJAL_FILE_NAME and DEJAL_COMPILE_DATE_TIME macros, to help with debug information.
  • Adds a DejalWeakSelf macro to easily make a weak representation of self for use with blocks.
  • Plus several other handy macros and inline functions.

You can get the code and more information from the Dejal Open Source page.

(Looking for an iOS or Mac developer? I'm available for contract or full-time work. Learn more about me.)

Simon 4.0.1 released

Simon 4 was a major release, and great to get it out. Fortunately, there weren't any major issues with it — I always dread having to do an urgent bug fix immediately after a major release.

But there were some minor issues that were important enough to get fixed fairly quickly, especially for non-English users. So here's version 4.0.1. It's a recommended update for everyone.

  • Fixed an issue with duplicating a test where filter settings could become linked with the original until quit.
  • Fixed a localization issue with the Method popup menu in the Web (HTTP) service.
  • Fixed layout of the Twitter and SMS notifier editors.
  • Fixed issues with selecting URLs to import in the Setup Assistant.
  • Fixed the Dock icon static animation getting mixed with the normal status icon when checking or uploading reports while the Licenses or Setup Assistant windows are displayed.
  • Changed the way relative dates are formatted, to make them more compatible with localizations.
  • While uploading a report, a circular progress indicator is now displayed in the Reports list.
  • Fixed the scheduler not starting until the Tests list is displayed.
  • Tweaked the layout of the Context views, and made localizable.

Download Simon 4.0.1 now!

BlogAssist 2.4.1 release

I know that I just said that BlogAssist doesn't need any updates, but apparently I was wrong: I had a report that the Substitutions preference page didn't behave properly under Yosemite. So here's a fix.

  • Fixed some issues with the Substitutions preference page.
  • Updated for OS X 10.10 (Yosemite).

Download BlogAssist 2.4.1 now!

Dejal year in review: 2014

Happy holidays!

Another year is coming to a close, so let's review what happened with the Dejal apps in 2014:

My flagship product to monitor websites and servers for changes and failures, Simon, had a big year in 2014. It started the year with the version 3.6 release, and ended it with the biggest upgrade ever, to version 4.0, with a radical new design and many significant improvements, both visible and behind the scenes.
My handy break reminder tool, Time Out, had several alpha builds of version 2 in 2014. Work on it kept being sidelined by other projects, mostly my contract work on Profile, but the builds slowly improved the app. As previously mentioned, everyone who makes a donation for Time Out now will be automatically eligible for the full-featured paid edition at no additional cost — so you can set your own price for it now! This offer expires when version 2 is released. This also makes you eligible for the alpha; after donating, tell me if you'd like to try it. Thank you to everyone who has already donated; the volume of donations is really encouraging.
Caboodle, my lean clean snippet machine, didn't see any updates in 2014. I do have a new version in the works, which I'll finish off and release once I get Time Out 2 out.
BlogAssist, my tool to help with HTML markup, also wasn't updated in 2014. It isn't a huge seller, so I'm not sure I can justify devoting much time to it. But I use it myself regularly, so I have no plans to discontinue it any time soon. It's basically great as-is.
I introduced a new app in 2014: Pack, a simple iPhone app to make it easy to pack for trips. I actually wrote this for myself a couple of years ago, and have used a pre-release build when packing for vacations and other travel of my wife and I ever since. This year, I polished it up and released it as my first freemium app. Try it for your next trip!
Tweeps, an app for iOS to easily manage Twitter accounts, was updated to version 3.3, including the modern iOS 7 & 8 look.
Profile, a client for Intrahealth's powerful medical practice management system, is where I spent the vast majority of my time once again, as contract work. It hasn't had a public update for a while, but internal builds have numerous improvements. I'm no longer working on this project; Intrahealth has taken maintenance of it in-house, as they continue to grow and refine their processes.

So what's coming up in 2015? Firstly, I am currently looking for a new contract or salary position as an iOS and/or Mac developer. I enjoy working on the Dejal products, but they don't bring in enough to pay the bills, so have always been more of a hobby side-business for me. If you have any leads, please refer them to me; see the Dejal consulting page for more about me.

For my own projects, I am about to get back to work on Time Out 2.0 — full-time until I find some other project. I'll also have updates of Simon, Caboodle and others throughout the year.

Thank you and welcome to my new customers, and many thanks to my long-term customers who are still enjoying my apps. I really appreciate your support. I'm very excited about the much-anticipated Time Out 2 release in the coming year, and other projects in the pipeline.

Syndicate content