Restricting Orientations in different view controllers

At some point you may want to restrict certain device orientations to certain view controllers. Its quite easy to do from your App Delegate.

First add a new variable to your AppDelegate.swift file:

   var allowableOrientations: UIInterfaceOrientationMask = .All

Next, implement the application:supportedInterfaceOrientationsForWindow function in the same file:

func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask {
    return allowableOrientations;
}

This will be called whenever you rotate the device to determine if that orientation is allowed. It doesn’t, however, force a rotation. We can implement another function in the same file to handle setting the allowableOrientations file and making sure the current orientation is one of those:

    func setAllowableOrientations(orientationMask:UIInterfaceOrientationMask,
                               defaultOrientation:UIInterfaceOrientation) {
        self.allowableOrientations=orientationMask;
        if ((orientationMask.rawValue & UInt(UIDevice.currentDevice().orientation.rawValue)) == 0) {
            UIDevice.currentDevice().setValue(defaultOrientation.rawValue, forKey: "orientation")
        }
    }

To use it, simply call this function from your View Controller’s viewDidLoad function, similar to this:

    (UIApplication.sharedApplication().delegate as! AppDelegate).setAllowableOrientations(.Landscape,defaultOrientation: .LandscapeRight)

That’s it!

Saving UIImage to the photo album.

The simplest way to save a UIImage to the photo album is with the UIImageWriteToSavedPhotosAlbum() function. The simplest way is by specifying the UIImage, and none of the other parameters:

    UIImageWriteToSavedPhotosAlbum(image, nil,nil, nil);

However, this will save the image as a jpg, which is not always ideal. If you want to save it as PNG, you have to first encode the image as a PNG, then make a new image based on that PNG so that the PNG data is attached to it, and then save the new image:

    NSData *data=UIImagePNGRepresentation(image);
    image=[UIImage imageWithData:data];
    UIImageWriteToSavedPhotosAlbum(image, nil,nil, nil);

You can also force JPEG format (with your own compression quality) by doing the same thing, but with the UIImageJPEGRepresentation() call:

    NSData *data=UIImageJPEGRepresentation(image,1.0); // best quality
    image=[UIImage imageWithData:data];
    UIImageWriteToSavedPhotosAlbum(image, nil,nil, nil);

Making your iOS OpenGLES view transparent

At some point you may want to make your OpenGL ES view transparent, so you can display something else behind it. (Maybe a camera view?) For this article, I’m going to assume you are using a Game project template from XCode 6.

In your GameViewController.m’s viewDidLoad function, just before the ‘[self setupGL];’ call, add the following lines:

    view.backgroundColor=[UIColor clearColor];
    
    CAEAGLLayer *eaglLayer=(CAEAGLLayer*)self.view.layer;
    eaglLayer.opaque=NO;

Also, in the glkView:drawInRect function, change the glClearColor line to be all zeroes:

    glClearColor(0.00f, 0.00f, 0.00f, 0.0f);

That should be it.

Debugging no-touch zones

When converting some iOS apps from iOS6 to iOS7, I came across a very strange bug. I had an app with tons of views and view controllers, and on the right side of the screen, touches wouldn’t be recognized. (For buttons, gestures, scrolling, etc.) It was quite obvious that one of the views frame’s wasn’t covering that part of the screen. The easy way to figure out which view it is is to add:

    self.view.clipsToBounds=true;

inside each viewDidLoad, verify that the dead zone is no longer visible, and then slowly remove the clipsToBounds calls one at a time until the dead zone is visible again, and you’ve found the offending view! Then take whatever steps you need to adjust that view properly.

In my case, a view had a frame (0,0+768,1024) for a landscape view. Changing this to (0,0+1024,768) resolved the issue. Oddly enough, either sized frame didn’t cause iOS6 to have issues, just iOS7.

Playing a full screen video in an android app the easy way.

The Android sdk has a MediaPlayer api that should make it easy to play videos. There is, however, a much easier way.

VideoView makes it super simple to play videos. Here’s how we use it.

The XML files

First, add a VideoView to the activities xml file. We’ll call it videoplayer.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <VideoView
        android:id="@+id/myvideoview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_gravity="center"
    />
</LinearLayout>

We set the LinearLayout to horizontal orientation in this case because my videos are all landscape. We set the layout’s to “fill_parent” so our VideoView takes up the whole parents view. We don’t want to stretch it out of proportion, but we do want the video centered, so we set the layout_gravity to “center”.

