NSOutlineView setNeedsDisplayInRect calls fail - objective-c

I have an NSOutlineView that uses a custom NSCell subclass to draw an NSProgressIndicator. Each NSCell has a refreshing property that is set by the NSOutlineView delegate willDisplayCell:forItem: method, like so:
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
cell.refreshing = item.refreshing;
}
Each item instance contains an NSProgressIndicator that is started and stopped dependent upon whether that particular item is refreshing.
- (NSProgressIndicator *)startProgressIndicator
{
if(!self.progressIndicator)
{
self.progressIndicator = [[[NSProgressIndicator alloc] initWithFrame:NSMakeRect(0, 0, 16.0f, 16.0f)] autorelease];
[self.progressIndicator setControlSize:NSSmallControlSize];
[self.progressIndicator setStyle:NSProgressIndicatorSpinningStyle];
[self.progressIndicator setDisplayedWhenStopped:YES];
[self.progressIndicator setUsesThreadedAnimation:YES];
[self.progressIndicator startAnimation:self];
}
return self.progressIndicator;
}
- (void)stopProgressIndicator
{
if(self.progressIndicator != nil)
{
NSInteger row = [sourceList rowForItem:self];
[self.progressIndicator setDisplayedWhenStopped:NO];
[self.progressIndicator stopAnimation:self];
[[self.progressIndicator superview] setNeedsDisplayInRect:[sourceList rectOfRow:row]];
[self.progressIndicator removeFromSuperviewWithoutNeedingDisplay];
self.progressIndicator = nil;
}
for(ProjectListItem *node in self.children)
{
[node stopProgressIndicator];
}
}
The items NSProgressIndicator instances are stopped and started within my NSCell's drawInteriorWithFrame:inView: class, like so:
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
if(self.refreshing)
{
NSProgressIndicator *progressIndicator = [item progressIndicator];
if (!progressIndicator)
{
progressIndicator = [item startProgressIndicator];
}
// Set the progress indicators frame here ...
if ([progressIndicator superview] != controlView)
[controlView addSubview:progressIndicator];
if (!NSEqualRects([progressIndicator frame], progressIndicatorFrame)) {
[progressIndicator setFrame:progressIndicatorFrame];
}
}
else
{
[item stopProgressIndicator];
}
[super drawInteriorWithFrame:cellFrame inView:controlView];
}
The issue I'm having is that although the NSProgressIndicators are correctly told to stop, the calls to stopProgressIndicator have no effect. Here's the code that fails to trigger the refresh of the NSOutlineView row in question. I've manually checked the NSRect returned by my call to rectOfRow and can confirm that the value is correct.
[self.progressIndicator setDisplayedWhenStopped:NO];
[self.progressIndicator stopAnimation:self];
[[self.progressIndicator superview] setNeedsDisplayInRect:[sourceList rectOfRow:row]];
[self.progressIndicator removeFromSuperviewWithoutNeedingDisplay];
self.progressIndicator = nil;
When the NSOutlineView finishes refreshing all of its items it triggers a reloadData: call. This is the only thing that seems to reliably update all of the cells in question, finally removing the NSProgressIndicators.
What am I doing wrong here?

It's bad practice to mess with view hierarchies while things are getting drawn. The best thing to do would be to use an NSCell version of NSProgressIndicator, but AppKit doesn't have such a thing. The solution described in Daniel Jalkut's answer to a similar question is probably the fastest path to what you want. Here's the basic idea:
Rather than use the refreshing variable to switch paths during drawing, observe your items' refreshing state. When it becomes true, add an NSProgressIndicator as a subview of your NSOutlineView at the item's position given frameOfCellAtColumn:row: (it can be convenient to set up a column for the progress indicators on the far right). When it becomes false, remove the corresponding progress indicator. You will also want to be sure to set the autosizing flags appropriately on these progress indicators so they move around as the outline view gets resized.

Related

EXC_BAD_ACCESS while addingAnnotations in MKMapView

