July 24, 2009

MapKit annotation drag and drop with callout info update

Introduce MapKitDragAndDrop
There're few samples [1][2] about creating a MapKit app for iPhone, and Apple only provides snippets of code for people who want to dig out the secret of MapKit. So I decide to create my own MapKit sample that meets my needs. The sample code is available on my GitHub, MapKitDragAndDrop, feel free to download it.

In this sample, you can:

  • Use CLLocationManager to find out current location
  • Use MKReverseGeocoder to convert current location coordinate to place information
  • Customize annotation/pin callout info
  • Update callout info when MKPlacemark is found by MKReverseGeocoder
  • And finally, allow annotation/pin to be able to drag and drop


UPDATE 2


Thanks to Uffe Overgaard Koch's help, the MapKitDragAndDrop now can update callout info automatically in both 3.0 and 3.1 SDK.

UPDATE


In 3.0 SDK, the callout view info won't update unless you tap to close it and tap pin again to bring it back. But in 3.1 SDK, the bug seems fixed, the MapKit will update the callout view info automatically when DDAnnotation changed.

UIViewController


The sample works pretty straight forward, it all begins by creating CLLocationManager to update location in UIViewController's viewDidLoad:


Once CLLocationManager updates the location, we create DDAnnotation and add into the map view. Then we stop CLLocationManager from updating location again, otherwise you will see another new annotation drop on the map.


When we add newly created annotation to the map view, the MKMapViewDelegate methods take over. We prepare our own annotation view in mapView:viewForAnnotation:.

First, we avoid user's current location annotation (MKUserLocation, the blue round spot you saw on the map) use our custom annotation view. And then, we try to dequeue our annotation view from map view, if there's no existing DDAnnotationView for us to reuse, we create a new one.

Finally, the tricky part, we assign the map view to DDAnnotationView before return. This is because when later user dragging the annotation/pin to new position, we will need to use map view's convertPoint:toCoordinateFromView: to get the new coordinate.


MKAnnotationView and MKAnnotation


We create our own MKAnnotationView and MKAnnotation, and named them DDAnnotationView and DDAnnotation.

The DDAnnotation is designed to work like MKPlacemark or MKUserLocation, is a specific type of annotation that provides custom title and auto coordinate-to-placemark subtitle for DDAnnotationView, MKPinAnnotationView or other MKAnnotationView to use later.


And this is how DDAnnotation works: When you create DDAnnotation with initWithCoordinate:title:, we save the coordinate and title, and create MKReverseGeocoder to try to reverse coordinate.

Once MKReverseGeocoder converts the geocode to placemark information, MKReverseGeocoderDelegate mehod reverseGeocoder:didFindPlacemark: gets called, and we save the MKPlacemark, and post MKAnnotationCalloutInfoDidChangeNotification notification, to let the map view know about the change. Then, map view will ask DDAnnotationView to retrieve DDAnnotation's title: and subtitle: and update the callout info.


As for DDAnnotationView, this is the part we handle drag and drop events and setup callout view. The event handling code was copied from Apple's iPhone Application Programming Guide, you can check more detail from here.

But, basically, we implement all four touch event methods, which are touchesBegan: withEvent: for recording information about the initial touch event, let us know the start location of the movement, and touchesMoved:withEvent: method adjusts the position of the view by checking the new position to see if the dragging is actually happened.

When the user stops dragging an annotation view, the touchesEnded:withEvent: method takes over, we use map view's convertPoint:toCoordinateFromView: to convert new pixel point back to map coordinate value, and set the new value back to this DDAnnotationView's annotation object with DDAnnotation's changeCoordinate: method. We are doing this was because MKAnnotation's coordinate property is readonly, you are not allow to modify it by default, so we create a "setter" method to do the change.

And Lastly, the touchesCancelled:withEvent: method, this is not a optional method, if you decide to take care touch event, do not ignore this one (Apple said so). We reset the position and states here if draggin is not detected in ouchesMoved:withEvent:.


That's it. Hope you enjoy the ride on the map.