We also need to update the AndroidManifest.xml file to make sure our VideoPlayer activity is full screen (using theme), stays in landscape orientation (using screenOrientation) and only has one instance, so we don’t get a bunch of video players running. (launchMode)

    <activity
        android:name=".VideoPlayer" 
        android:label="@string/app_name"
        android:launchMode="singleTask"
        android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
        android:screenOrientation="landscape" ></activity>

Calling code

When we want to show a full screen video, we’ll do it by creating the intent for the VideoPlayer activity, grabbing the video resource id, pass it to the VideoPlayer activity, and start the activity:

private void playVideo(String resourceName) {
    Intent videoPlaybackActivity = new Intent(this, VideoPlayer.class);
    int res=this.getResources().getIdentifier(resourceName, "raw", getPackageName());
    videoPlaybackActivity.putExtra("fileRes", res);
    startActivity(videoPlaybackActivity);
}

The VideoPlayer Activity code

Now we can get to the VideoPlayer activity code. It turns out to be not that complex. Here’s VideoPlayer.java:

public class VideoPlayer extends Activity implements OnCompletionListener {
		   
    private VideoView mVV;
		 
    @Override
    public void onCreate(Bundle b) {
        super.onCreate(b);

        setContentView(R.layout.videoplayer);

        int fileRes=0;     
        Bundle e = getIntent().getExtras();
        if (e!=null) {
            fileRes = e.getInt("fileRes");
        }

        mVV = (VideoView)findViewById(R.id.myvideoview);
        mVV.setOnCompletionListener(this);
        mVV.setOnPreparedListener(this);
        mVV.setOnTouchListener(this);

        if (!playFileRes(fileRes)) return;

        mVV.start();
    }
		 
    private boolean playFileRes(int fileRes) {
        if (fileRes==0) {
            stopPlaying();
            return false;
        } else {
            mVV.setVideoURI(Uri.parse("android.resource://" + getPackageName() + "/" + fileRes));
            return true;
        }
    }

    public void stopPlaying() {
        mVV.stopPlayback();
        this.finish();		    	
    }

    @Override
    public void onCompletion(MediaPlayer mp) {
        finish();
    }
}

It’s pretty simple. We set the VideoView’s video URI to point to the resource id passed (if it exists), and start playing. If it doesn’t exist, we exit.

Playing new video files

Since we added the singleLaunch taskMode to the activity, that means that if we attempt to launch the VideoPlayer activity while its still running, onCreate() won’t be called, so it won’t know that its supposed to be playing a different video. In this case, the onNewIntent() function will be called with the new intent. We simply parse the extras bundle to get the file resource, if any, and it will play the new file:

    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);
        int fileRes = 0;
        Bundle e = getIntent().getExtras();
        if (e != null) {
            fileRes = e.getInt("fileRes");
        }
        playFileRes(fileRes);
    }

Making the video loop

We would also like our video to loop until we stop it. The best place to do this is once the MediaPlayer has been prepared. We add OnPreparedListener to the implements list of the VideoPlayer activity, and a simple onPrepared function that simply sets our MediaPlayer instance to looping:

public class VideoPlayer extends Activity implements OnCompletionListener,OnPreparedListener {
...
    @Override
    public void onPrepared(MediaPlayer mp) {
        mp.setLooping(true);
    }
...
}

Stopping at a Touch

We would also like to be able to stop the video with a tap on the screen, so we add OnTouchListener to the implements list of the VideoPlayer activity, and a simple onTouch function that simply calls our stopPlaying() function:

public class VideoPlayer extends Activity implements OnCompletionListener,OnPreparedListener,OnTouchListener {
...
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        stopPlaying();
        return true;
    }
...
}

Complete Java Source

Here’s the complete source, in case you missed any additions:

public class VideoPlayer extends Activity implements OnCompletionListener,OnPreparedListener,OnTouchListener {
		   
    private VideoView mVV;
		 
    @Override
    public void onCreate(Bundle b) {
        super.onCreate(b);

        setContentView(R.layout.videoplayer);

        int fileRes=0;     
        Bundle e = getIntent().getExtras();
        if (e!=null) {
            fileRes = e.getInt("fileRes");
        }

        mVV = (VideoView)findViewById(R.id.myvideoview);
        mVV.setOnCompletionListener(this);
        mVV.setOnPreparedListener(this);
        mVV.setOnTouchListener(this);

        if (!playFileRes(fileRes)) return;

        mVV.start();
    }
		 
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);
        int fileRes = 0;
        Bundle e = getIntent().getExtras();
        if (e != null) {
            fileRes = e.getInt("fileRes");
        }
        playFileRes(fileRes);
    }
		    
    private boolean playFileRes(int fileRes) {
        if (fileRes==0) {
            stopPlaying();
            return false;
        } else {
            mVV.setVideoURI(Uri.parse("android.resource://" + getPackageName() + "/" + fileRes));
            return true;
        }
    }

    public void stopPlaying() {
        mVV.stopPlayback();
        this.finish();		    	
    }

    @Override
    public void onCompletion(MediaPlayer mp) {
        finish();
    }
			
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        stopPlaying();
        return true;
    }

    @Override
    public void onPrepared(MediaPlayer mp) {
        mp.setLooping(true);
    }
}

