Category Archives: UX

Creating your first “Today” widget on iOS 8.0

Widgets? On iOS?!

At last us developers have been empowered by iOS to make our own widgets. This means that there are now additional available points of contact where we can interact with our users and allow them to use our app. Today we’re going to talk about one of these “extension points” – the “Today” extension.

The “Today” extensions are the ones available through the drop-down notification center drawer (that’s the one the comes down from the top). You’ll notice that in iOS 8.0, there’s a new “Edit” option there – it will open up a list of available extensions to add/remove to this “Today” view:

Screen Shot 2014-10-11 at 2.24.01 PM

I’m going to show you how to add a widget to your application that will be available in this list – this means that it will be available to your users once it’s added by them to their “Today” view.

Some things you should know about “Today” widgets

  • They are compiled separately as a standalone binary. This means that they can’t interact directly with your app or access the same memory.
  • They are essentially view controllers, meaning you can do anything you would do in a view controller within your widget. Keep in mind that the controls you put in there need to be accessed by the user in the notification center context so for instance UITextFields aren’t suitable for this controller (though they are technically addable).
  • They are required to be efficient. The basic guidelines for efficient data fetch, caching and proper multithreading is crucial here. Your app (and widget) will be rejected if your “Today” widget is unresponsive and interferes with the overall awesomeness of the notification center.

Ladies and gentlemen, this is your widget speaking

Time to create our widget.

What we’re going to do today is create a flight info widget that acts as a companion widget for a flight check-in app. It offers up-to-date information about your flight in terms of boarding, takeoff and landing times as well as an inline option to transfer to your application for the actual online checkin process (which we won’t actually implement due to laziness).

First things first, let’s actually create a new widget. In order to do this, go to your project details in XCode 6 by selecting the project file in the navigator and click the “+” on the bottom left in order to add a new target. Next, select “Application Extension” under iOS. You’ll see the following menu drop down:

 

Select “Today Extension” and click “Next”. Now you’ll notice a dialog that looks very similar to the one you get when opening a new project. Remember when I said it’s a separate binary?

OK, now let’s get down to business. You’ll notice a new folder in your project which will contain a designated storyboard and a single view controller class. The storyboard is there to help you define your widget layout as you would any controller. In our demo application, we’re going to add some static and dynamic labels, some image views with preset icons, 2 buttons, a title label and an activity indicator, so it would look something like this:

 

Now let’s write some code.

Updating your data

The underlying controller is where the magic happens. First thing you will notice is that your controller is pre populated with a widgetPerformUpdateWithCompletionHandler: method. This method requires you to perform your updates and notify the OS when you’re done with the following possible outcomes:

  • NCUpdateResultNewData – New data is available. The widget might need to update layout.
  • NCUpdateResultNoData – There was no new data available since the last update.
  • NCUpdateResultFailed – We’ve failed the attempt to update.

For the sake of simplicity, we’re going to work with a simulated fetch of our data. This is going to be performed by calling an asynchronous method with a block callback:

- (void)fetchFlightInfoWithCompletion:(FlightInfoCompletion)completion
{
    //Preset dummy info
    static NSString *boardingTime = @"12:05";
    static NSString *takeoffTime = @"12:35";
    static NSString *landingTime = @"18:10";
    static BOOL checkedIn = YES;

    //Simulate asynchronous fetch
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(5);

        //Update data
        self.lastKnownTakeoffTime = takeoffTime;
        self.lastKnownBoardingTime = boardingTime;
        self.lastKnownLandingTime = landingTime;
        self.lastKnownCheckinStatus = checkedIn;
        self.dataLoaded = YES;

        //Call completion on main thread
        dispatch_async(dispatch_get_main_queue(), ^{
            if (completion)
            {
                completion(YES,boardingTime,takeoffTime,landingTime,YES,nil);
            }
        });
    });
}

Every time the operating system is ready for updating our widget, it’s going to call the following widgetPerformUpdateWithCompletionHandler: method

- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler {
    if (!self.dataLoaded)
    {
        //Start fetch
        [self.aiLoading startAnimating];
        [self fetchFlightInfoWithCompletion:^(BOOL newData, NSString *boardingTime, NSString *takeoffTime, NSString *landingTime, BOOL checkedIn, NSError *err) {
            [self.aiLoading stopAnimating];
            if (err) //An error occured - make sure to notify the OS
            {
                completionHandler(NCUpdateResultFailed);
            }
            else if (newData) //New data is available - update UI and notify OS there's new data
            {
                self.lblBoarding.text = boardingTime;
                self.lblTakeoff.text = takeoffTime;
                self.lblLanding.text = landingTime;
                [self updateCheckinStatusUI:checkedIn];
                completionHandler(NCUpdateResultNewData);
            }
            else //Nothing new - no data to update
            {
                completionHandler(NCUpdateResultNoData);
            }
        }];
    }
    else
    {
        //refresh for next time and force refresh
        [self fetchFlightInfoWithCompletion:nil];
        self.dataLoaded = NO;

        //update UI
        self.lblBoarding.text = self.lastKnownBoardingTime;
        self.lblTakeoff.text = self.lastKnownTakeoffTime;
        self.lblLanding.text = self.lastKnownLandingTime;
        [self updateCheckinStatusUI:self.lastKnownCheckinStatus];

        //The data fetched is not new, make sure to notify the OS
        completionHandler(NCUpdateResultNoData);
    }
}

In order to make sure we’re updated before this is called, a good option would be to call the update method from viewWillAppear:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    //Make sure we're up to date next time we're viewed
    [self fetchFlightInfoWithCompletion:nil];
}

Layout

In terms of layout, we need to make sure to notify about our preferred content size. This is ideal to do in awakeFromNib:

- (void)awakeFromNib
{
    [super awakeFromNib];

    [self setPreferredContentSize:CGSizeMake(kMyFlightWidgetWidth, kMyFlightWidgetHeightWhenNotCheckedIn)];
}

User interactions and referring to your app

Now let’s take care of what happens when tapping “Check in”. What we want to do is open our app with additional data to handle. The best way to do this is with openURL (and of course implementing handleOpenURL in the target application). We’re not going to elaborate about this but make sure you register your app for the proper URL scheme so that it will be launched when calling openURL.

- (IBAction)checkInPressed:(id)sender
{
    [[self extensionContext] openURL:[NSURL URLWithString:@"myflight://check-in"] completionHandler:^(BOOL success) {

    }];
}

When the user wants to manually refresh the data, this is called:

- (IBAction)refreshPressed:(id)sender
{
    [self.aiLoading startAnimating];
    [self fetchFlightInfoWithCompletion:^(BOOL newData, NSString *boardingTime, NSString *takeoffTime, NSString *landingTime,BOOL checkedIn,NSError *err) {
        [self.aiLoading stopAnimating];
        if (newData)
        {
            self.lblBoarding.text = boardingTime;
            self.lblTakeoff.text = takeoffTime;
            self.lblLanding.text = landingTime;
            [self updateCheckinStatusUI:checkedIn];
        }
    }];
}

That’s all folks!

This is how our little flight widget appears in the notification center:


That’s about it – we now have a functioning widget available when your application is installed. You can try it by running its target as you would any other app. The dummy project and the widget itself can be downloaded here.

Icons used in the demo project were taken from icons8

 

Advertisements

Using notification actions

Hi guys! Sorry I was out of it for some time now, it was a pretty busy year so far.

I wanted to talk about a cool new feature available in iOS 8.0 – notification actions!

Once upon a push…

Up until iOS 7.0, whenever you got a remote notification while an app was in the background you could either:

A. Ignore it

B. Read it and ignore it

C. Read it and swipe / tap it

The third option would normally mean that the app gets relaunched and does whatever action it’s designated to do in that case (normally navigate to a certain screen in the app or do something general).

