Showing posts with label programming. Show all posts
Showing posts with label programming. Show all posts

Wednesday, October 10, 2012

Taking advantage of the iPhone 5's larger screen

Note: This blog is deprecated. @synthesize zach has moved to a new home, at zpasternack.org. This blog entry can be found on the new blog here. Please update your links.


Because it comes up on StackOverflow, I dunno, maybe five times per day, I thought I'd make a quick post describing how to make your apps work well with the new iPhone 5 screen dimensions (while still remaining compatible with older iPhones).


It's really not that hard at all. TaskLog had iPhone 5 support when it launched. It took me about 5 minutes to make this work, and probably a few additional hours to make it work well.


The first step is to include an iPhone 5 specific default launch image. This is what iOS uses to determine whether or not your app is ready for the larger screen size.

iPhone 5 launch image in Xcode


It should be 640 x 1136 pixels. If you drag it into the Launch Images in Xcode, it will be automatically named for you. If you just add it to the project yourself, make sure you name it correctly: "Default-568h@2x.png".


Once you do this, your app will be sized to fit the iPhone 5 screen. From there, the amount of work you need to do depends entirely on how your application is built. If you've set up all your views with proper resizing characteristics (either via Auto Layout or the old-school autoresizing masks), you might be done. Just make sure to test it out in the Simulator (choose Device->Hardware->iPhone 5 (Retina) ) and make sure all your views look nice. I can't stress this enough: test every one of your views on both 3.5" and 4" devices (or the simulator, at the very least).


Places where you might have gotchas: if you're doing any kind of hardcoding of coordinates, there might be some tweaking you need to do. If you're going to move or size things in code, based on coordinates, at least try to do so via relative, rather than absolute, coordinates.

Related StackOverflow questions:

Saturday, July 21, 2012

High resolution timing in Cocoa

Note: This blog is deprecated. @synthesize zach has moved to a new home, at zpasternack.org. This blog entry can be found on the new blog here. Please update your links.

For profiling app performance, it's necessary to accurately time your code. The best way to do this is to use mach_absolute_time. In conjunction with mach_timebase_info, you can get extremely high resolution timing to ease performance benchmarking.

I've wrapped this functionality up in a little class to make it super easy to use.

// MachTimer.h
#include <mach/mach_time.h>

@interface MachTimer : NSObject
{
    uint64_t timeZero;
}

+ (id) timer;

- (void) start;
- (uint64_t) elapsed;
- (float) elapsedSeconds;
@end
// MachTimer.m
#import "MachTimer.h"

static mach_timebase_info_data_t timeBase;

@implementation MachTimer

+ (void) initialize
{
    (void) mach_timebase_info( &timeBase );
}

+ (id) timer
{
#if( __has_feature( objc_arc ) )
    return [[[self class] alloc] init];
#else
    return [[[[self class] alloc] init] autorelease];
#endif
}

- (id) init
{
    if( (self = [super init]) ) {
        timeZero = mach_absolute_time();
    }
    return self;
}

- (void) start
{
    timeZero = mach_absolute_time();
}

- (uint64_t) elapsed
{
    return mach_absolute_time() - timeZero;
}

- (float) elapsedSeconds
{
    return ((float)(mach_absolute_time() - timeZero)) 
        * ((float)timeBase.numer) / ((float)timeBase.denom) / 1000000000.0f;
}

@end
You'd use it like this:
MachTimer* aTimer = [MachTimer timer];
[self performSomeOperation];
NSLog( @"performSomeOperation took %f seconds", [aTimer elapsedSeconds] );
I've used this code for iOS and Mac OS apps, and it works great.

Thursday, July 19, 2012

Retina Graphics in Mac OS X

Note: This blog is deprecated. @synthesize zach has moved to a new home, at zpasternack.org. This blog entry can be found on the new blog here. Please update your links.

I recently added Retina graphics to the Mac version of PuzzleTiles. I had Retina-ized TaskLog prior, and found that to be pretty trivial, as it's not a very graphics-heavy app. PuzzleTiles has many hundreds of images, so it was a bit more of a challenge.

