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.

Flatiron School Presents – The Multipeer Connectivity Framework (Lattice)

Another Flatiron student and I were interested in the Multipeer Connectivity Framework in iOS, so we made a chat app called Lattice that uses multipeer in the event of a natural disaster as a demonstration of the technology.

I’m going to talk about how the basics of multipeer connectivity work.

The Multipeer Connectivity (MC) Framework

What does it do?

Multipeer allows devices to connect with others nearby without the internet.

How does it work?

Multipeer Connectivity is an implementation of mesh networking, creating a local network between devices within Wifi or Bluetooth range. Not only are new devices connected with devices within range but also with all devices that those devices are connected to, even if the destination device is not within range of the sender.

That’s the real power of mesh networks. If permitted, devices can send messages relayed through other devices.

How might this be useful?

  1. Mesh networks allow communication when internet infrastructure is damaged (such as natural disasters).
  2. Mesh networks allow communication when internet access is restricted by governments, seen in the protests of Hong Kong.
  3. Mesh networks allow communication in internet-less situation (camping trips, road trips, subway commutes, and foreign vacations).

Lattice – Demo Project

Before we dive into the nitty gritty, I’ve placed Lattice on Github, so you can see exactly how the snippets below are used.


Setting up the MCManager

We have a MCManager class, similar to an API manager or data store for Core Data. Four properties need to be set up.

MCPeerID

_peerID = [[MCPeerID alloc] initWithDisplayName:displayName];

This is the name of your device.

MCSession

_session = [[MCSession alloc] initWithPeer:self.peerID securityIdentity:nil encryptionPreference:MCEncryptionNone];
_session.delegate = self;

This is the session that your device will start. Think of a session like a chatroom.

MCNearbyServiceAdvertiser

_advertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:self.peerID discoveryInfo:elements serviceType:LatticeServiceType];
self.advertiser.delegate = self;
[self.advertiser startAdvertisingPeer];

This adverser tells nearby browsers that it is looking to connect with a particular serviceType. DiscoveryInfo is a dictionary that the browser will receive when it finds this advertiser.

MCNearbyServiceBrowser

_browser = [[MCNearbyServiceBrowser alloc] initWithPeer:self.peerID serviceType:LatticeServiceType];
self.browser.delegate = self;
[self.browser startBrowsingForPeers];

This browser looks for advertisers of a particular service type to connect to.


Connecting to Other Devices

Lattice, the demo app, will have an advertiser and browser running at the same time on each device.

Browser Finds Peer, Invites Peer

- (void)browser:(MCNearbyServiceBrowser *)browser foundPeer:(MCPeerID *)peerID withDiscoveryInfo:(NSDictionary *)info
{
   [self.browser invitePeer:peerID toSession:self.session withContext:nil timeout:10]
}

When a browser finds an advertiser, it can parse the discoveryInfo that the advertiser set when it was created. In this case, we want the devices to connect automatically, so the browser sends an invitation to connect to the browser’s session.

Advertiser Accepts Invitation

