Skip to content

Recent Articles

20
Aug

How To Optimize PNG Images with TinyPNG

TinyPNG is a great service for optimizing and shrinking PNG files. I’ve been using it for several years now. I tried out as many apps, command line tools and services as I could find, and never found anything as easy to use or as high quality.

Most PNG images are created in a way that wastes a lot of space. TinyPNG discovers and removes this wasted space. I’ve found that most of my images are about twice as big as they need to be. And I don’t notice any visual differences after the optimization process. The differences are there, but they are almost impossible to notice unless you know what you’re looking for.

API

When I had only a few images, the website was perfect. Drag-and-drop up to 20 files at once, wait, click to download, done. However, as I added more images, it became a little tricky to keep track of them all.

About a year and a half ago, I started using their new developer API. Whenever I added or changed an image, I would put it in a folder with all my original images. I wrote a script to send all of the files to TinyPNG and then download the optimized versions into a separate folder.

Photoshop Plugin

TinyPNG has a Photoshop plugin too, but I’ve never used it.

Code

This is the script I wrote. It’s a command line tool that takes an input folder and an output folder. The optimized images end up in the output folder.

#!/usr/bin/ruby -w

#
# tinypng.rb — Placed into the public domain by Daniel Reese.
#

require 'rubygems'
require 'json'

# Set API key.
apikey = "your_api_key_goes_here"

# Verify arguments.
ARGV.length == 2 or fail("Usage: ./tinypng.rb <input-folder> <output-folder>")
src = ARGV[0]
dst = ARGV[1]
File.exist?(src) or fail("Input folder does not exist: " + src)
File.exist?(dst) or fail("Output folder does not exist: " + dst)

# Optimize each image in the source folder.
Dir.chdir(src)
Dir.glob('*.png') do |png_file|
    puts "\nOptimizing #{png_file}"

    # Optimize and deflate both images.
    cmd = "curl -u api:#{apikey} --data-binary @#{png_file} 'https://api.tinypng.com/shrink'"
    puts cmd
    r = JSON.parse `#{cmd}`
    if r['error']
        puts "TinyPNG Error: #{r['message']} (#{r['error']})"
        exit(1)
    end
    url = r['output']['url']
    cmd = "curl '#{url}' -o #{dst}/#{png_file}"
    puts cmd
    `#{cmd}`
end
Dir.chdir("..")

puts 'Done'

Recommended

The best part is that TinyPNG is free. If you are using the API and want to optimize more than 500 images a month, or if you are working with files larger than 5MB or so, then you’ll want to subscribe to one of their monthly plans. There are no commitments, so you can switch back to the free plan any time.

Overall, I highly recommend TinyPNG. They are continually tweaking things so that their service is always as good as it can be. The company that runs TinyPNG uses it themselves for their internal development projects. I think that may be why it works so well for developers.

If you want to save disk space and load time, especially for mobile apps, go check it out.

3
Apr

How and Why to Implement Keyboard Shortcuts in iOS 7

Support for keyboard shortcuts is new in iOS 7. The new UIKeyCommand class and the -[UIResponder keyCommands] method allow app developers to add keyboard shortcuts to their iOS apps without using ugly hacks.

Two Reasons

There are two reasons to add keyboard shortcuts to your iOS app:

  1. People often use wireless keyboads with their iPads, especially for text entry. Keyboard shortcuts for an external keyboard are helpful additions that can make typing on an iOS device more convenient.
  2. They work in the iOS Simulator! During development and testing, keyboard shortcuts are so much nicer than using the mouse to simulate gestures on the simulator.

I admit that second reason is a benefit only during development. However, keyboard shortcuts make using the simulator so much nicer that, ten minutes after adding them to my app, I was kicking myself for not adding them sooner.

Key Commands

The first thing to do is to implement the keyCommands method in one of your classes in the responder chain. On iOS, the responder chain includes the currently focused control, its super views, and their associated view controllers.

@implementation MyViewController

