Playing with iOS MapKit Part 4: Race Conditions, Other Bugs, Wrap-up

Playing With MapKit

Part 1 MapKit Tutorials 

Part 2 Reverse Geocoding and Custom Annotation Callouts

Part 3 Making it Pretty

Part 4 Race Conditions, Other Bugs, and Wrap-up

As the functionality and the look was coming into place, I took note of bugs that I saw.

The most insidious bugs are ones that don’t happen all the time or under conditions (ex. when internet is especially slow or the computer is especially fast). One example is the race condition.

What is a Race Condition?

A race condition is a situation when the sequence of asynchronous responses results in an undesired effect.

My Example

Very quickly after starting the app, I was tapping the current location dot and I was seeing “Current Location” in the bubble. It should have said the address that the address was closest to. If I waited a couple seconds to tap the current location, it would display the address as expected.

This led me to believe that I had a race condition.

Under normal conditions:

  1. The map updates the user location calling its delegate mapView:didUpdateUserLocation: which starts the reverseGeocoding call based on the user’s location.
  2. The reverseGeocoding response is received and the completion block sets the annotation’s title with the address of the user location.
  3. When the user location dot is tapped, it will display a custom bubble using the annotation’s title in the label.

Usually the internet is fast enough that the reverseGeocoding response returns before the user location dot is tapped.

The two things that are racing are

  1. the reverseGeocoding network call and
  2. the tap of the current location dot.

If 1 finished first, then the app would work as intended. If 2 finished first, the experience would be suboptimal.

Design Choice

One way to fix this is to update the label when the reverseGeocoding network call came back.

At the end of the completion block, I added a method call to updatePinCallout, which pretends that the user location dot was tapped again and reloads the address.

- (void)updatePinCallout
{    
  if (self.userLocationPin.selected) {
   [[NSOperationQueue mainQueue] addOperationWithBlock:^{
    [self.userLocationPin setSelected:YES animated:YES];
   }];
  }
}

Just remember to use the mainQueue to retap the dot or else the address won’t update on screen.

This change also fixed another bug that happened when the user location would jump when WIFI was enabled and the address label would not update to the new address.

Alternative Choice

Another thing that I found solved the problem pretty well was to zoom the map to the user location immediately, so that the animation didn’t even give a chance to tap the user location dot.

Other Bugs

Writing code for multiple scenarios unnecessarily

When I first implemented zooming to fit destinations, I only zoomed when the location was outside of the visible region. I realized that sometimes this would not be sufficient because sometimes the map would be zoomed way out, so I decided to account for that scenario too. I would zoom in if the destination was in the original visible area. This got complicated quickly.

Eventually, I realized that it was too much and just made it always zoom to the destination. The code went from 14 lines to 1.

Simpler is Better

Testing Days Later

I thought I was done with the project. Wrong. A few days later, I was trying out the app on the bus and came across two bugs.

  1. I was on a bus and the location was updating very quickly and constantly centering the map on the new location. The problem was that I wanted to look around the map and couldn’t do that without it changing while I was using it.
    • This is easily fixed by only zooming on the time the map loads.
  2. I was looking for a Pret A Manger. When I saw the results page, I found it utterly useless because all of them said the same thing and I couldn’t tell which location was where.
    • I think it would have been cool to add some directions as to how far each location was from my current location and in which direction or add the cross streets.

As the Pragmatic Programmer said:

“Test your software, or your users will.”

Where to next?

There’s certainly a lot of ways this can go. Add friends, add Facebook integration, add Yelp results, use Google Maps. That would be fun to do, but would lack direction.

For a product to be real, it must have a real use.

The way that this app fits into my life has something to do with lunch (ergo the name LunchTime). In an effort to build good habits, one of them is to walk 10K steps a day. As I’ve discovered, this means walking 1.3 hours a day. If I don’t plan to walk, I won’t get near 10K.

So over lunch, I like to walk 10 to 15 minutes away and back. I think it would be cool to draw a diamond around your current location to see how far you can walk in 5 and 15 minutes and show how far each location is.

Options abound. Looking forward to the next expedition.

It’s been fun working with MapKit. Time to play around with something else. Who knows? I might be looking at WatchKit, EventKit, or something else entirely.

A Trip to the Met

Surrounded in a sea of options, I felt an urge to head to the Met.

Software development is art

On the way there, I tried to explain this urge. It became readily apparent as I walked through the halls of Egyptian wing that software development is as much an art as these. One can only hope to be in a museum some day. Though, any dreams that my software creations will outlast me may be overly optimistic.

Art requires repetition and experimentation

