Nick Harris

iOS Slide-out Navigation Code

with 4 comments

Ken Yarmosh has a great post entitled New iOS Design Pattern: Slide-out Navigation in which he does a great job describing this new design pattern and how different applications are using it.

I personally like the approach so I decided to figure out how I would implement it.  I have no idea if any of the apps mentioned in Ken’s post actually do it this way, but it works.

Slide-out Navigation

The idea here is use the Application Delegate as the controller between the current content view of the application and the menu view.  When a view in the application needs to show the navigation menu, it can call a method on the App Delegate that handles all the work of showing and restoring the menu and content views.

The “illusion” of the content view sliding off to reveal the menu view is done by first grabbing a screenshot of the current content view and passing that off to the MenuViewController.  The MenuViewController has a UIImageView that it populates with that screenshot.  Its this UIImageView that acts as the content view overlay. It makes animation smooth using a single flat image rather then trying to animate an actual view back and forth.

AppDelegate

The AppDelegate acts as the central controller whenever any views need to show the menu view. It gets two new properties:

@property (strong, nonatomic) ContentViewController *contentViewController;

@property (strong, nonatomic) MenuViewController *menuViewController;

 

The contentViewController gets used as a temp holding place while the menuViewController is visible.  Having the two of them in the app delegate makes switching the window.rootViewController easier.  I do this in two new methods on the AppDelegate:

-(void)showSideMenu

{

// before swaping the views, we'll take a "screenshot" of the current view

// by rendering its CALayer into the an ImageContext then saving that off to a UIImage

CGSize viewSize = self.contentViewController.view.bounds.size;

UIGraphicsBeginImageContextWithOptions(viewSize, NO, 1.0);

[self.contentViewController.view.layer renderInContext:UIGraphicsGetCurrentContext()];

 

// Read the UIImage object

UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

 

// pass this image off to the MenuViewController then swap it in as the rootViewController

self.menuViewController.screenShotImage = image;

self.window.rootViewController = self.menuViewController;

}

 

-(void)hideSideMenu

{

// all animation takes place elsewhere. When this gets called just swap the contentViewController in

self.window.rootViewController = self.contentViewController;

}

 

MenuViewController

The MenuViewController handles all the animation and touch gestures that make the slide-out navigation feel real.  It has two important properties:

@property (strong, nonatomic) IBOutlet UIImageView *screenShotImageView;

@property (strong, nonatomic) UIImage *screenShotImage;

 

Using the viewWillAppear:animated method we can reset the menu view, covering the entire view with the screenShotImageView whose image has been set using the screenshot taken by the app delegate of the currentContentViewController.  It’s then just animated to the right.

-(void)viewWillAppear:(BOOL)animated

{

[super viewWillAppear:animated];

 

// when the menu view appears, it will create the illusion that the other view has slide to the side

// what its actually doing is sliding the screenShotImage passed in off to the side

// to start this, we always want the image to be the entire screen, so set it there

[screenShotImageView setImage:self.screenShotImage];

[screenShotImageView setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];

 

// now we'll animate it across to the right over 0.2 seconds with an Ease In and Out curve

// this uses blocks to do the animation. Inside the block the frame of the UIImageView has its

// x value changed to where it will end up with the animation is complete.

// this animation doesn't require any action when completed so the block is left empty

[UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{

[screenShotImageView setFrame:CGRectMake(265, 0, self.view.frame.size.width, self.view.frame.size.height)];

}

completion:^(BOOL finished){  }];

}

 

The easy part of the MenuViewController is if the user selects a new view.  Simply set the currentViewController on the AppDelegate with the new view, then call the slideThenHide method which animates the screenshot back over the entire screen completing the illusion.

-(IBAction)showLogoExpandingViewController

{

// this sets the currentViewController on the app_delegate to the expanding view controller

// then slides the screenshot back over

[app_delegate setContentViewController:[[LogoExpandingViewController alloc] initWithNibName:@"LogoExpandingViewController" bundle:nil]];

[self slideThenHide];

}

 

-(void) slideThenHide

{

// this animates the screenshot back to the left before telling the app delegate to swap out the MenuViewController

// it tells the app delegate using the completion block of the animation

[UIView animateWithDuration:0.15 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{

[screenShotImageView setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];

}

completion:^(BOOL finished){ [app_delegate hideSideMenu]; }];

}

 

The more difficult part is detecting when the user interacts with the screenshot.  If they touch it, the end result should be the menu hiding and the content view becoming active again.  So it needs a UITapGestureRecognizer.

It also needs to be user interactive.  If they touch and drag the screenshot it should respond by moving. So it also needs a UIPanGestureRecognizer.

