development

Dejal Open Source

Like anything else, source code can get a bit untidy and crusty after a while. My Cocoa code is about a decade old now, so it's been well overdue for a cleanup. I have just completed a process of reorganizing all of the source code for future Lion and iOS 5 editions of Dejal projects.

One of the changes was to rename the open source and shared code to use a "Dejal" prefix rather than "DS" as before. Although I could have left the old prefix, the new one fits better with Apple's naming guidelines: they reserve all two-letter prefixes for themselves.

I also rearranged the code. I used to have separate Subversion repositories for iOS and Mac projects. Now I have code for iOS, Mac and cross-platform mixed in a repository, since there is a lot of code that works on both platforms.

Another big change was to move the open source projects from Subversion to Git. I've long resisted Git, but it really seems the most popular version control system for open source projects. And GitHub is a very nice way to share Git projects, with handy online viewing of documentation and source code, plus useful additions like issue tracking.

So now my open source projects are hosted on GitHub. You can view my GitHub page. Initially I have two iOS projects there: the small DejalView (formerly DSView) project, plus the very popular DejalActivityView (formerly DSActivityView) project. I will add more over time.

These projects have been updated for iOS 5 and ARC, plus general code improvements. They also have new README and license files. They use a standard BSD license.

I also took this opportunity to redesign the Dejal Developer page. The design was inspired by Matt Gemmell's excellent open source page (I got his permission to copy his design, but it ended up somewhat different).

Take a look at the new pages:

  • Dejal Open Source: the main Developer page, with general information at the top, followed by information on each project (with more to come).
  • Dejal Open Source License: a summary of my intention with the license for the source code, suggested attribution format, and the legal text.
  • Developer Store: enables you to donate in appreciation for the open source, or buy non-attribution licenses.

This last is interesting — the open source is available completely free, provided that you give Dejal credit in your app's About view, documentation, or website. If the code helps you, it's often nice to give something back, so now you can express appreciation via a donation or (just for fun, also inspired by Matt) a gift from my Amazon.com Wish List. But if you can't or don't want to give credit, you can purchase licenses that allow you to use the code without having to give credit. Hopefully that'll cover everyone's needs, but if you have some other requirement, let me know.

I hope you find my open source code useful. Keep an eye out for future additions; I have a Mac project coming soon (a feature that will appear in Time Out 2).

You can follow @dejalopen on Twitter to get notifications of updates, or follow @dejal for general Dejal news. You can also subscribe to the RSS feed of the Dejal Blog filtered for Open Source topics.

DejalActivityView: open source iOS project to display an activity indicator with adjustable text

Note: this is an updated post based on an older one, due to renaming the project (was "DSActivityView") and moving it from Subversion to GitHub.

DejalActivityViewI wrote a reusable class for a couple of iOS apps I was working on, called DejalActivityView. I decided to release it as open source. It has proven quite popular with others, too. Read on for details (including a demo movie).

Firstly, I should say that this work was inspired in part by Matt Gallagher's excellent article, Showing a "Loading..." message over the iPhone keyboard. My code only uses the -keyboardView method from his article, but he deserves credit and thanks for that and many other helpful articles. If you're not reading his blog, Cocoa with Love, you're doing yourself a disservice.

Back to my class. Actually, there are four classes: DejalActivityView, DejalWhiteActivityView, DejalBezelActivityView, and DejalKeyboardActivityView. They provide four styles of activity view, and could easily be extended to support more.

DejalActivityView

DejalActivityViewThis does a simple horizontal-style loading view, intended for situations where you have a blank view while loading data. It can be displayed very easily — for the default "Loading..." label text, simply use:

[DejalActivityView activityViewForView:self.view];

The activity view is automatically added as a subview of the specified view (e.g. the current content view). No need to save the result to an ivar. It automatically supports rotation to any orientation, too.

You can specify a custom label via:

[DejalActivityView activityViewForView:self.view withLabel:@"Processing..."];

Or specify a custom width, e.g. so you can change the label while it is being displayed without upsetting the geometry, via:

[DejalActivityView activityViewForView:self.view withLabel:@"Connecting..." width:100];

You can also have it manage the network activity indicator in the status bar, via a showNetworkActivityIndicator boolean property. It is NO by default, but if set to YES the network activity indicator in the status bar will be displayed, and automatically hidden when the DejalActivityView is removed. For example:

[DejalActivityView activityViewForView:self.view].showNetworkActivityIndicator = YES;

You can also toggle it on an already-visible activity view:

[DejalActivityView currentActivityView].showNetworkActivityIndicator = YES;

When you're done with the activity view, simply invoke this to get rid of it:

[DejalActivityView removeView];

DejalWhiteActivityView

This is the same as DejalActivityView, but with a white indicator and text instead of black, for use in dark views.

DejalBezelActivityView

DejalBezelActivityViewThis is a subclass of DejalActivityView, which displays an animated round-rect-enclosed variation: it animates into view by zooming from full-screen, with a gray background fading in to cover the passed view, and animates out by zooming to half size and fading out the background (see below for a movie showing it in action). It is ideal for situations where you have content visible already, but want to do a network operation to validate or send data, or some other time-consuming activity.

Display it the same way as DejalActivityView:

[DejalBezelActivityView activityViewForView:self.view];

