add RATreeView as source

This commit is contained in:
王劲鹏 2021-07-16 15:03:23 +08:00 committed by osborn
parent 91e72eddb2
commit 502d731489
32 changed files with 3636 additions and 2 deletions

View File

@ -24,6 +24,4 @@ Doric iOS Devkit for debugging & hotload.
s.dependency 'DoricCore'
s.dependency 'SocketRocket'
s.dependency 'RATreeView'
end

View File

@ -0,0 +1,31 @@
//
// RABatchChangesEntity.h
// RATreeView
//
// Created by Rafal Augustyniak on 17/11/15.
// Copyright © 2015 Rafal Augustyniak. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, RABatchChangeType) {
RABatchChangeTypeItemRowInsertion = 0,
RABatchChangeTypeItemRowExpansion,
RABatchChangeTypeItemRowDeletion,
RABatchChangeTypeItemRowCollapse,
RABatchChangeTypeItemMove
};
@interface RABatchChangeEntity : NSObject
@property (nonatomic) RABatchChangeType type;
@property (nonatomic) NSInteger ranking;
@property (nonatomic, copy) void(^updatesBlock)();
+ (instancetype)batchChangeEntityWithBlock:(void(^)())updates type:(RABatchChangeType)type ranking:(NSInteger)ranking;
@end

View File

@ -0,0 +1,62 @@
//
// RABatchChangesEntity.m
// RATreeView
//
// Created by Rafal Augustyniak on 17/11/15.
// Copyright © 2015 Rafal Augustyniak. All rights reserved.
//
#import "RABatchChangeEntity.h"
@implementation RABatchChangeEntity
+ (instancetype)batchChangeEntityWithBlock:(void (^)())updates type:(RABatchChangeType)type ranking:(NSInteger)ranking
{
NSParameterAssert(updates);
RABatchChangeEntity *entity = [RABatchChangeEntity new];
entity.type = type;
entity.ranking = ranking;
entity.updatesBlock = updates;
return entity;
}
- (NSComparisonResult)compare:(RABatchChangeEntity *)otherEntity
{
if ([self destructiveOperation]) {
if (![otherEntity destructiveOperation]) {
return NSOrderedAscending;
} else {
return [@(otherEntity.ranking) compare:@(self.ranking)];
}
} else if (self.type == RABatchChangeTypeItemMove && otherEntity.type != RABatchChangeTypeItemMove) {
return [otherEntity destructiveOperation] ? NSOrderedAscending : NSOrderedDescending;
} else if ([self constructiveOperation]) {
if (![otherEntity constructiveOperation]) {
return NSOrderedDescending;
} else {
return [@(self.ranking) compare:@(otherEntity.ranking)];
}
} else {
return NSOrderedSame;
}
}
- (BOOL)constructiveOperation
{
return self.type == RABatchChangeTypeItemRowExpansion
|| self.type == RABatchChangeTypeItemRowInsertion;
}
- (BOOL)destructiveOperation
{
return self.type == RABatchChangeTypeItemRowCollapse
|| self.type == RABatchChangeTypeItemRowDeletion;
}
@end

View File

@ -0,0 +1,37 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import <Foundation/Foundation.h>
@interface RABatchChanges : NSObject
- (void)beginUpdates;
- (void)endUpdates;
- (void)expandItemWithBlock:(void(^)())update atIndex:(NSInteger)index;
- (void)insertItemWithBlock:(void(^)())update atIndex:(NSInteger)index;
- (void)collapseItemWithBlock:(void(^)())update lastIndex:(NSInteger)index;
- (void)deleteItemWithBlock:(void(^)())update lastIndex:(NSInteger)index;
- (void)moveItemWithDeletionBlock:(void (^)())deletionUpdate fromLastIndex:(NSInteger)lastIndex additionBlock:(void (^)())additionUpdate toIndex:(NSInteger)index;
@end

View File

@ -0,0 +1,124 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "RABatchChanges.h"
#import "RABatchChangeEntity.h"
@interface RABatchChanges ()
@property (nonatomic, strong) NSMutableArray *operationsStorage;
@property (nonatomic) NSInteger batchChangesCounter;
@end
@implementation RABatchChanges
- (id)init
{
self = [super init];
if (self) {
_batchChangesCounter = 0;
}
return self;
}
- (void)beginUpdates
{
if (self.batchChangesCounter++ == 0) {
self.operationsStorage = [NSMutableArray array];
}
}
- (void)endUpdates
{
self.batchChangesCounter--;
if (self.batchChangesCounter == 0) {
[self.operationsStorage sortUsingSelector:@selector(compare:)];
for (RABatchChangeEntity *entity in self.operationsStorage) {
entity.updatesBlock();
}
self.operationsStorage = nil;
}
}
- (void)insertItemWithBlock:(void (^)())update atIndex:(NSInteger)index
{
RABatchChangeEntity *entity = [RABatchChangeEntity batchChangeEntityWithBlock:update
type:RABatchChangeTypeItemRowInsertion
ranking:index];
[self addBatchChangeEntity:entity];
}
- (void)expandItemWithBlock:(void (^)())update atIndex:(NSInteger)index
{
RABatchChangeEntity *entity= [RABatchChangeEntity batchChangeEntityWithBlock:update
type:RABatchChangeTypeItemRowExpansion
ranking:index];
[self addBatchChangeEntity:entity];
}
- (void)deleteItemWithBlock:(void (^)())update lastIndex:(NSInteger)lastIndex
{
RABatchChangeEntity *entity = [RABatchChangeEntity batchChangeEntityWithBlock:update
type:RABatchChangeTypeItemRowDeletion
ranking:lastIndex];
[self addBatchChangeEntity:entity];
}
- (void)collapseItemWithBlock:(void (^)())update lastIndex:(NSInteger)lastIndex
{
RABatchChangeEntity *entity = [RABatchChangeEntity batchChangeEntityWithBlock:update
type:RABatchChangeTypeItemRowCollapse
ranking:lastIndex];
[self addBatchChangeEntity:entity];
}
- (void)moveItemWithDeletionBlock:(void (^)())deletionUpdate fromLastIndex:(NSInteger)lastIndex additionBlock:(void (^)())additionUpdate toIndex:(NSInteger)index
{
RABatchChangeEntity *firstEntity = [RABatchChangeEntity batchChangeEntityWithBlock:deletionUpdate
type:RABatchChangeTypeItemRowDeletion
ranking:lastIndex];
RABatchChangeEntity *secondEntity = [RABatchChangeEntity batchChangeEntityWithBlock:additionUpdate
type:RABatchChangeTypeItemRowInsertion
ranking:index];
[self addBatchChangeEntity:firstEntity];
[self addBatchChangeEntity:secondEntity];
}
#pragma mark -
- (void)addBatchChangeEntity:(RABatchChangeEntity *)entity
{
if (self.batchChangesCounter > 0) {
[self.operationsStorage addObject:entity];
} else {
entity.updatesBlock();
}
}
@end

View File

@ -0,0 +1,18 @@
//
// RATableView.h
// Pods
//
// Created by Rafal Augustyniak on 15/11/15.
//
//
#import <UIKit/UIKit.h>
@interface RATableView : UITableView
@property (nonatomic, nullable, weak) id<UITableViewDelegate> tableViewDelegate;
@property (nonatomic, nullable, weak) id<UIScrollViewDelegate> scrollViewDelegate;
@end

View File

