Move a NSWindow by dragging a NSView - macos

I have a NSWindow, on which i apply this:
window.styleMask = window.styleMask | NSFullSizeContentViewWindowMask
window.titleVisibility = NSWindowTitleVisibility.Hidden;
window.titlebarAppearsTransparent = true;
I then add a NSView behind the titlebar to simulate a bigger one.
Now it looks like this:
I want to be able to move the window, by dragging the light-blue view. I have already tried to subclass NSView and always returning true for mouseDownCanMoveWindow using this code:
class LSViewD: NSView {
override var mouseDownCanMoveWindow:Bool {
get {
return true
}
}
}
This didn't work.
After some googling i found this INAppStoreWindow on GitHub. However it doesn't support OS X versions over 10.9, so it's completely useless for me.
Edit1
This is how it looks in the Interface Builder.
How can i move the window, by dragging on this NSView?

None of the answers here worked for me. They all either don't work at all, or make the whole window draggable (note that OP is not asking for this).
Here's how to actually achieve this:
To make a NSView control the window with it's drag events, simply subclass it and override the mouseDown as such:
class WindowDragView: NSView {
override public func mouseDown(with event: NSEvent) {
window?.performDrag(with: event)
}
}
That's it. The mouseDown function will transfer further event tracking to it's parent window.
No need for window masks, isMovableByWindowBackground or mouseDownCanMoveWindow.

Try setting the window's movableByWindowBackground property to true.

There are two ways to do this. The first one would be to set the NSTexturedBackgroundWindowMask as well as the windows background color to the one of your view. This should work.
Otherwise you can take a look at this Sample Code

I somehow managed to solve my problem, i don't really know how, but here are some screenshots.
In the AppDelegate file where i edit the properties of my window, i added an IBOutlet of my contentView. This IBOutlet is a subclass of NSView, in which i've overriden the variable mouseDownCanMoveWindow so it always returns false.
I tried this before in only one file, but it didn't work. This however solved the problem.
Thanks to Ken Thomases and Max for leading me into the right direction.

Swift3.0 Version
override func viewDidAppear() {
//for hide the TitleBar
self.view.window?.styleMask = .borderless
self.view.window?.titlebarAppearsTransparent = true
self.view.window?.titleVisibility = .hidden
//for Window movable with NSView
self.view.window?.isMovableByWindowBackground = true
}

Swift 3:
I needed this but dynamically. It's a little long but well worth it (IMHO).
So I decided to enable this only while the command key is down. This is achieved by registering a local key handler in the delegate:
// MARK:- Local key monitor
var localKeyDownMonitor : Any? = nil
var commandKeyDown : Bool = false {
didSet {
let notif = Notification(name: Notification.Name(rawValue: "commandKeyDown"),
object: NSNumber(booleanLiteral: commandKeyDown))
NotificationCenter.default.post(notif)
}
}
func keyDownMonitor(event: NSEvent) -> Bool {
switch event.modifierFlags.intersection(.deviceIndependentFlagsMask) {
case [.command]:
self.commandKeyDown = true
return true
default:
self.commandKeyDown = false
return false
}
}
which is enabled within the delegate startup:
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Watch local keys for window movenment, etc.
localKeyDownMonitor = NSEvent.addLocalMonitorForEvents(matching: NSEventMask.flagsChanged) { (event) -> NSEvent? in
return self.keyDownMonitor(event: event) ? nil : event
}
}
and its removal
func applicationWillTerminate(_ aNotification: Notification) {
// Forget key down monitoring
NSEvent.removeMonitor(localKeyDownMonitor!)
}
Note that when the commandKeyDown value is changed by the key down handler. This value change is caught by the didset{} to post a notification. This notification is registered by any view you wish to have its window so moved - i.e., in the view delegate
override func viewDidLoad() {
super.viewDidLoad()
// Watch command key changes
NotificationCenter.default.addObserver(
self,
selector: #selector(ViewController.commandKeyDown(_:)),
name: NSNotification.Name(rawValue: "commandKeyDown"),
object: nil)
}
and discarded when the viewWillDisappear() (delegate) or the window controller windowShouldClose(); add this
<your-view>.removeObserver(self, forKeyPath: "commandKeyDown")
So sequence goes like this:
key pressed/release
handler called
notification posted
The view's window isMovableByWindowBackground property is changed by notification - placed within view controller / delegate or where you registered the observer.
internal func commandKeyDown(_ notification : Notification) {
let commandKeyDown : NSNumber = notification.object as! NSNumber
if let window = self.view.window {
window.isMovableByWindowBackground = commandKeyDown.boolValue
Swift.print(String(format: "command %#", commandKeyDown.boolValue ? "v" : "^"))
}
}
Remove the tracer output when happy. See it in action in SimpleViewer on github.