The whole process is pretty straightforward, especially if you've done Retina graphics for an iOS app. The TL;DR is:

  • Make double-size images and suffix them with @2x
  • Use NSImage imageNamed: to load them
  • Use Quartz Debug to test your new graphics (if you don't have a Retina MacBook Pro)
The code

The main thing is to use

NSImage imageNamed:
It does all the @2x image-loading magic. One thing not immediately obvious to me is that you don't specify the extension when doing so.

NSImage* myImage = [NSImage imageNamed:@"foo.png"];
// NO, this makes the magic not work.

NSImage* myImage = [NSImage imageNamed:@"foo"];
// YES, magic is a-comin'.
The art


Producing 2x artwork was, by far, the biggest task. Luckily we'd had the foresight to make all our source art with vector graphics (text, shapes, and effects layers) which are basically arbitrarily resizable.

The first step was batch upsizing everything to 2x. I ended up making a separate set of source art for our @2x images, so that we could tweak them by hand. Some of the artwork we upsized with no other changes, but for some of them we wanted to tweak some things: line or shadow thickness, for example. Some others (the Wood tile set, for example) had bitmaps which we had to completely redo.

My advice is this: if your source art isn't vector-based, it might be time to bite the bullet and do that.

If you're like me and don't have a graphic artist on staff to do all this stuff for you, I have a few pieces of advice to ease the Retina-izing process:

  • Get familiar with PhotoShop's automation tools (File->Automate->Batch and File->Scripts->Image Processor). This saved me tons of time when converting, resizing, and exporting hundreds of images at once.
  • Get a tool for batch file renaming. At one point I decided to change my file naming convention (to make things more amenable to using imageNamed:), and hand renaming nearly a thousand files would have been a time-consuming, error-prone task. I ended up using Core-Renamer for this, and it worked perfectly.
Testing

If you don't have a Retina MacBook Pro, you can use any old Mac to test out your Retina graphics. Download and install Graphics Tools for Xcode (from within Xcode, choose Xcode->Open Developer Tool->More Developer Tools). Run Quartz Debug, choose Window->UI Resolution, and check "Enable HiDPI display modes". Now, when you go to Displays in System Preferences, you'll see a bunch of HiDPI modes. Pick one, run your app, and see how it looks.

References

The WWDC 2012 session "Introduction to High Resolution on OS X" is fantastic; I highly recommend checking that out.

Thursday, June 21, 2012

Autolayout: Don't Fight It

Note: This blog is deprecated. @synthesize zach has moved to a new home, at zpasternack.org. This blog entry can be found on the new blog here. Please update your links.

TaskLog is the first app I've developed which uses the new Cocoa Autolayout introduced in Mac OS 10.7 Lion.

I first used it in TaskLog's log view, shown below: Task Log screenshot

As you can see, there are quite a few UI elements in there. But all I did was put them where I wanted them, and Autolayout figured it all out. I didn't have to configure a single thing, it all Just Worked™.

Now at some point I decided to make TaskLog's main view resizable (so that the entire window resizes to perfectly fit the text of the current task). And that's where Autolayout became a real headache. In this view, there are actually a lot of overlapping UI elements which are shown or hidden depending on what the state of the UI. For example, when you hit "Start a new task", a bunch of the UI is hidden, and a text entry field and Start/Cancel buttons are displayed. TaskLog main view screenshot 2

Then when you hit Start, those are hidden and a bunch of other views are displayed. TaskLog Main window screenshot

I wanted some of the views to be anchored to the bottom of the window, some to be anchored to the top, and a few to be anchored to both top and bottom and resize vertically. More importantly, Autolayout was having none of this. I spent an entire day adding my own constraints to try to get the behavior I was after, and in the end I wasn't able to get it to work that way I wanted.

Then I had an epiphany: give up. It turns out that springs and struts can totally do what I needed to do in the main view UI; there was no compelling reason to use Autolayout. I turned it off, set up some springs and struts, and went on my merry way.

Now, don't get me wrong, Autolayout is amazing. It allows you to do incredibly complicated layout with little to no effort. However, my advice is this: if you find yourself fighting Autolayout, stop and think about what you're trying to do. If what you're trying to accomplish can be done with springs/struts, and/or Autolayout isn't making your life better, simply turn it off for that view.

The Autolayout session from last year's WWDC is an awesome intro to Autolayout. I'd definitely recommend giving that a view if you haven't already.

Wednesday, November 16, 2011

Don't invalidate your NSTimer in dealloc

Note: This blog is deprecated. @synthesize zach has moved to a new home, at zpasternack.org. This blog entry can be found on the new blog here. Please update your links.

Seasoned Cocoa developers may snicker at the title of this post, because it's probably obvious to them. It should have been obvious to me, but it wasn't. Maybe I can save someone some head-scratching by relaying my tale.

NSTimers retain their target. Now, I've been around the Cocoa block a few times, so I knew this to be the case (the docs state it explicitly), but I clearly hadn't thought through the ramifications. Walk with me for a minute.

Let's say you have a window, MainWindow, and a controller class for it, MainWindowController. You put a timer on it to periodically do some stuff. It might look like this:


@interface MainWindowController : NSWindowController
{
NSTimer* myTimer;
}
- (void) doSomeStuff;
@end

@implementation MainWindowController

- (id) initWithWindow:(NSWindow*)window
{
self = [super initWithWindow:window];
if( self ) {
const NSTimeInterval timerInterval = 10.0f;
myTimer = [NSTimer scheduledTimerWithTimeInterval:timerInterval
target:self
selector:@selector(doSomeStuff)
userInfo:nil
repeats:YES];
}
return self;
}

- (void) dealloc
{
[myTimer invalidate], myTimer = nil;
[super dealloc];
}

- (void) doSomeStuff
{
NSLog( @"doing stuff" );
}

@end


Seems reasonable, no? It did to me. The problem is that, as the docs state, NSTimers retain their target. When you create that timer in initWithWindow:, it retains the window controller, which means dealloc will never be called. dealloc isn't called until the controller's retain count is zero, and until the timer invalidates (which will be never, on a repeating timer), the controller's retain count will never be zero.

The solution would be to invalidate the timer elsewhere, perhaps in windowWillClose: (of course, your controller must also be the window's delegate for that to happen).


- (void) windowWillClose:(NSNotification*)notification
{
[myTimer invalidate], myTimer = nil;
}


Another thing I'd like to mention is the use of retainCount for debugging purposes. I've found that brand new Cocoa programmers tend to rely way too much on retainCount to try to figure out their memory management issues. Cocoa pros, on the other hand, will tell you to never ever call retainCount. You can't get any useful information from it, some say, because you can't know who's retaining your objects.

I think the truth lies somewhere in between. The truth is, you should know who's retaining your objects, and why. Though retainCount shouldn't be the first thing you look to, it can be useful on occasion if it seems like things aren't working as you expect.

In my case, I observed that calling initWithWindowNibName: on my window controller was returning an object with a retainCount of 2, when I was expecting 1. From there it was a pretty short walk to get to "ok, my timer is created here, but it's invalidated in dealloc… oh, wait."

Thursday, September 29, 2011

Regarding first responders: make, don't become

Note: This blog is deprecated. @synthesize zach has moved to a new home, at zpasternack.org. This blog entry can be found on the new blog here. Please update your links.

Things That Were Not Immediately Obvious To Me, #27:

If you have a view which you wish to become first responder, do NOT call becomeFirstResponder on it; it doesn't actually make the view first responder. Instead, call NSWindow's makeFirstResponder:

The NSResponder doc says (emphasis mine):

Use the NSWindow makeFirstResponder: method, not this method, to make an object the first responder. Never invoke this method directly.


This was not immediately obvious to me. Moral of the story: always read the damn docs.

Recapping:

[self becomeFirstResponder]; // Nope, never do that.

[[self window] makeFirstResponder:self]; // That'll do, pig.


As a side note, the description of becomeFirstResponder says:

Notifies the receiver that it’s about to become first responder in its NSWindow.

and

The default implementation returns YES, accepting first responder status. Subclasses can override this method to update state or perform some action such as highlighting the selection, or to return NO, refusing first responder status.


OK, so can someone tell me why this method wasn't named shouldBecomeFirstResponder? Had that been the case, I wouldn't have had to resort to the docs to figure out why it wasn't doing what I thought it should do. Just sayin'.


Saturday, July 23, 2011

ZPAlertView, Redirector on github

Note: This blog is deprecated. @synthesize zach has moved to a new home, at zpasternack.org. This blog entry can be found on the new blog here. Please update your links.

I finally took a minute to put ZPAlertView and Redirector up on github.

The related articles are UIAlertView with Blocks, Revisited and Adventures in Redirection, Part Deux

If you find either one useful, drop me a line and let me know.

Wednesday, June 15, 2011

The rest of us need to be smarter

Note: This blog is deprecated. @synthesize zach has moved to a new home, at zpasternack.org. This blog entry can be found on the new blog here. Please update your links.

This made me simultaneously amused and angered. There’s probably nothing more to be said that hasn’t already been so in comments there and on Reddit. Still, I can’t help but put my $0.02 in.

I don’t know anything about construction, except that it’s hard. To build a house, you need to pour concrete, nail boards together, put up drywall, and a bunch of other things. That’s all hard. If only I had a robot that could do all those things for me... I still couldn’t build a house. Why? Because I also don’t know anything about architecture or structural engineering, or plumbing, or electrical engineering. I’d be lucky to build something that didn’t fall down, and it almost certainly wouldn’t be fit for habitation.

When people say “programming”, they think of the mechanical act of typing stuff into a computer. But that’s really only a tiny fraction of what a programmer does. If you don’t have a reasonable understanding of the innerworkings of computers; if you don’t have the capacity to break a large problem down into ever smaller problems; if you don’t have the ability to visualize every minute detail of a solution; no programming language will enable you to write decent software. That’s all there is to it.

Do you guys remember HyperCard? AppleScript? Prograph CPX? Dare I say it, Visual BASIC? All attempts to make a programming language for non-programmers. And every time one of these hot new “languages for the rest of us” come out, I polish up my résumé. Because once everyone realizes that’s programming is still hard, I get a bunch of new job offers. Best case, all it means is we’re momentarily awash in software written by people who have no business doing so.

That’s not me being elitist. I want to live in a world where everyone has the capacity to create great software, I truly do. I just don’t see it happening in my lifetime.

> those who know it have little interest in simplifying it as it devalues their own knowledge.

Bitch, please. The guy that’s able to engineer a programming language which enables non-programmers to create great software will instantly become ludicrously rich, not to mention ushering in a golden era of information technology. If you believe for one second that some of the smartest people on the planet aren’t working on this right now -- haven’t been working on this for decades -- you are sorely mistaken.

Do you have any musician friends? Go up to one and tell him you tried to play guitar today, and it was hard. Why don’t they make instruments that are easier to play? Let me know how that works out for you.

Any four-year-old of even average intellect has sufficient command of their native language to successfully convey any idea of which they conceive. That no four-year-old has ever won the Pulitzer Prize is proof of the failure of the English language. Clearly what is needed is an easier to use spoken language.