I am calling a web service that returns an array of annotations in a delegate method that I am adding to my map with the addAnnotations method for a MKMapView. Everything goes swimmingly until the delegate method send two arrays in quick succession (usually about 150ms - 500ms) and then I get a EXC_BAD_ACCESS (code=1 address 0x20) on this line [kMap addAnnotations:tileArray]; - This appears to be a memory issue but i am not really sure what to do how it or how to change my code to address it.
Here is the delegate method
-(void)rebelBaseManager:(RebelBaseManager *)manager didUpdateTileHour:(NSArray *)tileArray boundaryBreak:(NSString *)breakType atTileLevel:(int)callTileLevel {
if (timerStarted == NO) {
[self startTimer];
}
//Check for tileLevel in case multiple calls were made at different tile levels
if (tileLevel == callTileLevel) {
[kMap addAnnotations:tileArray];
[HourInMap addObjectsFromArray:tileArray];
}
}
I also added a method to allow me to animate the removal of annotations which is below in case it makes a difference:
- (void)removeAnnotationsWithFade:(NSArray *)annotations animated:(BOOL)shouldAnimate {
if (!shouldAnimate)
[self removeAnnotations:annotations];
else {
for (HourAnnotation *annotation in annotations) {
MKAnnotationView *annotationView = [self viewForAnnotation:annotation];
[UIView animateWithDuration:2
animations:^{
annotationView.alpha =0;
}completion:^(BOOL finished) {
[self removeAnnotation:annotation];
}];
}
}
--------- ADDITION ---------
Adding in my code from a custom annotation in response to #3 in Rob's answer below.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (id)initWithHourAnnotation:(HourAnnotation *)hourAnnotation reuseIdentifier:(NSString *)reuseIdentifier {
CGRect myFrame = self.frame;
myFrame.size.width = hourAnnotation.frameSize;
myFrame.size.height = hourAnnotation.frameSize;
self = [super initWithFrame:myFrame];
//When I use this here I seem to get the frame and color of the old annotation displayed
//self = [super initWithAnnotation:velocityAnnotation reuseIdentifier:reuseIdentifier];
if (self)
{
self.layer.cornerRadius = self.frame.size.width / 2;
self.clipsToBounds = YES;
[self setBackgroundColor:[UIColor clearColor]];
NSArray *alphaValue = [[NSArray alloc]initWithArray:[self alphaForTileLevel]];
self.fillColor = [UIColor colorWithHue:hourAnnotation.color saturation:.06 brightness:.23 alpha:[[alphaValue objectAtIndex:hourAnnotation.tileLevel-1]doubleValue]];
self.strokeColor = [UIColor colorWithHue:hourAnnotation.color saturation:.06 brightness:.23 alpha:.35];
self.enabled = NO;
self.canShowCallout = NO;
self.userInteractionEnabled = NO;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:rect];
[self.fillColor set];
[path fill];
[self.strokeColor set];
[path setLineWidth:2.0f];
[path stroke];
}
A couple of thoughts.
You're not showing where you are calling this removeAnnotationsWithFade. We have to assume that you're removing the appropriate model objects. use instruments to confirm that you're not leaking anywhere.
If the app is crashing when removing with fade, but not crashing without fade, then you might consider refactoring this code. Specifically, your animation code isn't actually removing the old animations until the completion block (i.e. two seconds later). Thus you are holding on to old annotations longer than needed. And if the annotations are coming in fast and furious, you could run into memory problems.
If you, for example, used transitionWithView:mapView instead, just removing the annotations directly, you might be freeing memory associated with the old annotations more quickly. It's a less elegant animation, but might be "good enough" and minimize your peak memory usage:
// this will immediately remove the annotations, but animate the fading of the transition
[UIView transitionWithView:self.mapView duration:2.0 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
[self.mapView removeAnnotations:annotations];
} completion:nil];
// presumably remove the annotations from your array, too
Is your viewForAnnotation dequeuing old annotation views and only instantiating new ones if it couldn't dequeue an old one, or is it always instantiating new ones? Stuff like that might help control peak memory usage, too.

