NSTextView scroll adjustment when typing at end of document - objective-c

I have a NSTextView with a pretty big bottom indentation. I'm using textDidChange delegate method to adjust some attributes in the text whenever it is changed.
When typing at the edge of the screen and the end of the document, the scroll correctly gets adjusted at the bottom. However, when typing in another character after that, the scroll jumps up a bit.
Looking at the call stack, I see different behavior between typing at a position which will cause the line to wrap, as compared to a normal line.
When typing causes wrapping, the call stack looks like this:
[NSTextView adjustScroll:]
[NSClipView _scrollTo:animateScroll:flashScrollerKnobs:]
[NSTextView keyDown:]
Normally, textDidChange: is called between keyDown: and _scrollTo:, but not here.
However, when typing on the line which doesn't cause a wrap, textDidChange is called as usual:
[NSTextView adjustScroll:]
[NSClipView _scrollTo:animateScroll:flashScrollerKnobs:]
// some attribute changes here
[Document textDidChange:]
[NSTextView keyDown:]
There are multiple questions on SO which state that changing attributes on NSTextView causes all sorts of scrolling trouble, but it's surprising that the scroll values are completely off between these two events. I'd rather have two equal results, but I'm confused what to do here. _scrollTo is a private method and can't be overridden, and I can't really override keyDown either.
Is there a way to hook into the call order of scrolling and the textDidChange event, or to change the control flow of text and clip views?
Further investigation
To reproduce the issue, you'll have to meet at least these conditions:
• You are setting attributes in delegate method
• Text view is scaled (using scaleUnitSquareToSize)
• You might need to make changes to both temporary attributes and the text view's attributed string
• Non-contiguous layout is on.
Based on this thread from 2016 it seems to be a non-contiguous layout bug, which is yet to be fixed. A blog post from 2017 goes through similar issues regarding the order in which text and attributes are processed.

Related

How can I make command-A select all the NSTextView text in rows in an NSTableView?

