Cursor isn’t changed properly when drag a file to a draggable view on Cocoa's WKWebView - swift

I wrote the following code to put a draggable view on WKWebView.
With this code, I expected a "+" icon will be displayed nearby a cursor when I dragged a file to the view.
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let rect = NSRect(x: 0, y: 0, width: 200, height: 200)
let webView = WKWebView(
frame: rect,
configuration: WKWebViewConfiguration())
webView.load(URLRequest(
url: URL(string: "https://i.imgur.com/D5ru3Q7.jpg")!))
let draggableView = DraggableView(frame: rect)
draggableView.registerForDraggedTypes([.fileURL])
self.view.addSubview(webView)
self.view.addSubview(draggableView)
}
}
class DraggableView: NSView {
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
return .copy
}
}
The result is here:
Sometimes the cursor changes to a magnifying glass (same as a mouse over behavior on WKWebView).
And sometimes "+" icon is shown.
I think the webView prevents the cursor changing.
So I tried the followings. But I couldn't fix.
Overriding hitTest of the webView worked only for non-dragging mouse over actions. But not for dragging.
webView.unregisterDraggedTypes() didn't work.
Is there anyway to fix this?

Found a solution:
for t in webView.trackingAreas {
webView.removeTrackingArea(t)
}
I think removing tracking areas on a web view is not a good idea.
But at least it satisfies my need.

Related

How to customize preview when context menu is presented in swift

I have created custom UIViewController and included it in contextMenuInteraction (below example):
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: nil, previewProvider: MyPawnPreviewProvider.init) { _ -> UIMenu? in
return self.createContextMenu()
}
}
I don't know what to put inside this class to properly customize it:
class MyPawnPreviewProvider: UIViewController {
private let imageView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(imageView)
}
}
What I want to achieve is when I long tap a knight icon (see screenshot), I want context menu to appear but without blurred background and without rescaling the icon.
the board of the game with knight icons
Default behaviour is that the icon is zoomed and background is blurred. I want the board to be visible all the time (apart from the context menu area of course).
after tap and hold the knight icon presenting context menu

How to make a NSTextView, that is added programmatically, active?

I am making an app where a user can click anywhere on the window and a NSTextView is added at the mouse location. I have got it working with the below code but I am not able to make it active (in focus) after adding it to the view (parent view). I have to click on the NSTextView to make it active but this is not what I want. I want it to automatically become active when its added to the parent view.
Code in my ViewController to add the NSTextView to its view:
private func addText(at point: NSPoint) {
let textView = MyTextView(frame: NSRect(origin: point, size: CGSize(width: 150.0, height: 40.0)))
view.addSubview(textView)
}
MyTextView class looks like below:
class MyTextView: NSTextView {
override var shouldDrawInsertionPoint: Bool {
true
}
override var canBecomeKeyView: Bool {
true
}
override func viewWillDraw() {
isHorizontallyResizable = true
isVerticallyResizable = true
insertionPointColor = .red
drawsBackground = false
isRichText = false
allowsUndo = true
font = NSFont.systemFont(ofSize: 40.0)
}
}
Also, I want it to lose focus (become inactive) when some other elements (view) are clicked. Right now, once a NSTextView becomes active, it stays active no matter what other elements I click except when I click on an empty space to create yet another NSTextView.
I have gone through the Apple docs multiple times but I think I am missing something. Any help would be much appreciated.
Get the NSWindow instance of the NSViewController's view and call makeFirstResponder passing the text view as parameter.
To lose focus call makeFirstResponder passing nil.

How to give an NSOutlineView a tiled background image that scrolls?

I have a standard NSOutlineView. I would like it to have a background image, which tiles vertically, and which scrolls together with the outline view cells.
I've somewhat achieved this using the following in my ViewController:
class ViewController: NSViewController {
#IBOutlet weak var outlineView: NSOutlineView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if let image = NSImage(named: "tile") {
let color = NSColor.init(patternImage: image)
outlineView.backgroundColor = color
}
}
}
That works, except when you scroll past the top or bottom of the view (with the stretch provided by the containing scroll view).
I've tried putting the background image on the scroll view, but then it is static and doesn't scroll with the outline view's content.
I've also tried subclassing various objects in the view hierarchy and overriding their draw(_ dirtyRect: NSRect) method and doing:
self.wantsLayer = true
self.layer?.backgroundColor = ...etc
but got no success from that either.
Can anyone provide any suggestions?
I ended up creating a new custom NSView:
class MyView: NSView {
override func draw(_ dirtyRect: NSRect) {
if let image = NSImage(named: "Tile") {
let color = NSColor.init(patternImage: image)
color.setFill()
dirtyRect.fill()
}
super.draw(dirtyRect)
}
}
Then in my ViewController class I added an instance of the custom view, and used autolayout constraints to pin the new view to my outlineView's clip view starting 2000points above it, and ending 2000 below. This means no matter how far you over-scroll into the stretch area, you still see the tiled background.
class MyViewController: NSViewController {
#IBOutlet weak var outlineView: NSOutlineView!
override func viewDidLoad() {
super.viewDidLoad()
guard let clipView = self.outlineView.superview else { return }
let newView = MyView(frame: .zero) // Frame is set by autolayout below.
newView.translatesAutoresizingMaskIntoConstraints = false
clipView.addSubview(newView, positioned: .below, relativeTo: self.outlineView)
// Add autolayout constraints to pin the new view to the clipView.
// See https://apple.co/3c6EMcH
newView.leadingAnchor.constraint(equalTo: clipView.leadingAnchor).isActive = true
newView.widthAnchor.constraint(equalTo: clipView.widthAnchor).isActive = true
newView.topAnchor.constraint(equalTo: clipView.topAnchor, constant: -2000).isActive = true
newView.bottomAnchor.constraint(equalTo: clipView.bottomAnchor, constant: 2000).isActive = true
}
}
I've removed other code from the above so hopefully I've left everything needed to illustrate the solution.

