Hold and Copy in UIKit

2010-02-23 19:00:00 -0500


This article has been cross-posted at MobileOrchard.com, our favorite place to get news and tips about iPhone/iPad development.

Recently, I wanted to implement an interface where a user holds down on a UIView class or subclass to reveal a copy menu. Basically, I didn’t want to have to present the user with a UITextField just to provide the ability to copy some data to the pasteboard. In this case, I’m working with a UILabel, but a similar paradigm already exists in many Apple-supplied apps where one can hold down on an image and be presented with the Copy menu option.

Going in it seemed pretty straight-forward, but it ended up taking me the better part of an afternoon of trial and error alongside the Event Handling section of iPhone Application Programming Guide to work it all out, so I believe a tutorial is in order. A reference project with sample code is available on Github.

Getting a Hold of a Hold

One can easily calculate the length of time a touch was held when the touch ends (by calculating the difference between the timestamp properties of the passed UIEvent and UITouch objects), but I found making this the point of responding to the event less than ideal because it means responding to the user’s interaction after the user lifts her finger, rather than while she is holding down on the screen. I’d rather respond while the user is still holding her finger down to let her know that the instruction was received. If the software will only respond after the user lifts her finger, she has no idea how long she has to hold her finger down, which is a nuisance, really.

Old Cocoa pros and experienced UIKitters probably saw the solution from a mile away: we intercept the touches began event for the view we’re interested in, and tell some object to do something after a long enough delay (the minimum time we want a user to have to hold to do something). We then cancel the request if any of the other touch events fire before our delay hits. That looks something like this, depending on your needs:


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *t = [touches anyObject];
if ([t locationInView:someViewWeAreInterestedIn])
[self performSelector:@selector(showMenu) withObject:nil afterDelay:0.8f];
}
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showMenu) object:nil];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showMenu) object:nil];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showMenu) object:nil];
}

There may be better ways to do this, but this seems pretty solid. In the sample code you can see this at work in the view controller, which shows a hidden image once a user holds down on another image for long enough. Just under a second (0.8s) seemed to feel right to me.


- (void)holdingView:(id)view {
[hiddenView setHidden:NO];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSSet *imageTouches = [event touchesForView:imageView];
if ([imageTouches count] > 0) {
[self performSelector:@selector(holdingView:) withObject:imageView afterDelay:0.8f];
}
[super touchesBegan:touches withEvent:event];
}

Implementing a Custom Copy

I feel like there’s real pun-potential for this subtitle, but reasonably groan-inducing text is eluding me. In any event, now that we can detect when a user has held our view long enough to warrant a response, we need to make a move: presenting the UIMenuController with the Copy option and actually copying something in response. I’m sure there are various approaches that can be taken, but my approach was to start by subclassing UILabel, curious to hear other ideas.

First, I wired the subclass to intercept touch events, and to save that touch-down for the extra point (ho!):


- (BOOL)canBecomeFirstResponder {
return YES;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if ([self canBecomeFirstResponder]) {
[self becomeFirstResponder];
UITouch *t = [touches anyObject];
holdPoint = [t locationInView:self];
[self performSelector:@selector(showMenu) withObject:nil afterDelay:0.8f];
}
}

// (other touches* methods implemented to cancel perform) ...

Showing the menu itself is a touch awkward, you need to provide a “target rectangle” (CGRect) to UIMenuController to tell it about where on the screen you want the menu to appear (it can appear above or below this point, depending on proximity to the screen bounds).


- (void)showMenu {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(reset) name:UIMenuControllerWillHideMenuNotification object:nil];

// bring up editing menu.
UIMenuController *theMenu = [UIMenuController sharedMenuController];
CGRect myFrame = [[self superview] frame];
CGRect selectionRect = CGRectMake(holdPoint.x, myFrame.origin.y - 12.0, 0, 0);

[self setNeedsDisplayInRect:selectionRect];
[theMenu setTargetRect:selectionRect inView:self];
[theMenu setMenuVisible:YES animated:YES];

// do a bit of highlighting to clarify what will be copied, specifically
_bgColor = [self backgroundColor];
[_bgColor retain];
[self setBackgroundColor:[UIColor blackColor]];
}

Note that I’m registering for a notification: I basically wanted to know whenever the menu disappeared, because that would mean it’s time to stop high-lighting the text in the label, and restore the original background color. Totally not required for getting the menu on screen.

