Keeping window on top when switching spaces - macos

I have created a window using -[NSWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces].
It only does half of what I want, though: when I switch spaces, the window also switches spaces (as expected), but my window moves to the back behind all other windows in that space. This is especially bad because my app is active but its window is below all other apps' windows. I tried changing the level to NSFloatingWindowLevel, and that does keep it on top, but then it loses key status (focus) when switching spaces.
I tried NSWindowCollectionBehaviorMoveToActiveSpace for the collection behaviour but it's definitely not what I'm looking for.
Is there hope? I know there are almost no other APIs that relate to Spaces.

Spaces is a pain. My solution was to register for a change notification like so:
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
selector:#selector(activeSpaceDidChange:)
name:NSWorkspaceActiveSpaceDidChangeNotification
object:nil];
Then in my WindowController class:
- (void) activeSpaceDidChange:(NSNotification *)aNotification {
if ([NSApp isActive]) [[self window] orderFront:self];
}

For borderless windows (created with NSBorderlessWindowMask), I banged my head until I came up with the following modification to Francis':
- (void) activeSpaceDidChange:(NSNotification *)aNotification {
if ([NSApp isActive])
{
NSRect windowRect = [[self window] frame];
[[self window] setStyleMask:NSTitledWindowMask];
[[self window] setStyleMask:NSBorderlessWindowMask];
[[self window] setFrame:windowRect display:YES];
[[NSApplication sharedApplication] activateIgnoringOtherApps : YES];
}
}
I saw others stating that borderless windows have issues which led me to the idea to trick it momentarily into not seeing it as a borderless window. First I had setting the style mask to "Titled" followed by "activateIgnoringOtherApps" and then setting the "borderless" style back, which seemed to be a more logical solution. Yet, just to see what minimal solution was required for it to function, I ended up seeing the above works. Be great if somebody could fill in what is exactly happening that allows this to work.

Swift 5 solution of #francis-mcgrew's answer ⤵︎
// Quick Solution: Place this in the AppDelegate in applicationDidFinishLaunching
NSWorkspace.shared.notificationCenter.addObserver(
self,
selector: #selector(notifySpaceChanged),
name: NSWorkspace.activeSpaceDidChangeNotification,
object: nil
)
// and this in the same class
#objc func notifySpaceChanged() {
window.orderFrontRegardless()
}

Related

UITextView setText should not jump to top in ios8