I wasn’t up to speed with dragging using Gestures so after a little searching I found this post by Soo How Ng which made it pretty simple:

- (void)viewDidLoad

{

[super viewDidLoad];

 

// create a UITapGestureRecognizer to detect when the screenshot recieves a single tap

tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTapScreenShot:)];

[screenShotImageView addGestureRecognizer:tapGesture];

 

// create a UIPanGestureRecognizer to detect when the screenshot is touched and dragged

panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureMoveAround:)];

[panGesture setMaximumNumberOfTouches:2];

[panGesture setDelegate:self];

[screenShotImageView addGestureRecognizer:panGesture];

}

 

- (void)viewDidUnload

{

[super viewDidUnload];

 

// remove the gesture recognizers

[self.screenShotImageView removeGestureRecognizer:self.tapGesture];

[self.screenShotImageView removeGestureRecognizer:self.panGesture];

}

 

- (void)singleTapScreenShot:(UITapGestureRecognizer *)gestureRecognizer

{

// on a single tap of the screenshot, assume the user is done viewing the menu

// and call the slideThenHide function

[self slideThenHide];

}

 

/* The following is from http://blog.shoguniphicus.com/2011/06/15/working-with-uigesturerecognizers-uipangesturerecognizer-uipinchgesturerecognizer/ */

-(void)panGestureMoveAround:(UIPanGestureRecognizer *)gesture;

{

UIView *piece = [gesture view];

[self adjustAnchorPointForGestureRecognizer:gesture];

 

if ([gesture state] == UIGestureRecognizerStateBegan || [gesture state] == UIGestureRecognizerStateChanged) {

 

CGPoint translation = [gesture translationInView:[piece superview]];

 

// I edited this line so that the image view cannont move vertically

[piece setCenter:CGPointMake([piece center].x + translation.x, [piece center].y)];

[gesture setTranslation:CGPointZero inView:[piece superview]];

}

else if ([gesture state] == UIGestureRecognizerStateEnded)

[self slideThenHide];

}

 

- (void)adjustAnchorPointForGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {

if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {

UIView *piece = gestureRecognizer.view;

CGPoint locationInView = [gestureRecognizer locationInView:piece];

CGPoint locationInSuperview = [gestureRecognizer locationInView:piece.superview];

 

piece.layer.anchorPoint = CGPointMake(locationInView.x / piece.bounds.size.width, locationInView.y / piece.bounds.size.height);

piece.center = locationInSuperview;

}

}

 

Sample

 

Source Code

https://bitbucket.org/nh129096/slideoutnavigationsample/src

 

Written by Nick Harris

February 5, 2012 at 3:53 am

Posted in Uncategorized

Our Language

leave a comment »

Brent recently chimed in on the age old argument of which language is the best.

I’m always disheartened by these arguments.

In the end we flick bits off and on.

Those bits flicker into the most beautiful things the mind has only begun to imagine.

Lets make beautiful things.

Written by Nick Harris

November 8, 2011 at 7:11 am

Posted in Uncategorized

iPhone 4 Pictures

with one comment

Reasons why I love this picture:

1. It was there
2. I get to share what I saw
3. S
4. Symmetry
5. Red / Blue
6. Engineering (built for semi-trucks at high speeds)
7. My shadow
8. Snow capped peaks

Written by Nick Harris

July 9, 2011 at 5:16 am

Posted in Uncategorized

Sports Thoughts – 6/18/2011

leave a comment »

Going on vacation with my family this weekend.  Need to get brushed up on all my sports related thoughts so I don’t get out maned by my sister…

LeBron James

I was happy Dirk Nowitzki, Jason Kidd and Mark Cuban won a championship.  I thought LeBron’s post game interviews and tweets were funny – but I agree that he needs a strong mentor – a Chris Carter type – to help him get to his next level.  He’s not there yet.  He will be.

Mark Cuban

Blog Maverick was one of the first blogs I subscribed to when I started at NewsGator 6 years ago.  Mark is a polarizing figure.  I fall on the “love him” side.  His handling of the playoff interviews this year was incredible, but him deferring the acceptance of the trophy to the original owner of the Mavs was one of the classiest things I’ve ever seen in sports.  The biggest FUCK YOU to all his haters.  Well done sir.

Vancouver

The city is overreacting.  We get it that it was just a few idiots.  We’re Americans.  We deal with those kind of people everyday.  I think it got so much coverage here cause we assumed you didn’t have those idiots too.

Boston

One of the most important cities in America’s history is also currently the best sports city in the world.  7 championships in all major American sports leagues in the past decade.  Kudos Boston!

Packers Ring Ceremony

