Xcode trick: Creating a shortcut to duplicate a line

A feature I love in TextMate that is missing in Xcode is the ability to duplicate a line of text with a simple two-key combo. Sure, it’s possible to select the line and then copy/paste, but it’s far too fiddly. For example, let’s say I wanted to add another line to this array:

xcode-duplicate-line-1.png

I would have to type:

+ move to start of line

+ (shift-down) select line

+ C copy

(down) (deselect)

+ V paste

And I would end up with this.

xcode-duplicate-line-2.png

That’s far too much work.

Digging through Xcode’s preferences I couldn’t find anything that would help, so I widened my search and stumbled on a page called Customizing the Cocoa Text System that showed me a way I could get this to work.

I created a directory:

mkdir ~/Library/KeyBindings

And created the file:

~/Library/KeyBindings/DefaultKeyBinding.dict

{
    "^d" = ("moveToEndOfLine:",
        "deleteToBeginningOfLine:", // line in kill buffer
        "yank:", // put back what was deleted
        "insertLineBreak:",
        "moveToBeginningOfLine:",
        "yank:"); // duplicate line
}

I restarted Xcode, and now a control+D (^d) duplicates the line. And I can easily add the new element to the array.

xcode-duplicate-line-3.png

And it’s a system wide change, so it will work in many applications that have text input. (And doesn’t seem to screw up TextMate either.)

If you want to experiment with your own text input tweaks, there is a list of all selectors. You’ll need to restart whatever application you’re using to test after each change. You may also run into strange side effects. I ran into a problem with Xcode doing auto-intent after i added a newline with insertNewline:. Luckily, insertLineBreak: has the same effect, but seems to trick Xcode into not adding the extra whitespace on the duplicated line.

No Comments »

Posted by Jason Moore on Friday, 14-May-2010 at 11:06 and is filed under General.

A long road to a simple solution

The first iPhone app I wrote was BigNames - a large-text contact list. It’s also one of the apps that I use the most, so I was surprised to hear that a particular user found that it worked the first time, but then crashed on subsequent loads.

The only thing I could think of was that the app was running out of memory and was being killed by the OS. BigNames builds an array of names when it first loads, so that it can load quickly in the future. A huge number of contacts might consume too much memory. This user had around 6700 (!!!) contacts – far more than I had ever tested – but still I would have expected to load hundreds of thousands of strings into an NSArray before hitting the 20-30 MB memory limit.

To replicate the problem, I wrote a method to create thousands of bogus contacts. Sure enough, when there were more than 4000 names being loaded from cache, it would crash on load. One odd point, was that that app would only crash when I wasn’t running it in debug mode. This was annoying, since normally I would add NSLog messages or breakpoints to identify and understand a problem, but the app would only crash when these tools were unavailable.

I remembered that Crash Logs are stored on the device and can be viewed with Xcode’s Organizer.

organizer-crash-logs-450x.png

I was able to see that the crashes weren’t the normal EXC_BAD_ACCESS variety but was something called 0×8badf00d (ha ha, get it?) and was actually the launchd complaining that the app was “failed to launch in time”. So, we aren’t actually taking about a crash, just a slow to load app.

And that explains why this wasn’t happening during a debug session. Debugging is slower than simply running an app, so I assume that launchd skips the timeout check when in debug mode.

The “failed to launch in time” error led me to think that I was doing too much work in applicationDidFinishLaunching:. I moved some of the loading of cached names to a separate thread so the app was able to load without getting killed by launchd – but then was unresponsive for the first 10-15 seconds. Not a huge improvement.

I realized that the problem was some code was taking far too long to load. It appeared to be UITableView reloadData. But since that is an API call - I couldn’t step through Apple’s code to see what it was doing. But I did know what the app was doing when it was killed by launchd. The stack trace from the Crash Log was:

Thread 0:
0   Foundation                   0x0005d9e0 -[_NSIndexPathUniqueTree uniqueIndexPath:withIndexes:count:] + 132
1   Foundation                   0x0005db8e -[NSIndexPath initWithIndexes:length:] + 86
2   Foundation                   0x0005d6ce +[NSIndexPath indexPathWithIndexes:length:] + 126
3   UIKit                        0x000a2ed8 +[NSIndexPath(UITableView) indexPathForRow:inSection:] + 40
4   UIKit                        0x0009fe84 -[UISectionRowData refreshWithSection:tableView:tableViewRowData:] + 2052
5   UIKit                        0x0009f5a0 -[UITableViewRowData rectForFooterInSection:] + 96
6   UIKit                        0x000c1380 -[UITableViewRowData rectForTableFooterView] + 168
7   UIKit                        0x001596ac -[UITableView setTableFooterView:] + 240
8   BigNames                     0x000043ea -[ContactTableViewController loadView] (ContactTableViewController.m:103)
9   UIKit                        0x00069750 -[UIViewController view] + 44
10  UIKit                        0x00088fd8 -[UIViewController contentScrollView] + 24
11  UIKit                        0x00088d90 -[UINavigationController _computeAndApplyScrollContentInsetDeltaForViewController:] + 36
12  UIKit                        0x00088c3c -[UINavigationController _layoutViewController:] + 28
13  UIKit                        0x0008863c -[UINavigationController _startTransition:fromViewController:toViewController:] + 504
14  UIKit                        0x000883a8 -[UINavigationController _startDeferredTransitionIfNeeded] + 256
15  UIKit                        0x00088298 -[UINavigationController viewWillLayoutSubviews] + 12
16  UIKit                        0x0006c86c -[UILayoutContainerView layoutSubviews] + 76
17  UIKit                        0x000482d0 -[UIView(CALayerDelegate) _layoutSublayersOfLayer:] + 32
18  QuartzCore                   0x0000c1b8 -[CALayer layoutSublayers] + 80
19  QuartzCore                   0x0000bed4 CALayerLayoutIfNeeded + 192
20  QuartzCore                   0x0000b83c CA::Context::commit_transaction(CA::Transaction*) + 256
21  QuartzCore                   0x0000b46c CA::Transaction::commit() + 276
22  QuartzCore                   0x0000b318 +[CATransaction flush] + 32
23  UIKit                        0x00052e94 -[UIApplication _reportAppLaunchFinished] + 28
24  UIKit                        0x00004a80 -[UIApplication _runWithURL:sourceBundleID:] + 608
25  UIKit                        0x00055df8 -[UIApplication handleEvent:withNewEvent:] + 1516
26  UIKit                        0x00055634 -[UIApplication sendEvent:] + 60
27  UIKit                        0x0005508c _UIApplicationHandleEvent + 4528
28  GraphicsServices             0x00005988 PurpleEventCallback + 1044
29  CoreFoundation               0x00057524 CFRunLoopRunSpecific + 2296
30  CoreFoundation               0x00056c18 CFRunLoopRunInMode + 44
31  UIKit                        0x00003c00 -[UIApplication _run] + 512
32  UIKit                        0x00002228 UIApplicationMain + 960
33  BigNames                     0x0000293e main (main.m:14)
34  BigNames                     0x000028d4 start + 44

Only the first few lines are relevant. The last bit of code that I control is loadView, and then it seems to get stuck in some sort of loop creating NSIndexPaths.

I thought perhaps the custom table footer was causing problems (lines 5-7), but removing the footer had no effect.

I was out of ideas, and the problem seemed to be in Apple’s UITableView code, so I decided I needed to send an email for the Apple developer support. If it was a bug that a UITableView couldn’t handle more than 4000 rows maybe they would have a workaround.

I created a new project and made a UITableView with 10,000 rows. It loaded almost instantly. Weird. I then added some of the UITableViewDelegate and UITableViewDataSource methods and it became incredibly slow. The method that was causing the problems: tableView:heightForRowAtIndexPath:

I looked closer at the UITableViewDelegateProtocol documentation and found this blurb under the tableView:heightForRowAtIndexPath:

There are performance implications to using tableView:heightForRowAtIndexPath: instead of rowHeight. Every time a table view is displayed, it calls tableView:heightForRowAtIndexPath: on the delegate for each of its rows, which can result in a significant performance problem with table views having a large number of rows (approximately 1000 or more).

Oh. (Why didn’t I see that before?) So, I removed this method from my UITableViewController subclass:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
	return ROW_HEIGHT;
}

And added the following to the loadView method:

self.tableView.rowHeight = ROW_HEIGHT;

Done! And so ridiculously simple and obvious in hindsight. Is there a moral to this story? Well, a specific lesson is only use cells of a consistent height if your table might be long, and never use the inefficient heightForRowAtIndexPath: if all cells are the same height. A more general take-away for me is that I should spend more time reading and re-reading the documentation. Not only is it easy to miss tiny details on an initial read, later versions of the documentation might specifically address the problem at hand. (I swear the issue on performance problems with heightForRowAtIndexPath: wasn’t there when I first read the docs!)

No Comments »

Posted by Jason Moore on Tuesday, 5-Jan-2010 at 10:06 and is filed under Geekery, iPhone.

BikeFixTO: iPhone app source code for sale

bikefixto screenshots

I built BikeFixTO - an iPhone app that shows your closest bike repair location. I’m thrilled with how it turned out: The UI is nice and clean. The animated sorting of the nearest locations looks great, and I personally use the app all the time. So, on all these technical fronts, it was a hit.

Where I’ve been less successful is on the marketing side. I never expected to sell thousands, but I expected to sell hundreds. And I had vague plans of rolling it out to other bike friendly cities. But, like so many developers I underestimated how much work it was involved in the sales process. It’s hard work to get the word out. Developers often have this idea that sales should be perfectly rational where the best software wins. The pitch is: “My software solves a problem you have. Therefore you should buy it.” And if there is no immediate response, they give up since clearly the person didn’t have that problem. The whole idea of “touching” people multiple times, reminding people, tweaking the message, running contests and having sales feels spammy and inefficient.