References:
[1] Craig Spitzkoff's Using MKAnnotation, MKPinAnnotationView and creating a custom MKAnnotationView in an MKMapView
[2] Gavi Narra's Playing with Map Kit series: Part 1, Part 2 and Part 3.

April 14, 2009

Black Cat Clock 1.0

After one week of coding and one week of waiting, the iPhone App: Black Cat Clock is available on iTunes Store now.

http://homepage.mac.com/digdog/blackcatclock


It's a simple clock app with my roommate, Lulu. Lulu was born in Sep. 2008, currently 8 months old, mixed, black cat. i love him so much that I decided to create an iPhone App for him, in honor of his handsome, vigorous and good manners.
Black Cat Clock: Screenshot

The clock comes with three little features:
  1. Analog Clock: an IKEA swiss clock in the picture, which has Lulu standing next as company.
  2. Meow Meow Hourly: by default, this clock will sounds the hours audibly on Lulu's meow. You can turn off this feature in the setting menu.
  3. Lulu's Messages: this is designed for network-connected iPhone and iPod touch users only. You can check Lulu's secret daily messages over internet.

And thanks for Brad and Hu's helps from Tokyo, this app comes with four different languages, including English, Japanese (日本語), Trad. Chinese (正體中文) and Simp. Chinese (简体中文). Hope it could satisfy all the cat lovers from these regions.

Last, it costs 99 cents, and your kindly supports can provide Lulu with better cat foods!

April 01, 2009

You can't make hacker tools on iPhone

What is Grupa Reveal


Grupa Reveal is an iPhone app decrypts the scrambled Group Password that used in Cisco's IPSec VPN Cleint.

User are allowed to input the scrambled Group Password (enc_GroupPwd) listed in .pcf file, and the app will decrypt it by using Apple's CommonCrypto library without any other infomation.

How it works


You can follow my previous posts for more detail:

Where to get


The source code is available on github.

March 14, 2009

Migrate from SVN to a remote Git Repository

Before today, I was using my lovely, 3 years old, Mac mini as remote Subversion server, and checkout projects to my MacBook through command-line tools or the limited Xcode's SCM (software control management) Subversion support. And yes, I am not very satisfied with svn, and I bet you know the reason.

And Git is such a wonderful experience, if you tried GitHub. So, I was serious considering to migrate from Subversion to Git in these two days, but it's really a hard decision, especially when Xcode is not supporting Git right now, which means you will have to make a turn and back to your shell prompt. But anyway, I stepped out into the bright sunlight from the darkness of..., huh, I switched. Cleanly moved from Subversion to a remote Git repository. Here are the steps, hope this helps those who wants to do the same:

Use MacPorts to install Git


Unlike other UNIX platforms, installing Git on Mac OS X is super easy. Assuming you had latest Xcode and MacPorts installed, then simply type the command in Terminal:
$ sudo port install git-core +svn +bash_completion

It takes maybe 10 to 20 minutes, depends on your CPU power, and everything is all set. I installed it on both my Mac mini (server) and Macbook (client).

Create Remote Repository on Server


Connect to the server, and setup a new "bare" Git repository:

$ ssh mini.local
Password:
Last login: Sat Mar 14 20:44:00 2009 from macbook.local

$ cd ~/
$ mkdir ~/git/
$ mkdir ~/git/projectname.git
$ cd ~/git/projectname.git
$ git --bare init<
Initialized empty Git repository in /Users/digdog/git/projectname.git
$ exit


That's it for the server side. You just create an empty remote repository for storing deltas and binaries later from your working Git repository.

Convert Subversion to Git from Client


Back to client side, first create a Git repository, and use git-svn to fetch code from your Subversion repository on the server, then clone it to a normal Git repository that does not have stuffs for supporting git-svn, and finally, push back to remote git repository.

Here's how I did:

$ cd ~/
$ echo "digdog = digdog <digdog@my.email.address>" > user.txt


Create a text file that contains username to allow us mapping from Subversion user to Git user later.

$ cd ~/
$ mkdir svn_to_git
$ cd svn_to_git
$ git svn init svn+ssh://mini.local/Users/digdog/svn/trunk/projectname --no-metadata
$ git config svn.authorsfile ~/user.txt
$ git svn fetch