The maturity of my favorite players on the team continues.  Arron Rodgers, Clay Matthews, Greg Jennings – all class acts.  It was fun to watch them post all their pictures on Twitter last night as they got their rings.

Tim Tebow

I won’t buy his jersey but I’m proud to have him in Denver.  I’m no Broncos fan, but players who reach out and make a positive impact on the community get my admiration.  Tebow’s partnership with Denver Mattress for his foundation to help orphans is just the latest example.  John Lynch was one of the greatest persons I think Denver had the pleasure of have play for a team.  Class act all the way.  Tebow I think has that same draw.. even if he’s never a starter here.

Roy McIlroy

I’m cheering for him.  His post-Masters-collapse interview was one of the most refreshing takes on ones personal faults I’ve heard from an athlete in a long time.

Ohio State Football

Looks more and more like Pryor is going to be portrayed as the poison pill.  Is it fair?  No.  It’s obvious for anyone who follows college football.  But the effort to help Tressels image by doing so doesn’t bother me much.  I think Tressel is one of the finest coaches in college football.  I wasn’t happy to see him go, though I was one who thought he should be fired to save the reputation of OSU.  His reputation was fairly safe no matter what.

Written by Nick Harris

June 18, 2011 at 7:45 am

Posted in Uncategorized

Let them have feedback!

leave a comment »

I’ve written and tweeted before about webservice API’s that make it difficult to figure out what you’re doing wrong… but at least those gave me some kind of feedback that something was wrong.  The worst are services that give you no feedback what-so-ever.  If anything my name is attached to ever does this to you, let me know immediately!

My Latest Experience

We use SendGrid to send emails from our platform.  Could we have done it ourselves?  Of course.  But reliably delivering emails is a lot more complicated then you would think, so we decided to go with a trusted company.  We’ve been using them for months and honestly they’ve been terrific!

This week I was tasked with estimating a solution for our users to be able to reply to emails we send.  My first thought was our own SMTP server since we have the code in the NewsGator platform.  But moving an SMTP service in your own datacenter to Azure turned out to be more difficult than I wanted.

My former CTO (who actually wrote the SMTP code I was working with) pointed out to me that SendGrid had its own solution for email replies.  I was pleasantly surprised with how easy it looked – and its SendGrid… they’ve been great!  They even had step-by-step instructions on how to get everything configured.  This included writing and deploying our own REST endpoint as well as DNS/MX configuration stuff.  Not a trivial task but it looked so easy I went for it.

About 6 hours later I had talked to the right people in our company to get the DNS/MX setup correctly, rolled my own endpoint, unit tested it and deployed it to our production environment. Now for the big test… send an email to our MX domain and watch it get parsed by SendGrid and passed to my endpoint.  I sent the first email.  Then the second.  Then the third, fourth, fifth and sixth.  Nothing.

OK – what’s going wrong?  My emails aren’t bouncing (I tested. GMail bounces invalid DNS lookups almost instantly).  My unit tests work so my endpoint is correct.  So I logged into the SendGrid dashboard to see if it would tell me what’s wrong.  Nothing.

Ugh!!!

Finally it hit me – what account type do we have and what’s supported with it?  I didn’t setup our account so I honestly didn’t know the differences.

On the dashboard our account is listed as a “Recurring Account”.  Cool.  So I went to the pricing matrix - no “Recurring Account” type there.

Well shit.  Back to our account page.  Clicked the “Upgrade Account” button.  Ah!  ”Current Package: BasicPlan: $9.95″.

Back to the pricing matrix.  No “Basic Plan”.

Oh for fucks’ sake.

Well the “Bronze” plan is $9.95… and there it is… the red X in the column for Parse API.

I had to sign into our SendGrid account to configure it correctly to use the Parse API.  Presumably it knew at that point what account type we had.  It should have never let me go ahead with configuring this feature.  At the very least it should have told me when I was done that it wouldn’t be enabled until we upgraded our account.  Instead I got nothing but an hour of detective work to figure out it wasn’t a problem with my code.

**Have we upgraded?  No.  $9.95 vs. $79.95 isn’t a small upgrade.

The Point

My point here is that if you offer a tiered service plan, make it EXPLICITLY CLEAR EVERYWHERE what options are available and which aren’t.  Hide them or disable them (which is better is a whole different UX discussion… but choose one!).  Not giving feedback anywhere along the way is a horrible experience!

My guess is that I’m the first to stumble upon this with SendGrid and based on our experience with them they will rectify this as soon as they can.  They have been a great service to us.  I hope they’ll do the right thing and I’ll still recommend SendGrid for anyone who needs to send email via Azure or for any other situation where you need your email delivered reliably.  But hopefully if you’re creating one of these services you’ll remember my story :-)