Next we have to make it clear to the UIMenuController that we mean serious business, and what kind of business we intend to conduct. In my case, I was only interested in Copy, but other options are available:


- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
BOOL answer = NO;

if (action == @selector(copy:))
answer = YES;

return answer;
}

And in my case, the data I’m looking to copy is simply the text of the label itself, and I just want to put it on the general pasteboard so the user can paste it into another app, or wherever:


- (void)copy:(id)sender {
UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];
[gpBoard setValue:[self text] forPasteboardType:@"public.utf8-plain-text"];
}

That’s it!

Zetetic is the creator of the encrypted iPhone data vault and password manager Strip and the open source encryption-enhanced database engine SQLCipher.

Link: Intercepting UITableView Touches

2010-02-18 19:00:00 -0500


Recently I did some digging into how I could customize the response of UITableView to touch events. Mostly, I wanted the touch to be forwarded on to a sub-view I’d stuck on a UITableViewCell, which is a tricky and somewhat quixotic journey that I think every iPhone app developer needs to make. In the end, I decided I didn’t like the functionality I was implementing, but on the way I found a great tutorial by app developer John Griffiths on how to get there.


Building Static Libraries to Share Code on iPhone AND Mac OS X Projects

2010-02-14 19:00:00 -0500


There are numerous good resources out there, like Clint Harris’ excellent tutorial, that describe how one can share common code across iPhone projects by keeping the shared code in a separate project, and including it in your various projects using the cross-project reference technique. The technique is further complicated due to the need to use static libraries for iPhone projects, as dynamic libraries are not allowed by Apple in iPhone apps.

Despite the nuisance, the technique works pretty well, and we’ve been using it as detailed here on Mobile Orchard for building OpenSSL and SQLCipher for iPhone projects. However, recently we started sharing a lot of our SQLCipher-specific code (like our custom data model, etc) with our upcoming Mac OS X application, Strip Sync. Therefore, numerous libraries we were sharing were linked against our openssl-xcode build, which is necessarily a little customized. Using the shared library there worked great for development (“Debug”) builds, but came to a screeching halt once it came time to do a “Release” build.

The Release build process sets up a universal binary. Thus, using the common technique described above, the linker ends up trying to link a PPC build of your binary against our static openssl library, which was only building either i386, or ARMv6. If you are using the code and instructions we described in our SQLCipher tutorial to use SQLCipher in your iPhone project, and you decide to use that technique for a Mac OS X project, this problem will affect you (if you are building the standard universal binary on release builds).

Happily, we’ve got a fix! Once we realized what was going on, I mentioned it to Stephen, who put together our handy openssl-xcode project, that provides you a drop-in way of setting up the static library for iPhone development. In a few minutes he hooked up a new version of the Run Script build phase that sets up the static library to do the usual for iPhone builds, but to detect a universal binary build and do the right thing:


set

if [ "$SDKROOT" != "" ]; then
ISYSROOT="-isysroot $SDKROOT"
fi

## this is a universal build
if [ "$ARCHS_STANDARD_32_BIT" = "i386 ppc" ]; then

mkdir -p temp-universal

BUILDARCH="ppc"
echo "***** BUILDING UNIVERSAL ARCH $BUILDARCH ******"
make clean
./config -openssldir="$BUILD_DIR"
make CC=$PLATFORM_DEVELOPER_BIN_DIR/gcc-4.0 CFLAG="-D_DARWIN_C_SOURCE -arch $BUILDARCH $ISYSROOT" SHARED_LDFLAGS="-arch $BUILDARCH -dynamiclib"
cp libcrypto.a temp-universal/$BUILDARCH-libcrypto.a
cp libssl.a temp-universal/$BUILDARCH-libssl.a

BUILDARCH="i386"
echo "***** BUILDING UNIVERSAL ARCH $BUILDARCH ******"
make clean
./config -openssldir="$BUILD_DIR"
make CC=$PLATFORM_DEVELOPER_BIN_DIR/gcc-4.0 CFLAG="-D_DARWIN_C_SOURCE -arch $BUILDARCH $ISYSROOT" SHARED_LDFLAGS="-arch $BUILDARCH -dynamiclib"
cp libcrypto.a temp-universal/$BUILDARCH-libcrypto.a
cp libssl.a temp-universal/$BUILDARCH-libssl.a