The [DejalBezelActivityView activityViewForView:withLabel:] and [DejalBezelActivityView activityViewForView:withLabel:width:] variations are also available.

You can also split the label over multiple lines, thanks to a change contributed by Suleman Sidat. Thank you! Simply include one or more \n sequences in the label text, e.g.

[DejalBezelActivityView activityViewForView:self.view withLabel:@"Split over\nMultiple lines..."]

Similarly, to display an activity view with just the activity indicator, and no label, simply specify a blank label:

[DejalBezelActivityView activityViewForView:self.view withLabel:@""]

To remove with animation, call:

[DejalBezelActivityView removeViewAnimated:YES];

DejalKeyboardActivityView

DejalKeyboardActivityViewThis is a subclass of DejalBezelActivityView, which displays over the keyboard, somewhat like the iOS 2 Text app used to do. It is useful to simply prevent further typing while validating a field or sending data (though you might also want to disable the field, to prevent pasteboard operations on it). No need to specify a view to use for this, since it uses the keyboard. (This class may be removed in the future, since it isn't that useful nowadays; let me know if you need it.)

[DejalKeyboardActivityView activityView];

Plus a [DejalKeyboardActivityView activityViewWithLabel:] variation for custom text. Remove it the same as for the bezel style:

[DejalKeyboardActivityView removeViewAnimated:YES];

Demo

I've included a demo project that builds an app to show the various options: the four styles, default or custom label text, covering just the content view or whole window, etc. It requires iOS 5. Here's a movie of an earlier version of the demo, showing the demo app running (this project used to be called "DSActivityView"):

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

DejalView: open source project for iOS to detect a tap outside a button

Note: this is an updated post based on an older one, due to renaming the project (was "DSView") and moving it from Subversion to GitHub.

In Tweeps I have a button that I wanted to behave like the Delete button in a table view. You know, when you tap the delete toggle to the left of a cell, a red Delete button appears. And tapping anywhere other than that button will hide it without doing anything else:

Table Delete button
(Contacts app)

I couldn't see any obvious way to do it, so asked on the iPhone Developer forums, and got a helpful reply suggesting a UIWindow subclass, overriding -sendEvent:.

I tried implementing that, but what I really wanted was to override -hitTest:withEvent:, since I wanted to block taps on views other than a specific button, and the documentation says one should always invoke the superclass of -sendEvent:.

Then I noticed that -hitTest:withEvent: is actually defined in UIView, and further experimenting with the table Delete feature showed that it appears to be implemented UITableView, since the cancel tap behavior only occurs in the table, not the navigation bar or toolbar. Besides, implementing in a UIView subclass is more focal, so a better choice.

So here is my UIView subclass to do this. It uses a delegate approach, with a protocol to declare the method:

@class DejalView;

@protocol DejalViewDelegate <NSObject>
@optional

- (UIView *)view:(DejalView *)view hitTest:(CGPoint)point withEvent:(UIEvent *)event hitView:(UIView *)hitView;

@end

And the actual subclass interface:

@interface DejalView : UIView

@property (nonatomic, weak) id <DejalViewDelegate> viewDelegate;

@end

With the implementation just overriding the hit test method. It simply invokes the superclass then gives the delegate a chance to change it (or perform some other action) if it implements the delegate protocol method:

#import "DejalView.h"


@implementation DejalView

@synthesize viewDelegate = dejalViewDelegate;

/*
  hitTest:withEvent:
 
  Overrides this method to add support for the -view:hitTest:withEvent:hitView view delegate behavior.
 
  Written by DJS 2009-09.
*/

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
{
    UIView *hitView = [super hitTest:point withEvent:event];
   
    if ([self.viewDelegate respondsToSelector:@selector(view:hitTest:withEvent:hitView:)])
        return [self.viewDelegate view:self hitTest:point withEvent:event
            hitView:hitView];
    else
        return hitView;
}

@end

To use this, simply change a container UIView to DejalView in the view hierarchy, then set the delegate property to your view controller (via code or IB):

self.view.viewDelegate = self;

Then implement the -view:hitTest:withEvent:hitView: delegate method in your view controller, e.g. as follows — this will cause a tap on some special control (or if that control is hidden) to go through as normal, but tapping anywhere else in the view will hide the special control, without passing the tap on to whatever was actually tapped:

- (UIView *)view:(DSView *)view hitTest:(CGPoint)point
        withEvent:(UIEvent *)event hitView:(UIView *)hitView;
{
    if (someSpecialControl.hidden || hitView == someSpecialControl)
        return hitView;
   
    someSpecialControl.hidden = YES;
   
    return nil;
}

I hope this is useful to someone else too.

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

DSActivityView updated to support multiple label lines, and demo of no label

This blog post has been replaced by a newer edition.

Please see blog posts on DejalActivityView.

DSActivityViewI've committed a minor update to the DSActivityView open source project for iOS. See the DSActivityView introductory post for more information, including a video demo.

This update adds support for splitting the label over multiple lines for the DSBezelActivityView variation. This change was contributed by Suleman Sidat. Thank you!

To use multiple lines for the label, simply include one or more \n sequences in the label text, e.g.


[DSBezelActivityView newActivityViewForView:self.view withLabel:@"Split over\nMultiple lines..."]

Similarly, to display an activity view with just the activity indicator, and no label, simply specify a blank label:


[DSBezelActivityView newActivityViewForView:self.view withLabel:@""]

DSActivityView is compatible with iOS 3.0 and later, including iOS 4 (and I believe iOS 5), on iPad, iPhone and iPod touch.

You can get the project from my Dejal Open Source Subversion repository via this Terminal command:


svn checkout http://dejal.svn.beanstalkapp.com/open/DSActivityView

Or browse the source directly on the web.

If you make any enhancements to DSActivityView, please contribute them back to me so I can share with other developers.

DSActivityView updated for iOS 4

This blog post has been replaced by a newer edition.

Please see blog posts on DejalActivityView.

DSActivityViewI've committed a minor update to the DSActivityView open source project for iOS. See the DSActivityView introductory post for more information, including a video demo.

This update adds a new prefix to the class methods to create the activity view, to make it more clear that they return a retained object, rather than autoreleased.

So you'd now display the activity view via something like this:


[DSActivityView newActivityViewForView:self.view]

This update also includes a bug fix for iOS 4, where the activity view would appear behind the keyboard.

It is compatible with iOS 3.0 and later, including iOS 4, on iPad, iPhone and iPod touch.

You can get the project from my Dejal Open Source Subversion repository via this Terminal command:


svn checkout http://dejal.svn.beanstalkapp.com/open/DSActivityView

Or browse the source directly on the web.

Building Universal iPad/iPhone apps

Warning, developer topic... uninterested customers can skip on to the next post....

I was having difficulty getting my new app, Tweeps, working as a universal app: running natively on both iPhone and iPad from one binary.

For some reason, I was in a Mac universal mindset. For Mac apps, a universal app uses two separate targets: one for PowerPC, one for Intel. This is necessary since they are of course very different architectures, so have to be compiled separately.

In iPhone OS, that isn't the case — both iPhone and iPad have the same processor architecture, so both editions can use the same code without needing conditional compilation.

However, there are important differences. Currently, iPhone (and iPod touch) is on iPhone OS 3.1.3, whereas iPad is on OS 3.2. The iPhone can't use OS 3.2, and iPad can't use 3.1. So extra steps are required.

The way this works is to set the Base SDK to the latest one you want to use (in this case 3.2), and the Deployment Target to the earliest you want to support (3.1). You can then use any available APIs from 3.1 and earlier with impunity, and can use APIs from 3.2 if you check that they are available before using them.

The recommended way to check for a new method is to use +instancesRespondToSelector:. For example, 3.2 renames the method to hide the status bar. So to use the new method if available, or fall back to the old method, you'd write:

    if ([UIApplication instancesRespondToSelector:@selector(setStatusBarHidden:withAnimation:)])
        [[UIApplication sharedApplication] setStatusBarHidden:hiding withAnimation:YES];
    else
        [[UIApplication sharedApplication] setStatusBarHidden:hiding animated:YES];

Sometimes, you need to take alternative code paths depending on whether you're running on iPad or iPhone. So the way to do that is:

    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
        ...

I used the following definition to save some typing:

#define IS_IPAD        (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)

That's all fine and good. But things get more curly when you want to use new classes. Apple's TopPaid sample code demonstrates the best way to handle this. You can load different xibs depending on which device you're running on, to set up the root views (e.g. to use a split view or a navigation controller). Each xib would load a different controller, which would contain relevant properties for each view.

There's one final gotcha that caused me trouble recently: you have to be careful about programmatically allocating newly introduced classes. If you run and it crashes with an error like the following, this is the issue:

dyld: Symbol not found: _OBJC_CLASS_$_UIPopoverController

The solution is to use NSClassFromString to resolve the class name, like so:

    Class popoverClass = NSClassFromString(@"UIPopoverController");
   
    if (popoverClass != nil)
        popoverController = [[popoverClass alloc] initWithContentViewController:contentViewController];

What got me was that this is necessary even in code only called on iPad, which I thought counter-intuitive. But it makes sense on further reflection, as the dynamic nature of ObjC means that it wants to resolve all class references when loading the bundle on startup — it doesn't know that that code won't ever get called if the current device happens to be an iPhone.

I hope this helps others having difficulty building universal iPhone/iPad apps.

More Tweeps for iPad mockups

I previously posted some mockups of an iPad edition of Tweeps, my new iPhone OS app to easily manage Twitter accounts.

But as I said, I've been struggling with coming up with a satisfactory design. My latest thought is that I shouldn't try to emulate a physical object like a notepad or book, but would be better following Apple's example with apps like Mail, and use a split view.

So here are my latest design mockups (again done in OmniGraffle).

In landscape orientation, a split view shows a list of pages on the left, and the details on the right; in this case, the main Profile page for your own account (click to see full-sized):

In portrait, the left view appears in a "popover" instead:

I'm not sure if the Edit button should be in the left or right views... but there's more room in the right, so that seems reasonable.

Here's a sample of the Following page in landscape:

What do you think? I think this is a better design, more clean and consistent — and allows me to use much the same color theme as on the iPhone, as a minor bonus.

Tweeps for iPad mockup

Now that Tweeps is available in the iPhone App Store, I'm starting work on the iPad edition.

Obviously, the iPad has a lot more screen space than the iPhone, so a different design is needed to take full advantage of this extras space. I've been thinking about iPad design concepts ever since the iPad was announced, but have yet to come up with something that entirely satisfies me.

A difficulty with coming up with a good design is that Tweeps can show any number of levels. You start with a list of your accounts, then show your profile overview, then can show a list of people you're following (for example), then delve deeper by showing the profile overview for one of them, and their followers, and so on to any number of levels. This works fine with the navigation display in the iPhone edition, where you can keep pushing views onto the screen, but is harder with a more traditional interface.

For quite a while, I've been thinking about something like the iPad Contacts app design, with a two-page book metaphor. The idea would be to display the profile details on the left page, and the avatar, web, map, following, followers etc views on the right (one at a time). It'd then flip the page when viewing a different person's information. That seems like a reasonable approach, though the very different content displays on the right seems to break the book metaphor.

The latest idea I've been exploring is more of a notepad metaphor. The idea is a single notepad page with the profile overview, and bookmark tabs (like Post-it® flags) sticking out from the right for related pages like those listed above. So you touch a tab to flip the notepad to that page. There would also be bookmark tabs on the left side to go back to the previous profile(s) or the accounts list.

Here's a rough mockup, done in the great OmniGraffle (click to see full-sized):

Don't worry about the fine details; as I said, it's very rough.

You'd tap the Following tab on the right to flip pages to the Following view, which would be similar to that in Tweeps now, except would have room to show more information about each person:

You could then go back to the profile overview via the new tab with the avatar icon on the left, or go straight to other pages via the other tabs on the right.

If you tap a row in the Following list, it'd flip the page to the profile overview for that person, and the tabs on the right would then show more information about them.

It might look better with a black background, to merge into the iPad bezel, as follows. In which case I'd eliminate the space around the edges (still shown in this mockup), providing more room for the content:

What do you think? Would this design work, or am I on the wrong track? Should I forget about trying for a real-world look? I'd love to hear other design ideas too.

In App Purchase for free iPhone apps

iPhone App StoreI've been meaning to follow up on this for the past few days. Back in March, I wrote a blog post titled "Apple, please support iPhone trial apps", where I urged Apple to reconsider their position that "free apps will always be free."

I discussed how not allowing free apps to use the new In App Purchase feature introduced in iPhone OS 3.0 was very limiting — this restriction prevented free trials of apps, as is very popular in Mac software, meaning that the only way a developer could provide a trial is to have two separate apps (e.g. Lite and Pro editions), which is more hassle for the developer and customers, including issues with migrating data.

So you can imagine how pleased I was when I learnt recently that Apple has removed this restriction. Now any app can use In App Purchase, including free ones. So now the trial distribution model is finally feasible.

There is no more need to release Lite and Pro editions. Developers can release a single application as a free download, perhaps with limited features, then enable people to purchase an upgrade to the "full" edition.

There are still some restrictions. The most common trial model for Mac software is a time-limited demo, where the application lets people evaluate it for a certain time period (e.g. 14 or 30 days), then disables some functionality if still not purchased. My Mac apps do this, as do most other "shareware" apps. This is not allowed on the iPhone App Store. iPhone apps have to be fully functional, and can't disable essential features.

But there are other things that can be done, like provide a basic set of functionality, and perhaps display ads, then the purchase provides extended functionality and removes the ads. So the app remains useful forever as a free app, but becomes more powerful (and without distracting ads) if the user chooses to purchase.

This is what I plan to do for my future iPhone apps. For the secret project I'm working on now, I'll release it as a free app, with ads and perhaps some feature limits (but nothing that impinges on the usability), then offer In App Purchase to disable the ads and extend the functionality.

It looks like it'll be quite easy to set up; for this sort of situation, it appears that the purchase can be handled entirely via Apple's server, storing the purchase in the iTunes account, enabling multiple phones used by that account to use the full app (e.g. when buying a new phone).

I haven't decided on terminology for this yet... call the purchased upgrade the "Paid" or "Premium" edition, or something else.

It'll be most interesting to see how many app developers adopt this technique. I've seen a few so far, and expect it to be quite popular, especially with the indie developers who are used to this kind of distribution model.

But, thank you Apple!

A fork of DSActivityView: WTFeedbackView

iPhone developers: you may have seen my DSActivityView open source project. Another developer was inspired by it, and created his own variation, WTFeedbackView, with support for progress bars, among other changes.

Here's his introduction:

WTFeedbackView is a class to display a HUD-like view with either an activity indicator view or a progress view. It's based on DSActivityView by David Sinclair (http://www.dejal.com/developer/dsactivityview), with some significant additions and modifications.

More specifically, WTFeedbackView offers:

  • Client access through a single class, by means of class methods only, for all features;
  • Changes to the text being shown trigger an animation that resizes the HUD view appropriately;
  • Three built-in styles (like DSActivityView):
    • Simple style: displays a transparent view containing an activity indicator view next to the text explaining the ongoing activity;
    • Bezel style: displays a dark semi-transparent view containing either an activity indicator view or a progress view, above the text explaining the ongoing activity;
    • Keyboard style: same as the Bezel style, but covering only the keyboard;
  • Three built-in kinds:
    • Activity kind: displays only an activity indicator view, plus the text explaining the ongoing activity;
    • Progress kind: displays only a progress view, plus the text explaining the ongoing activity;
    • Flexible kind: contains both an activity indicator view and a progress view (plus the text explaining the ongoing activity), but displays only one at a time, on demand. This is useful when the ongoing activity has parts whose lengths are sometimes known and sometimes unknown. Rather than create a new feedback view for each part, a single one can be used, minimizing screen distractions;
  • Easy updating of the progress view, through a class method +updateProgress: (CGFloat) progress;
  • Thread-safety where needed. For instance, +updateProgress: can be safely invoked from a background thread;
  • Possibility to subclass WTFeedbackView to create custom feedback views having the general behavior of WTFeedbackView but looking differently.

Although iPhoneOS 3.x isn't a requirement, WTFeedbackView and the demo were compiled using iPhoneOS 3.1.2. They were *not* tested on any version prior to 3.x, so don't assume they will work with 2.x.

Enjoy!
Wagner

I'm hosting it for him: download now.

iPhone Open Source: detect tap outside a button like table's Delete

This blog post has been replaced by a newer edition.

Please see blog posts on DejalView.

Sorry customers, another development blog post. :)

