2359 Media Engineering

Sharing our experience in mobile and web development

Preparing an iOS Application for Distribution Manually

Introduction

iPhone applications can be distributed mainly in 2 ways. Either via AppStore or through AdHoc (Enterprise or Personal). While each of these publication methods addresses different user base, both of these methods require the program to be compiled, build and exported through Xcode (or terminal using xcodebuild). Exporting an iPhone application requires 2 entities.

  1. A provisioning profile which is a collection of entities listing down the allowed devices (or all devices), and developer information.
  2. A distribution certificate to sign the application.

Background

Usually the process of application submit / export through Xcode starts by creating an archive and this archive can be used to submit the application to Appstore directly or build an ipa file ready to be distributed manually. Often times when you’re working on client projects you do not always get the clients’ itunesconnect credentials to submit the application, instead you’re given provisioning file and distribution certificate to build and give the signed application (.ipa file) to client.

Problem

Given the fact you only have those files, building the ipa has not been a problem so far. But starting with Xcode 6 exporting through archive requires you to sign into apple account. The purpose of this blog post is to give an alternate method to build the ipa file old fashion way which does not require to log into client’s apple account.

Solution

I’ll be using a sample project to walkthrough the steps required to achieve this. What you will need?

  • A working iPhone project
  • A valid distribution provisioning profile and a valid certificate.

The project I’m using is configured to have 3 Build Configurations (Debug, Adhoc, Appstore) and 2 Schemes (Development, Appstore)

config schema

Are you familiar with customising build profiles and schemas? Else I’d highly recommend to go through this article regarding build profiles and schemas written by my colleague, Hu Junfeng.

Schema Appstore is using appstore build profile for all the options and Development schema is using adhoc for archive and debug for everything else, as shown below in the schema manager.

Dev Archive Appstore Run

1. Building the application file.

In this post I’ll show 3 different methods to extract the application file, which can then be used to build our ipa file. First is by simply using the xcode build, second method is using xcode archive (see below) and thirdly using terminal.

Steps for Xcode build option

  1. From the drop down menu for schemas in Xcode, select Appstore schema then select any iOS device besides a simulator.
  2. Clean the project and you will notice the under the products, the target becomes red in color indicating its being cleaned out.

production section 1

  1. Build the project and this will build our application with appstore profile.
  2. When the build is complete the target under product will be in black text.

production section 2

  1. Now right click, select show in Finder and copy this file.

2. Generating ipa file.

  1. With the target file copied, create a blank folder named ‘Payload’ (mind the case) on Desktop.
  2. Paste the copied file into this folder.
  3. Right click and select compress. This will create a zip file named Payload.zip
  4. Rename this file to extension .ipa ( Payload.ipa )

3. Clean up (important step)

File create at above step is almost ready to be distributed to appstore or by adhoc methods. But before that a clean up inside the ipa file should be performed. The reason is in mac os, a hidden system file named .DS_Store can exist within a folder. Typically this file is used to store icon positions, background image of the Folder. The important point is, unless you have hidden files shown in Finder, you will not see this file and the created zip archive will also contain this file making the ipa archive invalid. This hidden file can sometimes exist and sometimes not (kind of like Schrodinger’s file? ), therefore we must make sure to remove this file from the archive before we submit our ipa file.

There is ofcourse a way to delete this file from the Payload folder using terminal before creating the archive using the command ls –a Payload/ to know it’s existance and issueing the command rm .DS_Store to remove it. However I’m going to use an edit command for zip utility on terminal to remove it from archive itself. To do so, head over to terminal and navigate into the location where the archive ipa file is (Desktop in this case). Now issue this command on terminal.

> zip –d Payload.ipa Payload/.DS_\*

What this command does is remove file under Payload folder starting with the .DS_ resembling our hidden file. Now depending on the existance of the hidden system file, the output of the command will vary, either by informing the file did not exist or file has been removed. Now that the hidden system file is cleaned up, archive is ready to be submitted.

Building from Xcode Archive.