Following iOS 8 code is called every second:
- (void)appendString(NSString *)newString toTextView:(UITextView *)textView {
textView.scrollEnabled = NO;
textView.text = [NSString stringWithFormat:#"%#%#%#", textView.text, newString, #"\n"];
textView.scrollEnabled = YES;
[textView scrollRangeToVisible:NSMakeRange(textView.text.length, 0)];
}
The goal is to have the same scrolling down behaviour as the XCode console when the text starts running off the bottom. Unfortunately, setText causes the view to reset to the top before I can scroll down again with scrollRangeToVisible.
This was solved in iOS7 with the above code and it worked, but after upgrading last week to iOS8, that solution no longer seems to work anymore.
I can't figure out how to get this going fluently without the jumping behaviour?
I meet this problem too. You can try this.
textView.layoutManager.allowsNonContiguousLayout = NO;
refrence:http://hayatomo.com/2014/09/26/1307
The following two solutions don't work for me on iOS 8.0.
textView.scrollEnabled = NO;
[textView.setText: text];
textView.scrollEnabled = YES;
and
CGPoint offset = textView.contentOffset;
[textView.setText: text];
[textView setContentOffset:offset];
I setup a delegate to the textview to monitor the scroll event, and noticed that after my operation to restore the offset, the offset is reset to 0 again. So I instead use the main operation queue to make sure my restore operation happens after the "reset to 0" option.
Here's my solution that works for iOS 8.0.
CGPoint offset = self.textView.contentOffset;
self.textView.attributedText = replace;
[[NSOperationQueue mainQueue] addOperationWithBlock: ^{
[self.textView setContentOffset: offset];
}];
Try just to add text to UITextView (without scrollRangeToVisible/scrollEnabled). It seams that hack with scroll enabled/disabled is no more needed in iOS8 SDK. UITextView scrolls automatically.

NSScrollView and manually setting the content offset while scrolling

I am trying to build an endless NSScrollView, i.e. a scroll view that can scroll infinitely in either direction. This is achieved by having a scroll view with fixed dimension which is 'recentered' once it gets too close to either edge, and keeping track of an additional endless offset that is updated when recentering. Apple even demonstrated this approach in a WWDC video for iOS, if I recall correctly.
On iOS everything is working. I perform the recentering logic in -scrollViewDidScroll: and it even works when the scrolling motion is decelerating without breaking the deceleration.
Now for the Mac version. Let me tell you that I'm fairly new to Mac development, so I may simply not have performed these operations in the correct places. I currently have the recentering logic in -reflectScrolledClipView:. When I perform the move operation immediately, however, the scroll view jumps exactly twice as far as I want it to (in this case, to 4000). If I delay the method slightly, it works just as expected.
- (void)reflectScrolledClipView:(NSClipView *)cView
{
[self recenteringLogic];
[super reflectScrolledClipView:cView];
}
- (void)recenteringLogic
{
CGFloat offset = self.documentVisibleRect.origin.y;
if (offset > 6000) {
// This makes the scroll view jump to ~4000 instead of 5000.
[self performSelector:#selector(move) withObject:nil];
// This works, but seems wrong to me
// [self performSelector:#selector(move) withObject:nil afterDelay:0.0];
}
}
- (void)move
{
[self.documentView scrollPoint:NSMakePoint(0, 4000)];
}
Any ideas on how I could achieve the behavior I want?
Try this:
- (void)scrollWheel:(NSEvent *)event {
[super scrollWheel:event];
[self recenteringLogic];
}
- (void)recenteringLogic
{
NSRect rect = self.documentVisibleRect;
if (rect.origin.y > 6000) {
rect.origin.y = 4000;
[self.contentView setBounds:rect];
}
}
reflectScrolledClipView seemed to be clashing with scrollToPoint somehow, and it caused a stack overflows when used with the [self.contentView setBounds:rect]; method of scrolling.
I ended up working with [self performSelector:#selector(move) withObject:nil afterDelay:0.0]; and haven't encountered any serious issues with it, despite it seeming a little wrong.

How to get the frame (origin, Size) of every visible windows on the active space?

I'm trying to figure out how to get the frame of all visible windows.
I tried the following code, but it only works for the app itself other windows report {0,0,0,0}
NSArray *windowArray = [NSWindow windowNumbersWithOptions:NSWindowNumberListAllApplications | NSWindowNumberListAllSpaces];
for(NSNumber *number in windowArray){
NSLog(#"Window number: %#", number);
NSWindow *window = [[NSApplication sharedApplication] windowWithWindowNumber:[number intValue]];
NSLog(#"Window: %#", NSStringFromRect( [[window contentView] frame]));
}
Sample code is appreciated.
I figured it out:
NSMutableArray *windows = (__bridge NSMutableArray *)CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);
for (NSDictionary *window in windows) {
NSString *name = [window objectForKey:#"kCGWindowName" ];
CGRect bounds;
CGRectMakeWithDictionaryRepresentation((CFDictionaryRef)[window objectForKey:#"kCGWindowBounds"], &bounds);
NSLog(#"%#: %#",name,NSStringFromRect(bounds));
}
You can't create an NSWindow for a window of another application. In general, you can't access the objects of other applications except through an interface that they cooperate with, like scripting.
You can get what you're looking for using the Quartz Window Services (a.k.a. CGWindowList) API.
I'm not at all sure that the window numbers returned by Cocoa are the same as the window numbers used by that API. In fact, the docs for -[NSWindow windowNumber] specifically say "note that this isn’t the same as the global window number assigned by the window server". I'm note sure to what use you can put the window numbers returned by +[NSWindow windowNumbersWithOptions:] which are not for your application's windows.

NSTrackingArea works weird - Entire View, or nothing... no rectangles respected

In my "InitWithFrame" method of a view I'm setting a tracking area for which I want to capture mouse enter/exit events.
My problems are two fold:
Without NSTrackingInVisibleRect the events won't be called at all.
No matter what "rect" I put it, one that covers the entire view's frame or one that occupies just a small portion of it - the mouse enter/exited events are called for the entire view, regardless of where the mouse cursor is on the view.
this is how I initialize the tracking area:
trackingArea = [[NSTrackingArea alloc] initWithRect:rect
options: (NSTrackingMouseEnteredAndExited | NSTrackingInVisibleRect | NSTrackingActiveAlways )
owner:self userInfo:nil];
[self addTrackingArea:trackingArea];
Any clues why this happens? I want the mouse enter/exit events to be called only for a small portion (the bottom part) of my view.
Mike Abdullah's answer explains point 2.
Here is a guess about why you don't receive events at all when not using the NSTrackingInVisibleRect flag:
Probably the variable rect you provide is not within the view's coordinate system. You could use the following code as the designated initializer of your NSView subclass to receive mouseEntered: and mouseExited: events for the whole area of your view:
- (id)initWithFrame:(NSRect)frame
{
if ((self = [super initWithFrame:frame]))
{
//by using [self bounds] we get our internal origin (0, 0)
NSTrackingArea* trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:(NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways) owner:self userInfo:nil];
[self addTrackingArea:trackingArea];
[trackingArea release];
}
return self;
}
Apple's documentation says:
When creating a tracking-area object,
you specify a rectangle (in the view’s
coordinate system), ...
Straight from the docs for NSTrackingInVisibleRect:
The NSTrackingArea object is automatically synchronized with changes in the view’s visible area (visibleRect) and the value returned from rect is ignored.

Problem with setContentBorderThickness:forEdge: not actually setting the value

I'm trying to use setContentBorderThickness:forEdge: to create a bottom bar in a Cocoa application.
mipadi was on to something, but in testing it out, I think maybe this is a slightly different problem:
-(void) adjustContentBorderBasedOnArrayControllerSelection{
if(([[self.resultsArrayController selectionIndexes] count] == 0)){
[[self window] setContentBorderThickness:40.0f forEdge:CGRectMinYEdge];
NSLog(#"%f", [[self window] contentBorderThicknessForEdge:CGRectMinYEdge]);
} else {
[[self window] setContentBorderThickness:60.0f forEdge:CGRectMinYEdge];
NSLog(#"%f", [[self window] contentBorderThicknessForEdge:CGRectMinYEdge]);
}
}
Both of those NSLog() messages show the thickness value is 0.0 even after I explicitly set it. Anyone know what's up with that?
You can use CGRectMinYEdge. (On 64-bit systems, NSMinYEdge is #define'd as CGRectMinYEdge anyway).
By any chance have you checked to make sure [self window] isn't nil? Do you have your outlet setup right? I get that behavior if the outlet to the window isn't set.

Resources