View being cut off (landscape vs. portrait orientation) - ios

I have had this problem countless times and cannot figure out how to fix it. I am working in an Xcode project (empty project - NO XIB's!). I have my orientation set to landscape:
But this keeps happening:
The view is being cutoff. No matter what I do it doesn't seem to set to the proper size. For some reason it is displaying the view in landscape using portrait bounds. Does anyone know how to fix this? I also want to restrict the orientation to ONLY landscape.
UPDATE
The view does not get cutoff if I hard-code 1024 as the width and 768 as the height. This is obviously a terrible solution, but I cannot figure it out. Does anyone out there know of a solution?

Check the rooViewController that you are setting in application:didFinishLaunchingWithOptions: in app delegate class. Make sure you are returning proper allowed orientations in this view controller class whose object you are setting to rootViewController:
- (NSUInteger) supportedInterfaceOrientations{
return UIInterfaceOrientationMaskLandscape|UIInterfaceOrientationLandscapeRight;
}
- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation{
return UIInterfaceOrientationIsLandscape(toInterfaceOrientation);
}
In your app delegate add this function :
- (NSUInteger) application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window{
return UIInterfaceOrientationMaskLandscape|UIInterfaceOrientationLandscapeRight;
}

I have the answer! One of my friends helped me out with this one.
Views are not oriented until they appear, so, if you are going to add any components to the view and expect them to adhere to an orientation other than the default, which I suspect is portrait, you must add those components in the -(void)viewDidAppear:(BOOL)animated. I was calling methods that added several components to the view from within the viewDidLoad method, however, the view has not appeared, and the orientation not set, when that method is called. Moving my initialization code into the viewDidAppear method fixes my problem.
Here is an example:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidLoad];
//Probably dont want to draw stuff in here, but if you did, it would adhere to the
//correct orientation!
CAShapeLayer *layer = [CAShapeLayer layer];
layer.backgroundColor = [UIColor blueColor].CGColor;
layer.anchorPoint = CGPointMake(0, 0);
layer.bounds = CGRectMake(0, 0, self.view.bounds.size.width, 300);
[self.view.layer addSublayer:layer];
//Call methods from here
[self initializeScrollView];
[self addItemToScrollView];
[self addGraphToView];
}

Related

iOS in-Call indicator is pushing down view/content, modifying root view `frame`

I have a problem that my root view (the UIViewController view) is being pushed down by the in-call indicator: window.rootViewController.view.frame is being modifeid (Y is set to 20). As I respond to did/willStatusBarFrameChange on my own, I don't want this behaviour.
I'm looking for the property, or setup, that prevents the modification of the frame in response to an in-call status bar. I use other APIs to respond to changes in the top/bottom frames and iPhone X safe areas.
I've tried things like autoResizingMask, extendedLayoutIncludesOpaqueBars, edgesForExtendedLayout, viewRespectsSystemMinimumLayoutMargins but can't get anything working.
If relevant, the view is also animating down, indicating it's not some side-effect but an intended behaviour somewhere.
I've read many reports of similar behaviour but have yet to figure out if they actually resolved it and/or what the solution actually was (each solution appears to address a slightly different problem).
Related questions: Prevent In-Call Status Bar from Affecting View (Answer has insufficient detail), Auto Layout and in-call status bar (Unclear how to adapt this)
--
I can't provide a simple reproduction, but the portions of code setting up the view looks something like this:
Window setup:
uWindow* window = [[uContext sharedContext] window];
window.rootViewController = (UIViewController*)[[UIApplication sharedApplication] delegate];
[window makeKeyAndVisible];
Our AppDelegate implementation (relevant part)
#interface uAppDelegate : UIViewController<#(AppDelegate.Implements:Join(', '))>
...
#implementation uAppDelegate
- (id)init
{
CGRect screenBounds = [UIScreen mainScreen].bounds;
uWindow* window = [[uWindow alloc] initWithFrame:screenBounds];
return self;
}
We assign our root view to the above delegate, the UIViewController's .view property.
#interface OurRootView : UIControl<UIKeyInput>
UIControl* root = [[::OurRootView alloc] init];
[root setUserInteractionEnabled: true];
[root setMultipleTouchEnabled: true];
[root setOpaque: false];
[[root layer] setAnchorPoint: { 0.0f, 0.0f }];
// some roundabout calls that make `root` the `rootViewController.view = root`
[root sizeToFit];
The goal is that OurRootView occupies the entire screen space at all times, regardless of what frames/controls/margins are adjusted. I'm using other APIs to detect those frames and adjust the contents accordingly. I'm not using any other controller, view, or layout.
It's unclear if there is a flag to disable this behaviour. I did however find a way that negates the effect.
Whatever is causing the frame to shift down does so by modifying the frame of the root view. It's possible to override this setter and block the movement. In our case the root view is fixed in position, thus I did this:
#implementation OurRootView
- (void)setFrame:(CGRect)frame;
{
frame.origin.y = 0;
[super setFrame:frame];
}
#endf
This keeps the view in a fixed location when the in-call display is shown (we handle the new size ourselves via a change in the statusBarFrame and/or safeAreaInsets). I do not know why this also avoids the animation of the frame, but it does.
If for some reason you cannot override setFrame you can get a near similar seffect by overriding the app delegate's didChangeStatusBarFrame and modifying the root view's frame (setting origin back to 0). The animation still plays with this route.
I hope I understand your problem: If you have some indicator like incall, or in my case location using by maps. You need to detect on launching of the app that there is some indicator and re-set the frame of the whole window. My solution for this:
In didFinishLaunchingWithOptions you check for the frame of the status bar, because incall is the part of status bar.
CGFloat height = [UIApplication sharedApplication].statusBarFrame.size.height;
if (height == 20) {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
}
else {
CGRect frame = [[UIScreen mainScreen] bounds];
frame.size.height = frame.size.height - height +20;
frame.origin.y = height-20;
self.window = [[UIWindow alloc] initWithFrame:frame];
}
You can listen to the notification UIApplicationDidChangeStatusBarFrameNotification in your view controller(s) to catch when the status bar has changed. Then you adjust your view controller's main view rectangle to always cover the entire screen.
// Declare in your class
#property (strong, nonatomic) id<NSObject> observer;
- (void)viewDidLoad {
[super viewDidLoad];
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidChangeStatusBarFrameNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
CGFloat newHeight = self.view.frame.size.height + self.view.frame.origin.y;
self.view.frame = CGRectMake(0.0, 0.0, self.view.frame.size.width, newHeight);
}];
}
-(void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:_observer];
}
I tried it on various models, and it works fine, as far as I can tell. On iPhone X the notification is not posted since it does not alter the status bar height on calls.
There is also a corresponding UIApplicationWillChangeStatusBarFrameNotification which is fired before the status bar changes, in case you want to prepare your view in some way.

