Skip to content

Posts from the ‘Technology’ Category

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.

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.

17
Oct

Automatic Build Numbers for iOS Apps Using Xcode

UPDATE Sep 2014: Added another idea from Jared Sinclair, who has example code for Git.
UPDATE Jan 2013: Now also updating the build version in the dSYM file.

There are many articles that discuss how to automate build numbers in Xcode. However, some are misleading for iOS apps. Many are quite long. I wanted to find a short, simple way to do this.

Background

On the target Summary tab your project settings, Xcode lets you set a Version and a Build. The version is what we are all familiar with, such as 5.0 and 5.1.1. The build is what often appears in parentheses after the version, as in 5.0 (134) and 5.1.1 (147).

Apple doesn’t seem to care what you use for Version (aka CFBundleShortVersionString), but Build (aka CFBundleVersion) must be a monotonically increasing string, comprised of one or more period-separated integers. Thus Apple will reject your update if you go from 1.1 (10) to 1.2 (0). The version is ignored and, alas, zero is clearly less than ten.

The part about integers is important too because Apple will also reject your update if you go from 1.01 (1.01) to 1.1 (1.1). The period is not a decimal place. Both (1.01) and (1.1) are interpreted as “the integer one followed by the integer one”. We saw this logic in action when OS X went from version 10.4.9 to 10.4.10.

Problem

If you do this wrong, you’ll see this error when you upload your app update:

This bundle is invalid. The key CFBundleVersion in the Info.plist file must contain a higher version than that of the previously uploaded version.

To get your monotonically increasing period-separated integer build number, you could just use the app version, like 5.1.1 (5.1.1). But I think it’s better to use a build number that can help identify the code from which that version of your app was built. Both Git and Mercurial include the ability to count commits, which is perfect—always increasing and helpful in identifying the code.

If you modify the Info.plist file in your project folder during the build process, you’ll probably need to commit the change to your code repository. This extra commit, while not harmful, is unnecessary. Instead, you can modify the Info.plist file in the app package after the build process is finished. The file will be in a binary format, but the PListBuddy tool can handle it.

Solution

Here is the script you need for Mercurial. This script also adds the bundle version to the dSYM file, which is necessary for correctly symbolicating your crash logs. It’s also required for several distribution mechanisms including TestFlight and HockeyApp.

ver = `/usr/local/bin/hg id -n`.strip
puts "Build number is #{ver}"
filepath = "#{ENV['BUILT_PRODUCTS_DIR']}/#{ENV['INFOPLIST_PATH']}"
puts "Updating #{filepath}"
`/usr/libexec/PlistBuddy -c "Set :CFBundleVersion #{ver}" "#{filepath}"`
filepath = "#{ENV['DWARF_DSYM_FOLDER_PATH']}/#{ENV['DWARF_DSYM_FILE_NAME']}/Contents/Info.plist"
puts "Updating dSYM at #{filepath}"
`/usr/libexec/PlistBuddy -c "Set :CFBundleVersion #{ver}" "#{filepath}"`

The first line counts the number of commits in your local repository. It’s safe to use as long as your builds are done on the same machine. Repositories on other computers may have different commit counts. For Git, you can count your commits using a similar command.

Steps

  1. Select your project in the Project Navigator
  2. Select your target
  3. Select the Build Phases tab
  4. Choose “Add Build Phase”
  5. Select “Add Run Script”
  6. Change the Shell to “/usr/bin/ruby”
  7. Copy and paste the script

Within your app, you can grab the version and build number with this code:

NSDictionary *appInfo = [[NSBundle mainBundle] infoDictionary];
NSString *appVersion = [NSString stringWithFormat:@"%@ (%@)", appInfo[@"CFBundleShortVersionString"], appInfo[@"CFBundleVersion"]];

This will give you a string like 2.2 (134) that you can display in your app. I’m using the new object subscripting syntax in Objective-C, but it’s not too hard to switch back to using objectForKey:.

Follow Up

Jared Sinclair has a nice example of how to do this with Git. I really like the idea of using the branch name as a build suffix for Debug builds. I’m going to update my code to match.

3
Aug

3 Tips on Auto Synthesized Properties

Ha ha! You fool! You fell victim to one of the classic blunders, the most famous of which is “never get involved in a land war in Asia.” But only slightly less well-known is this: “Never have an auto-synthesized property and an instance variable with the same name when death is on the line!”

—Vizzini

At work, my team decided to drop support for iOS 4. Most of our customers have upgraded to iOS 5.1. Surprisingly, very few are still using iOS 5.0. I guess over-the-air upgrades are working really well for people.

Our app is old. In some places we still checked if the user had upgraded to iOS 3 yet. :) My team spent the week on cleanup and paying down technical debt. My job was to:

  1. Convert to using object literals for arrays, dictionaries and numbers.
  2. Remove @synthesize calls.
  3. Switch all of our instance variables to properties.