Related

How to clear NSTextView selection without it becoming first responder?

I have a basic Cocoa app with a number of NSTextViews. When a text view loses focus (i.e. resigns its first responder status), I'd like to clear its selection.
My strategy was to extend NSTextView and override resignFirstResponder():
override func resignFirstResponder() -> Bool {
// Both result in the text view becoming first responder again:
clearSelection(nil)
setSelectedRange(NSRange(location: 0, length: 0))
return super.resignFirstResponder()
}
The problem is that calling clearSelection() and setSelectedRange() both cause the text view to become first responder again.
Is there a way to clear the selection without it becoming the first responder?
I tried to also override acceptsFirstResponder and temporarily return false, but that didn't work either.
Met the same issue today and found the solution
You can do setSelectedRange in NSTextView's delegate method textDidEndEditing and it wouldn't cause NSTextView become first responder.
class TextView: NSTextView {
init() {
self.delegate = self
....
}
....
}
extension TextView: NSTextViewDelegate {
public func textDidEndEditing(_ notification: Notification) {
setSelectedRange(NSMakeRange(string.count, 0))
}
}

How to detect a Pan Gesture inside a NSTouchBarView

Is it possible to detect a finger pan on a NSTouchBarView?
Sorry for the lack of code but I don't even know where to start.
MacOS is not made for finger touches but the TouchBar is but I do not see how to do it on a NSTouchBarView
I don't know specifically about using NSTouchBarView, but using a pan recognizer in a touch bar usually works like this: create a view, then create a NSPanGestureRecognizer (don't forget to set the target and action) then add the recognizer to the previously created view. Finally, create your NSCustomTouchBarItem and assign the previously created view to the item's view. Quick example in Swift:
func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItemIdentifier) -> NSTouchBarItem? {
switch identifier {
case NSTouchBarItemIdentifier.yourCustomItem:
return itemWithRecognizer(identifier: identifier)
default:
return nil
}
}
func itemWithRecognizer(identifier: NSTouchBarItemIdentifier) -> NSTouchBarItem {
let customView = NSView()
customView.wantsLayer = true
let recognizer = NSPanGestureRecognizer()
recognizer.target = self
recognizer.action = #selector(doSomething)
customView.addGestureRecognizer(recognizer)
let item = NSCustomTouchBarItem(identifier: identifier)
item.view = customView
return item
}
func doSomething() {
// gesture was activated
}

How to hide the shortcut bar in iOS9

