NSOutlineView scroll animation is not working - macos

I want to remove the NSOutlineView's show/hide button.So,I override the NSOutlineView and get the mouseDown event.The follow is the code.
-(void)mouseDown:(NSEvent *)theEvent
{
NSLog(#"LeftFolderListOutlineView mouseDown");
[super mouseDown:theEvent];
NSPoint localPoint = [self convertPoint:theEvent.locationInWindow
fromView:nil];
NSInteger row = [self rowAtPoint:localPoint];
id clickedItem = [self itemAtRow:row];
if (![clickedItem isKindOfClass:[NSDictionary class]]) {
return;
}
if ([self isItemExpanded:clickedItem]) {
[[self animator] collapseItem:clickedItem];
}else{
[[self animator] expandItem:clickedItem];
}
}
It should be a scroll animation when the NSOutlineView collapse or expand.But in this case it's not working.Anyone tell me why and how can I improve this?

To remove 'show/hide button' (outline cell) you could implement - (NSRect)frameOfOutlineCellAtRow:(NSInteger)row method in the NSOutliveView subclass and return NSZeroRect.
NSOutlineView collapse/expand animation is not animatable via animator.
Only OS 10.7 or above provide collapse/expand animation effects. So it you planed to support older OS versions you need to provide separate implementation.
If you want to provide collapse/expand animation on OS 10.6 or below, you definitely needed to override 'drawRect' of NSOutlineView.
-- Update --
Sorry, I think I neglected the main point. 10.7 expand/collapse animation is automatically kick in only when users clicked the outline cell. If we want to show the animation without default outline cells, there is no other way but manually implementing animation effects, I think.
I made a sample project that implement expand/collapse animation effects with image drawing.
Check the source codes in here: https://github.com/roh0sun/ovanimation

Related

NSTextField Drawing on Top of Sub View

I have created a secondary NSViewController to create a progress indicator "popup". The reason for this is that the software has to interact with some hardware and some of the functions take the device a few seconds to respond. So being thoughtful of the end user I have a NSViewController that has a NSView (that is black and semi-transparent) and then a message/progress bar on top. This is added to the window using addSubView.
Everything works great except when the screen has a NSTextField in it. The popup shows but the NSTextField is drawn on top. What is this?
The view code I used for drawing semi-transparent:
#implementation ConnectingView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
// Drawing code here.
CGContextRef context = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
CGContextSetRGBFillColor(context, 0.227,0.251,0.337,0.8);
CGContextFillRect(context, NSRectToCGRect(dirtyRect));
}
#end
The code I use to show the progress view
-(void) showProgressWithMessage:(NSString *) message andIsIndet:(BOOL) indet
{
connectingView = [[ConnectingViewController alloc] init];
[self.view.window.contentView addSubview:connectingView.view];
connectingView.view.frame = ((NSView*)self.view.window.contentView).bounds;
[connectingView changeProgressLabel:message];
if (indet)
[connectingView makeProgressBar:NO];
}
Is there a better way to add the subview or to tell the NSTextFields I don't want them to be drawn on top?
Thanks!
So Setting [self setWantsLayer] to my custom NSViews sort of worked however there are a lot of redraw issues (white borders, and backgrounds). A NSPopover may be better in some instances however I was going for "locked down" approach where the interface is unreachable until it finishes (or times out).
What worked for me was to go to the instance of my NSView, select the window in Interface Builder, then go to layers (far right on properties view) and select my view under "Core Animation Layer".

Two Finger Drag with IKImageView and NSScrollView in Mountain Lion