@ -0,0 +1,183 @@
//
// RATableView.m
// Pods
//
// Created by Rafal Augustyniak on 15/11/15.
//
//
#import "RATableView.h"
#import <objc/runtime.h>
@interface RATableView () <UITableViewDelegate>
@end
@implementation RATableView
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style
{
self = [super initWithFrame:frame style:style];
if (self) {
[self commonInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self commonInit];
}
return self;
}
- (void)commonInit
{
[super setDelegate:self];
}
- (void)setTableViewDelegate:(id<UITableViewDelegate>)tableViewDelegate
{
if (_tableViewDelegate == tableViewDelegate) {
return;
}
[super setDelegate:nil];
_tableViewDelegate = tableViewDelegate;
[super setDelegate:self];
}
- (void)setDelegate:(id<UITableViewDelegate>)delegate
{
if (self.scrollViewDelegate == delegate) {
return;
}
[super setDelegate:nil];
self.scrollViewDelegate = delegate;
[super setDelegate:self];
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
return [super respondsToSelector:aSelector]
|| (SelectorBelongsToProtocol(@protocol(UIScrollViewDelegate), aSelector) && [self.scrollViewDelegate respondsToSelector:aSelector])
|| (SelectorBelongsToProtocol(@protocol(UITableViewDelegate), aSelector) && [self.tableViewDelegate respondsToSelector:aSelector]);
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (SelectorBelongsToProtocol(@protocol(UIScrollViewDelegate), aSelector)) {
return self.scrollViewDelegate;
} else if (SelectorBelongsToProtocol(@protocol(UITableViewDelegate), aSelector)) {
return self.tableViewDelegate;
} else {
return nil;
}
}
#pragma mark - ScrollView delegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if ([self.scrollViewDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) {
[self.scrollViewDelegate scrollViewDidScroll:scrollView];
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
if ([self.scrollViewDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]) {
[self.scrollViewDelegate scrollViewWillBeginDragging:scrollView];
}
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
if ([self.scrollViewDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) {
[self.scrollViewDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
}
}
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView
{
if ([self.scrollViewDelegate respondsToSelector:@selector(scrollViewShouldScrollToTop:)]) {
return [self.scrollViewDelegate scrollViewShouldScrollToTop:scrollView];
}
return YES;
}
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView
{
if ([self.scrollViewDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) {
[self.scrollViewDelegate scrollViewDidScroll:scrollView];
}
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
if ([self.scrollViewDelegate respondsToSelector:@selector(scrollViewWillBeginDecelerating:)]) {
[self.scrollViewDelegate scrollViewWillBeginDecelerating:scrollView];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
if ([self.scrollViewDelegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]) {
[self.scrollViewDelegate scrollViewDidEndDecelerating:scrollView];
}
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
if ([self.scrollViewDelegate respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
return [self.scrollViewDelegate viewForZoomingInScrollView:scrollView];
}
return nil;
}
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
{
if ([self.scrollViewDelegate respondsToSelector:@selector(scrollViewWillBeginZooming:withView:)]) {
[self.scrollViewDelegate scrollViewWillBeginZooming:scrollView withView:view];
}
}
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale
{
if ([self.scrollViewDelegate respondsToSelector:@selector(scrollViewDidEndZooming:withView:atScale:)]) {
[self.scrollViewDelegate scrollViewDidEndZooming:scrollView withView:view atScale:scale];
}
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
if ([self.scrollViewDelegate respondsToSelector:@selector(scrollViewDidZoom:)]) {
[self.scrollViewDelegate scrollViewDidZoom:scrollView];
}
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
if ([self.scrollViewDelegate respondsToSelector:@selector(scrollViewDidEndScrollingAnimation:)]) {
[self.scrollViewDelegate scrollViewDidEndScrollingAnimation:scrollView];
}
}
#pragma mark -
static BOOL SelectorBelongsToProtocol(Protocol *protocol, SEL selector)
{
struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES);
return NULL != methodDescription.name;
}
@end

View File

@ -0,0 +1,34 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import <Foundation/Foundation.h>
@class RATreeNodeItem;
@interface RATreeNode : NSObject
@property (nonatomic, readonly) BOOL expanded;
@property (strong, nonatomic, readonly) id item;
- (id)initWithLazyItem:(RATreeNodeItem *)item expandedBlock:(BOOL (^)(id))expandedBlock;
@end

View File

@ -0,0 +1,75 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "RATreeNode.h"
#import "RATreeNodeItem.h"
@interface RATreeNode () {
BOOL _expanded;
}
@property (nonatomic) BOOL expanded;
@property (nonatomic, strong) RATreeNodeItem *lazyItem;
@property (nonatomic, copy) BOOL (^expandedBlock)(id);
@end
@implementation RATreeNode
- (id)initWithLazyItem:(RATreeNodeItem *)item expandedBlock:(BOOL (^)(id))expandedBlock;
{
self = [super init];
if (self) {
_lazyItem = item;
_expandedBlock = expandedBlock;
}
return self;
}
#pragma mark -
- (RATreeNodeItem *)item
{
return self.lazyItem.item;
}
- (BOOL)expanded
{
if (self.expandedBlock) {
_expanded = self.expandedBlock(self.item);
self.expandedBlock = nil;
}
return _expanded;
}
- (void)setExpanded:(BOOL)expanded
{
self.expandedBlock = nil;
_expanded = expanded;
}
@end

View File

@ -0,0 +1,53 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import <Foundation/Foundation.h>
@class RATreeNodeController, RATreeNode, RATreeNodeCollectionController;
@protocol RATreeNodeCollectionControllerDataSource <NSObject>
- (NSInteger)treeNodeCollectionController:(RATreeNodeCollectionController *)controller numberOfChildrenForItem:(id)item;
- (id)treeNodeCollectionController:(RATreeNodeCollectionController *)controller child:(NSInteger)childIndex ofItem:(id)item;
@end
@interface RATreeNodeCollectionController : NSObject
@property (nonatomic, weak) id<RATreeNodeCollectionControllerDataSource> dataSource;
@property (nonatomic, readonly) NSInteger numberOfVisibleRowsForItems;
- (RATreeNode *)treeNodeForIndex:(NSInteger)index;
- (NSInteger)levelForItem:(id)item;
- (id)parentForItem:(id)item;
- (id)childInParent:(id)parent atIndex:(NSInteger)index;
- (NSInteger)indexForItem:(id)item;
- (NSInteger)lastVisibleDescendantIndexForItem:(id)item;
- (void)collapseRowForItem:(id)item collapseChildren:(BOOL)collapseChildren updates:(void(^)(NSIndexSet *))updates;
- (void)expandRowForItem:(id)item expandChildren:(BOOL)expandChildren updates:(void (^)(NSIndexSet *))updates;
- (void)insertItemsAtIndexes:(NSIndexSet *)indexes inParent:(id)item;
- (void)moveItemAtIndex:(NSInteger)index inParent:(id)parent toIndex:(NSInteger)newIndex inParent:(id)newParent updates:(void(^)(NSIndexSet *deletions, NSIndexSet *additions))updates;
- (void)removeItemsAtIndexes:(NSIndexSet *)indexes inParent:(id)item updates:(void(^)(NSIndexSet *))updates;
@end

View File

@ -0,0 +1,301 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "RATreeNodeCollectionController.h"
#import "RATreeNodeController.h"
#import "RATreeNode.h"
#import "RATreeNodeItem+Private.h"
#import "RABatchChanges.h"
@interface RATreeNodeCollectionController () <RATreeNodeItemDataSource>
@property (nonatomic, strong) RATreeNodeController *rootController;
@end
@implementation RATreeNodeCollectionController
- (NSInteger)numberOfVisibleRowsForItems
{
return self.rootController.numberOfVisibleDescendants;
}
- (RATreeNode *)treeNodeForIndex:(NSInteger)index
{
return [self.rootController controllerForIndex:index].treeNode;
}
- (NSInteger)indexForItem:(id)item
{
return [self.rootController indexForItem:item];
}
- (NSInteger)lastVisibleDescendantIndexForItem:(id)item
{
return [self.rootController lastVisibleDescendatIndexForItem:item];
}
- (id)parentForItem:(id)item
{
RATreeNodeController *controller = [self.rootController controllerForItem:item];
return controller.parentController.treeNode.item;
}
- (NSInteger)levelForItem:(id)item
{
return [self.rootController controllerForItem:item].level;
}
- (id)childInParent:(id)parent atIndex:(NSInteger)index
{
RATreeNodeController *controller = [self.rootController controllerForItem:parent].childControllers[index];
return controller.treeNode.item;
}
- (void)expandRowForItem:(id)item updates:(void (^)(NSIndexSet *))updates
{
[self expandRowForItem:item expandChildren:YES updates:updates];
}
- (void)expandRowForItem:(id)item expandChildren:(BOOL)expandChildren updates:(void (^)(NSIndexSet *))updates
{
NSParameterAssert(updates);
RATreeNodeController *parentController = [self.rootController controllerForItem:item];
NSMutableArray *items = [@[item] mutableCopy];
while ([items count] > 0) {
id currentItem = [items firstObject];
[items removeObject:currentItem];
RATreeNodeController *controller = [self.rootController controllerForItem:currentItem];
NSMutableArray *oldChildItems = [NSMutableArray array];
for (RATreeNodeController *nodeController in controller.childControllers) {
[oldChildItems addObject:nodeController.treeNode.item];
}
NSInteger numberOfChildren = [self.dataSource treeNodeCollectionController:self numberOfChildrenForItem:controller.treeNode.item];
NSIndexSet *allIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, numberOfChildren)];
NSArray *currentChildControllersAndIndexes = [self controllersAndIndexesForNodesWithIndexes:allIndexes inParentController:controller];
NSArray *currentChildControllers = [currentChildControllersAndIndexes valueForKey:@"controller"];
NSMutableArray *childControllersToInsert = [NSMutableArray array];
NSMutableIndexSet *indexesForInsertions = [NSMutableIndexSet indexSet];
NSMutableArray *childControllersToRemove = [NSMutableArray array];
NSMutableIndexSet *indexesForDeletions = [NSMutableIndexSet indexSet];
for (RATreeNodeController *loopNodeController in currentChildControllers) {
if (![controller.childControllers containsObject:loopNodeController]
&& ![oldChildItems containsObject:controller.treeNode.item]) {
[childControllersToInsert addObject:loopNodeController];
NSInteger index = [currentChildControllers indexOfObject:loopNodeController];
NSAssert(index != NSNotFound, nil);
[indexesForInsertions addIndex:index];
}
}
for (RATreeNodeController *loopNodeController in controller.childControllers) {
if (![currentChildControllers containsObject:loopNodeController]
&& ![childControllersToInsert containsObject:loopNodeController]) {
[childControllersToRemove addObject:loopNodeController];
NSInteger index = [controller.childControllers indexOfObject:loopNodeController];
NSAssert(index != NSNotFound, nil);
[indexesForDeletions addIndex:index];
}
}
[controller removeChildControllersAtIndexes:indexesForDeletions];
[controller insertChildControllers:childControllersToInsert atIndexes:indexesForInsertions];
if (expandChildren) {
for (RATreeNodeController *nodeController in controller.childControllers) {
[items addObject:nodeController.treeNode.item];
}
}
[controller expandAndExpandChildren:expandChildren];
}
updates(parentController.descendantsIndexes);
}
- (void)collapseRowForItem:(id)item collapseChildren:(BOOL)collapseChildren updates:(void (^)(NSIndexSet *))updates
{
NSParameterAssert(updates);
RATreeNodeController *controller = [self.rootController controllerForItem:item];
NSIndexSet *deletions = controller.descendantsIndexes;
[controller collapseAndCollapseChildren:collapseChildren];
updates(deletions);
}
- (void)insertItemsAtIndexes:(NSIndexSet *)indexes inParent:(id)item
{
RATreeNodeController *parentController = [self.rootController controllerForItem:item];
NSArray *newControllers = [self controllersForNodesWithIndexes:indexes inParentController:parentController];
[parentController insertChildControllers:newControllers atIndexes:indexes];
}
- (void)moveItemAtIndex:(NSInteger)index inParent:(id)parent toIndex:(NSInteger)newIndex inParent:(id)newParent updates:(void (^)(NSIndexSet *, NSIndexSet *))updates
{
NSParameterAssert(updates);
NSMutableIndexSet *removedIndexes = [NSMutableIndexSet indexSet];
NSMutableIndexSet *addedIndexes = [NSMutableIndexSet indexSet];
RATreeNodeController *parentController = [self.rootController controllerForItem:parent];
if (parent == newParent) {
[parentController moveChildControllerAtIndex:index toIndex:newIndex];
} else {
RATreeNodeController *childController = parentController.childControllers[index];
[removedIndexes addIndex:childController.index];
[removedIndexes addIndexes:childController.descendantsIndexes];
RATreeNodeController *newParentController = [self.rootController controllerForItem:parent];
[parentController removeChildControllersAtIndexes:[NSIndexSet indexSetWithIndex:index]];
[newParentController insertChildControllers:@[childController] atIndexes:[NSIndexSet indexSetWithIndex:newIndex]];
[addedIndexes addIndex:childController.index];
[addedIndexes addIndexes:childController.descendantsIndexes];
}
updates(removedIndexes, addedIndexes);
}
- (void)removeItemsAtIndexes:(NSIndexSet *)indexes inParent:(id)item updates:(void (^)(NSIndexSet *))updates
{
RATreeNodeController *parentController = [self.rootController controllerForItem:item];
NSMutableIndexSet *indexesToRemoval = [NSMutableIndexSet indexSet];
[indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
RATreeNodeController *controller = parentController.childControllers[idx];
[indexesToRemoval addIndex:controller.index];
[indexesToRemoval addIndexes:controller.descendantsIndexes];
}];
[parentController removeChildControllersAtIndexes:indexes];
updates(indexesToRemoval);
}
- (NSArray *)controllersAndIndexesForNodesWithIndexes:(NSIndexSet *)indexes inParentController:(RATreeNodeController *)parentController
{
NSMutableArray *childControllers = [parentController.childControllers mutableCopy];
NSMutableArray *currentControllers = [NSMutableArray array];
NSMutableArray *invalidItems = [NSMutableArray array];
for (RATreeNodeController *nodeController in parentController.childControllers) {
[invalidItems addObject:nodeController.treeNode.item];
}
[indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
RATreeNodeController *controller;
RATreeNodeController *oldControllerForCurrentIndex = nil;
RATreeNodeItem *lazyItem = [[RATreeNodeItem alloc] initWithParent:parentController.treeNode.item index:idx];
lazyItem.dataSource = self;
for (RATreeNodeController *controller in parentController.childControllers) {
if ([controller.treeNode.item isEqual:lazyItem.item]) {
oldControllerForCurrentIndex = controller;
}
}
if (oldControllerForCurrentIndex != nil) {
controller = oldControllerForCurrentIndex;
} else {
controller = [[RATreeNodeController alloc] initWithParent:parentController item:lazyItem expandedBlock:^BOOL(id item) {
return [childControllers indexOfObjectPassingTest:^BOOL(RATreeNodeController *controller, NSUInteger idx, BOOL *stop) {
return [controller.treeNode.item isEqual:item];
}] != NSNotFound;
}];
}
[currentControllers addObject:@{ @"index" : @(idx),
@"controller" : controller }];
}];
return [currentControllers copy];
}
- (NSArray *)controllersForNodesWithIndexes:(NSIndexSet *)indexes inParentController:(RATreeNodeController *)parentController
{
return [[self controllersAndIndexesForNodesWithIndexes:indexes inParentController:parentController] valueForKey:@"controller"];
}
- (NSArray *)controllersForNodes:(NSInteger)nodesNumber inParentController:(RATreeNodeController *)parentController
{
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, nodesNumber)];
return [self controllersForNodesWithIndexes:indexSet inParentController:parentController];
}
#pragma mark - RATreeNodeController delegate
- (id)treeNodeController:(RATreeNodeController *)controller child:(NSInteger)childIndex
{
return [self.dataSource treeNodeCollectionController:self child:childIndex ofItem:controller.treeNode.item];
}
- (NSInteger)numberOfChildrenForTreeNodeController:(RATreeNodeController *)controller
{
return [self.dataSource treeNodeCollectionController:self numberOfChildrenForItem:controller.treeNode.item];
}
#pragma mark - RATreeNodeItem data source
- (id)itemForTreeNodeItem:(RATreeNodeItem *)treeNodeItem
{
return [self.dataSource treeNodeCollectionController:self child:treeNodeItem.index ofItem:treeNodeItem.parent];
}
#pragma mark - Properties
- (RATreeNodeController *)rootController
{
if (!_rootController) {
_rootController = [[RATreeNodeController alloc] initWithParent:nil item:nil expandedBlock:^BOOL(id _) {
return YES;
}];
NSInteger numberOfChildren = [self.dataSource treeNodeCollectionController:self numberOfChildrenForItem:nil];
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, numberOfChildren)];
NSArray *childControllers = [self controllersForNodesWithIndexes:indexSet inParentController:_rootController];
[_rootController insertChildControllers:childControllers atIndexes:indexSet];
}
return _rootController;
}
@end