For a new iPhone app I'm working on (shh), I have a button that I wanted to behave like the Delete button in a table view. You know, when you tap the delete toggle to the left of a cell, a red Delete button appears. And tapping anywhere other than that button will hide it without doing anything else:

Table Delete button
(Contacts app)

I couldn't see any obvious way to do it, so asked on the iPhone Developer forums, and got a helpful reply suggesting a UIWindow subclass, overriding -sendEvent:.

I tried implementing that, but what I really wanted was to override -hitTest:withEvent:, since I wanted to block taps on views other than a specific button, and the documentation says one should always invoke the superclass of -sendEvent:.

Then I noticed that -hitTest:withEvent: is actually defined in UIView, and further experimenting with the table Delete feature showed that it appears to be implemented UITableView, since the cancel tap behavior only occurs in the table, not the navigation bar or toolbar. Besides, implementing in a UIView subclass is more focal, so a better choice.

So here is my UIView subclass to do this. It uses a delegate approach, with a protocol to declare the method:


@class DSView;

@protocol DSViewDelegate
@optional

- (UIView *)view:(DSView *)view hitTest:(CGPoint)point
withEvent:(UIEvent *)event hitView:(UIView *)hitView;

@end

And the actual subclass interface; as conventional, the delegate is not retained:


@interface DSView : UIView
{
id DS_viewDelegate;
}

