Cooking was not designed for millenials

 

To me, cooking means so many things.

Food is the basis for health and cooking is educating yourself about what foods are good for you and what foods taste good.

 

People cook differently than companies do and way healthier.

  • People cook with real food and companies cook with ingredients you can’t pronounce.
  • People don’t use as much salt, sugar or fat as companies do.
  • People prioritize taste and health and companies optimize on cost.

 

A lot of my family’s celebrations are centered around food. Cooking is passed down from one generation to another and I feel that I’m losing cultural heritage if I don’t learn how to cook. Cooking means community.

 

Economically, if I cook 6 meals a week, I can save 200 dollars a month.

I think people like the idea of cooking. I think cooking just has a bad user interface. For people who don’t have much experience cooking, it’s a large hurdle to cross.

 

Cooking has a bad user interface

  • Millenials are usually single which means cooking for one. Cooking every night for one is time and effort intensive.

 

  • Recipes are broken. They are meant for families who have a large kitchen, plenty of equipment, and a stocked pantry with lots of ingredients. Millenials (especially city dwellers) don’t have these things.
    1. Ingredients. Recipes often require 10-15 ingredients, so it’s hard to make things just from what you have. If you try to make chicken tikka masala one night and thai curry the next, you will have a lot of these ingredients that are not used very often, so there lots of leftover ingredients, which end up spoiling. The ingredient list that need 1 oz of sherry vinegar  means you need to buy a specialize item
    2. Equipment. Recipes need equipment like food processors and large mixing bowls that there’s not room to store. Using a lot of equipment means cleaning a lot of dirty dishes, so it’s a lot of work unless you have a dishwasher (not standard in New York).
    3. Presentation. Recipes are not visual and rely heavily on text to convey instructions. If Snapchat and Instagram are any indication, millenials love visual media.

 

  • Cooking education is lacking. Usually, cooking is taught by parents, who themselves may not be very good. Most education is focused on following recipes and not enough emphasis is placed on learning techniques so students learn to create their own recipes. When a beginner goes online to find a recipe, so many recipes that it’s hard to know what to pick.

 

What’s the answer? That’s next.

 

The Best Credit Card Rewards Calculator

There are so many articles about what the best rewards credit card is. They talk about all the features, pros and cons. Revolving categories. Redemption minimums. Cash back pretending to be miles. It’s really complicated.

You know what would actually answer this question once and for all? If we just calculated your expected rewards from each card and compared them. So, that’s what we’re going to do.

 

Credit Card Rewards Basics

Rewards are only rewards if you’re not paying interest. If you still have balances, pay them off before continuing. You should be paying off your balances every month for the rest of this to be relevant.

There are cashback rewards, there are miles at airlines or hotels, and then there are cashback points pretending to be miles. Here, we’re going to look at the first and the third types.

Length of credit history makes up 15% of the FICO credit score so you should have some cards which are open for a long time. Let’s see what are the best credit cards in the long run.

 

What’s the best cashback credit card to have forever?

You want the redemption rate to be the highest after the sign up bonus has already been obtained. This is the card that you will be spending most of your money on.

What you want to avoid is keeping a card with an expensive annual membership fee open for a long time. It’s not doing you much good.

Screen Shot 2016-07-07 at 4.47.10 AM.png

Citi DoubleCash is the best in this case and that’s great because it also doesn’t have an annual membership fee, so you can keep it open forever without paying for it each year. Also, unlike cards that can only be used to redeem for travel, DoubleCash can be redeemed for anything.

I calculated these numbers using the average New York household spending from before. This example household spends $25,143 a year on their credit cards.

 

What’s the deal with Sign Up Bonuses?

Notice how I said that permanent cards don’t take into account consider the sign up bonus. Here’s why:

The best credit cards in the long term are not the best in the short term, because sign up bonuses make up a large part of the value of rewards cards.

Screen Shot 2016-07-07 at 4.53.24 AM.png

Compare the first year reward values to second year reward values. The red arrow shows Citi DoubleCash. It’s no longer the at the top of the list because all the cards above it have some sort of signup bonus.

If you average the signup bonus over the years that you have the card open, here’s what you get.