Making a single-instance android app with Intents

If you have Intents that you want to react to in your android app, but you don’t want each new Intent to launch a new instance of your android app, you can make it single instance by adding the following to your main activity’s section in your AndroidManifest.xml:

  android:launchMode="singleInstance"

When you do this, instead of processing your intents in onCreate(), you process it in onNewIntent():

@Override
protected void onNewIntent(Intent intent) {
  // ... process the intent
}

No touches on bottom of iPhone 5 screen

If you ever convert an old project to work with the iPhone 5 (or future larger devices), you may encounter a situation where everything displays correctly on-screen, but for some reason, you’re not getting any touches in the lower 88 pixels. (The extra height of the iPhone 5 screen in X-Code.)

If you’re using a MainWindow.xib file, this is most likely caused because you haven’t selected ‘Full Screen At Launch’ for the Window, so it uses your original size for the window (480×640). (Even though it uses your original size for the window, the larger views will ‘leak’ and the entire screen will be filled, but any input on the lower section will be masked, so you won’t get events there.)

Adding Push notifications to your iOS app

At some point you’ll probably want to add Push notifications to your iOS app, be it for app updates, messages, etc. It’s actually pretty easy to do.

Creating/Updating the App ID For Push Notification

First, go into the provisioning portal, and click on ‘App IDs’ on the left under ‘Identifiers’. On the upper right corner click on the ‘+’ sign to get to the ‘Registering an AppID’ screen, fill in the App Id Description and App ID Suffix, using an Explicit App ID. (The app ID cannot have a wildcard (*) in its Bundle ID.) Under App Services, make sure ‘Push Notifications’ is checked. Click Continue, and on the ‘Confirm your App ID’ screen, click on ‘Submit’. Your new App ID should now be shown in the App ID’s list.

Click on the new App ID in the list. The ‘Push Notifications’ line should be ‘Configurable’. Click ‘Edit’ to get to the ‘iOS App ID Settings’ screen. Scroll down to ‘Push Notifications’ and click ‘Create Certificate’ under the appropriate section.

You’ll now see the ‘About Creating a Certificate Signing Request (CSR)’ screen. It will tell you to launch Keychain Access under Applications/Utilities, and select Keychain Access->Certificate Assistant->Request a Certificate from a Certificate Authority. You then fill in your email address and create a name for your private key. In the Request is group, you select ‘Save to disk’ option, and click continue. You’ll be prompted for the location and name of your CertificateSigningRequest.

Once you’ve done as requested, hit ‘Continue’ on the ‘About Creating a Certificate Signing Request (CSR)’ screen, and you’ll be in the ‘Generate your certificate’ screen. Click on ‘Choose File’ and select the CertificateSigningRequest file you just created in Keychain Access and click Generate.

After up to a minute, you’ll get the message saying that your certificate is ready. Click on ‘Download’. When you’ve downloaded it, click ‘Continue’ and you’ll get back to the Configure App ID screen. Hit ‘Done’ at the bottom to save your new push notification settings.

Double click on the APNs SSL Certificate to install it into your keychain. Once its installed, export it as ‘PushCertificate.p12’. (No password.) We’ll need it later.

Creating/Updating a provisioning profile

You can’t use your wildcard provisioning profile for push notifications; you have to use one specifically for push notifications for the given App ID. Once again, go into your provisioning portal to start.

Once there, click on ‘Provisioning’ on the left hand side. If you have a provisioning profile with the same app ID as above, Edit/Modify it; if not, create a new profile with that app ID, calling it something like ‘MyApp Push’. Once you’re done, make sure to Download the new provioning profile and select it as the current development code signing identity. If you don’t do this, you’ll get “no valid ‘aps-environment’ entitlement string found for application” messages when you try to run it.

Registering for notifications

Now that you’ve got the correct provisioning profile installed and in use, you’ll need to add code to register your app for push notifications.

