Get value of "Displays have separate spaces" option in Mavericks - objective-c

Is it possible to determine if the "Displays have separate spaces" option is checked in OSX Mavericks? I found the option is stored in com.apple.spaces.plist with name "spans-displays" but this code doesn't work with sandboxing:
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init];
[userDefaults addSuiteNamed:#"com.apple.spaces"];
NSLog(#"%i", [[userDefaults objectForKey:#"spans-displays"] integerValue]);
[userDefaults release];
Thanks!

To my knowledge there is no simple API to discover this, Apple have never provided comprehensive API relating to Spaces.
However, with a bit of lateral thinking you can figure it out.
What is a distinctive feature of displays having separate spaces?
There are multiple menubars.
So "Are there multiple menubars?" has the same answer as "Do displays have separate spaces?"
Is there an API to tell you if a screen has a menubar?
Again, not to my knowledge, but can we figure it out?
NSWindow has an instance method constrainFrameRect:toScreen: which given a rectangle, representing a window frame, and a screen returns an adjusted rectangle where at least part of the rectangle is visible on the screen. Furthermore, by definition if the top edge of the rectangle is above the menubar area the rectangle will be adjusted so the top edge abuts the menubar...
Which means if we pass it a rectangle abutting the top edge of the screen the returned rectangle will abut the top edge of the menubar, provided there is a menubar. If there is no menubar then the returned rectangle will have the same top edge.
So we can determine if there is a menubar and its height. One small wrinkle, as constrainFrameRect:toScreen: is an instance method we need a window, any window, to make our code work.
Here is one way to code this as a function:
CGFloat menuBarHeight(NSScreen *screen)
{
// A dummy window so we can call constrainFrameRect:toScreen
NSWindow *dummy = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 100, 100)
styleMask:NSTitledWindowMask
backing:NSBackingStoreBuffered defer:YES];
// create a small rectangle at the top left corner of the screen
NSRect screenFrame = screen.frame;
NSRect testFrame = NSMakeRect(NSMinX(screenFrame), NSMaxY(screenFrame)-30, 30, 30);
// constrain the rectangle to be visible
NSRect constrainedFrame = [dummy constrainFrameRect:testFrame toScreen:screen];
// did the top edge move? delta = 0 -> no, delta > 0 - yes and by the height of the menubar
CGFloat delta = NSMaxY(testFrame) - NSMaxY(constrainedFrame);
return delta;
}
So now we can tell if a particular screen has a menubar. How about more than one screen?
Well NSScreen's class method screens returns an array of all the available screens, so all we need to do is call our menuBarHeight function on each screen and see how many menubars we find.
If we find more than 1 then we've determined that displays have separate spaces.
Here is one way to code that, again as a function:
BOOL haveIndepenantScreens()
{
BOOL foundMenuBar = NO;
for (NSScreen *aScreen in NSScreen.screens)
{
if (menuBarHeight(aScreen) > 0)
{
if (foundMenuBar)
// second menu bar found
return YES;
else
// record found first menubar
foundMenuBar = YES;
}
}
return NO; // did not find multiple menubars
}
Job done :-)

use
NSScreen.screensHaveSeparateSpaces
Not sure when this appeared in the documents, but it is there as of 2021!
One advantage of this over #CRD's excellent answer is that it works even if the user has selected 'Automatically hide and show the menu bar'
credit for the answer to #chockenberry

Related

Mouse Enter/Exit events on partially hidden NSViews