unexpected nil window in _UIApplicationHandleEventFromQueueEvent, _windowServerHitTestWindow

I am trying to set up an edge swipe gesture in iOS 8 on iPad but getting and error that seems like a bug.
I have the following code:
UIScreenEdgePanGestureRecognizer *edgeRecognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:#selector(handleRightEdgeSwipe:)];
edgeRecognizer.edges = UIRectEdgeRight;
[self.view addGestureRecognizer:edgeRecognizer];
and then I handle the gesture:
-(void)handleRightEdgeSwipe:(UIGestureRecognizer*)sender
{
//slide in view code here
}
The problem is that it doesn't detect the right edge swipe every time. And sometimes it detects it multiple times.
Whether it detects or not it always shows the following information in the console when swiping the right edge on iPad:
2014-10-07 00:04:40.386 Office Log[1531:500896] unexpected nil window in _UIApplicationHandleEventFromQueueEvent, _windowServerHitTestWindow: ; layer = >
What does this message mean and how can I fix it so that the right edge swipe is detected consistently?
I think it's a bug in iOS, which I can confirm on an iPad mini 2 and an iPad Air on iOS 7 or higher, even on the Home Screen.
In "Landscape Left" (Home button on the left) a "Right Edge Gesture" from outside the Screen is not working for me. Test it by your self on the Home Screen.
I reported a bug to Apple 9 Month ago, but noting further happened.
Update:
I played a bit with the UIWindow init and when it is a bit bigger than it really is, the Gesture works. Of course this is a horrible fix.
self.window = [UIWindow new];
self.window.rootViewController = [[UIViewController alloc] init];
// Real Size
CGRect frame = [UIScreen mainScreen].bounds;
// Real Size + 0.000001
self.window.frame = CGRectMake(0, 0, frame.size.width+0.000001, frame.size.height+0.000001);
[self.window makeKeyAndVisible];
I got the same issue.
My solution works fine: just set in your xib your Windows to hidden.
I don't really understand why it works, but it works.
EDIT 1:
I found another solution, better I think, and more understandable:
Put this code on your willFinishLaunchingWithOptions, in your appDelegate:
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
CGRect bounds = [[UIScreen mainScreen] bounds];
[self.window setFrame:bounds];
[self.window setBounds:bounds];
return YES;
}
Then, on your didFinishLaunchingWithOptions:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Your codes...
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
return YES;
}
Your can then set your window object hidden to NO, and it should works.
I got issue when testing iPhone app on iPad. No problems on simulator and no problem if I compile app as universal and run on iPad.
unexpected nil window in _UIApplicationHandleEventFromQueueEvent, _windowServerHitTestWindow: <UIClassicWindow: 0x1276065a0; frame = (0 0; 768 1024); userInteractionEnabled = NO; gestureRecognizers = <NSArray: 0x1740557e0>; layer = <UIWindowLayer: 0x17403fd80>>
Perhaps frame is reported wrong ? ( frame = (0 0; 768 1024) )
iOS 8 has a bug where any touch that begins exactly on an iPad right edge when in Portrait Upside-Down (home button on top) or Landscape Left (home button on left) mode fails to be hit tested to the correct view.
The fix is to subclass UIWindow to hit test the right side properly.
#implementation FixedWindow
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
UIView* hit = [super hitTest:point withEvent:event];
if (!hit && point.x >= CGRectGetMaxX(self.bounds))
hit = [super hitTest:CGPointMake(point.x - 0.5, point.y) withEvent:event];
return hit;
}
#end
Attach the window to your application delegate via the window property.
#implementation AppDelegate
- (UIWindow*)window
{
if (!_window)
_window = [[IVWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
return _window;
}
#end
In Portrait and Landscape Right modes, I've confirmed that right edge touches are always at least 0.5px from the edge instead of exactly on the edge, so this fix should work in analogy to that.
Expanding the window frame
Note that firebug's fix will also work i.e. slightly expanding the window frame to include the right side. However:
If you do this at application:willFinishLaunchingWithOptions: or application:didFinishLaunchingWithOptions:, your view hierarchy doesn't get resized to the new frame and right edge touches won't make it through the hierarchy.
If you rotate the device, the window may not be centered correctly. This leads to either smearing or bumping interface elements slightly.
iOS 7:
iOS 7 has a similar bug in that the hit test also fails but with a non-nil result and unrotated geometry. This fix should work with both iOS 7 and 8:
#implementation FixedWindow
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
UIView* hit = [super hitTest:point withEvent:event];
if (!hit || hit == self)
{
CGRect bounds = self.bounds;
hit = [super hitTest:CGPointMake(MIN(MAX(point.x, CGRectGetMinX(bounds) + 0.5), CGRectGetMaxX(bounds) - 0.5),
MIN(MAX(point.y, CGRectGetMinY(bounds) + 0.5), CGRectGetMaxY(bounds) - 0.5))
withEvent:event];
}
return hit;
}
#end
One possible fix is to remove or comment out code for hiding status bar if you have that.
I was pulling my hair to solve it, and I could only reproduce it on my root view. It appears that if you hide the status bar you cannot drag down today widgets/notification center (you can with some effort).
/* <- add this
- (BOOL)prefersStatusBarHidden
{
return YES;
}
add this -> */
Set your deployment to 8.x or above, set launch screen as your main xib.
Done!
It may be to late but some people may still need it,
normally the cause is that you haven't supplied correctly sized launch images or a launch screen and/or Main Interface is not set to your own storyboard at General> Deployment Info

How to center a UIButton after device is rotated in iOS

I have created a UIButton programmatically and horizontally centered it using the following code
button.center = CGPointMake(self.view.center.x, 50);
When device is rotated, it's no longer in the center. How can I fix this problem? Thanks in advance.
Kevin is correct, but a better solution would be to set the button's center in your view controller's willAnimateRotationToInterfaceOrientation:duration: method.
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration
{
button.center = CGPointMake(self.view.center.x, 50);
}
He uses the didRotateFromInterfaceOrientation:fromInterfaceOrientation method, which, as the Apple documentation states "this method might be used to reenable view interactions, start media playback again, or turn on expensive drawing or live updates."
In the case of laying out subviews, the willAnimateRotationToInterfaceOrientation:duration: will provide a smoother transition, as it is called from within the rotation's animation block.
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
button.center = CGPointMake(self.view.center.x, 50);
}