If you are using an Xcode archive you may want to try this method to copy out the application file instead.

  1. From Xcode select Development schema (Remember the Development schema has AdHoc configuration for archive option).
  2. From Xcode product menu select archive. Once the archive is built.
  3. Open the xcode organizer (Window > Organizer) and select the archive that just finished building.
  4. Right click & Show in Finder.
  5. Right click again on the selected archive from Finder > Show package contents then drill down into Productcs > Applications.
  6. Now copy this application file and follow from the step 2 in this article on Generating ipa file.

Building from Terminal

Finally if you prefer to do all these steps using terminal you can follow this method instead. Keep in mind however that by using the terminal you will need to type in all parameters accordingly. In this example I will be using Development scheme and AdHoc build configuration to create an archive on the desktop.

From terminal navigate to the location where project is located. To build Xcode project type in the command

> xcodebuild -project ApplicationBuild.xcodeproj -scheme Development -configuration AdHoc -archivePath ~/Desktop/ApplicationBuild.xarchive archive

or for an Xcode Workspace enter the command

> xcodebuild -workspace ApplicationBuild -scheme Development -configuration AdHoc -archivePath ~/Desktop/ApplicationBuild.xarchive archive

Now we use this archive to export the archive to ipa file,

> xcodebuild -exportArchive -exportFormat IPA -archivePath ~/Desktop/ApplicationBuild.xarchive.xcarchive -exportPath ~/Desktop/ApplicationBuild.ipa

Conclusion

Xcode 6 is using a lot of automated procedures to create, maintain and export developer profiles, provisioning and applications. This approach is great to keep things simple but does not work on situations where the developer does not have the credentials for itunesconnect account of client. In such situation methods described in this article can save the day to create ipa file from xcode project using only the provisioning file and certificate.

Rebuilding Instagram Feed Table View

Background

Instagram is what we have all known as one of the best apps to take a picture and share it on-the-go. It is simple, fast and intuitive, yet it doesn’t lack the features — camera, simple image editing, and filters — that most people would need from a photography app. However, a photography app would be incomplete without an ability to share what the user had done to their friends, family, followers, or the general public.

Sharing is the key feature of Instagram that has gained lots of users from all backgrounds and culture, regardless of their photography skills. Thus, we’re going to take a look at instagram feed, which is where most users will spend their time with.

Analysis

Instagram feed is unique in a way that it’s able to provide lots of important information in a condensed space without overwhelming its user.

If we take a look at each section where it displays text, #hashtags and @username mentions are the way in which a user is sharing what they have in mind — either a description of the picture, or a comment to another picture. Users would like another user to know, and they would also like the general public to know by marking it with a #hashtag.

We took this idea as a design inspiration for styleXstyle — a mobile app that showcases fashion and styles from its users with sharing as one of its key features.

Dissecting

Initially, it’s easier and more intuitive to think of the feed layout as a series of posts listed in a table view. Each post contains a picture, caption, likes, comments, and the typical sharing buttons at the bottom.

A picture will take the size of the whole row and it intersects with a profile picture of the person who owns the post. For styleXstyle, the picture is followed by a list of outfits or accessories showcased in the picture — with a link to an online store which is selling the particular item. Onwards, it’s followed by the caption, likes, comments, and sharing buttons. This is a similar layout, yet still a bit different compared to Instagram.

styleXstyle table view post

From this layout, we thought it’s beneficial to use Auto Layout, which has been released since iOS 6 SDK, and it gains lots of improvements since Xcode 5 with iOS 7 SDK. We could’ve taken the traditional approach of manually laying out each view by calculating frames.

Manual layout could be an advantage from Core Animation performance’s point of view. But, it will add an unnecessary complexity that arise from ensuring that the view frames calculation is correct in every situation that could happen such as text size adjustments, or a change in the layout structure (switching the position of caption with the store items).

There’s a good post on table view cells with dynamic heights which has tried both approaches (manual and auto layout) with a useful result that we can use.

Starting Up and Initial Problems

To begin with, we created a subclass of UITableViewCell which represents a big cell that contains the individual post. From this one big cell it’s easy to follow that we only need to layout all of the content, relative to each other inside the UITableViewCell’s contentView. But soon we discovered that there’s an inherent complexity resulting from this nested hierarchy of UIViews.

