Wednesday, August 25, 2010

Using the External VGA Connector

One of the things that we wanted to do was plug in the external VGA adapter to a projector and demo the app to a room of people.  Sounds simple right?  Well, not really.

When using the external VGA Adapter, the device as two windows that you must add separate views to.  After an entire day attempting to capture all events from one window and attempting to pass them to the other window, I decided to take a more brute force method.  Take a screen capture every 1/4 second and display it on the external display.

The steps are below.

1. Create a UIViewController with a XIB and call it ExternalDisplayViewController.
2. In Interface Builder add a single UIImageView and set the View Mode to Aspect Fit.
3. Add a property to the ExternalDisplayViewController.h.  Don't forget to "Wire" it up in interface builder.
4. Modify the AppDelegate.h file accordingly.

#import <UIKit/UIKit.h>

@class ExternalDisplayViewController;

@interface MyAppDelegate : NSObject <UIApplicationDelegate, UIAlertViewDelegate> {
    UIWindow *deviceWindow;
 
 NSArray *screenModes;
 UIScreen *externalScreen;
 
 ExternalDisplayViewController *externalVC;

}
@property (nonatomic, retain) NSTimer *repeatingTimer;

@property (nonatomic, retain) IBOutlet UIWindow *deviceWindow;
@property (nonatomic, retain) IBOutlet UIWindow *externalWindow;

- (void) takeCapture:(NSTimer*)theTimer;

@end

5. Modify the MainWindow.xib file.  Add a second window (that will dsplayed on the external display).  Don't forget to "Wire" two windows to the new properties.

6. Add the Quartz Framework to your project and add the import line to the AppDelegate.m file.

#import <QuartzCore/QuartzCore.h>

7. Add the following code to your AppDelegate.m didFinishLaunchingWithOptions
if ([[UIScreen screens] count] > 1) {
    
   //[self log:@"Found an external screen."];
   
   // Internal display is 0, external is 1.
   externalScreen = [[[UIScreen screens] objectAtIndex:1] retain];
   //[self log:[NSString stringWithFormat:@"External screen: %@", externalScreen]];
   
   screenModes = [externalScreen.availableModes retain];
   //[self log:[NSString stringWithFormat:@"Available modes: %@", screenModes]];
   
   // Allow user to choose from available screen-modes (pixel-sizes).
   UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:@"External Display Size" 
            message:@"Choose a size for the external display." 
            delegate:self 
            cancelButtonTitle:nil 
            otherButtonTitles:nil] autorelease];
   for (UIScreenMode *mode in screenModes) {
    CGSize modeScreenSize = mode.size;
    [alert addButtonWithTitle:[NSString stringWithFormat:@"%.0f x %.0f pixels", modeScreenSize.width, modeScreenSize.height]];
   }
   [alert show];
   
}

8. Add the following code to your AppDelegate.m file.

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
 UIScreenMode *desiredMode = [screenModes objectAtIndex:buttonIndex];
 externalScreen.currentMode = desiredMode;
 externalWindow.screen = externalScreen;
 
 [screenModes release];
 [externalScreen release];
 
 CGRect rect = CGRectZero;
 rect.size = desiredMode.size;
 externalWindow.frame = rect;
 externalWindow.clipsToBounds = YES;
 
 externalWindow.hidden = NO;
 [externalWindow makeKeyAndVisible];
 
 externalVC = [[ExternalDisplayViewController alloc] initWithNibName:@"ExternalDisplayViewController" bundle:nil];
 CGRect frame = [externalScreen applicationFrame];
 switch(externalVC.interfaceOrientation){
  case UIInterfaceOrientationPortrait:
  case UIInterfaceOrientationPortraitUpsideDown:
   [externalVC.view setFrame:frame];
   break;
  case UIInterfaceOrientationLandscapeLeft:
  case UIInterfaceOrientationLandscapeRight:
   [externalVC.view setFrame:CGRectMake(frame.origin.x, frame.origin.y, frame.size.height, frame.size.width)];
   break;
 }
 
 [externalWindow addSubview:externalVC.view];
 
 NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.25
   target:self selector:@selector(takeCapture:)
   userInfo:nil repeats:YES];
 self.repeatingTimer = timer;
}

- (void) takeCapture:(NSTimer*)theTimer{
 UIView *mainView = [deviceWindow.subviews objectAtIndex:0];
 
 if (mainView) {
  UIGraphicsBeginImageContext(mainView.frame.size);
  [mainView.layer renderInContext:UIGraphicsGetCurrentContext()];
  UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
  
  [externalVC.imgView setImage:viewImage];
  UIGraphicsEndImageContext();
 }
 
}




8 comments:

  1. Hi,
    I implemented same in my application. It worked. Thanks a lot :)

    ReplyDelete
  2. Hey I dont have and xib in my project. How to get this to work ?

    ReplyDelete
  3. hi! Thank you for posting this!!

    could you help me with this? I can't seem to get the wiring correct. How woud I wire these?

    Also:

    1.) in step 2, is the interface builder file the one that is ourappname.xib? I think I just don't understand the wiring part.

    thank you!

    ReplyDelete
  4. Hi,
    It does look great. Can we implement this in a app aimed at running on iPhone+iPad, but just when it runs on iphone, that feature is just ignored by the app ?

    ReplyDelete
  5. Nice job, works great except for all the overlays (keyboards, modal windows) that are not mirrored.
    The solution is simple and is described here :
    http://developer.apple.com/library/ios/#qa/qa2010/qa1703.html

    ReplyDelete
  6. Thanks man, this really helped me. I needed to do the same thing, hook up the iPad to a projector and show the application to a room full of people. Your solution worked great, thanks again.

    ReplyDelete
  7. but...
    [externalVC.imgView setImage:viewImage];line at
    imgView is code error.

    ReplyDelete
  8. Thanks a lot for the post.. It helps a lot. I needed to see the VGA output when ipad is Landscape screen. How to achive this.

    ReplyDelete