Is it possible to access properties of an iOS system animation?

Is it possible to get the animation properties (speed and easing curve) for a built in iOS system animation? Specifically, the UIStatusBarAnimationSlide when you hide or show the status bar. Right now I'm just eyeballing it and have come up with a good match: .35 seconds using the default animation curve. This works fine, but Apple is liable to change an animation like this in a future iOS update and it would be nice to match it exactly and not rely on hard-coded values I came up with myself.
For what it's worth, here is the method my view controller is calling when I tap the view to hide the status bar and resize the view to fill the screen.
-(void)tappedView:(UIGestureRecognizer *)gestureRecognizer
{
UIApplication *app = [UIApplication sharedApplication];
// First, toggle the visibility of the status bar
[[UIApplication sharedApplication] setStatusBarHidden:![app isStatusBarHidden] withAnimation:UIStatusBarAnimationSlide];
// Then scale this view controller's view, attempting to match the built-in
// UIStatusBarAnimationSlide animation
[UIView animateWithDuration:.35
animations:^{
self.view.frame = [UIScreen mainScreen].applicationFrame;
}];
}
As an aside, I'm surprised I couldn't find a built in way to handle resizing a VC's view when the status bar is hidden. After all, if the status bar doubles its height when a call is in progress, the view resizes automatically. Tell me I'm missing something and there's a way to get the view to grow automatically, too.
Here's a chuck of code I use in my app:
- (void)application:(UIApplication *)application willChangeStatusBarFrame:
(CGRect)oldStatusBarFrame {
[UIView animateWithDuration:0.355f animations:^{
if(floating_point_values_are_equal(oldStatusBarFrame.size.height, 20.0f)) {
for(UIViewController* VC in self.tabBarController.viewControllers) {
UIView* view = VC.view;
[view setTransform:CGAffineTransformMakeScale(1.0f, 1.0f)];
}
} else {
for(UIViewController* VC in self.tabBarController.viewControllers) {
UIView* view = VC.view;
CGFloat ratio = (view.frame.size.height - 20) / view.frame.size.height;
[view setTransform:CGAffineTransformMakeScale(1.0f, ratio)];
}
}
}];
}
It basically scales the entire app depending on the new screen dimensions. It only works because the scale ratio is not a big change- doing this for the new iPhone screen would not look right.