When showing the real time resizing of a pane, it struggles while dragging and top of the pane is hidden after released

I went through several posts on dragging but couldn't find an answer to my problem.
I can use the mouseDown and mouseUp events to track the current positions and redraw the resized pane. What I want is to show the real time movement of the pane. Everytime mouseDragged event is fired, y coordinate of the new location is taken and setFrame is called to redraw. The window seems to flicker and gets stuck finally (title bar goes out of bounds and hidden) as it seems to miss the final events in the run loop.
Is there a way to solve this problem?
The view has been implemented in the following way
NSSplitView (divided into sections left dock, right dock, etc.)
NSView is used to implement a sub view inside the dock
NSTableView is used inside the NSView to hold multiple "panels"
There can be several panels inside this table view (one below another)
I need to resize these panels by dragging the border line. For this I'm using an NSButton in the bottom.(I want to show a thicker separation line there)
Here is the code for mouseDown, mouseUp, mouseDragged callbacks, used to resize the panel
-(void)mouseDown:(NSEvent *)theEvent {
draggingInProgress = YES;
firstDraggingPointFound = NO;
}
-(void)mouseUp:(NSEvent *)theEvent {
if (!draggingInProgress) {
return;
}
NSPoint point = [self convertPoint: [theEvent locationInWindow] fromView: nil];
if (firstDraggingPointFound) {
[_delegate heightChanged:[NSNumber numberWithFloat:(point.y - previousDragPosition)]];
}
draggingInProgress = NO;
[_delegate heightChangingEnded]; //draggingInProgress is set to NO
}
-(void)mouseDragged:(NSEvent *)theEvent {
if (!draggingInProgress) {
return;
}
NSPoint point = [self convertPoint: [theEvent locationInWindow] fromView: nil];
if (firstDraggingPointFound) {
[_delegate heightChanged:[NSNumber numberWithFloat:(point.y - previousDragPosition)]];
} else {
firstDraggingPointFound = YES;
}
previousDragPosition = point.y;
}
//Delegate method
-(void)heightChanged:(NSNumber *)change {
NSRect f = [[self view] frame];
f.size.height += [change floatValue];
if (f.size.height < 100) {
f.size.height = 100;
}
[[self view] setFrame:f];
[self.panelViewModel setPanelHeight:f.size.height];
if (_heightChangeDelegate) {
[_heightChangeDelegate heightChangedForPanel:self];
}
[[self view] setFrame:f];
}
What would be the problem here?
Is there a better way to do this?
First off, I wouldn’t use the word “Panel” to describe what you want, since an “NSPanel” is a kind of “NSWindow”. Let’s call them “panes.”
Second, I wouldn’t use an NSTableView to contain your panes. NSTableView really isn’t designed for that, it’s for tabular data. You can use an NSStackView, which IS designed for it, or just use raw constraints and autolayout, manually setting the top of each pane to equal the bottom of the previous one.

Disabling NSView fade animation for NSView `setHidden:`

