OSX Cocoa NSSearchField clear button not responding to click - swift

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.

Related

How can I round the corners of only one side of an NSButton in Swift 4?

I am new to Swift and Stack Overflow in general, so I hope I'm not asking much to bear with me.
I am trying to achieve a 'grouped' button style that can be found on Finder or in the XCode editor toolbar, like these two button groups. As you can see in the first group of buttons, the left button is only rounded on the left side, the centre button is not rounded at all, and the right button is only rounded on the right side. The same thing applies to the second group of buttons. I want to accomplish something like this, but I'm unsure of how to achieve this.
After searching for a solution online (including iOS tutorials), I tried providing an extension to the NSButton class and manually rounding the two left corners like so:
// Extensions.swift
extension NSButton {
func roundLeftCorners() {
self.layer?.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
self.layer?.cornerRadius = 20.0 // Some arbitrary number, just wanted to make the rounded corner visible
self.layer?.masksToBounds = true
}
}
Then, on my view controller's viewDidLoad() function, I tried calling this member:
// MyViewController.swift
class MyViewController: NSViewController {
#IBOutlet weak var leftButton: NSButton!
override func viewDidLoad() {
super.viewDidLoad()
leftButton.roundLeftCorners()
}
// ...
}
...but that didn't work for me. Some simple debugging showed that the Optional values of self.layer were nil, so I'm not sure what's going on there.
Next, I tried creating my own custom class and overriding the draw(_ dirtyRect:) function with the same code above, like so:
// LeftButton.swift
class LeftButton: NSButton {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
self.layer?.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
self.layer?.cornerRadius = 20.0
self.layer?.masksToBounds = true
}
}
// MyViewController.swift
class MyViewController: NSViewController {
#IBOulet weak var leftButton: LeftButton!
// ...
}
...but that didn't remove the rounded corners on the right side. Weirdly enough, the new cornerRadius value is only obvious if the number is around 50.0 or greater; any less and the left corners looks exactly the same as any other NSButton.
Some answers mentioned manually drawing the points in a path with NSBezierPath, but it doesn't achieve what I want. I also can't find any related properties/attributes on the Storyboard editor. Perhaps I've overcomplicated my approach to this seemingly easy problem, or maybe I'm not looking at it the right way, but I hope someone could help me with this. Thanks in advance!
1) The images you showed are using a simple NSSegmentedControl. Nothing needs customized.
2) What you tried to do wouldn't work anyway; If it could mechanically work, what it would end up doing is merely clipping the drawn content on the left corners. It wouldn't magically fill in drawing on the right, and create appropriate control borders etc.
AppKit controls are not merely CALayers with filled in properties like border, background, etc. They are almost all entirely drawn using Core Graphics via the classic drawRect: method one way or another. The fact that views have a layer is due to layer-backing. There are very few things you can end up doing with the layer of an existing control. To customize them properly, you would override the standard drawing routines in NSView, NSControl, NSCell, etc as appropriate.

NSScrollView not scrolling