Screen Shot 2016-07-07 at 4.56.28 AM.png

As time goes on, the cards separate into two groups, one around $500 a year and another around $375 a year.

Now, looking at just the top cards in the first year compared to Citi DoubleCash.

Screen Shot 2016-07-07 at 4.58.25 AM.png

Notice how these cards start very high due to the signup bonus and then fall below DoubleCash over time. You could stop using the card once you have the sign up bonus, but that generally means having to cancel the card because these signup up bonus cards tend to have annual fees. Cancelling cards affects the length of cards open and so has a small negative effect on your credit score.

 

I don’t spend 25k on my credit cards in a year. Does the math change?

Remember that these numbers were calculated off an annual household spending of 25k.

What happens if you spend less?

12k a year

Screen Shot 2016-07-07 at 5.16.32 AM.png

Citi DoubleCash is still the best card in the long run.

Screen Shot 2016-07-07 at 5.18.22 AM.png

But, in the first year, we see some other cards with signup bonuses overtake DoubleCash.

If you spend less than 12k a year, you may not spend enough to get the signup bonus (ex. Barclays Arrival and Capital One Venture requires 3K spent in three months).

What happens if you spend more?

50k a year

Screen Shot 2016-07-07 at 5.30.25 AM.png

In the long run, DoubleCash still comes out on top. Interestingly, at 95K annually, Barclays Arrival actually equals DoubleCash.

Screen Shot 2016-07-07 at 5.27.22 AM.png

In the short run, DoubleCash is actually doing better now as the weight of the signup bonus is reduced by the increase in spending.

 

What about airline cards, American Express membership points and hotel cards?

They’re very complicated because they can be redeemed for different values depending on how they are redeemed. Another subject for another time.

 

What’s the best cashback rewards card?

For most people spending more than 8k a year, Citi DoubleCash is the best. It gives you the most money without considering signup bonuses.

There are many good cards above with signup bonuses and you could chase them, but it may not be worth your time. You might as well find a card that will last you a lifetime.

Sounds like dating.

How I deleted all my local Evernote notes and recovered them

I am not the first nor the last idiot to delete all my local Evernote notes. Given that they were in a local notebook, you can imagine how important they were to me. Commence panic.

Events leading up to the catastrophe

1. I updated Yosemite.

2. Evernote starts acting up and won’t let open.

3. I go to the app store to download Evernote only to find that I can only “Install” it. Hit install.

4. Find all local files missing. Have a heart attack.

How to Recover your Local Evernote notes

1. Turn on hidden files.

Hit Command + Spacebar to bring up spotlight and search for Terminal

After opening terminal, it should look like this:

Screen Shot 2015-02-01 at 9.54.02 PM

Close all of your finder windows

Enter this line in Terminal and hit enter

defaults write com.apple.finder AppleShowAllFiles TRUE

Nothing will happen. Open Finder again and you will see grayed out files. Those are the hidden ones.

2. Restore notes from Time Machine

Go to Macintosh HD/Users/yourUsername/Library/CoreData

It should have be empty.

Open up Time Machine and go back to an hour ago when you still had the com.evernote.Evernote folder. If you don’t have Time Machine enabled, then you’re out of luck. Sorry. Should look something like this.

Screen Shot 2015-02-01 at 9.58.47 PM

The green files there are your Evernotes.

Hit restore and you will find them in Finder.

3. Delete Evernote from Applications

4. Download Evernote from the website if you had installed Evernote from the App Store

5. Install Evernote again and find your files

Why did it happen?

Installing Evernote from the App Store replaced my old version of Evernote with a clean one which synced with the server. Unfortunately, my local notebooks were not synced with the server, so they were lost.

Why does this solution work?

Evernote Installed from the App Store and Evernote downloaded from the web and installed store their notes in two different places.

Evernote from the App Store puts their notes in the folder:

Macintosh_HD/Users/yourUsername/Library/Containers/
com.evernote.Evernote/Data/Library/CoreData/
com.everNote.Evernote/longAlphanumericString/ENNote/
_records/

Evernote downloaded from their website puts their notes in the folder:

Macintosh_HD/Users/yourUsername/Library/CoreData/
com.evernote.Evernote/longAlphanumericString/ENNote/
_records/

This makes sense because Apple wants all the files for the apps in the App Store to be neatly organized in one container. Normally, when you install a program, it will add file in lots of different places.

Installing Evernote from the App Store deleted the data from the folder where Evernote from the website would typically store it’s files.

You just have to put it back by restoring from Time Machine and getting the right Evernote (the one from the website) to read the files from the right place.

What attempts did not work?

Replace the CoreData folder in the container with the restored one

You would think that simply moving the files from the CoreData folder into the container that App Store Evernote uses would be sufficient, but in fact that Evernote will create a new folder to store it’s notes because the longAlphanumericString is not the same as the one it was expecting.

The notes simply will not open in Evernote or worse tell you that it has an unspecified owner and for you to log out.

Copying the .enspot files directly over also did not work

I will be keeping my notes synced from now on

What use is privacy when you lose your data?

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.

Playing with iOS MapKit Part 3: Making it Pretty

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

After getting down the basic functionality, I wanted to polish up the app to give it a quality feel and to dig deeper into the nitty-gritty details of MapKit.

So I made a list of things that could be improved and proceeded to knock each of them out one by one.

To do list

*I had the most fun with this one.

  1. Remove the jumpy effect on loading
  2. Move bar buttons from toolbar to buttons on the map
  3. Make the callout for the first search result come up
  4. Make the zip code and city on the same line
  5. Make the map center and zoom when the current location is tapped
  6. After clicking the textbox, resign the first responder when the map is clicked
  7. Add Directions
  8. *Zoom to include the destination in the Routeviewcontroller
  9. Intelligently choose walking vs driving directions
  10. Make the starting region of the route, the same as that of the previous view

1. Remove the jumpy effect on loading

When the app loaded, the default map showed the United States and when the user location was updated, it would move center on the current location, leading to a jumpy effect (I didn’t know about setCenterCoordinate: animated: at the time). The jump came about a couple seconds after opening the app depending on the internet connection and lead to a laggy feeling.

Design Choice

Initially, I chose to not center the map on the current location because it was a quick fix. I let the user tap the current location to zoom in. I chose this because it was the easiest fix.

After some iterations, I decided it was better just to zoom in automatically.

Back to list

2. Move bar buttons from toolbar to buttons on the map

Many of the map apps that I’ve seen have floating buttons on the map instead of a toolbar. I planned to do this, but it didn’t seem worth the effort.

Design Choice

After looking at the default maps app and seeing that it used a toolbar too, I decided to save time and stick with the toolbar. I just separated the buttons to make it more symmetrical.

Back to list

3. Make the callout for the first search result come up

Before, I had the callout for the last search result come up. This lead to the map whipping to the last location when it was off the visible region.

Design Choice

Make the first search result come up.

I used enumerateObjectsUsingBlock to save the first item. In doing this, I found out that I needed to set my firstAnnotation object to __block, so that the change would be visible outside of the block. (More on StackOverflow.)

How do I select a MKPointAnnotation?

[self.mapView selectAnnotation:firstAnnotation 
animated:YES];

Back to list

4. Make the zip code and city on the same line

Custom Callout View

The view for the address callout was too small to show the whole line for city, state, and zip code.

Design Choice

The easiest way to fix this was to make the view wider and shorten the zip to five numbers.

Afterwards, I ran into problems with special places that had an extra line for the placename, so I had to add extra code to parse the formatted address lines in cases where there were two or three lines.

In the final version, the callout view is now a bit too big, so future work may be nice to set the size dynamically and even animated.

Back to list

5. Make the map center and zoom when the current location is tapped

Easy, call the zoomIn method that the zoom in button calls.

Back to list

6. After clicking the textbox, resign the first responder when the map is clicked

The touchesBegan: withEvent: method detects when I touch outside the textfield onto the map, so this was easy to fix.

- (void) touchesBegan:(NSSet *)touches withEvent:
(UIEvent *)event
{
    [self.searchText resignFirstResponder];
}

Back to list

7. Add Directions

With the route shown, it was natural to want to show the directions and the distances involved.

Design Choice

