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.
3 Answers
Answers 1
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.
Answers 2
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]; } Answers 3
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.
0 comments:
Post a Comment