iOS AirPlay Second Screen Tutorial - ios

I am looking at adding AirPlay capabilities to one of my ViewControllers. The View Controller just shows a UIWebView. What I want to do is add a button that will mirror this content to an Apple TV. I know system-wide mirroring can be done, but it doesn't fill up the entire screen, has black bars all around. I have been searching online, but most everything I have found is way back from iOS 5 and out of date. Could someone point me in the direction of a tutorial or drop-in library that would help out? I just need it to mirror the content of just one view to be full-screen on Apple TV.
So far, here is what I have done, but I believe it only creates the second Window, without putting anything on it.
In the AppDelegate, I create a property for it:
#property (nonatomic, retain) UIWindow *secondWindow;
In didFinish method of AppDelegate I run:
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(handleScreenDidConnectNotification:)
name:UIScreenDidConnectNotification object:nil];
[center addObserver:self selector:#selector(handleScreenDidDisconnectNotification:)
name:UIScreenDidDisconnectNotification object:nil];
Then in AppDelegate I have:
- (void)handleScreenDidConnectNotification:(NSNotification*)aNotification
{
UIScreen *newScreen = [aNotification object];
CGRect screenBounds = newScreen.bounds;
if (!self.secondWindow)
{
self.secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
self.secondWindow.screen = newScreen;
// Set the initial UI for the window.
}
}
- (void)handleScreenDidDisconnectNotification:(NSNotification*)aNotification
{
if (self.secondWindow)
{
// Hide and then delete the window.
self.secondWindow.hidden = YES;
self.secondWindow = nil;
}
}
In the viewController in which I would like to allow to mirror the WebView on Apple TV, I have:
- (void)checkForExistingScreenAndInitializeIfPresent
{
if ([[UIScreen screens] count] > 1)
{
// Get the screen object that represents the external display.
UIScreen *secondScreen = [[UIScreen screens] objectAtIndex:1];
// Get the screen's bounds so that you can create a window of the correct size.
CGRect screenBounds = secondScreen.bounds;
appDelegate.secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
appDelegate.secondWindow.screen = secondScreen;
// Set up initial content to display...
// Show the window.
appDelegate.secondWindow.hidden = NO;
NSLog(#"YES");
}
}
I got all this from here. However, that's all that it shows, so I'm not sure how to get the content onto that screen.

Depending on what’s going on in your web view, you’ll either have to make a second one pointed at the same page or move your existing one to the new window. Either way, you treat the second window pretty much the same as you do your app’s main window—add views to it and they should show up on the second display.

I assume you've seen it, but this is the only sample project I could find: https://github.com/quellish/AirplayDemo/
Here are some related questions that might be worth reading:
does anyone know how to get started with airplay?
Airplay Mirroring + External UIScreen = fullscreen UIWebView video playback?
iOS AirPlay: my app is only notified of an external display when mirroring is ON?
Good luck!

There are only two options to do Airplay 'mirroring' at the moment: the system-wide monitoring and completely custom mirroring. Since the system-wide mirroring is not a solution for you, you'll have to go down the way you already identified in your code fragments.
As Noah pointed out, this means providing the content for the second screen, the same way as providing it for the internal display. As I understand you, you want to show the same data/website as before on the internal display, but display it differently in the remote view/webview (e.g. different aspect ratio). One way can be having one webview follow the other in a master/slave setup. You'd have to monitor the changes (like user scolling) in the master and propagate them to the slave. A second way could be rendering the original webview contents to a buffer and drawing this buffer in part in a 'dumb' UIView. This would be a bit faster, as the website would not have to be loaded and rendered twice.

Related

Is Apple using black magic to accomplish camera's preview orientation?

I have this AVFoundation camera app of mine. The camera preview is the result of a filter, applied by didOutputSampleBuffer method.
When I setup the camera I am following what apple did on one of their sample codes (CIFunHouse):
// setting up the camera
CGRect bounds = [self.containerOpenGL bounds];
_eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
_videoPreviewView = [[GLKView alloc] initWithFrame:bounds
context:_eaglContext];
[self.containerOpenGL addSubview:_videoPreviewView];
[self.containerOpenGL sendSubviewToBack:_videoPreviewView];
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
NSDictionary *options = #{kCIContextUseSoftwareRenderer : #(NO),
kCIContextPriorityRequestLow : #(YES),
kCIContextWorkingColorSpace : [NSNull null]};
_ciContext = [CIContext contextWithEAGLContext:_eaglContext options:options];
[_videoPreviewView bindDrawable];
_videoPreviewViewBounds = CGRectZero;
_videoPreviewViewBounds.size.width = _videoPreviewView.drawableWidth;
_videoPreviewViewBounds.size.height = _videoPreviewView.drawableHeight;
dispatch_async(dispatch_get_main_queue(), ^(void) {
CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI);
_videoPreviewView.transform = transform;
_videoPreviewView.frame = bounds;
});
self.containerOpenGL is a full screen view and is constrained to the four corners of the screen. Autorotation is on.
But this is the problem...
When I setup the GLKView and self.ciContext it is created assuming the device is on a particular orientation. If the device is on a particular orientation and I run the application, the previewView will fit the entire self.containerOpenGL area but when I rotate the device the previewView will be out center.
I see that Apple code works perfectly and they don't use any constraints. They do not use any autorotation method, no didLayoutSubviews and when you rotate the device, running their code, everything rotates except the preview view. And worse than that, my previewView appears to rotate but not their's.
Is this black magic? How do I they do that?
They add their preview view to a uiwindow and that is why it does not rotate. I hope this answers the question. If not I will continue to look through their source code.
Quote from source code.
we make our video preview view a subview of the window, and send it to the back; this makes FHViewController's view (and its UI elements) on top of the video preview, and also makes video preview unaffected by device rotation
They also add this
_videoPreviewView.enableSetNeedsDisplay = NO;
This may keep it from responding as well
Edit: It appears that now the preview rotates and the UI does as well so to combat this you can add a second window and send it to the back and make the main window clear and add the previewView to the second window with a dummyViewController that tells it not to autorotate by overriding the appropriate method. This will allow the preview to not rotate but the UI to rotate.