@property (nonatomic, assign) id viewDelegate;

@end

With the implementation just overriding the hit test method. It simply invokes the superclass then gives the delegate a chance to change it (or perform some other action) if it implements the delegate protocol method:


#import "DSView.h"

@implementation DSView

@synthesize viewDelegate = DS_viewDelegate;

/*
hitTest:withEvent:

Overrides this method to add support for the -view:hitTest:withEvent:hitView view delegate behavior.

Written by DJS 2009-09.
*/

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
{
UIView *hitView = [super hitTest:point withEvent:event];

if ([self.viewDelegate respondsToSelector:
@selector(view:hitTest:withEvent:hitView:)])
return [self.viewDelegate view:self hitTest:point
withEvent:event hitView:hitView];
else
return hitView;
}

@end

To use this, simply change a container UIView to DSView in the view hierarchy, then set the delegate property to your view controller (via code or IB):

self.view.viewDelegate = self;

Then implement the -view:hitTest:withEvent:hitView: delegate method in your view controller, e.g. as follows — this will cause a tap on some special control to go through as normal, but tapping anywhere else in the view will hide the special control, without passing the tap on to whatever was actually tapped:


- (UIView *)view:(DSView *)view hitTest:(CGPoint)point
withEvent:(UIEvent *)event hitView:(UIView *)hitView;
{
if (hitView == someSpecialControl)
return hitView;

someSpecialControl.hidden = YES;

return nil;
}