I am working on a project that has the concept of draggable controls, everything is working fine except that NSView seems to employ a fade in/out animation when calling setHidden:.
I have been able to work around the problem by changing the line session.animatesToStartingPositionsOnCancelOrFail = YES; to NO and implementing the image snapback myself with a custom animated NSWindow subclass. it looks great, but I know there must be an easier way.
I have tried:
using NSAnimationContext grouping with duration of 0 around the setHidden: calls
setting the view animations dictionary using various keys (alpha, hidden, isHidden) on the control and superview
overriding animationForKey: for both the control and its superview
I am not using CALayers and have even tried explicitly setting wantsLayer: to NO.
Does anybody know how to either disable this animation, or have a simpler solution then my animated NSWindow?
here is my stripped down altered code with the bare minimum to see what I'm talking about.
#implementation NSControl (DragControl)
- (NSDraggingSession*)beginDraggingSessionWithDraggingCell:(NSActionCell <NSDraggingSource> *)cell event:(NSEvent*) theEvent
{
NSImage* image = [self imageForCell:cell];
NSDraggingItem* di = [[NSDraggingItem alloc] initWithPasteboardWriter:image];
NSRect dragFrame = [self frameForCell:cell];
dragFrame.size = image.size;
[di setDraggingFrame:dragFrame contents:image];
NSArray* items = [NSArray arrayWithObject:di];
[self setHidden:YES];
return [self beginDraggingSessionWithItems:items event:theEvent source:cell];
}
- (NSRect)frameForCell:(NSCell*)cell
{
// override in multi-cell cubclasses!
return self.bounds;
}
- (NSImage*)imageForCell:(NSCell*)cell
{
return [self imageForCell:cell highlighted:[cell isHighlighted]];
}
- (NSImage*)imageForCell:(NSCell*)cell highlighted:(BOOL) highlight
{
// override in multicell cubclasses to just get an image of the dragged cell.
// for any single cell control we can just make sure that cell is the controls cell
if (cell == self.cell || cell == nil) { // nil signifies entire control
// basically a bitmap of the control
// NOTE: the cell is irrelevant when dealing with a single cell control
BOOL isHighlighted = [cell isHighlighted];
[cell setHighlighted:highlight];
NSRect cellFrame = [self frameForCell:cell];
// We COULD just draw the cell, to an NSImage, but button cells draw their content
// in a special way that would complicate that implementation (ex text alignment).
// subclasses that have multiple cells may wish to override this to only draw the cell
NSBitmapImageRep* rep = [self bitmapImageRepForCachingDisplayInRect:cellFrame];
NSImage* image = [[NSImage alloc] initWithSize:rep.size];
[self cacheDisplayInRect:cellFrame toBitmapImageRep:rep];
[image addRepresentation:rep];
// reset the original cell state
[cell setHighlighted:isHighlighted];
return image;
}
// cell doesnt belong to this control!
return nil;
}
#pragma mark NSDraggingDestination
- (void)draggingEnded:(id < NSDraggingInfo >)sender
{
[self setHidden:NO];
}
#end
#implementation NSActionCell (DragCell)
- (void)setControlView:(NSView *)view
{
// this is a bit of a hack, but the easiest way to make the control dragging work.
// force the control to accept image drags.
// the control will forward us the drag destination events via our DragControl category
[view registerForDraggedTypes:[NSImage imagePasteboardTypes]];
[super setControlView:view];
}
- (BOOL)trackMouse:(NSEvent *)theEvent inRect:(NSRect)cellFrame ofView:(NSView *)controlView untilMouseUp:(BOOL)untilMouseUp
{
BOOL result = NO;
NSPoint currentPoint = theEvent.locationInWindow;
BOOL done = NO;
BOOL trackContinously = [self startTrackingAt:currentPoint inView:controlView];
BOOL mouseIsUp = NO;
NSEvent *event = nil;
while (!done)
{
NSPoint lastPoint = currentPoint;
event = [NSApp nextEventMatchingMask:(NSLeftMouseUpMask|NSLeftMouseDraggedMask)
untilDate:[NSDate distantFuture]
inMode:NSEventTrackingRunLoopMode
dequeue:YES];
if (event)
{
currentPoint = event.locationInWindow;
// Send continueTracking.../stopTracking...
if (trackContinously)
{
if (![self continueTracking:lastPoint
at:currentPoint
inView:controlView])
{
done = YES;
[self stopTracking:lastPoint
at:currentPoint
inView:controlView
mouseIsUp:mouseIsUp];
}
if (self.isContinuous)
{
[NSApp sendAction:self.action
to:self.target
from:controlView];
}
}
mouseIsUp = (event.type == NSLeftMouseUp);
done = done || mouseIsUp;
if (untilMouseUp)
{
result = mouseIsUp;
} else {
// Check if the mouse left our cell rect
result = NSPointInRect([controlView
convertPoint:currentPoint
fromView:nil], cellFrame);
if (!result)
done = YES;
}
if (done && result && ![self isContinuous])
[NSApp sendAction:self.action
to:self.target
from:controlView];
else {
done = YES;
result = YES;
// this bit-o-magic executes on either a drag event or immidiately following timer expiration
// this initiates the control drag event using NSDragging protocols
NSControl* cv = (NSControl*)self.controlView;
NSDraggingSession* session = [cv beginDraggingSessionWithDraggingCell:self
event:theEvent];
// Note that you will get an ugly flash effect when the image returns if this is set to yes
// you can work around it by setting NO and faking the release by animating an NSWindowSubclass with the image as the content
// create the window in the drag ended method for NSDragOperationNone
// there is [probably a better and easier way around this behavior by playing with view animation properties.
session.animatesToStartingPositionsOnCancelOrFail = YES;
}
}
}
return result;
}
#pragma mark - NSDraggingSource Methods
- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
{
switch(context) {
case NSDraggingContextOutsideApplication:
return NSDragOperationNone;
break;
case NSDraggingContextWithinApplication:
default:
return NSDragOperationPrivate;
break;
}
}
- (void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation
{
// now tell the control view the drag ended so it can do any cleanup it needs
// this is somewhat hackish
[self.controlView draggingEnded:nil];
}
#end
There must be a layer enabled somewhere in your view hierarchy, otherwise there wouldn't be a fade animation. Here is my way of disabling such animations:
#interface NoAnimationImageView : NSImageView
#end
#implementation NoAnimationImageView
+ (id)defaultAnimationForKey: (NSString *)key
{
return nil;
}
#end
The solution you already tried by setting the view animations dictionary should work. But not for the keys you mention but for the following. Use it somewhere before the animation is triggered the first time. If you have to do it on the window or view or both, I don't know.
NSMutableDictionary *animations = [NSMutableDictionary dictionaryWithDictionary:[[theViewOrTheWindow animator] animations];
[animations setObject:[NSNull null] forKey: NSAnimationTriggerOrderIn];
[animations setObject:[NSNull null] forKey: NSAnimationTriggerOrderOut];
[[theViewOrTheWindow animator] setAnimations:animations];
Or also just remove the keys if they are there (might not be the case as they are implicit / default):
NSMutableDictionary *animations = [NSMutableDictionary dictionaryWithDictionary:[[theViewOrTheWindow animator] animations];
[animations removeObjectForKey:NSAnimationTriggerOrderIn];
[animations removeObjectForKey:NSAnimationTriggerOrderOut];
[[theViewOrTheWindow animator] setAnimations:animations];
Ok. I figured out that the animation I'm seeing is not the control, the superview, nor the control's window. It appears that animatesToStartingPositionsOnCancelOrFail causes NSDraggingSession to create a window (observed with QuartzDebug) and put the drag image in it and it is this window that animates back to the origin and fades out before the setHidden: call is executed (i.e. before the drag operation is concluded).
Unfortunately, the window that it creates is not an NSWindow so creating a category on NSWindow doesn't disable the fade animation.
Secondly, there is no public way that I know of to get a handle on the window, so I can't attempt directly manipulating the window instance.
It looks like maybe my workaround is the best way to do this, after all its not far from what AppKit does for you anyway.
If anybody knows how to get a handle on this window, or what class it is I would be interested to know.

NSOutlineView Changing disclosure Image

I my outline view, i am adding Custom cell, To drawing custom cell, i am referring example code , present in the Cocoa documentation
http://www.martinkahr.com/2007/05/04/nscell-image-and-text-sample/
I want to change the disclosure image of the cell with my custom image, i have tried following things
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
if([item isKindOfClass:[NSValue class]])
{
MyData *pDt = (MyData *)[item pointerValue];
if(pDt->isGroupElement())
{
[cell setImage:pGroupImage];
}
}
}
but that too not working, Is there any other way to change the disclosure image,
also how can i find out in willDisplayCell whether Item is expand or collapse, so i can set the image accordingly,
Is this only the place to change the disclosure image ?
After researching this issue myself, and trying some of the answers here, I have found that the other approaches mentioned will work, but will require that you perform a lot more manual intervention in order to avoid screen artifacts and other strange behavior.
The simplest solution I found is the following, which should work in most cases.
This solution has the added benefit of the system automatically handling a great many other cases, such as column movement, etc, without your involvement.
- (void)outlineView:(NSOutlineView *)outlineView willDisplayOutlineCell:(id)cell
forTableColumn:(NSTableColumn *)tableColumn
item:(id)item
{
[cell setImage:[NSImage imageNamed: #"Navigation right 16x16 vWhite_tx"]];
[cell setAlternateImage:[NSImage imageNamed: #"Navigation down 16x16 vWhite_tx"]];
}
In your case, you would wrap this up with your class detection logic, and set the cell images appropriately for your cases.
A nice way to change the disclosure image is to use a view based outline view:
In your ViewController with NSOutlineViewDelegate:
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
CustomNSTableCellView *cell = [outlineView makeViewWithIdentifier:tableColumn.identifier owner:self];
cell.item = item;
return cell;
}
You have to subclass your NSOutlineView and overide the method:
- (id)makeViewWithIdentifier:(NSString *)identifier owner:(id)owner
{
id view = [super makeViewWithIdentifier:identifier owner:owner];
if ([identifier isEqualToString:NSOutlineViewDisclosureButtonKey])
{
// Do your customization
// return disclosure button view
[view setImage:[NSImage imageNamed:#"Disclosure_Categories_Plus"]];
[view setAlternateImage:[NSImage imageNamed:#"Disclosure_Categories_Minus"]];
[view setBordered:NO];
[view setTitle:#""];
return view;
}
return view;
}
//Frame of the disclosure view
- (NSRect)frameOfOutlineCellAtRow:(NSInteger)row
{
NSRect frame = NSMakeRect(4, (row * 22), 19, 19);
return frame;
}
For who looks for Swift2 Solution. Subclass NSRow of your outlineview and override didAddSubview method as below.
override func didAddSubview(subview: NSView) {
super.didAddSubview(subview)
if let sv = subview as? NSButton {
sv.image = NSImage(named:"IconNameForCollapsedState")
sv.alternateImage = NSImage(named:"IconNameForExpandedState")
}
}
You've got the basic idea but what you will need to do is draw the image yourself. Here's the code I use:
- (void)outlineView:(NSOutlineView *)outlineView willDisplayOutlineCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item {
NSString *theImageName;
NSInteger theCellValue = [cell integerValue];
if (theCellValue==1) {
theImageName = #"PMOutlineCellOn";
} else if (theCellValue==0) {
theImageName = #"PMOutlineCellOff";
} else {
theImageName = #"PMOutlineCellMixed";
}
NSImage *theImage = [NSImage imageNamed: theImageName];
NSRect theFrame = [outlineView frameOfOutlineCellAtRow:[outlineView rowForItem: item]];
theFrame.origin.y = theFrame.origin.y +17;
// adjust theFrame here to position your image
[theImage compositeToPoint: theFrame.origin operation:NSCompositeSourceOver];
[cell setImagePosition: NSNoImage];
}
You will need 3 different images as you can see, one for the ON state, one for the OFF state and also one for the MIXED state which should be halfway between the two. The mixed state makes sure you still get the opening and closing animation.
This is what i have tried and working so far,
/* because we are showing our own disclose and expand button */
- (NSRect)frameOfOutlineCellAtRow:(NSInteger)row {
return NSZeroRect;
}
- (NSRect)frameOfCellAtColumn:(NSInteger)column row:(NSInteger)row {
NSRect superFrame = [super frameOfCellAtColumn:column row:row];
if ((column == 0) && ([self isGroupItem:[self itemAtRow:row]])) {
return NSMakeRect(0, superFrame.origin.y, [self bounds].size.width, superFrame.size.height);
}
return superFrame;
}
I have subclassed NSOutlineView class and override these methods,
[self isGroupItem] is to check whether its group or not.
but got one problem, now looks like mousehandling i need to do :( , on double clicking group row is not toggling

NSOutlineView Drag-n-Drop

In my application, NSOutlineView used as below,
1 -- Using CustomOutlineView because i want to control NSOutlineView Background,
2 -- Using CustomeCell, becuase i need to have customize Cell
Header File : MyListView.h
/*
MyUICustomView is the Subclass from NSView and this is i need to have
for some other my application purpose
*/
#interface MyListView : MyUICustomView<NSOutlineViewDataSource>
{
// MyCustomOutlineview because, i need to override DrawRect Method to have
// customized background
MyCustomOutlineView *pMyOutlineView;
}
#property(nonatomic,retain)MyCustomOutlineView *pMyOutlineView;
and also i should be able to drag-n-drop within Outline view,
for having drag-n-drop i have done following,
-(void)InitOutlineView{
// Creating outline view
NSRect scrollFrame = [self bounds];
NSScrollView* scrollView = [[[NSScrollView alloc] initWithFrame:scrollFrame] autorelease];
[scrollView setBorderType:NSNoBorder];
[scrollView setHasVerticalScroller:YES];
[scrollView setHasHorizontalScroller:NO];
[scrollView setAutohidesScrollers:YES];
[scrollView setDrawsBackground: NO];
NSRect clipViewBounds = [[scrollView contentView] bounds];
pMyOutlineView = [[[MyCustomOutlineView alloc] initWithFrame:clipViewBounds] autorelease];
NSTableColumn* firstColumn = [[[NSTableColumn alloc] initWithIdentifier:#"firstColumn"] autorelease];
#ifdef ENABLE_CUSTOM_CELL
// Becuase cell should have Image, Header Info and brief detail in small font,
// so i need to have custom cell
ImageTextCell *pCell = [[ImageTextCell alloc]init];
[firstColumn setDataCell:pCell];
// SO i can fill the data
[pCell setDataDelegate:self];
# endif
[pMyOutlineView setDataSource:self];
/* This is to tell MyCustomOutlineView to handle the context menu */
[pMyOutlineView setDataDelegate:self];
[scrollView setDocumentView:pCTOutlineView];
[pMyOutlineView addTableColumn:firstColumn];
[pMyOutlineView registerForDraggedTypes:
[NSArray arrayWithObjects:OutlinePrivateTableViewDataType,nil]];
[pMyOutlineView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES];**
}
and to Support drag-n-drop Implemented following method
- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard{
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:items];
[pboard declareTypes:[NSArray arrayWithObject:OutlinePrivateTableViewDataType] owner:self];
[pboard setData:data forType:OutlinePrivateTableViewDataType];
return YES;
}
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index{
// Add code here to validate the drop
NSLog(#"validate Drop");
return NSDragOperationEvery;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index{
NSLog(#"validate Drop");
}
but still when i try to drag the row of NSOutlineView nothing is happening, even i tried to debug via NSLog but i couldn't see any log from above function,
Am i missing any important method ?
to support Drag-n-drop
Based on your code, it looks like you're not returning anything for the ...writeItems... method. This method is supposed to return a BOOL (whether the items were successfully written or you're denying the drag). Returning nothing should be giving you a compiler warning ...
HI,
FInally got able to rid of this,
The problem is
[firstColumn setWidth:25];
So drag was working only upto 25 pixel form the left, and i commented this, then its working time, but strange to see, my outline view has only one column , for drawing, its not checking the restriction but for drag-n-drop its checking,
Thanks Joshua