Cocoa: Making NSTextField editable after a click and short delay (like renaming in Finder) - macos

I cannot find a simple example of how to use an NSTextField to edit it's contents in place.
Exactly like in the Finder - you're able to click, and with a short delay the text field becomes editable.
It seems like it's some combination of the textField, it's cell, and the fieldEditor? Problem is I can't find the most basic example of how to do it.
I've tried subclassing NSTextField with a couple different tests but it hasn't worked:
#import "GWTextField.h"
#implementation GWTextField
- (id) initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect];
return self;
}
- (void) mouseDown:(NSEvent *)theEvent {
[super mouseDown:theEvent];
[self.cell editWithFrame:self.frame inView:self.superview editor:[self.cell fieldEditorForView:self] delegate:self event:theEvent];
//[self setEditable:TRUE];
//[self setSelectable:TRUE];
//[self selectText:nil];
[NSTimer scheduledTimerWithTimeInterval:.3 target:self selector:#selector(edit:) userInfo:nil repeats:FALSE];
}
- (void) edit:(id) sende {
NSLog(#"edit");
[[NSApplication sharedApplication].mainWindow makeFirstResponder:self];
[self selectText:nil];
}
#end
Any ideas?

Here's another solution with no NSCell - one user pointed out that NSCell is deprecated and will at some point be gone.
#import <Cocoa/Cocoa.h>
#interface EditTextField : NSTextField <NSTextDelegate,NSTextViewDelegate,NSTextFieldDelegate>
#property BOOL isEditing;
#property BOOL commitChangesOnEscapeKey;
#property BOOL editAfterDelay;
#property CGFloat delay;
#end
----
#import "EditTextField.h"
#interface EditTextField ()
#property NSObject <NSTextFieldDelegate,NSTextViewDelegate> * userDelegate;
#property NSString * originalStringValue;
#property NSTimer * editTimer;
#property NSTrackingArea * editTrackingArea;
#end
#implementation EditTextField
- (id) initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
[self defaultInit];
return self;
}
- (id) initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect];
[self defaultInit];
return self;
}
- (id) init {
self = [super init];
[self defaultInit];
return self;
}
- (void) defaultInit {
self.delay = .8;
}
- (void) mouseDown:(NSEvent *) theEvent {
if(theEvent.clickCount == 2) {
[self startEditing];
} else {
[super mouseDown:theEvent];
if(self.editAfterDelay) {
[self startTracking];
self.editTimer = [NSTimer scheduledTimerWithTimeInterval:.8 target:self selector:#selector(startEditing) userInfo:nil repeats:FALSE];
}
}
}
- (void) startTracking {
if(!self.editTrackingArea) {
self.editTrackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds options:NSTrackingMouseEnteredAndExited|NSTrackingMouseMoved|NSTrackingActiveInActiveApp|NSTrackingAssumeInside|NSTrackingInVisibleRect owner:self userInfo:nil];
}
[self addTrackingArea:self.editTrackingArea];
}
- (void) mouseExited:(NSEvent *)theEvent {
[self.editTimer invalidate];
self.editTimer = nil;
}
- (void) mouseMoved:(NSEvent *) theEvent {
[self.editTimer invalidate];
self.editTimer = nil;
}
- (void) startEditing {
id firstResponder = self.window.firstResponder;
if([firstResponder isKindOfClass:[NSTextView class]]) {
NSTextView * tv = (NSTextView *)firstResponder;
if(tv.delegate && [tv.delegate isKindOfClass:[EditTextField class]]) {
EditTextField * fr = (EditTextField *)tv.delegate;
[fr stopEditingCommitChanges:FALSE clearFirstResponder:FALSE];
}
}
if(self.delegate != self) {
self.userDelegate = (NSObject <NSTextFieldDelegate,NSTextViewDelegate> *)self.delegate;
}
self.isEditing = TRUE;
self.delegate = self;
self.editable = TRUE;
self.originalStringValue = self.stringValue;
[self.window makeFirstResponder:self];
}
- (void) stopEditingCommitChanges:(BOOL) commitChanges clearFirstResponder:(BOOL) clearFirstResponder {
self.editable = FALSE;
self.isEditing = FALSE;
self.delegate = nil;
[self removeTrackingArea:self.editTrackingArea];
if(!commitChanges) {
self.stringValue = self.originalStringValue;
}
if(clearFirstResponder) {
[self.window makeFirstResponder:nil];
}
}
- (void) cancelOperation:(id) sender {
if(self.commitChangesOnEscapeKey) {
[self stopEditingCommitChanges:TRUE clearFirstResponder:TRUE];
} else {
[self stopEditingCommitChanges:FALSE clearFirstResponder:TRUE];
}
}
- (BOOL) textView:(NSTextView *) textView doCommandBySelector:(SEL) commandSelector {
BOOL handlesCommand = FALSE;
NSString * selector = NSStringFromSelector(commandSelector);
if(self.userDelegate) {
if([self.userDelegate respondsToSelector:#selector(control:textView:doCommandBySelector:)]) {
handlesCommand = [self.userDelegate control:self textView:textView doCommandBySelector:commandSelector];
} else if([self.userDelegate respondsToSelector:#selector(textView:doCommandBySelector:)]) {
handlesCommand = [self.userDelegate textView:textView doCommandBySelector:commandSelector];
}
if(!handlesCommand) {
if([selector isEqualToString:#"insertNewline:"]) {
[self stopEditingCommitChanges:TRUE clearFirstResponder:TRUE];
handlesCommand = TRUE;
}
if([selector isEqualToString:#"insertTab:"]) {
[self stopEditingCommitChanges:TRUE clearFirstResponder:FALSE];
handlesCommand = FALSE;
}
}
} else {
if([selector isEqualToString:#"insertNewline:"]) {
[self stopEditingCommitChanges:TRUE clearFirstResponder:TRUE];
handlesCommand = TRUE;
}
if([selector isEqualToString:#"insertTab:"]) {
[self stopEditingCommitChanges:TRUE clearFirstResponder:FALSE];
handlesCommand = FALSE;
}
}
return handlesCommand;
}
#end

I built a re-usable NSTextField subclass you can use for edit in place functionality. http://pastebin.com/QymunMYB

I came up with a better solution to the edit in place problem. I believe this is how to properly do edit in place with NSCell. Please show and tell if this is wrong.
#import <Cocoa/Cocoa.h>
#interface EditTextField : NSTextField <NSTextDelegate>
#end
---
#import "EditTextField.h"
#implementation EditTextField
- (void) mouseDown:(NSEvent *)theEvent {
if(theEvent.clickCount == 2) {
self.editable = TRUE;
NSText * fieldEditor = [self.window fieldEditor:TRUE forObject:self];
[self.cell editWithFrame:self.bounds inView:self editor:fieldEditor delegate:self event:theEvent];
} else {
[super mouseDown:theEvent];
}
}
- (void) cancelOperation:(id)sender {
[self.cell endEditing:nil];
self.editable = FALSE;
}
- (BOOL) textView:(NSTextView *) textView doCommandBySelector:(SEL) commandSelector {
NSString * selector = NSStringFromSelector(commandSelector);
if([selector isEqualToString:#"insertNewline:"]) {
NSText * fieldEditor = [self.window fieldEditor:TRUE forObject:self];
[self.cell endEditing:fieldEditor];
self.editable = FALSE;
return TRUE;
}
return FALSE;
}
#end

In my application I have two text fields - one non editable, and second, hidden, editable, and activates title editing by calling:
[self addSubview:windowTitle];
[windowTitleLabel removeFromSuperview];
[self.window makeFirstResponder:windowTitle];
This is called from mouseUp: on view behind the label.
I don't remember why I needed to have two text fields (i didn't know Cocoa good that time), probably it will work even without label swapping.

Related

mouseExited is no longer called after mouseDown in NSButton subclass

In building a custom NSButton, I've run into a problem handling highlight behavior. After clicking down on the button, holding, and dragging the cursor outside the button's bounds, mouseExited: and mouseEntered: events are not delivered. I understand the reason why, because in mouseDown: calling [super mouseDown:event]; will block until the click is released.
In researching this I came across this Stack Overflow post which describes the same problem. The solution noted is to add NSTrackingEnabledDuringMouseDrag to the NSTrackingArea options, which I have done, yet I continue to see this problem. I tried the other proposed solution with handling the next events in a loop, but this resulted in odd behavior. The button text color turns black on mouse down instead of highlighting the dimmed color, and it doesn't unhighlight upon releasing the mouse, it remains black.
I am using Xcode 9.3, running on macOS 10.13.4.
Here is my NSButton subclass:
#interface BorderlessButton : NSButton {
NSColor *_tempColor;
}
#property (strong, nonatomic) NSColor *color;
#end
#interface BorderlessButton ()
#property (nonatomic) BOOL pressed;
#end
#implementation BorderlessButton
- (id)init {
if (self = [super init]) {
[self setUp];
}
return self;
}
- (id)initWithFrame:(NSRect)frameRect {
if (self = [super initWithFrame:frameRect]) {
[self setUp];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
[self setUp];
}
return self;
}
- (void)setUp {
_color = [NSColor redColor];
[self setTitle:self.title];
[self setButtonType:NSButtonTypeMomentaryChange];
[self setBordered:NO];
NSTrackingArea *area = [[NSTrackingArea alloc] initWithRect:self.bounds
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingEnabledDuringMouseDrag
owner:self
userInfo:nil];
[self addTrackingArea:area];
}
- (void)setTitle:(NSString *)title {
[super setTitle:title];
NSMutableAttributedString *colorTitle = [[NSMutableAttributedString alloc] initWithAttributedString:[self attributedTitle]];
[colorTitle addAttributes:#{NSFontAttributeName: self.font, NSForegroundColorAttributeName: self.color} range:NSMakeRange(0, [colorTitle length])];
[self setAttributedTitle:colorTitle];
}
- (void)setColor:(NSColor *)color {
_color = color;
[self setTitle:self.title];
}
- (void)mouseDown:(NSEvent *)event {
self.pressed = YES;
[self highlight:YES];
[super mouseDown:event]; // this blocks until released
[self mouseUp:event];
}
- (void)mouseUp:(NSEvent *)event {
self.pressed = NO;
[self highlight:NO];
[super mouseUp:event];
}
//FIXME: Not called after mouse press down and hold then exit
- (void)mouseExited:(NSEvent *)event {
if (self.pressed) {
[self highlight:NO];
}
[super mouseExited:event];
}
- (void)mouseEntered:(NSEvent *)event {
if (self.pressed) {
[self highlight:YES];
}
[super mouseEntered:event];
}
- (void)highlight:(BOOL)flag {
if (flag) {
if (self.isEnabled) {
NSColor *dimmedColor = [self dimmedColor];
_tempColor = _color;
self.color = dimmedColor;
[self setTitle:self.title];
}
} else {
if (self.isEnabled) {
self.color = _tempColor;
[self setTitle:self.title];
}
}
}
- (NSColor *)dimmedColor {
return [self.color colorWithAlphaComponent:0.5];
}
#end

Xcode doesn't recognize NSLabel

I'm trying to create a NSLabel for my osx app however Xcode is not recognizing the type "NSLabel" as valid and is suggesting I try "NSPanel" instead.
In the header file I have the following imports:
#import <Cocoa/Cocoa.h>
#import <AppKit/AppKit.h>
How do I fix this? Is there another file I need to import?
There is no label class (NSLabel) on OS X. You have to use NSTextField instead, remove the bezel and make it non editable:
[textField setBezeled:NO];
[textField setDrawsBackground:NO];
[textField setEditable:NO];
[textField setSelectable:NO];
Swift 4.2 🔸
open class NSLabel: NSTextField {
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.isBezeled = false
self.drawsBackground = false
self.isEditable = false
self.isSelectable = false
}
}
I had the same question, following DrummerB advice I created this NSLabel class.
Header
//
// NSLabel.h
//
// Created by Axel Guilmin on 11/5/14.
//
#import <AppKit/AppKit.h>
#interface NSLabel : NSTextField
#property (nonatomic, assign) CGFloat fontSize;
#property (nonatomic, strong) NSString *text;
#end
Implementation
//
// NSLabel.m
//
// Created by Axel Guilmin on 11/5/14.
//
#import "NSLabel.h"
#implementation NSLabel
#pragma mark INIT
- (instancetype)init {
self = [super init];
if (self) {
[self textFieldToLabel];
}
return self;
}
- (instancetype)initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect];
if (self) {
[self textFieldToLabel];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self textFieldToLabel];
}
return self;
}
#pragma mark SETTER
- (void)setFontSize:(CGFloat)fontSize {
super.font = [NSFont fontWithName:self.font.fontName size:fontSize];
}
- (void)setText:(NSString *)text {
[super setStringValue:text];
}
#pragma mark GETTER
- (CGFloat)fontSize {
return super.font.pointSize;
}
- (NSString*)text {
return [super stringValue];
}
#pragma mark - PRIVATE
- (void)textFieldToLabel {
super.bezeled = NO;
super.drawsBackground = NO;
super.editable = NO;
super.selectable = YES;
}
#end
You'll need to #import "NSLabel.h" to use it, but I think it's more clean.

How do I open an NSSheet in Mavericks?

In Mavericks, the methods to open and close NSSheets has changed. And to make it tougher, the Release Notes do not match the current documentation (see below).
I'm trying to do this:
MainSheetController (NSWindowController):
-(IBAction)callSheet:(id)sender {
[sheetController openSheet];
}
SheetController (NSObject):
(void)openSheet {
[[NSBundle mainBundle] loadNibNamed:sheetName owner:self topLevelObjects:nil];
NSLog(#"1");
[self.mainWindowController.window beginSheet:self.sheet completionHandler:nil];
NSLog(#"2");
}
I get to 2, with no errors or warnings, but no sheet..
Current Documentation:
#if NS_BLOCKS_AVAILABLE
- (void)beginSheet:(NSWindow *)sheetWindow completionHandler:(void (^)(NSModalResponse returnCode))handler NS_AVAILABLE_MAC(10_9);
- (void)beginCriticalSheet:(NSWindow *)sheetWindow completionHandler:(void (^)(NSModalResponse returnCode))handler NS_AVAILABLE_MAC(10_9);
#endif
- (IBAction)userButtonPressed:(id)sender {
UserLoginWindowController * wc = [UserLoginWindowController new];
// we keep a reference, so the WC doesn't deallocate
self.modalWindowController = wc;
[[self window] beginSheet:[wc window] completionHandler:^(NSModalResponse returnCode) {
self.modalWindowController = nil;
}];
}
in the UserLoginWindowController
- (IBAction)cancelButtonPressed:(id)sender {
[[[self window] sheetParent] endSheet:[self window] returnCode:NSModalResponseCancel];
}
- (IBAction)showSheet:(id)sender
{
if (_windowController == nil)
{
_windowController = [MyWindowController new];
}
[[self window] beginSheet:[_windowController window] completionHandler:^(NSModalResponse returnCode)
{
}];
}
// And inside your MyWindowController class:
- (id)init
{
self = [super initWithWindowNibName:#"MyWindowNibName"]; // TODO: Change the name of your NIB
return self;
}
In your nib file, make sure the "Visible At Launch" flag is unticked for the window.
I figured out how to do this. Hope it's ok to post..
MainWindow.h
#interface MainWindowController : NSWindowController {
NSString *sheetName;
IBOutlet NSWindow *sheet;
id result1;
id result2;
...
id resultn;
}
#property (strong) NSString *sheetName;
#property (strong, nonatomic) IBOutlet NSWindow *sheet;
-(IBAction)callSheet0:(id)sender;
-(IBAction)callSheet1:(id)sender;
-(IBAction)callSheetn:(id)sender;
- (void)openSheet;
- (IBAction)save:(id)sender;
- (IBAction)cancel:(id)sender;
MainWindow.m
- (void)windowDidLoad
{
NSLog(#"%s", __FUNCTION__);
[super windowDidLoad];
sheetName = [[NSString alloc] init];
}
-(IBAction)callSheet0:(id)sender {
NSLog(#"%s", __FUNCTION__);
sheetName = #"Sheet0";
[self openSheet];
}
....
-(IBAction)callSheetn:(id)sender {
NSLog(#"%s", __FUNCTION__);
sheetName = #"Sheetn";
[self openSheet];
- (void)openSheet {
NSLog(#"%s", __FUNCTION__);
NSLog(#"sheetName: %#",sheetName );
[[NSBundle mainBundle] loadNibNamed:sheetName owner:self topLevelObjects:nil];
[self.window beginSheet:self.sheet completionHandler:nil];
}
- (void)save:(NSButton*)sender {
switch ([sender tag]) {
case 0:
[self doSave1];
result = #"1";
break;
case 1:
[self doSave2];
result = #"2";
break;
case n:
[self doSaven];
result = #"x";
break;
}
[self endSheet:self.sheet returnCode:result];
}
- (IBAction)cancel:(id)sender {
NSLog(#"%s", __FUNCTION__);
result = #"0";
[self endSheet:self.sheet returnCode:result];
// returnCode is optional
}
//endSheet:(NSWindow *)sheetWindow {
- (void)endSheet:(NSWindow *)sheetWindow returnCode:returnCode {
//NSLog(#"%s", __FUNCTION__);
[sheetWindow orderOut:self];
}
- (void)save:(NSButton*)sender {
switch ([sender tag]) {
case 0:
[self doSave1];
result = #"1";
break;
case n:
[self doSave3];
result = #"3";
break;
}
[self endSheet:self.sheet returnCode:result];
}
With this method, new in 10.9,I don't need a special sheet controller, and control remains quote local.

multiple uitextfields in one view with different input (keyboard/picker)

I have a form-like view in my application which contains 8 textfields. Two of them are to be filled in by pickers, and the rest by normal heyboard typing. When picker-row is selected or when return button is pressed, the corresponding text should be entered into the textfield.
Could you please give some advice how to implement this properly? Also so that pickers/keyboards are dismissed after selection made/return pressed.
I found an answer to something similar here. ANd I managed to get the 2 picker-textfields working, but now I cannot make the keyboard appear for the rest. I guess it's because I'm overriding the textFieldShouldBegineEditing method. Any ideas to get round that? Can I somehow call the default method from inside textFieldShouldBeginEditing?? Note, if I uncomment my last lines of textFieldShouldBeginEditing I get a crash...
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
currentTextField = textField;
if (textField == self.pickerField1) {
currentArray = self.array1;
[pickerView reloadAllComponents];
pickerView.hidden = NO;
[self animatePickerViewIn];
return NO;
}
if (textField == self.pickerField2){
currentArray = self.array2;
[pickerView reloadAllComponents];
pickerView.hidden = NO;
[self animatePickerViewIn];
return NO;
}
// else {
// [currentTextField becomeFirstResponder];
// return NO;
// }
- (void)pickerView:(UIPickerView *)pickerView
didSelectRow:(NSInteger)row inComponent: (NSInteger)component
{
[currentTextField setText:[currentArray objectAtIndex:row]];
[currentTextField resignFirstResponder];
pickerView.hidden = YES;
}
Thanks!
Adding this for anyone that needs more information - here's how I got it work using user2135738's provided code:
MyViewController.h
#import <UIKit/UIKit.h>
#interface MyViewController : UIViewController <UITextFieldDelegate, UIPickerViewDataSource, UIPickerViewDelegate>
#property (weak, nonatomic) IBOutlet UITextField *aTextField;
#property (weak, nonatomic) IBOutlet UITextField *bTextField;
#end
MyViewController.m
#import "MyViewController.h"
#interface MyViewController ()
#end
#implementation MyViewController {
NSArray *aArray,
*bArray,
*currentArray;
UITextField *currentTextField;
UIPickerView *pickerView;
}
- (void)viewDidLoad {
aArray = [NSArray arrayWithObjects: #1, #2, #3, #4, #5, #6, #7, #8, #9, #10, #11, #12, #13, #14, #15, #16, #17, #18, #19, #20, nil];
bArray = [NSArray arrayWithObjects:#"aString", #"bString", nil];
[super viewDidLoad];
pickerView = [[UIPickerView alloc] init];
pickerView.dataSource = self;
pickerView.delegate = self;
self.aTextField.delegate = self;
self.bTextField.delegate = self;
self.aTextField.inputView = pickerView;
self.bTextField.inputView = pickerView;
}
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
currentTextField = textField;
if (textField == self.aTextField) {
currentArray = aArray;
[pickerView reloadAllComponents];
pickerView.hidden = NO;
return YES;
} else if (textField == self.bTextField){
currentArray = bArray;
[pickerView reloadAllComponents];
pickerView.hidden = NO;
return YES;
} else {
[currentTextField becomeFirstResponder];
return NO;
}
}
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
return [currentArray count];
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{
return [NSString stringWithFormat:#"%#",[currentArray objectAtIndex:row]];
}
#end

Moving borderless NSWindow fully covered with Web View

In my COCOA application I have implemented a custom borderless window. The content area of the Window is fully covered by a WebView. I want this borderless window to move when user clicks and drag mouse anywhere in the content area. I tried by overriding isMovableByWindowBackground but no use. How can I fix this problem?
Calling -setMovableByWindowBackround:YES on the WebView and making the window textured might work.
This is how I did it.
#import "BorderlessWindow.h"
#implementation BorderlessWindow
#synthesize initialLocation;
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)windowStyle
backing:(NSBackingStoreType)bufferingType
defer:(BOOL)deferCreation
{
if((self = [super initWithContentRect:contentRect
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO]))
{
return self;
}
return nil;
}
- (BOOL) canBecomeKeyWindow
{
return YES;
}
- (BOOL) acceptsFirstResponder
{
return YES;
}
- (NSTimeInterval)animationResizeTime:(NSRect)newWindowFrame
{
return 0.1;
}
- (void)sendEvent:(NSEvent *)theEvent
{
if([theEvent type] == NSKeyDown)
{
if([theEvent keyCode] == 36)
return;
}
if([theEvent type] == NSLeftMouseDown)
[self mouseDown:theEvent];
else if([theEvent type] == NSLeftMouseDragged)
[self mouseDragged:theEvent];
[super sendEvent:theEvent];
}
- (void)mouseDown:(NSEvent *)theEvent
{
self.initialLocation = [theEvent locationInWindow];
}
- (void)mouseDragged:(NSEvent *)theEvent
{
NSRect screenVisibleFrame = [[NSScreen mainScreen] visibleFrame];
NSRect windowFrame = [self frame];
NSPoint newOrigin = windowFrame.origin;
NSPoint currentLocation = [theEvent locationInWindow];
if(initialLocation.y > windowFrame.size.height - 40)
{
newOrigin.x += (currentLocation.x - initialLocation.x);
newOrigin.y += (currentLocation.y - initialLocation.y);
if ((newOrigin.y + windowFrame.size.height) > (screenVisibleFrame.origin.y + screenVisibleFrame.size.height))
{
newOrigin.y = screenVisibleFrame.origin.y + (screenVisibleFrame.size.height - windowFrame.size.height);
}
[self setFrameOrigin:newOrigin];
}
}
#end
And .h file:
#import <Cocoa/Cocoa.h>
#interface BorderlessWindow : NSWindow {
NSPoint initialLocation;
}
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)windowStyle
backing:(NSBackingStoreType)bufferingType
defer:(BOOL)deferCreation;
#property (assign) NSPoint initialLocation;
#end
Since this is the top hit on Google...the provided approach didn't work for me as WKWebView intercepts the mouse events before they reach the window. I had to instead create a subclass of WKWebView and do the work there (h/t to Apple's Photo Editor/WindowDraggableButton.swift example).
I use Xamarin, but the code is pretty simple...here are the important bits:
// How far from the top of the window you are allowed to grab the window
// to begin the drag...the title bar height, basically
public Int32 DraggableAreaHeight { get; set; } = 28;
public override void MouseDown(NSEvent theEvent)
{
base.MouseDown(theEvent);
var clickLocation = theEvent.LocationInWindow;
var windowHeight = Window.Frame.Height;
if (clickLocation.Y > (windowHeight - DraggableAreaHeight))
_dragShouldRepositionWindow = true;
}
public override void MouseUp(NSEvent theEvent)
{
base.MouseUp(theEvent);
_dragShouldRepositionWindow = false;
}
public override void MouseDragged(NSEvent theEvent)
{
base.MouseDragged(theEvent);
if (_dragShouldRepositionWindow)
{
this.Window.PerformWindowDrag(theEvent);
}
}
#starkos porvided the correct answer at https://stackoverflow.com/a/54987061/140927 The following is just the ObjC implementation in a subclass of WKWebView:
BOOL _dragShouldRepositionWindow = NO;
- (void)mouseDown:(NSEvent *)event {
[super mouseDown:event];
NSPoint loc = event.locationInWindow;
CGFloat height = self.window.frame.size.height;
if (loc.y > height - 28) {
_dragShouldRepositionWindow = YES;
}
}
- (void)mouseUp:(NSEvent *)event {
[super mouseUp:event];
_dragShouldRepositionWindow = NO;
}
- (void)mouseDragged:(NSEvent *)event {
[super mouseDragged:event];
if (_dragShouldRepositionWindow) {
[self.window performWindowDragWithEvent:event];
}
}
For further info about how to manipulate the title bar, see https://github.com/lukakerr/NSWindowStyles

Resources