And that is why developer’s need to consciously stop thinking like developers when it comes to marketing. And if that isn’t possible, they should partner with someone who can better wear the marketer’s hat.

I would like BikeFixTO to take on a new life. I would love it to spread to other cities where it can promote cycling, local bike shops and bicycle culture. Personally, I’m not the guy to do that, but maybe you are.

I’ve decided to make BikeFixTO free*, but am selling the source code. For $199 you get the full source to an iPhone application that you can build, tweak, customize and sell. There are no limitations with what you can do, except that I require that you don’t distribute the source code (which would prevent me from selling other copies).

The code is also an excellent code base for anyone wanting to build location-aware applications. Some of the details about what you’ll find in the source code:

  • query data from sqlite database
  • custom sqlite function to calculate distance between coordinates
  • parse and display opening hours
  • animation to visually sort items
  • dial phone numbers
  • php code for displaying maps on a webserver
  • access GPS data

If you’re interested, download BikeFixTO and give it a test drive. Drop me a line if you have any questions or would like to purchase the source code.

*Update: I’ve changed the price back to 99¢, but if you’re interested in kicking the tires, I’m happy to send you a promo code.

*Update 2: I’ve updated the source to use MapKit.framework for fully dynamic map of all locations instead of a static map of a single location.

50 Comments »

Posted by Jason Moore on Thursday, 9-Jul-2009 at 11:10 and is filed under Toronto, UI, Geekery.

FabLab in Toronto: Other organizations that provide tools, workspaces and expertise

fablab-dsc00186.jpg
Image source: http://fablab.waag.org/

I’m interested in setting up a FabLab in Toronto - a place where people can come to build (almost) anything.

Other than an intriguing anonymous text snippit, I haven’t found an existing FabLab working group. During my background research, I’ve found several organizations that have tools and workspaces that are somewhat open to the public. These groups might help to create an interm or virtual FabLab until an actual space and funding are obtained.

If you know of any additional workshops or organizations that make tools accessible, please leave a comment below or contact me. If you are interested in keeping up to date on setting up a FabLab in Toronto, follow me on twitter: @xinsight

Existing Toronto Labs|Workspaces|Tools

Interaccess
http://www.interaccess.org/
9 Ossington Ave (Queen and Ossington)
416-599-7206
focus on digital art.
tools: electronics lab, CNC, drill press…
cost: $100/year

The Workroom
http://www.theworkroom.ca/
1340 Queen St. West (Parkdale)
416-534-5305
Epilog Mini 35W Laser cutter
- cutter only has filter (no vent) so plastic cannot be cut or etched. Wood, leather, paper, cloth, etc. OK.
cost: hourly rate (?)

HackLabTO
http://hacklab.to/
170a Baldwin St. (Kensington Mkt)
Tuesday evenings (6-ish) are open house.
Electronics/embedded software focus. Book library.
cost: $50/month

Community Bicycle Network
http://www.communitybicyclenetwork.org/

761 Queen St. West (Queen and Euclid)
416-504-2918
Full range of bike tools and parts. They have a drill press, but only staff can use power tools.
cost: $6/hour

Bike Pirates
1292 Bloor St. West (Bloor and Landsdowne)
Full range of bike tools and parts. (Welder?)
cost: by donation

Other Organizations with Tools (but no access)

There are many other publicly-funded organizations in the city that no doubt have amazing labs and tools. At this point I am unclear if there is any public access to their resources.

Toronto Public School Board
e.g. Central Tech (Bathurst and Harbord) - auto repair, sheet metal shop, milling, etc.
OCAD
University of Toronto
Ryerson

3 Comments »

Posted by Jason Moore on Monday, 29-Jun-2009 at 16:24 and is filed under Toronto, FabLab.

A simple, secure password-protected file area

A secure area on a website is a great way to deliver content to clients. You don’t have to send attachments around, and you can watch the logs to see when the files were accessed.

Recently, I worked on a site that required a password-protected media area. They didn’t want HTTP Authentication (which you have to admit is clunky) so I reworked the client area script that runs on my consulting site. It’s not rocket science, but there are a few tricky bits so I thought I’d share it and maybe save you some time.

Before releasing it, I asked for some advice on distributing PHP applications on Stack Overflow. I incorporated most of the suggestions, so I hope you’ll find the install painless. You’ll need PHP. This was tested on Linux and OS X, but I don’t see why it wouldn’t work on Windows.

Download securearea-1.0.tar.gz

Installation

1. Download and untar: securearea-1.0.tar.gz

2. open SecureAreaConfig.php.sample and edit it for your setup

3. save as: SecureAreaConfig.php

Now, you can copy these files to your webserver and access the file: client.php

You’ll see a some vanilla HTML which you can easily customize to match your site.

Once you have SecureArea working, you should move the “secure” directory to somewhere outside your webserver root, so that the files can only be accessed through the SecureArea script.

Download securearea-1.0.tar.gz

Demo: Try out SecureArea

Send feedback to the SecureArea comment box

No Comments »

Posted by Jason Moore on Thursday, 13-Nov-2008 at 0:00 and is filed under Geekery.