The easiest thing was to show the directions in a tableViewController and segue to it from a UIBarButton on the navigation bar of the directions page. This is pretty standard, so I just passed the instructions to a table view controller and displayed it in the cells.

The distances are currently in meters. That’s not very intuitive so it would be nice to have this in blocks.

Back to list

*8. Zoom to include the destination in the RouteViewController

The idea is that when I selected a venue which was outside the visible range, I wanted the map to zoom to fit the destination and my current location. While tedious, executing this was not hard when I saw down to work backwards from

[self.routeMap setRegion:self.adjustedRegion 
animated:YES];

How do I zoom in to fit two locations on a map?

Basically,

  • Get the coordinates for your location and the destination location.
  • Find the difference in latitude and longitude and scale this up by a multiplier. A multiplier of 1 will have both locations on the edges of the screen.
  • Make sure to get the absolute difference. Negative numbers are no good.
  • Make a MKCoordinateSpan with the ABSOLUTE differences in latitude and longitude.
  • Find the average latitude and longitude to get the center for the region.
  • Make the MKCoordinateRegion with the centerCoordinate and the span.
  • Find the adjustedRegion by calling regionThatFits on the region in the last step.
  • Set the MKMapView to that adjustedRegion.
- (void)resizeRegionWithDestination:(MKMapItem *)destination 
userLocation:(MKUserLocation *)userLocation
{
    NSLog(@"need to resize");
    CLLocationCoordinate2D destinationCoordinate = 
        destination.placemark.location.coordinate;
    CLLocationCoordinate2D userCoordinate = 
        userLocation.location.coordinate;
 
    double scaleFactor = 2;
    CLLocationDegrees latitudeDelta = 
        (userCoordinate.latitude - 
        destinationCoordinate.latitude)*scaleFactor;
    CLLocationDegrees longitudeDelta = 
        (userCoordinate.longitude - 
        destinationCoordinate.longitude) *scaleFactor;
 
    if (latitudeDelta < 0) {
        latitudeDelta = latitudeDelta * -1;
    }
    if (longitudeDelta < 0) {
        longitudeDelta = longitudeDelta * -1;
    }
 
    MKCoordinateSpan span = 
    MKCoordinateSpanMake(latitudeDelta, longitudeDelta);
    CLLocationDegrees averageLatitude = 
        (userCoordinate.latitude + 
        destinationCoordinate.latitude)/2;
    CLLocationDegrees averageLongitude = 
        (userCoordinate.longitude + 
        destinationCoordinate.longitude)/2;
    CLLocationCoordinate2D centerCoordinate = 
    CLLocationCoordinate2DMake(averageLatitude, 
        averageLongitude);

    MKCoordinateRegion region = 
    MKCoordinateRegionMake(centerCoordinate, span);

    self.adjustedRegion = [self.routeMap regionThatFits:region];
    [self.routeMap setRegion:self.adjustedRegion animated:YES];
}

The cool part about this code is that it doesn’t care if it’s zooming in or out. It just zooms perfectly to the right region. I had to play with the scalingFactor a couple times to get it to look right. 2 seems to work pretty well.

Back to list

9. Intelligently choose walking vs driving directions

To do this, I needed to get the estimated time for walking directions.

A note on directions. If the MKDirectionRequest has the transportationType set to walking and the destination is not walkable, then the service will return no results. That’s why the transportation type is defaulted to any.

MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init];
 
request.source = [MKMapItem mapItemForCurrentLocation];
request.destination = self.destination;
request.requestsAlternateRoutes = NO;
request.transportType = MKDirectionsTransportTypeWalking;
 
MKDirections *estimatedTimeOfArrival = [[MKDirections alloc] initWithRequest:request];
 
[estimatedTimeOfArrival calculateETAWithCompletionHandler:^(MKETAResponse *response, NSError *error) {
    //do something with the response
}];

After I knew that there was no error, I would check to see if the response.expectedTravelTime was greater than 15 minutes (how much I’d be willing to walk). If it was, I would change the MKDirectionsRequest’s transportType to include driving and find the directions with the modified request.

With the asynchronous call within the completion block, this might be a good candidate for wrapping the asynchronous calls using promises.