I have a Mac App that's been in the app store for a year or so now. It was first published with target SDK 10.7, Lion. Upon the update to Mountain Lion it no longer works.
The application displays large images in an IKImageView which is embedded in an NSScrollView. The purpose of putting it into a scrollview was to get two finger dragging working, rather than the user having to click to drag. Using ScrollViewWorkaround by Nicholas Riley, I was able to use two finger scrolling to show the clipped content after the user had zoomed in. Just like you see in the Preview app.
Nicholas Riley's Solution:
IKImageView and scroll bars
Now in Mountain Lion this doesn't work. After zooming in, pinch or zoom button, the image is locked in the lower left portion of the image. It won't scroll.
So the question is, what's the appropriate way to display a large image in IKImageView and have two finger dragging of the zoomed image?
Thank you,
Stateful
Well, Nicholas Riley's Solution is an ugly hack in that it addresses the wrong class; the issue isn't with NSClipView (which he subclassed, but which works just fine as is), but with IKImageView.
The issue with IKImageView is actually quite simple (God knows why Apple hasn't fixed this in what? … 7 years ...): Its size does not adjust to the size of the image it displays. Now, when you embed an IKImageView in an NSScrollView, the scroll view obviously can only adjust its scroll bars relative to the size of the embedded IKImageView, not to the image it contains. And since the size of the IKImageView always stays the same, the scroll bars won't work as expected.
The following code subclasses IKImageView and fixes this behavior. Alas, it won't fix the fact that IKImageView is crash-prone in Mountain Lion as soon as you zoom …
///////////////////// HEADER FILE - FixedIKImageView.h
#import <Quartz/Quartz.h>
#interface FixedIKImageView : IKImageView
#end
///////////////////// IMPLEMENTATION FILE - FixedIKImageView.m
#import "FixedIKImageView.h"
#implementation FixedIKImageView
- (void)awakeFromNib
{
[self setTranslatesAutoresizingMaskIntoConstraints:NO]; // compatibility with Auto Layout; without this, there could be Auto Layout error messages when we are resized (delete this line if your app does not use Auto Layout)
}
// FixedIKImageView must *only* be used embedded within an NSScrollView. This means that setFrame: should never be called explicitly from outside the scroll view. Instead, this method is overwritten here to provide the correct behavior within a scroll view. The new implementation ignores the frameRect parameter.
- (void)setFrame:(NSRect)frameRect
{
NSSize imageSize = [self imageSize];
CGFloat zoomFactor = [self zoomFactor];
NSSize clipViewSize = [[self superview] frame].size;
// The content of our scroll view (which is ourselves) should stay at least as large as the scroll clip view, so we make ourselves as large as the clip view in case our (zoomed) image is smaller. However, if our image is larger than the clip view, we make ourselves as large as the image, to make the scrollbars appear and scale appropriately.
CGFloat newWidth = (imageSize.width * zoomFactor < clipViewSize.width)? clipViewSize.width : imageSize.width * zoomFactor;
CGFloat newHeight = (imageSize.height * zoomFactor < clipViewSize.height)? clipViewSize.height : imageSize.height * zoomFactor;
[super setFrame:NSMakeRect(0, 0, newWidth - 2, newHeight - 2)]; // actually, the clip view is 1 pixel larger than the content view on each side, so we must take that into account
}
//// We forward size affecting messages to our superclass, but add [self setFrame:NSZeroRect] to update the scroll bars. We also add [self setAutoresizes:NO]. Since IKImageView, instead of using [self setAutoresizes:NO], seems to set the autoresizes instance variable to NO directly, the scrollers would not be activated again without invoking [self setAutoresizes:NO] ourselves when these methods are invoked.
- (void)setZoomFactor:(CGFloat)zoomFactor
{
[super setZoomFactor:zoomFactor];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomImageToRect:(NSRect)rect
{
[super zoomImageToRect:rect];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomIn:(id)sender
{
[super zoomIn:self];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomOut:(id)sender
{
[super zoomOut:self];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomImageToActualSize:(id)sender
{
[super zoomImageToActualSize:sender];
[self setFrame:NSZeroRect];
[self setAutoresizes:NO];
}
- (void)zoomImageToFit:(id)sender
{
[self setAutoresizes:YES]; // instead of invoking super's zoomImageToFit: method, which has problems of its own, we invoke setAutoresizes:YES, which does the same thing, but also makes sure the image stays zoomed to fit even if the scroll view is resized, which is the most intuitive behavior, anyway. Since there are no scroll bars in autoresize mode, we need not add [self setFrame:NSZeroRect].
}
- (void)setAutoresizes:(BOOL)autoresizes // As long as we autoresize, make sure that no scrollers flicker up occasionally during live update.
{
[self setHasHorizontalScroller:!autoresizes];
[self setHasVerticalScroller:!autoresizes];
[super setAutoresizes:autoresizes];
}
#end

NSEventPhaseEnded not called on NSScrollView with IKImageBrowserView

I have an IKImageBrowserView embedded within an NSScrollView, to help test this problem the IKImageBrowserView only fills half the of width of the NSScrollView.
My NSScrollView subclass implements the following method and logs when a scroll began and ended on the scrollview.
- (void)scrollWheel:(NSEvent *)event {
switch (event.phase) {
case NSEventPhaseBegan:
NSLog(#"NSEventPhaseBegan");
break;
case NSEventPhaseEnded:
NSLog(#"NSEventPhaseEnded");
break;
}
[super scrollWheel:event];
}
The NSEventPhaseBegan event phase successfully calls whether the cursor is over the IKImageBrowserView or not when you start scrolling. This is the expected behaviour.
But NSEventPhaseEnded event phase is not called when the cursor is over the IKImageBrowserView when you stop scrolling. If the cursor is not over the IKImageBrowserView then NSEventPhaseEnded does get called.
Why is the IKImageBrowserView preventing NSEventPhaseEnded being called on my NSScrollView?
Spoke to a helpful engineer at Apple who told me this behaviour is in fact a bug. ImageKit is eating some scrollWheel events unfortunately. As a workaround, subclass the IKImageBrowserView, implement scrollWheel and forward the event both to "super" and the enclosing scrollview.
#implementation MyImageBrowserView
- (void)scrollWheel:(NSEvent *)theEvent {
[super scrollWheel:theEvent];
if ([theEvent phase] == NSEventPhaseEnded)
[[self enclosingScrollView] scrollWheel:theEvent];
}
}
#end

Lion compound animations for full screen

I'm transitioning a window to full screen mode (the new Lion kind of full screen mode). While I do the transition, I'd like to also slide one of the views in my NSWindow to a new position.
So, in my NSWindowDelegate, I've tried returning the window and implementing the custom animation:
- (NSArray *)customWindowsToEnterFullScreenForWindow:(NSWindow *)window
{
return [NSArray arrayWithObject: window];
}
- (void)window:(NSWindow *)_window startCustomAnimationToEnterFullScreenWithDuration:(NSTimeInterval)duration
{
// book is NSView *ivar
[[book animator] setFrame: NSMakeRect(/*computed rect*/)];
}
But this completely kills the default animation of going to full-screen mode and my window suddenly doesn't paint correctly.
Is there some way to compound these while still using the default animation? I'm pretty new to core animation beyond [view animator] level stuff, So I'm sure I'm screwing up something quite simple.
You have to write something like this in order to have the two animations in sync:
- (void)window:(NSWindow *)_window startCustomAnimationToEnterFullScreenWithDuration:(NSTimeInterval)duration
{
// book is NSView *ivar
[[NSAnimationContext currentContext] setDuration:duration];
[[book animator] setFrame: NSMakeRect(/*computed rect*/)];
}

NSColorWell subclass not getting mouseMoved events

I'm trying to implement a color picker in my Cocoa app. (Yes, I know about NSColorPanel. I don't like it very much. The point of rolling my own is that I think I can do better.)
Here's a picture of the current state of my picker.
(source: ryanballantyne.name)
The wells surrounding the color wheel are NSColorWell subclasses. They are instantiated programmatically and added to the color wheel view (an NSView subclass) by calling addSubView on the color wheel class.
I want to make it so that you can drag the color wells around by their grab handles. The start of that journey is making the cursor change to an open hand when the mouse hovers over the handles. Sadly, I can't use a cursor rect for this because most of my views are rotated. I must therefore use mouseMoved events and do the hit detection myself.
Here's the mouse event code I'm trying to make work:
- (void)mouseMoved:(NSEvent*)event
{
NSLog(#"I am over here!\n");
[super mouseMoved:event];
NSPoint eventPoint = [self convertPoint:[event locationInWindow] fromView:nil];
BOOL isInHandle = [grabHandle containsPoint:eventPoint];
if (isInHandle && [NSCursor currentCursor] != [NSCursor openHandCursor]) {
[[NSCursor openHandCursor] push];
}
else if (!isInHandle) [NSCursor pop];
}
- (void)mouseEntered:(NSEvent*)event
{
[[self window] setAcceptsMouseMovedEvents:YES];
}
- (void)mouseExited:(NSEvent*)event
{
[[self window] setAcceptsMouseMovedEvents:NO];
[NSCursor pop];
}
- (BOOL)acceptsFirstResponder
{
return YES;
}
- (BOOL)resignFirstResponder
{
return YES;
}
I find that my mouseMoved method is never called. Ditto for entered and exited. However, when I implement mouseDown, that one does get called, so at least some events are getting to me, just not the ones I want.
Any ideas? Thanks!
mouseEntered: and mouseExited: don't track entering/exiting your view directly; they track entering/exiting any tracking areas you've established in your view. The relevant methods are -addTrackingRect:owner:userData:assumeInside: and -removeTrackingRect:. Just pass [self bounds] for the first parameter if you want your whole view to be tracked. If your app is 10.5+ only, you should probably use NSTrackingArea instead as it directly supports getting mouse-moved events only inside the tracking area.
Keep in mind that 1) tracking rects have the same somewhat odd behavior as cursor rects w/r/t rotated views, and 2) if your bounds change (not merely your frame) you'll probably need to re-establish your tracking rect, so save the tracking rect's tag to remove it later.

Resources