NSTextField force becomeFirstResponder removes text - swift

Using NSTextField on Custom NSTableCellView.
When the user presses anywhere on the cell, I would like NSTextField to become the first responder.
When I do, the text disappears, and the user needs to press any keyboard key for it to come back, how to disable this behavior?
Code:
override func mouseDown(with event: NSEvent) {
super.mouseDown(with: event)
if (event.clickCount == 2) {
self.beginEditing()
} else {
self.endEditing()
}
}
private func beginEditing() {
self.lblTitle.isEditable = true
self.window?.makeFirstResponder(self.lblTitle)
self.lblTitle.backgroundColor = Colors.clear
self.lblTitle.borderColor = Colors.clear
}
private func endEditing() {
self.lblTitle.isEditable = false
self.lblTitle.backgroundColor = Colors.red
}

Removing this lines solved it
self.lblTitle.backgroundColor = Colors.clear
self.lblTitle.borderColor = Colors.clear

Related

Swift 5 - Mac OS - NSTrackingArea overlapping views

Currently I have a little issue when it comes to buttons(NSButton) which have a tracking area and views(NSView overlay) above these buttons, this is my setup:
Custom button:
class AppButton: NSButton {
override func updateTrackingAreas() {
super.updateTrackingAreas()
let area = NSTrackingArea(
rect: self.bounds,
options: [.mouseEnteredAndExited, .activeAlways],
owner: self,
userInfo: nil
)
self.addTrackingArea(area)
}
override func mouseEntered(with event: NSEvent) {
NSCursor.pointingHand.set()
}
override func mouseExited(with event: NSEvent) {
NSCursor.arrow.set()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
An instance of this button class is used i a very basic NSView.
When I hover over the button, the cursor changes correctly.
When I click the button a new overlay(NSView) is opened above the button...
This is where the problem starts:
When I hover over the overlay where my button is placed, the cursor still changes...
I did not know that a NSTrackingArea is going through all views..
How can i solve this issue?
Can I set any property on the overlay(NSView) to somehow disable the NSTrackingArea on the button?
Thanks!!
You can subclass NSView and add local monitoring for events. Check if the event has occurred over the view and if true return nil. This will avoid propagating the events being monitored. If the event is outside the view frame you can propagate it normally returning the event monitored.
class CustomView: NSView {
override func viewWillMove(toSuperview newSuperview: NSView?) {
super.viewWillMove(toSuperview: newSuperview)
wantsLayer = true
layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor
layer?.borderWidth = 1
NSEvent.addLocalMonitorForEvents(matching: [.mouseEntered, .mouseExited, .leftMouseDown]) { event in
if self.frame.contains(event.locationInWindow) {
// if cursor is over the view just return nil to do not propagate the events
return nil
}
return event
}
}
}
If you are trying to stop mouse events from a viewController, add this code in viewDidLoad
NSEvent.addLocalMonitorForEvents(matching: [.mouseEntered, .mouseExited, .leftMouseDown]) { event in
if self.view.frame.contains(event.locationInWindow) {
return nil
}
return event
}

How to detect changes in a UIImageView and change a Boolean when this happens

I'm using UIPresentationController to prevent the user from accidentally closing a UIViewController presented modally if the user has made any changes. Everything works as it should when it comes to UITextFields since I detect the changes with .editingChanged. A sample code is shown below. I have an UIImageView where the user can change to provide a profile photo. I can enable the save button (rightBarButtonItem) once the user has uploaded an image using didFinishPickingMediaWithInfo in UIImagePickerController but that would not prevent the UIViewController from closing accidentally. Ideally, I would like to change the value of the hasChanges var but it is a get-only property.
var hasChanges: Bool {
guard let customer = customer else { return false }
if
firstNameTextField.text!.isNotEmpty && firstNameTextField.text != customer.firstName
// additional textfields etc…
{
return true
}
return false
}
override func viewWillLayoutSubviews() {
// If our model has unsaved changes, prevent pull to dismiss and enable the save button
let hasChanges = self.hasChanges
isModalInPresentation = hasChanges
saveButton.isEnabled = hasChanges
}
#objc func cancel(_ sender: Any) {
if hasChanges {
// The user tapped Cancel with unsaved changes
// Confirm that they really mean to cancel
confirmCancel(showingSave: false)
} else {
// No unsaved changes, so dismiss immediately
sendDidCancel()
}
}
#objc func textFieldDidChange(_ textField: UITextField) {
if hasChanges {
navigationItem.rightBarButtonItem?.isEnabled = true
} else {
navigationItem.rightBarButtonItem?.isEnabled = false
}
}
func setupTextFieldDelegates() {
let textFields = [all the textfields are included here]
for textField in textFields {
textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
}
}
To achieve this, you have to create a custom UIImageView, where you have to create a delegate variable and a protocol with a method and need to override the image variable.
when override the image variable you have to call the delegation method inside the didSet method of variable.
class CustomImageView: UIImageView{
override var image: UIImage?{
didSet{
delegate?.didChangeImage()
}
}
var delegate: ImageViewDelegate?
}
protocol ImageViewDelegate {
func didChangeImage()
}
Next in your ViewController set the delegate.
class ImageViewController: UIViewController {
#IBOutlet weak var imageView: CustomimageView!
override func viewDidLoad() {
super.viewDidLoad()
imageView.delegate = self
}
}
If you use the outlet from the storyboard, make sure to provide the custom class name to outlet. Otherwise it will not work.

