NSTextView not refreshed properly on scrolling - cocoa

I have a NSTextView with a sizeable quantity of text. Whenever I scroll however, the view isn't updated properly. There are some artifacts that remain at the top or the bottom of the view. It appears that the view doesn't refresh itself often enough. If I scroll very slowly the view updates correctly though. If I add a border to the view everything works perfectly, borderless view is the one that has a problem. Here's a link to a screenshot:
Thanks

Have you set the setDrawsBackground and copiesOnScroll propertes for either the NSScrollView or the NSClipView?
The first thing I would suggest is turning off the "draws background" property of the NSScrollView:
[myScrollView setDrawsBackground:NO];
Note that this should be set on the NSScrollView, and not on the embedded NSClipView.
The following excerpt from the documentation may be relevant:
If your NSScrollView encloses an NSClipView sending a setDrawsBackground: message with a parameter of NO to the NSScrollView has the added effect of sending the NSClipView a setCopiesOnScroll: message with a parameter of NO. The side effect of sending the setDrawsBackground: message directly to the NSClipView instead would be the appearance of “trails” (vestiges of previous drawing) in the document view as it is scrolled.

Looks like the text field isn't even in the scrolling-area... Are you sure something isnt overlapping it?

I had a similar trouble - artifacts develop when the NSTextView is embedded in another scrollview (ie. a NSTableView).
I actually turned on the setdrawsbackground, and then added a nice color to make it disappear again.
-(void)awakeFromNib{
NSScrollView *scroll = [self enclosingScrollView];
[scroll setBorderType:NSNoBorder];
[scroll setDrawsBackground:YES];
[scroll setBackgroundColor:[NSColor windowBackgroundColor]];
}
This in combination with a scrollWheel event let me use the NSTextView in a NSTableView.
-(void)scrollWheel:(NSEvent *)theEvent{
NSScrollView *scroll = [self enclosingScrollView];
[[scroll superview] scrollWheel:theEvent];
}

I had the same trouble some time ago. I don't remember how I solved it.
Try to place the NSTextView to another view if the superview is a custom view. Just to see what will happen.

Related

NSView inside NSScrollview won't change size

I've made an NSWindow in Interface Builder. Inside this window is an NSScrollView and inside that is a custom NSView.
The NSScrollview fills the NSWindow and the custom NSView fills the NSScrollview.
When the custom NSView is sent the awakeFromNib method its bounds are 0,0 and 256x373 as I'd expect, filling the scrollview.
However later I change the size of the NSView to be larger than 373high but it never changes size in the scrollview.
I've tried setting the frame, I've tried setting the bounds, but nothing makes it change.
Except, when I tried changing the intrinsicSize of the custom NSView it did change, but it made the NSWindow and NSScrollview change sizes as well to fit the new size of 256x1452
Can anyone tell me where I might be going wrong?
Is it something to do with the constraints set on the Scrollview or the NSView? I haven't set any but when I added the items in Interface Builder they were automatically added for me
[EDIT]
I've changed it so that the custom NSView is created programmatically and added to the NSScrollView with setDocumentView: and everything works as I expect. So I guess technically I've solved the problem, but I'd still like an explanation on why it's not working via Interface Builder if anyone knows.
I have a partial solution, which also causes me to pose an additional question. I had a similar issue, I needed to programatically change the size of a view embedded in a NSScrolView.
This code works, need both methods
-(void)markViewSizeChanged /* Works correctly */
{
[self setFrameSize:currentViewSize];
[self setBoundsSize:currentViewSize];
[self setNeedsDisplay:YES];
}
-(NSSize)intrinsicContentSize // Override of class method
{
return currentViewSize;
}
Note: MUST set currentViewSize in awakeFromNib
Now for the curious part. If I reverse the order of the two calls setting the frame and bounds, the size of the embedded view is correct, but the scaling factor of objects drawn is off.
-(void)markViewSizeChanged /* DOES NOT work correctly, scaling in drawing off */
{
[self setBoundsSize:currentViewSize];
[self setFrameSize:currentViewSize];
[self setNeedsDisplay:YES];
}

Cocoa: NSView drawRect painting over IBOutlets

