Cocoa show NSWindow on a specific screen - macos

In a Mac app how can I open a NSWindow on a specific NSScreen (let's say the second screen)?
This is how I show the window, but it only shows on the main screen
self.windowController = NSStoryboard(name: "Main", bundle: nil).instantiateControllerWithIdentifier("mainWindow") as! NSWindowController
let window = self.windowController.window!
window.makeKeyAndOrderFront(self)
Answers in both Swift and OC are welcome.

Use the class function 'screens' to get an array of all the screens you have. From the array, pick the screen you want your window to appear on. Use the co-ordinates on that window (which are relative to the main window) to make a rect for your new window like this;
[self.window setFrame:CGRectMake(pos.x, pos.y, [mywindow frame].size.width
, [mywindow frame].size.height) display:YES];
where pos is computed from the array of screens and your selectoin.

Related

Why does NSTextView not always wrap text fluently while resizing?

I've stumbled upon a behavior in NSTextView that does not seem intended, or that I at least do not understand the reasoning behind.
When you have a large body of text in an NSTextView and you resize the control/window, the wrapping of words only happens fluently and immediately while resizing when the text is scrolled near the top. If you scroll far down in the text, it does not, and it doesn't seem to "commit" the wrapping until you release and finish resizing.
Is there some internal limitation, or is this a bug?
The issue seems to be reproducible:
macOS 10.15.4, Xcode 11.4.1
Create a new macOS App project
Put an NSTextView on the default generated view controller (doesn't matter which of the 3: rich, plain or default) and constrain it so that it resizes with the window (top, bottom, leading, trailing)
Run the application and paste a large body of text into the text view (for example: http://www.gutenberg.org/cache/epub/12281/pg12281.txt)
Scroll to the top of the NSTextView and observe how the text wraps while resizing the window
Scroll to the bottom and observe how it only wraps after resizing the window
Hoping there's any Cocoa detectives out there who can provide some enlightenment on this one.
EDIT:
As per the docs, it states that "the layout manager reserves the right to perform layout for larger ranges". I take it that this means it is indeed intended as a performance consideration.
Is there any way to determine what the limit is, though?
EDIT: You could try subclassing NSScrollView to render the text into multiple containers.
NSTextStorage *storage = [[NSTextStorage alloc] initWithString:string];
NSLayoutManager *manager = [[NSLayoutManager alloc] init];
[storage addLayoutManager:manager];
NSInteger i = 0;
while (YES) {
NSTextContainer *container = [[NSTextContainer alloc] initWithSize:CGSizeMake(width, height)];
[manager addTextContainer:container];
NSTextView *textView = [[NSTextView alloc] initWithFrame:CGRectMake(x, y, width, height) textContainer:container];
[self.contentView addSubview:textView];
i++;
NSRange range = [manager glyphRangeForTextContainer:container];
if ( range.length + range.location == string.length )
break;
}
Then, while resizing the window, you can call NSLayoutManager to ensure the layout only for visible containers.

MAC Cocoa - Programmatically set window size

I have a one window application that has some checkboxes on the screen.
I use NSUserDefaults to store not only the state of the checkboxes but also the main window width, height, and position (x/y).
My issue is to find the right event to read and set the window properties.
Currently I do it at:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// read preferences
UserPreferences *userPrefs = [[UserPreferences alloc] init];
NSRect oldFrame = [window frame];
if( [userPrefs MainWindowWidth] > 0)
oldFrame.size.width = [userPrefs MainWindowWidth];
if( [userPrefs MainWindowHeight] > 0)
oldFrame.size.height = [userPrefs MainWindowHeight];
if( [userPrefs MainWindowTop] > 0)
oldFrame.origin.y = [userPrefs MainWindowTop];
if( [userPrefs MainWindowLeft] > 0)
oldFrame.origin.x = [userPrefs MainWindowLeft];
// set windows properties
[window setFrame:oldFrame display:YES animate:NO];
}
It works but the screen first shows default size and then changes to the stored size so visually a hiccup. This tells me that its too late in the event chain to set these parameters.
I also tried awakefromnib but that seems too early in the chain since setting width and height is simply ignored.
Which event would be the right one to plug this code in to reset the window right before it is show on screen?
Any advise would be appreciated. Every beginning is hard.
thank you.
This is because window's frame is first loaded from nib, and then window is shown (once finished loading from nib).
You can disable 'show window on start' checkbox in interface builder, and show it manually in applicationDidFinishLaunching.
The applicationDidFinishLaunching function is a place to do things, well... as soon as the app has finished launching. But what you really want is to catch the window at the time when it has just been loaded from the nib, but before it has shown. IOW, you're trying to do this in the wrong place.
You need more control over your window, so... create your own window controller! Create your own class which inherits from NSWindowController, say MyWindTrol. In the implementation file, add the awakeFromNib function, and put your efforts to control your window's size and location in there.
In your nib file, drag an NSObject from the library, declare it to be of class MyWindTrol, and control-drag the connections so that your MyWindTrol object's window property points to the window object.

Xcode How do you make circular hitboxes on buttons?

I'm using Xcode 5 to make an UIButton. I made the button in storyboard and set the background image to a .png of a circle with transparency. I set up a simple action for the button to display how many times it was pressed in a label.
When I press the corners of the circle on the screen, it still adds to the score. So the button is still keeping its square hitbox, even though it has a round image as its background. I searched everywhere for a way to make a circular hitbox, but I can't find anything. Is this even possible? Is there an alternative way to do this?
The button responds to touch on corners because the bounding box of the button is a rectangle no matter what image you set for background.
To achieve a circular UIButton you must make a circular bounding box by simply using this code:
- (UIView *)setRoundedView:(UIView *)roundedView toDiameter:(float)newSize {
CGPoint saveCenter = roundedView.center;
CGRect newFrame = CGRectMake(roundedView.frame.origin.x, roundedView.frame.origin.y, newSize, newSize);
roundedView.frame = newFrame;
roundedView.layer.cornerRadius = newSize / 2.0;
roundedView.center = saveCenter;
roundedView.layer.masksToBounds = YES;
return roundedView;
}
In this method you can pass any UIView or subclass of UIView in your case UIButton. So in code you must create an IBOutlet of you button and in your -viewDidLoad method simply put this line of code:
self.btnMyButton = (UIButton *)[self setRoundedView:self.profilePic toDiameter:34];
Assuming you UIButton outlet is btnMyButton and set the diameter as per your requirement.
Hope this helps.
Don't forget to import QuartzCore Framework.

Automatically resize an NSButton to fit programmatically changed text (Xcode)

I have an NSButton (Push Button) with some temporary title text built in Interface Builder / Xcode. Elsewhere, the title text inside the button is changed programmatically to a string of unknown length (actually, many times to many different lengths).
I'd like the button to automatically be resized (with a fixed right position--so it grows out to the left) to fit whatever length of string is programmatically inserted as button text. But I can't figure it out. Any suggestions? Thanks in advance!
If you can't use Auto Layout as suggested by #jtbandes (it's only available in Lion), then you can call [button sizeToFit] after setting its string value, which will make the button resize to fit its string. You would then need to adjust its frame based on the new width.
You can't do this automatically, but it would be easy to do in a subclass of NSButton.
#implementation RKSizeToFitButton
- (void)setStringValue:(NSString*)aString
{
//get the current frame
NSRect frame = [self frame];
//button label
[super setStringValue:aString];
//resize to fit the new string
[self sizeToFit];
//calculate the difference between the two frame widths
NSSize newSize = self.frame.size;
CGFloat widthDelta = newSize.width - NSWidth(frame);
//set the frame origin
[self setFrameOrigin:NSMakePoint(NSMinX(self.frame) - widthDelta, NSMinY(self.frame))];
}
#end
This way you can just set your button's class to RKSizeToFitButton in Interface Builder and then calling setStringValue: on the button to change its label will "just work" with no additional code.
Sure! Just use Auto Layout! :)

