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
}
Related
I'm trying to make RTCMTLNSVideoView available in webrtc for mac to fill the entire container view (videoContainerView is the container NSView, rtcTrack is RTCVideoTrack)
let videoView = RTCMTLNSVideoView(frame: .zero)
videoView.frame = videoContainerView.bounds
videoView.makeBackingLayer()
videoView.layer = CAMetalLayer()
videoView.wantsLayer = true
videoView.layer?.backgroundColor = NSColor.red.cgColor
videoView.layer?.contentsGravity = .resizeAspectFill
rtcTrack?.add(videoView)
videoContainerView.addSubview(videoView)
It results with video being fit only using one size (aspectFit). I can't seem to make it fill the entire container. iOS webrtc has a property videoContentMode. On mac it's missing. How to control the video content mode in this case on mac webrtc? Any ideas appreciated.
Thanks
Easy fix:
if let metalView = videoView.subviews.first(where: { view in view is MTKView }) {
metalView.layerContentsPlacement = .scaleProportionallyToFill
}
Old answer:
As I said in the comments, you have to access private property metalView.
To do this, create file named RTCMTLNSVideoView+Private.h in your project with the following code
#import <MetalKit/MetalKit.h>
#import "WebRTC/RTCMTLNSVideoView.h"
#interface RTCMTLNSVideoView ()
#property(nonatomic, strong) MTKView *metalView;
#end
And then you can reassign content mode:
videoView.metalView.layerContentsPlacement = .scaleProportionallyToFill
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.
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
I load the webview and set allowsScrolling to NO, but webview still shows scroll bars... Banging your head on your computer hurts a lot more now that MacBooks have sharp metal edges.
My code:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
NSString *webFolder = #"file:///<WebFolderPath>";
[[[productWeb mainFrame] frameView] setAllowsScrolling:NO];
[productWeb setFrameLoadDelegate:self];
[[productWeb mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[webFolder stringByAppendingString:#"webpage.html"]]]];
}
I even setup the frame loading delegate to report about the scrolling status:
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
{
NSLog(#"Scrolling %#",[[frame frameView] allowsScrolling] ? #"Allowed" : #"Not Allowed");
[[frame frameView] setAllowsScrolling:NO];
NSLog(#"Scrolling %#",[[frame frameView] allowsScrolling] ? #"Allowed" : #"Not Allowed");
}
Which still gives me the unhappy:
2010-08-24 15:20:09.102 myApp[30437:a0f] Scrolling Allowed
2010-08-24 15:20:09.104 myApp[30437:a0f] Scrolling Not Allowed
And yet the scrollbars continue to show! Hopefully, it is something stupid I'm doing as I don't want to get any more blood on my laptop.
I found I had to edit the HTML of the page I was trying to display to make sure that it was setup to take the full screen (and not more)... there was a DIV that had a minimum width set. Once I made sure the body had height = 100% and that none of the DIV's had a fixed or minimum width set that was smaller then the box I wanted to show it in everything came together :)
Are you trying to prevent the user from scrolling the view at all? You can just set productWeb.userInteractionEnabled = NO. Or are you just trying to prevent the bars from showing when the user is scrolling?
Here's another thing you can try: inject some JavaScript into your UIWebView that disables the touchmove event:
[productWeb stringByEvaluatingJavaScriptFromString:#"document.ontouchmove = function(e){e.preventDefault();}"];
This leaves the user interaction enabled, but should prevent any scrolling.
Try this if you use Swift:
import Foundation
import Cocoa
import WebKit
class WebViewRep: WebView {
//... hidden code
required init?(coder: NSCoder) {
super.init(coder: coder)
//Disable scrolling
self.mainFrame.frameView.allowsScrolling = false
//Sets current object as the receiver of delegates
self.policyDelegate = self
self.frameLoadDelegate = self
//Load homepage
self.loadHomePage()
}
//... hidden code
override func webView(sender: WebView!, didFinishLoadForFrame frame: WebFrame!) {
//Completely disable scroll
frame.frameView.allowsScrolling = false
self.stringByEvaluatingJavaScriptFromString("document.documentElement.style.overflow = 'hidden';")
}
}
Sadly, it's not enough to use allowScrolling. It works sometimes, but not always. So, you have to manipulate loaded page too.
Of course, don't forget to set WebViewRep as custom class for your WebView.
I'm guessing your web view is in a nib that's being loaded before -applicationDidFinishLaunching: is called.
A quick fix:
In your nib, select the window and deselect "Visible at Launch" attribute.
Then after you call -setAllowsScrolling:, call -[self.window makeKeyAndOrderFront:nil] to show the window.
A better fix is to move your window loading code from the app delegate to a window controller subclass, do the call to -setAllowsScrolling: in -[NSWindowController windowDidLoad], and call -[NSWindowController showWindow:] from your app delegate to show the window.
I am using textview in cocoa
How we show a HTML data in textview
You didn't say if you're targeting the Mac or the iPhone. I'll assume the Mac since you tagged your question with cocoa.
It would be worth looking into the Text System documentation for an overview of how NSTextView works. But at a basic level, all NSTextViews have an NSTextStorage object (accessible via the textStorage method) NSTextStorage happens to be a subclass of NSMutableAttributedString.
NSAttributedString has an initializer called initWithHTML:baseURL:documentAttributes: which will parse an HTML string for you. Once you've got that, you can append the resulting string to your NSTextView's textStorage. For example:
NSData *htmlData = // get the html, e.g. from a file
NSURL *aBaseURL = // a URL used to resolve relative links e.g. '/directory/page.html'
NSAttributedString *formattedHTML = [[NSAttributedString alloc] initWithHTML:htmlData baseURL:aBaseURL documentAttributes:NULL];
[[myTextView textStorage] appendAttributedString:formattedHTML];
[formattedHTML release];
You can use an UIWebView and fill it with a string instead of a webpage.
Why not use a WebView (part of the WebKit framework)? Although NSTextView has rudimentary HTML support, WebView gives you all of the power of the modern, standards-compliant, WebKit framework.
If macOS NSTextView
Swift 5:
let data = htmlString.data(using: .utf8)!
if let attributedString = NSAttributedString(html: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding:String.Encoding.utf8.rawValue], documentAttributes: nil)
{
textView.textStorage?.append(attributedString)
textView.textColor = .labelColor
textView.font = NSFont.systemFont(ofSize: 15)
}