So what’s new?

Starting iOS 8.0, we have a fourth option:

D. Choose something to do from a menu

Think about the following scenario: You’ve created an awesome game that lets users around the world fight about nothing. Whenever someone challenges you to fight you should either respond with “Fight” or “Surrender”. Normally we’d present a notification to the user saying “dumdum86 has challenged you to fight about nothing” and you would need to open the notification by swiping (or tapping) and then choose either “Fight” or “Surrender” within the app. Luckily with the new notification actions we can let the user give us his input from the notification itself, which looks something like this:

Screen Shot 2014-08-30 at 4.14.32 PM

Note that the “Fight” and “Surrender” buttons appear to the user when he swipes the notification banner downward, otherwise the notification is simply standard text.

In the lock screen, the notification appears like it always had with one exception: you can now swipe to reveal actions:

 

Screen Shot 2014-08-30 at 4.17.14 PMScreen Shot 2014-08-30 at 4.17.11 PM

 

 

 

 

 

 

 

 

 

 

 

 

 

On a side note: If the user has chosen to display alerts from your app within an alert rather than a banner, he’ll see the message along with “close” and “options” buttons. If he taps “options”, he’ll see a second alert with the same message but this time with up to 4 options. When using the banners you can only show up to 2. For example – the following can be presented in an alert (the open and close options are added by the OS):

Screen Shot 2014-08-30 at 5.10.46 PM

Shut up and gimme teh codez!

The way to do this is to configure and register something called UIUserNotificationSettings. This class (available iOS 8.0 and up) is used to encapsulate the types of notifications that can be displayed to the user by the app. We will create 2 instances of a UIUserNotificationAction (one for fighting and one for surrendering), set them as actions within a UIMutableUserNotificationCategory which will be used for all “fighting” challenges and then add this category to our settings:


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    //1
    UIMutableUserNotificationAction *declineAction = [[UIMutableUserNotificationAction alloc] init];
    declineAction.identifier = @"REJECT_IDENTIFIER";
    declineAction.title = @"Surrender";
    declineAction.activationMode = UIUserNotificationActivationModeBackground;
    declineAction.destructive = NO;
    declineAction.authenticationRequired = NO;

    //2
    UIMutableUserNotificationAction *acceptAction = [[UIMutableUserNotificationAction alloc] init];
    acceptAction.identifier = @"ACCEPT_IDENTIFIER";
    acceptAction.title = @"Fight";
    acceptAction.activationMode = UIUserNotificationActivationModeForeground;
    acceptAction.destructive = NO;
    acceptAction.authenticationRequired = NO;

    //3
    UIMutableUserNotificationCategory *fightCategory = [[UIMutableUserNotificationCategory alloc] init];
    fightCategory.identifier = @"FIGHT_CATEGORY";
    [fightCategory setActions:@[declineAction,acceptAction] forContext:UIUserNotificationActionContextDefault];

    //4
    NSSet *categories = [NSSet setWithObjects:fightCategory, nil];
    UIUserNotificationType types = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert;
    UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:categories];
    [[UIApplication sharedApplication] registerUserNotificationSettings:settings];

    // Override point for customization after application launch.
    return YES;
}

Here’s a breakdown of the above:

1 – Creation and configuration of an action for rejecting a fight. Note that the activationMode is set to “UIUserNotificationActivationModeBackground” – this specifies that the app doesn’t need to be foregrounded in order to respond to this action. (No need to bother the user if he isn’t up for a fight). The “destructive” property is NO because we don’t need the option to be highlighted as destructive and the “authenticationRequired” property is NO because we don’t need the user to enter his passcode to respond to this action.
2 – Creation and configuration of an action for accepting a fight. Note that the “UIUserNotificationActivationModeForeground” option specifies that the app needs to be launched in the foreground in order to respond to this action. This time “authenticationRequired” is YES because the user must unlock his device in order to play.
3 – Creation and configuration of a “fight” category used to encapsulate these actions. We use setActions:forContext: with the “UIUserNotificationActionContextMinimal” context, allowing us to show up to 4 options when displaying an alert (not a banner). If we also wish to specify a minimal subset of these options for when the notification is displayed as a banner, we can also register part of the options as “UIUserNotificationActionContextMinimal”.
4 – Creation of a settings object that includes all of the above + registering these settings with the app.

