Archive for February, 2010
Implementing Apple push notifications into your iPhone/iPod Touch or iPad app can be quite the challenging task. You really have to pay close attention to the official docs when setting it up and even then it might not work right out of the box.
If you’re new to APNS, you might want to check out the tutorials, I mentioned in one of my previous posts:
http://blog.boxedice.com/2009/07/10/how-to-build-an-apple-push-notification-provider-server-tutorial/
and
http://mobiforge.com/developing/story/programming-apple-push-notification-services
I can only repeat myself by saying, that these tutorials really rock and should definitely get you started.
If you don’t plan on running your own server, check out Urban Airship. They currently offer two different plans, which should perfectly fit your needs.
However, implementing APNS on your own server is most fun, as you’re likely going to face some issues
Rule #1, if nothing you tried so far works: Get some open source package and learn from the professionals: EasyAPNS is exactly what you might want to check out in this kinda situation.
Don’t forget to watch the video tutorial, which can be found right on the landing page.
Some additional hints and suggestions:
If your iPhone/iPod Touch/iPad app communicates with your server (maybe via php scripts), check out ASIHTTPRequest. This is some awsome package, that you can easily integrate into your existing XCode project. It’s perfect for contacting your server via POST requests. However, ASIHTTPRequest can do much more – just check out the official docs, which can be found on the developer’s website.
If you’re testing push notifications with your beta testers, make sure that you’re using the correct APNS certificate on your server. By correct I mean: Development certificate or Production certificate. In case you followed the tutorials above, you probably created both. In order to clear things up, check out the steps below:
- If your iPhone/iPod Touch/iPad app communicates with your server (maybe via php scripts), check out ASIHTTPRequest. This is some awsome package, that you can easily integrate into your existing XCode project. It’s perfect for contacting your server via POST requests. However, ASIHTTPRequest can do much more – just check out the official docs, which can be found on the developer’s website.
- If you’re testing push notifications with your beta testers, make sure that you’re using the correct APNS certificate on your server. By correct I mean: Development certificate or Production certificate. In case you followed the tutorials above, you probably created both. In order to clear things up, check out the steps below:
- Find your Provisioning certificate and the AdHoc one, you are using for your beta testers: Open up XCode and go to “Window”->”Organizer”. Now click on your Provisioning certificate, then right click -> “Reveal in Finder”. Do the same for your AdHoc certificate.
- Open up both files with TextEdit (DON’T EVER CHANGE ANYTHING IN THERE!!!) and look for the following string: “Environment”.
- Right below you should see either “Development” or “Production”.
- If you want your beta testers to receive push notifications and their AdHoc certificate says “Production”, you now know that you will have to use the APNS Production certificate on your server. Otherwise push messages won’t get delivered.
Happy pushing ![]()
NSFileManager offers a convenient way to write images to and load them from the documents directory.
If you’re frequently doing that in your project, I suggest to wrap up NSFileManager support in three simple methods:
//saving an image
- (void)saveImage:(UIImage*)image:(NSString*)imageName {
NSData *imageData = UIImagePNGRepresentation(image); //convert image into .png format.
NSFileManager *fileManager = [NSFileManager defaultManager];//create instance of NSFileManager
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); //create an array and store result of our search for the documents directory in it
NSString *documentsDirectory = [paths objectAtIndex:0]; //create NSString object, that holds our exact path to the documents directory
NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.png", imageName]]; //add our image to the path
[fileManager createFileAtPath:fullPath contents:imageData attributes:nil]; //finally save the path (image)
NSLog(@"image saved");
}
//removing an image
- (void)removeImage:(NSString*)fileName {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.png", fileName]];
[fileManager removeItemAtPath: fullPath error:NULL];
NSLog(@"image removed");
}
//loading an image
- (UIImage*)loadImage:(NSString*)imageName {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.png", imageName]];
return [UIImage imageWithContentsOfFile:fullPath];
}
Now, you can easily save an image like:
[self saveImage: myUIImage: @"myUIImageName"];
or load it like:
myUIImage = [self loadImage: @"myUIImageName"];
or remove it like:
[self removeImage: @"myUIImageName"];
Usually, iPhone apps are built using some kinda hierarchy:
- ->AppDelegate
- ->-> RootViewController
- ->->->OtherViewController(s)
- ->->->->Views
On various occasions, you might want to get some information from your AppDelegate (e.g. values, array infos, etc…).
You might already know, that there is a simple line of code, which gives you access to your AppDelegate:
AppDelegate *mainDelegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
If you need access to your AppDelegate only once or twice in a View or ViewController, this is the best way to go. However, if you have several methods in your View or ViewController and some of them need AppDelegate access, you always have to repeat the above line of code first.
In order to avoid that, there is a simple way, to permanently implement AppDelegate access in your View or ViewController:
//this example assumes, that we are using a ViewController
//inside .h file
//below #import lines
@class AppDelgate;
@interface MyViewController : UIViewController {
AppDelegate *mainDelegate;
}
//@property (nonatomic, retain) AppDelegate *mainDelegate; //error correction here - no need to @property
@end
//inside .m file
//below #import lines
#import "AppDelegate.h"
@implementation MyViewController
@synthesize mainDelegate;
- (void)viewDidLoad {
mainDelegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
}
- (void)dealloc {
//[mainDelegate release]; //error correction here - the mainDelegate object does not need to be released, since we do not call alloc init on it.
[super dealloc];
}
//now you have access to your AppDelegate in every method
//example: we assume, that our AppDelegate holds three different integer values we want to grab
- (void)methodA {
int numberA = mainDelegate.numberA;
}
- (void)methodB {
int numberB = mainDelegate.numberB;
}
- (void)methodC {
int numberC = mainDelegate.numberC;
}
As you can see, it’s slightly more work to implement AppDelegate access than just using that single line of code over and over again. However, in my opinion it’s well worth the time, especially if your .m file holds many methods.
Your code appears much cleaner that way.
[IMPORTANT: The information below is no longer up to date. Apple does now no longer allow you to use UIGetScreenImage(). Instead you need to use a different method. You can read about that on the official developer forum, located at developer.apple.com]
Not too long ago Apple decided to make on of their private APIs public. They now officially allow developers to make use of a very handy method:
+ (UIImage *)imageWithScreenContents;
Before they released it, grabbing screen contents could be a real nightmare. In some (rare) situations, it was pretty much impossible.
When you were using lots of openGL stuff in your project and wanted to grab the current state of your screen, you had to use glReadPixels();
This worked fine most of the time, but when you were dealing with transparency, it could make you pull your hair off
Anyways, those times are finally over.
To make use of this API, simply add the following lines to the .m file where you want to call it later:
//right below #import lines
CGImageRef UIGetScreenImage();
@interface UIImage (ScreenImage)
+ (UIImage *)imageWithScreenContents;
@end
@implementation UIImage (ScreenImage)
+ (UIImage *)imageWithScreenContents
{
CGImageRef cgScreen = UIGetScreenImage();
if (cgScreen) {
UIImage *result = [UIImage imageWithCGImage:cgScreen];
CGImageRelease(cgScreen);
return result;
}
return nil;
}
@end
Now you can easily do something like this:
- (void)saveScreenshotToPhotolibrary {
UIImageWriteToSavedPhotosAlbum([UIImage imageWithScreenContents], nil, nil, nil);
}
Congrats! You just saved an image with screen contents to your iPhone photo library.
-(int)getRandomNumber:(int)from to:(int)to {
return (int)from + arc4random() % (to-from+1);
}
How to use:
1) Implement method above into your .m file
2) Add the following line to your .h file:
-(int)getRandomNumber:(int)from to:(int)to;
3) Call the method like:
int randomNumber = [self getRandomNumber:9 to:99]; //this gets you a random number between 9 and 99
The following script connects to a mailbox, reads an email, fetches some important data and then deletes the email.
This is probably useful, if you want to automatically empty your mailbox on a regular basis and store data in a database.
<?
$mailbox = imap_open("{localhost:143/notls}INBOX", "your@emailaddress.com", "mailboxPassword"); //connects to mailbox on your server
if ($mailbox == false) {
echo "<p>Error: Can't open mailbox!</p>";
echo imap_last_error();
}
else {
//Check number of messages
$num = imap_num_msg($mailbox);
//if there is a message in your inbox
if( $num > 0 ) { //this just reads the most recent email. In order to go through all the emails, you'll have to loop through the number of messages
$email = imap_fetchheader($mailbox, $num); //get email header
$lines = explode("\n", $email);
// data we are looking for
$from = "";
$subject = "";
$to = "";
$splittingheaders = true;
for ($i=0; $i < count($lines); $i++) {
if ($splittingheaders) {
// this is a header
$headers .= $lines[$i]."\n";
// look out for special headers
if (preg_match("/^Subject: (.*)/", $lines[$i], $matches)) {
$subject = $matches[1];
}
if (preg_match("/^From: (.*)/", $lines[$i], $matches)) {
$from = $matches[1];
}
if (preg_match("/^To: (.*)/", $lines[$i], $matches)) {
$to = $matches[1];
}
}
}
//We can just display the relevant information in our browser, like below or write some method, that will put that information in a database
echo "FROM: ".$from."<br>";
echo "TO: ".$to."<br>";
echo "SUBJECT: ".$subject."<br>";
echo "BODY: ".imap_qprint(imap_body($mailbox, $num));
//delete message
imap_delete($mailbox,$num);
imap_expunge($mailbox);
}
else {
echo "No more messages";
}
imap_close($mailbox);
}
?>
If you’re an iPhone, iPod Touch or iPad developer, you might have heard about Apple’s push notification service. It’s a convenient way to send messages to your users or even allow them to communicate with each other (via your server). You can find great tutorials on the internet, that show you how you can get started. I can especially recommend:
http://blog.boxedice.com/2009/07/10/how-to-build-an-apple-push-notification-provider-server-tutorial/
and
http://mobiforge.com/developing/story/programming-apple-push-notification-services
I admit, it really depends on the scale of your service and the possible message load, but you might wanna pay close attention to the following statement, which can be found in the official Apple docs:
Refrain from opening and closing the connections to the APNs for each push notification that you want to send. Rapid opening and closing of connections to the APNs will be deemed as a Denial-of-Service (DOS) attack and may prevent your provider from sending push notifications to your applications.
What does this mean?
It means, that you should retain an open connection to APNS and not open/close it every time you send a message. Most php scripts out there dealing with APNS exactly do that! They open a connection, send a single message or a couple of messages from your database and then close the connection. As I already mentioned, it depends on how you are using APNS. If you send messages every hour or every couple of hours, you should be fine and there is probably nothing to worry about.
However, if you are rather looking for a dynamic way of pushing (user to user via your server), you of course wanna avoid putting all messages in a queue and sending them out every hour.
What you need in this case, is something a bit more advanced.
[I don't claim for this solution to be the best one - it certainly is not - but it GET'S THE JOB DONE]
So, let’s take a look at it, shall we?
1) We need to create an empty php script. You can do this in a text editor or whatever php editor you prefer. Name it PushScript.php or give it some other meaningful name.
<?
set_time_limit(0); //Keep process running forever...
$timeNow = strtotime("now"); //get current time and time + 2 hours. I read, that an idle connection might time out after 2 hours, so we will later use this information to re-connect
$inTwoHours = strtotime("+ 2 hours");
First, we set time limit to 0. This makes sure, that the php script does not accidentally time out.
Next, we get the current time and another time stamp (+2 hours). We will later use this to close connection to APNS and re-open it.
Why are we doing that?
Well, I read on several blogs, that Apple might close an idle connection after some time. Some people report, that it can actually stay open for several days, but keeping it down to 2 hours, will most likely give us the least trouble.
//connect to APNS - ONLY 1 time after script is started
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'nameOfDevCertificateInSameFolder.pem');
$apnsConnection = stream_socket_client('ssl://gateway.sandbox.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
error_log(date('Y-m-d H:i')." - Successfully connected to APNS", 3, 'PushLog.log'); //log, that we are successfully connected - used for debugging
The script runs from top to bottom and when we call it we wanna make sure, that our server makes a connection to APNS.
//let's start our "deamon"
while (true) {
//here we are sending a single message to a single device every 60 seconds. In theory you could run some mySQL query to empty a queue.
$deviceToken = '************************************************'; // your device token here
$message = 'Hi, I'm a push message!';
$badge = (int)$argv[2];
$sound = 'soundFile.caf'; //name of the sound file inside the XCode project.
// Construct the notification payload
$body = array();
$body['aps'] = array('alert' => $message);
if ($badge)
$body['aps']['badge'] = $badge;
if ($sound)
$body['aps']['sound'] = $sound;
$payload = json_encode($body);
$msg = chr(0) . pack("n",32) . pack('H*', str_replace(' ', '', $deviceToken)) . pack("n",strlen($payload)) . $payload;
error_log(date('Y-m-d H:i')." - Pushing message to APNS", 3, 'PushLog.log'); //another log entry
fwrite($apnsConnection, $msg); //this pushes the message to APNS
This is actually our “deamon”. It will loop and loop and because we set the time limit to 0, this process won’t get killed. Look at the code comments, it’s pretty much self explanatory.
//now let's close and re-open connection every 2 hours
if (strtotime("now") >= $inTwoHours) {
$timeNow = strtotime("now");
$inTwoHours = strtotime("+ 2 hours");
//close APNS connection
fclose($apnsConnection);
error_log(date('Y-m-d H:i')." - Closing connection to APNS", 3, 'PushLog.log');
//re-open APNS connection
//connect to APNS - ONLY 1 time after script is started
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'apns-dev.pem');
// assume the private key passphase was removed.
// stream_context_set_option($ctx, 'ssl', 'passphrase', $pass);
$apnsConnection = stream_socket_client('ssl://gateway.sandbox.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
error_log(date('Y-m-d H:i')." - Reconnecting to APNS", 3, 'PushLog.log');
}
Now, we’re using the time stamps, we created earlier. In case our old time stamp has expired (script ran longer than 2 hours), we renew it.
Finally:
sleep(60); // loop every X seconds ?>
Pause the loop for X seconds, until we start it again.
That’s pretty much it ![]()
When reading the code, you might have noticed, that we’re sometimes writing information to PushLog.log. I can highly recommend this. It will show you how many times you actually connect/disconnect and send a message. This way, you can see what your script is doing and that it does, what we want it to do.
In order to get this up and running, create the php file and the PushLog.log file (simple text file – just make sure, it has a .log suffix, instead of .txt).
Put both files, plus your push certificate into the same folder on your webserver and you’re good to go. Now, you only need to call the php script from your browser or terminal and you got your very own php deamon.
Hope, this is helpful. In case, you can’t get it to work, make sure the solution from the tutorials above works. Then use my script.
Happy pushing ![]()
[Code posted without any warranty whatsoever];