I love shortening and removing code without losing functionality. The first two tasks went fairly quickly. But I…uh…learned a lot while tackling number three.

You can declare an instance variable in an @interface block — either the public interface in the .h file or the private interface in the .m file. You can also declare one in the @implementation block in the .m file. Or all three at once.

You can mix instance variables, properties and auto-synthesized properties. But auto-synthesized properties are so much nicer: less code, less maintenance, less boilerplate typing. Here are 3 tips to help you use properties more effectively.

1. Deal With Read-Only Properties

An auto-synthesized property generates an instance variable with the same name as the property except prefixed with an underscore. This is great because it’s clear whether you’re using the property (self.myprop) or the instance variable (_myprop). Using the bare name (myprop) doesn’t even compile, so you limit the confusion and bugs caused by accidental mistypes.

Auto-synthesized properties create their own getter and setter methods (avoiding some unnecessary typing). You can implement your own getter and/or setter, in which case those methods are not automatically generated. The instance variable (_myprop) is still synthesized.

However, if your property is readonly AND you implement your own getter method, then an instance variable is not synthesized. I suspect this is to allow for a named property that is dynamically calculated. In any case, if you would like an instance variable, you can force it to be synthesized by either:

  1. Using @synthesize myprop = _myprop; in your @implementation, or
  2. Redeclaring the property as readwrite in your private @interface

The second option works like inheritance in that you can redeclare a property as less-restrictive but not the other way. Redeclaring a public readwrite property as privately readonly isn’t possible, though why you’d want to do that remains a mystery.

2. Avoid Instance Variables

It is a Bad Thing™ to have both an auto-synthesized property and an instance variable declared with the same name.

  1. self.myprop = 1 uses the auto-synthesized instance variable
  2. myprop = 1 uses the declared instance variable
  3. _myprop = 1 uses the auto-synthesized instance variable

Since you end up with two instance variables, when you probably intended to have only one, doing this can cause lots of unexpected bugs and problems. Better to avoid declaring instance variables for your synthesized properties.

It is a Worse Thing™ to have two auto-synthesized properties of the same name with one prefixed with an underscore.

    @property myprop;
    @property _myprop;

This causes problems because self._myprop is different than _myprop when you really should have used __myprop, making you start to wonder about your own sanity. This doesn’t seem like it would happen much, but it can if you are importing several .h files, one of which uses underscores in its property names.

So don’t do that. :)

3. Use Instance Variables Correctly

In most cases, it’s best to stick with using the property (self.myprop) in your code. However, it’s important to use the instance variable (_myprop) in two cases:

  1. In your init: methods to avoid possible side-effects if you implement your own setter later, and
  2. In your custom getter and setter methods to avoid infinite recursion (always nice).

Auto-synthesized properties are great. Not quite as awesome as ARC, but close.

24
Jan

200 Million iPads Update

UPDATE 2012-04-24: My March estimate of 11.8 million was accurate. I’ve added a link below and updated the image to reflect the change.

UPDATE 2012-08-01: My July estimate of 23.3 million was way off. Like Asymco says, “It’s perhaps unrealistic to expect 150% growth for more than one year.”

Back in September, I estimated that Apple would sell 200 million iPads by the end of 2012. It looks like I was a little too optimistic.

Apple just reported stellar quarterly results. However, iPad sales growth was not quite as strong as Apple’s initial sales seemed to indicate. Two years after introduction, the iPad is selling better than the iPhone did two years after its debut. But it looks like it may take one extra quarter for Apple to hit 200 million iPads.

Actual Results

Estimated iPad sales through 2012

Estimated Results

  • Jan to Mar 2012: 11.8 million (this was accurate)
  • Apr to Jun 2012: 23.3 million (actual amount was 17.0 million)
  • Jul to Sep 2012: 28.0 million
  • Oct to Dec 2012: 38.9 million
  • Jan to Mar 2013: 29.8 million (was 29.9 due to rounding differences)

Disclosure: I own AAPL stock.

11
Jan

The First 100 Days of a Startup

Josh Coates, founder of Mozy and current CEO of Instructure, once taught a series of classes at BYU on high-tech startups. I jumped at the chance to audit his class.

One of the things Josh covered was what should happen during the first 100 days (14 weeks) of a high-tech startup.

Week 1 — Research and choose business
Week 2 — Build financial model and development plan
Week 3 — Build pitch with screenshots and practice
Week 4 — Interview law firm, staff and advisers
Week 5 — Incorporate and setup shop with office space and equipment
Week 6 — Initial documents, books, hires and cap table
Week 7 — Create website and logo (do a trademark search)
Week 8 — Identify 10 to 20 potential investors and study who else they invest in
Week 9 — Practice the pitch and setup meeting with the least important investor
Week 10 — Interview, build product, pitch again
Week 11 — Interview, build product, pitch again
Week 12 — Interview, build product, pitch again
Week 13 — Interview, build product, pitch again
Week 14 — Interview, build product, pitch again