I have a textfield with a hidden keyboard (since I'm using it with bluetooth). However, in iOS9 the shortcut bar keeps appearing.
Is there a way to hide it too?
Thank you so much!
You can pass your textfield name in place of userNameTextField for which you want to remove shortcut bar.
UITextInputAssistantItem* item = [userNameTextField inputAssistantItem];
item.leadingBarButtonGroups = #[];
item.trailingBarButtonGroups = #[];
In Swift 2.0
if #available(iOS 9.0, *) {
let item : UITextInputAssistantItem = yourTextView.inputAssistantItem
item.leadingBarButtonGroups = []
item.trailingBarButtonGroups = []
} else {
// Fallback on earlier versions
}
I had the same issue. And so starts a search of SO. So the above helped me out, but the whole, "if iOS9 thing" might be best framed like this:
if ([self respondsToSelector:#selector(inputAssistantItem)]) {
// iOS9.
UITextInputAssistantItem* item = [self inputAssistantItem];
item.leadingBarButtonGroups = #[];
item.trailingBarButtonGroups = #[];
}
Happily, I'd created a sub-class of a UITextField, (CHTextField) and was in use everywhere. So it was a very easy fix to whack this in the over-ridden "init" method.
Hope it helps.
Alternatively, just create an extension for UITextField in Swift 2.0 like this.
extension UITextField
{
public func hideAssistantBar()
{
if #available(iOS 9.0, *) {
let assistant = self.inputAssistantItem;
assistant.leadingBarButtonGroups = [];
assistant.trailingBarButtonGroups = [];
}
}
}
Then you can just call hideAssistantBar() on any text field you like.
#IBOutlet weak var myTextField: UITextField?;
override public func viewDidLoad() {
super.viewDidLoad();
myTextField?.hideAssistantbar();
}
In Swift 3.0 and 4.0
self.textField.inputAssistantItem.leadingBarButtonGroups.removeAll()
self.textField.inputAssistantItem.trailingBarButtonGroups.removeAll()
An easy way to do this for all text fields in your app is to create a category on UITextInputAssistantItem and override the getters for leadingBarButtonGroups and trailingBarButtonGroups like this:
#implementation UITextInputAssistantItem (RemoveBars)
- (NSArray<UIBarButtonItemGroup *> *)leadingBarButtonGroups
{
return #[];
}
- (NSArray<UIBarButtonItemGroup *> *)trailingBarButtonGroups
{
return #[];
}
#end
This worked for me on iOS 9.x and 8.x, no need for any conditional code.
Be careful with this though, this overrides those properties for EVERYTHING that uses UITextInputAssistantItem
Just to expand on the other answers here. I cobbled together some Swift 2.0 code that will loop through all subviews of a given view and disable the UITextInputAssistantItems for all UITextFields and UISearchBars.
func hideTheAssistantBar(view:UIView) {
//Check this view
for case let textField as UITextField in view.subviews {
let item : UITextInputAssistantItem = textField.inputAssistantItem
item.leadingBarButtonGroups = []
item.trailingBarButtonGroups = []
}
for case let searchBar as UISearchBar in view.subviews {
let item : UITextInputAssistantItem = searchBar.inputAssistantItem
item.leadingBarButtonGroups = []
item.trailingBarButtonGroups = []
}
//Now find this views subviews
let subviews = view.subviews
for subview : AnyObject in subviews {
if subview.isKindOfClass(UIView) {
hideTheAssistantBar(subview as! UIView)
}
}
}
You can then call this function passing in whatever view you would like to start at. I call this inside of my ViewDidLoad() method and pass in self.view like hideTheAssistantBar(self.view).
I actually went a step further for my needs and added this function to a helper class I use for common code. Therefore inside of my viewDidLoad() function I actually just call helper.hideTheAssistantBar(self.view) and then I don't have to put that function in every file.
Hope this helps someone coming along looking for an easy way to remove the assistant bar from all UITextFields and UISearchBars in one fail swoop.
Thanks to #Arkader for the swift code to recursively find all subviews. Swift List Subviews
Just to build on what Pranavan posted because setting the bar button groups to an empty array doesn't seem to work in iOS 12 or 13 using Xcode 11.
let inputAssistantItem = textFieldForTypingText.inputAssistantItem
inputAssistantItem.leadingBarButtonGroups.removeAll()
inputAssistantItem.trailingBarButtonGroups.removeAll()
I placed the above code in the viewDidLoad() function.
You can also give the option to the user:
inputAssistantItem.allowsHidingShortcuts = true
In the case letting the user hide it, if the text field becomes first responder again, they'll have to hide it again.

Add completion handler to presentViewControllerAsSheet(NSViewController)?