Link in an Editable NSTextView (like Apple's Notes)

I have an NSTextView that is initially set as read-only like this:
taskDescription.isEditable = false
I set a click gesture recognizer in viewDidLoad() on it like this so that when the user clicks the field, it gets set as editable.
override func viewDidLoad() {
super.viewDidLoad()
let clickGesture = NSClickGestureRecognizer(target: self, action: #selector(setDescriptionEditState))
clickGesture.numberOfClicksRequired = 1
taskDescription.addGestureRecognizer(clickGesture)
}
And here is the function called on click:
#objc func setDescriptionEditState(){
//Make it editable
taskDescription.isEditable = true
//Don't show automatic link detect while editing (I want plain text)
taskDescription.checkTextInDocument(nil)
//Put the cursor in the NSTextView
taskDescription.window?.makeFirstResponder(taskDescription)
}
Then when I load an NSAttributedString into it with a link in it, it shows up fine:
Last of all, I implement this delegate method for NSTextView:
func textView(_ textView: NSTextView, clickedOnLink link: Any, at charIndex: Int) -> Bool {
print(link) //<-- Never called
}
What's happening is the clickGesture is getting called instead of the link click. I know this because if I manually set the NSTextView to isEditable = false then the link works fine.
How can I allow the link click to happen while still allowing the user to click on the NSTextView to switch it into edit mode?
--- Update ---
I set the content of this NSTextView with an NSAttributedString. When logged to the console, it looks like this:
I want {
NSColor = "...";
NSFont = "...";
}a big link{
NSColor = "...";
NSFont = "...";
NSLink = "google.com";
} here.{
NSColor = "...";
NSFont = "...";
}
So the link is specified with an NSLink.
The key is to override the function "touchesBegan" and implement your own logic.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
guard let location = touch?.location(in: self.view) else { return }
if !popupContainer.frame.contains(location) {
goodTouch = true
} else {
goodTouch = false
}
}
The variable "goodTouch" is established as a class property. You can then check the status of this variable at the beginning of the functions in question and then route your application appropriately from there. (This was specifically adapted from an iOS project I wrote, but should be the same for Cocoa). Make sure if you implement this, you reset the "goodTouch" variable to false after inspecting it to be ready for the next round of touches.
EDIT
I think this is a more appropriate response to your specific example, but I didn't want to delete the code from above:
override func mouseDown(with event: NSEvent) {
let textField = NSTextField() //this is for demo only...reference your actual object
let location = event.locationInWindow
let cgPoint = CGPoint(x: location.x, y: location.y)
if textField.bounds.contains(cgPoint) {
//fire the link here, over ride the click gesture recognizer
} else {
//change the editable style of the text field
}
}

Prevent NSPopUpButton to open its menu

I need to prevent the popup's menu to be opened when some conditions are met, so I implemented "willOpenMenu" to know when the menu is about to be opened:
class MyPopUpButton: NSPopUpButton {
override func willOpenMenu(_ menu: NSMenu, with event: NSEvent) {
print("willOpenMenu")
// it fires, but what I can do to prevent menu to be opened??
}
}
How can I prevent, now, the menu to not show up?
EDIT
below, in order, what happens when you click on a popup (Type column), when a "Value" column is under editing
Finally I found the way:
override func willOpenMenu(_ menu: NSMenu, with event: NSEvent) {
if (self.outline != nil) {
/*
the following is needed when user click on the type column:
He can have ongoing edits on some rows, so changing first responder
We can aesily inform the outline that We're playing with something else
and have to force ask the user in case edits are wrong.
This also prevent a crash.
*/
if (self.outline?.wrongValue)! {
// this grant the popup menu to not showup (or disappear so quickly)
menu.cancelTrackingWithoutAnimation()
}
self.window?.makeFirstResponder(self)
self.window?.makeFirstResponder(self.outline)
}
}
self.outline is a get/set variable. "wrongvalue" works this way:
override func controlTextDidBeginEditing(_ obj: Notification) {
if obj.object is MyTextField {
self.outline.wrongValue = true
}
}
override func controlTextDidEndEditing(_ obj: Notification) {
if obj.object is MyTextField {
/*
some code here to check if stringValue is ok for the selected tag, otherwise
show the relative alert, the popup menu will not show up anymore
because cancelTracking is called on it
*/
self.outline.wrongValue = false
}
}
cancelTracking() was the key!

Best strategy in swift to detect keyboad input in NSViewController

I want to detect keyboard input in my NSViewController.
The idea is to have several actions performed if the user presses certain keys followed by ENTER/RETURN.
I have checked if keyDown would be a appropriate way. But I would receive an event any time the user has pressed a key.
I also have though on using an NSTextField, set it to hidden and let it have the focus.
But maybe there are other better solution.
Any ideas?
Thanks
I've finally got a solution that I like.
First it has nothing todo with any hidden UI Elements but rather let the viewcontroller detect keyboard input.
var monitor: Any?
var text = ""
override func viewDidLoad() {
super.viewDidLoad()
self.monitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: myKeyDownEvent)
}
override func viewWillDisappear() {
//Clean up in case your ViewController can be closed an reopened
if let monitor = self.monitor {
NSEvent.removeMonitor(monitor)
}
}
// Detect each keyboard event
func myKeyDownEvent(event: NSEvent) -> NSEvent {
// keyCode 36 is for detect RETURN/ENTER
if event.keyCode == 36 {
print(text)
text = ""
} else {
text.append( event.characters! )
}
return event
}