So if I have an NSView based tableview and inside the views are NSTextViews which are non-editable but selectable...
how can I get that nice functionality of command-A selects all the text? I don't mean row selection. I have row selection disabled for the tableview. I mean highlighting the text in blue so you can copy it to your clipboard. But not just 1 NSTextView's text from one row, all of them from all the rows.
And in addition to command-A click and drag should do this too. But out of the box it seems I can only select one row's text. Here is video showing problem:
https://dl.dropboxusercontent.com/u/2510380/table.mov
(i keep clicking and dragging but can't highlight text on the next row)
here are two mac apps (skype and gabble) that do this:
https://dl.dropboxusercontent.com/u/2510380/skype.mov
and
https://dl.dropboxusercontent.com/u/2510380/gabble.mov
Assuming they are NOT using WebViews with just HTML inside, how do you get this control over the clipboard? i.e. in Skype you select the text and only the conversation is highlighted, not the timestamp of each message. Also the text copied to the clipboard is formatted very nicely. Can you point me in the right direction to reverse engineer skype?
Unfortunately there's no way to do this easily. This is because only ONE control can be the first responder at a time. This means that, though you can have selection in multiple text views, there are several problems:
Only one text view's text will actually be highlighted with the "live" highlight color; the others will have the gray highlight of non-focused controls.
Copy commands will only apply to the first responder text view.
Drag session starts will be initiated from the control the mouse was actually pointing at (irrespective of first responder) and will only drag that control's text.
In a view-based table view, the controls may not even "exist" for a row not currently displayed, so it'll never get the message unless you forcibly create each row, which could be costly for a large table.
Knowing all this, you might be able to "fake it" by having your controller be complicit in a text view and table view subclass's special handling of a select-all message when it's first responder. On receiving this message, the text view subclass can call super then notify the controller (to get its default behavior AND to let you know it happened), at which point the controller can turn around and send the command to all (existing) text views. Highlighting can be spoofed by overriding the text view's drawing and a drag initiation could defer to a delegate (the controller), which would handle writing ALL the strings from your model to the pasteboard (not even touching the text views in possibly-nonexistent row views). The table view subclass would simply pass the same select-all message to the controller without calling super (and even forcibly making sure nothing is selected before returning for good measure).
I hope this helps. If I've forgotten any of your requirements, let me know.
Try like this:-
First create button programatically then write this code after you create button and also write this code in your load method or awakefromnib method.
NSButton *Buttn=// alloc initwithframe;
[Buttn setKeyEquivalentModifierMask:
NSCommandKeyMask];
[Buttn setKeyEquivalent:#"A"];
[Buttn
setAction:#selector(yourmeth:)];
[Buttn setTarget:self];
// now when you press cmd a write
below code in action method
- (void)selectRowIndexes:(NSIndexSet
*)indexes byExtendingSelection:
(BOOL)extend

Embedding a field editor in a NSScrollView

Has anyone ever had experience embedding a field editor (for a NSTextField) inside a scroll view? I'm trying to make the NSTextField scrollable while editing.
Things I've tried:
Dynamically embed it when the custom field editor's -becomeFirstResponder gets called. This semi works; the problem is that when the NSTextField gets resized during editing the custom field editor no longer gets resized with it (and I need this - making an accordion
style application)
Create a "masquerading" field editor out of a NSScrollView, and using NSInvocation forward the methods to the actual surrogate field editor. This is the method I really hope would work; I've implemented all the methods as listed here; but I get an EXC_BAD_ACCESS whenever the field editor is actually loaded (e.g. when I call [customTextField selectText:nil]). I can't seem to pry any information out of the debugger even with Zombies enabled, and looking at the logs of NSObjCMessageLoggingEnabled yields nothing either. It seems like these guys got it working but that was seven years ago.
The last resort would be to drop NSTextFields completely and use NSTextViews (or instead of relying on the field editor mechanism, write one myself), but since I have many rows of data of which only one will be edited at a time, I don't want to instantiate a NSTextView for every single one of them... but then, perhaps it won't be so bad.
I ended up using option 1, and getting it to work without much difficulty. Option 2 was a complete dead end because EXC_BAD_ACCESS popped up everywhere I went.
My custom field editor now keeps a reference to a (custom) scroll view to embed itself in (vvScrollView), and inserts it into the view hierarchy. My code inside my custom field editor (NSTextView) for embedding it inside a scroll view, which is called as soon as the field editor becomes first responder and is automatically inserted into the view hierarchy:
- (void)embedSelfInScrollView {
NSView *realSuperview = [[self superview] superview];
// [self superview] is some kind of private NSClipView class
if ([realSuperview isKindOfClass:[NSTextField class]]) { // the expected behavior: this may change? TODO make less prone to chance
[realSuperview addSubview:[self vvScrollView]]; // insert into view
[[self vvScrollView] setFrameSize:[realSuperview frame].size]; // se the initial size equivalent to control size so it can autoresize the same way
// add the scrollview into the view hierarchy
[[self vvScrollView] setDocumentView:self]; // removes self from previous superview
}
}
The initial problem I had was that I was trying to insert the scrollview into the superview immediately above the field editor's (the private class of NSClipView) which broke almost every automatic sizing option (because I want to be able to resize the NSTextField while editing). Going a step further and bypassing the private class seems to work, but almost seems arbitrary.

How to add a second UILabel to a UIButton, configurable per UIControlState

I want to display 2 strings (at different positions in the button) with different fonts and colors (I'm using the button's setTitle for one, and I need another one), and some attributes must be changed based on the current UIControlState (like the color).
So, I'm searching the best way to add a second label to a UIButton.
I need the label to be configurable per UIControlState (I want a different color for UIControlStateNormal and UIControleStateHighlighted for example).
I've tried the following approches:
Subclass a UIButton and use drawRect: while not recommended (and I now understand why), I don't think it's even possible, it looks like the button's drawRect method is called (and after the one of my subclass) even if I don't call super.
Create a new UILabel and add it as a subview to my button: this is working quite well, except I don't know how to change the color when the UIControlState of the button is changing
Create a new layer and use drawLayer: I don't know how to get the drawLayer method to be called every time the button state is changing (my drawLayer only gets called once, when I use setNeedsDisplay just after adding my layer to the button)
Is there another way to achieve what I'm trying to do, or maybe one of those solutions might work (with a few tweaks)?
Thanks!
The second of your approach works fine. Just add 2 targets: First update to "normal state" target using "all touch events". Second update to "highlighted" using "touch down" event.
If the states are not only changed by touches and want to handle this more generally, Id suggest multithreading. All you really need is calling performSelectorInBackground when initializing all this elements (the selector updates label according to button state) and then again call same performSelectorInBackground on the end of "update label" method, creating an infinite loop.
Ok, I think I found a working solution (for my problem at least).
I'm subclassing the UIButton class (it works for me, since I'm using a custom drawn button anyway), and I override the titleRectForContentRect method that gets called everytime the title has to be displayed (including after a state change, just before display).
I added an UILabel to the button's view to display the second string I want, and during the titleRectForContentRect, I compute the correct frame location for my label, I update my label's text font and color based on the button's state (self.state), and that's all I need.

How do I make a Cocoa NSTextView grow as the user types into it?

For a Cocoa application I am writing, I would like to support a panel to the right of the main document content where users can add notes for the currently selected document content. (If you are familiar with Microsoft Word or Scrivener, this feature is similar to the comment feature in those applications.) Scrivener does a nice job of starting with a text field sized to fit the default text, and then growing it taller as the user types into it. I'd like to implement the same behavior for my Cocoa app.
What's the basic strategy?
There are delegate methods that allow you to capture the actual keystrokes as they come in.
Implement the below delegate method to resign first responder, based upon the keyboard
-(BOOL)textFieldShouldReturn:(UITextField *)textfield
Implement the below delegate method to detect when focus has been given back to the TextField. You may also want to perform the deletion of current text, or retain the text that was already there if you wish
-(void)textFieldDidBeginEditing:(UITextField *)textfield
Implement the below delegate method to detect the character(s) entered and where (based on the caret position), and essentially add the characters to your privately held and displayed string (displayed out to your textfield that is)
-(BOOL)textView:(NSTextView *)aTextView shouldChangeTextInRange:(NSRange)affectedCharRange replacementString:(NSString *)replacementString
Implement the below delegate method to detect when editing has finished so that you can perform any other cleanup etc... that you wish to do.
-(void)textFieldDidEndEditing:(UITextField *)textField
I will get back to you on the dynamic sizing of your TextView itself, but that (at least on iOS) as Ive seen has a solution and at one point I have used it. You will essentially make your font size static, potentially your width static, then edit your height based on how many lines you have, or you could keep your height static, and change your width based on characters, etc... up to you.
Here is a great set of responses on StackOverflow about dynamic sizing
How do I size a UITextView to its content?
So if you combine the keystroke recognition with the dynamic sizing you should have it!!!
A custom non NSScrollView embedded NSTextView does the trick. See my answer here How do I make NSTextView grow with text?.

NSOutlineView elements remain hopelessly undraggable

I have a program with a NSOutlineView (that supports single selection only) from which I'd like to be able to drag elements. These elements should either be received as text or files: for instance, dropping the item on a TextEdit window should put text, but dropping the item on the Finder should create a file. I don't want anything to be dropped over my outline view, even it it comes from itself. This seems easy enough, but for some reason, I can't get it to work.
I checked the NSOutlineView drag and drop example from Apple, and I came to implement the following methods (plus a few definitely unrelated ones):
-(BOOL)outlineView:shouldSelectItem: // I don't expect to drag unselectable items
-(NSArray*)outlineView:namesOfPromisedFilesDroppedAtDestination:forDraggedItems:
-(BOOL)outlineView:writeItems:toPasteboard:
However, when I try to drag an item from my outline view, nothing happens. Instead, it just changes the selection following the cursor.
I've put breakpoints in the two last methods, and they never get called, so their implementation is not the immediate issue.
I must be missing something really obvious here.
Also, this is not (yet) a problem, but how am I supposed to provide contents to my promised files?
I was being stupid and I implemented the methods in the delegate instead of the data source (the two are distinct in my app). Problem solved!
Are you using a custom table view cell? The result of NSCell's hitTestForEvent:inRect:ofView: determines whether a dragging operation can be initiated. It also determines whether your outlineView:writeItems:toPasteboard: should be called.
This method should return NSCellHitContentArea to initiate a drag, or NSCellHitTrackableArea to extend or change the selection.
A standard text cell returns NSCellHitContentArea when you click on the actual text of the cell, and NSCellHitTrackableArea when you click outside of the text. This produces the drag behavior you see in Finder's table view.
You can override this method and always return NSCellHitContentArea if you want all areas of the cell to initiate a drag operation.
See Hit Testing for more information.