A minimal post contains a photo, caption, one item, and the buttons. The number of items, likes and comments will vary depending on the user’s post. Knowing this, we need to adjust layout constraints at runtime depending on how many items, likes, or comments that a post will have. A declared layout in an interface builder file is not enough to satisfy these dynamic constraints.

This way it easily generates a structure of code similar to the following:

- (void)layoutItemViewsFromItems:(NSArray *)items
{
    [self invalidateIntrinsicContentSize];

    NSUInteger storeItemIndex = 1;

    for (STXStoreItem *storeItem in items) {
        NSArray *topLevelObjects = [self.storeItemViewNib instantiateWithOwner:self options:nil];
        if ([topLevelObjects count] > 0) {
            NSInteger viewTag = storeItemIndex > FIRST_ITEM_VIEW_TAG ? storeItemIndex-1 : FIRST_ITEM_VIEW_TAG;
            STXStoreItemCell *previousItemView = (STXStoreItemCell *)[self viewWithTag:viewTag];
            STXStoreItemCell *nextItemView;

            if (previousItemView == nil) {
                UIView *topSeparatorView = [self addTopSeparatorToView];
                topSeparatorView.hidden = !self.shouldAddTopSeparator;

                previousItemView = topLevelObjects[0];
                previousItemView.tag = FIRST_ITEM_VIEW_TAG;
                previousItemView.storeItem = storeItem;
                previousItemView.delegate = self;
                [self addSubview:previousItemView];

                previousItemView.translatesAutoresizingMaskIntoConstraints = NO;

                [previousItemView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:topSeparatorView withOffset:STXItemViewTopEdgeInset];
                [previousItemView autoPinEdgeToSuperviewEdge:ALEdgeLeading withInset:0];
                [previousItemView autoPinEdgeToSuperviewEdge:ALEdgeTrailing withInset:0];

                if ([items count] == 1) {
                    UIView *bottomSeparatorView = [self addBottomSeparatorToView];
                    bottomSeparatorView.hidden = !self.shouldAddBottomSeparator;
                    [bottomSeparatorView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:previousItemView withOffset:STXItemViewBottomEdgeInset];
                }
            } else {
                nextItemView = topLevelObjects[0];
                nextItemView.tag = storeItemIndex;
                nextItemView.storeItem = storeItem;
                nextItemView.delegate = self;
                [self addSubview:nextItemView];

                nextItemView.translatesAutoresizingMaskIntoConstraints = NO;

                [nextItemView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:previousItemView];
                [nextItemView autoPinEdge:ALEdgeLeading toEdge:ALEdgeLeading ofView:previousItemView];
                [nextItemView autoPinEdge:ALEdgeTrailing toEdge:ALEdgeTrailing ofView:previousItemView];

                if (storeItemIndex == [items count]) {
                    UIView *bottomSeparatorView = [self addBottomSeparatorToView];
                    bottomSeparatorView.hidden = !self.shouldAddBottomSeparator;
                    [bottomSeparatorView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:nextItemView withOffset:STXItemViewBottomEdgeInset];
                }
            }
        }

        ++storeItemIndex;
    }
}

The above code snippet works, but it’s quite hard to comprehend. Moreover, if we want to understand whether there’s any layout constraints problems resulting from it — we’ve already used UIView-AutoLayout that’s simpler to use compared to the Auto Layout’s visual format syntax.

There’s a solution to this code smell in the following section. Next, we tackle dynamic content height.

Dynamic Cell Content Height

Each post has a different content, and it will affect the overall size of the text components being displayed (caption, name of items, who likes the post, comments). Thus, it follows that each cell may have a different height.

To solve this, each cell is cached with NSCache.