- (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didReceiveInvitationFromPeer:(MCPeerID *)peerID withContext:(NSData *)context invitationHandler:(void (^)(BOOL, MCSession *))invitationHandler
{ 
    invitationHandler(YES, self.session); 
}

Since we want the advertiser to connect automatically, the advertiser will reply YES to the invitation.

Session Confirms Connection

- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state

We can indeed confirm that the two devices are connected with this delegate method from the session. The session will tells us that the peer is now in the connected state.


Sending and Receiving Messages

Sending From the View Controller

[self.multipeerManager.session sendData:messageData toPeers:self.multipeerManager.session.connectedPeers withMode:MCSessionSendDataUnreliable error:&error];

Messages are sent from the session to the peers in the session.

Receiving In the MCManager

- (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID
{
    //Kicks off a notification to the View Controller 
}

Messages are received by the session on the other devices. In Lattice, we triggered a notification with each message received.

Catching the Notification In the View Controller

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(peerDidReceiveDataWithNotification:) name:@"MCDidReceiveDataNotification" object:nil];
- (void)peerDidReceiveDataWithNotification:(NSNotification *)notification
{
    NSData *messageData = notification.userInfo[@"data"];
    NSArray *messages = [NSKeyedUnarchiver unarchiveObjectWithData:messageData];
    [self.demoData.messages addObject:messages[0]];
}

The notification was handled by the view controller to display the new message.


Limitations

iOS only

Multipeer Connectivity is a iOS framework.

Range can be limited on Bluetooth

We found it to be about 60 – 100 feet.

Downloading the app requires the internet?!

There should be a way for the app to spread virally with no internet.

Limited usefulness in zombie apocalypse

This would be great for finding survivors nearby without having to go room by room through scary dark buildings. During night when the zombies are outside, the bluetooth signal may not be long enough to communicate between buildings.


Special Thanks

Peter Fennema for showing how to connect via multipeer automatically

Jesse Squires for the messaging user interface: JSQMessagesViewController

Flatiron School Week 7 – BocceGame built with UIDynamicAnimator

UIKit Dynamics

In my spare time, I wanted to look at animating physics collisions so I found UI Dynamics. It’s a part of UIKit that has some build-in functionality to animate physics based interactions. It’s generally not used for games, but rather for transitions and cool UI tricks.

A bocce game is a simple way to demonstrate this functionality. In bocce, there’s a target ball called the jack and two teams try to get their ball as close to the jack as possible. The team gets points for the number of balls that are closer to the jack than the nearest opponent ball.

Bocce Game Demo

Github Repo

How to use UIDynamicAnimator

You have an animator.

Animators can add behaviors.

Behaviors can add items that they affect.

My animator

    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

One behavior is the UIDynamicItemBehavior behavior that allows the ball to continue moving after released by the hand with the add

self.linearVelocity = [[UIDynamicItemBehavior alloc] init];
[self.linearVelocity addLinearVelocity:velocity forItem:gesture.view];
[self.animator addBehavior:self.linearVelocity];

Another behavior is the collision behavior that collides with either a wall or another item.

//initialize collision behavior
self.collision = [[UICollisionBehavior alloc] init];
[self.animator addBehavior:self.collision];

How to make this game

Dragging a ball

Set up a UIPanGestureRecognizer that calls the dragged: method.

    UIPanGestureRecognizer *draggable = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(dragged:)];
    [self.currentBlock addGestureRecognizer:draggable];

The dragged method has the form:

- (void) dragged:(UIPanGestureRecognizer *)gesture
{
switch (gesture.state) {
case UIGestureRecognizerStateBegan:
//save the starting location
break;

case UIGestureRecognizerStateChanged:
{
CGPoint translation = [gesture translationInView:self.view];
 
//calculate the new location with the starting location and the translation
//set the location to the new location
 
break;
}
 
case UIGestureRecognizerStateEnded:
{
 
//figure out the velocity when the ball is released and add it to the item with the UIDynamicItemBehavior
}
 
break;
}
 
default:
break;
}

}
}

Continue a ball moving after release

Right now, the ball would stop when I let go of it. To keep it going at the same speed, get the velocity in view from the gestureRecognizer and add it to the UIDynamicItemBehavior with addLinearVelocity: forItem:

//Inside the case UIGestureRecognizerStateEnded
CGPoint velocity = [gesture velocityInView:self.view];
[self.linearVelocity addLinearVelocity:velocity forItem:gesture.view];
[self.animator addBehavior:self.linearVelocity];
 
//necessary to make the motion smooth, otherwise it will be jumpy if you hold down the 
[self.animator updateItemUsingCurrentState:gesture.view];

Stopping the ball

The ball will keep going on for a long time, so I needed to stop it somehow.

Stop the ball by adding friction. In UIDynamicItemBehavior, friction is the resistive force between two objects. I wanted resistance to simulate air resistance. Angular resistance was needed to stop the balls from spinning too.

self.linearVelocity.resistance = 8;
self.linearVelocity.angularResistance = 8;

Only allow one animation to run at a time

I found that trying to flick a ball before another had finished moving was making a really jumpy animation, so checked if the animation is running with each gesture recognized and only let the new block move after the other was finished moving.