** I don’t have the authorization to upgrade our account.  I’ll revisit if we do.

Written by Nick Harris

June 16, 2011 at 4:19 am

Posted in Uncategorized

CoreData Migration Testing

leave a comment »

Testing CoreData Migration is usually my last step before submitting to the AppStore.  Typically I label my last successful released version in Mercurial, then when I’m ready to submit again I pull that source code into a different directory and run it in the debugger.  This creates my old sqlite database.  Then I can run the new code and make sure the migration is successful.

I ran into an issue yesterday though with my latest release where I didn’t have the right version labeled in Mercurial… so I wasn’t sure how to get a copy of the old sqlite database.  After an admittedly lazy tweet, @ideal1st put me on the right track – “Download a datastore from your device, pop in simulator and run mapping model on it“.  But I wasn’t quite sure how to get at the Application directory.  A quick Google search later I found iPhone Backup Extractor.  From there it was easy…

1. Download the existing app from the app store and run it
2. Take a backup in iTunes
3. Use iPhone Backup Extractor to grab the Documents and Library directories of my app which has my sqlite database file
4. Run the new code in the iPhone Simulator
5. Navigate to the Documents directory of the app in the simulator and replace its contents from what was in the backup
6. Run the new code again and verify the migration

Glad I did too.  I had an issue where an attribute was getting an invalid type, but a quick look with SQLite Database Browser helped clear that up.

Hopefully that helps someone else.  I’d love to hear better suggestions as well!

Written by Nick Harris

June 14, 2011 at 6:56 pm

Posted in Uncategorized

Some dude named Brent…

leave a comment »

Written by Nick Harris

June 4, 2011 at 5:54 am

Posted in Uncategorized

Exceptions are Exceptional

with 4 comments

When I started writing Objective-C I had been in the C# world for years.  The mantra that Exceptions are Exceptional was new to me.  But after 2 years of Objective-C, the idea of writing @catch anywhere freaked me out.

C# developers get this wrong… a lot….

My favorite example that I ran into once while debugging another developers code…

try
{
// ...
}
catch(System.Exception)
{
Console.WriteLine("huh...");
}

This was in a commercially released product.  Pardon the pun but that’s unexceptable.

But everything has its place.  And since going back to helping architect and develop a C# webservice platform, we’ve molded a pattern which uses custom exceptions to handle bad things that we know might happen but allowing other exceptions to bubble all the way up to the “this shit is broken – fix it” level.

The platform uses the basic three tier approach – data level/logic level/webservice.  The idea is that if any client developer using the webservice sees a 500 error from the webservice tier, then something we never expected happened in the logic or data tier and it’s a serious bug. Top priority fix.  Pretty basic idea right?

In our code design we concentrated objects that will be used across multiple applications – be they service applications, web applications or even unit test projects – into their own library.  We then added our our subclass of System.Exception.  Now anywhere in the solution we know if the exception was something we created and should handle accordingly, or if its a bigger problem.

On top of that we added our own error code to our exceptions and documented what each of them means. They’re segmented into ranges just like Http Status codes and returned to anyone using the webservice in the headers of the response along with a detailed error message.  You can run Firebug and see exactly why your request failed.

This approach also gave us the ability to correctly set the http status code to something a client application can understand.  I’ve run into so many webservices that return HTTP STATUS 200 OK with an html body that says “400″.  If you’re doing this, you suck.

With our approach we have a central place on each endpoint that returns the appropriate http status code with an error code the client developers program can recognize and provide the appropriate user feedback, along with a useful trace message the savvy developer can use to figure out what’s wrong.

It’s an “everybody wins” goal platform architects should strive for.  It also saves on the technical questions as well.

We already have numerous developers building on our platform with minimal technical questions to our platform developers since its all relatively self explanatory.  And as someone who would rather write code than answer questions… that’s pretty awesome.

Written by Nick Harris

May 10, 2011 at 5:26 pm

Posted in Uncategorized

Jazz vs Rock…

leave a comment »

Jazz vs rock…

Jazz flows

Like life

Chaos

Sounds come in the same tones
But never the same beat
or rhythm

Rock is structured
ridged
four four

Life thrives on structure
Life diversifies in chaos

Written by Nick Harris

May 2, 2011 at 5:52 am

Posted in Uncategorized

… that glint in your eye…

leave a comment »

Does a sparkle in ones eye mean love?

I’m a hopeless romantic…

These are my girls Anna and Shoshanna – sparkling eyes and all…
IMG_3217


IMG_3238

Written by Nick Harris

April 26, 2011 at 8:29 am

Posted in Uncategorized

Follow

Get every new post delivered to your Inbox.