Archive for the ·

Programming

· Category...

On constructive critizism

no comments

There has been a couple of blog posts lately regarding the bloggy criticism of iOS applications by other app developers as well as general tech bloggers. These posts seem to suggest that iOS developers should band together and not criticize each others applications. I generally agree with the statement “be excellent to each other” – but if that means patting each others backs and saying good job even if the application has flaws then the iOS platform is in trouble. I think that one of the great things about the iOS platform is the constant push to make something that is greater and more amazing than what has been done before.


Getting criticism, be it from users through app reviews or emails or from bloggers, is a great way to push everyone to make better applications. I can clearly identify with the fact that developers have an emotional connection to the applications that they make. None of my applications make a huge amount of money and my main motivation is to provide people with great tools to use on their phone – so getting emails or reviews saying “this app sucks” is a feeling not much different than helping a complete stranger just to have them spit at you.


In my opinion, the app review system setup by Apple is one of the greatest innovations of the iOS platform and has been instrumental in the success of the App Store – before this the only way to give feedback was to email the developer and then they could do whatever they wanted – you had already paid for the app so they had no incentive to actually fix the issue. With public reviews and a rating system the incentive to fix issues brought up is much greater and this is something that will push apps to become better. Several times this has brought great new features to my apps and alerted me to features that were not as intuitive as I had originally thought.


Like many other Apple enthusiasts I was very excited for the launch of The Daily. Like many others I was disappointed at the initial product. I think that a lot of people expected the app to be Apple Great (like Maps, Garage Band etc) because of the hype and media buildup for the app and the apparent endorsement of the app by Apple. However, the app has several short falls which were pointed out and suggestions for improvements with implementation were posted. I do not understand why the developer of the app would be surprised that the app got a harsh reception when the developer admits himself that there were issues with the app that if they had longer time could have been sorted out.


I hope that the iOS developer community is grown up enough that we can accept criticism of our work and that we can learn from this and all strive towards making excellent if not perfect apps.

Share

Implementing iAd in iPhone applications

no comments

There are a couple of tricks to implementing iAd in iPhone applications that are not 100% spelled out in the documentation provided by Apple. If you do not implement right your app will crash on phones running older versions of iOS such as iOS4.1 and iOS3.

For your .h file:

#import <UIKit/UIKit.h>;
#import <iAd/iAd.h>;
@interface iAdViewController : UIViewController <ADBannerViewDelegate> {
	ADBannerView *adView;
 	BOOL bannerIsVisible;
}
@property (nonatomic,assign) BOOL bannerIsVisible;
@end

In the .m file you need the following:

- (void) viewWillAppear:(BOOL)animated {
	// check if iAd is available
	Class classAdBannerView = NSClassFromString(@"ADBannerView");
	if (classAdBannerView) {
		// create iAd
		ADBannerView *bannerView = [[classAdBannerView alloc] initWithFrame:CGRectZero];

		if (&ADBannerContentSizeIdentifierPortrait != nil) {
			// NEWER
			DLog(@"NEWER");
			bannerView.requiredContentSizeIdentifiers = [NSSet setWithObject:ADBannerContentSizeIdentifierPortrait];
			bannerView.currentContentSizeIdentifier = ADBannerContentSizeIdentifierPortrait;
		} else {
			// OLDER
			DLog(@"OLDER");
			bannerView.requiredContentSizeIdentifiers = [NSSet setWithObject:ADBannerContentSizeIdentifier320x50];
			bannerView.currentContentSizeIdentifier = ADBannerContentSizeIdentifier320x50;
		}

		bannerView.delegate=self;

		self.adView = bannerView;
		[self.view addSubview:adView];
		self.bannerIsVisible=NO;
		[bannerView release];

	}
	[super viewWillAppear: animated];
}

I am creating the bannerView programatically in order to have the app also work on pre iOS 4.1 devices. The check for ADBannerContentSizeIdentifierPortrait is to see if the user is using iOS 4.1 or newer iOS. In iOS 4.1 the identifiers were based on size – but in newer iOS the iPad got iAds and the size no longer made sense – thus the landscape/portrait designations.

Finally – if your app supports multiple interface orientations – you need to let the banner know that the device rotated so it can show the right size ad.

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {

    if (&ADBannerContentSizeIdentifierPortrait != nil) {
        // NEWER
		DLog(@"NEWER");
        if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation))
            adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifierLandscape;
        else
            adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifierPortrait;
    } else {
        // OLDER
		DLog(@"OLDER");
        if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation))
            adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifier480x32;
        else
            adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifier320x50;
    }
}
Share

How to batch change volume of aif files

1 comment