View File

@ -0,0 +1,51 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import <Foundation/Foundation.h>
@class RATreeNodeController, RATreeNode, RATreeNodeItem;
@interface RATreeNodeController : NSObject
@property (nonatomic, weak, readonly) RATreeNodeController *parentController;
@property (nonatomic, strong, readonly) NSArray *childControllers;
@property (nonatomic, strong, readonly) RATreeNode *treeNode;
@property (nonatomic, readonly) NSInteger index;
@property (nonatomic, readonly) NSInteger numberOfVisibleDescendants;
@property (nonatomic, strong, readonly) NSIndexSet *descendantsIndexes;
@property (nonatomic, readonly) NSInteger level;
- (instancetype)initWithParent:(RATreeNodeController *)parentController item:(RATreeNodeItem *)item expandedBlock:(BOOL (^)(id))expanded;
- (void)collapseAndCollapseChildren:(BOOL)collapseChildren;
- (void)expandAndExpandChildren:(BOOL)expandChildren;
- (void)insertChildControllers:(NSArray *)controllers atIndexes:(NSIndexSet *)indexes;
- (void)moveChildControllerAtIndex:(NSInteger)index toIndex:(NSInteger)newIndex;
- (void)removeChildControllersAtIndexes:(NSIndexSet *)indexes;
- (NSInteger)indexForItem:(id)item;
- (NSInteger)lastVisibleDescendatIndexForItem:(id)item;
- (RATreeNodeController *)controllerForIndex:(NSInteger)index;
- (RATreeNodeController *)controllerForItem:(id)item;
@end

View File

@ -0,0 +1,306 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "RATreeNodeController.h"
#import "RATreeNode.h"
#import "RATreeNode_ClassExtension.h"
@interface RATreeNodeController ()
@property (nonatomic, strong) RATreeNode *treeNode;
@property (nonatomic) NSInteger index;
@property (nonatomic) NSInteger numberOfVisibleDescendants;
@property (nonatomic) NSInteger level;
@property (nonatomic, weak) RATreeNodeController *parentController;
@property (nonatomic, strong) NSMutableArray *mutablechildControllers;
@end
@implementation RATreeNodeController
- (instancetype)initWithParent:(RATreeNodeController *)parentController item:(RATreeNodeItem *)item expandedBlock:(BOOL (^)(id))expandedBlock
{
self = [super init];
if (self) {
[self invalidate];
_level = NSIntegerMin;
_parentController = parentController;
_treeNode = [[RATreeNode alloc] initWithLazyItem:item expandedBlock:expandedBlock];
_mutablechildControllers = [NSMutableArray array];
}
return self;
}
- (void)insertChildControllers:(NSArray *)controllers atIndexes:(NSIndexSet *)indexes
{
if (indexes.count == 0) {
return;
}
[self.mutablechildControllers insertObjects:controllers atIndexes:indexes];
[self invalidateTreeNodesAfterChildAtIndex:[indexes firstIndex] - 1];
}
- (void)removeChildControllersAtIndexes:(NSIndexSet *)indexes
{
if (indexes.count == 0) {
return;
}
[self.mutablechildControllers removeObjectsAtIndexes:indexes];
[self invalidateTreeNodesAfterChildAtIndex:[indexes firstIndex] - 1];
}
- (void)moveChildControllerAtIndex:(NSInteger)index toIndex:(NSInteger)newIndex
{
if (index == newIndex) {
return;
}
id controller = self.mutablechildControllers[index];
[self.mutablechildControllers removeObjectAtIndex:index];
[self.mutablechildControllers insertObject:controller atIndex:index];
[self invalidateTreeNodesAfterChildAtIndex:MIN(index, newIndex)-1];
}
- (RATreeNodeController *)controllerForItem:(id)item
{
if (item == self.treeNode.item) {
return self;
}
for (RATreeNodeController *controller in self.childControllers) {
RATreeNodeController *result = [controller controllerForItem:item];
if (result) {
return result;
}
}
return nil;
}
- (RATreeNodeController *)controllerForIndex:(NSInteger)index
{
if (self.index == index) {
return self;
}
if (!self.treeNode.expanded) {
return nil;
}
for (RATreeNodeController *controller in self.childControllers) {
RATreeNodeController *result = [controller controllerForIndex:index];
if (result) {
return result;
}
}
return nil;
}
- (NSInteger)indexForItem:(id)item
{
RATreeNodeController *controller = [self controllerForItem:item];
return controller ? controller.index : NSNotFound;
}
- (NSInteger)lastVisibleDescendatIndexForItem:(id)item
{
if (self.treeNode.item == item) {
return [self lastVisibleDescendatIndex];
}
for (RATreeNodeController *nodeController in self.childControllers) {
NSInteger lastIndex = [nodeController lastVisibleDescendatIndexForItem:item];
if (lastIndex != NSNotFound) {
return lastIndex;
}
}
return NSNotFound;
}
#pragma mark - Collapsing and expanding
- (void)expandAndExpandChildren:(BOOL)expandChildren
{
for (RATreeNodeController *nodeController in self.childControllers) {
[nodeController invalidate];
}
[self privateExpandAndExpandChildren:expandChildren];
}
- (void)privateExpandAndExpandChildren:(BOOL)expandChildren
{
[self.treeNode setExpanded:YES];
[self invalidate];
for (RATreeNodeController *nodeController in self.childControllers) {
if (nodeController.treeNode.expanded || expandChildren) {
[nodeController expandAndExpandChildren:expandChildren];
}
}
[self.parentController invalidateTreeNodesAfterChildAtIndex:[self.parentController.childControllers indexOfObject:self]];
}
- (void)collapseAndCollapseChildren:(BOOL)collapseChildren
{
[self privateCollapseAndCollapseChildren:collapseChildren];
}
- (void)privateCollapseAndCollapseChildren:(BOOL)collapseChildren
{
[self.treeNode setExpanded:NO];
[self invalidate];
if (collapseChildren) {
for (RATreeNodeController *controller in self.childControllers) {
[controller collapseAndCollapseChildren:collapseChildren];
}
}
[self.parentController invalidateTreeNodesAfterChildAtIndex:[self.parentController.childControllers indexOfObject:self]];
}
#pragma mark -
- (void)invalidate
{
[self invalidateNumberOfVisibleDescendants];
[self invalideIndex];
}
- (void)invalidateNumberOfVisibleDescendants
{
self.numberOfVisibleDescendants = NSIntegerMin;
}
- (void)invalideIndex
{
self.index = NSIntegerMin;
}
- (void)invalidateTreeNodesAfterChildAtIndex:(NSInteger)index
{
NSInteger selfIndex = [self.parentController.childControllers indexOfObject:self];
[self.parentController invalidateTreeNodesAfterChildAtIndex:selfIndex];
[self invalidate];
[self invalidateDescendantsNodesAfterChildAtIndex:index];
}
- (void)invalidateDescendantsNodesAfterChildAtIndex:(NSInteger)index
{
if (!self.treeNode.expanded) {
return;
}
for (NSInteger i = index + 1; i < self.childControllers.count; i++) {
RATreeNodeController *controller = self.childControllers[i];
[controller invalidate];
[controller invalidateDescendantsNodesAfterChildAtIndex:-1];
}
}
#pragma mark - Properties
- (NSArray *)childControllers
{
return self.mutablechildControllers;
}
- (NSInteger)index
{
if (_index != NSIntegerMin) {
return _index;
}
if (!self.parentController) {
_index = -1;
} else if (!self.parentController.treeNode.expanded) {
_index = NSNotFound;
} else {
NSInteger indexInParent = [self.parentController.childControllers indexOfObject:self];
if (indexInParent != 0) {
RATreeNodeController *controller = self.parentController.childControllers[indexInParent-1];
_index = [controller lastVisibleDescendatIndex] + 1;
} else {
_index = self.parentController.index + 1;
}
}
return _index;
}
- (NSInteger)lastVisibleDescendatIndex
{
return self.index + self.numberOfVisibleDescendants;
}
- (NSIndexSet *)descendantsIndexes
{
NSInteger numberOfVisibleDescendants = self.numberOfVisibleDescendants;
NSInteger startIndex = self.index + 1;
NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet];
for (NSInteger i = startIndex; i < startIndex + numberOfVisibleDescendants; i++) {
[indexSet addIndex:i];
}
return [indexSet copy];
}
- (NSInteger)numberOfVisibleDescendants
{
if (_numberOfVisibleDescendants == NSIntegerMin) {
if (self.treeNode.expanded) {
NSInteger numberOfVisibleDescendants = [self.childControllers count];
for (RATreeNodeController *controller in self.childControllers) {
numberOfVisibleDescendants += controller.numberOfVisibleDescendants;
}
_numberOfVisibleDescendants = numberOfVisibleDescendants;
} else {
_numberOfVisibleDescendants = 0;
}
}
return _numberOfVisibleDescendants;
}
- (NSInteger)level
{
if (self.treeNode.item == nil) {
return -1;
}
if (_level == NSIntegerMin) {
_level = self.parentController.level + 1;
}
return _level;
}
@end

View File

@ -0,0 +1,31 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "RATreeNodeItem.h"
@interface RATreeNodeItem ()
@property (nonatomic, strong) id item;
@property (nonatomic, weak) id parent;
@property (nonatomic) NSInteger index;
@property (nonatomic, weak) id<RATreeNodeItemDataSource> dataSource;
@end

View File

@ -0,0 +1,37 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "RATreeNodeItem.h"
@protocol RATreeNodeItemDataSource <NSObject>
- (id)itemForTreeNodeItem:(RATreeNodeItem *)treeNodeItem;
@end
@interface RATreeNodeItem (Private)
@property (nonatomic, strong, readonly) id parent;
@property (nonatomic, readonly) NSInteger index;
@property (nonatomic, weak) id<RATreeNodeItemDataSource> dataSource;
@end

View File

@ -0,0 +1,29 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "RATreeNodeItem+Private.h"
@implementation RATreeNodeItem (Private)
@dynamic dataSource;
@dynamic index;
@dynamic parent;
@end

View File

@ -0,0 +1,30 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import <Foundation/Foundation.h>
@interface RATreeNodeItem : NSObject
@property (nonatomic, strong, readonly) id item;
- (instancetype)initWithParent:(id)parent index:(NSInteger)index;
@end

View File

@ -0,0 +1,48 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "RATreeNodeItem.h"
#import "RATreeNodeItem+Private.h"
#import "RATreeNodeItem+ClassExtension.h"
@implementation RATreeNodeItem
- (instancetype)initWithParent:(id)parent index:(NSInteger)index
{
self = [super init];
if (self) {
_parent = parent;
_index = index;
}
return self;
}
- (id)item
{
if (!_item) {
_item = [self.dataSource itemForTreeNodeItem:self];
}
return _item;
}
@end

View File

@ -0,0 +1,27 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "RATreeNode.h"
@interface RATreeNode ()
@property (nonatomic) BOOL expanded;
@end

View File

@ -0,0 +1,41 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "RATreeView.h"
@interface RATreeView (Enums)
//Row Animations
+ (UITableViewRowAnimation)tableViewRowAnimationForTreeViewRowAnimation:(RATreeViewRowAnimation)rowAnimation;
#if TARGET_OS_IOS
//Cell Separator Styles
+ (RATreeViewCellSeparatorStyle)treeViewCellSeparatorStyleForTableViewSeparatorStyle:(UITableViewCellSeparatorStyle)style;
+ (UITableViewCellSeparatorStyle)tableViewCellSeparatorStyleForTreeViewCellSeparatorStyle:(RATreeViewCellSeparatorStyle)style;
#endif
//Tree View Style
+ (UITableViewStyle)tableViewStyleForTreeViewStyle:(RATreeViewStyle)treeViewStyle;
+ (RATreeViewStyle)treeViewStyleForTableViewStyle:(UITableViewStyle)tableViewStyle;
//Scroll Positions
+ (UITableViewScrollPosition)tableViewScrollPositionForTreeViewScrollPosition:(RATreeViewScrollPosition)scrollPosition;
@end