Looking at the Madame Cezanne exhibit, with it’s many renditions of the same lady, I am reminded that many versions were made before the final product was complete. This is an inherent part of the process of experimentation and exploration. Bad art has to be made before they can be judged bad and discarded to made room for good. This exhibit was only just a peek. Many shitty drafts were made that will never be on seen except by the artist.

Artists are judged on the height of their greatest accomplishment, not the first thing they make

Another thought that occurred to me was that people are judged on their greatest contributions or indiscretions. People only care at all about the early work when your best work has already been accepted as great. In the hindsight of a career, only highlights make it and most first moves are necessary to build the foundations of a great career. It’s really only by building a lot that one builds great things.

Let’s delay no further. Let’s get started.

Flatiron School Week 4- Questions and Answers

Topics: Cocoapods and Core Data

Question: Should I use [array mutablecopy] or [NSMutableArray arrayFromArray:array]  ?

Answer: Both are the same if the array is already made. Stylistically, prefer the latter.


Question: Is order maintained in Core Data?

Answer: No, if you want to maintain order, add an attribute like createdAt and use an NSSortDescriptor to order by created time.

Flatiron School Week 4 Mistakes and Lessons

Topics: Cocoapods, Core Data

Symptom: The search functionality works most of the time but in a small set of users, the search results are empty.

Mistake: Sending an API call in the prepareForSegue method.

Let’s say that we have a view controller that searches Foursquare for restaurants. If I press submit on my search view controller, I’m pushed to a table view controller with the results. I can put the API call to fetch the data from Foursquare in the prepareForSegue call.

99.5% of the the time, it works, but the other 0.5% of the time, the previous view controller can be removed before our API call has come back with the results and we’ll be left with a table view controller with no results.

This is called a race condition and it’s a very annoying problem to fix because it’s hard to detect and I would have to have millions of users like Facebook to have this problem.

With search view controllers, pass the query to the results view controller and send the API call from there.

Lesson: Avoid race conditions always.


Symptom: I was trying to test a tableViewController to make sure that it worked with Core Data by adding 3 test objects: @”Test”, @”Test Test”, and @”Test Test Test”. I was only seeing one of the three items repeated three times.

Mistake: Messing up basics when moving on to more advanced topics.

I wasted a couple hours last week when I messed up the cellForRowAtIndexRow by writing

cell.textlabel.text = self.store.pirates[[self.tableview indexPathForSelectedRow].row];

instead of

cell.textlabel.text = self.store.pirates[indexPath.row];

Lesson: Watch for simple mistakes, when something is not working as expected.

Flatiron School Week 3 Notes to Self on Unit Testing

As I’ve started to work on my TinderClone side project more, I’ve realized the need to have good tests.

Why are tests useful?

Test Driven Development allows you to limit scope and develop in an efficient iterative way.

Tests makes sure things are working when you make changes in other parts of the code.

Tests automate a lot of actions that would be tedious to do manually every time.

When are tests needed?

Tests are especially helpful when you’re trying something experimental and don’t want it to break the rest of your code. (Branching is also a great idea when starting a new feature or something that you’re not sure will work.)

Tests are helpful for debugging large projects.

When are tests not needed?

When I work on smaller projects, I’ve found that tests are not worth the time needed to write them.

How do I tap the back button when testing a UITableViewController in KIF?

I tried for the longest time to click the back button, but I couldn’t reference it using an accessibilityLabel. I found that you need the navigation controller to pop the top view controller.

In the it block:

[nav popViewControllerAnimated:YES];

To get the navigation controller add these lines

In the beforeAll block:

UIWindow *window = [UIApplication sharedApplication].keyWindow;
nav = ((UINavigationController*)window.rootViewController);

Since the navigation controller needs to be declared throughout the whole test. Declare a navigation controller

Between the first describe statement and the beforeAll block:

__block UINavigationController *nav;

Common tests for KIF automated UI testing for iOS

Example tests for KIF

See if a view is displayed.