NSPopover below caret in NSTextView

I know that in order to show a popover I need an NSView, but I don't think that there is one associated with the caret (inside the NSTextView). Is there a way to show a NSPopover below the caret?
I tried to alloc a NSView and position it using (NSRect)boundingRectForGlyphRange:(NSRange)glyphRange inTextContainer:(NSTextContainer *)container, but the popover will not appear (and there's a reason, that method returns NSRect: {{0, 0}, {0, 0}}).
I'm not sure if you are still looking for answer. I recently was working on a project which happens to need a very similar feature like you described.
You can do the following inside a subclass of NSTextView:
the function you are going to call is : showRelativeToRect:ofView:preferredEdge:
the rect will be a rect inside the NSTextView, using the NSTextView coordinate system, the ofView is the NSTextView, and the preferredEdge is the edge you want this popover thing to hook with.
now, you are saying that you want the PopOver thing to show under the caret, well you have to give him a Rect, a Point is not enough. The NSTextView has a selector called selectedRange, which gives you the range of the selected text, you can use that to locate your caret.
the next thing is to call firstRectForCharacterRange (the class must delegate NSTextInputClient), this method will return a screen coordinate of the selected text inside the NSTextView, then you convert them into the NSTextView coordinate system, you will be able to show the NSPopover thing at a correct position. Here's my code of doing this.
NSRect rect = [self firstRectForCharacterRange:[self selectedRange]]; //screen coordinates
// Convert the NSAdvancedTextView bounds rect to screen coordinates
NSRect textViewBounds = [self convertRectToBase:[self bounds]];
textViewBounds.origin = [[self window] convertBaseToScreen:textViewBounds.origin];
rect.origin.x -= textViewBounds.origin.x;
rect.origin.y -= textViewBounds.origin.y;
rect.origin.y = textViewBounds.size.height - rect.origin.y - 10; //this 10 is tricky, if without, my control shows a little below the text, which makes it ugly.
NSLog(#"rect %#", NSStringFromRect(rect));
NSLog(#"bounds %#", NSStringFromRect([self bounds]));
if([popover isShown] == false)
[popover showRelativeToRect:rect
ofView:self preferredEdge:NSMaxYEdge];
and this is the result.
All the thing I am wondering is that if there is a way to convert using System functions, although I tried the convertRect:toView, but since this method is written in a delegate, the NSTextView always has the coordinate system of (0,0), which makes this method useless.

Resources