- (STXFeedCell *)feedCellForTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath
{
    NSString *identifier = [NSString stringWithFormat:@"STXFeedCell-%@-%p", @(indexPath.section), tableView];
    STXFeedCell *cell = [self.feedCells objectForKey:identifier];
    if (cell == nil) {
        cell = [[self.feedCellNib instantiateWithOwner:self options:nil] firstObject];

        cell.indexPath = indexPath;
        cell.delegate = self.controller;

        if (indexPath.section < [self.posts count]) {
            STXPost *post = self.posts[indexPath.section];
            cell.post = post;
        }

        [self.feedCells setObject:cell forKey:identifier];
    }

    return cell;
}

From a few experiments with the UITableView queueing system, it’s just too hard to make sure that each row of table view has the correct height, and it’s not reusing the wrong cell. Also, because we’ve started layout with Interface Builder and Storyboard, it’s not possible to use different identifier for different cells, unless if we create separate cells, which will create unnecessary IB files.

Reconstructing the cell layout constraints and re-calculating the height every time we dequeued a cell from a table view is also not a good idea, because it means the layout engine has to do extra work on each row.

How did we get the height for each row? It’s actually done by the magic of Auto Layout’s –[UIView systemLayoutSizeFittingSize:], which queries the layout engine for a valid layout of the view that fits as close to the given size as possible. It is used in tableView:heightForRowAtIndexPath: and given UILayoutFittingCompressedSize, which ensures the calculated size is the smallest possible that fits the contents.

Other than the post by John Szumski, there are series of answers on StackOverflow by Tyler Fox which comprehensively detailed how to construct a table view with dynamic cells height utilizing Auto Layout.

We’ve been working with this implementation for about a month. It’s capable to display a feed cell with dynamic content, but somehow it’s lacking what we originally intend to achieve with Auto Layout. Core Animation performance (when the user scrolls) is suboptimal, and the idea of simplicity that we want to achieve with declarative layout doesn’t complement the effort that we need to do to optimize it.

Back to Instagram

From this realization, we need to think of smarter ways to solve this. First, there’s one noticeable characteristic of the Instagram table view, the profile picture, username, and date are scrolling independently from the rest of the content view in the cell when it’s being scrolled. This gives us the idea that this must be a section header view. While styleXstyle doesn’t have this section header view, it’s still a good idea to explore the use of table view sections to separate each post.

Then, we overlooked that there’s a way to examine how Instagram build the content feed. To do this, we’ve used the commonly known way to inspect 3rd party apps.

As demonstrated by the above video, each post is separated by a section, and each section consists of different types of cells that correspond to the type of content (photo, video, text, action, or header). There’s no separation of likes, comments, or caption at the UITableViewCell level.

We can also see that there’s no ornamentation (no borders) surrounding the content, each text content has the same characteristics and styling. The bold font could represent number of likes or username. Every text could contain a #hashtag or @username mentions. It simplifies the effort needed to reuse the code, and it also simplifies the amount of processing that needs to be done by the table view.

From the view hierarchy of instagram feed, it’s a sudden realization that this is a better way to structure the table view section. Once we understand this, it should be very obvious from the start, but somehow we didn’t come up with that solution in the first place.

Following this is a step-by-step refactoring on the existing styleXstyle codebase, starting with separating the profile picture, username, date, and photo into a separate UITableViewCell, then we separated out the store items into its own cell, to avoid the notorious code smell that was shown above. Other than the benefit of modularity, and code that’s easier to understand, it reduces the overhead of –[UIView systemLayoutSizeFittingSize:] calculation.

Previously, the following is done for each section of the table view:

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        STXFeedCell *cell = [self feedCellAtIndexPath:indexPath];

        cell.bounds = CGRectMake(0, 0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));

        [cell setNeedsLayout];
        [cell layoutIfNeeded];

        CGSize cellSize = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

        // Add extra padding
        CGFloat height = cellSize.height + 1;

        return height;
    }

