The MirrorWorld app is live!

So this is what I've been working on for the past 10 months or so: an interactive storytelling app within the MirrorWorld series by author Cornelia Funke. It's an amazing fantasy world where we were able to explore a lot of different ways to tell a story.

The project was an absolute blast to work on. I had to pick up OpenGL on the job, which was a challenge but has been the most gratifying type of development I've done so far.

There's a free version of the app, featuring a seamless scrolling parallax comic book:
One for the Other
And the full, paid version, featuring 16 different stories:
MirrorWorld

>> comments…

Getting to your Home Directory in Unix

by David Sweetman in misc tags: unix

The command-line shortcut for a user's home directory is the tilde (~). The main problem with this is it's so annoying to type. If you try to type it with only your left hand, you'll contort your hand into an imprecise claw trying to make it simultaneously hit shift & ~.

I just made a STARTLING DISCOVERY. It's pretty easy if you do it this way:
Right pinky -> right shift;
Left pinky -> tilde.

This removes the claw aspect and makes it a pretty reasonable maneuver.

For an interesting explanation of why '~' was chosen in the first place, see here.

>> comments…

Bitcoin Can't Gain Mainstream Ground Without a More Reliable Infrastructure

I've been fascinated with the idea of bitcoin since I first heard about it and downloaded the blockchain, precisely the same time as the 0.7/0.8 blockchain fork.

At the time, I nearly bought, but I was extremely wary of linking a bank account to any of the available funding options.

Enter BitInstant -- it's great because it allows you to pay with cash. Cash seems like a good potential funding option for bitcoin, because unlike credit cards it provides a non-revocable payment source, but unlike bank account funding protects your downside against unscrupulous or poor quality services.

This past week, after 144 of my dollars disappeared somewhere between BitInstant and MTGox, "poor quality" is exactly how I would describe both services.

It has been four days since the transaction. Promptly after I made the moneygram cash deposit, BitInstant reported it had successfully deposited the money into my MTGox account.

Sweet, I thought, as I hopped over to MTGox. Zero dollars. I sent an email to BitInstant - the first of three emails and one phone call to go unanswered.

A day later I sent a support ticket to MTGox, the only available support channel -- also the first of three that I'm still waiting for any kind of answer on.

This isn't a sustainable infrastructure. Perhaps I would have had better luck if I had linked a bank account. But with this kind of support, why the hell would I trust them with that kind of information?

I only have one theory for what went wrong, if it's something that happened on my end. MTGox lists your account number in two places: on your account settings page and at the top of the window when you're logged in. But here's the thing: it's slightly different in both places. On your settings page it's called the "Account No./Merchant ID",starts with an uppercase letter and ends with an uppercase letter. But at the top of the screen it's just called the "Account Number", and it's missing the two letters and two prefix numbers. this is not only confusing, it's really dumb, even if this wasn't the point of failure. They're both called the same thing, but they're different.

I put in the uppercase-letter-prefixed one because I didn't even see the other one. With "Account No." written so boldly next to it, how could it be wrong? But when I later tested initiating the BitInstant trasnsaction from MTGox's side, it says to use the Account Number from the top of the screen, which it now calls "Exchange Account." So perhaps my mistake was initiating the transaction at BitInstant and not at MTGox. But when BitInstant asks for an Account Number, and MTGox supplies two different values labeled Account Number, and then when BitInstant reports a successful transaction, no errors, no hiccups, but has clearly failed to put the money in the correct account or possibly failed to put it in any account at all, it becomes apparent that this whole infrastructure is less than acceptable.

UPDATE 4/3: A day after I made this post and notified MTGox of it, the Account Number is now the same in both places. Both appear as the longer uppercase-prefixed version. However, when initiating a BitInstant transfer from MTGox's side, it still says to use the disappeared secondary version of the account number.

Anyway, I can't say for sure if that was the problem because nobody will contact me.

I'm still fascinated by the idea of bitcoin, but it's got some serious growing up to do if even the site that "handles over 80 percent of all Bitcoin trade" is so flaky and rough-edged. For a financial infrastructure, even an experimental one, it needs to be better. It's not good enough.

>> comments…