- (void) dragged:(UIPanGestureRecognizer *)gesture
{
    if (![self.animator isRunning]) {
        switch (gesture.state) {
...

Detecting when the ball was stopped

To find out when to make the next block, I needed to know when the ball had stopped moving. I used the

- (void)dynamicAnimatorDidPause:(UIDynamicAnimator *)animator

method. It’s a delegate method so I had to set the

    self.animator.delegate = self;

Making the target ball bounce off the walls

I didn’t want the target ball to go off the screen, so I set the screen boundaries as walls,

[self.collision setTranslatesReferenceBoundsIntoBoundary:YES];

made the ball bound off all objects including boundaries (referenceBounds being those of self.view because the animator has self.view as the reference view),

[self.collision setCollisionMode:UICollisionBehaviorModeEverything];

and added another invisible wall, so that the ball would not end up on the top edge of the screen.

[self.collision addBoundaryWithIdentifier:@"end" fromPoint:CGPointMake(0, self.view.frame.size.height*0.1) toPoint:CGPointMake(self.view.frame.size.width, self.view.frame.size.height*0.1)];

Take the target ball out of collision

I wanted to make the target ball immovable, so I just had to remove it as an item of the collision.

[self.collision removeItem:self.currentBlock];

I also wanted future object to be able to go off the screen, so I removed the “end” boundary and the window boundaries.

[self.collision removeBoundaryWithIdentifier:@"end"];
[self.collision setTranslatesReferenceBoundsIntoBoundary:NO];

Ignoring most the call to dynamicAnimatorDidPause after a ball is made

After a new object is added, dynamicAnimatorDidPause is called, so I had to add a BOOL property justMadeBlock to ignore those calls.

Stop the moving block when it goes offscreen

If a ball was flicked too hard, then it would go off the screen and I would have to wait for it to stop before the next ball would load. To shorten that time, I made another boundary outside the edge of the screen and overwrote a delegate method to make the ball stop moving and not able to collide with other blocks.

[self.collision addBoundaryWithIdentifier:@"outOfBounds" forPath:[UIBezierPath bezierPathWithRect:CGRectMake(-90, -90, self.view.frame.size.width+180, self.view.frame.size.height+180)]];

Delegate method for the UICollisionBehavor

self.collision.collisionDelegate = self;
- (void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier atPoint:(CGPoint)p
{
    if ([((NSString *)identifier) isEqualToString:@"outOfBounds"]) {
        //not allow it to move
        [self.linearVelocity removeItem:item];
        //not allow it to interact with future blocks
        [self.collision removeItem:item];
    }
}

Calculate the nearest ball and marking the nearest ones

Once a colored ball had been thrown, there is now a winning ball (closest to the target).

I took all the balls on screen.

    NSArray *blocksOnScreen = [self.collision items];

Got their locations

Sorted the array of balls by location

And found the balls of the same color that were closest to the target ball

With those “winning balls”, I added a CALayer to them to mark them as winning.

The next to go would be the team further away from the target.

At the end of the game, a UIAlertController asks if you want to play again.

Adding indicators for the number of balls remaining per team

I used CAShapeLayers and added them to the self.view.layer. They would update as balls were made.

Adding a flashing turnIndicator

The alpha controls the opacity of the text label, so setAlpha:1.0 means make it appear.

UIViewAnimationOptionAutoreverse makes it disappear again.
UIViewAnimationOptionRepeat makes the animation keep going until you stop it.
UIViewAnimationOptionAllowUserInteraction allows you to remove the animation later on.

[UIView animateWithDuration:1.5 delay:0 options:UIViewAnimationOptionAutoreverse| UIViewAnimationOptionRepeat | UIViewAnimationOptionAllowUserInteraction  animations:^{
        [self.turnIndicator setAlpha:1.0];
    } completion:nil];

Stopping animations

When I tried to reset the game, setting all the properties back to nil worked well, except for stopping the turnIndicator animation.

There I found that the animation is in the layer of the animated object.

[self.turnIndicator.layer removeAllAnimations];

Choosing a more attractive color scheme

Adobe’s color wheel was really cool. I like pastel colors, so this is what I chose.

Notes to Self

Writing code takes way longer than expected

I didn’t think that it would take very long to make this, (just slap some blocks on a screen right?) but building features takes time and focus. Really quickly, seeing jumpy animations makes you want to fix them and that’s the 20% that takes the most time. Writing blogs about code also takes hours…

User testing is super important to do throughout

I knew how the game was played, but it wasn’t obvious to people I showed it to. I really wouldn’t have known if I hadn’t had someone use the game.

Don’t let sidetracked on super advanced features when the basics are not done

The flip side of asking for feedback is that people start giving you really good suggestions, but I think you have to finish one phase before hacking away at the next thing.

Hacking is fun with something quick, but it’s no fun with a big project

This is the second project that I’ve hacked to together. It’s been fun, but I really would have liked to have classes for the data model. This game was made over three weekends and I was finding that when I wanted to add new features, I had to recall the mental model that I had for the project. Instead I should have had a game class that remembered the model for me.

Flatiron School Week 6 – An analogy for understanding blocks

Blocks can be a really confusing concept and they’re further obfuscated by the crazy syntax. So crazy there’s a website just for the syntax.

Here’s one way to think about blocks.

If you’ve ever worked in a corporate office before, you know about making decks (powerpoint presentations) as I did in my former life.

  1. When your team needs to build a deck, your manager probably tells you to help him with a couple slides for the story he’s going to tell. He’s the architect but he doesn’t make graphs. So he IM’s you to make them and to tell him when you’re done. (Message one sent.) While he’s waiting for you, he’s continuing to work on other parts of the slide, so that he’s ready when you come back with the data.
  2. Let’s say that you can only make these fancy looking graphs for him and you don’t know what he’s going to do with it. This time, the data he needs is outside your normal scope. So you call up your friend over in finance to get the right numbers and ask him to email it to you. (Message two sent.) While he’s digging those out for you, you figure out how you want to display the data on the chart.
  3. Your friend get the data and emails it back to you.
  4. You take the data and put it in the graphs and send it to your manager.
  5. Your manager takes your graphs and puts it in the slide.

Congratulations! You’ve just used blocks.

Here’s what it would look like in code.

You have the Manager, Analyst, and DataAnalyst classes.

What does the manager need to do?

To the outside, the manager knows how to make decks, but they don’t know how the decks are made.

Manager.h

#import <Foundation/Foundation.h>

@interface Manager : NSObject

- (void)makeDeck;

@end

Manager.m

//
//  Manager.m
//  blockAnalogy
//
//  Created by Xida Zheng on 11/8/14.
//  Copyright (c) 2014 xidazheng. All rights reserved.
//

#import "Manager.h"
#import "Analyst.h"

@interface Manager ()
@property (strong, nonatomic) Analyst *analyst;

@end


@implementation Manager


- (void)makeDeck
{
    //make the start of the deck
    
    [self getGraphsFromAnalyst:self.analyst withCustomStyling:^(NSArray *graphs) {
        //any custom styling required for this deck
    }];
    
    //make the rest of the deck
}


- (void)getGraphsFromAnalyst:(Analyst *)analyst withCustomStyling:(void (^)(NSArray *))customStyling
{
    [analyst makeGraphsWithSpecifications:[NSDictionary new] CompletionEmail:^(NSArray *graphs) {
        //standard graph handling (i.e. put the graphs on slides)
        customStyling(graphs);
    }];
    
    //make the rest of content on the slides with graphs
}

@end

In order to make the deck, the manager, needs their analyst to get graphs and passes those graphs through any custom styling instructions before the slide makes it into the deck.

(Step 1.) Calling [analyst makeGraphsWithCompletionEmail…] is like IM’ing the analyst to make the graphs. The manager provides the analyst with specifications for the graphs and instructions for when the analyst is done.

What does the analyst do with that information?

(Step 2.) He takes it and translates the conversation with his manager into an algorithm to make the slides and a data request for his friend.

(Step 4.) After he receives a response back, he turns the data into graphs based on his manager’s specifications and send the graphs back to his manager via email.

//
//  Analyst.m
//  blockAnalogy
//
//  Created by Xida Zheng on 11/8/14.
//  Copyright (c) 2014 xidazheng. All rights reserved.
//

#import "Analyst.h"
#import "DataAnalyst.h"

@interface Analyst ()
@property (strong, nonatomic) DataAnalyst *friend;
@end


@implementation Analyst

- (void) makeGraphsWithSpecifications:(NSDictionary *)specifications CompletionEmail:(void (^)(NSArray *))completionEmail
{
    //turn specifications into a way to make for the graphs
    //turn specifications into a data request to your friend
    [self.friend getDataWithSpecifications:[NSDictionary new] CompletionEmail:^(NSString *data)    {
        //apply specifications with data
        NSArray *graphs = @[data];
        
        completionEmail(graphs);
    }];
    
    //make other graphs with data you're familiar with
}

@end

What did the data analyst friend have to do?

(Step 3.) Just take in the specifications from the analyst and send back the data in an email.

//
//  DataAnalyst.m
//  blockAnalogy
//
//  Created by Xida Zheng on 11/8/14.
//  Copyright (c) 2014 xidazheng. All rights reserved.
//

#import "DataAnalyst.h"

@implementation DataAnalyst

- (void) getDataWithSpecifications:(NSDictionary *)specifications CompletionEmail:(void (^)(NSString *))completionEmail
{
    NSString *data = @"data";
    
    completionEmail(data);
}

@end

I hope that helps clarify blocks. Let me know what you thought.

Remember, even though blocks are just funny looking objects, they’re misunderstood, and they deserve some love too.