NSView cannot grab Ctrl+Tab Keydown event - objective-c

In my Mac App, I listen to key press events and pass them on to the internal client, depending on modifiers and key code.
Currently, I'm facing the problem, that I can't get a hold of the "Ctrl+Tab" event. It seems that the "App" itself tries to handle this, which makes sense for tab based applications. So I disabled the Tabbingmode, but still, the Ctrl+Tab never fires the KeyDown event. Any other combination of key code and modifier seems to pass just fine.
Any suggestions on how to get the key down event fired for Ctrl+Tab?

In my testing, NSView's -keyDown: method does not seem to get called on NSView subclasses for control-tab key events. However, you can intercept them at the application level with an NSApplication subclass:
#interface MyApplication: NSApplication
#end
#implementation MyApplication
- (void)sendEvent:(NSEvent *)event {
if (event.type == NSEventTypeKeyDown &&
[event.charactersIgnoringModifiers isEqualToString:#"\t"] &&
(event.modifierFlags & NSEventModifierFlagDeviceIndependentFlagsMask) == NSEventModifierFlagControl) {
NSLog(#"ctrl-tab");
}
[super sendEvent:event];
}
#end

Use
override func viewDidLoad() {
super.viewDidLoad()
NSEvent.addLocalMonitorForEvents(matching: .keyDown) {
if self.myKeyDown(with: $0) {
return nil
} else {
return $0
}
}
}
and
func myKeyDown(with event: NSEvent) -> Bool {
// handle keyDown only if current window has focus, i.e. is keyWindow
guard let locWindow = self.view.window,
NSApplication.shared.keyWindow === locWindow else {
return false
}
switch event.specialKey {
case NSEvent.SpecialKey.tab:
// your event for tab action
return true
default:
break
}
return false
}
if you need shortcut keys
func myKeyDown(with event: NSEvent) -> Bool {
// handle keyDown only if current window has focus, i.e. is keyWindow
guard let locWindow = self.view.window,
NSApplication.shared.keyWindow === locWindow else {
return false
}
switch event.specialKey {
case NSEvent.SpecialKey.tab:
// your code for tab action
return true
default:
break
}
switch event.modifierFlags.intersection(.deviceIndependentFlagsMask) {
case [.command]:
switch event.charactersIgnoringModifiers! {
case "w":
// your code for cmd+w action (example)
break
default:
break
}
}
return false
}

Related

How do I check the currently loaded view controller ? (ObjC + Swift)

In my app I have a function where I want it to print something depending on the current view controller that is loaded. I do this by setting a global variable (Bool) and then toggling the flags in the view controller classes. From my main class I have something like this:
var FirstViewControllerisVisible: Bool = false
var SecondViewControllerisVisible: Bool = false
var ThirdViewControllerisVisible: Bool = false
#objc func PlayAgainfunc(_ sender: Any) {
if counter % 15 == 0 {
if FirstViewControllerisVisible == true {
print("First View Controller is visible")
} else if SecondViewControllerisVisible == true {
print("Second View Controller is visible")
} else if ThirdViewControllerisVisible == true {
print("Third View Controller is visible")
}
}
counter += 1
}
Then, in those classes I can set the flags like this:
override func viewDidAppear(_ animated: Bool) {
FirstViewControllerisVisible = true
}
override func viewDidDisappear(_ animated: Bool) {
FirstViewControllerisVisible = false
}
This worked great when it was exclusively Swift, but the problem with global variables is that they can't be accessed by Objective-C. I can't find a way to set flags in my ObjC classes and then check if true or false. For instance, If I tried:
- (void)viewDidAppear:(BOOL)animated; {
[(FirstViewControllerisVisible) == true];
}
- (void)viewDidDisappear:(BOOL)animated; {
[(FirstViewControllerisVisible) == false];
}
I would get the error 'use of undeclared identifier' because my global vars declared in Swift are Swift only.
After the view controller has loaded, you can
if let viewController = UIApplication.shared.keyWindow?.rootViewController {
print(type(of: viewController))
}
add below mentioned extension. you will get the desired result.
extension UIApplication {
/// will return currently showing view controller
static var topMostViewController: UIViewController? {
return UIApplication.shared.keyWindow?.rootViewController?.visibleViewController
}
}
extension UIViewController {
/// The visible view controller from a given view controller
var visibleViewController: UIViewController? {
if let navigationController = self as? UINavigationController {
return navigationController.topViewController?.visibleViewController
} else if let tabBarController = self as? UITabBarController {
return tabBarController.selectedViewController?.visibleViewController
} else if let presentedViewController = presentedViewController {
return presentedViewController.visibleViewController
} else {
return self
}
}
}
and access it like:
let viewController = UIApplication.topMostViewController
Happy Coding

setStatusBarHidden is deprecated in iOS 9.0

I am upgrading my code from iOS 8 to iOS 9. I have a code snippet in my program
[[UIApplication applicationName] setStatusBarHidden:YES];.
I am getting the warning "setStatusBarHidden is deprecated in iOS 9.0, Use -[UIViewController prefersStatusBarHidden". If I just replace 'setStatusBarHidden' with 'prefersStatusBarHidden', I get 'instance method not found'.
Can someone please suggest me how to solve this problem?
Add below code to your view controller..
- (BOOL)prefersStatusBarHidden {
return NO;
}
Note :
If you change the return value for this method, call the
setNeedsStatusBarAppearanceUpdate method.
For childViewController, To specify that a child view controller
should control preferred status bar hidden/unhidden state, implement
the childViewControllerForStatusBarHidden method.
prefersStatusBarHidden is available from iOS 7+.
Use this in Your UIViewController class
var isHidden = true{
didSet{
self.setNeedsStatusBarAppearanceUpdate()
}
}
override var prefersStatusBarHidden: Bool {
return isHidden
}
If you change the return value for this method, call the
setNeedsStatusBarAppearanceUpdate() method. To specify that a child
view controller should control preferred status bar hidden/unhidden
state, implement the childViewControllerForStatusBarHidden method.
you have to add method in yourViewController.m
- (BOOL)prefersStatusBarHidden {
return NO;
}
Swift 3.1 Xcode 8.2.1
Change in info.plist the row View controller-based status bar appearance and set it to NO
In your target settings tick "Hide Status bar"
Both steps are required
Here is my swift code for setting status bar hidden and style.
extension UIViewController {
public var privateStatusBarHidden: Bool {
return statusBarHidden
}
public var privateStatusBarStyle: UIStatusBarStyle {
return statusBarStyle
}
public func setStatusBarHidden(hidden: Bool, animated: Bool = false) {
statusBarHidden = hidden
if animated {
UIView.animate(withDuration: 0.25, animations: {
self.setNeedsStatusBarAppearanceUpdate()
})
} else {
self.setNeedsStatusBarAppearanceUpdate()
}
}
public func setStatusBar(style: UIStatusBarStyle) {
statusBarStyle = style
self.setNeedsStatusBarAppearanceUpdate()
}
public static func swizzleStatusBarHiddenPropertyForViewController() {
var original = class_getInstanceMethod(UIViewController.self, #selector(getter: UIViewController.prefersStatusBarHidden))
var changeling = class_getInstanceMethod(UIViewController.self, #selector(getter: UIViewController.privateStatusBarHidden))
method_exchangeImplementations(original, changeling)
original = class_getInstanceMethod(UIViewController.self, #selector(getter: UIViewController.preferredStatusBarStyle))
changeling = class_getInstanceMethod(UIViewController.self, #selector(getter: UIViewController.privateStatusBarStyle))
method_exchangeImplementations(original, changeling)
original = class_getClassMethod(UIViewController.self, #selector(UIViewController.swizzleStatusBarHiddenPropertyForViewController))
changeling = class_getClassMethod(UIViewController.self, #selector(UIViewController.emptyFunction))
method_exchangeImplementations(original, changeling)
}
#objc private static func emptyFunction() {}
}
Usage
in lauching function
UIViewController.swizzleStatusBarHiddenPropertyForViewController()
for hide/show statusBar, in UIViewController
. self.setStatusBar(hidden: true/false)
Swift 3 with Xcode 8.3.3
1) Add a row in you Info.plist.
2) In your ViewController ViewDidLoad() override add:
UIApplication.shared.isStatusBarHidden = true

Create and Respond to a Hyperlink within a NSTableView Text Cell

I have a program that has a NSTableView populated with files to be uploaded. Once the file is sent, the Text Cell with the file's name gets a hyperlink placed into it (the array data is given an NSMutableString with an NSLinkAttributeName attribute). How do I allow users to click this link to open the webpage in their default browser?
After much searching and trying multiple methods, this is what I came up with as a solution.
Creating a custom class that extends NSTableViewCell:
class TableViewCellCursor: NSTableCellView {
internal var active = false
//MARK: - View Life Cycle
override func awakeFromNib() {
superview?.awakeFromNib()
self.createTrackingArea()
}
//MARK: - IBActions
override func mouseEntered(theEvent: NSEvent) {
if (NSCursor.currentCursor() == NSCursor.arrowCursor() && active) {
NSCursor.pointingHandCursor().set()
}
}
override func mouseExited(theEvent: NSEvent) {
if (NSCursor.currentCursor() == NSCursor.pointingHandCursor() && active) {
NSCursor.arrowCursor().set()
}
}
//Informs the receiver that the mouse cursor has moved into a cursor rectangle.
override func cursorUpdate(event: NSEvent) {
if (active) {
NSCursor.pointingHandCursor().set()
}
}
//MARK: - Util
func createTrackingArea() {
var focusTrackingAreaOptions:NSTrackingAreaOptions = NSTrackingAreaOptions.ActiveInActiveApp
focusTrackingAreaOptions |= NSTrackingAreaOptions.MouseEnteredAndExited
focusTrackingAreaOptions |= NSTrackingAreaOptions.AssumeInside
focusTrackingAreaOptions |= NSTrackingAreaOptions.InVisibleRect
var focusTrackingArea:NSTrackingArea = NSTrackingArea(rect: NSZeroRect,
options: focusTrackingAreaOptions,
owner: self, userInfo: nil)
self.addTrackingArea(focusTrackingArea)
}
}
Checking first responder status when the NSTableView selection changes. This is necessary because the table's selection can be changed, even when it is not the firstResponder:
func tableViewSelectionDidChange(aNotification: NSNotification) {
if (self.firstResponder == filesToTransferTable) {
changeSelectedRowTextColorTo(NSColor.whiteColor(), unselectedColor: NSColor.blueColor())
} else {
changeSelectedRowTextColorTo(NSColor.blackColor(), unselectedColor: NSColor.blueColor())
}
}
func changeSelectedRowTextColorTo(selectedColor: NSColor, unselectedColor: NSColor) {
let selectedRows = filesToTransferTable.selectedRowIndexes
for (index, tableEntry) in enumerate (tableData) {
if tableData[index]["FileName"] is NSMutableAttributedString {
var name = tableData[index]["FileName"] as! NSMutableAttributedString
var range = NSMakeRange(0, NSString(string:name.string).length)
name.beginEditing()
name.removeAttribute(NSForegroundColorAttributeName, range: range)
if (selectedRows.containsIndex(index)) {
name.addAttribute(NSForegroundColorAttributeName, value:selectedColor, range:range)
} else {
name.addAttribute(NSForegroundColorAttributeName, value:unselectedColor, range:range)
}
name.endEditing()
tableData[index]["FileName"] = name
}
filesToTransferTable.reloadDataForRowIndexes(NSIndexSet(index: index), columnIndexes: NSIndexSet(index:0))
}
}
Adding KVO for checking when FirstResponder changes:
//This is somewhere in your code where you initialize things
//KVO for first responder behavior regarding tableView and updating attributedStrings' colors
self.addObserver(self, forKeyPath: "firstResponder", options: NSKeyValueObservingOptions.Old | NSKeyValueObservingOptions.New, context: nil)
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
if (change[NSKeyValueChangeNewKey] is NSTableView) {
changeSelectedRowTextColorTo(NSColor.whiteColor(), unselectedColor: NSColor.blueColor())
} else if (change[NSKeyValueChangeOldKey] is NSTableView) {
changeSelectedRowTextColorTo(NSColor.blackColor(), unselectedColor: NSColor.blueColor())
}
}
Finally, checking if the main window (the app itself) is in focus (if this is not done, then the colors won't change appropriately when the window loses focus):
//Put these in the same place as the KVO code
NSNotificationCenter.defaultCenter().addObserver(self, selector: "windowDidBecomeKey:",
name: NSWindowDidBecomeKeyNotification , object: self)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "windowDidResignKey:",
name: NSWindowDidResignKeyNotification , object: self)
func windowDidBecomeKey(notification: NSNotification) {
if (self.firstResponder == filesToTransferTable) {
changeSelectedRowTextColorTo(NSColor.whiteColor(), unselectedColor: NSColor.blueColor())
} else {
changeSelectedRowTextColorTo(NSColor.blackColor(), unselectedColor: NSColor.blueColor())
}
}
func windowDidResignKey(notification: NSNotification) {
if (self.firstResponder == filesToTransferTable) {
changeSelectedRowTextColorTo(NSColor.blackColor(), unselectedColor: NSColor.blueColor())
}
}
Text fields automatically support clicking on embedded links, but only if they are at least selectable (if not editable). So, set your text field to be selectable.

UITextField Changes Not Being Detected?

I have two flag properties that should change when a text field contains an integer, and I have IBActions, when the text field editing ends, that change the flags. When both of the variables are true, those methods should enable a button. I ran the iOS simulator, but the button isn't enabling. I also declared the text field delegate for both the text fields.
I am new to swift, so please be clear with your answer. Also, I haven't set any breakpoints. Here's the code for what I have so far:
var yourWeightFilled = false
var calorieNumberFilled = false
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// Find out what the text field will be after adding the current edit
let text = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString: string)
if textField == yourWeightTextField {
yourWeightFilled = text.toInt() != nil
} else if textField == calorieNumberTextField {
calorieNumberFilled = text.toInt() != nil
}
return true
}
#IBAction func yourWeightEditingDidEnd(sender: AnyObject) {
if self.yourWeightFilled && self.calorieNumberFilled {
self.calculateButton.enabled = true
}
yourWeightTextField.resignFirstResponder()
}
#IBAction func calorieNumberEditingDidEnd(sender: AnyObject) {
if self.yourWeightFilled && self.calorieNumberFilled {
self.calculateButton.enabled = true
}
calorieNumberTextField.resignFirstResponder()
}
UITextField is a subclass of UIControl and thus needs to have action methods registered to be called in response to control events. You do that with the addTarget(_:action:forControlEvents:) method.
For example:
weightField.addTarget(self, action:"yourWeightEditingDidEnd:", forControlEvents:.EditingDidEnd);
Would work in your case to call your action method yourWeightEditingDidEnd() when the user finishes editing the text field. This assumes your field property is named weightField. A good place for this code is in your view controller's viewDidLoad() method.
There is one more important step. You appear to be implementing UITextFieldDelegate, which is good because you also need a textFieldShouldReturn(textField:) -> Bool method that returns true and resigns the text field as first responder. Example:
func textFieldShouldReturn(textField: UITextField) -> Bool
{
textField.resignFirstResponder();
return true;
}
This in turn causes the .EditingDidEnd control event to fire and the action method you registered to be called.