I am attempting to present a sheet configuration view (AddSoundEffect) for my main window/view controller (I'm using storyboards), and when the configuration view controller is dismissed, take the values entered in the AddSoundEffect view and pass that back to the main view. My current code in the main view controller:
presentViewControllerAsSheet(self.storyboard!.instantiateControllerWithIdentifier("AddSoundEffect") as! AddSoundViewController
And in the AddSoundViewController.swift file, the code to dismiss it is:
self.dismissViewController(self)
To pass the data, I have a class-independent tuple that I save data to. How do I add a completion handler to presentViewControllerAsSheet, and (optionally) is there a better way to pass the data between view controllers?
Setup: Xcode version 6.4, OS X 10.10.4
Delegation pattern is the easiest way for you.
// Replace this with your tuple or whatever data represents your sound effect
struct SoundEffect {}
protocol AddSoundViewControllerDelegate: class {
func soundViewController(controller: AddSoundViewController, didAddSoundEffect: SoundEffect)
}
//
// Let's say this controller is a modal view controller for adding new sound effects
//
class AddSoundViewController: UIViewController {
weak var delegate: AddSoundViewControllerDelegate?
func done(sender: AnyObject) {
// Dummy sound effect info, replace it with your own data
let soundEffect = SoundEffect()
//
// Call it whenever you would like to inform presenting view controller
// about added sound effect (in case of Done, Add, ... button tapped, do not call it
// when user taps on Cancel to just dismiss AddSoundViewController)
//
self.delegate?.soundViewController(self, didAddSoundEffect: soundEffect)
// Dismiss self
self.dismissViewControllerAnimated(true, completion: {})
}
}
//
// Let's say this controller is main view controller, which contains list of all sound effects,
// with button to add new sound effect via AddSoundViewController
//
class SoundEffectsViewController: UIViewController, AddSoundViewControllerDelegate {
func presentAddSoundEffectController(sender: AnyObject) {
if let addSoundController = self.storyboard?.instantiateViewControllerWithIdentifier("AddSoundEffect") as? AddSoundViewController {
addSoundController.delegate = self
self.presentViewController(addSoundController, animated: true, completion: {})
}
}
func soundViewController(controller: AddSoundViewController, didAddSoundEffect: SoundEffect) {
// This method is called only when new sound effect is added
}
}
Another way is to use closures:
// Replace this with your tuple or whatever data represents your sound effect
struct SoundEffect {}
//
// Let's say this controller is a modal view controller for adding new sound effects
//
class AddSoundViewController: UIViewController {
var completionHandler: ((SoundEffect) -> ())?
func done(sender: AnyObject) {
// Dummy sound effect info, replace it with your own data
let soundEffect = SoundEffect()
//
// Call it whenever you would like to inform presenting view controller
// about added sound effect (in case of Done, Add, ... button tapped, do not call it
// when user taps on Cancel to just dismiss AddSoundViewController)
//
self.completionHandler?(soundEffect)
// Dismiss self
self.dismissViewControllerAnimated(true, completion: {})
}
}
//
// Let's say this controller is main view controller, which contains list of all sound effects,
// with button to add new sound effect via AddSoundViewController
//
class SoundEffectsViewController: UIViewController {
func presentAddSoundEffectController(sender: AnyObject) {
if let addSoundController = self.storyboard?.instantiateViewControllerWithIdentifier("AddSoundEffect") as? AddSoundViewController {
addSoundController.completionHandler = { [weak self] (soundEffect) -> () in
// Called when new sound effect is added
}
self.presentViewController(addSoundController, animated: true, completion: {})
}
}
}
Or many other ways like sending notification, ... Whatever suits your needs. But delegation pattern or closures is the best way to go in this specific case.
I missed that your question is about NSViewController. This example is for iOS, but same pattern can be used on OS X without any issues.
The easiest way to detect sheet opening or closing is to use the Sheet Notifications:
class ViewController: NSViewController, NSWindowDelegate {
override func viewDidLoad(){
NSApplication.sharedApplication().windows.first?.delegate = self
}
func windowDidEndSheet(notification: NSNotification) {
}
func windowWillBeginSheet(notification: NSNotification) {
}
}

Detecting key press event in Swift

I'm trying to find a way to detect if a key (on a keyboard) has been pressed on Swift. Any ideas and suggestions will be greatly appreciated.
Since you updated your question and you wanted to know how to do this for a window, here's an answer. Subclass NSWindow and use this subclass instead.
Your custom class should look like this:
import Cocoa
class EditorWindow: NSWindow {
override func keyDown(event: NSEvent) {
super.keyDown(event)
Swift.print("Caught a key down: \(event.keyCode)!")
}
}
If you've made your window in Interface Builder/XCode, click the window object and go to the Attribute Inspector (⌥+⌘+3). The Attribute Inspector will be in the sidebar on the right. Making sure your window is selected in Interface Builder, at the top of the Attribute Inspector in the Custom Class area put your new class in the class input.
In order to communicate the event from the this window class to my app I add a function to the window that accepts a callback function that I then store in an array of callback functions. I get access to this window through the AppDelegate which can get a weak reference to the current main window. Then in the above function I iterate overall the callbacks and call it with the NSEvent as the argument. I also first check to see if any command keys like the option keys are being pressed first through modifierFlags property on the event. It ends up looking like this:
import Cocoa
typealias Callback = (NSEvent) -> ()
class KeyCaptureWindow: NSWindow {
var keyEventListeners = Array<Callback>()
override func keyDown(event: NSEvent) {
if event.modifierFlags.contains(NSEventModifierFlags.CommandKeyMask) {
super.keyDown(event)
return
}
for callback in keyEventListeners {
callback(event)
}
}
func addKeyEventCallback(callback: Callback) {
keyEventListeners.append(callback)
}
}
And then elsewhere in my code I have a line like so:
let appDelegate = NSApplication.sharedApplication().delegate as! AppDelegate
let mainWindow = appDelegate.getWindow()
mainWindow.addKeyEventCallback(handleKeyEvent)
I added the getWindow method to my app delegate class. This method returns the NSWindow cast to KeyCaptureWindow. There may be a better way to do all this but this works for me. Another way to possibly do this is to use first responders and NSView, but that's not how I've been doing it.
You have to override the keyDown-method.
var direction:String = ""
override func keyDown(theEvent: NSEvent!) // A key is pressed
{
if theEvent.keyCode == 123
{
direction = "left" //get the pressed key
}
else if theEvent.keyCode == 124
{
direction = "right" //get the pressed key
}
println("Key with number: \(theEvent.keyCode) was pressed")
}

Resources