Now, we don’t have to do this for every single section (or row, because in this case it’s only 1 row per section):

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CGFloat height = 0;

    // In the spirit of lighter view controllers,
    // the data source is separated out
    // http://www.objc.io/issue-1/lighter-view-controllers.html
    STXFeedTableViewDataSource *dataSource = tableView.dataSource;
    STXPost *post = ([dataSource.posts count] > indexPath.section) ? dataSource.posts[indexPath.section] : nil;

    NSInteger photoRowOffset = 1;
    NSInteger itemRowLimit = photoRowOffset + [post.items count];
    if (indexPath.row == PHOTO_CELL_ROW) {
        height = PhotoCellRowHeight;
    } else if (indexPath.row > PHOTO_CELL_ROW && indexPath.row < itemRowLimit) {
        height = ItemCellRowHeight;
    } else {
        height = [self heightForTableView:tableView contentCellAtIndexPath:indexPath];
    }

    return height;
}

Let’s take a look at the advantage of the above code. First, the boilerplate of Auto Layout size calculation is moved into heightForTableView:contentCellAtIndexPath:. Second, the above code will use a constant height for photo and items because they will not change their height.

Then, the cells corresponding to the photos and items will be re-used normally as we use reuse a standard UITableViewCell.

The feed photo cell:

- (STXFeedPhotoCell *)photoCellForTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath
{
    NSString *CellIdentifier = NSStringFromClass([STXFeedPhotoCell class]);
    STXFeedPhotoCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    cell.indexPath = indexPath;

    if (indexPath.section < [self.posts count]) {
        STXPost *post = self.posts[indexPath.section];
        cell.post = post;
        cell.delegate = self.controller;
    }

    return cell;
}

and the store item cell:

- (STXStoreItemCell *)itemCellForTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath
{
    STXPost *post = ([self.posts count] > indexPath.section) ? self.posts[indexPath.section] : nil;

    NSString *CellIdentifier = NSStringFromClass([STXStoreItemCell class]);
    STXStoreItemCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    cell.shouldAddBorders = YES;

    if (indexPath.row < [post.items count]) {
        cell.storeItem = post.items[indexPath.row];
        cell.delegate = self.controller;

        if (indexPath.row == [post.items count] - 1) {
            cell.shouldAddBottomSeparator = YES;
        } else {
            cell.shouldAddBottomSeparator = NO;
        }
    }

    return cell;
}

We had to add an extra code to determine where to add the borders (because there’s no longer a “bordered” container view as a superview of these store item cells), and the separators. So, it’s just one of those sacrifices that needs to be done for refactoring a code.

From here, it follows that we can do further optimization by also separating the caption and each comment into its own UITableViewCell subclass.

Experiment with Instagram Feed

For the purpose of study, we’ve created a sample project with the instagram popular media json feed as a content using a table view structure similar to that of Instagram app. This has a much simpler and slightly different feed layout compared to the one we had on the styleXstyle app codebase, because we have to do lots of colors, view margins, and font styling on the real app. But, it’s intended to be a bit more generalized (e.g. we’ve added protocols to support data models for the UITableView’s data source).

If we take a look at the view hierarchy of the instagram inspired table view:

Instagram inspired dynamic table view

As it has been mentioned before we’re going to separate likes, caption, and comments. From our table view delegate code, it will result in the following height calculation for a table view section:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CGFloat height = 0;
    NSInteger captionRowOffset = 3;
    NSInteger commentsRowLimit = captionRowOffset + MAX_NUMBER_OF_COMMENTS;

    UITableViewCell *cell;
    STXFeedTableViewDataSource *dataSource = tableView.dataSource;

    if (indexPath.row == PHOTO_CELL_ROW) {
        return PhotoCellRowHeight;
    } else if (indexPath.row == LIKES_CELL_ROW) {
        cell = [dataSource likesCellForTableView:tableView atIndexPath:indexPath];
    } else if (indexPath.row == CAPTION_CELL_ROW) {
        cell = [dataSource captionCellForTableView:tableView atIndexPath:indexPath];
    } else if (indexPath.row > CAPTION_CELL_ROW && indexPath.row < commentsRowLimit) {
        NSIndexPath *commentIndexPath = [NSIndexPath indexPathForRow:indexPath.row-captionRowOffset inSection:indexPath.section];
        cell = [dataSource commentCellForTableView:tableView atIndexPath:commentIndexPath];
    } else {
        return UserActionCellHeight;
    }

    [cell setNeedsUpdateConstraints];
    [cell updateConstraintsIfNeeded];

    height = [self heightForTableView:tableView cell:cell atIndexPath:indexPath];
    return height;
}