OSX Cocoa NSSearchField clear button not responding to click

I place an NSSearchField and set its border to none and I found that the clear button is not clickable a.k.a. not responding when clicked. If I set the border again it's working fine.
I've been debugging this for a few hours, and found out that when I set the border to none, the text editor width will expand and shadow (cover) the clear button.
Screenshot
View hierarchy debug screenshot
Steps to reproduce:
Create an empty cocoa project/app
Place an NSSearchField
Set border to none
Run the app, fill the search field and try to click the clear button
Is this a bug? Or is it intended to behave that way?
Note: Newbie in cocoa development
I faced with this problem and deemed it as a bug in Cocoa. But it is easy to fix in custom control or in a view controller. Just keep text field bordered in interface builder and then kill the border by having new CALayer. For example:
class ViewController: NSViewController {
#IBOutlet weak var searchField: NSSearchField!
override func viewDidLoad() {
super.viewDidLoad()
let maskLayer = CALayer()
searchField.layer = maskLayer
maskLayer.backgroundColor = searchField.backgroundColor?.CGColor
}
}
As you see, I am just restoring control color in new layer not preserving anything else. It is not perfect, but at least gives good start.
julia_v's answer is almost correct. You should also remove searchButtonWidth from rect.origin.x to offset rect back.
And also I've added some more logic to make these "tricks" only, when it needed.
override func select(withFrame rect: NSRect, in controlView: NSView, editor textObj: NSText, delegate: Any?, start selStart: Int, length selLength: Int) {
var newRect = rect
if !isBordered || isBezeled {
let cancelButtonWidth = NSWidth(cancelButtonRect(forBounds: rect))
let searchButtonWidth = NSWidth(searchButtonRect(forBounds: rect))
newRect.size.width -= (cancelButtonWidth + searchButtonWidth)
newRect.origin.x += searchButtonWidth
}
super.select(withFrame: newRect, in: controlView, editor: textObj, delegate: delegate, start: selStart, length: selLength)
}
After creating subclass simply set it to NSSearchFieldCell instance in IB identity inspector.
Had the same problem in NSSearchField, created in code. Solved it by overriding the NSSearchFieldCell method in a subclass:
- (void)selectWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject start:(NSInteger)selStart length:(NSInteger)selLength
{
NSRect newRect = aRect;
newRect.size.width -= (NSWidth([self searchButtonRectForBounds:aRect]) + NSWidth([self cancelButtonRectForBounds:aRect]));
[super selectWithFrame:newRect inView:controlView editor:textObj delegate:anObject start:selStart length:selLength];
}
This method is called after the mouse click on the text area of the field. It also appeared to be a nice place to set the color of the insertion point.

Is it possible to make a circular NSButton?

I am trying to create a custom shape NSButton. In particular I am trying to make a round button, using a custom image. I've found a tutorial on the creation of custom UIButton and tried to adapt it to NSButton. But there's a huge problem. clipsToBounds seems to be iOS only(
Here's my code:
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var mainButton: NSButton!
var size = 32
override func viewDidLoad() {
super.viewDidLoad()
configureButton()
// Do any additional setup after loading the view.
}
func configureButton()
{
mainButton.wantsLayer = true
mainButton.layerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay
mainButton.layer?.cornerRadius = 0.5 * mainButton.bounds.size.width
mainButton.layer?.borderColor = NSColor(red:0.0/255.0, green:122.0/255.0, blue:255.0/255.0, alpha:1).CGColor as CGColorRef
mainButton.layer?.borderWidth = 2.0
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
}
What am I doing wrong? How can I make a circular NSButton? Can you suggest anything on replacing clipsToBounds?
Because here is what I was able to get so far:
NSButton is a subclass of NSView, so all methods in NSView, such as drawRect(_:), are also available in NSButton.
So create a new Button.swift which you draw your custom layout
import Cocoa
class Button: NSButton {
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
// Drawing code here.
let path = NSBezierPath(ovalIn: dirtyRect)
NSColor.green.setFill()
path.fill()
}
}
Great Tutorial Here. It is iOS , but quite similar!
Don't play around with corner radius. A circle doesn't have corners. To make the button appear as a circle, mask the button's layer to a circle.
You are setting the radius based upon the width of the button. By the look of your screenshot, the button is not a square when you start, so rounding the corners cannot create a circle.