I hope this is useful to someone else too.

You can get the code from my Dejal Open Source Subversion repository via this Terminal command:


svn checkout http://dejal.svn.beanstalkapp.com/open/DSView

Or browse the source directly on the web.

If you haven't seen it already, check out DSActivityView, too.

DSActivityView updated

This blog post has been replaced by a newer edition.

Please see blog posts on DejalActivityView.

DSActivityViewI've committed a minor update to the DSActivityView open source project for iPhone. See the DSActivityView introductory post for more information, including a video demo.

This update adds a showNetworkActivityIndicator boolean property. It is NO by default, but if set to YES the network activity indicator in the status bar will be displayed, and automatically hidden when the DSActivityView is removed.

You can toggle this property as needed while the activity view is in use. For example, you might have the network activity indicator appear while fetching some data from the internet, then disable it while parsing it (while the activity view is still visible).

Of course, you can easily show and hide the network activity indicator yourself, but this tweak saves having to remember to disable both it and the DSActivityView.

You can set this property via:


[DSActivityView activityViewForView:self.view].showNetworkActivityIndicator = YES;

or to toggle it on an already-visible activity view:


[DSActivityView currentActivityView].showNetworkActivityIndicator = YES;

You can get the project from my Dejal Open Source Subversion repository via this Terminal command:


svn checkout http://dejal.svn.beanstalkapp.com/open/DSActivityView

Or browse the source directly on the web.

Announcing DSActivityView: open source for iPhone developers

This blog post has been replaced by a newer edition.

Please see blog posts on DejalActivityView.

DSActivityViewI recently wrote a reusable class for a couple of iPhone apps I'm currently working on, called DSActivityView. I decided to release it as open source. Read on for details.