Back to list

10. Make the starting region of the route, the same as that of the previous view

Pretty simple. Just pass the region from the first MKMapView to the search results view controller to the RouteViewController through the prepare for segues.

Back to list

At the end of this process, I had an app that I could actually show people and it feels really good.

Up next squashing bugs.

Playing with iOS MapKit Part 2: Reverse Geocoding and Custom Annotation Callouts

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

After following the tutorials in part 1, I wanted to keep exploring MapKit. As reverse geocoding was my next topic, I wanted to show the address of the current location in a callout (view that pops up above the pin).

To do this, I’ll need:

  1. A way to know when the current location was tapped
  2. A way to know the address of the current location
  3. A way to display the address in a callout

1. How do I know when the current location is tapped?

MKMapViewDelegate has a method called:

- (MKAnnotationView *) mapView:(MKMapView *)mapView 
viewForAnnotation:(id<MKAnnotation>)annotation

that is called when an annotation is tapped.

How do I tell that this annotation is my current location?

Notice that the annotation is any class that conforms to MKAnnotation.

Current location comes in a special class called MKUserLocation so you just need to test if the annotation is that class.

if ([annotation isKindOfClass:[MKUserLocation class]]
) {
...
}

Within this if statement, I need to turn the currentLocation into an address.

This MKUserLocation has a CLLocation property, a BOOL property for whether it’s updating, a CLHeading property, a title and a subtitle.

2. How do I find the address with coordinates? (Reverse Geocoding)

Apple provides a CLGeocoder class that has the method

- (void)reverseGeocodeLocation:(CLLocation *)location 
completionHandler:(CLGeocodeCompletionHandler)
completionHandler

All you need is the location which comes from MKUserLocation’s location property.

What does the completion handler return?

NSArray *placemarks, NSError *error

placemarks is an NSArray of CLPlacemarks. Each placemark has an addressDictionary. The key @”FormattedAddressLines” returns an array of formatted address lines.

Typically, there are three lines. One for address, another for city, state, and zip code, and one more for the country. In some situations, there are four lines where the first is the name of a venue.

3. How do I display the address?

One way is to set the callout for the annotation is to set the title of the annotation to the text you want in the callout. For example:

self.address = [NSString stringWithFormat:@" %@\n %@\n 
%@", street, cityState, country];
[(MKUserLocation *)annotation setTitle:self.address];

When I do this, the problem is that there is only one line and the rest of the address gets cut off.

Cut Off Callout

Adding line breaks to the title did not make it appear on additional lines, but only displayed the first line instead.

Callout title with line breaks

Messing with the left and right accessory buttons did not help either.

Callout with left and right accessory views colored red and blue
Callout with left and right accessory views colored red and blue

With no way to set more than two lines of text, I had to make a custom MKPinAnnotationView.

How do I make a custom annotation callout?

Since the callout is a property of the MKPinAnnotationView, I had to make a subclass of the MKPinAnnotationView and overwrite the setSelected: animated method.

- (void) setSelected:(BOOL)selected animated:(BOOL)animated
  1. Draw a label
  2. Set the text of the label with the annotation’s title
  3. Place a view as a subview of the MKPinAnnotationView subclass (which I called MultilineAnnotationView)
  4. Add the label as a subview of view.
setSelected
Why MKPinAnnotationView and not the default user location view?

The default user location view is a class that you cannot access or subclass.

Custom Callout View
The custom callout works! (but not perfectly)

Up next, making things look pretty.

Playing with iOS MapKit Part 1: MapKit Tutorials

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

A main difference between a smartphone and any other phone is that smart phones can tell you where they are. What does location tell you? Location provides context about what you are doing, where you’re going, and by extension, who you are.

Apple makes it easy to show this information on a map with MapKit.

At the Flatiron School, I got a good foundation for learning frameworks, but hadn’t worked with MapKit yet. Since it felt core to many mobile apps, I decided to explore it.

Demo Video

Here is a demo of the finished app. All the code is on Github.

First Stop on the MapKit Train: Tutorials

Whenever I get into a new framework, I learn best by jumping right in and  doing it. Tutorials are a great way to get started. They help me focus by limiting scope and stay productive before my natural curiosity wants to go down some rabbit holes. I also don’t want to reinvent the wheel.

If I have seen further it is by standing on the shoulders of giants.

-Isaac Newton

Searching for iOS Mapkit tutorial, I find some by Ray Wenderlich, TechTopia, and AppCoda. All three have been really useful in the past. I went with the TechTopia one because it was written about iOS 7.

Tutorial 1: Make a MKMapView and Show the Current Location

Basically:

  • Add the MapKit framework
  • Make a MKMapView with Storyboards and import MKMapView
  • Assign the MKMapViewDelegate to the view controller
  • Set the MKMapView to show the current Location
  • *Not In Tutorial: Asking for Permission to Access User Location
  • *Set the location in Simulator
  • Zoom in on the MKMapView by changing the region
  • Change the MKMapView type
  • Update the MKMapView using the MKMapViewDelegate methods

*Most of this is already well explained in the tutorial. I want to point out two bumps on the road.

1. Simulator does not have a GPS, so you have to set the location by the menu bar: Debug -> Location -> Custom Location.

Google maps will give you latitude and longitude for any location if you click on the map. If it’s an icon, right click and click “What’s here?”.

Note: Sometimes the location doesn’t show up in the simulator the first time I run it. When I run it on another sized simulator, it works. Don’t know why this is.

2. More importantly, iOS 8 requires that you get permission to use the user location.

How to Get the Current Location Authorization Status

    CLAuthorizationStatus status = [CLLocationManager 
authorizationStatus];

CLAuthorizationStatus is an enum of

  • kCLAuthorizationStatusNotDetermined = 0
  • kCLAuthorizationStatusRestricted = 1
  • kCLAuthorizationStatusDenied = 2
  • kCLAuthorizationStatusAuthorized = 3 (Deprecated iOS 8)
  • kCLAuthorizationStatusAuthorizedAlways = kCLAuthorizationStatusAuthorized = 4
  • kCLAuthorizationStatusAuthorizedWhenInUse = 5

Statuses start out as kCLAuthorizationStatusNotDetermined

How to Ask the User for Location Authorization

If you want current location data only when the customer is using the app:

[self.locationManager requestWhenInUseAuthorization];

If you want current location data even when the customer is not using the app:

[self.locationManager requestAlwaysAuthorization];

Here I have my CLLocationManager as a property of the class. Change self.locationManager to the name of your locationManager.

How to Add Properties to your Info.plist

Even though you think you’ve requested authorization, you are likely still missing one more piece.

Properties List

In the Supporting Files Folder of your app directory, there is a Info.plist file.

It’s a properties list that your app uses through out the app.

You have to have a property in there for NSLocationWhenInUseUsageDescription (or NSLocationAlwaysUsageDescription depending on which permission your asking for), which tells the customer why you are asking for their location information.

Permission Popup

How to tell if you received location authorization

There is a delegate method for the location manager that tell you when the authorization status changes.

- (void)locationManager:(CLLocationManager *)manager 
didChangeAuthorizationStatus:(CLAuthorizationStatus)
status

The status here is the CLAuthorizationStatus.

Remember to set the locationManager’s delegate to the class that implements the delegate method. In this case, I’ve set the delegate to self inside my view controller.

self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;

Tutorial 2: Add Local Search with MKLocalSearchRequest and Display Results as MKPointAnnotation

Basically,

  • Add a textField so the user can input search terms
  • Add an IB action for the textFieldReturn to call the performSearch method
  • Add a performSearch method that sends a MKLocalSearchRequest and handles the MKLocalSearchResponse by parsing its array of MKMapItems into MKPointAnnotations

This was pretty straight forward. Apple makes the searches super easy by giving you completion handlers.

How to Perform a Local Search

You just need a MKLocalSearch object and a MKLocalSearchRequest object.

MKLocalSearch *search = [[MKLocalSearch alloc] 
initWithRequest:request];

and call

- (void)startWithCompletionHandler: 
(MKLocalSearchCompletionHandler)completionHandler

Tutorial 3: Find Directions with MKDirectionsRequest and Draw Them on the Map

Basically,

  • Add a ResultsTableViewController to show the names and phone numbers of the venues
  • Add a RouteViewController to show the route to the destination
  • Set up a MKDirectionsRequest with a source and destination in the RouteViewController
  • Make a MKDirections instance that calculates directions
  • Pass the MKDirectionsResponse to a showRoute method that adds an overlay of the polylines of the MKRoutes in the response to the map
  • Set up how the overlay will look

Notes

The tutorial sets up a custom UITableViewCell, but that’s not really necessary because you can set the title as the name and the subtitle to the phoneNumber.

Why am I in the middle of the ocean?

It’s important that you implement the MKMapView delegate method

- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:
(MKUserLocation *)userLocation

Otherwise, you’ll find yourself in the ocean next to West Africa a lot (that’s what happens when your coordinates are (0,0)).

How to Get Directions

Make an instance of MKDirections and call:

- (void)calculateDirectionsWithCompletionHandler:
(MKDirectionsHandler)completionHandler

Up next: Going beyond the tutorials

Flatiron School Week 12 – iOS Full time Report

For notes on the first half of the program, check out the halftime report.

The End of the Beginning

These three months have been a tremendous experience. I recall other three-month periods where nearly nothing has changed. In stark contrast, I didn’t know anything about making apps three months ago and now I can make 80% of the good ones out there and I will soon be able to make the last 20%.

Amidst celebration, the feeling of the real world seeps in. The realities of finding a job are readily apparent. These three months have been a working vacation, a sabbatical, with no need to worry about worldly concern, just code. Yet, this search feels so much different from the post-college job hunt. That felt like gauntlet of hurdles, where the goal was to shape myself into the candidate that would be hired. This feels like a dating process. Maybe it’s always been that way, and I’ve only now grown mature enough to see it as such. I go into each interview, each conversation with as much thought about how much I like them as I do with how much they may like me.


Flatiron School Placements

This week, there’s been a lot of job advice, most of which I agree with wholeheartedly.

  • The placements team tell us how to prepare for the technical interview.
  • They tell us how to think about the long term (your career) not just your first job.
  • They tell us how to network.
  • They help us practice mock cultural and technical interviews.
  • They host talks to connect us with alumni and industry people.
  • They help us with our individual situations and questions.
  • All these things are meant to help us get a get a job.

Note: If you’re an international student, it may be hard to find a company to sponsor your work visa. 

Note 2: iOS developer make slightly more than Ruby on average.

I’m expecting the job finding process to go through a few iterations and take at least 2 months.

My advice: 1. Just like dating, you have to go out there, meet people and build relationships. 2. Keep coding to keep improving yourself. 3. Stop when you’ve found a place you’re excited to work for at least a year or two.


iOS Client Projects 

In interviews, people like to see that you’ve worked on projects before and especially if you’re worked with clients before.

One of the things that no one told me was that iOS students have client projects for the last four weeks. Ruby students work on their own projects.

I worked with a team for a client called HireCanvas, which makes the college campus recruiting process easier (how appropriate for my situation).  We helped them build out a iOS version of their web app.

Half of your time will be spend figuring out where to go

  • For the first eight weeks of labs, objectives and deadlines were established for us. Coding as a career gets real when I sit in front of a computer and spend half the time figuring what needs to be done when and what should be done next. Not having this structure makes me appreciate the time and thought put into designing a curriculum. On the project, we also have to make choices that will determine whether the project will be done in time. Do I use a cocoa pod or build my own?

Git only gets real in groups

Teamwork helps you figure out what makes good teammates

  • The other thing about working with people is that it helps you develop an intuition for who you would want to work with and who you wouldn’t. Sometimes people surprise you. You might think that an experienced programmer would be great to have on a team until you realize that commitment was a more important trait to look for.

Communication Communication Communication

  • As with any type of project, communication turned out to be key. When communication breaks down, people assume the worst and death spirals are started. Talking through it, you realize that the thing you were worried about was not that important to the other person.

A note on legal agreements

Read before you sign. Try to change what you don’t like.

Unlike software agreements, employment contract and non-disclosure agreements should be reason to pause. People will pressure you to sign, so you can get the job. They will tell you that it’s standard practice. That’s a whole lot of bs.

If they’re never going to use a part of the agreement, you shouldn’t have to agree to it.

Make sure you understand what you’re signing and if you don’t like it. Ask if you can change it. A lot of times it’s so boilerplate that the people you’re working with don’t even know what’s in it. If it can’t be changed, ask whether it’s worth taking on the work. I’d rather forgo a project than sign something I’ll regret later.

One thing the school could have done better was show us the legal agreements we would need to sign for the client projects and for attending the school before money and time was spent.


Overall Lessons

1. The number one goal for coming to Flatiron School is to learn how to be self-sufficient as a developer

Use the time to learn to fish and you will have skills for a lifetime. Use this time to try and fail, not produce a portfolio or kickstart your startup. The real world is not as forgiving.

2. Flatiron School brings together very nice, talented people. Build relationships with them.

For most of college, I didn’t appreciate the diversity of human talent and what I could learn from others. At Flatiron, the side projects I’ve seen really brings out people’s passions. I appreciate being around so many talented people (students especially).

3. Have enough living expenses for 6 months before you come to Flatiron

People who only had four months are now scrambling to find the first jobs they can. That’s an unenviable position to be in. Give yourself the time to think about what you want to do after Flatiron. You might be a different person by then.

4. Learning to code will make you feel more empowered than ever before

Not only can you make other people’s dreams come true, you can make your own reality.

Flatiron School Week 12 – Learn Swift or Objective-C?

I was asked whether a person with no iOS experience should learn Swift or Objective-C. After talking to a few people at the school, I’ve come to a simple conclusion.

Some Observations about Swift

In a couple days of playing with Swift, I’ve noticed that I could pick it up pretty easily. The frameworks are the same, but there’s some cool new syntax that will save a lot of typing (closures come to mind). I’ve also found that similar to learning Spanish and French at the same time, learning Swift will confuse your Objective-C syntax.

Learn Objective-C if you plan to be a professional iOS developer within the next year

For professional iOS developers, most companies are still in Objective-C and you will be expected to read and write it. Simple as that.

Learn Swift if you want to just work on your own side projects

Especially if you’re working on iOS development on the side, by the time you’re familiar enough with iOS frameworks, Swift will be more mature, more usable by then.

Flatiron School Week 9 – Resolving my first real git merge conflict

This week we started our client projects. 19 people broke down into teams of 4 or 5. There were four clients and our team had four people. This was the first time that I really needed to deal with git merge conflicts and develop a workflow.

It wasn’t long before I had a conflict.

There were three files that I conflicts with:

  1. AppDelegate.m
  2. the .pbxproj file
  3. a xccheckout file

1. AppDelegate.m

I knew how to resolve the first one, but as I tried to do that, my file directory on the left side of Xcode disappeared. It was time to ask for help. Zach, one of our TAs, helped me put together a plan to resolve the conflicts.

I had to back out of the merge, so I used

git merge --abort

and then reset the head to the last commit because there some uncommit changes that I didn’t want anymore.

git reset --hard <commit id>

2. the .pbxproj file

Apparently that file directory is stored in the .pbxproj file. The way that we resolved this was by telling git to just join the two versions together in the .gitattributes file by adding:

*.pbxproj merge=union

Another way to resolve this is to choose one version of the pbxproj.

git checkout --ours <directory/file>

Replace “ours” with “theirs” to get the version being merged into the current branch.

I had to add in some files that were no longer referenced by the pbxproj file after the merge using this second method.

3. a xccheckout file

For the .xccheckout file, this was a file that should have been ignored. I added this to the .gitignore file.

*.xccheckout

The problem was that both versions that I was trying to merge still had this file in tracked files. I had to remove this file from both repos by running:

git rm --cached <directory/file>

Once the .xccheckout file was removed, I could merge the two repos.

Both the .gitignore and .gitattributes files should be set up at the beginning of a project.

Update

Since that week, we’ve had to resolve conflicts many times. Following Github’s workflow has been pretty useful. I’ve found that the best way to resolve conflicts is prevention. We designed our workflow so that each person worked on a different part of the app and that minimized conflicts.