Downloading Your iTunesConnect Data

I've recently become interested in downloading my iTunesConnect data. I currently keep a spreadsheet of my sales history, but I began wondering if I could automate this at all with some kind of iTunes Connect API.

I haven't got the automation down yet -- there's a reason that products like AppViz provide a high amount of value: it takes time to set up a system to process the raw data.

But anyway, the raw data is fun to have. (unfortunatley, this won't include user reviews -- as far as I'm aware, the only way to get those is to do some scraping.)

Downloading the data

Downloading is fairly straightforward:

  1. Download Autoingest.class from apple:
    apple.com/itunesnews/docs/Autoingestion.class.zip

  2. Place Autoingest.class in the directory where you want to store the data.

  3. Run java Autoingest <parameters> to download the data.

The Parameters

Several parameters are required -- your username, password, vendorid, and what kind of data you want, etc.

But since that seems like a bunch of stuff to remember, I decided to make a little script that stores everything I need except my password.

Here it is, with all the available options listed at the bottom of the script:

So I have a little ways to go before I can see the data at a glance in a nice and meaningful way, but it's a good start.

>> comments…

Batch rename files in bash

Lately I've been renaming a bunch of files. A bunch of files. The iPad app we're developing has over 8,000 asset files. It's an interactive storytelling app, so it's heavy on the visuals, and 7,000 of those files are .png image tiles belonging to an extensive Parallax-style comic book.

Anyway, bash is great for all kinds of renaming tasks. Often I'll get a batch of assets from an artist, and they'll be a slightly different naming convention than I need. Or while I'm preparing all the images I'll want to strip off the @2x's for some reason, or add on the @2x's for some other reason.

I found myself wanting to do these types of renaming tasks so often that I put it into a script:

It's pretty simple, but has saved me a good amount of typing and thinking.

Examples:

match a string to RENAME:

Input:
File1.png, File2.png, File3.png

Script with arguments:
$ renameFilenameString.sh .png @2x.png

Result:
File1@2x.png, File2@2x.png, File3@2x.png

match a string to REMOVE:

Input:
File1@2x.png, File2@2x.png, File3@2x.png

Script with arguments:
$ renameFilenameString.sh @2x

Result:
File1.png, File2.png, File3.png

>> comments…

Merging .pbxproj Easily with Vim

When using git for source control while working on a project in Xcode, as long as you track your .pbxproj in git, it's inevitable that at some point you'll get a merge conflict.

My general procedure used to be to resolve all merge conflicts in Xcode. With regular source files this is possible, even though it turns out to be way faster with vim, but when a conflict crops up in the .pbxproj, it's impossible.

NOTE: the .pbxproj is not really intended to be edited by humans. It's possible to mangle the file to a point where it won't work any more, and then it will be difficult to figure out how to fix it. However, in my experience I've always been able to resolve all conflicts and end up with a working project. But keep in mind, this is the reason Google's policy is to keep the .pbxproj out of version control. Anyway, proceed with caution! Make sure you can easily get back to a working state prior to the merge if things go awry.

As a relative newcomer to git and Xcode, I'm going to make a suggestion to others who are as green as I am:
Never resolve merge conflicts in Xcode.

This will make your merge process much more pleasant in general, and will save a good amount of headache.

If you come across a conflict while you're using git from the terminal, you'll see something like this:

user$ git pull origin develop
Auto-merging MyProject/MyProject.xcodeproj/project.pbxproj
CONFLICT (content): Merge conflict in MyProject/MyProject.xcodeproj/project.pbxproj
Automatic merge failed; fix conflicts and then commit the result.

At this point, you need to fix the conflicts.

Good news: you're already in terminal, so vim is super handy. Other good news: git just told you the file that conflicts.

Since you know where the file lives, hardly any thought goes into opening the file with vim:

vim MyProject/MyProject.xcodeproj/project.pbxproj -- and of course you have tab-completion, too.

(If you weren't using vim, the process would be somewhat more convoluted: you'd navigate to MyProject/ in Finder, right-click on MyProject.xcodeproj, select "Show Package Contents," right-click on project.pbxproj, select "Open With", and if your favorite text editor is not shown, select "Other," find the app, and finally open the file.)

