Cocoa NSScrollView starts at center - cocoa

I have a NSScrollView item in my main window. Whenever I launch the program, there is text in the scroll view and it starts at the center. Shouldn't the user start reading at the top but why does the app launch it at the center? Thanks!

This can happen if you do not set your views up correctly.
Here's the information I have:
1: You use Interface Builder to set up your views.
2: You are using a NSTextView
3: The code you use for changing the text is:
[[_chapterContent insertText:[Book objectForKey:bookAndChapters]];
4: As you're using a NSTextView, we're not dealing with an issue caused by flipped coordinates.
I'm not sure whether you should use insertText for this task.
At least, you can use something like this...
range.location = 0;
range.length = 0;
[textView setSelectedRange:range];
...to set the position of the caret.
You can then do a...
[textView scrollRangeToVisible:range];
...if you like.
Note: You can use setSelectedRange with a length of 0 and any location within [textStorage length], to position the caret.
-Let me know if this makes the change you need.
I'd suggest using...
textStorage = [textView textStorage];
range.location = 0;
range.length = [textStorage length];
if([self shouldChangeTextInRange:range replacementString:string])
{
[textStorage beginEditing];
[textStorage replaceCharactersInRange:range withAttributedString:attrStr];
[textStorage endEditing];
}
... to replace the text with an attributed string.
If you don't need an attributed string, there's a simpler method available:
[textStorage replaceCharactersInRange:range withString:str];
It's worked well for me (and a number of other people) for many years; it's the recommended way of changing the contents of the NSTextView.
-If you just want to replace the entire contents with a string, you can use
[textView setString:string];

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.

Change the Color of specific substrings in NSTextView

I want to change the color of specific text in NSTextView. The method should check that after a keydown event.
For example: the word void is finished and the string void changes the
color to blue. Like a code editor.
I searched for a long time but don't find anything.
My Code:
NSRange range = [text rangeOfString:#"void"];
NSString *substring = [[text substringFromIndex:NSMaxRange(range)] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
//I think, here is the mistake.
NSAttributedString *now = [NSAttributedString initWithString:substring];
[now setTextColor:[[NSColor blueColor]]];
I have read that i have to use a NSAttributedString but i don't know how I can get this class from a string.
I'am quite new in cocoa programming.
Thanks for every help!
You can do this way:
NSString *str = #"Hello. That is a test attributed string.";
//in place of NSMakeRange put your range
[self.textview setRichText:YES];
[self.textview setString:str];
[self.textview setTextColor:[NSColor redColor] range:NSMakeRange(3,5)];

Premature line wrapping in NSTextView when tabs are used

I have a strange problem with NSTextView linewrapping after the 51st column if I enter a line of tabs. This only happens with tabs, not with any other character, which wrap correctly at the edge of the text view, not after the 51st character.
This is easy to repeat. Create a blank project in XCode with a single window and just one NSTextView. The only non-default settings are that I have removed constraints, and used the old style autosize to autosize the textview so that it fills the window. I have written no code. Now run the application, open up the window so that is much wider than 51 characters, hold down the tab key and it will wrap early.
Thanks in advance.
The issue here is that NSTextView has a default NSMutableParagraphStyle object which has a list of attributes such as line wrapping, tab stops, margins, etc... You can see this by going to the Format menu, text subview, and select the "Show Ruler" menu. (You get this menu for free with any NSTextView).
Once you show the ruler you will see all of your tab stops and this will explain why your tabs are wrapping once you reach the last tab stop.
So the solution you need is to create an array of tabs that you want for your paragraph style object and then set that to be the style for the NSTextView.
Here is a method to create tabs. In this example, it will create 5 left aligned tabs, each 1.5 inches apart:
-(NSMutableAttributedString *) textViewTabFormatter:(NSString *)aString
{
float columnWidthInInches = 1.5f;
float pointsPerInch = 72.0f;
NSMutableArray * tabArray = [NSMutableArray arrayWithCapacity:5];
for(NSInteger tabCounter = 0; tabCounter < 5; tabCounter++)
{
NSTextTab * aTab = [[NSTextTab alloc] initWithType:NSLeftTabStopType location:(tabCounter * columnWidthInInches * pointsPerInch)];
[tabArray addObject:aTab];
}
NSMutableParagraphStyle * aMutableParagraphStyle = [[NSParagraphStyle defaultParagraphStyle]mutableCopy];
[aMutableParagraphStyle setTabStops:tabArray];
NSMutableAttributedString * attributedString = [[NSMutableAttributedString alloc] initWithString:aString];
[attributedString addAttribute:NSParagraphStyleAttributeName value:aMutableParagraphStyle range:NSMakeRange(0,[aString length])];
return attributedString;
}
Then you invoke it before you add any text to your NSTextView in order to set the default paragraph style with those tab stops in it:
[[mainTextView textStorage] setAttributedString:[self textViewTabFormatter:#" "]];
You can find a additional informatio here, if you want a deeper tutorial:
http://www.mactech.com/articles/mactech/Vol.19/19.08/NSParagraphStyle/index.html
I'm sharing my experience since I recently had kind of the same type of problem(s)
- when pressing tabs, cursor jumps to the next line after about 10-12 tabs
- when there are multiple lines of text, when pressing tabs the whole paragraph turns into bulleted lines
I used the above method by "Ed Fernandez" and could only resolve the problem when there is no text in the NSTextView initially but when existing saved text is loaded it had above problems
For this I tried the below code from below link (It really worked and solved the both problems)
http://www.cocoabuilder.com/archive/cocoa/159692-nstextview-and-ruler-tab-settings.html
You don't need to call "release" if you are using automatic reference counting.
- (IBAction)formatTextView:(NSTextView *)editorTextView
{
int cnt;
int numStops = 20;
int tabInterval = 40;
NSTextTab *tabStop;
NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init];
//attributes for attributed String of TextView
NSMutableParagraphStyle *paraStyle = [[NSMutableParagraphStyle alloc] init];
// This first clears all tab stops, then adds tab stops, at desired intervals...
[paraStyle setTabStops:[NSArray array]];
for (cnt = 1; cnt <= numStops; cnt++) {
tabStop = [[NSTextTab alloc] initWithType:NSLeftTabStopType location: tabInterval * (cnt)];
[paraStyle addTabStop:tabStop];
}
[attrs setObject:paraStyle forKey:NSParagraphStyleAttributeName];
[[editorTextView textStorage] addAttributes:attrs range:NSMakeRange(0, [[[editorTextView textStorage] string] length])];
}
This tab limit is there due to the "Ruler" concept where it's limited to about 12 tabstops. You can see the Ruler by calling
[editorTextView setRulerVisible:YES];

NSTextView line break on character, not word

I have an NSTextView for editing a long string without spaces, but with punctuation characters. I'd like it to wrap at whatever character falls at the end of the line instead of trying to split it into words where it finds punctuation, which results in uneven lines.
I thought it would be as easy as making a subclass of NSATSTypesetter to reimplement this method like so:
- (BOOL) shouldBreakLineByWordBeforeCharacterAtIndex:(NSUInteger)charIndex {
return YES;
}
This is not having any effect on the layout of the text view. It is being called once for every line break on every layout, but only where the line break would have occurred anyway.
The line breaking setting in a NSTextView is actually a attribute of the text in it's textStorage.
You need to get the NSTextStorage of that view which is a NSMutableAttributedString then set that string's style to your needed line break setting.
Keep in mind the text in the storage keeps changing and the text itself contains the setting so you need to keep setting it on each change.
Call this method below as [self setLineBreakMode:NSLineBreakByCharWrapping forView:txtView]; every time the text storage is changed, e.g.: KVO observe txtView.string
-(void)setLineBreakMode:(NSLineBreakMode)mode forView:(NSTextView*)view
{
NSTextStorage *storage = [view textStorage];
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[style setLineBreakMode:mode];
[storage addAttribute:NSParagraphStyleAttributeName value:style range:NSMakeRange(0, [storage length])];
[style release];
}

Changing text selection color in NSTextView

I'm trying to write a "highlight" feature on an NSTextView. Currently, everything works great. You select a range of text and the background color of that text changes to yellow. However, while it's still selected, the background is that standard blue of selected text. How do I make that standard selection indicator color not show up in certain cases?
Thanks!
Use -[NSTextView setSelectedTextAttributes:...].
For example:
[textView setSelectedTextAttributes:
[NSDictionary dictionaryWithObjectsAndKeys:
[NSColor blackColor], NSBackgroundColorAttributeName,
[NSColor whiteColor], NSForegroundColorAttributeName,
nil]];
You can simply pass an empty dictionary if you don't want the selection indicated in any way at all (short of hiding the insertion point).
Another option is to watch for selection changes and apply the "selection" using temporary attributes. Note that temporary attributes are used to show spelling and grammar mistakes and find results; so if you care about preserving these features of NSTextView then make sure only to add and remove temporary attributes, not replace them.
An example of this is (in a NSTextView subclass):
- (void)setSelectedRanges:(NSArray *)ranges affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag;
{
NSArray *oldRanges = [self selectedRanges];
for (NSValue *v in oldRanges) {
NSRange oldRange = [v rangeValue];
if (oldRange.length > 0)
[[self layoutManager] removeTemporaryAttribute:NSBackgroundColorAttributeName forCharacterRange:oldRange];
}
for (NSValue *v in ranges) {
NSRange range = [v rangeValue];
if (range.length > 0)
[[self layoutManager] addTemporaryAttributes:[NSDictionary dictionaryWithObject:[NSColor blueColor] forKey:NSBackgroundColorAttributeName]
forCharacterRange:range];
}
[super setSelectedRanges:ranges affinity:affinity stillSelecting:stillSelectingFlag];
}

Resources