NSOutlineView Drag-n-Drop - objective-c

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

Related

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.

UICollectionView Views Layer Priority

I'm seeing a very odd behavior with UICollectionViews.
Here is the scenario.
I have a UIViewController that has been pushed on to a UINavigationController stack.
The UIViewController view has nav bar and UICollectionView in grid layout. 3 cells wide by unlimited tall.
Just below extent of screen, I also have a UIToolbar hidden. The UIToolbar is on top of UICollectionView in layer hierarchy.
I then allow the user to put view in to "edit mode" and I animate UIToolbar on to the screen and covers bottom portion of UICollectionView. If user leaves "edit mode" I move UIToolbar back off screen.
While in "edit mode" I allow the user to multi select cells with check boxes that appear and uitoolbar has delete button.
Delete does the following:
- (void)deletePhotos:(id)sender {
if ([[self.selectedCells allKeys] count] > 0) {
[[DataManager instance] deletePhotosAtIndexes:[self.selectedCells allKeys]];
[self.selectedCells removeAllObjects];
[self.collectionview reloadData];
[self.collectionview performBatchUpdates:nil completion:nil];
}
}
// Data Manager method in singleton class:
- (void)deletePhotosAtIndexes:(NSArray *)indexes {
NSMutableIndexSet *indexesToDelete = [NSMutableIndexSet indexSet];
for (int i = 0; i < [indexes count]; i++) {
[indexesToDelete addIndex:[[indexes objectAtIndex:i] integerValue]];
NSString *filePath = [self.photosPath stringByAppendingPathComponent:[self.currentPhotos objectAtIndex:[[indexes objectAtIndex:i] integerValue]]];
NSString *thumbnailPath = [self.thumbPath stringByAppendingPathComponent:[self.currentPhotos objectAtIndex:[[indexes objectAtIndex:i] integerValue]]];
if ([[NSFileManager defaultManager] fileExistsAtPath: filePath]) {
[[NSFileManager defaultManager] removeItemAtPath: filePath error:NULL];
[[NSFileManager defaultManager] removeItemAtPath: thumbnailPath error:NULL];
}
}
[self.currentPhotos removeObjectsAtIndexes:indexesToDelete];
}
The data manager contains photo objects and are used in cell creation like so.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
ImageCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"imageCell" forIndexPath:indexPath];
NSString *pngfile = [[[DataManager instance] thumbPath] stringByAppendingPathComponent:[[[DataManager instance] currentPhotos] objectAtIndex:indexPath.row]];
if ([[NSFileManager defaultManager] fileExistsAtPath:pngfile]) {
NSData *imageData = [NSData dataWithContentsOfFile:pngfile];
UIImage *img = [UIImage imageWithData:imageData];
[cell.imageView setImage:img];
}
if ([self.selectedCells objectForKey:[NSString stringWithFormat:#"%d", indexPath.row]] != nil) {
cell.checkbox.hidden = NO;
} else {
cell.checkbox.hidden = YES;
}
return cell;
}
So here is where I'm finding issues:
When deleting enough cells so that number of visible rows changes, UIToolbar is disappearing. In case of a full row of 3, if I only delete 1 or 2 items, the UIToolbar doesn't disappear. I am not doing any animation on the UIToolbar in delete method and only when hitting a Done button that ends edit mode. I've confirmed that this method isn't being called.
I've also confirmed that the UIToolbar isn't actually moving. If I add "self.collectionview removeFromSuperView" on hitting delete in cases where UIToolbar would normally disappear, the UIToolbar is exactly where expected on the screen. This gives me the impression the UICollectionView is somehow changing layer hierarchy in draw of parent view.
I've attempted trying to bringSubviewToFront for UIToolbar and sendSubviewToBack for collectionview and has no affect.
Re-iniating open toolbar causes uitoolbar to animate back in. Oddly, however, it seems to animate from below screen! This makes no sense unless the UICollectionView is somehow pushing the UIToolbar off the screen due after the point where I would be calling the removeFromSuperview call so I can't re-create.
One "solution" I have is to force the UIToolbar to come back in to position but without animation after a 0.01 second delay
[self performSelector:#selector(showToolbarNoAnimation) withObject:nil afterDelay:0.01];
This works.
Here is the question:
Any idea why UICollectionView causing this behavior to push UIToolbar offscreen after a full row is deleted? The hack works but doesn't explain the issue.
Thanks,
James
When you use auto layout, and your views are loaded from a storyboard (or xib), you can't set the frames of your views. Doing so may seem to work initially, but at some point auto layout will reset the view's frame based on the constraints, and you won't understand what happened, and then you'll post a question to stack overflow.
If you need to change the layout of a view, you need to update the view's constraints instead.
There is a constraint specifying the distance between the bottom edge of your toolbar and the bottom edge of its superview. Presumably that distance is -44 (where 44 is the height of the toolbar).
You need to connect that constraint to an outlet in your view controller. The outlet will have type NSLayoutConstraint *. Call it toolbarBottomEdgeConstraint.
When you want to animate the toolbar onto the screen, set constraint's constant to zero and call layoutIfNeeded in an animation block:
- (void)showToolbarAnimated {
[UIView animateWithDuration:0.25 animations:^{
self.toolbarBottomEdgeConstraint.constant = 0;
[self.toolbar layoutIfNeeded];
}];
}
To hide the toolbar, set the constraint's constant back to its original value:
- (void)hideToolbarAnimated {
[UIView animateWithDuration:0.25 animations:^{
self.toolbarBottomEdgeConstraint.constant = -toolbar.bounds.size.height;
[self.toolbar layoutIfNeeded];
}];
}

NSOutlineView setNeedsDisplayInRect calls fail

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.

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

Tab-like-thing with tab-switch animation in a section of UITableView

I have an UITableView with sections in my iPhone application where it looks as something similar to viewing a contact details in standard contacts app. In one of the sections I have a UISegmentedControl set as a header of the section and the items in that section depend on what segment is selected in the segmented control. What I wanted to do is to have animations shifting the rows of one tab out of the view to one side and shifting the rows of another tab in from the other side. I have managed to achieve this by inserting/deleting the respective rows with default UITableView's animations for that but it looks a bit weird when the number of rows in one tab is different from the other one. Also the code for that is quite nasty.
Here's the code I have (simplified a bit):
[self.tableView beginUpdates];
UITableViewRowAnimation delAnim = UITableViewRowAnimationRight;
UITableViewRowAnimation insAnim = UITableViewRowAnimationLeft;
NSUInteger numdel = [self.list count];
NSUInteger numins = [newlist count];
NSMutableArray* indices = [NSMutableArray arrayWithCapacity: numdel];
for (NSUInteger i = 0; i < numins; i++)
{
[indices addObject: [NSIndexPath indexPathForRow: i inSection: SECT]];
}
[self.tableView insertRowsAtIndexPaths: indices withRowAnimation: insAnim];
[indices removeAllObjects];
for (NSUInteger i = 0; i < numdel; i++)
{
[indices addObject: [NSIndexPath indexPathForRow: i inSection: SECT]];
}
[self.tableView deleteRowsAtIndexPaths: indices withRowAnimation: delAnim];
self.list = newlist;
[self.tableView endUpdates];
I'm fairly new to Objective C and Cocoa, so any advices are very welcome.
You could try using a separate table view which you animate in directly. The snippet here is just typed off-the-cuff, so might need some work, but it should give you some pointers at least:
- (void) switchToNewTableFromRight
{
UITableView * newTableView = [[UITableView alloc] initWithFrame: self.tableView.frame style: self.tableView.style];
// put it off to the right of the existing table
CGRect frame = newTableView.frame;
frame.origin.x += frame.size.width;
newTableView.frame = frame;
// set data for new table
// you should ensure you're setup to supply data for the new table here, btw
newTableView.delegate = self;
newTableView.dataSource = self;
[newTableView reloadData];
// add to parent of current table view at this (offscreen) location
[self.tableView.superview addSubview: newTableView];
// now we animate
[UIView beginAnimations: #"TableFromRight" context: newTableView];
// set the function it should call when the animation completes
[UIView setAnimationDelegate: self];
[UIView setAnimationDidStopSelector: #selector(animation:finished:context:)];
// set new table's frame to current table's frame
newTableView.frame = self.tableView.frame;
// set current table's frame to be offscreen to the left
frame = self.tableView.frame;
frame.origin.x -= frame.size.width;
self.tableView.frame = frame;
// commit the animations to start them going
[UIView commitAnimations];
}
- (void) animation: (NSString *) animationID finished: (BOOL) finished context: (void *) context
{
// could be a good idea to check that finished == YES here
UITableView * newTableView = (UITableView *) context;
self.tableView = newTableView;
// newTableView has been inited but not autoreleased, etc.
// now the controller (self) owns it, so release that first reference
[newTableView release];
}
The idea here is that you setup the new table (making it the same size as the existing one), place it offscreen to the right of the existing table, and then animate the movement of both tables left by their width. This way the existing table will shift offscreen while the new one will shift onscreen. When the animation completes it will call the supplied method, giving you the opportunity to make the new table view the official table view.
Another option would be to use a flip transition, which might go something like this:
// setup new table
UITableView * newTableView = [[UITableView alloc] initWithFrame: self.tableView.frame style: self.tableView.style];
newTableView.delegate = self;
newTableView.dataSource = self;
[newTableView reloadData];
[UIView beginAnimations: nil context: NULL];
[UIView setAnimationDuration: 1.0];
[UIView setAnimationTransition: UIViewAnimationTransitionFlipFromRight forView: self.tableView.superview cache: YES];
// generally here you'd remove the old view and add the new view
// I'm *assuming* that UITableViewController's -setTableView: will do the same thing
self.tableView = newTableView;
[UIView commitAnimations];
Hopefully one of those will have the desired effect.