After creating aif files that tells the distance of upto 100 miles in both miles and kilometers (around 50 individual files) I tested the distance announcement while playing music and found that the volume of all my files was too low.  Now I could go in and manually increase the volume on each file – but that would be a bit of a pain and possibly not very consistent.  Besides one of the things computers are good at is doing the same task over and over again.  So I looked for a software solution.  Luckily there is a great open source command line sound manipulation software called <a href=”http://sox.sourceforge.net/”>SOX</a>.  This tool has a function for gain which lets you set the gain of any files in decibels (dB).

To run this program on all files in a folder run the following command from the terminal after copying the sox program to the folder:

for i in *.aif; do ./sox $i ${i%.*}.aiff gain -n 8; done;

In the instance above the original files are .aif and they were converted to have the file extension .aiff.  I tried to overwrite the original file – but that did not work well.

Share

Getting the timezone on the device

no comments

Since the LogYourRun iPhone application will show you your running data in week and month views it is necessary to know the timezone of the user in order to bring up the data for the correct week/month. At first I thought I could use the NSDateFormatter to do this. The documentation says that you can get timezone informationby using %z or %Z as the format. The %z should get you the offset in hours while the %Z should get you the name. However when tested it turned out that the opposite is the case…

The following code gets you the timezone offset in hours:

NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"Z"]; // error in documentation - the zone in hours should be 'z' not 'Z'
timezoneoffset = (int) [[dateFormatter stringFromDate:[NSDate date]] intValue]/100;
DLog(@"format %@", [dateFormatter stringFromDate:[NSDate date]]);
[dateFormatter release];

(Note the division by 100 is because the timezone is reported in military time so for 30 min offset the final timezone would be read as x.30 which is not really correct – but for getting a ballpark figure it would suffice).

Since the code is not behaving according to documentation it is not a good idea to use this to get the timezone because the code will break if the code ever starts behaving according to documentation.

Instead it turns out that there is a very nice NSTimeZone object which will give you the timezone offset from GMT in seconds:

int timezoneoffset = ([[NSTimeZone systemTimeZone] secondsFromGMT] / 3600);

Then all you have to do to get the hour difference is divide by 3600 (seconds per hour). If you cared about the half hour offsets you would make this a float – but this is plenty for getting a ballpark figure.

Share

MGTwitterEngine and Locations

1 comment

So Twitter just updated their API to disallow the use of authentication through the REST API. They were nice enough to announce that in an email 2 days after they turned it off so that meant that I had to learn all about OAuth overnight and scramble to find something that could take over the Twitter functionality of the LogYourRun iPhone app.

Luckily most of the leg work in this are has been done by Matt Gemmell and his Twitter engine. Some slight modifications are required in order to get it running on the iPhone and working with OAuth.  Update:  The guys at iCodeBlog have a great tutorial on how to implement the libraries.

With these libraries dropped in the new Twitter authentication system is fairly easy to implement. The main problem is that the MGTwitterEngine does not support the Twitter location API. Since this is an important piece of TweetMyDistance I modified the MGTwitterEngine.m and added a function for tweeting with location. The new method takes latitude and longitude as well as the tweet and if this is a reply to a previous tweet. As you can see from my tweet it works great. The code below should replace the code in MGTwitterEngine and don’t forget to also update your .h file.

- (NSString *)sendUpdate:(NSString *)status
{
    return [self sendUpdate:status inReplyTo:0];
}

- (NSString *)sendUpdate:(NSString *)status inReplyTo:(unsigned long)updateID
{

	return [self sendUpdate: status inReplyTo: updateID withLatitude: 0.0 andLongitude: 0.0];

}

- (NSString *)sendUpdate:(NSString *)status
			   inReplyTo:(unsigned long)updateID
			withLatitude:(double) lat
			andLongitude: (double) lng
{
	if (!status) {
        return nil;
    }

    NSString *path = [NSString stringWithFormat:@"statuses/update.%@", API_FORMAT];

    NSString *trimmedText = status;
    if ([trimmedText length] > MAX_MESSAGE_LENGTH) {
        trimmedText = [trimmedText substringToIndex:MAX_MESSAGE_LENGTH];
    }

    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
    [params setObject:trimmedText forKey:@"status"];
    if (updateID > 0) {
        [params setObject:[NSString stringWithFormat:@"%u", updateID] forKey:@"in_reply_to_status_id"];
    }
    if (lat != 0.0 && lng != 0.0) {
		// lat=%1.6f&long=%1.6f&display_coordinates=true
        [params setObject:[NSString stringWithFormat:@"%1.6f", lat] forKey:@"lat"];
        [params setObject:[NSString stringWithFormat:@"%1.6f", lng] forKey:@"long"];
        [params setObject:@"true" forKey:@"display_coordinates"];
    }
    NSString *body = [self _queryStringWithBase:nil parameters:params prefixed:NO];

	DLog(@" twitterbody: %@", body);

    return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path
                        queryParameters:params body:body
                            requestType:MGTwitterUpdateSendRequest
                           responseType:MGTwitterStatus];
}
Share