So with the file open, your goal is to find & resolve the conflicts.

A conflict looks like this:

732AFB1B167ABC3F00D28EF7 /* Filename.jpg ...  (A bunch of stuff that doesn't conflict)
<<<<<<< HEAD:project.pbxproj
732AFB1B167ABC3F00D28EF7 /* NextFilename.jpg ...  (New stuff in YOUR branch)
=======
732AFB1B167ABC3F00D28EF7 /* OtherFilename.jpg ...  (New stuff in THEIR branch)
>>>>>>> Message on merged commit:project.pbxproj
732AFB1B167ABC3F00D28EF7 /* MoreFilenames.jpg ...  (A bunch of stuff that doesn't conflict)

In project.pbxproj, you'll be looking at a slew of numbers that are pretty hard on the eye. It's how Xcode keeps track of all the files & references in the project, and a conflict will usually occur when two people have added resources to the project.

But at this point you need to apply a bit of thought to which of the changes you want to keep. Everything under the <<<< will be from your own branch, the one you were merging into. Everything under the ==== will be from the other branch, the one you were merging from. If the conflict is from multiple resource additions, you'll probably want to keep both changes.

In vim, search is performed with the / key. Here's the bread and butter of merging the conflics:

Type /<<< and press enter. You'll land on the first conflict.

Want to keep both changes? Then all you need to do is remove git's conflict markers, and leave all the text that's in between. Go into Visual Line edit mode (shift+v), and press x to delete the whole line.

Now delete the other markers:
/===, enter, shift+v, x.
/>>>, enter, shift+v, x.

Check that there are no other conflicts:
/<<<, enter. If you get another hit, resolve the conflict as before.

When there are no more conflicts, tap the colon and type wq (write, quit):
:wq, enter.

You'll land back in terminal, ready to commit the merge:
git add .
git commit -m "Resolved merge conflicts."

And that's it! Usually you'll have to close the project and re-open it for Xcode to recognize that the .pbxproj is now valid.

>> comments…

Diverting Burnout

Or, "I'm Working Too Much, but Not Forever"

For the past year and five months, without any prior programming experience, I've been on a nonstop campaign to learn iOS and OpenGL development.

I can calculate the time because my Apple Developer account renewed two months ago. Along the way I've had some success, at least enough to keep going: my app StorySkeleton has sold about 1300 copies since its release in April, and I've got a job at an awesome company in Marina Del Rey, CA where I'm developing an interactive storytelling app for iPad. The OpenGL stuff has been the most gratifying type of development I've done so far, and also the most challenging.

My life, career path, and all my ambitions have changed fundamentally in the past two years. Two years ago, I was an aimless, under-talented, and under-applied comic-book and graffiti artist.

Here's an example of the transformation I've taken:
Before, I hated math -- I despised its impracticality.
Now I love to solve problems with math. Modeling a solution on paper, translating it to code, and watching it work is an enjoyable and rewarding process.

But the journey is starting to take its toll:
My round-trip commute time varies from 2.5 - 4 hours per day.
My work day is 10 hours long - 9 hrs/day with a 1 hr lunch.
My mind feels as though it's continually operating at maximum capacity.

As a result, I am starting to experience burnout.

I had a bout with it seven months ago, right around the time I completed StorySkeleton and just before I started the new job. I felt as though I'd been learning so much, and pushing so hard, that I couldn't possibly learn any more and perhaps I had hit my personal learning wall -- after which point, learning would be increasingly difficult and infrequent.

This hunch turned out to be false, luckily. My ability to learn returned after a couple weeks of downtime, and was completely restored when I was faced with a new set of challenges: the new job, and OpenGL. But lately, burnout has been creeping back in.

Here are some of the burnout-diverting tips that have worked for me. They divide into two types of need: physical and spiritual. This idea has been re-hashed in plenty of 'burnout' posts I've read over the last several months, and I'm not sure the world is crying out for another one, but I feel it's important for me to acknowledge anyway.

Physical

  • Exercise. I do some uphill running most mornings to try and keep in shape and improve my heart rate. I usually can only spare 10 minutes, but I figure that's better than nothing.
  • Eat food and drink water. Both of these are essential, but easy for me to skip when I get working. I try to make the food as healthy as possible, given that I have limited time to cook or prepare lunches, and end up eating out a lot.

Spiritual

  • Prayer. I've found a great source of peace in the idea expressed in the verse, "For what do you have that you did not receive? If then you received it, why do you boast as if you did not receive it?" -- it's a check against an inflated sense of self-importance, and it makes me feel thankful.
  • Close Goals. I believe this falls in the spiritual realm because it essentially influences how you deal with your challenges inside of your mind. The concept of the self-fulfilling prophecy suggests that positive thinking produces positive results. To this end, it helps to maintain focus on a goal, and to be excited about a goal. For me, this focus is only possible if the goal can be realized within a reasonable timeframe.

The last one, "Close Goals," I'll expand on a bit more because it's the biggest help for me.

I realize that I cannot sustain my current lifestyle indefinitely. I work too much. So knowing that the projects I'm working on are short-term, with an end in sight, is a significant source of encouragement.

But other than the goal of finishing the project, I have another goal above that: to increase my skill and the value I provide as a developer in order to create awesome things, pay the bills, and help establish a comfortable and well-balanced life for myself and my wife.

This is the goal that drove me into development to begin with, and it's the one that keeps me going when I hit blocks. And thanks to the current demand for developers and the speed at which learning is possible these days, this goal feels pretty close as well.

So although I'm burning out, I know that I can hold on for another couple months, until I get an opportunity to re-arrange my work schedule into a configuration that more closely resembles something like this. And at any rate, I really am having a blast doing what I do, which I'm very thankful for since the status quo seems to be dissatisfaction.

>> comments…

MobileMeetup Talk: GLKit Demo

This past week I had the awesome opportunity to present a demo on beginning OpenGL ES 2.0 development on iOS at the Motion Theory-sponosred MobileMeetup.

The code from the talk is here: GLKitDemo

The main thesis of my talk was that since OpenGL is hard to learn, Apple's GLKit framework is a great resource, because you can use it to defer learning certain parts of OpenGL while you focus on other parts.

For example: in the GLKitDemo code, I didn't write any OpenGL functions to handle the framebuffer or texture loading. GLKViewController encapsulates the framebuffer, and GLKTextureInfo encapsulates the texture loading. As a result, the OpenGL code it contains focuses only on loading vertices and texture coordinates, using an OpenGL program and shader (in this case encapsulated within Jeff Lamarche's GLProgram), and drawing the scene.

I tried to comment the code pretty well, so I think it's probably decent getting-started code to supplement other tutorials.

The best resource I've found for this type of learning path is Learning OpenGL ES for iOS: A Hands-On Guide to Modern 3D Graphics Programming, since it does a great job of explaining what exactly is going on within the GLKit classes.

>> comments…

Why I Enjoy Using Vim

by David Sweetman in writing tags: vim

I really love using vim. It's not necessarily because it's much faster for me -- the main reason I enjoy it is the precision. After achieving some comfort with the basic keystrokes and commands, it becomes possible to articulate your way around the document with an incredible amount of precision.

I've been an advocate of keyboard shortcuts for most programs I've used. But vim is a whole new level entirely.

As i understand it, vim's precision has to do with its history: it was developed in a context where internet connections were generally much slower and less reliable. In order to remain productive while editing a remote document over a slow connection, the creators invented methods of navigating and manipulating a document with the goal of minimizing the productivity-detriment experienced by the slow speeds. The result is a set of incredibly powerful commands, which, as you manage to climb up the learning curve, become not only second-nature but increasingly expressive.

I've been fascinated with vim since my introduction to unix, but only recently have I reached a level of familiarity where editing text is a pure joy.

Here's my latest find:

:%norm

The % just says 'do this for every line,' just like when you search every line with %s

The 'norm' says 'execute the following commands.'

So then, whatever follows will be executed on each line. I had a list of OpenGL vertices that I wanted to put in braces, which I accomplished with the following two commands:

:%norm I{

:%norm A}

...to insert a bracket at the beginning and end of each line.

>> comments…

Using Git as a Specialized DIY Dropbox

by David Sweetman in writing tags: git

Over the last few years, I've been using Dropbox to backup my writing projects and access them from multiple computers.

However, this system always seemed somewhat flawed, and my general practice was to keep a 'working' folder on my most-used computer, and only move the files into Dropbox sporadically.

I simply didn't like working out of the Dropbox folder; it didn't seem right. So the result was that Dropbox only held an outdated back-up copy of whatever I was working on, and if I used another computer to keep working on an idea, I'd have to go and update the working files on my main computer by hand.

So I've always had this feeling that my Dropbox solution was less than ideal. On top of that, I've recently begun using a 7" netbook quite a bit, running debian mostly from the command line. It's underpowered but extremely portable, more so than the macbook air. While it is possible to install and use dropbox from the command line, with less than one GB of free space remaining on the internal drive I didn't want to sync everything, and the process of configuring which folders should be synced and which shouldn't seemed like it would probably be a pain.

Having become acquainted with Git, it occured to me that if I were to transition my document backup system to a git repo, the benefits would be substantial, and it would go very far to improve my writing process overall.

Here's a list of benefits I can think of:

  • Control. I have direct control over what happens to my documents, and when it happens.
  • Merging. If for some reason I edited a document on one machine and didn't push the changes upstream, I don't have to worry about editing that same document on another machine. If there are any conflicts, I'll be able to resolve them, but most of the time the changes will merge hassle-free.
  • Versioning and Branching. I can compare my documents at different stages of their lives, and roll back if I need to. It eliminates the risk of accidental erasures, and reduces the risk of taking an idea in a certain direction, only to later wish you could go back in time and choose a different direciton instead.
  • Logical gouping of files. Dropbox is like one huge repo containing everything, and I don't think there's a way to break it up easily. But you can have as many git repos as you want, and use them each for a different purpose. I've started with one repo for writing, but it's possible to use the same idea to track any kind of dynamic, often-changing files where this kind of system seems like a better solution than dropbox.
  • Many computers, easy install. I don't have to install and sign into Dropbox on each computer I use. I only have to install git if it isn't installed, which is much less intrusive, faster, and easier. It won't conflict with anyone else's dropbox if I don't happen to own the computer. I can keep the bandwidth usage down.
  • The server space is mine. This removes any concerns inherent in the fact that Dropbox is a third-party data storage service. I have better options and better flexibility if I ever get concerned about data confidentiality. If dropbox goes out of business, I don't have to find a new solution.

I've called my repo Scratchbook (super creative, I know, thanks) and it contains a file "about.txt", which describes the setup:

# about.txt

This is the Scratchbook repo.

It's designed to hold and track all of my ideas & documents,
so that I can edit them on any computer, and get the benefits of
a distributed versioning system.

The system is comprised of the following structure:

server:~/scratch/scratchbook.git : central bare repo, always push/pull from here.

server:~/scratch/scratchbook/    : working repo; use to edit docs remotely.

local:.../scratchbook/           : working repo; use to edit docs locally.

I've got a working repo on the server so that I can work on my documents from anything with an ssh client. So I don't even need to install git or anything else locally if I'm working from a temporary machine that already has ssh, and I can use iSSH if I'm working from an iPhone/iPad. Otherwise I can just clone scratchbook.git and start working.

So that's the basic setup. This may all seem obvious to anyone who has been using git for a while, but as I'm fairly new to it, I'm SUPER excited about the flexibility it gives me, and overjoyed to have a seamless multi-computer document editing system that not only solves the problem, but is fun to use.

>> comments…

Add an SSH key with one line

I usually put off adding my ssh keys and opt to type in my password every time I need to connect to a web server/push to github/etc. I'm fairly new to Unix & the command line, but am becoming increasingly fascinated by what you can do, so I decided to figure this out.

It can actually be done in one line: cat ~/.ssh/id_rsa.pub | ssh user@hostname 'cat >> .ssh/authorized_keys'

I also put it in a github gist for future reference: One-Line Command to Add an SSH Key

>> comments…

CardSliderView for iOS - Open Source

I just published my first open source project, CardSliderView for iOS.

It's the UI element from my iOS app StorySkeleton.

StorySkeleton imitates a deck of cards. It didn't take long to realize that while UIPageViewController has the exact behavior I wanted, it doesn't actually allow you to change the transition style. (Well, it does, but there's only one transition style.)

The transition style I wanted was for each page to be completely rigid, like a card, with no bending animation.

So that's the intention of CardSliderView. The idea behind CardSliderView is that you've got a deck of cards, and want to flip through them naturally.

As you swipe from right to left, the next card in your deck comes on screen, and passes on top of the current card. When you swipe from left to right, the current card pushes off-screen to the right and reveals the previous card sitting underneath it.

Check out the StorySkeleton website for a video of this concept in action.

If you're a developer, please let me know what you think!

>> comments…

Extending the PageControl sample to UN-load pages

There is a good (altough somewhat dated) Apple sample application which demonstrates lazy-loading pages in a paginated scroll view, called PageControl.

As it turns out, this sample is a good starting point, but I think it definitely wants some work before it's ready to use in any significant way.

Some problems with its current state:

The Basics

So how does it work? Basically, PageControl instantiates an array of empty pages, using [NSNull null] as a placeholder for each page, to be later replaced with a page's contents. The scroll view is then loaded up with a particular page from this array, using [scrollView addSubview:].

On awakeFromNib, after setting everything up, it loads the first two pages. The idea is that the page a user is on and the two pages around it will always be loaded, so that scrolling left or right reveals an already-loaded page which does not appear blank.

Implement the Changes

First things first: make sure we're not nesting views owned by different ViewControllers.

Pages get lazy-loaded with the method - (void)loadScrollViewWithPage:(int)page.

Here is the relevant piece of loadScrollViewWithPage code:

// replace the placeholder if necessary
MyViewController *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null])
{
    controller = [[MyViewController alloc] initWithPageNumber:page];
    [viewControllers replaceObjectAtIndex:page withObject:controller];
    [controller release];
}

As you can see from the code, viewControllers is an array holding two types of pages: instances of MyViewController and instances of [NSNull null].

To see if a page is null, we first create a new MyViewController object, returning whatever is the result of calling [viewControllers objectAtIndex:page] -- which will give us either the NSNull or the MyViewController for the page we want.

To check if the page needs to be loaded, we cast our new object as an (NSNull*) and check it against [NSNull null]. If this returns YES, we go ahead and create (lazy-load) the new page.

Here's how I decided to change this:

The following assumes you've got a root ViewController that holds a UIScrollView you want to load pages into.

This part will differ slightly depending on what you need. But instead of adding views belonging to ViewControllers to the scroll view as in the sample, you're going to want to somehow construct a UIView in whatever custom way you want.

I've done this two different ways, one using a XIB and one without. In both cases, I created a new Objective-C class inheriting from NSObject. Most importantly, this class should have the following in its interface:

@interface MyObj : NSObject {
    UIView *view;
}
@property (strong, nonatomic) UIView *imageView;
@end

If you plan to use a XIB file, the @property should be an IBOutlet.

The .m file should contain the method:

- (UIView *)view {
    return view;
}

Now to create the XIB and connect it to your new class. Cmd+N > UserInterface > View > Next, select iPhone or iPad, and give it a name. Now click on the .xib file, click on 'File's Owner,' and go to the Identity inspector. Open the 'Class' dropdown and change it to "MyObj", or whatever you called the class you just made.

Now connect up the XIB to its outlets in the .h file. With the XIB file still open, click the Assistant Editor button (the one at the top right above Editor - it looks like a suit & bowtie.) You should now see your XIB file and your MyObj class side-by-side. While holding the CTRL key, click on the XIB's View and drag to the UIView @property in your MyObj class.

Here's the idea: you now have a UIView that you can manipulate and add things to, without all the UIViewController overhead, without methods like viewDidLoad and viewWillAppear and all of that, which you won't need, since all you're interested is adding the UIView itself to your scroll view.

So you can go ahead and build up the XIB like you want, adding subviews to the View, and any other elements and logic you think you might need.

With these changes, your - (void)loadScrollViewWithPage:(int)page method should now look something like this (notice that I've changed the name of the viewControllers array to 'pages', and 'controller' to 'thisPage'):

// replace the placeholder if necessary
MyObj *thisPage = [pages objectAtIndex:page];
if ((NSNull *)page == [NSNull null])
{
    page = [[MyPage alloc] initWithPageNumber:page];
    [pages replaceObjectAtIndex:page withObject:thisPage];
}

// add the controller's view to the scroll view
if (thisPage.view.superview == nil)
{
    CGRect frame = scrollView.frame;
    frame.origin.x = frame.size.width * page;
    frame.origin.y = 0;
    thisPage.view.frame = frame;
    [scrollView addSubview:thisPage.view];
}

I've also taken out the call to release, since of course we're using ARC.

NOTE: This assumes that you have an init method on your MyObj class, as Apple's example has on its UIViewController subclass, named initWithPageNumber:(int)page. How you choose to fill in your pages is largely up to you, but you should create a method which takes an integer of the page number as its parameter, and fills out MyObj based on that. You can look at PageControl's initWithPageNumber implementation, their solution should still work well.

In PageControl, after they add the new page's view to the scrollView, they use the setters on their instance of MyPageControl to set the page's specific content right in loadScrollViewWithPage.

This is the bit where they do it:

    NSDictionary *numberItem = [self.contentList objectAtIndex:page];
    controller.numberImage.image = [UIImage imageNamed:[numberItem valueForKey:ImageKey]];
    controller.numberTitle.text = [numberItem valueForKey:NameKey];

And it could look the same in whatever your implementation is. All you need to do is use the generated setters for whatever properties you've added to MyObj.

That should be pretty much it for cleaning up the implementation when you're loading views into a scroll view that is owned by a ViewController. You'll still use the lazy-loading method - (void)loadScrollViewWithPage:(int)page for any specific page you want to lazy-load.

Tripping A Lazy-Load

But when do you call the method loadScrollViewWithPage?

The way the PageControl sample does it is a good start: right in awakeFromNib (or for us, since we're using a ViewController, in viewDidLoad or viewWillAppear), they explicitly call:

[self loadScrollViewWithPage:0];
[self loadScrollViewWithPage:1];

This loads the first visible pages, and is a good way to go.

From here on out, loadScrollViewWithPage only gets called in one other place: in scrollViewDidScroll.

Which brings us to our second problem: loading pages halfway through a scroll.

As mentioned earlier, this is bad because with a page of any significant size and complexity, a noticable mid-scroll hang-up will occur; the page simply freezes halfway through its scroll. It is definitely an unwanted glitch in the user experience.

I found that I could solve this very simply: by triggering the lazy-load at full width increments of the page, rather than half-width increments.

My original thought had been to do the lazy-load in scrollViewDidEndDecelerating, but this is not ideal because, of course, a user can scroll past multiple pages without that method getting called, since it requires the scroll view to come to a complete rest.

So the following code will trip a lazy-load only at whole page increments:

CGFloat pageWidth = scrollView.frame.size.width;
if (page != floor((scrollView.contentOffset.x - pageWidth) / pageWidth) + 1) {
    page = floor((scrollView.contentOffset.x - pageWidth) / pageWidth) + 1;
    [self loadScrollViewWithPage:page - 1];
    [self loadScrollViewWithPage:page];
    [self loadScrollViewWithPage:page + 1];
}

In my experiece this simple change is enough to get smooth performance even while loading large images into a page, targeted to an iPhone 3Gs and iPad 1 performance benchmark.

Lazy Un-Loading

Finally, what I promised from the beginning, the un-loading.

My original and failed attempt was to create a method called unloadScrollViewWithPage:, and pass in page - 2 and page + 2 in order to unload the pages around the three active ones.

This worked okay until I implemented device rotation. When the device is rotatedand the scrollView's contentOffset is changed to fill the new width, scrollViewDidSroll gets called, so you need to make sure you account for that. With unloadScrollViewWithPage:page - 2...:page+2, I was getting really whacky results after rotations; pages were randomly appearing and dissappearing and re-ordering themselves.

Rather than fix this in the rotation event, I figured out a way to solve it in the page-unloading method, which was to change it to:

unloadIrrelevantPages,

which, rather than specify the pages to unload, specifies the pages not to unload, and unloads all the others. It takes no parameters and blasts any pages EXCEPT for the current page and the two pages on either side of it.

First, you need a currentPage ivar with getters and setters on your ViewController. In scrollViewDidScroll, set this variable instead of creating a local variable like they do in PageControl (they call it int page = floor...).

Your unloadIrrelevantPages method could look like this:

-(void) unloadIrrelevantPages {

    //Loop through all the pages in the pages array:
    for (int i = 0; i < pages.count; i++) {

        //Check to see if it's a page that should stay alive:
        if (i != currentPage && i!=currentPage+1 && i!= currentPage-1) {

            //if not, remove it:
            MyObj *pageToRemove = [pages objectAtIndex:i];
            if ((NSNull *)pageToRemove != [NSNull null]) {
                NSLog(@"removing page %d", i);
                [pageToRemove removeFromSuperview];
                NSNull *emptyPage = [NSNull null];
                [pages replaceObjectAtIndex:i withObject:emptyPage];
            }
        }
    }    
}

Now it doesn't matter if scrollViewDidScroll is called on a rotate, because it will always cause currentPage to be freshly calculated, and it will only unload pages that are not currentPage +/- 1!

I hope this helps someone who is trying to solve the same problem I was. This'll do as a first draft of this article, I hope to come back and clean it up later.

>> comments…

Benefits of a Git-backed Blog

Several months ago when I first started using git, I made a terrible mistake. It was one of the first command-line tools I had used, and I didn't realize "git init" was invoked on the current directory. So I ran it on my home directory, thinking that some time later git would prompt me for the path for the repo. After a "git add .", I had added all the files and folders from my home directory into the repo.

I started to get a feeling this wasn't right, and so I googled around for a way to undo the add. I found the "unstage" command, so I ran that. So then I had a git repo with a bunch of unstaged files, and I could still see all the unstaged files listed when I ran "git status." I was beginning to understand a little of how git worked, but wanted to clean out that repo. Well, simple, "git clean." I assumed that git only held references to my files, and this clean command would just reset the repo. So I ran "git clean -f -d."

Well the -f was "force," and the -d was "all files in all directories." Since my home directory was a repo, what I had done when I unstaged the files was tell git that those files were not part of this repo. Then "git clean" gets rid of everything that is not part of the repo.

More precisely, "git clean" will permanently and irrevocably delete all files that are not a part of the repo. I was mistaken in my assumption that git would not have any real affect on the file structure, and several minutes into the cleaning process I decided to kill the terminal session, since my poor laptop was thinking way too hard for it to just be removing a superfluous set of references.

And that's how I wiped out my home directory.

Fast forward a bit, and after more resarch and learning, I've come back to git. This time, vim is no longer a foreign world, and I've got a better grasp of how to use command line tools.

In the past day, I've learned how to generate a static blog, create a local git repo including the static content, install git on a confounding godaddy shared host, create a remote repository on the server, configure ssh, create a post-receive hook on the server to automatically checkout the content to the document root, and create a post-commit hook in my local repo to automatically push the new site to the server after a commit.

So for me, one of the main benefits of blogging this way is that it teaches me git. As a self-taught independent developer, I haven't come across a ton of situations where I needed to use git. I've been using the git integration in Xcode, but only because it's so easy to use. For the first time I'm starting to really understand it, and I've only scratched the surface.

It's also remarkably fun. For anyone interested in git, vim, and markdown, I'd definitely reccomend it. It's a great way to learn git on your own.

>> comments…

Getting Started

I've found the best way to blog! This blog is being generated statically in a local git repository by pelican, then pushed to a git repo on the live site.

This blogging method fascinates me. Everything I need to do to write the blog, I can do from Terminal. I'm writing in vim, generating the static blog with the pelican cli, and pushing to the server with git.

Anyway, this blog will be a collection of ramblings or discoveries in relation to my programming attempts -- right now I'm working in iOS so I expect that's what I'll be blogging about most often to start.

>> comments…