it(@"should be able to load a table view controller", ^{
     [tester waitForViewWithAccessibilityLabel:@"Locations Table"];
}

Tap a cell in a table view controller.

it(@"should be able to tap a cell in a table view controller", ^{
     NSIndexPath *thirdLocation = [NSIndexPath indexPathForRow:2 inSection:0];
     [tester tapRowAtIndexPath:thirdLocation inTableViewWithAccessibilityIdentifier:@"Locations Table"];
}

Tap a view (such as a button)

it(@"should be able to tap a cell in a table view controller", ^{
      [tester tapRowAtIndexPath:thirdLocation inTableViewWithAccessibilityIdentifier:@"Locations Table"];
}

Check text of a cell

it(@"should have the correct third element in the table view", ^{
       NSIndexPath *thirdLocation = [NSIndexPath indexPathForRow:2 inSection:0];
       UITableViewCell *cell = (UITableViewCell*)[tester waitForCellAtIndexPath:thirdLocation inTableViewWithAccessibilityIdentifier:@"Locations Table"];
       expect(cell.textLabel.text).to.equal(@"Statue Of Liberty");
       expect(cell.detailTextLabel.text).to.equal(@"1");
});

Accessibility Label

KIF is relying on the accessibility label to find the views on the app. The accessibility label is on the identity inspector or it can be set programmatically.

Accessibility Label
Accessibility Label. Make sure it’s enabled.

Programmatically (in viewDidLoad of a table view controller for example):

self.tableView.accessibilityIdentifier = @"Trivia Table";

Flatiron School Week 3 Questions and Answers (Cool stuff I found this week)

Question: How do I got a progress bar to update on it’s own? 

Answer: Use a NSTimer with a UIProgressView. Stack Overflow


Question: What method is called after a UITableViewCell is tapped?

Answer: didSelectRowAtIndexPath


Question: What’s the thing that allows you to toggle a playlist sort by artist, album and title?

Answer: UISegmentedControl. It’s like a switch that has more than 2 settings.


Question: How should I make a static table view?

Answer: Use a table view with static cells, do not try to make a table view with dynamic cells. It’s a major waste of time.

Week 3 Mistakes and Lessons

Topics this week: TableViewControllers

Symptom: When I see an empty table view controller, this is usually the culprit. 

Mistake: Forgetting to initializing the array in a table view controller.

Lesson: always initialize the array.


Symptom: The dreaded Apple Mach-O Linker Error. Linker command failed with exit code 1. This error is super intimidating because it’s so cryptic. (What the heck does that mean anyway?)

Mistake: Turned out that I had imported a KIF test file “TableViewControllerSpec.m” into another TableViewControllerSpec.m and it was giving the errors:

duplicate symbol _OBJC_METACLASS_$_TableViewControllerSpec ...
duplicate symbol _OBJC_CLASS_$_TableViewControllerSpec ...

Lesson: Don’t quit when you hit error messages. Read the logs, it usually gets you close to the problem.


Symptom: Realizing that the full solution to a problem is not worth implementing and not having the previous version. 

Mistake: Deleting code when it’s a partial solution to go for a full solution.

Lesson: Commit code before trying an experimental solution.


Symptom: Tests fail sometimes but not all the time.

Mistake: Not resetting the test conditions after each or before each tests. Test do not go on in order in KIF.

Lesson: Check the starting condition each time before a test is run. Resetting the tester is cool.

Week 2- Tinder Clone. Breaking down into Objects. Setting up the PageViewController

I felt that I had enough knowledge to start building apps so I decided to copy some popular apps with interesting mechanisms. The idea is to do one every week or two, so that I can build a lot of these apps and learn how they are built.

Why build lots of apps?

Part of my inspiration has been Ira Glass’s notes on storytelling where he walks about beginners needing to do a great volume of work before they can make things that they think are good enough. I’m starting that journey now.

Why reverse engineer apps and not build original apps?

When artists learn to paint, they copy other artists to learn how something is done. In app making, I’m doing the same by analyzing and appreciating how other developers have made their apps.

Project 1 is Tinder.

The interesting mechanism here is the swipe left or right to like or dislike.

Step 1 Break down the app into views

Sketch of Tinder Settings Page
Sketch of Tinder Settings Page
Sketch of Tinder Match Page
Sketch of Tinder Match Page
Sketch of Tinder Messages Page
Sketch of Tinder Messages Page

For my first iteration, I really just want the settings and the match page to work. The messages would be part of the second iteration and moments would be part of the third.

Step 2. Take the views and make the data model

I started by translating the Settings and Match views into classes and properties, leaving the methods fluid for now.

There needs to be a Person class.

Person Class
Person Class

The Facebook properties would be imported with Facebook authentication and the rest would be set by the customer.

For simplicity, I have the location as a NSString now.

When I thought about how the custom initializer would work, I realized that I would probably need to split this class up into small modules with Discovery Properties, and Facebook Properties as classes.

I realized that I would also need a way to match people. It made more sense to make a Matcher class because I would want a central place to store data and review the results (whether people liked each other) to see how well my matching algorithm was working.

MatchMaker Class
MatchMaker Class

Naturally, the array of matches would need a match object, so I wrote a match class.

Match Class
Match Class

Step 3. Make the basic interface in storyboard

Tinder has four pages but I just wanted to mock up the Settings, Match and Messages pages.

Specifications:

  • Slides horizontally from page to page with Settings, Match, and Messages page from left to right.
  • Starts on the Match Page

1st Iteration

Test: I tried three UIViewControllers with buttons on navigation bars that went between the three pages.

  1. Select the storyboard or make one (File > New > File Cmd-N, iOS > User Interface > Storyboard)
  2. Add three View Controllers from the bottom of the Utilities column (right sidebar).
  3. Add a navigation bar to each of the three view controllers.
  4. Add bar button items to each screen as shown below.
  5. Add segues for the bar buttons to the adjacent screens.
1st Iteration of the Storyboard
1st Iteration of the Storyboard

Results: As I found out after running this, the transitions make the new screen pop up from the bottom like a modal window.

  • Slides horizontally from page to page with Settings, Match, and Messages page from left to right. 
  • Starts on the Match Page

2nd Iteration

Test: Use a navigation controller.

I have a navigation controller with the root controller as the Settings page with the Match page and the Messages Page linked to that.

2nd Iteration of the Storyboard
2nd Iteration of the Storyboard using a Navigation Controller

Results: The transitions are now horizontal between pages. It’s not smooth like Tinder’s touch gestures, but it’s close. The app doesn’t start on the match page though if I want the sliding to work in this order.

  • Slides horizontally from page to page with Settings, Match, and Messages page from left to right. 
  • Starts on the Match Page

3rd Iteration

Test: Make the Match page the root view controller and add a link back to the settings page.

3rd Iteration of the Storyboard
3rd Iteration of the Storyboard

Results: After clicking the settings button, the transition slid from right to left and the Settings pages looks just like the messages page. Not what I wanted.

  • Slides horizontally from page to page with Settings, Match, and Messages page from left to right. 
  • Starts on the Match Page

4th Iteration

Test: I’m guessing that the transition is either a customer segue or there’s something about gestures that I don’t know. I’m going to look into gestures between views.

As I looked through the gestures, I noticed the Page View Controller. After googling it and finding this AppCoda tutorial,  the scroll transition looks exactly like what Tinder uses.

5th Iteration

A Page View Controller is a container controller that you put other view controllers inside.

So, I’m going to need to make three classes of view controllers that show the Settings, Match and Messages pages. Then, I’ll put them inside the page view controller.

Looking through the PageViewDemo from the AppCoda tutorial, I see that they have one view controller that creates a pageViewController, provides the data for the pageViewController and sets an array of PageContentViewControllers in the pageViewController.

Sidebar: Navigation Bar vs. Navigation Item

When I was using the Navigation Controller, I needed to add navigation items to the Settings, Match and Message view controllers to be able to add a title to each slide.

When I deleted the Navigation Controller, I made the navigation bar disappear. While the navigation item was still there, it was useless without the navigation bar, so I had to add three navigation bars onto the Settings, Match and Message view controllers.

Where a navigation controller exists, add navigation items. Where there is not , add navigation bars. 

Test: In order to implement the pageViewController, I added a ViewController class inheriting from UIViewController and added a PageViewController, a SettingsViewController, MatchViewController, and MessagesViewController into the properties. The last three are custom classes inheriting from UIViewController.

ViewController.h inheriting from UIViewController
ViewController.m inheriting from UIViewController

I created the five view controllers in the Storyboard and added their class name to each corresponding storyboard ID.

5th iteration of the storyboard
5th iteration of the storyboard

Then I set the properties in ViewController.m to each of the storyboard IDs.

ViewController.m
ViewController.m

Here I set the match view controller to be the starting one.

  • Slides horizontally from page to page with Settings, Match, and Messages page from left to right. 
  • Starts on the Match Page

Now it starts with the match page, but doesn’t have any other other pages. The Settings and Messages pages are there, but they are not connected yet.

I see that the PageViewDemo has two methods called

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController

and

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController

That’s probably what I want to tell the page view controller how to switch between the Settings, Match and Messages view controllers.

When I try to type those in, they were not autocompleting, which means that I was missing something. I think it’s because I haven’t set the ViewController to follow the pageViewControllerDataSource Protocol. After I add that to the ViewController.h file. I get a warning. Yes!!

PageViewControllerDataSource Protocol Warning
PageViewControllerDataSource Protocol Warning

Now I understand why the PageViewDemo had

    self.pageViewController.dataSource = self;

in the viewDidLoad.

Since I only had three pages, I wrote two simple if statement for the viewControllerBefore and viewControllerAfter methods and everything works.

  • Slides horizontally from page to page with Settings, Match, and Messages page from left to right. 
  • Starts on the Match Page