I have a a subclass of NSView which contains the following NSTrackingArea code. But for some reason the mouse events won't trigger in Playground.
The viewWillMoveToWindowis being called, but nothing else seems to fire. Does anyone have a clue to what is missing?
class MyView: NSView {
private var trackingArea: NSTrackingArea = NSTrackingArea()
// Other stuff omitted here...
// ...
override func viewWillMoveToWindow(newWindow: NSWindow?) {
// Setup a new tracking area when the view is added to the window.
trackingArea = NSTrackingArea(rect: self.bounds, options: [.MouseEnteredAndExited, .ActiveAlways], owner: self, userInfo: nil)
self.addTrackingArea(trackingArea)
}
override func updateTrackingAreas() {
self.removeTrackingArea(trackingArea)
trackingArea = NSTrackingArea(rect: self.bounds, options: [.MouseEnteredAndExited, .ActiveAlways], owner: self, userInfo: nil)
self.addTrackingArea(trackingArea)
}
// Mouse events
override func mouseEntered(theEvent: NSEvent) {
NSLog("MouseEntered")
}
override func mouseExited(theEvent: NSEvent) {
NSLog("MouseExited")
}
override func mouseDown(theEvent: NSEvent) {
NSLog("MouseDown")
}
}
According to this 2014 WWDC session:
There are few more limitations with Playgrounds.
Playgrounds cannot be used for things which require user interaction.
So we have great support for showing live views but you can only see them, you can't touch them.
You can find the original video here
Related
I have been experimenting with mouse clicks. I am ok with left mouse clicks getting raw values etc etc. I now want to add right mouse clicks. I have setup a basic example. What i would like to achieve if there is one mouse right click it performs one function, and if there is two mouse right clicks it performs a different function. The problem is if you do two mouse clicks it obviously cannot differentiate between the two and so fire the one mouse click function before performing the second mouse function.
I was thinking of maybe using a timer of some sort to record the number of click. But i end up going round in circles as i just seem to start the timers over and over. I'm hoping some one might help. thanks for reading here is the code.
Xcode 8 Swift 3 Mac OSX Sierra NOT IOS.. NOT IOS
import Cocoa
class MainWindowController: NSWindowController {
#IBOutlet weak var MyView: NSView!
override func windowDidLoad() {
super.windowDidLoad()
//Initialize mouse for Right Click numberOfClicksRequired = 1
let recogRightClick1 = NSClickGestureRecognizer(target: self, action: #selector(oneMouseClick))
recogRightClick1.buttonMask = 0x2
recogRightClick1.numberOfClicksRequired = 1
MyView.addGestureRecognizer(recogRightClick1)
//Initialize mouse for Right ClicknumberOfClicksRequired = 2
let recogRightClick2 = NSClickGestureRecognizer(target: self, action: #selector(twoMouseClick(myrec:myRightClick:)))
recogRightClick2.buttonMask = 0x2
recogRightClick2.numberOfClicksRequired = 2
MyView.addGestureRecognizer(recogRightClick2)
}//EO Overide
func oneMouseClick(myrec: NSPanGestureRecognizer,myRightClick:NSClickGestureRecognizer){
let rightClick = myRightClick.state.rawValue
print("oneMouseClick",rightClick)
}
func twoMouseClick(myrec: NSPanGestureRecognizer,myRightClick:NSClickGestureRecognizer){
let rightClick = myRightClick.state.rawValue
print("twoMouseClick",rightClick)
}
}//EnD oF thE wORld
UPDATE
I have re generated the code following the advice given. Now the code reflects more of what i wanted to do. My only problem is that I would like all the mouse operations to be triggered only inside 'myView' rather than within the main window. I thought it might have something to do with first responder but that doesn't seem to work. Again any thought would be appreciated. Please excuse any bad code i'm self taught.
Xcode 8 Swift 3 Mac OSX Sierra NOT IOS.. NOT IOS
import Cocoa
class MainWindowController: NSWindowController {
#IBOutlet weak var myView: NSView!
var mouseX:CGFloat = 0
var mouseY:CGFloat = 0
override func windowDidLoad() {
myView.window?.becomeFirstResponder()
myView.window?.acceptsMouseMovedEvents = true
super.windowDidLoad()
myView.wantsLayer = true
myView.layer?.backgroundColor = CGColor(red: 0.05, green: 0.57, blue: 0.80, alpha: 0.6)
NSEvent.addLocalMonitorForEvents(matching:.leftMouseDown){
self.mouseEventFunction(data: 1)
return $0
}
NSEvent.addLocalMonitorForEvents(matching:.leftMouseUp){
self.mouseEventFunction(data:2)
return $0
}
NSEvent.addLocalMonitorForEvents(matching:.rightMouseDown){
self.mouseEventFunction(data: 3)
return $0
}
NSEvent.addLocalMonitorForEvents(matching:.rightMouseUp){
self.mouseEventFunction(data: 4)
return $0
}
NSEvent.addLocalMonitorForEvents(matching:.mouseMoved) {
self.mouseX = NSEvent.mouseLocation().x
self.mouseY = NSEvent.mouseLocation().y
return $0 }
}//EO Overide
func mouseEventFunction (data: Int){
switch data{
case 1 :
print("LeftMouseDown")
case 2 :
print("LeftMouseUp")
case 3 :
print("RightMouseDown")
case 3 :
print("RightMouseUP")
default: break
}
if data == 1 {print("mouseX",mouseX,"mouseY",mouseY)}
}//eo mouseEvent
}//EnD oF thE wORld
UPDATE 2
I have now updated subClassing the view controller, so the mouse clicks are now only working in myView. I'm still having problems with 'func mouseDragged' What i need to achieve is the bottom left of my view is x = 0 and Y = 0. I had a try with converting but thats not working. hoping someone might guide me. thanks for reading here is the updated code.
Xcode 8 Swift 3 Mac OSX Sierra NOT IOS.. NOT IOS
import Cocoa
class MainWindowController: NSWindowController {
#IBOutlet weak var myView: NSView!
override func windowDidLoad() {
myView.window?.becomeFirstResponder()
myView.window?.acceptsMouseMovedEvents = true
window?.contentView?.addSubview(myView)
super.windowDidLoad()
}//EO Overide
}//EnD oF thE wORld
class testView: NSView {
override func draw(_ dirtyRect: NSRect) {
let backgroundColor = NSColor.lightGray
backgroundColor.set()
NSBezierPath.fill(bounds)
}
override func mouseDragged(with theEvent: NSEvent) {
let myLocationInWindow = theEvent.locationInWindow
let location = convert(myLocationInWindow, to: self)
Swift.print("myLocationInWindow",myLocationInWindow,"location",location)
}
override func mouseDown(with theEvent: NSEvent) {
Swift.print("mouseDown")
}
override func mouseUp(with theEvent: NSEvent) {
Swift.print("mouseUp clickCount: \(theEvent.clickCount)")
}
}//eo testView
To define mouse inside view you use
let myLocationInWindow = theEvent.locationInWindow
let location = convert(myLocationInWindow, from: nil)
where nil is the window
here is the final code
import Cocoa
class MainWindowController: NSWindowController {
#IBOutlet weak var myView: NSView!
override func windowDidLoad() {
super.windowDidLoad()
}//EO Overide
}//EnD oF thE wORld
class testView: NSView {
override func draw(_ dirtyRect: NSRect) {
let backgroundColor = NSColor.lightGray
backgroundColor.set()
NSBezierPath.fill(bounds)
}
override func mouseDragged(with theEvent: NSEvent) {
let myLocationInWindow = theEvent.locationInWindow
let location = convert(myLocationInWindow, from: nil)
Swift.print("location",location)
}
override func mouseDown(with theEvent: NSEvent) {
Swift.print("mouseDown")
}
override func mouseUp(with theEvent: NSEvent) {
Swift.print("mouseUp clickCount: \(theEvent.clickCount)")
}
}//eo testView
I have an NSPageController containing 8 or so NSViewControllers. I want to have a semi transparent bottom bar when the mouse is inside of the window, and a semi transparent top bar that persists no matter where the mouse is.
I add the top bar and bottom bar to the view, along with constraints in NSPageControllers viewDidLoad() method.
They show up fine on the first page, but when I start to transition from one page to another, the new NSViewController is redrawn over the overlaying views and they disappear. I can verify that they are under the NSViewControllers because then I drag all the way to a specific side I can see them underneath.
Any ideas why this is happening / how I can avoid it?
Code:
class MyPageController: NSPageController {
// MARK: - Properties
fileprivate var mouseIsInside = false
fileprivate var tabBar: TabBar!
// MARK: - NSViewController
override func viewDidLoad() {
super.viewDidLoad()
// add tab bar, then hide it (mouse in or outside of window will restore current state)
tabBar = TabBar(frame: NSRect(x: 0, y:0, width: view.frame.size.width, height: 40))
addTabBar(withAnimation: false)
removeTabBar(withAnimation: false)
NSLayoutConstraint.activate([
tabBar.heightAnchor.constraint(equalToConstant: 40),
tabBar.bottomAnchor.constraint(equalTo: view.bottomAnchor),
tabBar.centerXAnchor.constraint(equalTo: view.centerXAnchor),
tabBar.widthAnchor.constraint(equalTo: view.widthAnchor)
])
delegate = self
transitionStyle = .horizontalStrip
arrangedObjects = ["0","1","2","3","4","5","6","7"]
selectedIndex = 0
view.wantsLayer = true
// register for mouse events
let trackingArea = NSTrackingArea(rect: view.bounds, options: [.mouseEnteredAndExited, .mouseMoved, .activeAlways, .inVisibleRect], owner: self, userInfo: nil)
view.addTrackingArea(trackingArea)
}
}
// NSPageController Delegate
extension PageController: NSPageControllerDelegate {
func pageController(_ pageController: NSPageController, frameFor object: Any?) -> NSRect {
return NSInsetRect(view.frame, -1, 0)
}
func pageController(_ pageController: NSPageController, identifierFor object: Any) -> String {
return (object as? String)!
}
func pageController(_ pageController: NSPageController, viewControllerForIdentifier identifier: String) -> NSViewController {
return ViewController(id: identifier)
}
func pageControllerWillStartLiveTransition(_ pageController: NSPageController) {
Swift.print("pageControllerWillStartLiveTransition")
addTabBar(withAnimation: false)
}
func pageControllerDidEndLiveTransition(_ pageController: NSPageController) {
pageController.completeTransition()
addTabBar(withAnimation: false)
Swift.print("pageControllerWillEndLiveTransition")
}
}
// tabBar functions
extension PageController {
func addTabBar(withAnimation shouldAnimate: Bool) {
view.addSubview(tabBar)
tabBar.frame.size.width = view.frame.size.width
if mouseIsInside {
tabBar.showWithAnimation(shouldAnimate)
}
}
func removeTabBar(withAnimation shouldAnimate: Bool) {
tabBar.hideWithAnimation(shouldAnimate)
}
}
// Mouse Movements
extension PageController {
override func mouseEntered(with event: NSEvent) {
mouseIsInside = true
addTabBar(withAnimation: true)
}
override func mouseExited(with event: NSEvent) {
mouseIsInside = false
removeTabBar(withAnimation: true)
}
}
Thanks in advance!
This appear to be a not resolved bug. This fixed for me:
If the pageController is fullfilling the window's view, set to nil the contentView's background. This way the background we'll see is always the background of the pageController.
Sort views in this method:
func pageControllerDidEndLiveTransition(_ pageController: NSPageController) {
let controller = selectedViewController as! (YOUR_NSVIEWCONTROLLER_CLASS)
controller.view.superview?.addSubview(controller.view, positioned: .below, relativeTo: TOOLBAR)
}
Replace YOUR_NSVIEWCONTROLLER_CLASS for your ViewControllerclass name, and then replace TOOLBAR for the view that you want to see on top.
I have NSTextView and I want to show autocompletion options using NSTableView (like Xcode autocompletion). The problem is that when textView is the first responder, tableView is shown as unfocused (which is true), but I want to pretend that it's also active. Is there an easy way to achieve this (having firstResponder textView and tableView with active cell selection color)?
So I managed to resolve the issue with lots of experiments.
Here are my solution:
setup NSTableView:
tableView.refusesFirstResponder = true
tableView.selectionHighlightStyle = .none
on NSTableCellView subclass implement following code:
override func awakeFromNib() {
super.awakeFromNib()
registerNotifications()
}
deinit {
unregisterNotifications()
}
private func registerNotifications() {
NotificationCenter.default.addObserver(self,
selector: #selector(selectionIsChanging),
name: NSNotification.Name.NSTableViewSelectionIsChanging,
object: nil)
}
private func unregisterNotifications() {
NotificationCenter.default.removeObserver(self)
}
#objc private func selectionIsChanging() {
if let row = superview as? NSTableRowView, row.isSelected == true {
self.backgroundColor = NSColor.alternateSelectedControlColor
} else {
self.backgroundColor = NSColor.clear
}
}
I am working on an app that requires my main view controller to receive notifications from its child view controller. To do this, I set up the observer in the viewDidLoad() method of the parent controller, and I am trying to call the postNotificationName() function when a button is pressed that is located in the child view controller.
When the postNotificationName() call is located in the #IBAction func buttonPressed(){ } function, the observer never gets the notification. However, when the same code is called from a different function in the same view controller, it works.
I thought this might be a thread issue, and unsuccessfully tried many of the solutions for that issue. I'm not very familiar with threading, though, so maybe I still missed it. Does anyone have any idea what the issue might be?
Thanks for the help!
ViewController
class GameController: UIViewController {
override func viewDidLoad() {
super.init()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "attemptHumanMove:", name: Constants.NSNotificationKey.tilePressed, object: nil)
}
func attemptHumanMove(notification: NSNotification) {
print("test")
}
ChildViewController
#IBAction func tileTouched(sender: AnyObject) {
NSNotificationCenter.defaultCenter().postNotificationName(Constants.NSNotificationKey.tilePressed, object: nil, userInfo: ["a": 0])
}
Experimenting with the flow you have mentioned, the following code does receive the notification message from a child view controller.
ParentViewController
class ViewController: UIViewController {
#IBOutlet weak var notificationMsgLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
notificationMsgLabel.text = "Waiting for notification..."
NSNotificationCenter.defaultCenter().addObserver(self, selector: "attemptHumanMove:", name: "tilePressed", object: nil)
}
func attemptHumanMove(notification: NSNotification) {
print("Notification message received!")
notificationMsgLabel.text = "Notification received!!!"
}
}
ChildViewController
class ChildViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func sendNotification() {
print("Sending notification message!!!")
NSNotificationCenter.defaultCenter().postNotificationName("tilePressed", object: nil, userInfo: ["a": 0])
}
}
Here you can find the screen shot of the storyboard:
Storyboard Snapshot
I am creating a custom NSButtonCell for a custom rendering.
Now, I want to have different aspect depending if the mouse is over the button or not. How can I get this information?
Thanks and regards,
Here is i have created and worked for me perfectly...
Step 1: Create the Button with tracking area
NSButton *myButton = [[NSButton alloc] initWithFrame:NSMakeRect(100, 7, 100, 50)];
[myButton setTitle:#"sample"];
[self.window.contentView addSubview:myButton];
// Insert code here to initialize your application
NSTrackingArea* trackingArea = [[NSTrackingArea alloc]
initWithRect:[myButton bounds]
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways
owner:self userInfo:nil];
[myButton addTrackingArea:trackingArea];
Step: 2 Implement the following methods
- (void)mouseEntered:(NSEvent *)theEvent{
NSLog(#"entered");
[[myButton cell] setBackgroundColor:[NSColor blueColor]];
}
- (void)mouseExited:(NSEvent *)theEvent{
[[myButton cell] setBackgroundColor:[NSColor redColor]];
NSLog(#"exited");
}
Swift 3:
Create the button with code or just use it's #IBOutlet.
Then define the button's tracking area for mouse over (hover):
let area = NSTrackingArea.init(rect: yourButtonName.bounds,
options: [.mouseEnteredAndExited, .activeAlways],
owner: self,
userInfo: nil)
yourButtonName.addTrackingArea(area)
Then override mouseEntered and mouseExited, set whatever you want to change (button color, button image, button text,..) in these functions:
override func mouseEntered(with event: NSEvent) {
print("Entered: \(event)")
}
override func mouseExited(with event: NSEvent) {
print("Exited: \(event)")
}
If you have multiple buttons (with tracking area added for each) and you need to identify which button triggered the mouseEntered event, you can add some userInfo information for this purpose, so instead of:
userInfo: nil
Add your custom button name in userInfo for each button, for example:
userInfo: ["btnName": "yourButtonName"]
Then you can write a switch-case or if statements in your mouseEntered and mouseExited functions like this:
override func mouseEntered(with event: NSEvent) {
// Identify which button triggered the mouseEntered event
if let buttonName = event.trackingArea?.userInfo?.values.first as? String {
switch (buttonName) {
case "yourButtonName":
//do whatever you want for this button..
case "anotherButtonName":
//do whatever you want for this button..
default:
print("The given button name: \"\(buttonName)\" is unknown!")
}
}
}
You need to Subclass the NSButton class (or even better the NSButtonCell class).
As Justin said if you put the two method
- (void)mouseEntered:(NSEvent *)theEvent;
- (void)mouseExited:(NSEvent *)theEvent;
They should get called when the mouse enter and exit the area. You may also need to re create the tracking area, look here:
- (void)updateTrackingAreas
For fade in and fade out effect I played with animator and alpha value for example:
[self animator]setAlphaValue:0.5];
Swift 5 version with callback, super easy to use:
final class HoverButton: NSButton {
private let callback: (HoverButton, Bool) -> Void
init(callback: #escaping (HoverButton, Bool) -> Void) {
self.callback = callback
super.init(frame: .zero)
let area = NSTrackingArea(rect: bounds, options: [.mouseEnteredAndExited, .activeAlways, .inVisibleRect], owner: self, userInfo: nil)
addTrackingArea(area)
}
#available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func mouseEntered(with event: NSEvent) {
super.mouseEntered(with: event)
callback(self, true)
}
override func mouseExited(with event: NSEvent) {
super.mouseExited(with: event)
callback(self, false)
}
}
Usage:
let button = HoverButton { button, isHovered in
button.animator().alphaValue = isHovered ? 1 : 0.5
}
a good starting point, declared in NSResponder:
- (void)mouseEntered:(NSEvent *)theEvent;
- (void)mouseExited:(NSEvent *)theEvent;
specifically, the button cell's container (not the cell itself) is the NSResponder.
For those who prefer subclassing, you can also make your own NSButton and assigning the NSTrackingArea in it.
Here is a really simple and elegant way to do it, thanks to Joey Zhou : https://github.com/Swift-Kit/JZHoverNSButton
It is written in Swift 2 but XCode will automatically translate it in Swift 3 - 4 without any issue.
Hope it can help someone
This is a simple code to track mouse enter and mouse exit events on some controls (Images, Label, etc...):
#IBOutlet weak var myImage: NSImageView!
#IBOutlet weak var myLabel: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
let area1 = myTrakingArea(control: self.myImage)
let area2 = myTrakingArea(control: self.myLabel)
self.myImage.addTrackingArea(area1)
self.myLabel.addTrackingArea(area2)
}
func myTrakingArea(control: NSControl) -> NSTrackingArea {
return NSTrackingArea.init(rect: control.bounds,
options: [.mouseEnteredAndExited, .activeAlways],
owner: control,
userInfo: nil)
}
override func mouseEntered(with event: NSEvent) {
if let owner = event.trackingArea?.owner as? NSControl {
let id : String = owner.identifier!.rawValue
switch id {
case self.myLabel.identifier!.rawValue:
print("Entered Quit Label")
case self.myImage.identifier!.rawValue:
print("Entered Quit Image")
default:
print("Entered ???")
}
}
}
override func mouseExited(with event: NSEvent) {
if let owner = event.trackingArea?.owner as? NSControl {
let id : String = owner.identifier!.rawValue
switch id {
case self.myLabel.identifier!.rawValue:
print("Exited Quit Label")
case self.myImage.identifier!.rawValue:
print("Exited Quit Image")
default:
print("Exited ???")
}
}
}