UITabBar / UITabBarItem and iOS 13's Dark Mode - uitabbaritem

In our app, we use custom UITabBarItem dynamically (see below) programmatically. We use ImageAssets to get the dark/light right images. But it doesn't work at 100% : if we restart the app, it's fine. If iOS switch to dark mode and the app is running, images keep the light mode. Wondering if it's a bug or if I can manage to make it work now...
UIImage *mImage = [UIImage imageNamed:#"tabBar1"];
UIImage *mImageSel = [UIImage imageNamed:#"tabBar1Sel"];
mImage = [mImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
mImageSel = [mImageSel imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
self.tabBarItem = [[UITabBarItem alloc] initWithTitle:nil image:mImage selectedImage:mImageSel];
UIEdgeInsets titleInsets = UIEdgeInsetsMake(-6, 0.0, -6.0, 0.0);
self.tabBarItem.imageInsets = titleInsets;
No error messages are displayed...

We're seeing the same issue with programmatically created UITabBarItems.
We tried recreating the TabBarItem within UIViewController.traitCollectionDidChange(:) and the issue persists. Seems like an Apple bug. Notably, the issue only occurs for SelectedImage for us. The default, non-selected state seems to respect UIUserInterfaceStyle changes (dark mode).
We found a temporary solution: if you reassign the same selectedImage to myViewController.tabBarItem within UIViewController.traitCollectionDidChange(:) the issue resolves.
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
self.tabBarItem.selectedImage = mySelectedImage //same image asset that was used in the initializer
}
Not sure why this would fix it but it works for us.

Related

OSX NSVisualEffectView Decrease Blur Radius [duplicate]

Is it possible to adjust the blur radius and transparency of an NSVisualEffectView when it's applied to an NSWindow (Swift or Objective-C)? I tried all variations of NSVisualEffectMaterial (dark, medium, light) - but that's not cutting it. In the image below I've used Apple's non-public API with CGSSetWindowBackgroundBlurRadius on the left, and NSVisualEffectView on the right.
I'm trying to achieve the look of what's on the left, but it seems I'm relegated to use the methods of the right.
Here's my code:
blurView.blendingMode = NSVisualEffectBlendingMode.BehindWindow
blurView.material = NSVisualEffectMaterial.Medium
blurView.state = NSVisualEffectState.Active
self.window!.contentView!.addSubview(blurView)
Possibly, related - but doesn't answer my question:
OS X NSVisualEffect decrease blur radius? - no answer
Although I wouldn't recommend this unless you are ready to fall back to it not working in a future release, you can subclass NSVisualEffectView with the following to do what you want:
- (void)updateLayer
{
[super updateLayer];
[CATransaction begin];
[CATransaction setDisableActions:YES];
CALayer *backdropLayer = self.layer.sublayers.firstObject;
if ([backdropLayer.name hasPrefix:#"kCUIVariantMac"]) {
for (CALayer *activeLayer in backdropLayer.sublayers) {
if ([activeLayer.name isEqualToString:#"Active"]) {
for (CALayer *sublayer in activeLayer.sublayers) {
if ([sublayer.name isEqualToString:#"Backdrop"]) {
for (id filter in sublayer.filters) {
if ([filter respondsToSelector:#selector(name)] && [[filter name] isEqualToString:#"blur"]) {
if ([filter respondsToSelector:#selector(setValue:forKey:)]) {
[filter setValue:#5 forKey:#"inputRadius"];
}
}
}
}
}
}
}
}
[CATransaction commit];
}
Although this doesn't use Private APIs per se, it does start to dig into layer hierarchies which you do not own, so be sure to double check that what you are getting back is what you expect, and fail gracefully if not. For instance, on 10.10 Yosemite, the Backdrop layer was a direct decedent of the Visual Effect view, so things are likely to change in the future.
I had the same issue as you had and I have solved it with a little trick seems to do the job that I wanted. I hope that it will also help you.
So in my case, I have added the NSVisualEffectView in storyboards and set its properties as follows:
and my View hierarchy is as follows:
All that reduces the blur is in the NSViewController in:
override func viewWillAppear() {
super.viewWillAppear()
//Adds transparency to the app
view.window?.isOpaque = false
view.window?.alphaValue = 0.98 //tweak the alphaValue for your desired effect
}
Going with your example this code should work in addition with the tweak above:
let blurView = NSVisualEffectView(frame: view.bounds)
blurView.blendingMode = .behindWindow
blurView.material = .fullScreenUI
blurView.state = .active
view.window?.contentView?.addSubview(blurView)
Swift 5 code
For anyone interested here is a link to my repo where I have created a small prank app which uses the code above:
GitHub link

Issue with portrait display after playing landscape video

I have an iPhone app that displays files to the user (videos, pdf files and documents). When you click on a file thumbnail in my FileViewController I call UIDocumentInteractionController to present the file to the user. All app screens are locked to portrait orientation apart from the document controller, that can be rotated to view landscape. This has all worked fine in previous releases, but it has suddenly changed and broken, I presume when iOS 8 was released.
Now, if I play a video and rotate it to landscape and hit done, the video is removed from the screen and the FileViewController nib that called the document the view is cut off half way down the screen.
I've done some testing and have printed out the view bounds width and height. Testing on an iPhone 4S, when I first load the FileViewController the width and height is 320 x 480, correct.
When I play a video in landscape mode and hit done, the underlaying view is automatically rotated back to portrait as landscape isn't support in this view, but when printing out the screen bounds the width is 480 and height 320. It still thinks the app is in landscape even though it's been moved back to portrait. The screen itself is the right way round, just cut off after 320 pixels!
I've been trying to manipulate the orientation of alsorts of controls but I can't see what's even generating the issue, let alone fixing it.
This is how I have implemented by orientations:
The App has been set up to support ALL orientations.
I have subclasses the tab bar controller to specify the following:
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return UIInterfaceOrientationPortrait;
}
Within FileViewController I call the following code:
- (void) Show_File:(File *)file
{
NSURL *targetURL = [NSURL fileURLWithPath:filePath];
UIDocumentInteractionController *document = [UIDocumentInteractionController interactionControllerWithURL: targetURL];
document.delegate = self;
[document presentPreviewAnimated: YES];
}
- (UIViewController *)documentInteractionControllerViewControllerForPreview: (UIDocumentInteractionController *) controller
{
AppDelegate *theDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
return theDelegate.fileNavController.childViewControllerForStatusBarHidden;
}
Any ideas? I've search around but I can't find anyone having this particular issue. Thanks.
Just for anyone else experiencing this issue, I raised a support ticket with Apple and apparently it is an iOS bug....
"This is a bug QLPreviewController (which is used by UIDocumentInteractionController to display previews)"
I have a work around which is working ok for now. In my subclass of UITabBarController I've incldued this code:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (self.transitionCoordinator && self.presentedViewController.presentationController {
self.view.frame = self.presentedViewController.presentationController.containerView.bounds;
}
}

NSTabViewController ignoring transitions and title propagation settings

I am trying to create a preferences panel for my app using storyboards and the new NSTabViewController class.
I can get it working, but the transition setting in the storyboard seems to be ignored. It just jumps from one tab to the next, with the size of the window changing instantaneously.
I thought it might depend on whether I use autolayout or not, but it didn't seem to change the transition behavior when I toggled it.
I also have the 'Propagates title' setting checked. I had expected that it would take the label of the tab item, or the title of the view controller, and propagate that as the window title, but it doesn't seem to do that.
Anyone got this to work?
Here is a simple sample app I am testing with: https://www.dropbox.com/s/roxaplxy5gtlqns/Again.zip?dl=0
Update: Got this working thanks to Pierre. Ended up making a nice transitioning preferences window by subclassing NSTabViewController as follows:
#implementation MCPreferencesTabViewController
-(void)tabView:(NSTabView *)tabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem
{
[super tabView:tabView willSelectTabViewItem:tabViewItem];
NSTabViewItem *currentTabItem = tabView.selectedTabViewItem;
currentTabItem.view.hidden = YES;
tabViewItem.view.hidden = YES;
NSWindow *window = self.view.window;
NSSize contentSize = tabViewItem.view.fittingSize;
NSSize newWindowSize = [window frameRectForContentRect:(CGRect){CGPointZero, contentSize}].size;
NSRect frame = [window frame];
frame.origin.y += frame.size.height;
frame.origin.y -= newWindowSize.height;
frame.size = newWindowSize;
[self.view.window setFrame:frame display:NO animate:YES];
}
- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
{
[super tabView:tabView didSelectTabViewItem:tabViewItem];
tabViewItem.view.hidden = NO;
}
#end
You need to make the NSTabViewController the delegate of the NSTabView.
In Interface Builder, control-drag from No Shadow Tab View to Tab View Controller and set the delegate outlet.
One would expect Interface Builder to set this up correctly when creating a new tab view controller. It does not.
Please note that this was true until Xcode 9.
Since Xcode 9, you need to remove (or not add it) this line:
self.tabView.delegate = self
otherwise you will receive an error:
*** Assertion failure in -[YourApp.CustomTabView setDelegate:],
/BuildRoot/Library/Caches/com.apple.xbs/Sources/AppKit/AppKit-1561.0.100/AppKit.subproj/NSTabView.m:2766
2017-10-25 19:29:06.301282+0200 YourApp[23106:5687795] Failed to set (contentViewController) user defined inspected property on (NSWindow):
A TabView managed by a TabViewController cannot have its delegate modified
Or in viewDidLoad() for the NSTabViewController, include
self.tabView.delegate = self

Transparent background WKWebView (NSView)

I am building a Mac application using Swift. Therefor, I want to make a WKWebView transparent, so it shows the text of the loaded HTML, but the background of my underlaying NSWindow is visible.
I tried
webView.layer?.backgroundColor = NSColor.clearColor().CGColor;
which hadn't any effect. WKWebView inherits from NSView, but I don't know if this helps.
Another solution would be to insert a NSVisualEffectView as the background of the WebView, but I don't know how to accomplish that, either!
Use this in macOS 10.12 and higher:
webView.setValue(false, forKey: "drawsBackground")
It was not supported, then they fixed it:
https://bugs.webkit.org/show_bug.cgi?id=134779
The way to make it transparent is to:
myWebView.opaque = false
Code below works for me perfectly, also color is set to clearColor by default.
[wkWebView setValue:YES forKey:#"drawsTransparentBackground"];
I used this for macOS 10.12. without problems in OjbC:
[self.webView setValue:#YES forKey:#"drawsTransparentBackground"];
Under macOS 10.13.+ I got the following console warning message:
-[WKWebView _setDrawsTransparentBackground:] is deprecated and should not be used
The ONLY working solution was:
[self.webView setValue:#(NO) forKey:#"drawsBackground"];
I tried the below in many scenarios and it didn't work:
give the webView and the enclosingScrollView a layer and edit it's properties (backgroundColor, isOpaque)
give the webView and the enclosingScrollView a clear background color
inject javascript without the setValue forKey: in the webview.
Additionally I did use:
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation
{
if (self.isWebviewsBackgroundTransparent) {
[self insertTransparentBackgroundTo:webView];
}
}
- (void)insertTransparentBackgroundTo:(WKWebView *)webView
{
NSString *transparentBackgroundJSSString = #"document.body.style = document.body.style.cssText + \";background: transparent !important;\";";
[webView evaluateJavaScript:transparentBackgroundJSSString completionHandler:nil];
}
Updated, slightly better solution (2022). There is a private property drawsBackground on WKWebViewConfiguration. This property has been introduced in macOS 10.14 so it won't go away.
//https://opensource.apple.com/source/WebKit2/WebKit2-7610.2.11.51.8/UIProcess/API/Cocoa/WKWebViewConfigurationPrivate.h.auto.html
//One can verify that the property still exists:
//https://github.com/WebKit/WebKit/blob/main/Source/WebKit/UIProcess/API/Cocoa/WKWebViewConfigurationPrivate.h
#property (nonatomic, setter=_setDrawsBackground:) BOOL _drawsBackground WK_API_AVAILABLE(macos(10.14), ios(12.0));
Example:
let configuration = WKWebViewConfiguration()
var requiresDrawBackgroundFallback = false
if #available(OSX 10.14, *) {
configuration.setValue(false, forKey: "sward".reversed() + "background".capitalized) //drawsBackground KVC hack; works but private
} else {
requiresDrawBackgroundFallback = true
}
let webView = WKWebView(frame: .zero, configuration: configuration)
if requiresDrawBackgroundFallback {
webView.setValue(false, forKey: "sward".reversed() + "background".capitalized) //drawsBackground KVC hack; works but private
}

LaunchServices: invalidationHandler called - iOS 8 share sheet

Seeing this error message in the logs, though not consistently, around the time that I use SLComposeViewController to open a Twitter or Facebook share sheet. I am not using any new iOS 8 API, just testing existing code on iOS 8. I see others have had this problem and even seen crashes when using other modal view controllers from the Cocoa Touch SDK.
LaunchServices: invalidationHandler called
Are there new precautions to take with SLComposeViewController and UIActivityViewController in iOS 8? Something else to consider?
Add this code after you present your activity view controller:
if ([activityVC respondsToSelector:#selector(popoverPresentationController)])
{
// iOS 8+
UIPopoverPresentationController *presentationController = [activityVC popoverPresentationController];
presentationController.sourceView = sender; // if button or change to self.view.
}
Looking at the developer forums: "That log message does not indicate any error on your part."
I had a similar problem with a UIDocumentInteractionController, where when I tapped outside it to dismiss it, or selected another app to open the document in, it would crash with the "LaunchServices: invalideationHandler called" console message displayed twice (only using iOS 8).
A workaround is to add the call to presentOpenInMenuFromRect:inView:animated to the main queue, i.e.
dispatch_async(dispatch_get_main_queue(), ^() {
[self.documentInteraction presentOpenInMenuFromRect:theRect inView:self.view animated:YES];
});
You may also need to define the sourceRect. I used the following code to display a SLComposeViewController from a tableView.
if ([controller respondsToSelector:#selector(popoverPresentationController)]) {
//get rect for this row in table
CGRect frame = [self.tableView rectForRowAtIndexPath:indexPath];
//convert table row frame to view reference
CGRect frameInView = [self.tableView convertRect:frame toView:self.view];
[controller popoverPresentationController].sourceRect = frameInView;
[controller popoverPresentationController].sourceView = self.view;
}
Regarding the auto-closing (not the crash):
I think it's probably related to the link you are trying to share. I'm seeing the same thing when trying to post music links (Spotify, SoundCloud,...). The same tweet works if I replace the link by a link to some non-media-content. I'll file radar on this to see whether it's intentional...
This gets rid of the Error message for me and works as expected. You have to get rid of the if statement that calls "isAvailableForServiceType:"
It should look like this. Happy coding.
SLComposeViewController *tweetSheet = [SLComposeViewController
composeViewControllerForServiceType:SLServiceTypeTwitter];
[tweetSheet setInitialText:#"Great fun to learn iOS programming at appcoda.com!"];
[self presentViewController:tweetSheet animated:YES completion:nil];
if ([tweetSheet respondsToSelector:#selector(popoverPresentationController)])
{
// iOS 8+
UIPopoverPresentationController *presentationController = [tweetSheet popoverPresentationController];
presentationController.sourceView = sender; // if button or change to self.view.
}

Resources