Create a temporary directory called "svn_to_git", then initialize this directory as Git repository, and point it to the SVN repository on my Mac mini server. Next, ask Git to use user.txt to map Subversion user to Git user in next command. Following the step sixth, fetch all the source code from server.

$ cd ~/
$ git clone svn_to_git temp_projectname
$ cd temp_projectname
$ git remote rm origin
$ git remote add origin ssh://mini.local/Users/digdog/git/projectname.git
$ git push origin master


In step two, clone the svn_to_git repository to a another temporary repository, which will will push back to remote repository later. And delete remote branch after cloning in step fourth. Step Five, add the remote repository to local temporary repository configuration, as a remote server called "origin". At the end, we push local master branch (in temporary repository) to the origin's master branch (on the server).

Clone from Remote Repository


So we finished, just clone it:

$ git clone ssh://mini.local/Users/digdog/git/projectname.git


and push/pull your code like a happy Git user.

References


Jon Maddox from Mustache, wrote a great and simple tutorial about how to cleanly migrate from subversion repository to git repository.

Tim Lucas, a web app developer, wrote a clear step-by-step tutorial about how to setup remote git repository.

March 01, 2009

Don't choke your watchdog

Your application will be terminated if wasting damn too much time in applicationDidFinishLaunching.

February 27, 2009

Emoji.plugin for Safari/WebKit Browser

Emoji.plugin is a WebKit Plug-in for Safari/WebKit browser. It will convert Emoji characters into viewable iPhone Emoji icons, if HTML has "<object type="text/x-emoji"></object>" tag.

How to test


  • Install the plug-in, and open the Safari (or click "Build and Go" in Xcode project, it will open Safari too)
  • Drag the test.html to Safari


Screenshot

How it works


When browser found HTML or XHTML content has MIME "text/x-emoji" type object, it will load this standard WebKit Plug-in.

During the plug-ing initialization, it loads a JavaScript file within the plug-in bundle, and asks current web frame to evaluate this script. The script is based on two people's work over internet, one is photar's Grease Monkey script, and the other is Johannes Schriewer's iPhone emoji image data.

The Grease Monkey script will use XPATH to grep out all the text node, and compares each characters with Emoji character codes, and replace them with <img> image element and uses the image data stored as JSON format in the script.

And browser will update the web frame with Emoji icons instantly.

Known Issues


  • Your HTML/XHTML content needs "<object type="text/x-emoji"></object>" tag to let browsers load this plug-in.
  • If your browser disable the plug-ins or JavaScript support in preference, this plug-in won't work.
  • If your HTML/XHTML is too complex, the script could take very long time to finish the replacement.

Where to get


This plug-in is licensed under MIT license.
The source code is available on github.
Pre-build binary can be downloaded from here.

February 16, 2009

I want $(PLATFORM_SDK_DIR)

From Apple's Xcode 3.1 release note:


The "SDK Path” build setting has been renamed “Base SDK.” Its value can be either a path or a name of an SDK. When setting the value in the Build Settings Inspector, you choose an installed SDK from a pop-up menu, rather than entering or navigating to an SDK package in the Developer folder.

In .xcconfig files and when manually editing the SDKROOT build setting, you can enter either the full path to the SDK or its short name.

That means the $(SDKROOT) in your Xcode project will become some stupid short name like "macosx10.5", "iphoneos2.2.1" or "iphonesimulator2.2.1", etc., which doesn't even represent the actual directory path of the SDK.

Though $(DEVELOPER_SDK_DIR) does provide SDK path, it's "/Developer/SDKs/", not the iPhone SDK path (/Developer/Platforms/iPhoneOS.platform/Developers/SDKs) we want to deal with.

Apple should serious consider... either rearrange the SDK/Platform directory structure in Xcode, or just simply give us the $(PLATFORM_SDK_DIR) setting property.

Update:
This is in radar://6593370 now.

Copyright © Ching-Lan 'digdog' Huang 2009. All rights reserved.