usable view size after status bar and navigation bar

I am writing an iPad app that needs to know the usable area of the view for drawing purposes. The view is added into a Navigation controller, so I have the status bar plus the navigation controller both taking up a certain number of pixels. My app happens to be in landscape mode, although I don't think that's relevant.
I am able to get the correct view size AFTER rotation using didRotateFromInterfaceOrientation. But I can't figure out how to do it without the screen being rotated.
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
[self.view setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
NSLog(#"drfi %d %d", (int)self.view.frame.size.width, (int)self.view.frame.size.height);
}
^^ that works after rotation. Not before. Can't figure out how to get accurate numbers. And I REALLY don't want to hard wire this.
I will also need this function to be device independent -- it should work on the NEW iPad as well as the older iPad resolutions. I can handle the scaling issues once I know the exact usable area. Why is this so hard? Help!!
I don't think you need to specify your frame's view within the didRotateFromInterfaceOrientation what i will suggest instead is setting some properties to your view autoresizing mask so that it automatically resize itself according to your view orientation.
By setting this for example to your view when your view is loaded (viewDidLoad method):
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
you specify that your view will change its width and height automatically and can get the right values you need to get from there.
You should read this: http://developer.apple.com/library/ios/#documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingViews/CreatingViews.html#//apple_ref/doc/uid/TP40009503-CH5-SW1
for a better understanding of views in iOS
EDIT
Also you probably want to spot what is the orientation of your device which can be accomplish with [[UIApplication sharedApplication] statusBarOrientation];
Your application looks like: there is a start up view, then in this view you will load and add a main view into window, right? Then you should do as below in your main view:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
CGRect frame = self.view.frame;
frame.origin.y = frame.origin.y + 20.0;
self.view.frame = frame;
}
return self;
}
Try this.
CGRect frame = [UIScreen mainScreen].bounds;
CGRect navFrame = [[self.navigationController navigationBar] frame];
/* navFrame.origin.y is the status bar's height and navFrame.size.height is navigation bar's height.
So you can get usable view frame like this */
frame.size.height -= navFrame.origin.y + navFrame.size.height;
You can get this dynamically by combining an instance method with a category method:
Instance method:
This assumes that your view controller (self) is embedded within a navigation controller.
-(int)getUsableFrameHeight {
// get the current frame height not including the navigationBar or statusBar
return [MenuViewController screenHeight] - [self.navigationController navigationBar].frame.size.height;
}
Class category method:
+(CGFloat)screenHeight {
CGFloat screenHeight;
// it is important to do this after presentModalViewController:animated:
if ([[UIApplication sharedApplication] statusBarOrientation] == UIDeviceOrientationPortrait ||
[[UIApplication sharedApplication] statusBarOrientation] == UIDeviceOrientationPortraitUpsideDown){
screenHeight = [UIScreen mainScreen].applicationFrame.size.height;
} else {
screenHeight = [UIScreen mainScreen].applicationFrame.size.width;
}
return screenHeight;
}
The above will consistently give you the usable frame height after the status bar and navigation bar have been removed, in both portrait and landscape.
Note: the class method will automatically deduct the 20 pt for the status bar - then we just subtract the navigation header variable height (32 pt for landscape, 44 pt for portrait).

Resources