He recommended interviewing one potential employee every day. Pitching to the least important investor first lets you have a chance to practice in a situation where making a mistake isn’t as damaging.

I first met Josh just after publication of an article he wrote on how many angel investors in Utah were doing it wrong. The article, entitled “Poison in the Well”, in addition to having a great title, was direct and clear in its criticism. It was one of the reasons I later applied to work at Mozy.

For anyone who knows him, I think “direct and clear criticism” is a good phrase to describe what it’s like to work for Josh. His class was no exception. It was a great chance to learn from someone who’s been there and done it successfully.

16
Sep

200 Million iPads

About a month ago, I mentioned in a meeting at work (and later tweeted) that I believed Apple would sell about 200 million iPads by the end of 2012. Brief awkward silence. Then I was asked to send out the details of how I’d arrived at that number.

Honestly, at that point, I’d just eyeballed the sales trends. As I opened up a spreadsheet, I really hoped my estimate would hold up. :)

The first iPad went on sale on April 3, 2010. Here are Apple’s official iPad sales numbers:

At this point, we only have one year-over-year growth number for comparison. That’s fairly weak evidence on which to predict the future. But this is just for fun and games, right?

Assuming that the growth rate remains consistent, here is what iPad sales will look like:

  • Jul to Sep 2011: 11.8 million (actual result was 11.1 million)
  • Oct to Dec 2011: 20.7 million (actual result was 15.4 million)
  • Jan to Mar 2012: 13.2 million
  • Apr to Jun 2012: 26.1 million
  • Jul to Sep 2012: 33.3 million
  • Oct to Dec 2012: 58.4 million

Estimated iPad sales through 2012

192 million iPads by the end of 2012. Apple is selling every iPad they can make so these numbers may be limited by Apple’s ability to meet demand.

Put another way, here are the yearly totals:

  • 2010: 14.8 million
  • 2011: 46.4 million (actual result was 40.1 million)
  • 2012: 131.0 million

That’s the beginning of a very fast growth curve. There are a few others who agree.

Apple will report their next financial results in mid-October. Then we’ll have two data points.

Disclosure: I now own some AAPL stock.

4
Apr

Free DNS Hosting Performance Tests

UPDATE May 2011: Namecheap’s DNS service has recently experienced two outages: one due to a denial of service attack and the other caused by a misconfigured DNS entry at another registrar. Their service is relatively new, so I hope they handle these types of issues better in the future. I appreciate the honesty and openness with which they have reported these issues. ClouDNS is also experiencing intermittent downtime, but has not mentioned anything.

UPDATE July 2013: A few months ago, I switched to using CloudFlare’s Free plan. I’m really happy with the service, which is faster than what I’ve been using before. And I really like using a company specializing in DNS security and performance. SolveDNS’s DNS Speed Comparison Report agrees. Highly recommended.

ORIGINAL

Way back in April 2009, I read two articles about using Pingdom to test hosted DNS services. Pingdom has a limited free account, includes several types of web-related performance tests and has test servers in over 25 locations throughout North America and Europe. I started using it immediately and have been very pleased with their product and service.

I’ve been wanting to test the performance of hosted DNS services that include at least one domain for free. Two years later, I finally got around to it. All tests were done with my own domain name.

Each test covered eight days (except Afraid, which I ended early after six). I would have preferred to run the tests concurrently over a longer period of time. If I missed anyone, please let me know and I’ll add them to the list.

NameUptimeResponseStd DevNotes
Namecheap100.00%82 ms175 msIncludes free email forwarding.
ClouDNS100.00%93 ms319 msExcellent user interface.
Moniker100.00%114 ms248 ms*** Included with domain registration. ***
ZoneEdit99.65%91 ms280 ms
GeoScaling99.78%103 ms332 ms
XName100.00%117 ms325 ms
DNS Exit99.98%256 ms1025 ms
FreeDNS100.00%187 ms530 msWebsite down for days recently. No comment on why.
Afraid80.45%223 ms1022 msSub-domain sharing. Down 4 mins every 19.

I recommend Namecheap and ClouDNS. If you are willing to pay for DNS services, there may be faster and more reliable options. I’d love to try out easyDNS and DynDNS but neither have a free option.

Here are the charts to give you a visual idea of the test results. Pingdom needs to allow customers to control the vertical scale. I’m just too lazy to make my own charts using their cool API. :)

Namecheap

Namecheap performance results

ClouDNS

ClouDNS performance results

Moniker

Moniker performance results

ZoneEdit

ZoneEdit performance results

GeoScaling

GeoScaling performance results

XName

XName performance results

DNS Exit

DNS Exit performance results

FreeDNS

FreeDNS performance results

Afraid

Afraid performance results