I have a problem that I think is solvable with some hackery, but I'm very curious if there is an easier way to get the job done without having to do all of that.
I have a stack of NSViews (layer-backed, if that somehow helps provides some better solution), as shown below:
The thing here is that this is essentially a menu, but is hover-sensitive. If the user hovers over one of the exposed parts of the lower-level views, I need to perform an action depending on what that view is. It is a dynamic system so the number of stacked menu items like this may change, making static calculations more difficult. As you can see, they are basically all a copy (shape-wise) of the first item, but then rotated a bit the further you go down the stack via simple transform rotation.
My question to the SO community is what do you all think the best approach to getting mouseEntered: and mouseExited: events for just the literally visible portions of these views?
What I have attempted to do is use an NSTrackingArea on the visibleRect portion of these views, which sounds much more handy than it really is in this situation. In reality, the visibleRect seems to be "visible" for all of them, all the time. Nothing is explicitly blocked or hidden by anything more than just a partially overlapping NSView. All that happens is I get a spammed console from all of the views screaming out at once that a mouse entered their rect.
Something I am considering is making sub-NSView's of each menu item and having each of those be responsible for the tracking area... each menu item having a "strip" view along the right and bottom sides that could report, but that's still a bit of a hack and is icky.
Does anyone have a better idea? Perhaps one from experience?
Thanks!
I know you already have a solution, but I thought I would try a different approach, that didn't require getting tons of mouseMoved events. I created 3 custom views in code, added tracking rects for them and sent all mouseEntered and mouseExited messages to the same method that does a hitTest to determine which view is top most. This is the code for the content view of the window.
#implementation MainView
#synthesize oldView;
-(void)awakeFromNib {
oldView = nil;
Card *card1 = [[Card alloc]initWithFrame:NSMakeRect(150, 150, 200, 150) color:[NSColor redColor] name:#"Red Box"];
NSTrackingArea *area1 = [[NSTrackingArea alloc]initWithRect:card1.frame options:NSTrackingMouseEnteredAndExited|NSTrackingActiveInActiveApp owner:self userInfo:nil];
[self addTrackingArea:area1];
[self addSubview:card1];
Card *card2 = [[Card alloc]initWithFrame:NSMakeRect(180, 120, 200, 150) color:[NSColor yellowColor] name:#"Yellow Box"];
NSTrackingArea *area2 = [[NSTrackingArea alloc]initWithRect:card2.frame options:NSTrackingMouseEnteredAndExited|NSTrackingActiveInActiveApp owner:self userInfo:nil];
[self addTrackingArea:area2];
[self addSubview:card2];
Card *card3 = [[Card alloc]initWithFrame:NSMakeRect(210, 90, 200, 150) color:[NSColor greenColor] name:#"Green Box"];
NSTrackingArea *area3 = [[NSTrackingArea alloc]initWithRect:card3.frame options:NSTrackingMouseEnteredAndExited|NSTrackingActiveInActiveApp owner:self userInfo:nil];
[self addTrackingArea:area3];
[self addSubview:card3];
}
-(void)mouseEntered:(NSEvent *)theEvent {
[self reportTopView:theEvent];
}
-(void)mouseExited:(NSEvent *)theEvent {
[self reportTopView:theEvent];
}
-(void)reportTopView:(NSEvent *)theEvent {
id topView = [self hitTest:[theEvent locationInWindow]];
if (![topView isEqual:oldView]) {
oldView = topView;
([topView isKindOfClass:[Card class]])? NSLog(#"%#",[(Card *)topView name]):NULL;
}
}
This is the code for what I called cards (colored rectangles):
#implementation Card
#synthesize name,fillColor;
- (id)initWithFrame:(NSRect)frame color:(NSColor *)color name:(NSString *)aName{
self = [super initWithFrame:frame];
if (self) {
self.fillColor = color;
self.name = aName;
}
return self;
}
- (void)drawRect:(NSRect)rect {
[self.fillColor drawSwatchInRect:rect];
}
I finally came to a solution on Twitter via Steven Troughton-Smith. Here's how it works:
In each menu item, I am disregarding anything related to NSTrackingArea or direct mouse position interpretation. Instead, the parent controller view is handling all of the tracking and receiving mouse movement events.
Each menu item has an overridden hitTest: method that does the point conversion and returns whether or not the point being tested is within the background image (there are shadows and stuff in there, making it more difficult than the vanilla implementation).
I then setup a sort of "hover menu item changed" callback in the controller so that I can handle hover menu changes.
This was a pretty straightforward solution. Very glad I decided to stop and ask, rather than hack something together with my previous idea.
Thanks Steven!
Overlapping tracking-areas:
All you have to do is hitTest from view you are in. if this is true:
window.view.hitTest(window.mousePos) === self/*sudo code*/
What this code does is that it returns the view under the mouse position. Now all you have to do is setup a few "if" and "else" clauses to verify that your mouse is off or on the view.
Full code example:
https://gist.github.com/eonist/537ae53b86d5fc332fd3
Full description of the concept here: (perma link)
http://stylekit.org/blog/2015/12/20/Overlapping-tracking-areas/
VS the default enter and exit behaviour:
I had to add another answer to this question as this is another approach to solve the problem. This approach now also includes path assertion (think rects with round edges or other custom paths)
The answer is long winded but it works:
http://stylekit.org/blog/2016/01/28/Hit-testing-sub-views/
it involves using the apple provided method: CGPathContainsPoint(path,transform,point)
If you follow the link to that blog post and then from there check the styleKit repo on github. You will find the code need to achieve the gif animation example given above. Im providing this as a pointer to the answer as it may take you significantly less time than trying to research this on your own. I use this technique in all my UI elements and it works flawlessly.

Synchronised scrolling between two instances of NSScrollView

I have two instances of NSScrollView both presenting a view on the same content. The second scroll view however has a scaled down version of the document view presented in the first scroll view. Both width and height can be individually scaled and the original width - height constraints can be lost, but this is of no importance.
I have the synchronised scrolling working, even taking into account that the second scroll view needs to align its scrolling behaviour based on the scaling. There's one little snag I've been pulling my hairs out over:
As both views happily scroll along the smaller view needs to slowly catch up with the larger view, so that they both "arrive" at the end of their document at the same time. Right now this is not happening and the result is that the smaller view is at "end-of-document" before the larger view.
The code for synchronised scrolling is based on the example found in Apple's documentation titled "Synchronizing Scroll Views". I have adapted the synchronizedViewContentBoundsDidChange: to the following code:
- (void) synchronizedViewContentBoundsDidChange: (NSNotification *) notification {
// get the changed content view from the notification
NSClipView *changedContentView = [notification object];
// get the origin of the NSClipView of the scroll view that
// we're watching
NSPoint changedBoundsOrigin = [changedContentView documentVisibleRect].origin;;
// get our current origin
NSPoint curOffset = [[self contentView] bounds].origin;
NSPoint newOffset = curOffset;
// scrolling is synchronized in the horizontal plane
// so only modify the x component of the offset
// "scale" variable will correct for difference in size between views
NSSize ownSize = [[self documentView] frame].size;
NSSize otherSize = [[[self synchronizedScrollView] documentView] frame].size;
float scale = otherSize.width / ownSize.width;
newOffset.x = floor(changedBoundsOrigin.x / scale);
// if our synced position is different from our current
// position, reposition our content view
if (!NSEqualPoints(curOffset, changedBoundsOrigin)) {
// note that a scroll view watching this one will
// get notified here
[[self contentView] scrollToPoint:newOffset];
// we have to tell the NSScrollView to update its
// scrollers
[self reflectScrolledClipView:[self contentView]];
}
}
How would I need to change that code so that the required effect (both scroll bars arriving at an end of document) is achieved?
EDIT: Some clarification as it was confusing when I read it back myself: The smaller view needs to slow down when scrolling the first view reaches the end. This would probably mean re-evaluating that scaling factor... but how?
EDIT 2: I changed the method based on Alex's suggestion:
NSScroller *myScroll = [self horizontalScroller];
NSScroller *otherScroll = [[self synchronizedScrollView] horizontalScroller];
//[otherScroll setFloatValue: [myScroll floatValue]];
NSLog(#"My scroller value: %f", [myScroll floatValue]);
NSLog(#"Other scroller value: %f", [otherScroll floatValue]);
// Get the changed content view from the notification.
NSClipView *changedContentView = [notification object];
// Get the origin of the NSClipView of the scroll view that we're watching.
NSPoint changedBoundsOrigin = [changedContentView documentVisibleRect].origin;;
// Get our current origin.
NSPoint curOffset = [[self contentView] bounds].origin;
NSPoint newOffset = curOffset;
// Scrolling is synchronized in the horizontal plane so only modify the x component of the offset.
NSSize ownSize = [[self documentView] frame].size;
newOffset.x = floor(ownSize.width * [otherScroll floatValue]);
// If our synced position is different from our current position, reposition our content view.
if (!NSEqualPoints(curOffset, changedBoundsOrigin)) {
// Note that a scroll view watching this one will get notified here.
[[self contentView] scrollToPoint: newOffset];
// We have to tell the NSScrollView to update its scrollers.
[self reflectScrolledClipView:[self contentView]];
}
Using this method the smaller view is "overtaken" by the larger view when both scrollers reach a value of 0.7, which is not good. The larger view then scrolls past its end of document.
I think you might be approaching this in the wrong way. I think you should be getting a percentage of how far down each scroll be is scrolled in relation to itself and apply that to the other view. One example of how this could be done is this way using NSScroller's -floatValue:
NSScroller *myScroll = [self verticalScroller];
NSScroller *otherScroll = [otherScrollView verticalScroller];
[myScroll setFloatValue:otherScroll.floatValue];
I finally figured it out. The answer from Alex was a good hint but not the full solution as just setting the float value of a scroller doesn't do anything. That value needs translation to specific coordinates to which the scroll view needs to scroll its contents.
However, due to differences in size of the scrolled document view, you cannot just simply use this value, as the scaled down view will be overtaken by the "normal" view at some point. This will cause the normal view to scroll past its end of document.
The second part of the solution was to make the normal sized view wait with scrolling until the scaled down view has scrolled its own width.
The code:
// Scrolling is synchronized in the horizontal plane so only modify the x component of the offset.
NSSize ownSize = [[self documentView] frame].size;
newOffset.x = MAX(floor(ownSize.width * [otherScroll floatValue] - [self frame].size.width),0);
The waiting is achieved by subtracting the width of the scroll view from the width times the value of the scroller. When the scaled down version is still traversing its first scroll view width of pixels, this calculation will result in a negative offset. Using MAX will prevent strange effects and the original view will quietly wait until the value turns positive and then start its own scrolling. This solution also works when the user resizes the app window.

Problem with adding many tool tips to a view

My Cocoa application has a view with about fifty colored rectangles to be displayed, which represents a heat map of some data. I cannot figure out how to add tool tips to each of the rectangles showing information about the data which that rectangle represents. I looked at the developer documentation for NSView and have added the following code:
- (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)data
{
// use the tags to determine which rectangle is under the mouse
if (tag == blueTag) {
return NSLocalizedString(#"The Blue rectangle", #"");
}
if (tag == redTag) {
return NSLocalizedString(#"The Blue rectangle", #"");
}
// we should never get to here!
return NSLocalizedString(#"Unknown tooltip area", #"");
}
// add tooltips for the rectangles (in my drawRect method
// after the rects have been initialized etc.)
[self removeAllToolTips];
redTag = [self addToolTipRect:startingRect owner:self userData:NULL];
blueTag = [self addToolTipRect:blueRect owner:self userData:NULL];
I run into two issues:
1) when I print out the tag for the tooltips, they both show 1 as the tag even though they are for two different rectangles.
2) the stringForToolTip method is never called
Any help/suggestions would be great. Thanks!
I think the main problem is that you're adding the tool tip rects in -drawRect:. You only need to update the tooltip rects if the view is resized, not every time it's drawn. Instead, add a method to configure the tooltip rects and then call that from your view's -init method.
You can then override -setFrame: and call your tooltip configuration method after calling [super setFrame:newFrame].
I should point out that in your code both rectangles will output The Blue rectangle because the log strings are the same...

NSWindow setFrame | setPosition on the screen

In my application, I need to display a multiple window of same NIB, at a time, and it will be closed/released on the timer basis,
everything is working fine, except the window position. My requirement is, it should display the window exactly below the previous window, I am able to calculate origin for the new window if any window of same NIB is present.
I am using this function to get the origin:
-(void)awakeFromNib{
NSString *eventMsg = [NSString stringWithFormat:#"%s is set “,eventName];
[pTextField setStringValue:eventMsg];
[pWindow setFrameOrigin:[self pointstoDisplayScreen]];
/* On timer callback , i will show the fadeout and fadein effects*/
[self startTimer];
}
/* This method returns the coordinate where to
draw the window
*/
-(NSPoint)pointstoDisplayScreen{
NSRect frame = [[NSScreen mainScreen] visibleFrame];
NSRect windowRect = [pWindow frame];
CGFloat yCoordinate = frame.size.height-windowRect.size.height;
NSPoint point = NSMakePoint(frame.size.width - windowRect.size.width, frame.size.height-windowRect.size.height);
/* Let there be some gap, if any window present and let it draw below to
the existing window
*/
point.y -= (windowRect.size.height + windowOffset)*noOfWindow;
NSLog([NSString stringWithFormat:#"PositionToDisplayScreen x = [%f] y = [%f]",point.x,point.y ]);
return point;
}
The problem is if if previous window is present it draws the new point, slightly below and toward right side of the existing window,
Is there any property i need set, the problem is, if the previous position is exactly on the top right, then it draw the new window on the opposite corner.
[NSWindowController setShouldCascadeWindows:NO];
By setting this, its working, Now all the notification drawing at the top right corner of the screen.
Thanks.
Your window location is "jumping" because you are not doing any bounds checking to determine that the calculated new origin is within the screen bounds.
The class NSScreen will give you details of the screen(s), for each on you cant get its frame.
Now you need to decide how you want a window origin to move; e.g. to the screen edge and no further, onto an adjacent screen if there is one, wrapping (off right means come on left), etc. The choice will depend on your application.
Armed with the screen frame(s) and the movement algorithm you can calculate your new origin.

Objective-C : Endless UIScrollView without pagingEnabled

In my iPhone app there is a scrollview pagingEnabled=NO which can contain up to 200 subviews (150 x 150) and the challenge is to do lazy loading and simulate endless scrolling (without bouncing) in horizontal directions.
Is there a solution or an alternative for this request?
Lazy loading of a scroll view is demonstrated in one of Apple's sample code projects: PageControl.
To fake endless scrolling what I'd suggest is to make your scroll view very wide to begin with, wider than an average person would scroll in one set of scrolling behaviors. Then in your delegate methods -scrollViewDidEndScrollingAnimation:, -scrollViewDidEndDragging:willDecelerate: and -scrollViewDidEndDecelerating:, one or more of which will be called after the user is done scrolling, reposition your content to exist in the center of the scroll view and update your contentOffset point without animating.
For that to work visually you would need to also disable the horizontal scroller. You'll also need to consider how to determine what view to draw at a particular contentOffset with this method since you won't be able to just divide the contentOffset.x by the scroll view's bounds anymore to find out where you are.
Hello I found the way to do it.
I have a master array with all the subviews (in my case they are images, so I store the names).
The scrollview only has 3 subviews: left, current, right.
Paging is enabled, so the user cant really spin more that one view left/right at any time.
What I do is:
1) track his current position on the master array. If he moves left, subtract one; right add one. Like this:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[some code to determine current page, based on contentOffset]
if (page == 0){
NSLog(#"Going left");
if (currentPage > 0){
currentPage--;
} else {
//cycle to last
currentPage = [images count] -1;
}
} else if (page == 2){
NSLog(#"Going right");
if (currentPage < ([images count] -1)){
currentPage++;
} else {
//cycle to first
currentPage = 0;
}
} else{
NSLog(#"Not moving");
}
2) after the user moves, I reload 3 new images, like this:
//updates the 3 views of the scrollview with a new center page.
-(void) updateScrollViewForIndex:(NSInteger)newCenterPage{
//fist clean scroll view
for (UIView *sView in [scroll subviews]){
[sView removeFromSuperview];
}
NSInteger imgCount = [images count];
//set center view
[self loadImageIndex:newCenterPage atPosition:1];
//set left view
if (newCenterPage > 0){
[self loadImageIndex:newCenterPage-1 atPosition:0];
} else {
//its the first image, so the left one is the last one
[self loadImageIndex:imgCount-1 atPosition:0];
}
//set right view
if (newCenterPage < imgCount-1){
[self loadImageIndex:newCenterPage+1 atPosition:2];
} else {
//its the last image, so ther right one is the first one
[self loadImageIndex:0 atPosition:2];
}
}
3) Finally re-center the scroll view to the center view again:
[scroll setContentOffset:CGPointMake(1 * viewWidth, 0)];
Hope this helps, although "the man with the plan" is Mr. Clark, who pointed the way.
Gonso
Matt Gallagher has a blog post which describes a solution to this exact problem. I've used it and it works great.
The UIScrollView and UIPageControl in Cocoa Touch allow for user interfaces with multiple panning pages. The sample project that Apple provides (PageControl) keeps all child views for every page in a lazily loaded array. I'll show you how you can implement this using just two child views, no matter how many virtual pages you wish to represent.
It works by shuffling around the child views and reseting their content when necessary. I used this for displaying flash cards, where there could be anywhere from 3 to 3,000 items. Although it's set up right now for paging, I'm sure you could get it to work with regular scrolling.