lipo -create temp-universal/*-libcrypto.a -output libcrypto.a
lipo -create temp-universal/*-libssl.a -output libssl.a
else
BUILDARCH=$ARCHS
echo "***** BUILDING ARCH $BUILDARCH ******"
make clean
./config -openssldir="$BUILD_DIR"
make CC=$PLATFORM_DEVELOPER_BIN_DIR/gcc-4.0 CFLAG="-D_DARWIN_C_SOURCE -arch $BUILDARCH $ISYSROOT" SHARED_LDFLAGS="-arch $BUILDARCH -dynamiclib"
fi

ranlib libcrypto.a
ranlib libssl.a
cp libcrypto.a libssl.a "$BUILT_PRODUCTS_DIR"
exit 0

We’ve updated the Github project with this change, so if you are using this technique and openssl-xcode, you can simply download the latest (or pull from github master), and drag and drop the new project file into your XCode build directory and be off and running.

Again, the previous technique worked just fine for sharing code across iPhone projects, but doing a universal build of the static library is absolutely necessary if you use the same shared code in your Mac OS X project and you try to link it against a universally-built binary.

Zetetic is the creator of the encrypted iPhone data vault and password manager Strip and the open source encryption-enhanced database engine SQLCipher.

Encrypted iPhone Applications

2010-02-05 19:00:00 -0500


It was just over four months ago when we set about learning the iPhone SDK and putting together our first application, a revamp of STRIP, the personal information manager that was so popular for the Palm platform.

Now that we’ve got a mostly-finished product (we’re still making a few tweaks and getting ready to start the beta), we took a step back to look at the code we built to keep the development DRY and enable quick development of future applications. Essentially, we’ve built ourselves a framework for building applications that have a fully-encrypted database layer using SQLCipher, with a robust data model, a login facility, random password generation and seeding, etc.

What took us four months to do at first, now only takes us a few days. Using the code we extracted from STRIP we were able to put together one of our other app ideas in less than four work days. Introducing Codebook, a secure notepad:

Codebook is a LOT like the built-in Notes application on the iPhone. They aren’t exactly the same, but the critical difference is that no one is getting in and reading your notes unless they have your password:


Coming Soon: Strip Sync

2010-01-29 19:00:00 -0500

Updated: 6/6/2012 This blog post is out of date, and the software referenced in it, Strip Sync, has been discontinued in favor of Strip for Windows and Strip for OS X.

We’ve been getting a ton of mail lately about Strip, mostly along the lines of, “when will the desktop client be ready?” We’ve made a ton of progress over the last few weeks, and we really appreciate the various letters of encouragement and everyone’s enthusiasm.

Strip Sync

We are calling the initial desktop client Strip Sync. It provides some really critical functionality for the Strip platform going forward, and it will be offered for free here on our website. It runs on Mac OS X 10.5 and later, and most modern versions of Windows (although “modern” feels like a weird term, given the leap into the future that seems to be taking place at the moment). It’s really two separate, native applications that provide the same excellent and much-needed features:

  • Import from CSV – with files from Palm Strip Exporter or any other resource.
  • Synchronization with the iPhone – you’ll be able to bring over your old Palm data.
  • Independent back-up – for many people, the iTunes backup just isn’t enough.

At some point soon Strip Sync will also provide handy export and printing capabilities, but we’re really eager to get these much-asked-for features out there already, so export and print may not be introduced in the initial version. We’ll see soon.

We’re about ready to start looking for beta testers, we’ll post a notice here on the blog soon and send out a message to the mailing list. If you’d like to be added to our mailing list, send us an email, or just fill out the form over on the Strip product page.

Once Strip Sync is out the door we’re going to use it as the basis of the full-featured desktop version of Strip, which will allow you to enter and edit your data from the desktop, as opposed to having to slowly tap away with your thumbs on the iPhone.

What About Android?

We’ve been getting a lot of queries asking for a version of Strip for Android, but this one really jumps out:

Hi,

Years ago, around the turn of the century, I used to work as an
engineer at Palm Digital Media (eBook division of Palm); all of the
devs and admins in our group worshipped your Strip app.

I have recently acquired an Android device (Nexus One). Please release
a version for Android.

Thanks for making one of the most useful applications ever,

We’d really love to do this, I mean, really, love. Right now our hands are a bit full with those two aforementioned native desktop applications! Once we have the desktop component firmly in place, we can begin to explore other platforms like Android, but for now it’s going to have to wait. We do expect to see an explosion of Android devices over the next year or so, and it would be a fantastic addition to the Strip platform.

If you’ve just got to have Strip on your platform of choice, please let us know, we’re listening.