Handle Double-click Mouse Event and Return Pressed for NSTableView

OK, what I need is pretty straightforward, though I can still find nothing specific.
I want to be able to :
track double-click events
track when the NSTableView is in focus, and the "Return" key is pressed.
How would you go about it?
P.S. I've had a look into NSTableViewDelegate specification, but I can't find anything useful.
For double click you need to do just these :
-(void)awakeFromNib{
[self.tableView setDoubleAction:#selector(thisMethod)];
//And if you wish to take selector dynamically, I guess you know how to do :)
}
-(void)thisMethod{
NSLog(#"double clicked");
}
For the return event, subclass your NSTableView and override keyDown:
Swift 5.x:
override func keyDown(with event: NSEvent) {
if event.characters?.count == 1 {
let character = event.keyCode
switch (character) {
// 36 is return
case UInt16(36):
print("return: \(event)")
default:
print("any other key: \(event)")
}
} else {
super.keyDown(with: event)
}
}
There is a way to handle the Return key without having to manually check for its key code.
I'll show the answer in Swift, but it can be applied in Objective-C as well.
First, override keyDown(with:) in your view controller subclass that controls the table view and call interpretKeyEvents(_:):
override func keyDown(with event: NSEvent) {
interpretKeyEvents([event])
}
Second, in the same view controller subclass, override insertNewLine(_:). This is called when the user presses the Return key:
override func insertNewLine(_ sender: Any?) {
// Add your logic to handle the Return key being pressed
}
Here's an example:
class TableViewController: NSViewController {
#IBOutlet var tableView: NSTableView!
override func keyDown(with event: NSEvent) {
interpretKeyEvents([event])
}
override func insertNewLine(_ sender: Any?) {
guard tableView.selectedRow >= 0 else { return }
print("Pressed Return on row \(tableView.selectedRow)")
}
}