I have a form in a Mac app that needs to scroll. I have a scrollView embedded in a ViewController. I have the scrollView assigned with an identifier that links it to its own NSScrollView file. The constraints are set to the top, right, and left of the view controller, it also has the hight constraint set to the full height of the ViewController.
Here is my code:
import Cocoa
class ScrollView: NSScrollView {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
NSRect documentView.NSMakeSize(0, 0, 1058.width, 1232.height)
}
override func scrollWheel(with event: NSEvent) {
switch event.phase {
case NSEvent.Phase.began:
Swift.print("Began")
// case NSEvent.Phase.changed:
// Swift.print("Changed")
case NSEvent.Phase.ended:
Swift.print("Ended")
default:
break
}
switch event.momentumPhase {
case NSEvent.Phase.began:
Swift.print("Momentum Began")
// case NSEvent.Phase.changed:
// Swift.print("Momentum Changed")
case NSEvent.Phase.ended:
Swift.print("Momentum Ended")
default:
break
}
super.scrollWheel(with: event)
}
I cant seem to get my app to scroll at all. I think I am not setting the frame correctly. What is the best way to do set the frame correctly? Am I coding the NSScrollView correctly?
I think you are making your life very hard because you are doing things that are not exactly recommended by Apple. First of all, you should not subclass NSScrollView. Rather you should read first Introduction to Scroll View Programming Guide for Cocoa by Apple to understand how you should create the correct hierarchy of views for an NSScrollView to work correctly.
A second recommendation is for you to check this nice article about how you should set up an NSScrollView in a playground, so that you can play with the code you want to implement.
Third, using Autolayout and NSScrollView has caused a lot of grief to a lot of people. You need to set up the AutoLayout just right, so that everything is going to work as expected. I recommend that you check this answer by Ken Thomases, which clearly explains how you need to set up auto layout constraints for an NSScrollView to work properly.
I just got over the "hump" with a NSScrollView inside a NSWindow. In order for scrolling to occur the view inside the NSScrollview needs to be larger than the content window. That's hard to set with dynamic constraints. Statically setting the inner view to a larger width/height than the window "works" but the static sizes usually are not what you want.
Here is my interface builder view hierarchy and constraints, not including the programmatically added boxes
In my app the user is adding "boxes" (custom draggable views) inside the mainView, which is inside a scrollview in a NSwindow.
Here's the functionality I wanted:
If I expanded the NSWindow, I wanted the mainView inside the scrollview to expand to fill the whole window. No scrolling needed in this case if all the boxes are visible.
If I shrank the NSWindow, I wanted the mainView inside the scrollview to shrink just enough to include all my mainView subviews ("boxes"), but not any further (i added a minBorder of 20). This results in scrolling if a box's position is further right/up than the nswindow's width/height.
I found the trick is to calculate the size of the mainView I want based on the max corner of each draggable boxview, or the height/width of the content frame of the nswindow, whichever is larger.
Below is my code, including some debugging prints.
Be careful of which subviews you use to calculate the max size. If you include a subview that's dynamically attached to the right/top of the window, then your window will never shrink. If you add +20 border to that, you might infinite loop. Not a problem in my case.
extension MapWindowController: NSWindowDelegate {
func windowDidEndLiveResize(_ notification: Notification) {
if let frame = window?.frame, let content = window?.contentRect(forFrameRect: frame) {
print("window did resize \(frame)")
var maxX: CGFloat = content.width
var maxY: CGFloat = content.height
for view in mainView?.subviews ?? [] {
let frameMaxX = view.frame.maxX + minBorder
let frameMaxY = view.frame.maxY + minBorder
if frameMaxX > maxX {
maxX = frameMaxX
}
if frameMaxY > maxY {
maxY = frameMaxY
}
}
print("view maxX \(maxX) maxY \(maxY)")
print("window width \(content.width) height \(content.height)")
mainView?.setFrameSize(NSSize(width: maxX, height: maxY))
}
}
}

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

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.

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.

Set of UIImageView.Image re-positions parent view

I have a UIView and added a UIImageView as child to it. I later change the position of the UIView via another UIButton event.
If I change the image of the UIImageView the UIView - inc. all shields - moves back to the initial position.
Could anyone maybe explain me what is going on here :-D How do I keep the new position of the UIView ?
Here a image screen I took to visualize the issue
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var number: UIImageView!
#IBOutlet weak var popup: UIView!
#IBAction func openPopup(sender: UIButton) {
self.popup.center = sender.center
//old position: (190.5, 124.0) - new Position: (173.0, 414.0)
}
#IBAction func updateImage(sender: UIButton) {
self.number.image = UIImage(named: "img-1")
}
}
Update:
I found out that above code works if I deactivate autolayout. Here is a solution to do this with auto layout:
Auto Layout center UIView above another UIView at its center
Are you sure of that? Did you check by logging popup.frame.origin after updateImage being called? Have you properly set clipsToBounds and contentMode properties for number uiimageview?
I found out that above code works if I deactivate "Use Auto Layout" in Interface Builder Document section at tab File Inspector.
In addition here is a working solution to the issue using auto layout and constraints:
Auto Layout center UIView above another UIView at its center