The situation arises when my iPhone/iPad is to be connected to an external display.
In the normal situation, the entire device's screen gets mirrored to the external display. But, I need the screen on the external display to display content that is different from that in the device, for a specific view/page.
I have a UIImageView inside a UIScrollView, and some related buttons (next, previous, etc) as part of a specific View Controller of the app. I want the external device to display only the content in the UIScrollView (that is, the buttons are not to be shown). For this, it seems like I have to create another instance of UIWindow for the external display screen.
But, how can I make the UIWindow (and it's content) in the external display to respond correspondingly to the changes made to the main UIWindow (the one which is displayed in the iPhone/iPad). That is, changes like zooming in and zooming out.
You can do somthing like this:
- (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...
secondWindow.rootViewController = [ExternalScreenViewController sharedExternalScreen];
// Show the window.
self.secondWindow.hidden = NO;
}
}
Now all you need to do is create the ExternalScreenViewController with a UIView that fills the entire viewController. Finally you just set the view of the target equal to the view of the scrollView.
Related
I am trying to find a clean way to handle UIPageViewController and the status bar. I noticed Snapchat does it perfectly by sliding the viewcontroller OVER the status bar when you are sliding to a new page that does not show the status bar. It looks like this...
Does anyone know how this is being done? The only way I can think of is by using a different UIWindow, but how would you implement a UIPageViewController with multiple UIWindows? If that is even what is being done. Otherwise how is this effect being achieved?
Basically create a UIViewController, put the UIPageViewController inside that. Make the size of UIVC the size of iphone screen. Then set the appdelegate.window.rootviewcontroller as your UIViewControllers
Here is a great example repo for this task: https://github.com/goktugyil/EZSwipeController
Ok so this is actually done using some clever tricks.
Basically it's not actually moving the status bar, its moving an image of the status bar.
Disclaimer: I YOLO'd this implementation so I make no guarantees on if it'll work out of the box
Starting with iOS7 you can take a picture using
UIView *screen = [[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO];
There's a bit more useful information I saw here. But basically the status bar is cropped from the picture and added to a UIWindow with a windowLevel above UIWindowStatusLevelBar that will mask the real status bar. Something like:
UIWindow *statusBarWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
statusBarWindow.windowLevel = UIWindowLevelStatusBar;
statusBarWindow.hidden = NO; // fun fact you don't have to add a UIWindow to anything, just setting hidden = NO should display it
statusBarWindow.backgroundColor = [UIColor clearColor]; // I have no idea if this is necessary but since it's now visible you def don't want it to have a color
...
// The view that will hold the screen shot
UIView *statusBarView = [[UIView alloc] initWithFrame:[UIApplication sharedApplication].statusBarFrame];
statusBarView.clipsToBounds = YES;
// Now we add the screen shot we took to this status bar view
[statusBarView addSubview:screen]; // screen is the UIView from previous code block
// Now add this statusBarView with the image to the window we created
[statusBarWindow addSubview:statusBarView];
Now the rest really depends on what your implementation is looking like but from here you really just need to handle moving the view with either a pan, or whatever action is causing the new view to come in through the side:
// you're going to want to hide the status bar when this action starts so that your new window is visible
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
// then inside whatever method is handling the sliding you're going to adjust the frame of the statusBarView
// this can be done explicitly by setting a new frame, or animating its x position, or whatever
// if you're doing it explicitly something like:
CGFloat offset = self.viewThatIsScrolling.contentOffset.x; // may be negative depending on direction that is being swiped
statusBarView.frame = CGRectMake(offset, 0, self.statusBarView.frame.width, 20.0f);
// or maybe even
statusBarView.transform = CGAffineTransformMakeTranslation(offset -previousOffsetAmount, 0); // you only want it to move over by whatever new amount so it can match up, this will have to be tracked somehow if you go this route
...
// and finally once the 'sliding' is done don't forget to remove the screenshot and unhide the status bar
// (can check by looking at the offset value from earlier or as a callback after animation, or whatever)
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
[statusBarView removeFromSuperview];
statusBarView = nil;
Also note this doesn't handle orientation changes or anything (the fixed frame might cause it to not work). you could probably use autoResizingMask or something
I have a view with some UI components and a button on it, upon touch of a button I want to show a half view with some textfields on it overlapping the initial view, the initial view should be visible partly , the overlapping view will cover only half screen from bottom. Is this possible ?
I don't have any code as I am unable to figure out what it needs to be done, as we show any view it covers the entire screen.
Thanks
there are several ways you can do this, here are two:
1) add a popover controller that gets displayed on your button press:
here's some apple documentation on popovers: https://developer.apple.com/Library/ios/documentation/WindowsViews/Conceptual/ViewControllerCatalog/Chapters/Popovers.html
2) add the new view as a subview to your UIViewController
PROGRAMICALLY:
in the viewDidLoad function you can do the following to initialize the halfScreenView
GLfloat topOffset = self.view.frame.size.height/2;
UIView halfScreenView = [[UIView alloc] initWithFrame: CGRectMake(0, topOffset , [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height - topOffset)
[self.view addSubview:halfScreenView];
-more logic might be needed if you support Landscape orientation, you can always re-assign the location of the view with halfScreenView.frame.origin and halfScreenView.frame.size
-initially you can have this view be hidden
halfScreenView.hidden = YES;
-when you click the button it will show the overlaying view:
halfScreenView.hidden = NO;
USING STORYBOARD:
you can also set up your overlaying view in the storyboard if you have one
-drag a UIView into your UIViewController and set it up where you want it to be located
-initialize the view to be hidden by checking the hidden box in the attribute inspector
-add the view as a property to your view
-manage when to show this view with self.halfScreenView.hidden
-this technique allows you to customize what is inside the new view within the storyboard which is nice
FOR BOTH:
-be careful with layers, you don't want your view to show up behind the one you already present. In the storyboard the last thing inserted goes on top. With in the code you can always access/change the views z position with halfScreenView.layer.zPosition (higher z values are on top)
First create a new class subclassing UIViewController called SecondView (or whatever you want), then design the UI the way you want (in its .xib file)
Then go to your main view controller's file and make an IBAction for that button.
In that method, write:
SecondView* second = [[SecondView alloc] initWithFrame:CGRectMake(0, [[UIScreen mainScreen] bounds].size.height/2, height, width);
[self.view addSubview:second.view];
This will add it to the bottom half of the screen. Put your own parameters for its height and width. When you want to dismiss the view, you can do this inside your SecondView class
[self.view removeFromSuperview];
You can deal with the textFields from within the SecondView class and have them communicate with your other view by doing the following in SecondView.h
#property IBOutlet UITextField* textField;
Hope this helps!
Yes, assuming you are using Interface Builder, go ahead and build the overlapping view and hook up all of the IBOutlets and IBActions. Say this view is called myView. Set myView.hidden = YES and myView.enabled = NO. This hides and disables myView so that it isn't even there from the user's perspective. In the appropriate button's IBAction, change the hidden and enabled properties to YES. That will make the view visible and active again.
I need to add the ads functionality in my iOS App. And ads screen would appear after some time interval. My whole is in Landscape mode only. When I tried to add the view on current view then it shows the views in portrait mode not in landscape mode. I have set the view frame i.e. CGSizeMake(0,0, 568, 320)
time = [NSTimer scheduledTimerWithTimeInterval:2.0f
target:self
selector:#selector(showfirstad)
userInfo:nil
repeats:YES];
-(void)showfirstad {
[[[[UIApplication sharedApplication] windows] lastObject] addSubview:firstad];
}
It appears like this .
_window = [UIApplication sharedApplication].keyWindow;
if (!_window) _window = [[UIApplication sharedApplication].windows objectAtIndex:0];
UIInterfaceOrientation orientation = self.window.rootViewController.interfaceOrientation;
// Set appropriate view frame (it won't be autosized by addSubview:)
CGRect appFrame = [[UIScreen mainScreen] applicationFrame];
if (UIInterfaceOrientationIsLandscape(orientation))
{
// Need to flip the X-Y coordinates for landscape
self.view_login.frame = CGRectMake(appFrame.origin.y, appFrame.origin.x, appFrame.size.height, appFrame.size.width+20);
else
{
self.view_login.frame = appFrame;
}
[[[_window subviews] objectAtIndex:0] addSubview:self.view_login];
The reason your UIView gets displayed in portrait orientation while the rest of your app gets displayed in landscape is because you are adding the UIView as a subview of your window rather than adding it as a subview of a view controller's view. This places it outside of the view hierarchy that gets transformed automatically through autorotation.
The app's window object coordinates autorotation by finding its topmost subview that has a corresponding view controller. Upon device rotation, the window calls shouldAutorotateToInterfaceOrientation: on this view controller and transforms its view as appropriate. If you want your view to autorotate, it must be a part of this view's hierarchy.
So instead of [window addSubview:UIView];, do something like [self.view addSubview:UIView];
I had the same issues with rotation and autolayots when used addSubview:myView.
I managed to solve this problem by using standard container controllers or placing views directly to storyboard.
You can probably just add the view that will keep your ad into the screen in storyboard and then set hidden property to YES. Then you can change it to YES after some time.
My app utilizes both landscape mode and portrait mode and the user can switch between the two at will.
When a view controller is presented by a parent view controller that is in portrait orientation, the opened view controller will have the correct width & frame.
However, if the parent view controller is in landscape mode, then on IOS6 (it works correctly on IOS7), the child view controller will be too large and actually a little too short also when it is presented.
Note this is not because the values are reported incorrectly since [[UIScreen mainScreen] bounds] reports the same values regardless of the orientation the child controller is loaded in.
Any ideas on how to fix this / why this is happening? Any idea on how to force the IOS6 versions to behave like IOS7 is now behaving natively? Many thanks!!!
Edit::
Here's how the vc's are presented:
AppDelegate
Launch1 *launch1 =[[Launch1 alloc] init];
self.window.rootViewController = launcher;
[self.window makeKeyAndVisible];
Launch1 class
Search *search = [[Search alloc] init];
[self presentViewController:search animated:YES completion:nil];
Search class
//load list_container
views = [[Search_custom_views alloc] initWithControllerContext:self];
[self.view addSubview:views];
Search_custom_views UIView extension:
- (id)initWithControllerContext:(UIViewController*)contextstart {
//set size of the screen
CGRect screenRect = [[UIScreen mainScreen] bounds];
self = [super initWithFrame:screenRect];
if (self) {
....
So this was a tough one. I load all my views programmatically. They basically are UIView subclasses that correspond to each view controller. For some reason, when an IOS6 view controller is opened from a parent view controller in landscape mode, the child view controller's bounds are not immediately passed on the child vc's UIView subclasses (if you just use addSubview in the viewDidLoad method of the controller--it is not enough). IOS7 does not have this problem.
The fix for IOS6 for me was doing the following in the viewDidLoad method of the child view controller:
//add view subclass to view controller
[self.view addSubview:views];
//reset bounds & refresh layout for IOS6
if ([[[UIDevice currentDevice] systemVersion] floatValue] < 7) {
views.frame = self.view.bounds;
[views layoutIfNeeded];
}
iOS 7 likes it when you call [[UIScreen mainScreen] bounds] instead of [[UIScreen mainScreen] applicationFrame] because the applicationFrame property is not consistently calculated between different versions of iOS, while bounds is.
It should be backwards compatible, so you should be able to do something like this:
- (CGRect)filterBankFrameForOrientation:(UIInterfaceOrientation)orientation {
CGRect appFrame = [[UIScreen mainScreen] bounds];
if (UIInterfaceOrientationIsLandscape(orientation)) {//using bounds here instead of applicationFrame for iOS7 compatibility
//Handle landscape orientation
filterBankFrame = CGRectMake(0.0, k_filterBankOffsetFromTop, appFrame.size.height, k_filterBankHeight);
}
else {
//Handle portrait orientation
filterBankFrame = CGRectMake(0.0, k_filterBankOffsetFromTop, appFrame.size.width, k_filterBankHeight);
}
return filterBankFrame;
}
and simply flip the height and width values as needed (since bounds will always be in "portrait" orientation)
Using bounds should give you the consistency you need for identical behavior across iOS versions and device sizes.
Updating in response to OP's updated code
One approach I'd recommend you at least consider is wiring up these views in InterfaceBuilder and using AutoLayout to worry about the rotations for you. It has the added benefit of gracefully handling ALL of the different screen sizes available too, so that can be nice too.
Still, creating and managing it all in code is perfectly acceptable too, and may be the right call for your situation. If so, you'll want to override a few of the rotation handling methods of UIViewController. Probably most or all of these:
– shouldAutorotate
– supportedInterfaceOrientations
– preferredInterfaceOrientationForPresentation
– willRotateToInterfaceOrientation:duration:
- didRotateFromInterfaceOrientation
at a minimum the first one and the last two.
To avoid being tied to one orientation only at launch, it is a common design pattern (citation needed) to write a method like the one I posted above, and then utilize it from both viewDidLoad as well as from the willRotate / didRotate class methods.
When calling during viewDidLoad, you do something like this:
_filterBank.collectionView.frame = [self filterBankFrameForOrientation:[[UIApplication sharedApplication] statusBarOrientation]];
which uses the statusBarOrientation property to launch correctly in either landscape or portrait.
The willRotate / didRotate methods both give you a parameter you can pass to your frame generating method.
Your method gives you the right frame size, then it's all up to you to pass down this info and manipulate your view hierarchy accordingly. It's easier than it sounds...
(in your case, it looks like launcher would implement the methods, then coordinate the adjustments to launch1 and then down to search and finally to Search_custom_views)
(**last side note, you'll make more friends here by choosing SearchCustomViews instead of Search_custom_views)
I have just added an ADBannerview in my App. I create the AdBannerView in my UIApplicationDelegate in order to have only one instance of it and I share it in the different viewController
Everything works perfectly except I get the warning message: ADBannerView: WARNING A banner view (0x9c75550) has an ad but may be obscured. This message is only printed once per banner view.
when I open a modal view (using presentModalViewController) on top of the view that is currently displaying the ADBannerview. Before opening the modal view I'm using the following code to hide the ADBannerview:
- (void)viewWillDisappear:(BOOL)animated
{
ADBannerView *bannerView = [ (ScoreBoardAppDelegate*)[[UIApplication sharedApplication] delegate] adBanner];
[self hideBanner:bannerView];
[super viewWillDisappear:animated];
}
- (void)hideBanner:(ADBannerView*) adBanner {
NSLog(#"%s called", __FUNCTION__);
// Grow the tableview to occupy space left by banner, it's the size of the parent view
CGFloat fullViewHeight = self.tbView.frame.size.height;
CGRect tableFrame = self.tv.frame;
tableFrame.size.height = fullViewHeight;
// Move the banner view offscreen
CGRect bannerFrame = adBanner.frame;
CGRect screenBounds = [[UIScreen mainScreen] bounds];
bannerFrame.origin = CGPointMake(CGRectGetMinX(screenBounds), CGRectGetMaxY(screenBounds));
self.tv.frame = tableFrame;
adBanner.frame = bannerFrame;
}
I don't understand what to do to not have this warning message. It seems the ADBannerView is successfully hidden (offscreen) before the Modal view is displayed.
I probably missed something but I cannot see it.
Thanks for your help,
Sébastien.
Sébastien, I hope you've moved on from this since the question has gone unanswered for so many months. I recently added iAd support and found this warning to be quite annoying too. One of the subtleties of sharing an ad banner is that if you want to show it in your initial view controller, you have to do most of the setup in that view controller, not in the app delegate.
This is the viewWillAppear: method in my initial view controller:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (!SharedAdBannerView) {
// in my app, the ad banner is the bottom-most thing on screen
CGRect startingFrame = CGRectMake(0.0, self.view.frame.origin.y + self.view.frame.size.height, 320.0, 50.0);
adBanner = [[ADBannerView alloc] initWithFrame:startingFrame];
// Set the autoresizing mask so that the banner is pinned to the bottom
adBanner.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin;
// Since we support all orientations, support portrait and landscape content sizes.
// If you only supported landscape or portrait, you could remove the other from this set
adBanner.requiredContentSizeIdentifiers = [NSSet setWithObjects:ADBannerContentSizeIdentifierPortrait, nil];
adBanner.currentContentSizeIdentifier = ADBannerContentSizeIdentifierPortrait;
adBanner.delegate = self;
[self.view addSubview:adBanner];
SharedAdBannerView = adBanner;
} else {
adBanner = SharedAdBannerView;
}
SharedAdBannerView is a macro as defined in the TN2286, and it is utilizing an instance variable defined on the app delegate (which is how it remains shared amongst all views that display an iAd). I also decided to animate the ad banner off the screen before removing it from the view hierarchy as one scene was segueing to another scene. I read the documentation as saying that whenever an ad banner is a part of a view hierarchy you would get that message -- in other words, hiding the banner view isn't the way to prevent the warning message. Or put differently, if it is sufficient to hide the ad banner, it didn't work for me and it didn't help in troubleshooting. I learned a lot when I came across TN2239 which offered this tip in gdb:
po [[self view] recursiveDescription];
You have to adjust the object to whom you send the recursiveDescription message based on where you placed your breakpoint, but probably [self view] is fine.