- (NSArray *)keyCommands {
    return @[
        [UIKeyCommand keyCommandWithInput:@"f" modifierFlags:0 action:@selector(keyPressF)]
    ];
}

@end

Starting at the first responder and working up the responder chain, iOS will ask each object if it responds to the given action. The first object it finds that implements the method is allowed to handle the key press. This means that you can implement the keyCommands method in a different class than you define the action handler methods. However, to avoid compiler warnings about private methods, it’s often easier to do both in the same class.

The keyCommands method appears to be called three times for each key press, so you may want to cache the array if you have a lot commands.

First Responders

By default, iOS will only generate key press events when a control, such as a text field, is the first responder. You can fix that by overriding the canBecomeFirstResponder method in your root view controller so that it can become the first responder. You can implement the keyCommands method and your action methods there too.

@implementation MyRootViewController

- (BOOL)canBecomeFirstResponder {
    return YES;
}

- (NSArray *)keyCommands {
    return @[
        [UIKeyCommand keyCommandWithInput:@"f" modifierFlags:0 action:@selector(keyPressF)]
    ];
}

- (void)keyPressF {
    // Do something awesome here.
}

@end

This lets you avoid having to create and manage a hidden text field or something similar.

Handling Dialogs

For simple apps, that’s all you need to do. However, if your app presents a modal dialog with a text field, then your root view controller will not regain first responder status when that dialog is dismissed.

You could manually tell your root view controller to become the first responder each time a dialog is dismissed. But there is an easier way.

UIApplication is the top-level object in the responder chain. If you subclass UIApplication and override the canBecomeFirstResponder method, then it will become the first responder when a dialog is dismissed. Then it will tell your root view controller to become the first responder again.

@implementation MyApplication

- (BOOL)canBecomeFirstResponder {
    return YES;
}

@end

To tell iOS to use your new UIApplication subclass, you need to modify the main method in main.m:

int main(int argc, char *argv[]) {
    @autoreleasepool {
        // The third parameter is nil by default.
        return UIApplicationMain(argc, argv, NSStringFromClass([MyApplication class], NSStringFromClass([AppDelegate class]));
    }
}

Now a dialog with a text field will no longer mess up your keyboard shortcuts.

Simplifying Things

Things are working, but you can simplify a bit more if you want. Instead of having an app delegate separate from your UIApplication subclass, you can let your subclass be its own delegate.

Then you can move everything from your app delegate into your new MyApplication class, including the properties and app lifecycle methods, and delete your app delegate class.

@interface MyApplication : UIApplication <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

Then adjust main.m one more time to assign the same class as both the application and its delegate:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSString *app = NSStringFromClass([MyApplication class]);
        return UIApplicationMain(argc, argv, app, app);
    }
}

Let me know if I’ve missed something or there are better ways to do this. :)

Future Hopes

As of iOS 7, the UIApplicationDelegate is in the responder chain, and I think it would be better to do this stuff there than subclassing UIApplication. But right now the UIApplicationDelegate does not appear to forward first responder status to the root view controller. So this only works with a UIApplication subclass.

2
Apr

The Joke That Made Me Laugh The Most Yesterday

From a newsgroup, posted anonymously and slightly paraphrased here.

WARNING: May only be funny to developers and other creative types. :)

Hey everybody, I’m a student over at the business school. I have a great idea for a novel and am looking for an author to write it for me. If anyone is interested let me know.

I don’t have any money right now, but I’ll give you some equity. Looking for someone with at least a Masters in English, preferably a Ph.D.

It’s a great opportunity and will really help build your resume. If you’re interested please contact me, and we can set up a time to meet in the library. After signing an NDA, I’ll share my idea with you.

I’ve already talked to a couple of my teachers and they think it’s a great idea.

21
Jan

Sudden Moment of Certainty

Matt Gemmell is switching careers. For years, he’s had a job programming and a hobby writing. Those will now be reversed. He’s feeling excited, nervous, and—my favorite part—alive.

I need to come back to fear at some point, but today I couldn’t stop thinking about this comment:

It’s a big gamble, but I walked into my office last Monday morning for my first full week of exclusively writing, I opened the blinds, and I felt something I’ve only felt one other time in my life: this is right. The last time I had that certainty, my next words were “I do”, and my fiancée became my wife.

I know exactly how that feels. There have been four times in my life when I’ve had that sudden clarity of thought, when I knew the right answer with complete confidence. They are rare, exhilarating moments that have occurred after spending time in deep thought or wrestling with a major decision over a period of weeks or even years.

I’ve found that the feelings of certainty don’t last. And doubts can begin to creep in almost immediately. But when I keep the memory of that moment alive, and trust myself to continue despite doubt and fear, it has given me the courage to move forward, commit, make a change, and keep going.

The artist Jordan Voigt mentions these moments of certainty too:

Sometimes it happens almost incidentally and you suddenly realize: I’ve got it! As soon as this moment of certainty is there, sometimes I can work for four or five months at a time on a single subject – and it’s a continuous flow. But I can also wait four to five months or longer for this moment to arrive!

I didn’t know what to call that feeling. Defining moment seems too general because those can be caused by external events happening to you. This seems more personal, the result of some internal process or effort.

Thinking about it again, I had a sudden flash of insight (of course) into what to call it:

Sudden moment of certainty (noun)
A point in time when you realize, with perfect confidence, that an idea is true or a choice is right.

I don’t know if it will catch on. I don’t know that it needs to. Good enough that I now have words to identify such a sublime and catalytic event.

18
Oct

Kestrel — A Choosable Path Novel for Young Adults

Just over a year ago, I wrote about the interactive fiction fantasy book app I was working on. After much work by many people, we released it just a few weeks ago. It’s free, for iPad-only (so far). Please check it out and let me know what you think.

Kestrel is a choosable path story or novel for young adults where you make choices in behalf of the main character, altering how the story unfolds.

You get to decide where Kestrel goes, who she trusts, and how she reacts to the events around her. Depending on your choices, she can end up on either side of the conflict or weave a path along the middle.

Growing up on a farm where she can see the city lights at night, Kestrel dreams of a better future. One without cows. But in a world where two forces of magic are about to collide, Kestrel is about to be caught up in something that will change her world forever. And she may be the deciding factor.

The app currently has just one chapter. Each new chapter will be published as it becomes available. You will be able to buy each new chapter, or the whole book (and all future chapters) for a great discount until the book is complete.

Version 1.2 was just released today. It fixes a few more problems, including one occasional crash bug.

▸ Fixed another issue where the app could crash when tapping the next button at the top of the app.
▸ Increased the font size a bit when the device is in portrait orientation.
▸ Added a request for ratings, but hopefully in a way that isn’t annoying. If you’re annoyed, let me know. :)

11
Apr

Introducing FastSocket

FastSocket is a fast, synchronous Objective-C wrapper around BSD sockets for iOS and OS X. Use it to send and receive files or raw bytes over a socket very quickly. This is the class to use if fast network communication is what you need. An asynchronous API might be better if you want to do something else while your network operations finish.

UPDATE: Now available as a CocoaPod

I wrote FastSocket a couple years ago, and put it up on GitHub, but don’t think I ever mentioned it. There are classes for both clients and servers. It’s available under the MIT license. Please feel free to let me know if you have any problems, questions or suggestions.

To create and connect a client socket:

FastSocket *client = [[FastSocket alloc] initWithHost:@"localhost" andPort:@"34567"];
[client connect];

Send a file:

long sent = [client sendFile:@"/tmp/filetosend.txt"];

Receive a file of a given length:

long received = [client receiveFile:@"/tmp/newlyreceivedfile.txt" length:1024];

Send raw bytes:

char data[] = {42};
long sent = [client sendBytes:data count:1];

Receive raw bytes:

char data[42];
long received = [client receiveBytes:data limit:42];

Close the connection:

[client close];

Please check out the unit tests for more examples of how to use these classes.