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!
Related
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/
I'm using the MWPhotoBrowser library in my app. I'm showing photos, captions and everything works perfectly until the iOS 8 comes.
My problem is;
I can see my navigation bar in iOS 7 but in iOS 8 it disappears and I can't return back. I check gitHub but I couldn't find a thing. Do you have any idea?
- (void)viewPhotoBrowser{
self.photos = [NSMutableArray array];
for (Images *image in self.browserImages) {
[self.photos addObject:[MWPhoto photoWithURL:[NSURL URLWithString:image.image]]];
}
// Create & present browser
self.browser = [[MWPhotoBrowser alloc] initWithDelegate:self];
// Set options
self.browser.displayActionButton = YES; // Show action button to allow sharing, copying, etc (defaults to YES)
self.browser.displayNavArrows = NO; // Whether to display left and right nav arrows on toolbar (defaults to NO)
self.browser.displaySelectionButtons = NO; // Whether selection buttons are shown on each image (defaults to NO)
self.browser.zoomPhotosToFill = YES; // Images that almost fill the screen will be initially zoomed to fill (defaults to YES)
self.browser.alwaysShowControls = NO; // Allows to control whether the bars and controls are always visible or whether they fade away to show the photo full (defaults to NO)
self.browser.enableGrid = YES; // Whether to allow the viewing of all the photo thumbnails on a grid (defaults to YES)
self.browser.startOnGrid = NO; // Whether to start on the grid of thumbnails instead of the first photo (defaults to NO)
self.browser.wantsFullScreenLayout = NO; // iOS 5 & 6 only: Decide if you want the photo browser full screen, i.e. whether the status bar is affected (defaults to YES)
[self.browser setCurrentPhotoIndex:0]; // Example: allows second image to be presented first
// Manipulate
[self.browser showNextPhotoAnimated:YES];
[self.browser showPreviousPhotoAnimated:YES];
[self.browser.view setFrame:CGRectMake(0, -20, self.browser.view.frame.size.width, self.browser.view.frame.size.height)];
[self.view addSubview:self.browser.view];
[self.navigationController pushViewController:self.browser animated:YES];
}
You have to uncomment the line:
[self.view addSubview:self.browser.view];
this line will cause: Unbalanced calls to begin/end appearance transitions for MWPhotoBrowser.
You added the controller's view to your view, and then immediately push the controller onto the navigation stack.
I am trying to imitate an alert view and i show a view with two text fields above a view with a translucent background. The problem is that when i try to tap on the text fields , the keyboard is shown behind my translucent view and i can't tap it no more. Is there a solution?
Here is my code:
if (_grayView==nil) {
_grayView = [[UIView alloc]init];
_grayView.frame = [[UIScreen mainScreen]bounds];
_grayView.backgroundColor = [UIColor blackColor];
_grayView.alpha = 0.7;
[[[[UIApplication sharedApplication] windows] lastObject] addSubview:_grayView];
}
//Show the dimensions view when choosing an image
_dimensionsView.hidden = NO;
[[[[UIApplication sharedApplication] windows] lastObject] addSubview:_dimensionsView];
In the _dimensionsView i have the textfileds.
What you're doing isn't a very good idea to begin with. Also be aware that you can't count on windows.lastObject to always be the window you expect. iOS 7 and 8 are more and more liberal with creating new windows for keyboards and modals. You might not be adding your subview to the window you're expecting.
thanks for turning up :-)
I noticed that on the iPad the Form Sheet view does not have any iOS 7 parallax effects, and I would actually like to incorporate this because I think it kinda looks cool.
Well this is what I have and it doesn't have that, so is there a way to do this? How would one go about doing it? I've googled and googled and google has finally failed me because nothing appropriate is appearing.
Thanks :)
This can be done with some effort.
First off, you need to know how to add parallax to a view in general:
I have the following category method for UIView to make it easy:
- (void)addDepthMotionX:(CGFloat)x y:(CGFloat)y {
Class clazz = NSClassFromString(#"UIInterpolatingMotionEffect");
if (clazz) {
BOOL reverse = NO;
if (CGAffineTransformEqualToTransform(self.transform, CGAffineTransformMakeRotation(-M_PI_2)) || CGAffineTransformEqualToTransform(self.transform, CGAffineTransformMakeRotation(M_PI_2))) {
reverse = YES;
}
UIInterpolatingMotionEffect *eff = [[clazz alloc] initWithKeyPath:#"center.x" type:reverse ? UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis : UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
eff.maximumRelativeValue = #(x);
eff.minimumRelativeValue = #(-x);
[self addMotionEffect:eff];
eff = [[clazz alloc] initWithKeyPath:#"center.y" type:reverse ? UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis : UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
eff.maximumRelativeValue = #(y);
eff.minimumRelativeValue = #(-y);
[self addMotionEffect:eff];
}
}
The code is guarded so it won't crash if called from other than iOS 7 (or later).
Now to add parallax to a view you simply do:
[someView addDepthMotionX:10 y:10]; // pick an appropriate depth value
The final step to your question is to apply this to the root of the view controller you are displaying.
Here's code you can add in the viewWillAppear: method of the view controller. Adjust to meet your needs:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.isBeingPresented || self.isMovingToParentViewController) {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && self.modalPresentationStyle == UIModalPresentationFormSheet) {
[self.navigationController.view.superview addDepthMotionX:15 y:15];
}
}
}
One issue with this code is if the user rotates the iPad 90 degrees after the form sheet is presented, the parallax effect needs to be updated. But this will get you started.
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.