iphone

Anniversary of the iPhone launch

To celebrate the 10 year anniversary of the iPhone, I thought I'd repost this from a year ago. Happy birthday, iPhone!



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.

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.

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.

Fun rotation lock

Hey look, yet another hint on my secret new iPhone app!

This time, an unmasked close-up of one of the views, showing the animated rotation lock. This briefly appears and wobbles cutely when you rotate the iPhone or iPod touch. If you tap the lock, the current orientation is locked, so rotating the device won't change the screen orientation. Very handy if using it lying on your side, for example. You can unlock it just as easily. And the lock state is remembered, so you can have the app always remain portrait-only, or landscape-only, if you don't like rotation.

I'm on schedule to submit to Apple on Monday. Then the waiting game starts — will they approve in just hours, or days, or weeks? Exciting stuff!

Hey look, another hint

Ready for another hint on my secret new iPhone app?

Here's a similar view to last time, with an improved design:

And another non-pixelated one:

Who's excited to see the app? I'm hoping to release it in a week or so.

Another hint

Ready for another hint on my secret new iPhone app?

How about this highly-masked screenshot:

And another non-pixelated one:

Any guesses?

Upcoming iPhone app hint

I thought I'd drop another hint about my forthcoming iPhone app.

Here's a masked screenshot of one of its main views... can you make anything out?

Oh alright, one more... and not pixelated this time. Here's the Edit Location view:

Any ideas what the app does?!

Not too long to wait; it should be released within a few weeks. But if you can't wait that long, I'm adding a few more beta testers: apply now if you'd like to try it before release, and get a free copy if you provide helpful feedback.

iPhone In App Purchase considered harmful

I hinted before about my secret iPhone app, which is very near to being ready for release.

I was planning on distributing it as a free app with In App Purchase to upgrade to full functionality, as I discussed a while back.

But I've been rethinking that of late; while I really like the idea of In App Purchase (IAP), I don't think it really works in its current form. Many developers have reported that when they try free+IAP, they get lots of 1-star ratings via rate-on-delete from people who download the free app then delete it without really trying it. Plus they get lots of negative reviews from free downloaders who don't understand the concept of IAP and expect something for nothing.

I'm thinking that until Apple fixes this by removing the horrible rate-on-delete feature, and perhaps educates users more about IAP, this won't be a feasible distribution mechanism for demo-style purchases.

So, that leaves me with two options:

  1. Just release it as a paid app.
  2. Release free Lite and paid Pro editions.

I've done both variations (Valentines and SmileDial Lite & Pro), and neither was ideal. With option 1, people can't try the app to see if they want it, though apps are cheap enough that it isn't too much of a gamble to just buy. Whereas with option 2 people can try the free edition, but they have to re-enter their info if they switch to the Pro one, and I have to maintain two releases.

I'm currently leaning towards option 1: just release it as a paid app. Then maybe in the future I could add a free Lite edition, and perhaps offer IAP in that edition if there seems demand for it.

What do you think? Let me know in the comments or privately via my contact page.

My secret new iPhone app....

I thought I'd drop a hint about my secret new iPhone app, coming to the App Store in a few weeks. I have hinted a little about it via Twitter (@dejal), usually referring to it as [DEJALDACTED], a play on the "redacted" label often applied to new iPhone technologies covered by Non-Disclosure Agreements.

This app has been in development for about six months, part-time (around Dejal support, other app updates, and contract work). I'm still not ready to announce the app, but it won't be too much longer. The initial version will be for the iPhone and iPod touch. I also plan to add native support for the iPad, leveraging the larger screen, once it is available.

The app is currently being tested by a small group of beta testers. I will soon begin accepting a few more people into this elite beta group, to get some fresh perspectives and more testing. If you're interested, you can apply to become a Dejal beta tester.

Here's a little teaser of part of the app's icon:

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.

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).

App Store Reviews Now Distinguish Versions

TidBITS just published an article discussing how App Store Reviews Now Distinguish Versions. A recommended read.

I was quoted for this article. My full comment was:

I think that is a most welcome enhancement. Often, reviews mention deficiencies that are addressed in subsequent versions, but without a version number (and to a lesser extent, a date), potential customers have no real way of knowing if that comment is still relevant. For example, for my SmileDial Pro app, I had two reviews for version 1.0 saying it was a little slow to navigate, so I addressed that in version 1.1. Now, it is clear that those issues were with an older version.

(You can check out SmileDial via its product page.)

SmileDial Pro special price ending soon

When I released the version 1.1 update to SmileDial Pro, my fun iPhone app to call or text your favorite people, I also reduced the price from $3.99 to $1.99.

This was a temporary experiment, to see what would happen — would the reduced price be offset by increased sales volume? That has not eventuated; sales stayed about the same. So next week I'll be raising the price back to $3.99.

There has been much discussion on the intertubes recently about iPhone app pricing. It's recognized by most that $0.99 "ringtone apps" is not a sustainable pricing level for long-term success. While a few developers can make large amounts of money at such price points (mainly for really popular games), for most developers it simply doesn't work.

I am determined to continue supporting and improving my apps, so have to price them accordingly. If an app is too cheap, it doesn't justify the time it takes to improve it. So I can't support $0.99 or $1.99 prices, other than for quick "one day event" apps like Valentines.

Anyway, I thought I'd give notice of the price increase, so if you are considering buying SmileDial Pro, you can still get it at the special discounted price for another few days. Don't delay too long, though!

To learn more about SmileDial Pro, including seeing a video of its features, check out the SmileDial website!

Syndicate content