View File

@ -0,0 +1,128 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "RATreeView+Enums.h"
#import "RATreeView.h"
@implementation RATreeView (Enums)
#pragma mark Row Animations
+ (UITableViewRowAnimation)tableViewRowAnimationForTreeViewRowAnimation:(RATreeViewRowAnimation)rowAnimation
{
switch (rowAnimation) {
case RATreeViewRowAnimationFade:
return UITableViewRowAnimationFade;
case RATreeViewRowAnimationNone:
return UITableViewRowAnimationNone;
case RATreeViewRowAnimationAutomatic:
return UITableViewRowAnimationAutomatic;
case RATreeViewRowAnimationBottom:
return UITableViewRowAnimationBottom;
case RATreeViewRowAnimationLeft:
return UITableViewRowAnimationLeft;
case RATreeViewRowAnimationMiddle:
return UITableViewRowAnimationMiddle;
case RATreeViewRowAnimationRight:
return UITableViewRowAnimationRight;
case RATreeViewRowAnimationTop:
return UITableViewRowAnimationTop;
default:
return UITableViewRowAnimationNone;
}
}
#if TARGET_OS_IOS
#pragma mark Cell Separator Styles
+ (RATreeViewCellSeparatorStyle)treeViewCellSeparatorStyleForTableViewSeparatorStyle:(UITableViewCellSeparatorStyle)style
{
switch (style) {
case UITableViewCellSeparatorStyleNone:
return RATreeViewCellSeparatorStyleNone;
case UITableViewCellSeparatorStyleSingleLine:
return RATreeViewCellSeparatorStyleSingleLine;
case UITableViewCellSeparatorStyleSingleLineEtched:
return RATreeViewCellSeparatorStyleSingleLineEtched;
default:
return RATreeViewCellSeparatorStyleNone;
}
}
+ (UITableViewCellSeparatorStyle)tableViewCellSeparatorStyleForTreeViewCellSeparatorStyle:(RATreeViewCellSeparatorStyle)style
{
switch (style) {
case RATreeViewCellSeparatorStyleNone:
return UITableViewCellSeparatorStyleNone;
case RATreeViewCellSeparatorStyleSingleLine:
return UITableViewCellSeparatorStyleSingleLine;
case RATreeViewCellSeparatorStyleSingleLineEtched:
return UITableViewCellSeparatorStyleSingleLineEtched;
default:
return UITableViewCellSeparatorStyleNone;
}
}
#endif
#pragma mark Tree View Style
+ (UITableViewStyle)tableViewStyleForTreeViewStyle:(RATreeViewStyle)treeViewStyle
{
switch (treeViewStyle) {
case RATreeViewStylePlain:
return UITableViewStylePlain;
case RATreeViewStyleGrouped:
return UITableViewStyleGrouped;
}
}
+ (RATreeViewStyle)treeViewStyleForTableViewStyle:(UITableViewStyle)tableViewStyle
{
switch (tableViewStyle) {
case UITableViewStylePlain:
return RATreeViewStylePlain;
case UITableViewStyleGrouped:
return RATreeViewStyleGrouped;
default:
return RATreeViewStylePlain;
}
}
#pragma mark Scroll Positions
+ (UITableViewScrollPosition)tableViewScrollPositionForTreeViewScrollPosition:(RATreeViewScrollPosition)scrollPosition
{
switch (scrollPosition) {
case RATreeViewScrollPositionNone:
return UITableViewScrollPositionNone;
case RATreeViewScrollPositionTop:
return UITableViewScrollPositionTop;
case RATreeViewScrollPositionMiddle:
return UITableViewScrollPositionMiddle;
case RATreeViewScrollPositionBottom:
return UITableViewScrollPositionBottom;
default:
return UITableViewScrollPositionNone;
}
}
@end

View File

@ -0,0 +1,43 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "RATreeView.h"
@class RATreeNode;
@interface RATreeView (Private)
- (RATreeNode *)treeNodeForIndexPath:(NSIndexPath *)indexPath;
- (NSIndexPath *)indexPathForItem:(id)item;
- (void)setupTreeStructure;
- (void)collapseCellForTreeNode:(RATreeNode *)treeNode;
- (void)collapseCellForTreeNode:(RATreeNode *)treeNode collapseChildren:(BOOL)collapseChildren withRowAnimation:(RATreeViewRowAnimation)rowAnimation;
- (void)expandCellForTreeNode:(RATreeNode *)treeNode;
- (void)expandCellForTreeNode:(RATreeNode *)treeNode expandChildren:(BOOL)expandChildren withRowAnimation:(RATreeViewRowAnimation)rowAnimation;
- (void)insertItemAtIndex:(NSInteger)index inParent:(id)parent withAnimation:(RATreeViewRowAnimation)animation;
- (void)removeItemAtIndex:(NSInteger)indexe inParent:(id)parent withAnimation:(RATreeViewRowAnimation)animation;
@end

View File

@ -0,0 +1,192 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "RATreeView+Private.h"
#import "RATreeView+Enums.h"
#import "RATreeView+RATreeNodeCollectionControllerDataSource.h"
#import "RATreeView_ClassExtension.h"
#import "RABatchChanges.h"
#import "RATreeNode.h"
#import "RATreeNodeController.h"
#import "RATreeNodeCollectionController.h"
@implementation RATreeView (Private)
- (void)setupTreeStructure
{
self.treeNodeCollectionController = [[RATreeNodeCollectionController alloc] init];
self.treeNodeCollectionController.dataSource = self;
self.batchChanges = [[RABatchChanges alloc] init];
}
- (NSArray *)childrenForItem:(id)item
{
NSParameterAssert(item);
NSMutableArray *children = [NSMutableArray array];
NSInteger numberOfChildren = [self.dataSource treeView:self numberOfChildrenOfItem:item];
for (int i = 0; i < numberOfChildren; i++) {
[children addObject:[self.dataSource treeView:self child:i ofItem:item]];
}
return [NSArray arrayWithArray:children];
}
- (RATreeNode *)treeNodeForIndexPath:(NSIndexPath *)indexPath
{
NSParameterAssert(indexPath.section == 0);
return [self.treeNodeCollectionController treeNodeForIndex:indexPath.row];
}
- (NSIndexPath *)indexPathForItem:(id)item
{
return [NSIndexPath indexPathForRow:[self.treeNodeCollectionController indexForItem:item] inSection:0];
}
#pragma mark Collapsing and Expanding Rows
- (void)collapseCellForTreeNode:(RATreeNode *)treeNode
{
[self collapseCellForTreeNode:treeNode collapseChildren:self.collapsesChildRowsWhenRowCollapses withRowAnimation:self.rowsCollapsingAnimation];
}
- (void)collapseCellForTreeNode:(RATreeNode *)treeNode collapseChildren:(BOOL)collapseChildren withRowAnimation:(RATreeViewRowAnimation)rowAnimation
{
[self.tableView beginUpdates];
[self.batchChanges beginUpdates];
NSInteger index = [self.treeNodeCollectionController lastVisibleDescendantIndexForItem:treeNode.item];
__weak __typeof(self) weakSelf = self;
[self.batchChanges collapseItemWithBlock:^{
UITableViewRowAnimation tableViewRowAnimation = [RATreeView tableViewRowAnimationForTreeViewRowAnimation:rowAnimation];
[weakSelf.treeNodeCollectionController collapseRowForItem:treeNode.item collapseChildren:collapseChildren updates:^(NSIndexSet *deletions) {
[weakSelf.tableView deleteRowsAtIndexPaths:IndexesToIndexPaths(deletions) withRowAnimation:tableViewRowAnimation];
}];
} lastIndex:index];
[self.batchChanges endUpdates];
[self.tableView endUpdates];
}
- (void)expandCellForTreeNode:(RATreeNode *)treeNode
{
[self expandCellForTreeNode:treeNode expandChildren:self.expandsChildRowsWhenRowExpands withRowAnimation:self.rowsExpandingAnimation];
}
- (void)expandCellForTreeNode:(RATreeNode *)treeNode expandChildren:(BOOL)expandChildren withRowAnimation:(RATreeViewRowAnimation)rowAnimation
{
[self.tableView beginUpdates];
[self.batchChanges beginUpdates];
NSInteger index = [self.treeNodeCollectionController indexForItem:treeNode.item];
__weak __typeof(self) weakSelf = self;
[self.batchChanges expandItemWithBlock:^{
UITableViewRowAnimation tableViewRowAnimation = [RATreeView tableViewRowAnimationForTreeViewRowAnimation:rowAnimation];
[weakSelf.treeNodeCollectionController expandRowForItem:treeNode.item expandChildren:expandChildren updates:^(NSIndexSet *insertions) {
[weakSelf.tableView insertRowsAtIndexPaths:IndexesToIndexPaths(insertions) withRowAnimation:tableViewRowAnimation];
}];
} atIndex:index];
[self.batchChanges endUpdates];
[self.tableView endUpdates];
}
- (void)insertItemAtIndex:(NSInteger)index inParent:(id)parent withAnimation:(RATreeViewRowAnimation)animation
{
NSInteger idx = [self.treeNodeCollectionController indexForItem:parent];
if (idx == NSNotFound) {
return;
}
idx += index + 1;
__weak __typeof(self) weakSelf = self;
[self.batchChanges insertItemWithBlock:^{
[weakSelf.treeNodeCollectionController insertItemsAtIndexes:[NSIndexSet indexSetWithIndex:index] inParent:parent];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:idx inSection:0];
UITableViewRowAnimation tableViewRowAnimation = [RATreeView tableViewRowAnimationForTreeViewRowAnimation:animation];
[weakSelf.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:tableViewRowAnimation];
} atIndex:idx];
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
- (void)moveItemAtIndex:(NSInteger)index inParent:(id)parent toIndex:(NSInteger)newIndex inParent:(id)newParent
#pragma clang diagnostic pop
{
NSInteger idx = [self.treeNodeCollectionController indexForItem:parent];
if (idx == NSNotFound) {
return;
}
idx += index + 1;
__weak __typeof(self) weakSelf = self;
[self.batchChanges insertItemWithBlock:^{
[weakSelf.treeNodeCollectionController moveItemAtIndex:index inParent:parent toIndex:newIndex inParent:newParent updates:^(NSIndexSet *deletions, NSIndexSet *additions) {
NSArray *deletionsArray = IndexesToIndexPaths(deletions);
NSArray *additionsArray = IndexesToIndexPaths(additions);
NSInteger i = 0;
for (NSIndexPath *deletedIndexPath in deletionsArray) {
[weakSelf.tableView moveRowAtIndexPath:deletedIndexPath toIndexPath:additionsArray[i]];
i++;
}
}];
} atIndex:idx];
}
- (void)removeItemAtIndex:(NSInteger)index inParent:(id)parent withAnimation:(RATreeViewRowAnimation)animation
{
id child = [self.treeNodeCollectionController childInParent:parent atIndex:index];
NSInteger idx = [self.treeNodeCollectionController lastVisibleDescendantIndexForItem:child];
if (idx == NSNotFound) {
return;
}
__weak __typeof(self) weakSelf = self;
[self.batchChanges insertItemWithBlock:^{
[weakSelf.treeNodeCollectionController removeItemsAtIndexes:[NSIndexSet indexSetWithIndex:index] inParent:parent updates:^(NSIndexSet *removedIndexes) {
UITableViewRowAnimation tableViewRowAnimation = [RATreeView tableViewRowAnimationForTreeViewRowAnimation:animation];
[weakSelf.tableView deleteRowsAtIndexPaths:IndexesToIndexPaths(removedIndexes) withRowAnimation:tableViewRowAnimation];
}];
} atIndex:idx];
}
#pragma mark -
static NSArray * IndexesToIndexPaths(NSIndexSet *indexes)
{
NSMutableArray *indexPaths = [NSMutableArray array];
[indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
[indexPaths addObject:[NSIndexPath indexPathForRow:idx inSection:0]];
}];
return [indexPaths copy];
}
@end

View File

@ -0,0 +1,26 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "RATreeView.h"
#import "RATreeNodeCollectionController.h"
@interface RATreeView (RATreeNodeCollectionControllerDataSource) <RATreeNodeCollectionControllerDataSource>
@end