Firstly, I should say that this work was inspired in part by Matt Gallagher's excellent article, Showing a "Loading..." message over the iPhone keyboard. My code only uses the -keyboardView method from his article, but he deserves credit and thanks for that and many other helpful articles. If you're not reading his blog, Cocoa with Love, you're doing yourself a disservice.

Back to my class. Actually, there are three classes: DSActivityView, DSBezelActivityView, and DSKeyboardActivityView. They provide three styles of activity view, and could easily be extended to support more.

DSActivityView

DSActivityViewThis does a simple horizontal-style loading view, intended for situations where you have a blank view while loading data. It can be displayed very easily — for the default "Loading..." label text, simply use:

[DSActivityView activityViewForView:self.view];

The activity view is automatically added as a subview of the specified view (e.g. the current content view). No need to save the result to an ivar. It automatically supports rotation to any orientation, too.

You can specify a custom label via:

[DSActivityView activityViewForView:self.view withLabel:@"Processing..."];

Or specify a custom width, e.g. so you can change the label while it is being displayed without upsetting the geometry, via:

[DSActivityView activityViewForView:self.view withLabel:@"Connecting..." width:100];

Then when you're done with it, simply invoke this to get rid of it:

[DSActivityView removeView];

DSBezelActivityView

DSBezelActivityViewThis is a subclass of DSActivityView, which displays an animated round-rect-enclosed variation: it animates into view by zooming from full-screen, with a gray background fading in to cover the passed view, and animates out by zooming to half size and fading out the background (see below for a movie showing it in action). It is ideal for situations where you have content visible already, but want to do a network operation to validate or send data, or some other time-consuming activity.

Display it via:

[DSBezelActivityView activityViewForView:self.view];

The [DSBezelActivityView activityViewForView:withLabel:] and [DSBezelActivityView activityViewForView:withLabel:width:] variations are also available. To remove with animation, call:

[DSBezelActivityView removeViewAnimated:YES];

DSKeyboardActivityView

DSKeyboardActivityViewThis is a subclass of DSBezelActivityView, which displays over the keyboard, somewhat like the OS 2 Text app used to do. It is useful to simply prevent further typing while validating a field or sending data (though you might also want to disable the field, to prevent pasteboard operations on it). No need to specify a view to use for this, since it uses the keyboard:

[DSKeyboardActivityView activityView];

Plus a [DSKeyboardActivityView activityViewWithLabel:] variation for custom text. Remove it the same as for the bezel style:

[DSKeyboardActivityView removeViewAnimated:YES];

Demo

I've included a demo project that builds an app to show the various options: the three styles, default or custom label text, covering just the content view or whole window, etc. It requires iPhone OS 3. Here's a movie showing the demo app running:

You can get the project from my Dejal Open Source Subversion repository via this Terminal command:

svn checkout http://dejal.svn.beanstalkapp.com/open/DSActivityView

Or browse the source directly on the web.

You can also download a snapshot, though it may not remain up-to-date; using Subversion is the recommended approach.

Follow @dejalopen on Twitter for automated Subversion commit message updates. You may also like to follow @dejaldevdiary for my behind-the-scenes development diary, and @dejal for general Dejal and personal tweets. Finally, there's also a RSS feed for the repository.

I hope these classes are useful. You are welcome to use them in any project, commercial or otherwise. I just ask that you give me credit; see the DSActivityView header for the easy and free licensing terms. If you do use this code in any form, please tell me (or comment here).

If you make improvements, e.g. to add other activity styles or fix bugs, please send them to me so I can share them with the community. Thanks.

Enjoy!

Update: see also an update to optionally support the network indicator, and an update for iOS 4.

Anatomy of a feature

Brent Simmons wrote today about the anatomy of a feature, an article that really resonated with me.

It's tempting to think that adding a feature like this is just about adding the functionality — but there's a bunch more to it than that.

I get a lot of feature requests for my apps, which are certainly very welcome. But I think a lot of people don't quite realize how much work even the most trivial-sounding feature enhancement can be.

Brent gives a very clear and accurate picture of the process many developers, myself included, go through when considering and implementing such changes. Every aspect of them needs to be carefully analyzed and refined. Perhaps someone asks for a specific feature, but I can tell that what they really want is something different — they just came up with what sounded to them like an easy compromise, when the ideal solution might in fact be easier, as well as better for the overall app. Happens all the time.

But as I said, I do really value feature requests (and bug reports). I want my apps to work well and be as helpful as possible to my customers. For that reason, I keep track of such requests for each app, and also keep a running tally of "votes" for each feature (which sometimes requires some interpretation when different people have different takes on something). When lots of people are asking for the same thing, it rises to the top of my list, and I make it a priority for the next release. But only if I can do it in a way that is consistent with the design goals of the app. That's the tricky part.

Good thing I enjoy planning; I spend much more time analyzing and planning features than actually writing them.

Follow @dejaldevdiary for David's Dev Diary

Just thought I'd mention for any Mac or iPhone developers who read my blog, or customers who are interested in a behind-the-scenes look at my development process:

I recently joined the club and created a David's Dev Diary account on Twitter. It is a separate place for me to post a potentially boring diary of my development work. It is focused purely on the technical aspects that I normally wouldn't bother mentioning on my main Twitter account, @dejal.

