Nikola Zdravevski's Blog

I'm that guy who brought 21.5'' iMac all the way from Jersey to Macedonia as carry-on baggage.

UX Wins in the Foursquare iOS App

| Comments

The more I get into the iOS development the more I see how other fellow developers and/or designers don’t care at all about the UX (user experience) and user interface elements they use. I feel that not enough attention is paid on the visual beauty of the app. So we have all this apps that look-alike or apps that try to be original, but in some, for me wrong ways.

However, of course there are exceptions out there and apps that is pleasure to look at. One of them for me is the new foursquare iOS app, or foursquare version 5.x. I really like the subtle, simple and minimal, yet very cool and good looking details that make you love this app.

Following here are some of the distinctive UX features I have found throughout the foursquare app.

UINavigationController Back Button Custom Action (Quick Tip)

| Comments

We all know the good, old UINavigationController that is used in almost every iOS application. UINavigationController has a default action when back button is pressed on one of its stack’s view controllers i.e. - go back one level in the navigation stack. However, there are cases when we need to do something extra when back button is pressed.

JSON Serialized Date Passed Between iOS and WCF (and Vice Versa)

| Comments

Knowing that JSON still does not support Date objects, one can have a really painful experience sending and receiving date and time values through the wire. Following here is an example of how such a functionality can be achieved easily having .NET 4.0 WCF Web Service on one side and iOS 5.0 application on the other.

To make things simpler, we will obey to the WCF convention for serialization of DateTime objects which declares that such objects will be serialized as: "/Date(1292851800000+0100)/" or "\/Date(1292851800000-0100)\/" where 1292851800000 is milliseconds since 1970 and +0100 is the timezone. That way, we don’t have to touch anything on the WCF side and use the built in JSON parser in .NET accordingly.

Of course, everything that is communicated via JSON has to be a string value.

1. WCF .NET Server side code

Example Event class that represents data about events. Instances of this class are sent via WebInvoke methods to the iOS client application. Only the attributes of type DateTime are showed.

Event.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Runtime.Serialization;

namespace RESTfulWCF
{
    [DataContract]
    public class Event
    {
        [DataMember]
        public DateTime dateFrom { get; set; }

        [DataMember]
        public DateTime dateTo { get; set; }
    }
}

That’s it. This (plus additional simple return code at the WebInvoke methods that are responsible for the actual JSON responses) provide DateTime serialization in the format previously showed but for the set methods they except input parameters in the same format as well.

That practically means that we ourselves should do the proper formatting when sending NSDate on the iOS side, and also parse the consumed JSON string from the WCF side.

2. iOS Client side code

First, I am providing the method that does the conversion (parsing) of a DateTime (.NET) object serialized as JSON by WCF to a NSDate object. Have the format in mind [“/Date(1292851800000+0100)/”] before looking at the code. My practice here is always to keep date and time objects in their UTC time zone on the server and do the needed time zone calculations on the client side. That practically means that time zone offsets will be added here, on the client side parsing code.

Deserialization of Date Objects
1
2
3
4
5
6
7
8
9
10
11
12
- (NSDate *)deserializeJsonDateString: (NSString *)jsonDateString
{
    NSInteger offset = [[NSTimeZone defaultTimeZone] secondsFromGMT]; //get number of seconds to add or subtract according to the client default time zone 

    NSInteger startPosition = [jsonDateString rangeOfString:@"("].location + 1; //start of the date value

    NSTimeInterval unixTime = [[jsonDateString substringWithRange:NSMakeRange(startPosition, 13)] doubleValue] / 1000; //WCF will send 13 digit-long value for the time interval since 1970 (millisecond precision) whereas iOS works with 10 digit-long values (second precision), hence the divide by 1000

    [[NSDate dateWithTimeIntervalSince1970:unixTime] dateByAddingTimeInterval:offset];

    return date;
}

Now follows the code for formatting the NSDate objects in the appropriate format for transmission.

NSDate to JSON date string
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- (void)jsonPostRequestAddEvent
{
    NSError *error;

    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"Z"]; //for getting the timezone part of the date only.

    NSString *jsonDateFrom = [NSString stringWithFormat:@"/Date(%.0f000%@)/", [self.dateFrom timeIntervalSince1970],[formatter stringFromDate:self.dateFrom]]; //three zeroes at the end of the unix timestamp are added because thats the millisecond part (WCF supports the millisecond precision)
    NSString *jsonDateTo = [NSString stringWithFormat:@"/Date(%.0f000%@)/", [self.dateTo timeIntervalSince1970],[formatter stringFromDate:self.dateTo]];

    NSArray *objects = [NSArray arrayWithObjects:jsonDateFrom, jsonDateTo, nil];
    NSArray *keys = [NSArray arrayWithObjects:@"dateFrom", @"dateTo", nil];
    NSDictionary *eventDict = [NSDictionary dictionaryWithObjects:objects forKeys:keys];

    NSDictionary *jsonDict = [NSDictionary dictionaryWithObject:eventDict forKey:@"myEvent"];

    NSURL *url = [NSURL URLWithString:@"http://yourDomain.com/WCFService.svc/addEvent"];

    NSData* jsonData = [NSJSONSerialization dataWithJSONObject:jsonDict options:kNilOptions error:&error];

    //NSLog([[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]); //debug only

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy: NSURLRequestReloadIgnoringCacheData timeoutInterval: 30.f];

    [request setHTTPMethod:@"POST"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:[NSString stringWithFormat:@"%d", [jsonData length]] forHTTPHeaderField:@"Content-Length"];
    [request setHTTPBody: jsonData];

    self.urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; //this is important for the UX (User Experience)  
}

Thanks for reading my post, I hope it was helpful.