I have an NSView in IB which sits above the app window. I have a subclass of NSView (AddSource) which I assign to the NSView.
On awakeFromNib I instantiate the view:
//add a new Add Source class
addSourceView = [[AddSource alloc] initWithFrame:NSMakeRect(0.0, 959.0, 307.0, 118.0)];
[[winMain contentView] addSubview:addSourceView];
in addSourceView's drawRect method I am adding a white background to the view:
[[NSColor whiteColor] set];
NSRectFill(rect);
[self setNeedsDisplay:YES];//added this to see if it might solve the problem
In winMain's contentView I have a NSButton that when clicked slides the addSourceView onto the window:
NSRect addSourceViewFrame = [addSourceView frame];
addSourceViewFrame.origin.y = 841.0;
[[addSourceView animator] setFrame:addSourceViewFrame];
But it seems as if the app is painting over the IBOutlets I placed on the NSView in IB. If, in IB, I repoistion the NSView so that it is on screen when the app launches everything works fine, the IBOutlets are there as well as the background color.
I'm not sure why this is happening. I've done this before with no problems. I must be doing something different this time.
Thanks for any help.
*note - on the 3rd screen capture, when I say this is what the app looks like when opened, that's when I hard code the Y position of the NSView. When it is functioning correctly it should open as screen capture 1.
Most likely your buttons and custom view are siblings, i.e. they are both subviews of your window's content view. Since siblings are "Stacked" depending on the order in which they are added, when you add the view in code it is being added on top of the buttons. You should be able to fix it by explicitly specifying where the view should be positioned relative to its new siblings like so:
[[winMain contentView] addSubview:addSourceView positioned:NSWindowBelow relativeTo:nil];
which should place it below any existing subviews of your window's content view. Also, remove the setNeedsDisplay: line in drawRect, that leads to unncessary, possibly infinite, redrawing.
EDIT: OK I see what you're doing.
I would suggest creating a standalove view in the NIB by dragging a "Custom View" object into the left hand side (the vertically-aligned archived objects section) and adding your controls there, that should ensure the controls are actualy subviews of the view, then you can just create a reference to the archived view in code, and add/remove it dynamically as needed.
Honestly though, you should probably be using a sheet for these kinds of modal dialogs. Why reinvent the wheel, and make your app uglier in the process?
You added TWO AddSource views to the window. You added one in IB - this view contains your textFields and buttons that are connected to the IBOutlets and it is positioned outside the window.
Then in -awakeFromNib you create another, blank AddSource view (containing nothing) and animate it into the window.
I can't recommend highly enough the Hillegass as the best introduction to IB and the correct way to build Cocoa Apps.
Also, Assertions can be useful to make sure what you think is happening is actually what is happening.
If you are certain you added a button to your view in IB, assert it is so:-
- (void)awakeFromNib {
NSAssert( myButton, #"did i hook up the outlet?");
}
NSAssert is a macro that has zero overhead in a release build.
Calling [self setNeedsDisplay:YES] from -drawRect just causes the same -drawRect to be called again. This will give you big problems.

scrolling NSTextView leaves scrollbar in wrong position

I have an NSTextView inside an NSScrollView. I put text in the NSTextView and scroll it to the bottom programatically, which works OK, but the scrollbar stays at the top. Using the mouse to position the scrollbar causes it to jump to the bottom, where it belongs, and from that point it operates OK.
My code:
textView.string = s;
[textView scrollToEndOfDocument:self];
Don't get hung up on the scrollToEndOfDocument method--I also tried:
[textView scrollRangeToVisible:NSMakeRange(s.length, 0)];
and:
[[scrollViewText contentView] scrollToPoint:NSMakePoint(0, textView.frame.size.height)];
[scrollViewText reflectScrolledClipView:[scrollViewText contentView]];
with exactly the same problem, shown here:
I fixed the problem by adding one line:
textView.string = s;
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantPast]];
[textView scrollToEndOfDocument:self];
That runUntilDate call shouldn't be necessary. My theory is that it gives the NSScrollView a chance to catch up and synchronize itself, somehow.
This is all on Lion. I tried it with the System Preference set to both the Lion "backwards" scrolling and the the traditional pre-Lion scrolling, with identical results.
Any ideas about:
Why that call I added helped, and
How to make it work without that call?
Try NSScrollView's reflectScrolledClipView:
Edit: How about replacing
[[scrollViewText contentView] scrollToPoint:NSMakePoint(0, textView.frame.size.height)];
with
[scrollView.contentView scrollToPoint:NSMakePoint(0, scrollView.documentView.frame.size.height-scrollView.contentSize.height)];
Because as it stands I believe you're scrolling past the document view's frame.

Cocoa: hide custom scroller of textview on application startup