Follow @dejaldevdiary on Twitter for all the highly exciting technical details (and maybe a hint or two about what's coming up... e.g. I'm currently working on a secret new iPhone app).

For a list of other developers writing diaries, check out the Dev Diaries website.

Not enough time!

I'm feeling vexed. I have several projects I'd like to work on, but just don't have the time at present.

I have big plans for Simon, Time Out, Caboodle, and my other apps. Each has a long list of great feature enhancements for the next several versions.

Plus I'm working on a semi-secret new project that includes an iPhone app and Mac app that sync via a web app. That is somewhat vexing in itself; it takes time to develop quality software, so that time delays updates to my other products. (I do plan on sneaking in some updates soon, though.)

To top it off, I also do contract work for a client in New Zealand, and am currently working on a major update for that, with a fixed deadline that pushes other work aside. I might do an iPhone app for them, too.

I work seven days a week, doing Dejal customer support mostly in the mornings and development in the afternoons and evenings. But I wish I had more hours in the day so I could do more — or more precisely, get things done more quickly. I'll get to everything eventually, but just not as quickly as I'd like.

I have a really high-tech scheduling system, consisting of printed pages for each quarter of the year, and Post-It notes of different colors for each project:

Schedule

(The gaps are reserved for safety margins, if things take longer than expected, as they often do, and for bug-fix updates.)

This is mounted near the ceiling on the wall in front of me, so I can easily see it whenever desired. It is frustrating when I have to move the Post-Its around due to things taking longer than expected, though... and more so when I have to push a project off the end of the year.

It should be noted that these Post-Its only represent significant updates and new projects; bug-fix updates and very minor updates can be snuck in at any time, as needed.

But enough moaning... I've got work to do!

Developers should iPhone-optimize their sites

A while ago I wrote how the Dejal site is iPhone-optimized: when you view it on an iPhone or iPod touch, the website content is reformatted to fit neatly in the 320-pixel-wide display:

I would suggest that any developers who write iPhone software should do this too. So here's some technical info on what I did. This isn't necessarily the best solution, but it works for me, and isn't very difficult.

Firstly, of course, you need to be able to detect whether you're running on an iPhone or elsewhere. The standard way to do this is by looking at the "user agent" value of the HTTP session. In PHP, you can simply look at the $_SERVER['HTTP_USER_AGENT'] global variable. I have the following function in a utility PHP file included on every page (via the header code):

    function getIsIPhonePlatform()
    {
        global $private_is_iphone_platform;
        
        if (isset($private_is_iphone_platform))
            return $private_is_iphone_platform;
        
        $user_agent = $_SERVER['HTTP_USER_AGENT'];
        $private_is_iphone_platform = stristr($user_agent, 'iPhone') || 
            stristr($user_agent, 'iPod') ||
            stristr($_GET['platform'], 'iPhone');
        
        return $private_is_iphone_platform;
    }

This function returns whether or not the user agent is an iPhone or iPod touch, either by returning the state if already known, or determining it if not. It also allows testing the iPhone-optimized pages from your Mac by adding "platform=iphone" to a page's URL parameters (try it; it's fun!).

Not everyone would want the pages optimized, though: a great thing about the iPhone is MobileSafari does an excellent job of rendering "real" web pages. So I also added a checkbox at the bottom of every page to toggle iPhone-optimized mode off and on. The state is recorded in cookie. So I have another function to read that, on the iPhone platform:

    function getIsIPhoneOptimized()
    {
        global $private_is_iphone_optimized;
        
        if (isset($private_is_iphone_optimized))
            return $private_is_iphone_optimized;
        
        $private_is_iphone_optimized = getIsIPhonePlatform();
        
        if ($private_is_iphone_optimized && isset($_COOKIE['iphone_optimized']))
            $private_is_iphone_optimized = $_COOKIE['iphone_optimized'];
       
        return $private_is_iphone_optimized;
    }