Here, we still have two rows which returns static height, and all the other rows still require dynamic height calculation. This is because of the multilines label that are still required for caption, likes, and comments. It might seem that this doesn’t reduce the amount of times we need to call –[UIView systemLayoutSizeFittingSize:], but for each individual row, we have less amount of views that needs to be calculated relative to each other.

As demonstrated with the performance analysis by Florian Kugler and Martin Pilkington, an absolute layout (flat hierarchy of views absolutely positioned relative to a content view) will have less computing overhead compared to relative layout (flat hierarchy of views relatively positioned to each other). We’ve also managed to reduced nested hierarchy of views that we had in the initial code with store items, likes, and comments container views.

As an example, this is how we’ve rewritten the comment cell in the previous sample code:

- (STXCommentCell *)commentCellForTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath
{
    id<STXPostItem> post = self.posts[indexPath.section];
    STXCommentCell *cell;

    // We're no longer using a cell loaded from a IB file in this case,
    // because it's more straightforward to use the classic
    // UITableViewCell initWithStyle:reuseIdentifier:
    // for managing initialization of the cell, configuring styles,
    // and reuse identifiers.
    if (indexPath.row == 0 && [post totalComments] > MAX_NUMBER_OF_COMMENTS) {
        static NSString *AllCommentsCellIdentifier = @"STXAllCommentsCell";
        cell = [tableView dequeueReusableCellWithIdentifier:AllCommentsCellIdentifier];

        if (cell == nil) {
            cell = [[STXCommentCell alloc] initWithStyle:STXCommentCellStyleShowAllComments
                                           totalComments:[post totalComments]
                                         reuseIdentifier:AllCommentsCellIdentifier];
        } else {
            cell.totalComments = [post totalComments];
        }

    } else {
        static NSString *CellIdentifier = @"STXSingleCommentCell";
        cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

        NSArray *comments = [post comments];
        id<STXCommentItem> comment = comments[indexPath.row];

        if (indexPath.row < [comments count]) {
            if (cell == nil) {
                cell = [[STXCommentCell alloc] initWithStyle:STXCommentCellStyleSingleComment
                                                     comment:comment
                                             reuseIdentifier:CellIdentifier];
            } else {
                cell.comment = comment;
            }
        }
    }

    cell.delegate = self.controller;

    return cell;
}

Likes and caption are rewritten in a similar way to comments. Using UITableViewCell’s initWithStyle:reuseIdentifier: enables us to have more control over the initialization of the cell. Because it’s often the case that we’re not exactly sure when to pass the individual row text data that’s required for multilines label height calculation. So, we wrapped initWithStyle:reuseIdentifier: and pass the required data into it, in order to setup the styles or attributes of the labels.

- (id)initWithStyle:(STXCommentCellStyle)style comment:(id<STXCommentItem>)comment totalComments:(NSInteger)totalComments reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
    if (self) {
        _cellStyle = style;

        if (style == STXCommentCellStyleShowAllComments) {
            NSString *title = [NSString stringWithFormat:NSLocalizedString(@"Show %d comments", nil), totalComments];
            _commentLabel = [self allCommentsLabelWithTitle:title];
        } else {
            id<STXUserItem> commenter = [comment from];
            _commentLabel = [self commentLabelWithText:[comment text] commenter:[commenter username]];
        }

        [self.contentView addSubview:_commentLabel];
        _commentLabel.translatesAutoresizingMaskIntoConstraints = NO;

        self.selectionStyle = UITableViewCellSelectionStyleNone;
   }
    return self;
}

Wrapping up

As we can see we’ve reduced a lot of complexity of the table view code by replicating what Instagram did, with an improvement on scrolling performance. There are a few edge cases here and there but the real work is coming up with such an ingeniously simple and elegant way to solve a problem like this on a mobile device.

Checkout GitHub for a sample code of this project.