How to use the area around Macbook's notch in fullscreen? - swift

I need to use the area around (and behind) the notch while application is in full screen mode and display my app "fully full-screen" and use that area to display elements, as if there is no notch there.
I understand that is where the menu bar appears, but I am okay with either disabling menu bar in full screen OR making it act like older macs when it would appear over the application after we move cursor higher in that area.
I've tried (to no avail):
Playing with Safe Area
Hiding Title Bar from inspection menu in Xcode
Removing the Menu completely
Adding UISupportsTrueScreenSizeOnMac = true to plist
P.S. I've already done hours of searching on Google and SO, as well as Apple's documentation but haven't found any indication of how to achieve this.

I do not think you can use fullscreen mode to do this, because there is no public API for overriding your window's fullscreen frame to include the unsafe areas around the sensor housing (the ‘notch’).
You should be able to manually achieve this by looking at the NSScreen representing the built-in display. Set your window's frame to the screen's frame (not the screen's visibleFrame). The screen's auxiliaryTopLeftArea and auxiliaryTopRightArea describe the areas to the left and right of the notch. From those, you can deduce the area obscured by the notch.

This is how I managed to achieve this (simplified just for reference):
Hide application's "Title Bar"
Set your window's frame to full width and height on load (again, simplified)
override func windowDidLoad() {
window!.setFrame(CGRect(x: 0, y: 0, width: NSScreen.main!.frame.width, height: NSScreen.main!.frame.height), display: true)
}
Set LSUIPresentationMode in your plist
<key>LSUIPresentationMode</key>
<integer>3</integer> // Change this to 4 if you want to allow menu bar and dock to appear when user moves cursor top/bottom edges (they are initially hidden)
Note:
Without using LSUIPresentationMode or even hiding title bar, the following code would launch the app in fullscreen and for ~1 second it would fill the area around notch as well, but then it would revert back to the area below notch.
Just thought I should also mention this, so there might be ways to achieve this while using native fullscreen
window!.toggleFullScreen(self)
window!.setFrame(CGRect(x: 0, y: 0, width: 1728, height: 1117), display: true)

This is not a solution. But maybe helpful.
I tried swizzling -[_NSFullScreenContentController reservesSpaceForMenuBarInFullScreen] with retuning value NO.
#import <Cocoa/Cocoa.h>
#import <objc/message.h>
void swizzle(Class class, SEL cmd, IMP custom, IMP _Nullable * _Nullable original) {
Method originalMethod = class_getInstanceMethod(class, cmd);
IMP originalImp = method_getImplementation(originalMethod);
*original = originalImp;
class_replaceMethod(class, cmd, custom, nil);
}
BOOL (*original_NSFullScreenContentController_reservesSpaceForMenuBarInFullScreen)(id, SEL);
BOOL custom_NSFullScreenContentController_reservesSpaceForMenuBarInFullScreen(id self, SEL cmd) {
return NO;
}
#implementation NSWindow (Swizzle)
+ (void)load {
swizzle(NSClassFromString(#"_NSFullScreenContentController"), NSSelectorFromString(#"reservesSpaceForMenuBarInFullScreen"), (IMP)&custom_NSFullScreenContentController_reservesSpaceForMenuBarInFullScreen, (IMP *)&original_NSFullScreenContentController_reservesSpaceForMenuBarInFullScreen);
}
#end
Here's the results.
Before
After
With swizzling -[_NSFullScreenContentController reservesSpaceForMenuBarInFullScreen] frame of window will be fit to screen. But I cannot find how to remove a black bar.

Related

Obj-C, how do I get the height of a sub view to scale, across all iPhone sizes?

I'm having some trouble catering for the new iPhone 5 screen height, I need to resize my table view already to show an advert.
Up till iOS6 I didn't have a problem, I used the following function, but it doesn't use scale. To be honest I'm surprised it works.
+ (CGRect)setTableBoundsByHeight:(int)lHeight:(UITableView*)tbl {
CGRect tableFrame = tbl.frame;
return CGRectMake(tableFrame.origin.x,
tableFrame.origin.y,
tableFrame.size.width,
lHeight);
}
Here's the code, where I have hard coded the height of my table view at 367, that's minus the height of a navigation controller and a tab bar. 50 is the height of the advert.
if (!productPurchased) {
#ifdef VER_FREE
[[LARSAdController sharedManager]
addAdContainerToView:self.view withParentViewController:self];
[[LARSAdController sharedManager]
setGoogleAdPublisherId:#"number"];
[reportTableView setFrame:[General
setTableBoundsByHeight:(367-50):reportTableView]];
#endif
} else {
[reportTableView setFrame:[General
setTableBoundsByHeight:367:reportTableView]];
}
I've found some code which scales but I'm not sure how to implement this.
CGFloat scale = [UIScreen mainScreen].scale;
result = CGSizeMake(result.width * scale, result.height * scale);
If this code is inside your view controller, just use self.view.bounds.height rather than 367.
By the way: You should really rename
+ (CGRect)setTableBoundsByHeight:(int)lHeight:(UITableView*)tbl
to something like
+ (CGRect)setTableBoundsByHeight:(int)lHeight tableView:(UITableView *)tbl
Ignore the scale, it scales automatically. Just check if iPhone 5 and set different height, but you use iphone5 pixel count/2 as it'll scale it to 2x itself.
Using hard-coded values (a.k.a. "magic numbers") is a wrong habit, you see why now. Always prefer using constants or runtime-computed values. Besides, it makes code easier to read because by using constants you will know what the numbers correspond to, instead of being "magic numbers" coming from nowhere.
So for your problem, compute the height value at runtime using this kind of code below.
// simply use the height of the current viewController's `view`
// which is probably the view of the `navigationController`'s `topViewController`
// and is already at the correct size, namely 367 in iPhone 3.5" and 455 in iPhone 4".
CGFloat screenHeight = self.view.height;
if (!productPurchased)
{
static CGFloat advertHeight = 50;
#ifdef VER_FREE
[[LARSAdController sharedManager]
addAdContainerToView:self.view withParentViewController:self];
[[LARSAdController sharedManager]
setGoogleAdPublisherId:#"number"];
[reportTableView setFrame:[General
setTableBoundsByHeight:(screenHeight-advertHeight):reportTableView]];
#endif
} else {
[reportTableView setFrame:[General
setTableBoundsByHeight:screenHeight:reportTableView]];
}
Note that you don't need to do any substraction yourself, as the UIViewControllers resize their view according to the space available, so that if you have for example a UITabBarController that contains a UINavigationController that itself shows a UIViewController on top of its stack, the height of this last viewController's view will be the height of the screen minus the tabBar, statusBar and navBar heights.
So instead of fetching the [UIScreen mainScreen].applicationFrame for example, and subtract the tabBar (if any) and navBar heights to have your value of 367pt, simply use the height of the viewController's view directly and you should have the right value directly.
Additional note: you should give a prefix to your second argument, thus name your method setTableBoundsByHeight:tableView: instead of setTableBoundsByHeight:: where the second argument does not have any prefix. (See #MrMage answer that suggest this too).
A better naming for your method would even be setHeight:forTableView: for example, to beter fit the Apple naming conventions.

how do I resize a UIView after orientation change (code includes doesn't seem to work)

Why doesn't this UIView layout code work as I want?
Background:
I have a custom UIView I have in my UIViewController
The custom UIView has a clock background imageview and an hourhand imageview
After introducing some code (see below) to try to resize the hour hand for an orientation change I'm getting stuck.
The code below really stuffs things up - the border outlike of the hour glass hand is way off with this code
Any ideas what I'm doing wrong - there's obvious some assumption or misunderstanding I have for it to screw up like this...
Code called by UIViewController (in "didRotateFromInterfaceOrientation" method) to my custom view:
// Centre image
self.hourHandImageView.center = self.hourFaceUIImageView.center;
// Resize to cater for either Portrait or Landscape orientation
CGSize currSize = self.hourFaceUIImageView.frame.size;
float newHourHandImageheightWidth = currSize.width < currSize.height ? currSize.width : currSize.height;
CGSize newHourHandViewSize = CGSizeMake(newHourHandImageheightWidth, newHourHandImageheightWidth);
CGRect newRect = self.hourHandImageView.frame;
newRect.size = newHourHandViewSize;
self.hourHandImageView.frame = newRect;
PS. Without the "Resize to cater for either Portrait or Landscape orientation" code the hour hand correctly stays centered where it should (noting I use transformation to turn it around). There all I really need to do is resize the hour hand appropriately after an orientation change, noting the hour background image is smaller in the landscape mode.
First, how can you tell if you are in portrait or landscape orientation? With this (?):
float newHourHandImageheightWidth = currSize.width < currSize.height ? currSize.width : currSize.height;
What i would advice you, is to do something like this:
-
(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
if (UIInterfaceOrientationIsPortrait(toInterfaceOrientation)) {
[self reOrderToPortrait];
} else if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)){
[self reOrderToLandScape];
}
}
Then on each method, you should define the new frames for your views. Start by removing your autosizing from your views, because you will define the new frames, and you dont want that to interfere with. You could also define some mesures like this:
#define hourHandImageViewPortrait CGPointMake(...,...)
#define hourHandImageViewLandScape CGPointMake(..., ...)

iOS: Universal App Template supporting iAds & screen rotations

I am trying to construct a iOS universal application template that transparently handles iAds & screen rotations.
i.e. instead of using UIViewController for each new project, I will instead use my own iAdVC (which will subclass UIViewController). This will seamlessly handle the iAds, and hand over the remaining window space to the user.
I'm trying this: view controller contains uberView which contains {adView, content view}.
whenever an ad appears and disappears, both {adView, content view} will animate:
content view squashing the frame's top down slightly to make space for my iAd,
and fade in the ad along the top at the same time.
also, every time the device rotates, the views need to be resized.
I'm getting really dumb problem, when the first Ad gets served, I place it at the top of the screen and squash the remaining content frame to make space for it.
but if I change the content view's frame, I can no longer click the ad. and if I don't, the content view doesn't fit in its window,
http://d.pr/ZyQG
- (void) bannerViewDidLoadAd: (ADBannerView *) banner
{
bool isLandscape = UIInterfaceOrientationIsLandscape( self.interfaceOrientation );
NSString * contentSize = isLandscape ? ADBannerContentSizeIdentifierLandscape : ADBannerContentSizeIdentifierPortrait ;
[self.adBannerView setCurrentContentSizeIdentifier: contentSize];
CGSize bannerSize = [ADBannerView sizeFromBannerContentSizeIdentifier: contentSize];
self.adBannerView.frame = CGRectMake(0, 0, bannerSize.width, bannerSize.height);
// resize content frame & fade ad in
CGRect newContentFrame = uberView.bounds;
newContentFrame.size.height -= bannerSize.height;
newContentFrame.origin.y += bannerSize.height;
NSLog(#"%#", NSStringFromCGRect(newContentFrame)); // {{0, 50}, {320, 430}}
if (1) // 0 works
self.contentView.frame = newContentFrame; // NOW CANT CLICK AD
}
Minimum deployment target
The first question is: what is a sensible minimum deployment target? Seeing as this is a universal application, we should use iOS 4.2, as this is the first version that is unified between iPhone and iPad.
Question arises: what fraction of ad-clicking customers do we lose? eg is it worth supporting 4.0 just to get an extra 15% of customers?
http://insights.chitika.com/2011/ios-update-ipads-iphones-running-high-rate-of-ios-4/ shows that you still pick up 80% of ad-clicking customers if you select 4.2.
Obviously this fraction is going to increase with time, so I'm going for the easiest coding option rather than trying to squeeze every last penny out of the market.
This has an added benefit:
// Supported sizes of banner ads available from ad server. Dimensions are in points, not pixels.
// The dimensions are part of the value names to assist with design-time planning for view layout.
extern NSString * const ADBannerContentSizeIdentifier320x50 __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_NA,__MAC_NA,__IPHONE_4_0,__IPHONE_4_2);
extern NSString * const ADBannerContentSizeIdentifier480x32 __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_NA,__MAC_NA,__IPHONE_4_0,__IPHONE_4_2);
extern NSString * const ADBannerContentSizeIdentifierPortrait __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_2);
extern NSString * const ADBannerContentSizeIdentifierLandscape __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_2);
ie We can use the new symbols, which are generic (ie work for both iPhone and iPad)
strPortrait = ADBannerContentSizeIdentifierPortrait; // ADBannerContentSizeIdentifier320x50;
strLandscape = ADBannerContentSizeIdentifierLandscape; // ADBannerContentSizeIdentifier480x32;
The banner can be either horizontal or vertical, so you need to load in:
[self.adBannerView setRequiredContentSizeIdentifiers:
[NSSet setWithObjects: strPortrait, strLandscape, nil]
];
Then when the screen turns 90°, the AdBannerView needs to be told:
[self.adBannerView setCurrentContentSizeIdentifier: isLandscape ? strLandscape : strPortrait ];
Directly after this is set, you can query self.adBannerView.frame and get the new size
https://github.com/p-i-/iAdUniversalTemplate
This is a XIB-less universal-app iAd-rotatey-enabled template, which requires a minimum target of iOS 4.2
It took a lot of thrashing around, namely iAd -- cannot click banner
But it is in good shape now.

What is the best way to move a UIToolbar?

Here is an interesting problem. On the iPhone, I have a view set up that has a toolbar on the bottom of the screen. I am currently trying to make this a universal app so that it runs on iPad as well. I would like the toolbar to be at the top of the iPad screen, so in the viewDidLoad method of the specific viewController I have the following code.
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
//move the toolbar to the top of the page and move the webview down by the height of the toolbar
CGRect toolBarFrame = self.aToolBar.frame;
CGRect webFrame = self.aWebView.frame;
webFrame.origin.y = toolBarFrame.size.height;
[self.aWebView setFrame:webFrame];
toolBarFrame.origin.y = 0;
[self.aToolBar setFrame:toolBarFrame];
[Utils errorString:[NSString stringWithFormat:#"origen: x=%f y=%f", self.aToolBar.frame.origin.x, self.aToolBar.frame.origin.y]];
[Utils errorString:[NSString stringWithFormat:#"origen: x=%f y=%f", self.aWebView.frame.origin.x, self.aWebView.frame.origin.y]];
}
The problem I am having is that the webView moves down fine, but the toolbar only moves up to about what seems to be the height of a iPhone screen. The call to errorString tells me that the webView's origin is at 0,44 (where it should be) and that the toolbar's origin is at 0,0, but it is actually somewhere in the middle of the screen!
Anybody have a clue what is going on here?
I'd say this is because the frame is being set to the top of an iPhone frame (so 320px from the bottom), and then afterwards the view is being resized to fit the iPad screen. However UIToolbar by default is set to stick to the bottom of the window (fixed bottom margin, and flexible top margin) so it's staying 320px from the bottom.
To fix this try:
[self.aToolbar setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin];
before setting the toolbar's frame (if it doesn't work before setting the frame, try putting it after)

Is there a way to change the height of a UIToolbar?

I've got an UIToolbar in Interface Builder and I've noticed that it's locked to being 44px tall. Of course I'd like to make this larger.
Does Apple allow resizing of this control? If so, how do I go about it?
Sure, just set its frame differently:
[myToolbar setFrame:CGRectMake(0, 50, 320, 35)];
This will make your toolbar 35 pixels tall. Of course this requires an IBOutlet or creating the UIToolbar programmatically, but that's very easy to do.
If that does not work in SDK 6, it is possible to solve as below:
Select the toolbar element and choose Editor > Pin > Height to create a constraint.
Go to your View Controller Scene and select the created Height(44) constraint, then put the value you want.
I found that if I set the frame on the iPad, when hiding/showing the Toolbar would reset itself back to a height of 44 pixels. I ended up having to override UIToolbar and change the method:
// return 'best' size to fit given size. does not actually resize view. Default is return existing view size
- (CGSize)sizeThatFits:(CGSize)size {
CGSize result = [super sizeThatFits:size];
result.height = 55;
return result;
};
This would correct adjust the height even with the hide/show.
In iOS 6, with autolayout, the simplest approach is a UIToolbar subclass in which you override instrinsicContentSize. Here's code from one my apps, where the toolbar is tall. Its sides and bottom are pinned to the sides and bottom of the superview as usual.
-(CGSize)intrinsicContentSize {
return CGSizeMake(UIViewNoIntrinsicMetric, 85);
}
For Xcode 7.1 iOS 9, in auto layout, the size is locked to 44px. The Xcode menu option Editor > Pin > Height is not there, instead do the following action:
In InterfaceBuilder, click the toolbar element to select it.
Control+Drag down anywhere in the toolbar and release, a popup menu will display showing the option "Height" at the top, select it.
You now have a Height constraint to work with and adjust as necessary.
You could also just edit the xib file:
open it as source code and find the entry that defines the frame for the UIToolbar, something along the lines of
<string key="NSFrame">{{0,420}, {320,44}}</string>
and just change the value for 44 to whatever size you need.
This way the toolbar will be taller, and in InterfaceBuilder you'll see the new size grayed out and you'll be unable to change it, but you don't need any outlets or code.
As long as you have a height constraint on the toolbar you can use this little snippet that has helped me adjust heights for classes that inherit from UIView
-(void)setHeightConstraintTo:(CGFloat)height forView:(UIView *)view{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"firstAttribute = %d", NSLayoutAttributeHeight];
NSArray *filteredArray = [view.constraints filteredArrayUsingPredicate:predicate];
if(filteredArray.count > 0){
NSLayoutConstraint *constraint = filteredArray.firstObject;
constraint.constant = height;
}
}
I'm not sure how this would sit with Apple - and of course it depends how you wish to use the toolbar - but you can add a default UIView and change its class in the property inspector to UIToolbar. This gives you transparency and customisability (in this case height) for free, at the expense of the layout of bar button items.
Swift Solution:
myToolbar.frame = CGRect(x: myToolbar.frame.origin.x, y: myToolbar.frame.origin.y, width: myToolbar.frame.size.width, height: 20)
The CGRectMake is obsolete. This can be replaced with the CGRect. This will set the height of the toolbar to 20. The same works for Segmented control as well.
In interface builder, there is also the possibility to use "User Defined Runtime Attributes".
Simply add an entry with keypath set to "frame" of type "Rect" and set the value you want.