View File

@ -0,0 +1,35 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "RATreeView+RATreeNodeCollectionControllerDataSource.h"
@implementation RATreeView (RATreeNodeCollectionControllerDataSource)
- (NSInteger)treeNodeCollectionController:(RATreeNodeCollectionController *)controller numberOfChildrenForItem:(id)item
{
return [self.dataSource treeView:self numberOfChildrenOfItem:item];
}
- (id)treeNodeCollectionController:(RATreeNodeCollectionController *)controller child:(NSInteger)childIndex ofItem:(id)item
{
return [self.dataSource treeView:self child:childIndex ofItem:item];
}
@end

View File

@ -0,0 +1,25 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "RATreeView.h"
@interface RATreeView (TableViewDataSource) <UITableViewDataSource>
@end

View File

@ -0,0 +1,70 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "RATreeView+TableViewDataSource.h"
#import "RATreeView+Private.h"
#import "RATreeView_ClassExtension.h"
#import "RATreeNodeCollectionController.h"
#import "RATreeNodeController.h"
#import "RATreeNode.h"
@implementation RATreeView (TableViewDataSource)
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (self.treeNodeCollectionController == nil) {
[self setupTreeStructure];
}
return self.treeNodeCollectionController.numberOfVisibleRowsForItems;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
return [self.dataSource treeView:self cellForItem:treeNode.item];
}
#pragma mark - Inserting or Deleting Table Rows
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.dataSource respondsToSelector:@selector(treeView:commitEditingStyle:forRowForItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
[self.dataSource treeView:self commitEditingStyle:editingStyle forRowForItem:treeNode.item];
}
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.dataSource respondsToSelector:@selector(treeView:canEditRowForItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
return [self.dataSource treeView:self canEditRowForItem:treeNode.item];
}
return YES;
}
@end

View File

@ -0,0 +1,25 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "RATreeView.h"
@interface RATreeView (TableViewDelegate) <UITableViewDelegate>
@end

View File

@ -0,0 +1,318 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import <QuartzCore/QuartzCore.h>
#import "RATreeView+TableViewDelegate.h"
#import "RATreeView_ClassExtension.h"
#import "RATreeView+Private.h"
#import "RATreeView.h"
#import "RATreeNodeCollectionController.h"
#import "RATreeNode.h"
@implementation RATreeView (TableViewDelegate)
#pragma mark - Configuring Rows for the Table View
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:@selector(treeView:heightForRowForItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
return [self.delegate treeView:self heightForRowForItem:treeNode.item];
}
return self.tableView.rowHeight;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:@selector(treeView:estimatedHeightForRowForItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
return [self.delegate treeView:self estimatedHeightForRowForItem:treeNode.item];
}
return UITableViewAutomaticDimension;
}
- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:@selector(treeView:indentationLevelForRowForItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
return [self.delegate treeView:self indentationLevelForRowForItem:treeNode.item];
}
return 0;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:@selector(treeView:willDisplayCell:forItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
[self.delegate treeView:self willDisplayCell:cell forItem:treeNode.item];
}
}
#pragma mark - Managing Accessory Views
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:@selector(treeView:accessoryButtonTappedForRowForItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
[self.delegate treeView:self accessoryButtonTappedForRowForItem:treeNode.item];
}
}
#pragma mark - Managing Selection
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:@selector(treeView:willSelectRowForItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
id item = [self.delegate treeView:self willSelectRowForItem:treeNode.item];
if (item) {
NSIndexPath *newIndexPath = [self indexPathForItem:item];
return (newIndexPath.row == NSNotFound) ? indexPath : newIndexPath;
} else {
return nil;
}
}
return indexPath;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
if ([self.delegate respondsToSelector:@selector(treeView:didSelectRowForItem:)]) {
[self.delegate treeView:self didSelectRowForItem:treeNode.item];
}
if (treeNode.expanded) {
if ([self.delegate respondsToSelector:@selector(treeView:shouldCollapaseRowForItem:)]) {
if ([self.delegate treeView:self shouldCollapaseRowForItem:treeNode.item]) {
[self collapseCellForTreeNode:treeNode informDelegate:YES];
}
} else {
[self collapseCellForTreeNode:treeNode informDelegate:YES];
}
} else {
if ([self.delegate respondsToSelector:@selector(treeView:shouldExpandRowForItem:)]) {
if ([self.delegate treeView:self shouldExpandRowForItem:treeNode.item]) {
[self expandCellForTreeNode:treeNode informDelegate:YES];
}
} else {
[self expandCellForTreeNode:treeNode informDelegate:YES];
}
}
}
- (NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:@selector(treeView:willDeselectRowForItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
id item = [self.delegate treeView:self willDeselectRowForItem:treeNode.item];
NSIndexPath *delegateIndexPath = [self indexPathForItem:item];
return delegateIndexPath.row == NSNotFound ? indexPath : delegateIndexPath;
} else {
return indexPath;
}
}
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:@selector(treeView:didDeselectRowForItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
[self.delegate treeView:self didDeselectRowForItem:treeNode.item];
}
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:@selector(treeView:editingStyleForRowForItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
return [self.delegate treeView:self editingStyleForRowForItem:treeNode.item];
}
return UITableViewCellEditingStyleDelete;
}
- (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:@selector(treeView:titleForDeleteConfirmationButtonForRowForItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
return [self.delegate treeView:self titleForDeleteConfirmationButtonForRowForItem:treeNode.item];
}
return @"Delete";
}
- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:@selector(treeView:shouldIndentWhileEditingRowForItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
return [self.delegate treeView:self shouldIndentWhileEditingRowForItem:treeNode.item];
}
return YES;
}
#pragma mark - Editing Table Rows
- (void)tableView:(UITableView *)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:@selector(treeView:willBeginEditingRowForItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
[self.delegate treeView:self willBeginEditingRowForItem:treeNode.item];
}
}
- (void)tableView:(UITableView *)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:@selector(treeView:didEndEditingRowForItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
[self.delegate treeView:self didEndEditingRowForItem:treeNode.item];
}
}
- (NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:@selector(treeView:editActionsForItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
return [self.delegate treeView:self editActionsForItem:treeNode.item];
}
return nil;
}
#pragma mark - Tracking the Removal of Views
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:@selector(treeView:didEndDisplayingCell:forItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
[self.delegate treeView:self didEndDisplayingCell:cell forItem:treeNode.item];
}
}
#pragma mark - Copying and Pasting Row Content
- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:@selector(treeView:shouldShowMenuForRowForItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
return [self.delegate treeView:self shouldShowMenuForRowForItem:treeNode.item];
}
return NO;
}
- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
{
if ([self.delegate respondsToSelector:@selector(treeView:canPerformAction:forRowForItem:withSender:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
return [self.delegate treeView:self canPerformAction:action forRowForItem:treeNode.item withSender:sender];
}
return NO;
}
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
{
if ([self.delegate respondsToSelector:@selector(treeView:performAction:forRowForItem:withSender:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
return [self.delegate treeView:self performAction:action forRowForItem:treeNode.item withSender:sender];
}
}
#pragma mark - Managing Table View Highlighting
- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:@selector(treeView:shouldHighlightRowForItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
return [self.delegate treeView:self shouldHighlightRowForItem:treeNode.item];
}
return YES;
}
- (void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:@selector(treeView:didHighlightRowForItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
[self.delegate treeView:self didHighlightRowForItem:treeNode.item];
}
}
- (void)tableView:(UITableView *)tableView didUnhighlightRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:@selector(treeView:didUnhighlightRowForItem:)]) {
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
[self.delegate treeView:self didUnhighlightRowForItem:treeNode.item];
}
}
#pragma mark - Private Helpers
- (void)collapseCellForTreeNode:(RATreeNode *)treeNode informDelegate:(BOOL)informDelegate
{
if (informDelegate) {
if ([self.delegate respondsToSelector:@selector(treeView:willCollapseRowForItem:)]) {
[self.delegate treeView:self willCollapseRowForItem:treeNode.item];
}
}
[CATransaction begin];
[CATransaction setCompletionBlock:^{
if ([self.delegate respondsToSelector:@selector(treeView:didCollapseRowForItem:)] &&
informDelegate) {
dispatch_async(dispatch_get_main_queue(), ^{
//Content size of the UITableView isn't updates when completion block of the CATransaction is called. To make it possible for the user of the RATreeView to get a correct content size in the implementation of the 'treeView:didCollapseRowForItem' RATreeView calls this method in the next run loop.
[self.delegate treeView:self didCollapseRowForItem:treeNode.item];
});
}
}];
[self collapseCellForTreeNode:treeNode];
[CATransaction commit];
}
- (void)expandCellForTreeNode:(RATreeNode *)treeNode informDelegate:(BOOL)informDelegate
{
if (informDelegate) {
if ([self.delegate respondsToSelector:@selector(treeView:willExpandRowForItem:)]) {
[self.delegate treeView:self willExpandRowForItem:treeNode.item];
}
}
[CATransaction begin];
[CATransaction setCompletionBlock:^{
if ([self.delegate respondsToSelector:@selector(treeView:didExpandRowForItem:)] &&
informDelegate) {
dispatch_async(dispatch_get_main_queue(), ^{
//Content size of the UITableView isn't updates when completion block of the CATransaction is called. To make it possible for the user of the RATreeView to get a correct content size in the implementation of the 'treeView:didExpandRowForItem' RATreeView calls this method in the next run loop.
[self.delegate treeView:self didExpandRowForItem:treeNode.item];
});
}
}];
[self expandCellForTreeNode:treeNode];
[CATransaction commit];
}
@end

View File