How to differentiate apple tv and other devices in airplay?

I am trying to connect apple tv through airplay, but the issue is some time if i connect any other external device like bluetooth or some other device it shows like device connected in window. So i want to identify which device is connected i have to enable only when apple tv is connected.
How can i identify whether it is apple tv or some other device?
This how i create airplay custom button
for (UIButton *button in volumeView.subviews) {
if ([button isKindOfClass:[UIButton class]]) {
self.airplayButton = (UIButton*)button;
button.frame = CGRectMake(0, 0, 30, 23);
button.backgroundColor = [UIColor clearColor];
[self.airplayButton addObserver:self forKeyPath:#"alpha" options:NSKeyValueObservingOptionNew context:nil];
}
}
So the alpha always changes for button even some other devices gets connected.
I've had a look into this before, there's no easily provided way of determining whether the attached device is an Apple TV, there is a Airplay Picker which does this but the code/functions behind it don't seem to be available.
The best you can do it monitor for additional screens being added/removed and then showing your external content only when the screen has the capabilities to do what you need.
I have read somewhere previously that you can get the capabilities of an airplay device and use this information to detect an Apple TV but unfortunately I cannot find it at the moment. If I do find it I'll add a comment.
For now, your best option would be to use the concepts described in this guide
The code provided is in objective-c but its very easily converted to swift, here is the main part you should be looking at
- (void)checkForExistingScreenAndInitializeIfPresent
{
if ([[UIScreen screens] count] > 1)
{
// Get the screen object that represents the external display.
UIScreen *secondScreen = [[UIScreen screens] objectAtIndex:1];
// Get the screen's bounds so that you can create a window of the correct size.
CGRect screenBounds = secondScreen.bounds;
self.secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
self.secondWindow.screen = secondScreen;
// Set up initial content to display...
// Show the window.
self.secondWindow.hidden = NO;
}
}
Like I said you can code this so that it checks the device supports certain resolutions so you can rule out devices that wont support your UI
Some additional resources: https://developer.apple.com/airplay/

Re-enable mirroring on iOS

In my iOS app I need to display custom content on external display (using AirPlay) as well as mirroring some screens on TV.
For presenting custom content I use code from Multiple Display Programming Guide for iOS and it works well: while my iPad is in 'mirror' AirPlay mode I'm able to show some stuff on the TV. However, documentation says6
To re-enable mirroring after displaying unique content, simply remove the window you created from the appropriate screen object.
And this part isn't working at all. I just cannot destroy my window that I use to display content on external screen. Here's the code:
- (void) destroySecondWindow{
if (secondWindow){
for( UIView* view in secondWindow.subviews ){
[view removeFromSuperview];
}
secondWindow.backgroundColor = [UIColor clearColor];
secondWindow.hidden = YES;
// Hide and then delete the window.
[secondWindow removeFromSuperview];
secondWindow = nil;
}
}
As far as unique content should be displayed only when one particular view controller is visible, I'm trying to destroy external window like this:
- (void) viewWillDisappear:(BOOL)animated{
[self destroySecondWindow];
}
Here's how I create second window:
- (void) createSecondWindowForScreen:(UIScreen*)screen{
if( screen == nil || secondWindow != nil ){
return;
}
CGRect screenBounds = screen.bounds;
secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
secondWindow.backgroundColor = [UIColor blueColor];
secondWindow.screen = screen;
[secondWindow setHidden:NO];
}
So the question is: does anybody know how to re-enable screen mirroring after displaying unique content on TV?
Thanks in advance!

Cropping/zooming not working while setting iOS Wallpaper using PhotoLibrary private framework

I have managed (with the help of this post) to open up a PLStaticWallpaperImageViewController from the PhotoLibrary private framework, which allows the direct setting of the wallpaper and lock screen (using same UI as the Photos app). Unfortunately, the image cropping/zooming features don't seem to work, as touches to the image view itself don't seem to be coming through (the main view is also not dismissed properly after the cancel/set buttons are touched, but this isn't so important).
I have an Xcode project demonstrating the wallpaper setting (can be run in simulator as well as a non-jailbroken device):
https://github.com/newenglander/WallpaperTest/
The code is quite basic, and involves a ViewController inheriting from PLStaticWallpaperImageViewController and implementing an init method similar to the following:
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [self initWithUIImage:[UIImage imageWithContentsOfFile:#"/System/Library/WidgetResources /ibutton/white_i#2x.png"]];
self.allowsEditing = YES;
self.saveWallpaperData = YES;
return self;
}
(It will be necessary to allow access to the photo library after the first launch, and for some reason the popup for this comes up behind the app, rather than on top.)
Perhaps someone has insight as to why the cropping/zooming isn't working, or can give me an alternative way to set the wallpaper in an app (destined for Cydia rather than the App Store of course)?
Use this sample project, working very well.
Have inside camera control and custom layout, crop image when taken or after chose from your library, i used for my project and in very simple to customize.
https://github.com/yuvirajsinh/YCameraView
//---------- Answer improved----------//
I take a look on your project and i see 2 problem:
here you have 3 warning of semantic issue:
- (id)initWithUIImage:(id)arg1 cropRect:(struct CGRect { struct CGPoint { float x_1_1_1; float x_1_1_2; } x1; struct CGSize { float x_2_1_1; float x_2_1_2; } x2; })arg2;
in your ViewController.m you setting to get the image from where?
- (id)initWithCoder:(NSCoder *)aDecoder
{
// black_i
//what directory is this?
self = [self initWithUIImage:[UIImage imageWithContentsOfFile:#"/System/Library/WidgetResources/ibutton/white_i#2x.png"]];
//--------------------
self.allowsEditing = YES;
self.saveWallpaperData = YES;
return self;
}
i try to remove your
- (id)initWithUIImage:(id)arg1 cropRect:(struct CGRect { struct CGPoint { float x_1_1_1; float x_1_1_2; } x1; struct CGSize { float x_2_1_1; float x_2_1_2; } x2; })arg2;
change IMG directory in to:
self = [self initWithUIImage:[UIImage imageNamed:#"myImage.png"]];
and all working well but can't crop image, with my git hub YCameraView you have first understand how it work CROPPING function if you want to use crop or more simple, you have to create a fullScreen UICameraPicker allow user to get from camera or from library and allow the editing in cameraPicker then you can load a new picture in your View like this
self = [self initWithUIImage:[UIImage imageNamed:imageSelected.image]];
for a dismiss view, you can't because is a full app allow user to setUp background wallpaper and you can't terminate the app to see a SpringBoard, you have to create first view > picker > detail view with settings for a Home and LockScreen > then dismiss and come back to a first view.
PS: I think in your project to enable editing direct in a view you have to improve your code with a pinch and pan gesture on the UIView
Hope this help you!

iOS app UI has been pushed down due to Call Notification

Here is the scenario.
Before launching my app, I make a phone call. Then I open my app, the status bar height has doubled due to call notification. and because of that, the entire UI of my app has been pushed down by additional 20pt. Switching back to phone app and end the call, when I open my app again, the UI gets stuck as just now, except that the green notification bar's gone, leaving an additional 20pt black bar on top.
This does not happen if my app is already launched before I make the call.
I've used [self.window makeKeyAndVisible];
Any idea how to adjust UI properly in this case?
Update:
Here's the code
ADTransitionController *transitionController= [[ADTransitionController alloc] initWithRootViewController:[[MainViewController alloc] init]];
transitionController.wantsFullScreenLayout = YES;
ConfigurationSetupViewController *menuController = [[ConfigurationSetupViewController alloc] init];
REFrostedViewController *frostedViewController = [[REFrostedViewController alloc] initWithContentViewController:transitionController menuViewController:menuController];
frostedViewController.direction = REFrostedViewControllerDirectionLeft;
frostedViewController.liveBlurBackgroundStyle = REFrostedViewControllerLiveBackgroundStyleLight;
if ([GBDeviceInfo deviceDetails].majoriOSVersion < 7) {
frostedViewController.blurRadius = 2;
}
self.window.rootViewController = frostedViewController;
I've drilled down the problem with REFrostedViewController. If I set rootViewController to transitionController, the layout is perfectly aligned.
Update2:
So the fact is.. when a double status bar is present, ADTransitionController will set coordY = 20 and height = 548. REFrostedViewController does the same thing.. causing I have 40 margin on top.. One thing I'm not sure is.. when is the frame of these UIViews being set?

Resources