Don’t call your UILabels “title”

You will get an error

[UILabel copyWithZone:]: unrecognized selector sent to instance

http://stackoverflow.com/questions/10784207/uilabel-copywithzone-unrecognized-selector-sent-to-instance

Don’t call it Switch as well. There must be a reserve list of keywords somewhere.

Next Set of iOS Queries to Answer

  1. In the storyboard, which layout to work in? 3.5 or 4 inch?
  2. Do you drag the size of your view all the way to the edge of the 4 inch? How does this affect the 3.5 inch view?
  3. In a navigation controller, the Top Bar pushes down content when there’s a scroll view. It doesn’t push it down in a normal view. Why?
  4. How to manage autolayout and scroll view?
  5. Should you lazy instantiate all of your properties all the time? Including all the UILabels and UITextFields?
  6. Assuming I want the height of a view to occupy the few screen, what should the height of the UIView be in a navigation controller (with and without the status bar)
  7. In View Controller, what does Extend Edges — Under Top Bars/Under Bottom Bars/Under Opague Bars

First App on the App Store! But wait.. what’s this?

2014-02-27 00.14.01

My first iPhone app, Urban City Wallpaper, just got published to the app store today! *confetti*

But that’s not the interesting bit. What’s interesting is that I actually submitted 2 apps. Both with identical source code. The only exception was that one app is calling /r/earthporn end points and the other is calling /r/cityporn endpoints, both on imgur.

One got rejected, the other got accepted. The earthporn app got rejected because it crashed when there wasn’t an active internet connection present on the device. It’s a legitimate bug which I’ve since patched and resubmitted. Now the same bug probably exists on the cityporn app but it got through. Actually it got reject also but it was due to keyword stuffing on the app title. I removed it and it got through. Funny enough, the earthporn app is also keyword stuffed but Apple hasn’t said mentioned it yet. Let’s see if it gets through. If it does, I’ll essentially have two different rejections reasons for 2 idential apps, each getting away with the other’s rejection.

Loading Images in UICollectionViewCell: Naive to the Clever.

I’m working on an iOS app called Scenery. It pulls images from /r/earthporn via imgur’s API. It’s a simple 2 screen app that uses UICollectionViewCell to display images in a grid format. I went through a few code iterations in getting the images to display. Let’s go through them.

2014-02-05 12.19.28 2014-02-05 12.19.36

Iteration 1: The “Hello World” of downloads

NSURL *url = [NSURL URLWithString:@"https://api.imgur.com/3/gallery/r/earthporn/time/"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];

This works as expected except that it’s a synchronous request. This means the screen is blank to the user until all the images have been downloaded and displayed. In this case, each API request to imgur is returning 52 images making for a poor user experience.

Iteration 2: The Sensible Asynchronous Method

What’s obviously required here is to first load the screen and then download the images asynchronously.

        // download the image asynchronously
        NSString *imageString = [[_imgurArray objectAtIndex:indexPath.row] objectForKey:@"link"];
        NSURL *imageURL = [NSURL URLWithString:imageString];

        [self downloadImageWithURL:imageURL
                   completionBlock:^(BOOL succeeded, UIImage *image) {
            if (succeeded) {
                    imageView.image = image;
                }
            }
        }];

I’m now downloading the images asynchronously with downloadImageWithURL:. Great! Now the screen loads first and I can see the images pop up one by one as it finishes the download. However, every time I scroll down the screen and up again, the app re-downloads and re-requests the images again. Not great.

Note that all of this action is taking place in – (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath.

Iteration 3: The Sensible Asynchronous Method with Makeshift Cache

So to deal with the multiple requests to imgur’s server each time a user scrolls up and down, I loaded all the images into a NSMutableDictionary as a sort of cache.

    if ([_cachedImages objectForKey:identifier] != nil) {
        imageView.image = [_cachedImages valueForKey:identifier];
    } else {
        // download the image asynchronously
        NSString *imageString = [[_imgurArray objectAtIndex:indexPath.row] objectForKey:@"link"];
        NSURL *imageURL = [NSURL URLWithString:imageString];

        [self downloadImageWithURL:imageURL
                   completionBlock:^(BOOL succeeded, UIImage *image) {
            if (succeeded) {
                if ([_cachedImages objectForKey:identifier] == nil) {
                    [_cachedImages setValue:image forKey:identifier];
                    imageView.image = [_cachedImages valueForKey:identifier];
                }
            }
        }];
    }

Getting there! The scroll speed is smooth and the download is asynchronous. But look, what do we have here

Screen Shot 2014-02-05 at 12.40.16 pm Screen Shot 2014-02-05 at 12.40.49 pmThe memory footprint is out of this world. On first load, the app consumes 300MB. When I load the 2nd page, it also doubles. Each subsequent page load adds roughly 200MB. As you might have guessed, it crashes once I tried running this on my iPhone.

Note: the original code was heavily inspired by this post: http://natashatherobot.com/ios-how-to-download-images-asynchronously-make-uitableview-scroll-fast/

Iteration 4: The Real Sensible Method

We’re all standing on the shoulders of giants in the software industry. Image caching and url requests are a well trodden path that have been solved by other folks much smarter than myself. A cursory search reveals a few prominent libraries:

Using SDWebImage, I managed to reduce my memory footprint by 1 order of magnitude. And it does all the asynchronous loading as well.

    NSString *imageString = [[_imgurArray objectAtIndex:indexPath.row] objectForKey:@"link"];
    [imageView setImageWithURL:[NSURL URLWithString:imageString]];
    cell.backgroundView = imageView;

Screen Shot 2014-02-05 at 12.50.19 pm

 

 

Haha! Mind blown!