@ -0,0 +1,604 @@
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class RATreeView, RATreeNodeCollectionController, RATreeNode;
typedef enum {
RATreeViewStylePlain = 0,
RATreeViewStyleGrouped
} RATreeViewStyle;
typedef enum RATreeViewCellSeparatorStyle {
RATreeViewCellSeparatorStyleNone = 0,
RATreeViewCellSeparatorStyleSingleLine,
RATreeViewCellSeparatorStyleSingleLineEtched
} RATreeViewCellSeparatorStyle;
typedef enum RATreeViewScrollPosition {
RATreeViewScrollPositionNone = 0,
RATreeViewScrollPositionTop,
RATreeViewScrollPositionMiddle,
RATreeViewScrollPositionBottom
} RATreeViewScrollPosition;
typedef enum RATreeViewRowAnimation {
RATreeViewRowAnimationFade = 0,
RATreeViewRowAnimationRight,
RATreeViewRowAnimationLeft,
RATreeViewRowAnimationTop,
RATreeViewRowAnimationBottom,
RATreeViewRowAnimationNone,
RATreeViewRowAnimationMiddle,
RATreeViewRowAnimationAutomatic = UITableViewRowAnimationAutomatic
} RATreeViewRowAnimation;
/**
* The data source of the RATreeView object must conform to RATreeVIewDataSource protocol. It is implemented by an object with metdiates the application's data model for RATreeView object.
*/
@protocol RATreeViewDataSource <NSObject>
///------------------------------------------------
/// Configuring a Tree View
///------------------------------------------------
/**
* Ask the data source to return the number of child items encompassed by a given item. (required)
*
* @param treeView The tree-view that sent the message.
* @param item An item identifying a cell in tree view.
* @param treeNodeInfo Object including additional information about item.
*
* @return The number of child items encompassed by item. If item is nil, this method should return the number of children for the top-level item.
*/
- (NSInteger)treeView:(RATreeView *)treeView numberOfChildrenOfItem:(nullable id)item;
/**
* Asks the data source for a cell to insert for a specified item. (required)
*
* @param treeView A tree-view object requesting the cell.
* @param item An item identifying a cell in tree view.
*
* @return An object inheriting from UITableViewCell that the tree view can use for the specified row. An assertion is raised if you return nil.
*/
- (UITableViewCell *)treeView:(RATreeView *)treeView cellForItem:(nullable id)item;
/**
* Ask the data source to return the child item at the specified index of a given item. (required)
*
* @param treeView The tree-view object requesting child of the item at the specified index.
* @param index The index of the child item from item to return.
* @param item An item identifying a cell in tree view.
*
* @return The child item at index of a item. If item is nil, returns the appropriate child item of the root object.
*/
- (id)treeView:(RATreeView *)treeView child:(NSInteger)index ofItem:(nullable id)item;
@optional
///------------------------------------------------
/// Inserting or Deleting Tree Rows
///------------------------------------------------
/**
* Asks the data source to commit the insertion or deletion of a row for specified item in the receiver.
*
* @param treeView The tree-view object requesting the insertion or deletion.
* @param editingStyle The cell editing style corresponding to a insertion or deletion requested for the row specified by item. Possible editing styles are `UITableViewCellEditingStyleInsert` or `UITableViewCellEditingStyleDelete`.
* @param item An item identifying a cell in tree view.
* @param treeNodeInfo Object including additional information about item.
*/
- (void)treeView:(RATreeView *)treeView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowForItem:(id)item;
/**
* Asks the data source to verify that row for given item is editable.
*
* @param treeView The tree-view object requesting this information.
* @param item An item identifying a cell in tree view.
*
* @return `YES` if the row indicated by indexPath is editable; otherwise, `NO`.
*/
- (BOOL)treeView:(RATreeView *)treeView canEditRowForItem:(id)item;
@end
/**
* The delegate of a RATreeView object must adopt the RATreeViewDelegate protocol. Optional methods of the protocol allow the delegate to manage selections, help to delete and reorder cells, and perform other actions.
*/
@protocol RATreeViewDelegate <NSObject>
@optional
///------------------------------------------------
/// Configuring Rows for the Tree View
///------------------------------------------------
/**
* Asks the delegate for the height to use for a row for a specified item.
*
* @param treeView The tree-view object requesting this information.
* @param item An item identifying a cell in tree view.
*
* @return A nonnegative floating-point value that specifies the height (in points) that row should be.
*/
- (CGFloat)treeView:(RATreeView *)treeView heightForRowForItem:(id)item;
/**
* Asks the delegate for the estimated height of a row for a specified item.
*
* @param treeView The tree-view object requesting this information.
* @param item An item identifying a cell in tree view.
*
* @return A nonnegative floating-point value that specifies the height (in points) of the header for section.
*/
- (CGFloat)treeView:(RATreeView *)treeView estimatedHeightForRowForItem:(id)item NS_AVAILABLE_IOS(7_0);
/**
* Asks the delegate to return the level of indentation for a row for a specified item.
*
* @param treeView The tree-view object requesting this information.
* @param item An item identifying a cell in tree view.
*
* @return Returns the depth of the specified row to show its hierarchical position.
*/
- (NSInteger)treeView:(RATreeView *)treeView indentationLevelForRowForItem:(id)item;
/**
* Tells the delegate the tree view is about to draw a cell for a particular item.
*
* @param treeView The tree-view object informing the delegate of this impending event.
* @param cell A table-view cell object that tableView is going to use when drawing the row.
* @param item An item identifying a cell in tree view.
*/
- (void)treeView:(RATreeView *)treeView willDisplayCell:(UITableViewCell *)cell forItem:(id)item;
///------------------------------------------------
/// @name Managing Accessory Views
///------------------------------------------------
/**
* Tells the delegate that the user tapped the accessory (disclosure) view associated with a row for a given item.
*
* @param treeView The tree-view object informing the delegate of this event.
* @param item An item identifying a cell in tree view.
*/
- (void)treeView:(RATreeView *)treeView accessoryButtonTappedForRowForItem:(id)item;
///------------------------------------------------
/// @name Expanding and Collapsing Tree View rows
///------------------------------------------------
/**
* Asks delegate whether a row for a specified item should be expanded.
*
* @param treeView The tree-view object requesting this information.
* @param item An item identifying a row in tree view.
*
* @return YES if the background of the row should be expanded, otherwise NO.
* @discussion If the delegate does not implement this method, the default is YES.
*/
- (BOOL)treeView:(RATreeView *)treeView shouldExpandRowForItem:(id)item;
/**
* Asks delegate whether a row for a specified item should be collapsed.
*
* @param treeView The tree-view object requesting this information.
* @param item An item identifying a row in tree view.
*
* @return YES if the background of the row should be expanded, otherwise NO.
* @discussion If the delegate does not implement this method, the default is YES.
*/
- (BOOL)treeView:(RATreeView *)treeView shouldCollapaseRowForItem:(id)item;
/**
* Tells the delegate that a row for a specified item is about to be expanded.
*
* @param treeView A tree-view object informing the delegate about the impending expansion.
* @param item An item identifying a row in tree view.
*/
- (void)treeView:(RATreeView *)treeView willExpandRowForItem:(id)item;
/**
* Tells the delegate that a row for a specified item is about to be collapsed.
*
* @param treeView A tree-view object informing the delegate about the impending collapse.
* @param item An item identifying a row in tree view.
*/
- (void)treeView:(RATreeView *)treeView willCollapseRowForItem:(id)item;
/**
* Tells the delegate that the row for a specified item is now expanded.
*
* @param treeView A tree-view object informing the delegate that new row is expanded.
* @param item An item identifying a row in tree view.
*/
- (void)treeView:(RATreeView *)treeView didExpandRowForItem:(id)item;
/**
* Tells the delegate that the row for a specified item is now collapsed.
*
* @param treeView A tree-view object informing the delegate that new row is collapsed.
* @param item An item identifying a row in tree view.
*/
- (void)treeView:(RATreeView *)treeView didCollapseRowForItem:(id)item;
///------------------------------------------------
/// @name Managing Selections
///------------------------------------------------
/**
* Tells the delegate that a row for a specified item is about to be selected.
*
* @param treeView A tree-view object informing the delegate about the impending selection.
* @param item An item identifying a row in tree view.
*
* @return An id object that confirms or alters the selected row. Return an id object other than item if you want another cell to be selected. Return nil if you don't want the row selected.
*/
- (id)treeView:(RATreeView *)treeView willSelectRowForItem:(id)item;
/**
* Tells the delegate that the row for a specified item is now selected.
*
* @param treeView A tree-view object informing the delegate about the new row selection.
* @param item An item identifying a row in tree view.
*/
- (void)treeView:(RATreeView *)treeView didSelectRowForItem:(id)item;
/**
* Tells the delegate that a row for a specified item is about to be deselected.
*
* @param treeView A tree-view object informing the delegate about the impending deselection.
* @param item An item identifying a row in tree view.
*
* @return An id object that confirms or alters the deselected row. Return an id object other than item if you want another cell to be deselected. Return nil if you dont want the row deselected.
*/
- (id)treeView:(RATreeView *)treeView willDeselectRowForItem:(id)item;
/**
* Tells the delegate that the row for a specified item is now deselected.
*
* @param treeView A tree-view object informing the delegate about the row deselection.
* @param item An item identifying a row in tree view.
*/
- (void)treeView:(RATreeView *)treeView didDeselectRowForItem:(id)item;
///------------------------------------------------
/// @name Editing Tree Rows
///------------------------------------------------
/**
* Tells the delegate that the tree view is about to go into editing mode.
*
* @param treeView The tree-view object providing this information.
* @param item An item identifying a row in tree view.
*/
- (void)treeView:(RATreeView *)treeView willBeginEditingRowForItem:(id)item;
/**
* Tells the delegate that the tree view has left editing mode.
*
* @param treeView The tree-view object providing this information.
* @param item AAn item identifying a row in tree view.
*/
- (void)treeView:(RATreeView *)treeView didEndEditingRowForItem:(id)item;
/**
* Asks the delegate for the editing style of a row for a specified item.
*
* @param treeView The tree-view object requesting this information.
* @param item An item identifying a row in tree view.
*
* @return The editing style of the cell for the row identified by item.
* @discussion This method allows the delegate to customize the editing style of the cell for specified item. If the delegate does not implement this method and the UITableViewCell object is editable (that is, it has its editing property set to YES), the cell has the UITableViewCellEditingStyleDelete style set for it.
*/
- (UITableViewCellEditingStyle)treeView:(RATreeView *)treeView editingStyleForRowForItem:(id)item;
/**
* Changes the default title of the delete-confirmation button.
*
* @param treeView The tree-view object requesting this information.
* @param item An item identifying a row in tree view.
*
* @return A localized string to used as the title of the delete-confirmation button.
* @discussion By default, the delete-confirmation button, which appears on the right side of the cell, has the title of Delete. The tree view displays this button when the user attempts to delete a row, either by swiping the row or tapping the red minus icon in editing mode. You can implement this method to return an alternative title, which should be localized. Default title string ("Delete") isn't localized.
*/
- (NSString *)treeView:(RATreeView *)treeView titleForDeleteConfirmationButtonForRowForItem:(id)item;
/**
* Asks the delegate whether the background of the row for a specified item should be indented while the tree view is in editing mode.
*
* @param treeView The tree-view object requesting this information.
* @param item An item identifying a row in tree view.
*
* @return YES if the background of the row should be indented, otherwise NO.
* @discussion If the delegate does not implement this method, the default is YES.
*/
- (BOOL)treeView:(RATreeView *)treeView shouldIndentWhileEditingRowForItem:(id)item;
/**
* Asks the data source for the edit actions for an item. This is an iOS 8 only method.
*
* @praram treeView The tree-view object requesting this information.
* @param item An item identifying a cell in the tree view.
*
* @return An NSArray of `UITableViewRowAction` objects to show for editing.
*/
- (NSArray *)treeView:(RATreeView *)treeView editActionsForItem:(id)item;
///------------------------------------------------
/// @name Tracking the Removal of Views
///------------------------------------------------
/**
* Tells the delegate that the cell for a specified item was removed from the tree.
*
* @param treeView The tree-view object that removed the view.
* @param cell The cell that was removed.
* @param item An item identifying a cell in tree view.
*/
- (void)treeView:(RATreeView *)treeView didEndDisplayingCell:(UITableViewCell *)cell forItem:(id)item;
///------------------------------------------------
/// @name Copying and Pasting Row Content
///------------------------------------------------
/**
* Asks the delegate if the editing menu should be shown for a row for a specified item.
*
* @param treeView The tree-view object that is making this request.
* @param item An item identifying a row in tree view.
*
* @return YES if the editing menu should be shown positioned near the row and pointing to it, otherwise NO. The default value is NO.
*/
- (BOOL)treeView:(RATreeView *)treeView shouldShowMenuForRowForItem:(id)item;
/**
* Asks the delegate if the editing menu should omit the Copy or Paste command for a row for a specified item.
*
* @param treeView The tree-view object that is making this request.
* @param action A selector type identifying the copy: or paste: method of the UIResponderStandardEditActions informal protocol.
* @param item An item identifying a row in tree view.
* @param sender The object that initially sent the copy: or paste: message.
*
* @return YES if the command corresponding to action should appear in the editing menu, otherwise NO. The default value is NO.
*/
- (BOOL)treeView:(RATreeView *)treeView canPerformAction:(SEL)action forRowForItem:(id)item withSender:(id)sender;
/**
* Tells the delegate to perform a copy or paste operation on the content of a row for a specified item.
*
* @param treeView The tree-view object that is making this request.
* @param action A selector type identifying the copy: or paste: method of the UIResponderStandardEditActions informal protocol.
* @param item An item identifying a row in tree view.
* @param sender The object that initially sent the copy: or paste: message.
* @discussion The tree view invokes this method for a given action if the user taps Copy or Paste in the editing menu.
*/
- (void)treeView:(RATreeView *)treeView performAction:(SEL)action forRowForItem:(id)item withSender:(id)sender;
///------------------------------------------------
/// @name Managing Tree View Highlighting
///------------------------------------------------
/**
* Asks the delegate if the row for a specified item should be highlighted.
*
* @param treeView The tree-view object that is making this request.
* @param treeNodeInfo Object including additional information about item.
*
* @return YES if the row should be highlighted or NO if it should not.
*/
- (BOOL)treeView:(RATreeView *)treeView shouldHighlightRowForItem:(id)item;
/**
* Tells the delegate that the row for a specified item was highlighted.
*
* @param treeView The tree-view object that highlighted the cell.
* @param item An item identifying a row in tree view.
*/
- (void)treeView:(RATreeView *)treeView didHighlightRowForItem:(id)item;
/**
* Tells the delegate that the highlight was removed from the row for a specified item.
*
* @param treeView The tree-view object that removed the highlight from the cell.
* @param item An item identifying a row in tree view.
*/
- (void)treeView:(RATreeView *)treeView didUnhighlightRowForItem:(id)item;
@end
@interface RATreeView : UIView
///------------------------------------------------
/// @name Initializing a RATreeView Object
///------------------------------------------------
- (id)initWithFrame:(CGRect)frame style:(RATreeViewStyle)style;
///------------------------------------------------
/// @name Managing the Delegate and the Data Source
///------------------------------------------------
@property (nonatomic, nullable, weak) id<RATreeViewDataSource> dataSource;
@property (nonatomic, nullable, weak) id<RATreeViewDelegate> delegate;
///------------------------------------------------
/// @name Configuring the Tree View
///------------------------------------------------
- (NSInteger)numberOfRows;
@property (nonatomic, readonly) RATreeViewStyle style;
@property (nonatomic) RATreeViewCellSeparatorStyle separatorStyle;
@property (nonatomic, nullable, strong) UIColor *separatorColor;
@property (nonatomic) CGFloat rowHeight;
@property (nonatomic) CGFloat estimatedRowHeight NS_AVAILABLE_IOS(7_0);
@property (nonatomic) UIEdgeInsets separatorInset NS_AVAILABLE_IOS(7_0);
@property (nonatomic, nullable, copy) UIVisualEffect *separatorEffect NS_AVAILABLE_IOS(8_0) UI_APPEARANCE_SELECTOR;
@property (nonatomic) BOOL cellLayoutMarginsFollowReadableWidth NS_AVAILABLE_IOS(9_0);
@property (nonatomic, nullable, strong) UIView *backgroundView;
///------------------------------------------------
/// @name Expanding and Collapsing Rows
///------------------------------------------------
- (void)expandRowForItem:(nullable id)item expandChildren:(BOOL)expandChildren withRowAnimation:(RATreeViewRowAnimation)animation;
- (void)expandRowForItem:(nullable id)item withRowAnimation:(RATreeViewRowAnimation)animation;
- (void)expandRowForItem:(nullable id)item;
- (void)collapseRowForItem:(nullable id)item collapseChildren:(BOOL)collapseChildren withRowAnimation:(RATreeViewRowAnimation)animation;
- (void)collapseRowForItem:(nullable id)item withRowAnimation:(RATreeViewRowAnimation)animation;
- (void)collapseRowForItem:(nullable id)item;
@property (nonatomic) BOOL expandsChildRowsWhenRowExpands;
@property (nonatomic) BOOL collapsesChildRowsWhenRowCollapses;
@property (nonatomic) RATreeViewRowAnimation rowsExpandingAnimation;
@property (nonatomic) RATreeViewRowAnimation rowsCollapsingAnimation;
///------------------------------------------------
/// @name Inserting, Deleting, and Moving Rows
///------------------------------------------------
- (void)beginUpdates;
- (void)endUpdates;
- (void)insertItemsAtIndexes:(NSIndexSet *)indexes inParent:(nullable id)parent withAnimation:(RATreeViewRowAnimation)animation;
- (void)moveItemAtIndex:(NSInteger)oldIndex inParent:(nullable id)oldParent toIndex:(NSInteger)newIndex inParent:(nullable id)newParent;
- (void)deleteItemsAtIndexes:(NSIndexSet *)indexes inParent:(nullable id)parent withAnimation:(RATreeViewRowAnimation)animation;
///------------------------------------------------
/// @name Creating Tree View Cells
///------------------------------------------------
- (void)registerClass:(nullable Class)cellClass forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
- (void)registerNib:(nullable UINib *)nib forCellReuseIdentifier:(NSString *)identifier;
- (nullable id)dequeueReusableCellWithIdentifier:(NSString *)identifier;
///------------------------------------------------
/// @name Accessing Header and Footer Views
///------------------------------------------------
- (void)registerNib:(UINib *)nib forHeaderFooterViewReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
- (void)registerClass:(Class)aClass forHeaderFooterViewReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
- (id)dequeueReusableHeaderFooterViewWithIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
@property (nonatomic, nullable, strong) UIView *treeHeaderView;
@property (nonatomic, nullable, strong) UIView *treeFooterView;
///------------------------------------------------
/// @name Working with Expandability
///------------------------------------------------
- (BOOL)isCellForItemExpanded:(id)item;
- (BOOL)isCellExpanded:(UITableViewCell *)cell;
///------------------------------------------------
/// @name Working with Indentation
///------------------------------------------------
- (NSInteger)levelForCellForItem:(id)item;
- (NSInteger)levelForCell:(UITableViewCell *)cell;
///------------------------------------------------
/// @name Getting the Parent for an Item
///------------------------------------------------
- (nullable id)parentForItem:(id)parent;
///------------------------------------------------
/// @name Accessing Cells
///------------------------------------------------
- (nullable UITableViewCell *)cellForItem:(id)item;
- (nullable NSArray *)visibleCells;
- (nullable id)itemForCell:(UITableViewCell *)cell;
- (nullable id)itemForRowAtPoint:(CGPoint)point;
- (nullable id)itemsForRowsInRect:(CGRect)rect;
@property (nonatomic, nullable, copy, readonly) NSArray *itemsForVisibleRows;
///------------------------------------------------
/// @name Scrolling the TreeView
///------------------------------------------------
- (void)scrollToRowForItem:(id)item atScrollPosition:(RATreeViewScrollPosition)scrollPosition animated:(BOOL)animated;
- (void)scrollToNearestSelectedRowAtScrollPosition:(RATreeViewScrollPosition)scrollPosition animated:(BOOL)animated;
///------------------------------------------------
/// @name Managing Selections
///------------------------------------------------
- (nullable id)itemForSelectedRow;
- (nullable NSArray *)itemsForSelectedRows;
- (void)selectRowForItem:(nullable id)item animated:(BOOL)animated scrollPosition:(RATreeViewScrollPosition)scrollPosition;
- (void)deselectRowForItem:(id)item animated:(BOOL)animated;
@property (nonatomic) BOOL allowsSelection;
@property (nonatomic) BOOL allowsMultipleSelection;
@property (nonatomic) BOOL allowsSelectionDuringEditing;
@property (nonatomic) BOOL allowsMultipleSelectionDuringEditing;
///------------------------------------------------
/// @name Managing the Editing of Tree Cells
///------------------------------------------------
- (void)setEditing:(BOOL)editing animated:(BOOL)animated;
@property (nonatomic, getter = isEditing) BOOL editing;
///------------------------------------------------
/// @name Reloading the Tree View
///------------------------------------------------
- (void)reloadData;
- (void)reloadRowsForItems:(NSArray *)items withRowAnimation:(RATreeViewRowAnimation)animation;
- (void)reloadRows;
///------------------------------------------------
/// UIScrollView Staff
///------------------------------------------------
@property (nonatomic, strong, readonly) UIScrollView *scrollView;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,619 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "RATreeView.h"
#import "RATreeView_ClassExtension.h"
#import "RATreeView+Enums.h"
#import "RATreeView+Private.h"
#import "RABatchChanges.h"
#import "RATreeNodeCollectionController.h"
#import "RATreeNode.h"
#import "RATableView.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wincomplete-implementation"
@implementation RATreeView
#pragma clang diagnostic pop
#pragma mark Initializing a TreeView Object
- (id)init
{
return [self initWithFrame:CGRectMake(0, 0, 100, 100) style:RATreeViewStylePlain];
}
- (id)initWithFrame:(CGRect)frame
{
return [self initWithFrame:frame style:RATreeViewStylePlain];
}
- (id)initWithFrame:(CGRect)frame style:(RATreeViewStyle)style
{
self = [super initWithFrame:frame];
if (self) {
CGRect innerFrame = CGRectMake(0, 0, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds));
[self commonInitWithFrame:innerFrame style:style];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
CGRect innerFrame = CGRectMake(0, 0, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds));
[self commonInitWithFrame:innerFrame style:RATreeViewStylePlain];
}
return self;
}
- (void)commonInitWithFrame:(CGRect)frame style:(RATreeViewStyle)style
{
UITableViewStyle tableViewStyle = [RATreeView tableViewStyleForTreeViewStyle:style];
RATableView *tableView = [[RATableView alloc] initWithFrame:frame style:tableViewStyle];
tableView.tableViewDelegate = (id<UITableViewDelegate>)self;
tableView.dataSource = (id<UITableViewDataSource>)self;
tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
tableView.backgroundColor = [UIColor clearColor];
[self addSubview:tableView];
[self setTableView:tableView];
self.expandsChildRowsWhenRowExpands = NO;
self.collapsesChildRowsWhenRowCollapses = NO;
self.rowsExpandingAnimation = RATreeViewRowAnimationTop;
self.rowsCollapsingAnimation = RATreeViewRowAnimationBottom;
}
- (void)awakeFromNib
{
[super awakeFromNib];
self.tableView.backgroundColor = [UIColor clearColor];
}
#pragma mark Scroll View
- (UIScrollView *)scrollView
{
return self.tableView;
}
#pragma mark Configuring a Tree View
- (NSInteger)numberOfRows
{
return [self.tableView numberOfRowsInSection:0];
}
- (RATreeViewStyle)style
{
UITableViewStyle tableViewStyle = self.tableView.style;
return [RATreeView treeViewStyleForTableViewStyle:tableViewStyle];
}
#if TARGET_OS_IOS
- (RATreeViewCellSeparatorStyle)separatorStyle
{
RATreeViewCellSeparatorStyle style = [RATreeView treeViewCellSeparatorStyleForTableViewSeparatorStyle:self.tableView.separatorStyle];
return style;
}
- (void)setSeparatorStyle:(RATreeViewCellSeparatorStyle)separatorStyle
{
UITableViewCellSeparatorStyle tableViewSeparatorStyle = [RATreeView tableViewCellSeparatorStyleForTreeViewCellSeparatorStyle:separatorStyle];
self.tableView.separatorStyle = tableViewSeparatorStyle;
}
- (UIColor *)separatorColor
{
return self.tableView.separatorColor;
}
- (void)setSeparatorColor:(UIColor *)separatorColor
{
self.tableView.separatorColor = separatorColor;
}
#endif
- (CGFloat)rowHeight
{
return self.tableView.rowHeight;
}
- (void)setRowHeight:(CGFloat)rowHeight
{
self.tableView.rowHeight = rowHeight;
}
- (CGFloat)estimatedRowHeight
{
if ([self.tableView respondsToSelector:@selector(estimatedRowHeight)]) {
return self.tableView.estimatedRowHeight;
} else {
return 0;
}
}
- (void)setEstimatedRowHeight:(CGFloat)estimatedRowHeight
{
if ([self.tableView respondsToSelector:@selector(estimatedRowHeight)]) {
self.tableView.estimatedRowHeight = estimatedRowHeight;
}
}
- (UIEdgeInsets)separatorInset
{
if ([self.tableView respondsToSelector:@selector(separatorInset)]) {
return self.tableView.separatorInset;
} else {
return UIEdgeInsetsZero;
}
}
- (void)setSeparatorInset:(UIEdgeInsets)separatorInset
{
if ([self.tableView respondsToSelector:@selector(separatorInset)]) {
self.tableView.separatorInset = separatorInset;
}
}
#if TARGET_OS_IOS
- (UIVisualEffect *)separatorEffect
{
if ([self.tableView respondsToSelector:@selector(separatorEffect)]) {
return self.tableView.separatorEffect;
} else {
return nil;
}
}
- (void)setSeparatorEffect:(UIVisualEffect *)separatorEffect
{
if ([self.tableView respondsToSelector:@selector(separatorEffect)]) {
self.tableView.separatorEffect = separatorEffect;
}
}
#endif
- (BOOL)cellLayoutMarginsFollowReadableWidth
{
if ([self.tableView respondsToSelector:@selector(cellLayoutMarginsFollowReadableWidth)]) {
return self.tableView.cellLayoutMarginsFollowReadableWidth;
} else {
return NO;
}
}
- (void)setCellLayoutMarginsFollowReadableWidth:(BOOL)cellLayoutMarginsFollowReadableWidth
{
if ([self.tableView respondsToSelector:@selector(cellLayoutMarginsFollowReadableWidth)]) {
self.tableView.cellLayoutMarginsFollowReadableWidth = cellLayoutMarginsFollowReadableWidth;
}
}
- (UIView *)backgroundView
{
return self.tableView.backgroundView;
}
- (void)setBackgroundView:(UIView *)backgroundView
{
self.tableView.backgroundView = backgroundView;
}
#pragma mark Expanding and Collapsing Rows
- (void)expandRowForItem:(id)item
{
[self expandRowForItem:item withRowAnimation:self.rowsExpandingAnimation];
}
- (void)expandRowForItem:(id)item withRowAnimation:(RATreeViewRowAnimation)animation
{
[self expandRowForItem:item expandChildren:NO withRowAnimation:animation];
}
- (void)expandRowForItem:(id)item expandChildren:(BOOL)expandChildren withRowAnimation:(RATreeViewRowAnimation)animation
{
NSIndexPath *indexPath = [self indexPathForItem:item];
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
if (!treeNode || treeNode.expanded) {
return;
}
[self expandCellForTreeNode:treeNode expandChildren:expandChildren withRowAnimation:animation];
}
- (void)collapseRowForItem:(id)item
{
[self collapseRowForItem:item withRowAnimation:self.rowsCollapsingAnimation];
}
- (void)collapseRowForItem:(id)item withRowAnimation:(RATreeViewRowAnimation)animation
{
[self collapseRowForItem:item collapseChildren:NO withRowAnimation:animation];
}
- (void)collapseRowForItem:(id)item collapseChildren:(BOOL)collapseChildren withRowAnimation:(RATreeViewRowAnimation)animation
{
NSIndexPath *indexPath = [self indexPathForItem:item];
RATreeNode *treeNode = [self treeNodeForIndexPath:indexPath];
if (!treeNode) {
return;
}
[self collapseCellForTreeNode:treeNode collapseChildren:collapseChildren withRowAnimation:animation];
}
#pragma mark - Changing tree's structure
- (void)beginUpdates
{
[self.tableView beginUpdates];
[self.batchChanges beginUpdates];
}
- (void)endUpdates
{
[self.batchChanges endUpdates];
[self.tableView endUpdates];
}
- (void)insertItemsAtIndexes:(NSIndexSet *)indexes inParent:(id)parent withAnimation:(RATreeViewRowAnimation)animation
{
if (parent && ![self isCellForItemExpanded:parent]) {
return;
}
__weak __typeof(self) weakSelf = self;
[indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
[weakSelf insertItemAtIndex:idx inParent:parent withAnimation:animation];
}];
}
- (void)deleteItemsAtIndexes:(NSIndexSet *)indexes inParent:(id)parent withAnimation:(RATreeViewRowAnimation)animation
{
if (parent && ![self isCellForItemExpanded:parent]) {
return;
}
__weak __typeof(self) weakSelf = self;
[indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
[weakSelf removeItemAtIndex:idx inParent:parent withAnimation:animation];
}];
}
#pragma mark - Creating Table View Cells
- (void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier
{
[self.tableView registerNib:nib forCellReuseIdentifier:identifier];
}
- (void)registerClass:(Class)cellClass forCellReuseIdentifier:(NSString *)identifier
{
[self.tableView registerClass:cellClass forCellReuseIdentifier:identifier];
}
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier
{
return [self.tableView dequeueReusableCellWithIdentifier:identifier];
}
#pragma mark - Accessing Header and Footer Views
- (void)registerNib:(UINib *)nib forHeaderFooterViewReuseIdentifier:(NSString *)identifier
{
[self.tableView registerNib:nib forHeaderFooterViewReuseIdentifier:identifier];
}
- (void)registerClass:(Class)aClass forHeaderFooterViewReuseIdentifier:(NSString *)identifier
{
[self.tableView registerClass:aClass forHeaderFooterViewReuseIdentifier:identifier];
}
- (id)dequeueReusableHeaderFooterViewWithIdentifier:(NSString *)identifier
{
return [self.tableView dequeueReusableHeaderFooterViewWithIdentifier:identifier];
}
- (UIView *)treeHeaderView
{
return self.tableView.tableHeaderView;
}
- (void)setTreeHeaderView:(UIView *)treeHeaderView
{
self.tableView.tableHeaderView = treeHeaderView;
}
- (UIView *)treeFooterView
{
return self.tableView.tableFooterView;
}
- (void)setTreeFooterView:(UIView *)treeFooterView
{
self.tableView.tableFooterView = treeFooterView;
}
#pragma mark - Working with Expandability
- (BOOL)isCellForItemExpanded:(id)item
{
NSIndexPath *indexPath = [self indexPathForItem:item];
return [self treeNodeForIndexPath:indexPath].expanded;
}
- (BOOL)isCellExpanded:(UITableViewCell *)cell
{
id item = [self itemForCell:cell];
return [self isCellForItemExpanded:item];
}
#pragma mark - Working with Indentation
- (NSInteger)levelForCellForItem:(id)item
{
return [self.treeNodeCollectionController levelForItem:item];
}
- (NSInteger)levelForCell:(UITableViewCell *)cell
{
id item = [self itemForCell:cell];
return [self levelForCellForItem:item];
}
#pragma mark - Getting the Parent for an Item
- (id)parentForItem:(id)item
{
return [self.treeNodeCollectionController parentForItem:item];
}
#pragma mark - Accessing Cells
- (UITableViewCell *)cellForItem:(id)item
{
NSIndexPath *indexPath = [self indexPathForItem:item];
return [self.tableView cellForRowAtIndexPath:indexPath];
}
- (NSArray *)visibleCells
{
return [self.tableView visibleCells];
}
- (id)itemForCell:(UITableViewCell *)cell
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
return [self treeNodeForIndexPath:indexPath].item;
}
- (id)itemForRowAtPoint:(CGPoint)point
{
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:point];
return !indexPath ? nil : [self treeNodeForIndexPath:indexPath].item;
}
- (id)itemsForRowsInRect:(CGRect)rect
{
NSArray *indexPaths = [self.tableView indexPathsForRowsInRect:rect];
return [self itemsForIndexPaths:indexPaths];
}
- (NSArray *)itemsForVisibleRows
{
NSArray *indexPaths = [self.tableView indexPathsForVisibleRows];
return [self itemsForIndexPaths:indexPaths];
}
#pragma mark - Scrolling the TreeView
- (void)scrollToRowForItem:(id)item atScrollPosition:(RATreeViewScrollPosition)scrollPosition animated:(BOOL)animated
{
NSIndexPath *indexPath = [self indexPathForItem:item];
UITableViewScrollPosition tableViewScrollPosition = [RATreeView tableViewScrollPositionForTreeViewScrollPosition:scrollPosition];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:tableViewScrollPosition animated:animated];
}
- (void)scrollToNearestSelectedRowAtScrollPosition:(RATreeViewScrollPosition)scrollPosition animated:(BOOL)animated
{
UITableViewScrollPosition tableViewScrollPosition = [RATreeView tableViewScrollPositionForTreeViewScrollPosition:scrollPosition];
[self.tableView scrollToNearestSelectedRowAtScrollPosition:tableViewScrollPosition animated:animated];
}
#pragma mark - Managing Selections
- (id)itemForSelectedRow
{
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
return [self treeNodeForIndexPath:indexPath].item;
}
- (NSArray *)itemsForSelectedRows
{
NSArray *selectedRows = [self.tableView indexPathsForSelectedRows];
return [self itemsForIndexPaths:selectedRows];
}
- (void)selectRowForItem:(id)item animated:(BOOL)animated scrollPosition:(RATreeViewScrollPosition)scrollPosition
{
if ([self isCellForItemExpanded:[self parentForItem:item]]) {
NSIndexPath *indexPath = [self indexPathForItem:item];
UITableViewScrollPosition tableViewScrollPosition = [RATreeView tableViewScrollPositionForTreeViewScrollPosition:scrollPosition];
[self.tableView selectRowAtIndexPath:indexPath animated:animated scrollPosition:tableViewScrollPosition];
}
}
- (void)deselectRowForItem:(id)item animated:(BOOL)animated
{
if ([self isCellForItemExpanded:[self parentForItem:item]]) {
NSIndexPath *indexPath = [self indexPathForItem:item];
[self.tableView deselectRowAtIndexPath:indexPath animated:animated];
}
}
- (BOOL)allowsSelection
{
return self.tableView.allowsSelection;
}
- (void)setAllowsSelection:(BOOL)allowsSelection
{
self.tableView.allowsSelection = allowsSelection;
}
- (BOOL)allowsMultipleSelection
{
return self.tableView.allowsMultipleSelection;
}
- (void)setAllowsMultipleSelection:(BOOL)allowsMultipleSelection
{
self.tableView.allowsMultipleSelection = allowsMultipleSelection;
}
- (BOOL)allowsSelectionDuringEditing
{
return self.tableView.allowsSelectionDuringEditing;
}
- (void)setAllowsSelectionDuringEditing:(BOOL)allowsSelectionDuringEditing
{
self.tableView.allowsSelectionDuringEditing = allowsSelectionDuringEditing;
}
- (BOOL)allowsMultipleSelectionDuringEditing
{
return self.tableView.allowsMultipleSelectionDuringEditing;
}
- (void)setAllowsMultipleSelectionDuringEditing:(BOOL)allowsMultipleSelectionDuringEditing
{
self.tableView.allowsMultipleSelectionDuringEditing = allowsMultipleSelectionDuringEditing;
}
#pragma mark - Managing the Editing of Tree Cells
- (BOOL)isEditing
{
return self.tableView.isEditing;
}
- (void)setEditing:(BOOL)editing
{
self.tableView.editing = editing;
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[self.tableView setEditing:editing animated:animated];
}
#pragma mark - Reloading the Tree View
- (void)reloadData
{
[self setupTreeStructure];
[self.tableView reloadData];
}
- (void)reloadRowsForItems:(NSArray *)items withRowAnimation:(RATreeViewRowAnimation)animation
{
NSMutableArray *indexes = [NSMutableArray array];
UITableViewRowAnimation tableViewRowAnimation = [RATreeView tableViewRowAnimationForTreeViewRowAnimation:animation];
for (id item in items) {
NSIndexPath *indexPath = [self indexPathForItem:item];
[indexes addObject:indexPath];
}
[self.tableView reloadRowsAtIndexPaths:indexes withRowAnimation:tableViewRowAnimation];
}
- (void)reloadRows
{
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:0];
[self.tableView reloadSections:indexSet withRowAnimation:UITableViewRowAnimationNone];
}
#pragma mark - UIScrollView's properties
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
{
[self.tableView setContentOffset:contentOffset animated:animated];
}
- (void)scrollRectToVisible:(CGRect)visible animated:(BOOL)animated
{
[self.tableView scrollRectToVisible:visible animated:animated];
}
- (void)setZoomScale:(CGFloat)zoomScale animated:(BOOL)animated
{
[self.tableView setZoomScale:zoomScale animated:animated];
}
- (void)flashScrollIndicators
{
[self.tableView flashScrollIndicators];
}
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
{
[self.tableView zoomToRect:rect animated:animated];
}
#pragma mark -
- (NSArray *)itemsForIndexPaths:(NSArray *)indexPaths
{
if (!indexPaths) {
return nil;
}
NSMutableArray *items = [NSMutableArray array];
for (NSIndexPath *indexPath in indexPaths) {
[items addObject:[self treeNodeForIndexPath:indexPath].item];
}
return [items copy];
}
@end

View File

@ -0,0 +1,33 @@
//The MIT License (MIT)
//
//Copyright (c) 2014 Rafał Augustyniak
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
//the Software, and to permit persons to whom the Software is furnished to do so,
//subject to the following conditions:
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
//FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
//COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
//IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "RATreeView.h"
@class RABatchChanges;
@interface RATreeView ()
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) RATreeNodeCollectionController *treeNodeCollectionController;
@property (nonatomic, strong) RABatchChanges *batchChanges;
@end