Here is what I'm learning right now:
I'm currently transitioning from full-stack Ruby on Rails development to iOS development.
Check out my blog at natashatherobot.com or follow me on twitter on twitter.com/NatashaTheRobot. Let me know if you need help learning how to program or would like to learn with me :)
Here is what I'm learning right now:
- iOS Development
- Ruby on Rails
- Backbone.js
- Test Driven Development
- JavaScript / CoffeeScript
- JQuery
- HTML5 / HAML
- CSS3 / SASS
Dev Bootcamp is a 10 week intensive training in an intimate class lab setting where students learn how to build web applications using Ruby on Rails. Dev Bootcamp is not a typical classroom. Think of it more as apprenticeship or a workshop. Students learn by doing and building, not by sitting back and listening. Bootcamp also closely resembles an actual work environment.
Holler is a new company based in San Francisco working on a stealth mobile-location product. We are not another check-in company :) We'll be providing more details in the coming weeks.
Assist in project managing the Media and Platforms Strategic Partnerships Team.
Responsibilities included managing data integrity and workflow processes in Salesforce.com, running reports and creating dashboards in Salesforce.com, using Microsoft Excel to analyze large amounts of revenue data, helping train the Sales team on how to use Salesforce.com.
Top Secret.
Top Secret.
Assisted with a social psychology research project concerning the legal issue of eminent domain.
Responsibilities included researching historical legal information, running participants, and coding and analyzing data.
Top Secret
The Center of Wrongful Convictions works to help the wrongfully convicted tell their story and win their freedom, educate the public about wrongful convictions and how they happen, and promote legislation to prevent wrongful convictions.
Responsibilities included answering letters from individuals requesting help, organizing and tracking case files, and researching wrongful conviction cases to promote awareness.
Trial consulting is the use of social scientists, particularly psychologists and communication experts and economists, to aid attorneys in the presentation of a criminal trial or civil lawsuit.
Responsibilities included learning about the field of trial consulting through research and experience. A variety of experiences were included ranging from doing office work to entering data to learning and helping complete reports for clients. Attended mock trials, focus groups, and witness preparation seminars.
Assisted with a cognitive psychology research project focused on determining the importance of gestures in children’s cognitive development.
Responsibilities included conducting research, running participants, and coding and analyzing data. Conducted my own research project with the professor’s guidance.
This week, I’ve been working on an app which, as part of it’s functionality, displayed nearby photos from Flickr on a map (see screenshot below).
Since I got the images from Flickr, I didn’t have control of the size of the photo returned. I also actually wanted them to be bigger, so I could also show the photos as a slideshow (which you get to by clicking the SlideShow button).
However, while the MKAnnotationView has an image property, it’s not possible to resize these images. So to get the images to show up resized, I had to subclass MKAnnotationView, add an image view subview of a small size to it, and then display the image in the image view:
// FlickrImageAnnotationView.h #import <MapKit/MapKit.h> @interface FlickrImageAnnotationView : MKAnnotationView @end
// FlickrImageAnnotationView.m
#import "FlickrImageAnnotationView.h"
@interface FlickrImageAnnotationView()
{
UIImageView *_imageView;
}
@end
@implementation FlickrImageAnnotationView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// make sure the x and y of the CGRect are half it's
// width and height, so the callout shows when user clicks
// in the middle of the image
CGRect viewRect = CGRectMake(-20, -20, 40, 40);
UIImageView* imageView = [[UIImageView alloc] initWithFrame:viewRect];
// keeps the image dimensions correct
// so if you have a rectangle image, it will show up as a rectangle,
// instead of being resized into a square
imageView.contentMode = UIViewContentModeScaleAspectFit;
_imageView = imageView;
[self addSubview:imageView];
}
return self;
}
- (void)setImage:(UIImage *)image
{
// when an image is set for the annotation view,
// it actually adds the image to the image view
_imageView.image = image;
}
@end
I’ve recently been working on an app called FoodSquare, it’s basically Yelp sorted by Foursquare checkins instead of user reviews. Just like in the Yelp app, I wanted to populate an image for each restaurant on my UITableView. It’s not done yet, but here is a screenshot:
To get the images to load, I started out by storing the image URL that was returned via the FourSquare API, and then in the tableView:cellForRowAtIndexPath: method, getting the image data through the image url like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Venue *venue = ((Venue * )self.venues[indexPath.row]);
cell.imageView.image = [UIImage imageWithData:
[NSData dataWithContentsOfURL:venue.imageURL]];
}
While this seems reasonable, when I actually loaded up my table view, I noticed that scrolling was really slow. The UIImage imageWithData: method is NOT asynchronous, so as the table view loads each new cell, it has to go out to the image url and download the data, locking up the app while doing so.
So I made it my mission this weekend to figure out how to get the images asynchronously. Luckily, my friend, an iOS developer, Nick O’Neill was in town, and gave me a few amazing pointers on how to load the images according to Apple’s recommendation.
First, Nick pointed me to the SDWebImage library, that has a lot of features for working with images, including getting them asynchronously. However, if you’re just looking for that one functionality, there is a simple way to handle image downloads by first creating this method:
- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ( !error )
{
UIImage *image = [[UIImage alloc] initWithData:data];
completionBlock(YES,image);
} else{
completionBlock(NO,nil);
}
}];
}
Basically, you send in a URL (or you can modify the method to send in a url string if you want) and a completion block, and do a normal asynchronous url request to the image url. You know that the data that will be returned will be an image, which you instantiate, and pass in to the passed in completion block.
Now, in my tableView:cellForRowAtIndexPath: method, I can download the image and save it asynchronously:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = @"venue";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
}
Venue *venue = ((Venue * )self.venues[indexPath.row]);
if (venue.userImage) {
cell.imageView.image = venue.image;
} else {
// set default user image while image is being downloaded
cell.imageView.image = [UIImage imageNamed:@"batman.png"];
// download the image asynchronously
[self downloadImageWithURL:venue.url completionBlock:^(BOOL succeeded, UIImage *image) {
if (succeeded) {
// change the image in the cell
cell.imageView.image = image;
// cache the image for use later (when scrolling up)
venue.image = image;
}
}];
}
}
I really love the use of blocks in the downloadImageWithURL:completionBlock: – it finally makes sense to me why block are so important in iOS development. Looking forward to using this type of pattern a lot more!
So there is something that’s been driving me super crazy ever since I started using XCode. Whenever I drag in an outside file into my project (like an image I just downloaded from the internet), the file gets copied by Reference, and not actually saved in my project directory.
I noticed this when I deleted some images from my desktop (thinking they were now copied over to my project directory), and all my images in the project disappeared. The images also don’t go up to Github, so when someone else downloaded my project to check it out, they were missing the images. If I released this app, it would be missing the images as well! So I really don’t know why it’s even an option to NOT copy over the actual file into your file directory.
So recently, I’ve been making sure to save the files in my project directory (via the Finder), and then drag the images over to my XCode project from the project folder in the Finder. Again, super painful, but at least the references to the images in XCode pointing to the images in my project folder.
Yesterday, however, my friend mentioned that all I have to do is check the “Copy items into destination group’s folder (if needed)” option to actually copy the files into my project directory. This is a much easier solution! Too bad the vague wording doesn’t make it clear what that option actually means!
I love using the Navigation Controller for most of my projects, but unfortunately, the navigation bar just doesn’t look that great! Here is a screenshot of an app I’m working on this weekend – a Yelp sorted by the number of Foursquare checkins instead of user reviews:
I wanted my navigation background to be more like in the Twitter app, so I had a friend who’s good with photoshop quickly copy over the twitter navigation bar background and send it to me as a png. In my ViewController’s viewDidLoad: method, I then did the following to set the new navigation bar background image:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController.navigationBar
setBackgroundImage:[UIImage imageNamed:@"navBar.png"]
forBarMetrics:UIBarMetricsDefault];
// Do any additional setup after loading the view.
}
So now, my new nav bar looks like this:
Next step, need to change the navigation bar button color!
For the past two weeks, I’ve been working a lot with APIs, which of course are in the JSON format and usually have dictionaries inside of dictionaries inside of dictionaries.
Let’s take a look at the App.net public api. An example app.net update will look something like this:
{
"created_at": "2013-05-25T03:39:05Z",
"num_stars": 0,
"num_replies": 0,
"source": {
"link": "http://riposteapp.net",
"name": "Riposte",
"client_id": "737a54nLCdLLutcs2VzhtNKGnnMrakc4"
},
"text": "We need new rules for using acronyms. If googling an acronym does not find it in the first few links, you should spell it out instead.",
"num_reposts": 0,
"id": "6027821",
"entities": {
"mentions": [],
"hashtags": [],
"links": []
},
"html": "We need new rules for using acronyms. If googling an acronym does not find it in the first few links, you should spell it out instead.",
"machine_only": false,
"user": {
"username": "jrinn",
"avatar_image": {
"url": "https://d2rfichhc2fb9n.cloudfront.net/image/5/eGiW2i1Ziwn9i1YyM5jE99Wwat97InMiOiJzMyIsImIiOiJhZG4tdXNlci1hc3NldHMiLCJrIjoiYXNzZXRzL3VzZXIvZGQvMDIvMzAvZGQwMjMwMDAwMDAwMDAwMC5qcGciLCJvIjoiIn0",
"width": 200,
"is_default": false,
"height": 200
},
"description": {
"text": "iPhone app developer and father.",
"html": "iPhone app developer and father.",
"entities": {
"mentions": [],
"hashtags": [],
"links": []
}
},
"locale": "en_US",
"created_at": "2013-04-02T06:23:09Z",
"canonical_url": "https://alpha.app.net/jrinn",
"cover_image": {
"url": "https://d2rfichhc2fb9n.cloudfront.net/image/5/kZ-JRmTbmd3WVPswTJ8Nwxzkf917InMiOiJzMyIsImIiOiJ0YXBwLWFzc2V0cyIsImsiOiJpL1UvaS9ZL1VpWW5xRFNvTUtyTEhLNXA0OHN2NkxmTmRVMC5qcGciLCJvIjoiIn0",
"width": 960,
"is_default": true,
"height": 260
}
}
}
Let’s say you want to get the avatar url. Normally, you would do it using the objectForKey: method, like this:
NSString *avatarURL = [[[dictionary objectForKey:@"user"]
objectForKey:@"avatar_image"]
objectForKey:@"url"];
Well, apparently, you can also use the NSKeyValueCoding Protocol for Dictionaries, which includes the method called valueForKeyPath:. So to get the avatar url with this method will now be a lot cleaner:
NSString *avatarURL = [dictionary valueForKeyPath:@"user.avatar_image.url"];
Nice, right?!! What are some of your favorite methods for parsing dictionaries?
I am currently working with the Foursquare API to build a simple app which will display local restaurants based on the number of checkins in has (so think Yelp sorted by Foursquare checkins vs user reviews). The Foursquare API uses authorization with a Client ID and a Client Secret token, which I wanted to store as a global variable.
Here is how to do it:
In your app target, select Build Setting option at the top. In the bottom right corner, there is a button with a big plus called “Add Build Setting”. Click on that button, and select “Add User-Defined Setting”.
You should now have a field where you can enter your global variable name (e.g. FOURSQUARE_CLIENT_ID) and the value (e.g. “12344455656ifsadjkfdsfjklfdjkfjk”). Pro-tip: use tabs to tab between the variable name and value!
You’d think you’re done, but you’re not!
In the Build Settings search box, enter the word “macro”, and the Preprocessor Macros section should come up:
Now, the the Debug and Release sections, define your variables (DEBUG=1) is already set for you as an example. This serves as a search and replace, so the macro will look for the word “DEBUG” in your codebase, and replace it with a 1 before compiling. For strings, you want the variable replaced with an @ sign and double quotes, so set all your string variables as follows: FOURSQUARE_CLIENT_ID=@\”$(FOURSQUARE_CLIENT_ID)\”.
Here is an example of what your settings should look like:
The blacked out parts are the actual values that are auto-completed for you. Again, make sure to copy over your variables to the Release section as well, and change them if they’re different, or your production app won’t work!
So now, every time I need to use the Foursquare Client Id or the Foursquare Client Secret token, I can just use FOURSQUARE_CLIENT_ID / FOURSQUARE_CLIENT_SECRET throughout my app, without having to remember what the actual client id or client secret is. And if my client id changes, I can easily just update the value in one place – my user defined build settings!
One of the hardest programming concepts for me to understand in the past few weeks as I’m learning iOS development was, you guessed it, Delegates! So I’d like to share how I’ve come to think of them… using a CoffeeShop analogy. So here is the story:
You wake up this morning, and you want coffee. But alas, you can’t make coffee yourself. So you get up, get dressed and go to a coffee shop. You know that every coffee shop conforms to a specific protocol. You walk in and say “makeMeMyCoffee”, and they make you your coffee. In other words, you’re delegating coffee making to a coffee shop:
// CoffeShopDelegate.h @protocol CoffeeShopDelegate <NSObject> - (void)makeMeMyCoffee @end
So when you wake up, you want to delegate out your coffee making:
// You.h
#import "CoffeeShopDelegate.h"
@interface You : NSObject
@property (strong, nonatomic) id<CoffeeShopDelegate> coffeeDelegate;
@end
// You.m
@implementation You
- (void)whenIWakeUp
{
// you don't want to make the coffee yourself,
// so you delegate it out to the delegate (the coffee shop)
[self.coffeeDelegate makeMeMyCoffee];
}
@end
Each coffee shop, whether it’s Starbucks or Dunkin’ Donuts or your neighborhood mom and pop coffee shop, conform to the CoffeeShop protocol. When you walk in and say “makeMeMyCoffee” they know to make you coffee. Let’s say this morning you’re feeling like Starbucks:
// Starbucks.h
#import "CoffeeShopDelegate.h"
#import "You.h" // you walk into the coffeeshop
@interface StarbucksViewController : UIViewController <CoffeeShopDelegate>
@end
//Starbucks.m
@implementation StarbucksViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Starbucks is now your coffee delegate, in charge of making you coffee
You.coffeeDelegate = self;
}
- (void)makeMeMyCoffee
{
addSlowRoastedCoffee;
addTwoSpoonsOfSugar;
addASplashOfSyrupyGoo;
addHalfCupOfMilk;
GiveCoffeeToCustomer;
}
@end
If you decided to go with Dunkin’ Donuts, they would have a different implementation of the makeMeMyCoffee method:
// DunkinDonuts.h
#import "CoffeeShopDelegate.h"
#import "You.h"
@interface DunkinDonutsViewController : UIViewController <CoffeeShopDelegate>
@end
// DunkinDonuts.m
@implementation DunkinDonutsViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Dunkin Donuts is now in charge of making your coffee
You.coffeeDelegate = self;
}
- (void)makeMeMyCoffee
{
// The Dunkin Donuts coffee follows a different recipe than Starbucks Coffee
addCoffee;
addFiveSpoonsOfSugar;
addASplashOfHersheySyrup;
addAThirdCupOfMilk;
GiveCoffeeToCustomer;
}
@end
So that is all. Delegates are basically a way to outsource your tasks, and allow different classes (Starbucks vs Dunkin Donuts) to implement the task in their own different way.
I’ve been thinking a lot about what separates great software from mediocre or bad software. And the clear differentiator I keep noticing time and time again is the attention to detail. The business logic that is responsible for the user experience.
For example, this morning I got an email with the following subject from 1-800-CONTACTS, where I order my contacts:
Your last order should run out on 05/10/2013, reorder today!
The idea behind this email is awesome. I’d love to get reminders that my contacts will be running out and an easy link to just reorder them. But they missed a small detail in this email. It is 05/11/2013 today! In other words, they send me this email AFTER the estimated date of when my contacts will run out! If I actually woke up this morning with no more contacts to be found and then saw this email, I’d be pretty upset. Now, I have to wait for a few days until my contacts are shipped to me!
In other words, there is a very fine line between great and mediocre software. Getting the details of the implementation right is more important that just executing a general half-baked good idea.
In contrast, when I hold my iPhone in my hand, I feel like I’m handling an amazing piece of art. That’s because Apple, that created the iPhone, took the time to think about every single detail that makes up this device – from what it looks like, to the materials it’s made of, to the software that’s on it. It makes me feel loved to think that someone took the time to actually think of all these little details, just for me!
So great software is not just about functionality, but it’s also about love. All the details that have been thought of communicate to the user that whoever built this masterpiece really really cared about it. They put their soul into it, and that’s something that all of us want to feel more of in our lives.
I was working on a simple challenge today at Mobile Makers. We were basically adding some text to a text field, clicking an “Add Button” and showing the text in a text view (see below):
However, one thing that kept annoying me over and over again was that I could not just click “Return” on my keyboard to get the field added without pressing the “Add” button. So I looked around for a simple way to do this:
First, make your ViewController a UITextFieldDelegate:
// ViewController.h #import <UIKit/UIKit.h> @interface ViewController : UIViewController <UITextFieldDelegate> @end
Now, in your ViewController.m file, delegate your text field in the viewDidLoad method:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.textField.delegate = self;
}
Finally, implement the code you want implemented when the user clicks the return button on the keyboard. Mine was as follows (I was basically adding the new string to an array of strings and then printing out the array as a string to the textView):
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
[self.listOfStrings addObject:self.textField.text];
[self displayArrayAsString];
self.textField.text = @"";
return YES;
}
Of course, now that I think about it, my problem was really caused by using the simulator. It’s much easier to press the return key on my computer keyboard than clicking the “Add” button on an iPhone or iPad. I’ll need to experiment more with whether this interaction is really necessary on these devices!
Today, I was making a pretty simple app to test out delegations (source code here). Basically, as you press on one of the four buttons (Magenta, Purple, Green, or Red), the background color of view behind the buttons turns the pressed color (see below):
My initial take at the method to change the background color looked like this:
- (void)changeColor:(NSString *)colorName
{
if ([colorName isEqualToString:@"Green"]) {
self.view.backgroundColor = [UIColor greenColor];
} else if ([colorName isEqualToString:@"Red"]){
self.view.backgroundColor = [UIColor redColor];
} else if ([colorName isEqualToString:@"Purple"]){
self.view.backgroundColor = [UIColor purpleColor];
} else if ([colorName isEqualToString:@"Magenta"]){
self.view.backgroundColor = [UIColor magentaColor];
}
}
Obviously the above is very repetitive – the only thing that changes between the methods in the color called on the UIColor object – but it is also not scalable. Imagine if you had 50 buttons with color names! Or if you wanted to change up the color names on the buttons – you’ll have to find the previous color if statement and change it!
Good thing Objective-C has a bit of metaprogramming built in. So you can use Reflections to call the method by it’s string name! In other words, you can concatenate the color name (e.g “green”) with the word “Color”, and then call the method name “greenColor”.
Here is what my new and nicely refactored code looks like:
- (void)changeColor:(NSString *)colorName
{
NSString *colorMethodName = [[colorName lowercaseString] stringByAppendingString:@"Color"];
self.view.backgroundColor = [UIColor performSelector:NSSelectorFromString(colorMethodName)];
}
Nice, right?!