Once we have registered these actions with our app, we’re ready to present notifications and respond to the actions if they are chosen. The way to do this is to implement one of the following methods in your App Delegate (depending on whether this is a remote or local notification):

1. application:handleActionWithIdentifier:forLocalNotification:completionHandler:
2. application:handleActionWithIdentifier:forRemoteNotification:completionHandler:

For the sake of simplicity (and my free time) let’s assume we’re working with local notifications only. Here’s an example of a valid implementation:

- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:(void (^)())completionHandler
{

    if ([identifier isEqualToString:@"ACCEPT_IDENTIFIER"])
    {
        [[FightManager sharedManager] fightLikeAnIdiot];
    }
    else if ([identifier isEqualToString:@"REJECT_IDENTIFIER"])
    {
        [[FightManager sharedManager] surrenderLikeABaby];
    }

    completionHandler();
}

 

* Note: According to Apple’s docs you must call the completion handler at the end of your method. So do it. Because Apple.

Great, now all that’s left to do is call a notification in order to demonstrate our new feature:

- (IBAction)someButtonPressed:(id)sender
{
    UILocalNotification *notification = [[UILocalNotification alloc] init];
    notification.fireDate = [NSDate dateWithTimeInterval:10.0 sinceDate:[NSDate date]];
    notification.category = @"FIGHT_CATEGORY";
    notification.alertBody = @"dumdum86 has challenged you to fight about nothing.";
    [[UIApplication sharedApplication] scheduleLocalNotification:notification];
}

 

The above code responds to a tap of a button, creates a local notification that’s scheduled to fire in 10 seconds and presents the message shown at the begging of the post. The category identifier must be the same FIGHT_CATEGORY previously specified when registering the actions with the app. Of course this is just for demonstration purposes so everything in there is hard-coded.

That’s it! Theres much more to play around with but the main idea should be enough to get you started. Enjoy 🙂

UX: There’s a control for that

During my first year at university, I remember my Java professor telling us something I’ll never forget and that still guides me to this day: “Someone already wrote it”. His philosophy was very strong on this – if you’re trying to tackle a general problem, there’s a very good chance that someone already did most of the work and there is probably an open source library that does what you need. That’s the beauty of object oriented programming: If you do things right, everything is modular, plug & play, reusable and it’s especially true when coding for iOS.

There are extremely talented people writing amazing open source code for iOS. You can interact with them on Stack Overflow, where they volunteer their time and experience to help us solve our problems. You can see things they’ve done by browsing GitHub repositories and you can find endless reusable and open source code via simple Google searches.

And there’s even a greater advantage here when discussing user experience. There are hundreds of controls out there, some are free to use under several restrictions and some are for sale, that (if used correctly) give you the chance to create some very creative user interfaces. If you haven’t already, I suggest you give them a try.

A great site for browsing these controls (my personal favorite) is cocoacontrols.com – you can browse iOS and Mac OS X controls by rating, recency or just by keywords. Some very good and popular controls there include:

MFSideMenu – as seen on Path, Facebook and many MANY more:

full-2

MFSideMenu

MBProgressHUD – Great class for displaying blocking activity indicators. You’ve probably run into these in a billion apps:

MBProgressHUD

MBProgressHUD

iCarousel – A very flexible, well written control for beautiful 3D browsing:

iCarousel

iCarousel

I strongly recommend you to browse some or all of the iOS controls that are available for purchase/download. Even if you don’t choose to reuse them (some may not be up to standard, I admit), they will definitely open your mind in terms of what you can do on iOS. Enjoy!