In your app delegate’s application:didFinishLaunching or application:didFinishLuanchingWithOptions code, add the following code:

   [[UIApplication sharedApplication] registerForRemoteNotificationTypes:
               UIRemoteNotificationTypeBadge |
               UIRemoteNotificationTypeAlert |
               UIRemoteNotificationTypeSound];

Remove any notification types you don’t want to receive.

You’ll also need to add Push Notification callback code in your app delegate:

#pragma mark = Push Notification code

- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
   NSLog(@"did register, deviceToken: %d bytes, %@", devToken.length,devToken);

   // Add code here to send the device token to your server, probably converting to hex first
   [self sendDevToken:devToken toURL:@"http://www.example.com/register.php"];
}

- (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {
   NSLog(@"Error in registration. Error: %@", err);

   // deal with the error here
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
   NSLog(@"Got notification: %@",userInfo);
   
   // notify the user, etc about the notification just received
}

The easiest way to send a device token to your server to register your device is to convert the devToken to a hex string, and pass it as a parameter to a php script on a website. (ie http://www.example.com/register.php?key=) It would also be a good idea to spawn a thread to do this so you’re not blocking the user input while registering.

Your app should now be registering for notifications with Apple and registered their deviceToken on your server.

Server side registration

On your PHP server, you’ll want to receive the deviceToken and store it for later use. (When you want to notify the user of something.) We can start with something like this in our register.php file to make sure we’re getting registrations:

<?php

$key=$_GET["key"];

// We should store in db for later use, but we'll just log it for now.
$fp=fopen("register.log","a");
fwrite($fp,date("YmdHis")." $key\n");
fclose($fp);

?>

Run the app to verify registration

Now that you’ve set your app up to register, and your server set up to receive the registrations, try it out, and if all’s well, you should get an entry in a ‘register.log’ file. Make sure to accept push notifications!

Pushing notifications from your server

Now that we’re registered, we need to get our server to talk to apple’s server to send our registered devices push notifications. This is where the PushCertificate.p12 file we created early comes into play. On your linux php server, we need to create a cert/key file that’s used to communicate securely with the apple push server. Upload the PushCertificate.p12 file to your server and create the ck.pem file as follows:

$ openssl pkcs12 -clcerts -nokeys -out cert.pem -in PushCertificate.p12 
Enter Import Password:
MAC verified OK

$ openssl pkcs12 -nocerts -out key.pem -in PushCertificate.p12 
Enter Import Password:
MAC verified OK
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:

$ cat cert.pem key.pem > ck.pem

Make sure that you enter the same passphrase both times. (This will be used in the push code below.)

Now that we have a cert/key file, and a devToken, we can send our device a push notification with the following (replace with the passphrase you used above.):

<?php
$msg="<Your notification message>";
$pushToken= "<your captured hex push token>";
$pushToken=pack("H*",$pushToken);

send_apns($pushToken,$msg,1,'');

function send_apns($token,$message,$badge,$sound) {
   // Construct the notification payload
   $body = array();
   $body['aps'] = array('alert' => $message);
   if ($badge) $body['aps']['badge'] = $badge;
   if ($sound) $body['aps']['sound'] = $sound;

   $ctx = stream_context_create();
   stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');
   stream_context_set_option ($ctx, 'ssl', 'passphrase', '<password>');
   $fp = stream_socket_client('ssl://gateway.sandbox.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
   // for production change the server to ssl://gateway.push.apple.com:2195
   if (!$fp) {
      print "Failed to connect $err $errstr\n";
      return -1;
   }

   $payload = json_encode($body);
   $msg = chr(0) . pack("n",32) . $token . pack("n",strlen($payload)) . $payload;
   fwrite($fp, $msg);
   fclose($fp);

   return 0;
}
?>

At this point you should hopefully have a push notification sent to your device. As noted in the code, change the server as appropriate if you’re in production.

What to do when creating a PDF crashes your iOS device.

If you create PDF’s using the iOS SDK, and UIKit as mentioned in my previous post, you may find that while the PDF’s generate perfectly on the simulator, they crash your app on a real device. The reason it works on the simulator and not on the iOS device, usually has something to do with how much memory each has. The simulator has as much as your development machine, while the iOS device has… well, considerably less.

If you find your app crashing, what I’ve found helps the most is slicing your large images into smaller images to remove the whitespace in your images. When you create a PDF using UIKit, each image is loaded into memory as RGBA to be pasted into the PDF. If you’re using the x4 method to get high resolution PDF’s, that means a full page ‘background’ would take up almost 8 MB. Much of this is waste, since you’re probably going to have quite a bit of whitespace.