Then the checkbox is actually output (in the footer's include file) via somewhat messy code that uses PHP to output the JavaScript to set the cookie and reload the page when the checkbox is toggled, and the checkbox itself:
    function outputIPhoneOptimizationCheckbox()
    {
        if (getIsIPhonePlatform())
        {
            $query_params = $_SERVER['QUERY_STRING'];
            
            if ($query_params != '')
                $query_params = '?' . $query_params;
            
            echo('<script type="text/javascript">' . "\n");
            echo('<!--' . "\n\n");
            echo('function iphoneOptimizedToggled()' . "\n");
            echo('{' . "\n");
            echo('  document.cookie=\'iphone_optimized=' .
                !getIsIPhoneOptimized() . '; path=/\';' . "\n");
            echo('  window.location.reload(true);' . "\n");
            echo('}' . "\n\n");
            echo('-->' . "\n");
            echo('</script>' . "\n\n");
            
            echo('<p><input type="checkbox" id="iphone_optimized_checkbox"
                onclick="iphoneOptimizedToggled()"');
            
            if (getIsIPhoneOptimized())
                echo(' checked="checked"');
            
            echo(' /><span id="iphone_optimized_label"
                onclick="iphoneOptimizedToggled()">
                Display site optimized for iPhone</span></input>' . "\n");
            echo('</p>');
        }
    }
And finally, the getIsIPhoneOptimized() function is called in the header to use iPhone-optimized or normal style sheets. This is actually a simplification; it actually uses several style sheets, including some common ones and some platform-dependent ones. It also sets the viewport appropriately for each platform — that is a key aspect for iPhone optimization:
    if (getIsIPhoneOptimized())
    {
        echo('<link rel="stylesheet" href="/iphone/header.css"
            type="text/css" media="all" />' . "\n");
        echo('<meta name="viewport"
            content="width=device-width, user-scalable=no" />' . "\n");
    }
    else
    {
        echo('<link rel="stylesheet" href="/mac/header.css"
            type="text/css" media="all" />' . "\n");
        echo('<meta name="viewport" content="width=900" />' . "\n");
    }
Of course, configuring the CSS appropriately is another story, but not too difficult... and very site-specific. Feel free to explore my CSS files if desired: I hope this is helpful. There are a number of other aspects, like fitting images in the available space, supporting movies that can play on the iPhone, and more. If there's interest, I might write more about this in the future.

Apple, please support iPhone trial apps

iPhone App StoreThe new in-app purchasing feature in iPhone OS 3.0, as discussed in the keynote, promises to be a great addition. But it seems one of the most popular uses of this feature has been deliberately blocked: a shareware-like trial model.

They said in the keynote that "free apps will always be free"... which sounds good on the surface, but eliminates one of the most popular software distribution models.

It would be great if users could download an app for free, try it for a while to evaluate (perhaps with feature restrictions or a time limit), then use in-app purchasing to buy the full edition.

Apple and developers would still get paid, but users would be able to better evaluate whether or not they want to purchase the product. With high quality products, that would lead to more overall sales — and people would vote with their dollars to encourage quality apps.

Without such a mechanism, the only way to let users try a product before buying is to release two editions, e.g. a free Lite edition and a paid Pro edition. While that has some merit in itself, it adds additional hassles for the users (e.g. transferring data between the two apps, since each app sandboxes its own data) and having to find the Pro edition, buy and download it, re-configure it, and probably delete the Lite edition.

With in-app purchasing available, there should be no technical barrier to allowing free trial apps that can be purchased after trying them out.

Developers, if you want this option, please tell Apple — file a bug report duplicating mine, rdar://problem/6699761 (this link only works for Apple employees).

The iPhone App Store is broken

iPhone App StoreWell, maybe not broken, but definitely showing some cracks.

The iPhone App Store is a great concept. One central place to get all applications for the iPhone; everything is there, and only there, available right on your iPhone.

But is it everything? Of course, Apple rightly filters out malware and illegal applications... but they have caused some controversy of late by refusing entry to generally useful applications. Worse, they've rejected a couple of apps recently because they "duplicate existing functionality".

One such is a podcasting app, which they say duplicates the iPod app... but seems to have offered other benefits. Another is a Gmail reader, which apparently is more convenient than using Apple's Mail or Safari to access Gmail.

Notice something in common there? Both rejected apps would have competed with Apple's own apps. Yet Apple has no problem with dozens of flashlight and sudoku apps. So it seems to a plain-and-simple anti-competitive move on Apple's part. That is not playing nicely, and potentially illegal. Governments tend to frown on that kind of behavior.

Why do I care?

I haven't written any iPhone apps yet... but I'd like to, in due course. I've written up a rough design for one new app, and would likely want to write companions for some of my Mac apps, like Dejal Simon. However, episodes like these make me and many other developers hesitate to begin, or continue.

Writing software is hard; it can take months to write even a relatively small application properly. What if we come up with a great idea, spend months of time designing, developing and polishing it, then submit it to the App Store, only to have it rejected based on some unannounced policy, or whim? That would be a huge waste of time and effort, which makes developers like me concerned about whether it's worth starting.

What Apple needs to do is provide a clear, detailed description of the iPhone App Store policies, and stick with it. Perhaps offer a contact point for discussions early on, to ensure that an app concept is worth pursuing.

And preferably make the policies as open as possible — none of this anti-competitive behavior. If someone writes an alternative email app or web browser, let them release it. If it is superior to Apple's, it will flourish, and everyone will benefit (including Apple, via their sales cut). If it isn't any good, it'll fade into obscurity.

It's really in Apple's interest to do this, to ensure developers put the effort into building quality apps for the platform. Apple, the ball is in your court. Make it right.

Mac OS X versions for Simon

Since a few people have asked, regarding my announcement yesterday that Simon version 2.4 now requires a minimum of Tiger (Mac OS X 10.4), here's the current breakdown of OS versions for Simon:

  • 63% are on 10.5.2
  • 30% are on 10.4.11
  • 3% are on 10.3.9
  • 4% are on other OS versions (10.4.x or 10.5.x)

So a few people will be affected by this change, but a relatively small number. And that's always dropping, as more people buy new machines or get around to upgrading their OS.

For people stuck on 10.3.9, I'm sorry for the inconvenience... but it had to happen eventually, and Simon 2.3.5 is a fine version.

Interestingly, Simon is a little ahead of the curve when it comes to Leopard adoption. For my other products (excluding Narrator 2, which requires Leopard), the percentage averages to about 56% on 10.5.2, 40% on 10.4.11, 3% on 10.3.9, and 1% on others. So I could drop 10.3.9 support for those too, though I won't until necessary. It'll definitely happen eventually, but not for a while.

Syndicate content