I've created a custom scroller for my textview (initiating it in the awakeFromNib method of the scrollview) and now I want to let the user chose if he wants to show the scrollbar on application startup. The problem is that the scroller always appears even if I hide it immediately after I created it and set it to be the scroller of the scrollview. The weird thing is that trying to hide the scroller after an event has been triggered (for example by clicking on a checkbox in the preferences) the scroller properly hides and shows. What I'm I doing wrong? Any help is appreciated!
The weird thing is that before adding the custom scroller to the scrollview I have to use setHasVerticalScroller:YES, otherwise I can't scroll using the two-fingers scroll gesture. Then, if the user doesn't want the scrollbar to be shown I have to use setHasVerticalScroller:NO in the document's windowControllerDidLoadNib method, using it just after having added the scrollbar in the scrollview's awakeFromNib method won't work. Well, at least now it seems to work!
I've always used IB to set up scrollbars and then used the following line if I want to suppress one of them:
[self.aScrollView setHasHorizontalScroller:NO]; // so only the vertical scrollbar is active
Try using that line in awakeFromNib, later setting it to YES if user chooses, rather than using the "hidden" property.
P.S. An NSTextView added in IB is always embedded in an NSScrollView, and it's the scrollview that governs the scrollbars. So if the above doesn't work, try calling setHasWhateverScroller on the superview of your textview:
[[[aTextView superview] superview] setHasHorizontalScroller:NO];
If you get an "unrecognized selector" error, then try explicitly casting the superview to NSScrollView (which will work only if the superview really is an instance of NSScrollView):
[(NSScrollView *)[[aTextView superview] superview] setHasHorizontalScroller:NO];

How to make a transparent NSView subclass handle mouse events?

The problem
I have a transparent NSView on a transparent NSWindow. The view's drawRect: method draws some content (NSImages, NSBezierPaths and NSStrings) on the view but leaves parts of it transparent.
Clicking on the regions of the view that have been drawn on invokes the usual mouse event handling methods (mouseDown: and mouseUp:).
Clicking on the transparent areas gives focus to whatever window is behind my transparent window.
I would like to make parts of the transparent region clickable so that accidentally clicking between the elements drawn on my view does not cause the window to lose focus.
Solutions already attempted
Overriding the NSView's hitTest: method. Found that hitTest: was only called when clicking on a non-transparent area of the view.
Overriding the NSView's opaqueAncestor method. Found that this was not called when clicking on any part of the view.
Filling portions of the transparent area with [NSColor clearColor] in the drawRect: method, and with an almost-but-not-quite-transparent colour. This had no effect.
Experimented with the NSTrackingArea class. This appears to only add support for mouseEntered:, mouseExited:, mouseMoved:, and cursorUpdate: methods, not mouseUp: and mouseDown:.
I had the same problem. It looks like [window setIgnoresMouseEvents:NO] will do it.
(On Lion, at least. See http://www.cocoabuilder.com/archive/cocoa/306910-lion-breaks-the-ability-to-click-through-transparent-window-areas-when-the-window-is-resizable.html)
As far as I know, click events to transparent portions of windows aren't delivered to your application at all, so none of the normal event-chain overrides (i.e -hitTest:, -sendEvent:, etc) will work. The only way I can think of off the top of my head is to use Quartz Event Taps to capture all mouse clicks and then figure out if they're over a transparent area of your window manually. That, frankly, sounds like a huge PITA for not much gain.
George : you mentioned that you tried filling portions with an almost but not quite transparent color. In my testing, it only seems to work if the alpha value is above 0.05, so you might have some luck with something like this:
[[NSColor colorWithCalibratedRed:0.01 green:0.01 blue:0.01 alpha:0.05] set];
It's an ugly kludge, but it might work well enough to avoid using an event tap.
Did you try overriding
- (NSView *)hitTest:(NSPoint)aPoint
in your NSView sublcass?
You can use an event monitor to catch events outside a window/view.
https://developer.apple.com/library/mac/documentation/cocoa/conceptual/eventoverview/MonitoringEvents/MonitoringEvents.html
You can override the hitTest method in your NSView so that it always returns itself.
According to the NSView documentation, the hitTest method will either return the NSView that the user has clicked on, or nil if the point is not inside the NSView. In this case, I would invoke [super hitTest:], and then return the current view only if the result would otherwise be nil (just in case your custom view contains subviews).
- (NSView *)hitTest:(NSPoint)aPoint
{
NSView * clickedView = [super hitTest:aPoint];
if (clickedView == nil)
{
clickedView = self;
}
return clickedView;
}
You can do:
NSView* windowContent = [window contentView];
[windowContent setWantsLayer:YES]
Making sure that the background is transparent:
[[windowContent layer] setBackgroundColor:[[NSColor clearColor] CGColor]];
Another option would be to add a transparent background image that fills the contentView.

Resources