├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── Rakefile ├── SLExpandableTableView.podspec ├── SLExpandableTableView ├── SLExpandableTableView.h └── SLExpandableTableView.m ├── SLExpandableTableViewTests.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── SLExpandableTableViewTests.xccheckout ├── Screenshots ├── 1.png └── 2.png └── Tests ├── Podfile ├── Podfile.lock ├── SLExpandableTableViewTests.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── SLExpandableTableViewTests.xccheckout └── xcshareddata │ └── xcschemes │ └── SLExpandableTableViewTests.xcscheme ├── SLExpandableTableViewTests ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── SLAppDelegate.h ├── SLAppDelegate.m ├── SLExpandableTableViewController.h ├── SLExpandableTableViewController.m ├── SLExpandableTableViewTests-Info.plist ├── SLExpandableTableViewTests-Prefix.pch ├── en.lproj │ └── InfoPlist.strings └── main.m └── Tests ├── Tests-Info.plist ├── Tests-Prefix.pch ├── Tests.m └── en.lproj └── InfoPlist.strings /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -crlf -diff -merge 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # xcode noise 2 | *.mode1v3 3 | *.pbxuser 4 | *.perspective 5 | *.perspectivev3 6 | *.pyc 7 | *~.nib/ 8 | build/ 9 | xcuserdata/ 10 | xcuserdata/* 11 | *.xcuserdatad/ 12 | 13 | # Textmate - if you build your xcode projects with it 14 | *.tm_build_errors 15 | 16 | # old skool 17 | .svn 18 | 19 | # osx noise 20 | .DS_Store 21 | profile 22 | 23 | Pods/ 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | rvm: 1.9.3 3 | before_install: 4 | - brew update 5 | - gem update cocoapods xcpretty 6 | - pod repo add sparrow-labs https://github.com/Sparrow-Labs/Specs.git 7 | - cd Tests && rm Podfile.lock && pod install && cd $TRAVIS_BUILD_DIR 8 | script: rake test 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Oliver Letterer, Sparrow-Labs 2 | 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: 3 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 4 | 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. 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SLExpandableTableView 2 | 3 | [![CI Status](http://img.shields.io/travis/OliverLetterer/SLExpandableTableView.svg?style=flat)](https://travis-ci.org/OliverLetterer/SLExpandableTableView) 4 | [![Version](https://img.shields.io/cocoapods/v/SLExpandableTableView.svg?style=flat)](http://cocoadocs.org/docsets/SLExpandableTableView) 5 | [![License](https://img.shields.io/cocoapods/l/SLExpandableTableView.svg?style=flat)](http://cocoadocs.org/docsets/SLExpandableTableView) 6 | [![Platform](https://img.shields.io/cocoapods/p/SLExpandableTableView.svg?style=flat)](http://cocoadocs.org/docsets/SLExpandableTableView) 7 | 8 | SLExpandableTableView is a UITableView subclass that gives you easy access to expandable and collapsable sections by just implementing a few more delegate and dataSource protocols. 9 | 10 | ## How to use SLExpandableTableView 11 | 12 | * Installation 13 | 14 | ``` 15 | pod 'SLExpandableTableView' 16 | ``` 17 | 18 | * Load the SLExpandableTableView in a UITableViewController 19 | 20 | ```objective-c 21 | - (void)loadView 22 | { 23 | self.tableView = [[SLExpandableTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; 24 | } 25 | ``` 26 | 27 | * Implement the **SLExpandableTableViewDatasource** protocol 28 | 29 | ```objective-c 30 | - (BOOL)tableView:(SLExpandableTableView *)tableView canExpandSection:(NSInteger)section 31 | { 32 | // return YES, if the section should be expandable 33 | } 34 | 35 | - (BOOL)tableView:(SLExpandableTableView *)tableView needsToDownloadDataForExpandableSection:(NSInteger)section 36 | { 37 | // return YES, if you need to download data to expand this section. tableView will call tableView:downloadDataForExpandableSection: for this section 38 | } 39 | 40 | - (UITableViewCell *)tableView:(SLExpandableTableView *)tableView expandingCellForSection:(NSInteger)section 41 | { 42 | // this cell will be displayed at IndexPath with section: section and row 0 43 | } 44 | ``` 45 | 46 | * Implement the **SLExpandableTableViewDelegate** protocol 47 | 48 | ```objective-c 49 | - (void)tableView:(SLExpandableTableView *)tableView downloadDataForExpandableSection:(NSInteger)section 50 | { 51 | // download your data here 52 | // call [tableView expandSection:section animated:YES]; if download was successful 53 | // call [tableView cancelDownloadInSection:section]; if your download was NOT successful 54 | } 55 | 56 | - (void)tableView:(SLExpandableTableView *)tableView didExpandSection:(NSUInteger)section animated:(BOOL)animated 57 | { 58 | //... 59 | } 60 | 61 | - (void)tableView:(SLExpandableTableView *)tableView didCollapseSection:(NSUInteger)section animated:(BOOL)animated 62 | { 63 | //... 64 | } 65 | 66 | ``` 67 | 68 | ## Sample Screenshots 69 | 70 | 71 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | desc "Runs tests." 2 | task :test do 3 | exit system("xcodebuild -workspace SLExpandableTableViewTests.xcworkspace -scheme SLExpandableTableViewTests test -destination 'platform=iOS Simulator,name=iPhone Retina (4-inch)' | xcpretty -c; exit ${PIPESTATUS[0]}") 4 | end 5 | 6 | task :default => 'test' 7 | -------------------------------------------------------------------------------- /SLExpandableTableView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'SLExpandableTableView' 3 | spec.version = '1.3.1' 4 | spec.platform = :ios, '6.0' 5 | spec.license = 'MIT' 6 | spec.source = { :git => 'https://github.com/OliverLetterer/SLExpandableTableView.git', :tag => spec.version.to_s } 7 | spec.frameworks = 'Foundation', 'UIKit' 8 | spec.requires_arc = true 9 | spec.homepage = 'https://github.com/OliverLetterer/SLExpandableTableView' 10 | spec.summary = 'UITableView subclass with expandable sections.' 11 | spec.author = { 'Oliver Letterer' => 'oliver.letterer@gmail.com' } 12 | 13 | spec.source_files = 'SLExpandableTableView' 14 | end 15 | -------------------------------------------------------------------------------- /SLExpandableTableView/SLExpandableTableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SLExpandableTableView.h 3 | // iGithub 4 | // 5 | // Created by me on 11.04.11. 6 | // Copyright 2011 Home. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class SLExpandableTableView; 12 | 13 | typedef enum { 14 | UIExpansionStyleCollapsed = 0, 15 | UIExpansionStyleExpanded 16 | } UIExpansionStyle; 17 | 18 | @protocol UIExpandingTableViewCell 19 | 20 | @property (nonatomic, assign, getter = isLoading) BOOL loading; 21 | 22 | @property (nonatomic, readonly) UIExpansionStyle expansionStyle; 23 | - (void)setExpansionStyle:(UIExpansionStyle)style animated:(BOOL)animated; 24 | 25 | @end 26 | 27 | 28 | 29 | @protocol SLExpandableTableViewDatasource 30 | 31 | @required 32 | - (BOOL)tableView:(SLExpandableTableView *)tableView canExpandSection:(NSInteger)section; 33 | - (BOOL)tableView:(SLExpandableTableView *)tableView needsToDownloadDataForExpandableSection:(NSInteger)section; 34 | - (UITableViewCell *)tableView:(SLExpandableTableView *)tableView expandingCellForSection:(NSInteger)section; 35 | 36 | @end 37 | 38 | 39 | 40 | @protocol SLExpandableTableViewDelegate 41 | 42 | @required 43 | - (void)tableView:(SLExpandableTableView *)tableView downloadDataForExpandableSection:(NSInteger)section; 44 | 45 | @optional 46 | - (void)tableView:(SLExpandableTableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPathWhileAnimatingSection:(NSIndexPath *)indexPath; 47 | 48 | - (void)tableView:(SLExpandableTableView *)tableView willExpandSection:(NSUInteger)section animated:(BOOL)animated; 49 | - (void)tableView:(SLExpandableTableView *)tableView didExpandSection:(NSUInteger)section animated:(BOOL)animated; 50 | 51 | - (void)tableView:(SLExpandableTableView *)tableView willCollapseSection:(NSUInteger)section animated:(BOOL)animated; 52 | - (void)tableView:(SLExpandableTableView *)tableView didCollapseSection:(NSUInteger)section animated:(BOOL)animated; 53 | 54 | @end 55 | 56 | 57 | 58 | @interface SLExpandableTableView : UITableView { 59 | @private 60 | id __weak _myDelegate; 61 | id __weak _myDataSource; 62 | 63 | NSMutableDictionary *_expandableSectionsDictionary; // will store BOOLs for each section that is expandable 64 | NSMutableDictionary *_showingSectionsDictionary; // will store BOOLs for the sections state (nil: not expanded, 1: expanded) 65 | NSMutableDictionary *_downloadingSectionsDictionary; // will store BOOLs for the sections state (nil: not downloading, YES: downloading) 66 | NSMutableDictionary *_animatingSectionsDictionary; 67 | 68 | NSInteger _maximumRowCountToStillUseAnimationWhileExpanding; 69 | 70 | BOOL _onlyDisplayHeaderAndFooterViewIfTableViewIsNotEmpty; 71 | UIView *_storedTableHeaderView; 72 | UIView *_storedTableFooterView; 73 | } 74 | 75 | @property (nonatomic, weak) id delegate; 76 | 77 | // discussion 78 | // you wont receive any callbacks for row 0 in an expandable section anymore 79 | @property (nonatomic, weak) id dataSource; 80 | 81 | // discussion 82 | // never use tableView.delegate/ tableView.dataSource as a getter. setDataSource will set _myDataSource, etc. so use these getters instead 83 | @property (nonatomic, readonly, weak) id myDelegate; 84 | @property (nonatomic, readonly, weak) id myDataSource; 85 | 86 | @property (nonatomic, assign) NSInteger maximumRowCountToStillUseAnimationWhileExpanding; 87 | 88 | @property (nonatomic, assign) BOOL onlyDisplayHeaderAndFooterViewIfTableViewIsNotEmpty; 89 | 90 | @property (nonatomic, assign) UITableViewRowAnimation reloadAnimation; 91 | 92 | // call tableView:needsToDownloadDataForExpandableSection: to make sure we can expand the section, otherwise through exception 93 | - (void)expandSection:(NSInteger)section animated:(BOOL)animated; 94 | - (void)collapseSection:(NSInteger)section animated:(BOOL)animated; 95 | - (void)cancelDownloadInSection:(NSInteger)section; 96 | - (void)reloadDataAndResetExpansionStates:(BOOL)resetFlag; 97 | 98 | - (BOOL)canExpandSection:(NSUInteger)section; 99 | - (BOOL)isSectionExpanded:(NSInteger)section; 100 | 101 | @end 102 | -------------------------------------------------------------------------------- /SLExpandableTableView/SLExpandableTableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SLExpandableTableView.m 3 | // iGithub 4 | // 5 | // Created by me on 11.04.11. 6 | // Copyright 2011 Home. All rights reserved. 7 | // 8 | 9 | #import "SLExpandableTableView.h" 10 | #import 11 | #import 12 | 13 | static BOOL protocol_containsSelector(Protocol *protocol, SEL selector) 14 | { 15 | return protocol_getMethodDescription(protocol, selector, YES, YES).name != NULL || protocol_getMethodDescription(protocol, selector, NO, YES).name != NULL; 16 | } 17 | 18 | 19 | 20 | @interface SLExpandableTableView () 21 | 22 | @property (nonatomic, retain) NSMutableDictionary *expandableSectionsDictionary; 23 | @property (nonatomic, retain) NSMutableDictionary *showingSectionsDictionary; 24 | @property (nonatomic, retain) NSMutableDictionary *downloadingSectionsDictionary; 25 | @property (nonatomic, retain) NSMutableDictionary *animatingSectionsDictionary; 26 | 27 | @property (nonatomic, retain) UIView *storedTableHeaderView; 28 | @property (nonatomic, retain) UIView *storedTableFooterView; 29 | 30 | - (void)downloadDataInSection:(NSInteger)section; 31 | 32 | - (void)_resetExpansionStates; 33 | 34 | @end 35 | 36 | 37 | 38 | @implementation SLExpandableTableView 39 | 40 | #pragma mark - setters and getters 41 | 42 | - (id)delegate { 43 | return [super delegate]; 44 | } 45 | 46 | - (void)setDelegate:(id)delegate { 47 | _myDelegate = delegate; 48 | if (delegate) { 49 | //Set delegate to self only if original delegate is not nil 50 | [super setDelegate:self]; 51 | } else{ 52 | [super setDelegate:nil]; 53 | } 54 | } 55 | 56 | - (id)dataSource { 57 | return [super dataSource]; 58 | } 59 | 60 | - (void)setDataSource:(id)dataSource { 61 | _myDataSource = dataSource; 62 | [super setDataSource:self]; 63 | } 64 | 65 | - (void)setTableFooterView:(UIView *)tableFooterView { 66 | if (tableFooterView != _storedTableFooterView) { 67 | [super setTableFooterView:nil]; 68 | _storedTableFooterView = tableFooterView; 69 | [self reloadData]; 70 | } 71 | } 72 | 73 | - (void)setTableHeaderView:(UIView *)tableHeaderView { 74 | if (tableHeaderView != _storedTableHeaderView) { 75 | [super setTableHeaderView:nil]; 76 | _storedTableHeaderView = tableHeaderView; 77 | [self reloadData]; 78 | } 79 | } 80 | 81 | - (void)setOnlyDisplayHeaderAndFooterViewIfTableViewIsNotEmpty:(BOOL)onlyDisplayHeaderAndFooterViewIfTableViewIsNotEmpty { 82 | if (_onlyDisplayHeaderAndFooterViewIfTableViewIsNotEmpty != onlyDisplayHeaderAndFooterViewIfTableViewIsNotEmpty) { 83 | _onlyDisplayHeaderAndFooterViewIfTableViewIsNotEmpty = onlyDisplayHeaderAndFooterViewIfTableViewIsNotEmpty; 84 | [self reloadData]; 85 | } 86 | } 87 | 88 | #pragma mark - NSObject 89 | 90 | - (BOOL)respondsToSelector:(SEL)aSelector 91 | { 92 | if (protocol_containsSelector(@protocol(UITableViewDataSource), aSelector)) { 93 | return [super respondsToSelector:aSelector] || [_myDataSource respondsToSelector:aSelector]; 94 | } else if (protocol_containsSelector(@protocol(UITableViewDelegate), aSelector)) { 95 | return [super respondsToSelector:aSelector] || [_myDelegate respondsToSelector:aSelector]; 96 | } 97 | 98 | return [super respondsToSelector:aSelector]; 99 | } 100 | 101 | - (id)forwardingTargetForSelector:(SEL)aSelector 102 | { 103 | if (protocol_containsSelector(@protocol(UITableViewDataSource), aSelector)) { 104 | return _myDataSource; 105 | } else if (protocol_containsSelector(@protocol(UITableViewDelegate), aSelector)) { 106 | return _myDelegate; 107 | } 108 | 109 | return [super forwardingTargetForSelector:aSelector]; 110 | } 111 | 112 | #pragma mark - Initialization 113 | 114 | - (id)initWithFrame:(CGRect)frame style:(UITableViewStyle)style { 115 | if (self = [super initWithFrame:frame style:style]) { 116 | [self commonInit]; 117 | } 118 | return self; 119 | } 120 | 121 | - (id)initWithCoder:(NSCoder *)decoder { 122 | if (self = [super initWithCoder:decoder]) { 123 | [self commonInit]; 124 | } 125 | return self; 126 | } 127 | 128 | - (void)commonInit { 129 | self.maximumRowCountToStillUseAnimationWhileExpanding = NSIntegerMax; 130 | self.expandableSectionsDictionary = [NSMutableDictionary dictionary]; 131 | self.showingSectionsDictionary = [NSMutableDictionary dictionary]; 132 | self.downloadingSectionsDictionary = [NSMutableDictionary dictionary]; 133 | self.animatingSectionsDictionary = [NSMutableDictionary dictionary]; 134 | self.reloadAnimation = UITableViewRowAnimationFade; 135 | } 136 | 137 | - (void)awakeFromNib 138 | { 139 | [super awakeFromNib]; 140 | 141 | _storedTableHeaderView = self.tableHeaderView; 142 | _storedTableFooterView = self.tableFooterView; 143 | 144 | self.tableHeaderView = self.tableHeaderView; 145 | self.tableFooterView = self.tableFooterView; 146 | } 147 | 148 | #pragma mark - private methods 149 | 150 | - (void)_resetExpansionStates { 151 | [self.expandableSectionsDictionary removeAllObjects]; 152 | [self.showingSectionsDictionary removeAllObjects]; 153 | [self.downloadingSectionsDictionary removeAllObjects]; 154 | } 155 | 156 | - (void)downloadDataInSection:(NSInteger)section { 157 | (self.downloadingSectionsDictionary)[@(section)] = @YES; 158 | [self.myDelegate tableView:self downloadDataForExpandableSection:section]; 159 | [self reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:0 inSection:section]] 160 | withRowAnimation:UITableViewRowAnimationNone]; 161 | } 162 | 163 | #pragma mark - instance methods 164 | 165 | - (BOOL)canExpandSection:(NSUInteger)section { 166 | return [self.expandableSectionsDictionary[@(section)] boolValue]; 167 | } 168 | 169 | - (void)reloadDataAndResetExpansionStates:(BOOL)resetFlag { 170 | if (resetFlag) { 171 | [self _resetExpansionStates]; 172 | } 173 | 174 | if (self.onlyDisplayHeaderAndFooterViewIfTableViewIsNotEmpty) { 175 | if ([self numberOfSections] > 0) { 176 | if ([super tableFooterView] != self.storedTableFooterView) { 177 | [super setTableFooterView:self.storedTableFooterView]; 178 | } 179 | if ([super tableHeaderView] != self.storedTableHeaderView) { 180 | [super setTableHeaderView:self.storedTableHeaderView]; 181 | } 182 | } 183 | } else { 184 | if ([super tableFooterView] != self.storedTableFooterView) { 185 | [super setTableFooterView:self.storedTableFooterView]; 186 | } 187 | if ([super tableHeaderView] != self.storedTableHeaderView) { 188 | [super setTableHeaderView:self.storedTableHeaderView]; 189 | } 190 | } 191 | 192 | [super reloadData]; 193 | } 194 | 195 | - (void)cancelDownloadInSection:(NSInteger)section { 196 | self.downloadingSectionsDictionary[@(section)] = @NO; 197 | [self reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:0 inSection:section]] 198 | withRowAnimation:UITableViewRowAnimationNone]; 199 | } 200 | 201 | - (void)expandSection:(NSInteger)section animated:(BOOL)animated { 202 | NSNumber *key = @(section); 203 | if ([self.showingSectionsDictionary[key] boolValue]) { 204 | // section is already showing, return 205 | return; 206 | } 207 | 208 | [self deselectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section] animated:NO]; 209 | 210 | if ([self.myDataSource tableView:self needsToDownloadDataForExpandableSection:section]) { 211 | // data is still not ready to be displayed, return 212 | [self downloadDataInSection:section]; 213 | return; 214 | } 215 | 216 | if ([self.myDelegate respondsToSelector:@selector(tableView:willExpandSection:animated:)]) { 217 | [self.myDelegate tableView:self willExpandSection:section animated:animated]; 218 | } 219 | 220 | self.animatingSectionsDictionary[key] = @YES; 221 | 222 | // remove the download state 223 | self.downloadingSectionsDictionary[key] = @NO; 224 | 225 | // update the showing state 226 | self.showingSectionsDictionary[key] = @YES; 227 | 228 | NSInteger newRowCount = [self.myDataSource tableView:self numberOfRowsInSection:section]; 229 | // now do the animation magic to insert the new cells 230 | if (animated && newRowCount <= self.maximumRowCountToStillUseAnimationWhileExpanding) { 231 | [self beginUpdates]; 232 | 233 | UITableViewCell *cell = (UITableViewCell *)[self cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]]; 234 | cell.loading = NO; 235 | [cell setExpansionStyle:UIExpansionStyleExpanded animated:YES]; 236 | 237 | NSMutableArray *insertArray = [NSMutableArray array]; 238 | for (int i = 1; i < newRowCount; i++) { 239 | [insertArray addObject:[NSIndexPath indexPathForRow:i inSection:section] ]; 240 | } 241 | 242 | [self insertRowsAtIndexPaths:insertArray withRowAnimation:self.reloadAnimation]; 243 | 244 | [self endUpdates]; 245 | } else { 246 | [self reloadDataAndResetExpansionStates:NO]; 247 | } 248 | 249 | [self.animatingSectionsDictionary removeObjectForKey:@(section)]; 250 | 251 | void(^completionBlock)(void) = ^{ 252 | if ([self respondsToSelector:@selector(scrollViewDidScroll:)]) { 253 | [self scrollViewDidScroll:self]; 254 | } 255 | 256 | if ([self.myDelegate respondsToSelector:@selector(tableView:didExpandSection:animated:)]) { 257 | [self.myDelegate tableView:self didExpandSection:section animated:animated]; 258 | } 259 | }; 260 | 261 | if (animated) { 262 | [CATransaction setCompletionBlock:completionBlock]; 263 | } else { 264 | completionBlock(); 265 | } 266 | } 267 | 268 | - (void)collapseSection:(NSInteger)section animated:(BOOL)animated { 269 | NSNumber *key = @(section); 270 | if (![self.showingSectionsDictionary[key] boolValue]) { 271 | // section is not showing, return 272 | return; 273 | } 274 | 275 | if ([self.myDelegate respondsToSelector:@selector(tableView:willCollapseSection:animated:)]) { 276 | [self.myDelegate tableView:self willCollapseSection:section animated:animated]; 277 | } 278 | 279 | [self deselectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section] animated:NO]; 280 | 281 | self.animatingSectionsDictionary[key] = @YES; 282 | 283 | // update the showing state 284 | self.showingSectionsDictionary[key] = @NO; 285 | 286 | NSInteger newRowCount = [self.myDataSource tableView:self numberOfRowsInSection:section]; 287 | // now do the animation magic to delete the new cells 288 | if (animated && newRowCount <= self.maximumRowCountToStillUseAnimationWhileExpanding) { 289 | [self beginUpdates]; 290 | 291 | UITableViewCell *cell = (UITableViewCell *)[self cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]]; 292 | cell.loading = NO; 293 | [cell setExpansionStyle:UIExpansionStyleCollapsed animated:YES]; 294 | 295 | NSMutableArray *deleteArray = [NSMutableArray array]; 296 | for (int i = 1; i < newRowCount; i++) { 297 | [deleteArray addObject:[NSIndexPath indexPathForRow:i inSection:section] ]; 298 | } 299 | 300 | [self deleteRowsAtIndexPaths:deleteArray withRowAnimation:self.reloadAnimation]; 301 | 302 | [self endUpdates]; 303 | } else { 304 | [self reloadDataAndResetExpansionStates:NO]; 305 | } 306 | 307 | [self.animatingSectionsDictionary removeObjectForKey:@(section)]; 308 | 309 | [self scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section] 310 | atScrollPosition:UITableViewScrollPositionTop 311 | animated:animated]; 312 | 313 | void(^completionBlock)(void) = ^{ 314 | if ([self respondsToSelector:@selector(scrollViewDidScroll:)]) { 315 | [self scrollViewDidScroll:self]; 316 | } 317 | 318 | if ([self.myDelegate respondsToSelector:@selector(tableView:didCollapseSection:animated:)]) { 319 | [self.myDelegate tableView:self didCollapseSection:section animated:animated]; 320 | } 321 | }; 322 | 323 | if (animated) { 324 | [CATransaction setCompletionBlock:completionBlock]; 325 | } else { 326 | completionBlock(); 327 | } 328 | } 329 | 330 | - (BOOL)isSectionExpanded:(NSInteger)section { 331 | NSNumber *key = @(section); 332 | return [self.showingSectionsDictionary[key] boolValue]; 333 | } 334 | 335 | #pragma mark - super implementation 336 | 337 | - (void)reloadData { 338 | [self reloadDataAndResetExpansionStates:YES]; 339 | } 340 | 341 | - (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation { 342 | NSUInteger indexCount = self.numberOfSections; 343 | 344 | NSUInteger currentIndex = sections.firstIndex; 345 | NSInteger currentShift = 1; 346 | while (currentIndex != NSNotFound) { 347 | NSUInteger nextIndex = [sections indexGreaterThanIndex:currentIndex]; 348 | 349 | if (nextIndex == NSNotFound) { 350 | nextIndex = indexCount; 351 | } 352 | 353 | for (NSInteger i = currentIndex + 1; i < nextIndex; i++) { 354 | NSUInteger newIndex = i - currentShift; 355 | self.expandableSectionsDictionary[@(newIndex)] = @([self.expandableSectionsDictionary[@(i)] boolValue]); 356 | self.showingSectionsDictionary[@(newIndex)] = @([self.showingSectionsDictionary[@(i)] boolValue]); 357 | self.downloadingSectionsDictionary[@(newIndex)] = @([self.downloadingSectionsDictionary[@(i)] boolValue]); 358 | self.animatingSectionsDictionary[@(newIndex)] = @([self.animatingSectionsDictionary[@(i)] boolValue]); 359 | } 360 | 361 | currentShift++; 362 | currentIndex = [sections indexLessThanIndex:currentIndex]; 363 | } 364 | 365 | [super deleteSections:sections withRowAnimation:animation]; 366 | } 367 | 368 | #pragma mark - UITableViewDelegate 369 | 370 | - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { 371 | NSNumber *key = @(indexPath.section); 372 | NSNumber *value = self.animatingSectionsDictionary[key]; 373 | if ([value boolValue]) { 374 | if ([self.myDelegate respondsToSelector:@selector(tableView:willDisplayCell:forRowAtIndexPathWhileAnimatingSection:)]) { 375 | [self.myDelegate tableView:self willDisplayCell:cell forRowAtIndexPathWhileAnimatingSection:indexPath]; 376 | } 377 | } else { 378 | if ([self.myDelegate respondsToSelector:@selector(tableView:willDisplayCell:forRowAtIndexPath:)]) { 379 | [self.myDelegate tableView:tableView willDisplayCell:cell forRowAtIndexPath:indexPath]; 380 | } 381 | } 382 | } 383 | 384 | // Called after the user changes the selection. 385 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 386 | NSNumber *key = @(indexPath.section); 387 | if ([self.expandableSectionsDictionary[key] boolValue]) { 388 | // section is expandable 389 | if (indexPath.row == 0) { 390 | // expand cell got clicked 391 | if ([self.myDataSource tableView:self needsToDownloadDataForExpandableSection:indexPath.section]) { 392 | // we need to download some data first 393 | [self downloadDataInSection:indexPath.section]; 394 | } else { 395 | if ([self.showingSectionsDictionary[key] boolValue]) { 396 | [self collapseSection:indexPath.section animated:YES]; 397 | } else { 398 | [self expandSection:indexPath.section animated:YES]; 399 | } 400 | } 401 | } else { 402 | if ([self.myDelegate respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]) { 403 | [self.myDelegate tableView:tableView didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section] ]; 404 | } 405 | } 406 | } else { 407 | if ([self.myDelegate respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]) { 408 | [self.myDelegate tableView:tableView didSelectRowAtIndexPath:indexPath]; 409 | } 410 | } 411 | } 412 | 413 | #pragma mark - UITableViewDataSource 414 | 415 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 416 | NSNumber *key = @(section); 417 | if ([self.myDataSource tableView:self canExpandSection:section]) { 418 | if ([self.myDataSource tableView:tableView numberOfRowsInSection:section] == 0) { 419 | return 0; 420 | } 421 | self.expandableSectionsDictionary[key] = @YES; 422 | 423 | if ([self.showingSectionsDictionary[key] boolValue]) { 424 | return [self.myDataSource tableView:tableView numberOfRowsInSection:section]; 425 | } else { 426 | return 1; 427 | } 428 | } else { 429 | self.expandableSectionsDictionary[key] = @NO; 430 | // expanding is not supported 431 | return [self.myDataSource tableView:tableView numberOfRowsInSection:section]; 432 | } 433 | return 0; 434 | } 435 | 436 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 437 | NSNumber *key = @(indexPath.section); 438 | if (![self.expandableSectionsDictionary[key] boolValue]) { 439 | return [self.myDataSource tableView:tableView cellForRowAtIndexPath:indexPath]; 440 | } else { 441 | // cell is expandable 442 | if (indexPath.row == 0) { 443 | UITableViewCell *cell = [self.myDataSource tableView:self expandingCellForSection:indexPath.section]; 444 | if ([self.downloadingSectionsDictionary[key] boolValue]) { 445 | [cell setLoading:YES]; 446 | } else { 447 | [cell setLoading:NO]; 448 | if ([self.showingSectionsDictionary[key] boolValue]) { 449 | [cell setExpansionStyle:UIExpansionStyleExpanded animated:NO]; 450 | } else { 451 | [cell setExpansionStyle:UIExpansionStyleCollapsed animated:NO]; 452 | } 453 | } 454 | return cell; 455 | } else { 456 | return [self.myDataSource tableView:tableView cellForRowAtIndexPath:indexPath]; 457 | } 458 | } 459 | return nil; 460 | } 461 | 462 | @end 463 | -------------------------------------------------------------------------------- /SLExpandableTableViewTests.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /SLExpandableTableViewTests.xcworkspace/xcshareddata/SLExpandableTableViewTests.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | ED3B57D6-12A4-4419-85B4-090C34A70F62 9 | IDESourceControlProjectName 10 | SLExpandableTableViewTests 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | E80799AD1BF0F690D392BEE5F4E6774EACF93D33 14 | ssh://github.com/OliverLetterer/UIExpandableTableView.git 15 | 16 | IDESourceControlProjectPath 17 | SLExpandableTableViewTests.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | E80799AD1BF0F690D392BEE5F4E6774EACF93D33 21 | .. 22 | 23 | IDESourceControlProjectURL 24 | ssh://github.com/OliverLetterer/UIExpandableTableView.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | E80799AD1BF0F690D392BEE5F4E6774EACF93D33 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | E80799AD1BF0F690D392BEE5F4E6774EACF93D33 36 | IDESourceControlWCCName 37 | SLExpandableTableView 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OliverLetterer/SLExpandableTableView/3b05c8d24cccb17d638814e4c4bdb1497dcb6257/Screenshots/1.png -------------------------------------------------------------------------------- /Screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OliverLetterer/SLExpandableTableView/3b05c8d24cccb17d638814e4c4bdb1497dcb6257/Screenshots/2.png -------------------------------------------------------------------------------- /Tests/Podfile: -------------------------------------------------------------------------------- 1 | xcodeproj 'SLExpandableTableViewTests' 2 | workspace '../SLExpandableTableViewTests' 3 | inhibit_all_warnings! 4 | 5 | platform :ios, '6.0' 6 | 7 | pod 'SLExpandableTableView', path: '../' 8 | 9 | target 'Tests', :exclusive => true do 10 | pod 'OCMock', '~> 3.1.1' 11 | pod 'Expecta', '~> 0.3.1' 12 | pod 'KIF', '~> 3.0.8' 13 | end 14 | -------------------------------------------------------------------------------- /Tests/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Expecta (0.3.1) 3 | - KIF (3.0.8): 4 | - KIF/XCTest (= 3.0.8) 5 | - KIF/XCTest (3.0.8) 6 | - OCMock (3.1.1) 7 | - SLExpandableTableView (1.2.3) 8 | 9 | DEPENDENCIES: 10 | - Expecta (~> 0.3.1) 11 | - KIF (~> 3.0.8) 12 | - OCMock (~> 3.1.1) 13 | - SLExpandableTableView (from `..`) 14 | 15 | EXTERNAL SOURCES: 16 | SLExpandableTableView: 17 | :path: .. 18 | 19 | SPEC CHECKSUMS: 20 | Expecta: 03aabd0a89d8dea843baecb19a7fd7466a69a31d 21 | KIF: 2db5e8cf59136dd1267d49cca0d55883389b09d3 22 | OCMock: f6cb8c162ab9d5620dddf411282c7b2c0ee78854 23 | SLExpandableTableView: 0cd07e3cbfe9a9ad02ec9778c0778f2d5c7527f4 24 | 25 | COCOAPODS: 0.35.0 26 | -------------------------------------------------------------------------------- /Tests/SLExpandableTableViewTests.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A79EB20618B49E690096FED6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A79EB20518B49E690096FED6 /* Foundation.framework */; }; 11 | A79EB20818B49E690096FED6 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A79EB20718B49E690096FED6 /* CoreGraphics.framework */; }; 12 | A79EB20A18B49E690096FED6 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A79EB20918B49E690096FED6 /* UIKit.framework */; }; 13 | A79EB21018B49E690096FED6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = A79EB20E18B49E690096FED6 /* InfoPlist.strings */; }; 14 | A79EB21218B49E690096FED6 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A79EB21118B49E690096FED6 /* main.m */; }; 15 | A79EB21618B49E690096FED6 /* SLAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A79EB21518B49E690096FED6 /* SLAppDelegate.m */; }; 16 | A79EB21818B49E690096FED6 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A79EB21718B49E690096FED6 /* Images.xcassets */; }; 17 | A7A7E95718B49F05000DB6D9 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A79EB20518B49E690096FED6 /* Foundation.framework */; }; 18 | A7A7E95818B49F05000DB6D9 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A79EB20918B49E690096FED6 /* UIKit.framework */; }; 19 | A7A7E95E18B49F05000DB6D9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = A7A7E95C18B49F05000DB6D9 /* InfoPlist.strings */; }; 20 | A7A7E96018B49F05000DB6D9 /* Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = A7A7E95F18B49F05000DB6D9 /* Tests.m */; }; 21 | A7A7E96918B4A037000DB6D9 /* SLExpandableTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A7A7E96818B4A037000DB6D9 /* SLExpandableTableViewController.m */; }; 22 | CE972301613841BD8FA3C486 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B2751CC6176146ADA50BA1E1 /* libPods.a */; }; 23 | DFB2F166974540D8912B78B2 /* libPods-Tests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B93C16CB3524EE2BD6EDFC0 /* libPods-Tests.a */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | A7A7E96218B49F05000DB6D9 /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = A79EB1FA18B49E690096FED6 /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = A79EB20118B49E690096FED6; 32 | remoteInfo = SLExpandableTableViewTests; 33 | }; 34 | /* End PBXContainerItemProxy section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | 121C0153E379DD9C45CB6EB5 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; 38 | 6AB63B64346A95A094DDBA1C /* Pods-Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-Tests/Pods-Tests.release.xcconfig"; sourceTree = ""; }; 39 | 9B93C16CB3524EE2BD6EDFC0 /* libPods-Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | A79EB20218B49E690096FED6 /* SLExpandableTableViewTests.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SLExpandableTableViewTests.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | A79EB20518B49E690096FED6 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 42 | A79EB20718B49E690096FED6 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 43 | A79EB20918B49E690096FED6 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 44 | A79EB20D18B49E690096FED6 /* SLExpandableTableViewTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SLExpandableTableViewTests-Info.plist"; sourceTree = ""; }; 45 | A79EB20F18B49E690096FED6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 46 | A79EB21118B49E690096FED6 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 47 | A79EB21318B49E690096FED6 /* SLExpandableTableViewTests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SLExpandableTableViewTests-Prefix.pch"; sourceTree = ""; }; 48 | A79EB21418B49E690096FED6 /* SLAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLAppDelegate.h; sourceTree = ""; }; 49 | A79EB21518B49E690096FED6 /* SLAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLAppDelegate.m; sourceTree = ""; }; 50 | A79EB21718B49E690096FED6 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 51 | A79EB21E18B49E6A0096FED6 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 52 | A7A7E95418B49F05000DB6D9 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | A7A7E95B18B49F05000DB6D9 /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = ""; }; 54 | A7A7E95D18B49F05000DB6D9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 55 | A7A7E95F18B49F05000DB6D9 /* Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Tests.m; sourceTree = ""; }; 56 | A7A7E96118B49F05000DB6D9 /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = ""; }; 57 | A7A7E96718B4A037000DB6D9 /* SLExpandableTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SLExpandableTableViewController.h; sourceTree = ""; }; 58 | A7A7E96818B4A037000DB6D9 /* SLExpandableTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLExpandableTableViewController.m; sourceTree = ""; }; 59 | B2751CC6176146ADA50BA1E1 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 60 | B683BF6DD56641479C120152 /* Pods-Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Tests/Pods-Tests.debug.xcconfig"; sourceTree = ""; }; 61 | C8B884721DAED5AB48717FD3 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; 62 | /* End PBXFileReference section */ 63 | 64 | /* Begin PBXFrameworksBuildPhase section */ 65 | A79EB1FF18B49E690096FED6 /* Frameworks */ = { 66 | isa = PBXFrameworksBuildPhase; 67 | buildActionMask = 2147483647; 68 | files = ( 69 | A79EB20818B49E690096FED6 /* CoreGraphics.framework in Frameworks */, 70 | A79EB20A18B49E690096FED6 /* UIKit.framework in Frameworks */, 71 | A79EB20618B49E690096FED6 /* Foundation.framework in Frameworks */, 72 | CE972301613841BD8FA3C486 /* libPods.a in Frameworks */, 73 | ); 74 | runOnlyForDeploymentPostprocessing = 0; 75 | }; 76 | A7A7E95118B49F05000DB6D9 /* Frameworks */ = { 77 | isa = PBXFrameworksBuildPhase; 78 | buildActionMask = 2147483647; 79 | files = ( 80 | A7A7E95818B49F05000DB6D9 /* UIKit.framework in Frameworks */, 81 | A7A7E95718B49F05000DB6D9 /* Foundation.framework in Frameworks */, 82 | DFB2F166974540D8912B78B2 /* libPods-Tests.a in Frameworks */, 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | /* End PBXFrameworksBuildPhase section */ 87 | 88 | /* Begin PBXGroup section */ 89 | 93B760CFBC16F4AB720801D1 /* Pods */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | C8B884721DAED5AB48717FD3 /* Pods.debug.xcconfig */, 93 | 121C0153E379DD9C45CB6EB5 /* Pods.release.xcconfig */, 94 | B683BF6DD56641479C120152 /* Pods-Tests.debug.xcconfig */, 95 | 6AB63B64346A95A094DDBA1C /* Pods-Tests.release.xcconfig */, 96 | ); 97 | name = Pods; 98 | sourceTree = ""; 99 | }; 100 | A79EB1F918B49E690096FED6 = { 101 | isa = PBXGroup; 102 | children = ( 103 | A79EB20B18B49E690096FED6 /* SLExpandableTableViewTests */, 104 | A7A7E95918B49F05000DB6D9 /* Tests */, 105 | A79EB20418B49E690096FED6 /* Frameworks */, 106 | A79EB20318B49E690096FED6 /* Products */, 107 | 93B760CFBC16F4AB720801D1 /* Pods */, 108 | ); 109 | sourceTree = ""; 110 | }; 111 | A79EB20318B49E690096FED6 /* Products */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | A79EB20218B49E690096FED6 /* SLExpandableTableViewTests.app */, 115 | A7A7E95418B49F05000DB6D9 /* Tests.xctest */, 116 | ); 117 | name = Products; 118 | sourceTree = ""; 119 | }; 120 | A79EB20418B49E690096FED6 /* Frameworks */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | A79EB20518B49E690096FED6 /* Foundation.framework */, 124 | A79EB20718B49E690096FED6 /* CoreGraphics.framework */, 125 | A79EB20918B49E690096FED6 /* UIKit.framework */, 126 | A79EB21E18B49E6A0096FED6 /* XCTest.framework */, 127 | B2751CC6176146ADA50BA1E1 /* libPods.a */, 128 | 9B93C16CB3524EE2BD6EDFC0 /* libPods-Tests.a */, 129 | ); 130 | name = Frameworks; 131 | sourceTree = ""; 132 | }; 133 | A79EB20B18B49E690096FED6 /* SLExpandableTableViewTests */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | A7A7E96718B4A037000DB6D9 /* SLExpandableTableViewController.h */, 137 | A7A7E96818B4A037000DB6D9 /* SLExpandableTableViewController.m */, 138 | A79EB21418B49E690096FED6 /* SLAppDelegate.h */, 139 | A79EB21518B49E690096FED6 /* SLAppDelegate.m */, 140 | A79EB21718B49E690096FED6 /* Images.xcassets */, 141 | A79EB20C18B49E690096FED6 /* Supporting Files */, 142 | ); 143 | path = SLExpandableTableViewTests; 144 | sourceTree = ""; 145 | }; 146 | A79EB20C18B49E690096FED6 /* Supporting Files */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | A79EB20D18B49E690096FED6 /* SLExpandableTableViewTests-Info.plist */, 150 | A79EB20E18B49E690096FED6 /* InfoPlist.strings */, 151 | A79EB21118B49E690096FED6 /* main.m */, 152 | A79EB21318B49E690096FED6 /* SLExpandableTableViewTests-Prefix.pch */, 153 | ); 154 | name = "Supporting Files"; 155 | sourceTree = ""; 156 | }; 157 | A7A7E95918B49F05000DB6D9 /* Tests */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | A7A7E95F18B49F05000DB6D9 /* Tests.m */, 161 | A7A7E95A18B49F05000DB6D9 /* Supporting Files */, 162 | ); 163 | path = Tests; 164 | sourceTree = ""; 165 | }; 166 | A7A7E95A18B49F05000DB6D9 /* Supporting Files */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | A7A7E95B18B49F05000DB6D9 /* Tests-Info.plist */, 170 | A7A7E95C18B49F05000DB6D9 /* InfoPlist.strings */, 171 | A7A7E96118B49F05000DB6D9 /* Tests-Prefix.pch */, 172 | ); 173 | name = "Supporting Files"; 174 | sourceTree = ""; 175 | }; 176 | /* End PBXGroup section */ 177 | 178 | /* Begin PBXNativeTarget section */ 179 | A79EB20118B49E690096FED6 /* SLExpandableTableViewTests */ = { 180 | isa = PBXNativeTarget; 181 | buildConfigurationList = A79EB22E18B49E6A0096FED6 /* Build configuration list for PBXNativeTarget "SLExpandableTableViewTests" */; 182 | buildPhases = ( 183 | A798C6EB3AC94B2DB9BEE4C8 /* Check Pods Manifest.lock */, 184 | A79EB1FE18B49E690096FED6 /* Sources */, 185 | A79EB1FF18B49E690096FED6 /* Frameworks */, 186 | A79EB20018B49E690096FED6 /* Resources */, 187 | 174BEE5E47F745299AFE4B18 /* Copy Pods Resources */, 188 | ); 189 | buildRules = ( 190 | ); 191 | dependencies = ( 192 | ); 193 | name = SLExpandableTableViewTests; 194 | productName = SLExpandableTableViewTests; 195 | productReference = A79EB20218B49E690096FED6 /* SLExpandableTableViewTests.app */; 196 | productType = "com.apple.product-type.application"; 197 | }; 198 | A7A7E95318B49F05000DB6D9 /* Tests */ = { 199 | isa = PBXNativeTarget; 200 | buildConfigurationList = A7A7E96418B49F05000DB6D9 /* Build configuration list for PBXNativeTarget "Tests" */; 201 | buildPhases = ( 202 | 36A3C9BB24924104BF978D8E /* Check Pods Manifest.lock */, 203 | A7A7E95018B49F05000DB6D9 /* Sources */, 204 | A7A7E95118B49F05000DB6D9 /* Frameworks */, 205 | A7A7E95218B49F05000DB6D9 /* Resources */, 206 | B0B145C3B6704B5B987307CA /* Copy Pods Resources */, 207 | ); 208 | buildRules = ( 209 | ); 210 | dependencies = ( 211 | A7A7E96318B49F05000DB6D9 /* PBXTargetDependency */, 212 | ); 213 | name = Tests; 214 | productName = Tests; 215 | productReference = A7A7E95418B49F05000DB6D9 /* Tests.xctest */; 216 | productType = "com.apple.product-type.bundle.unit-test"; 217 | }; 218 | /* End PBXNativeTarget section */ 219 | 220 | /* Begin PBXProject section */ 221 | A79EB1FA18B49E690096FED6 /* Project object */ = { 222 | isa = PBXProject; 223 | attributes = { 224 | CLASSPREFIX = SL; 225 | LastUpgradeCheck = 0610; 226 | ORGANIZATIONNAME = "Sparrow-Labs"; 227 | TargetAttributes = { 228 | A7A7E95318B49F05000DB6D9 = { 229 | TestTargetID = A79EB20118B49E690096FED6; 230 | }; 231 | }; 232 | }; 233 | buildConfigurationList = A79EB1FD18B49E690096FED6 /* Build configuration list for PBXProject "SLExpandableTableViewTests" */; 234 | compatibilityVersion = "Xcode 3.2"; 235 | developmentRegion = English; 236 | hasScannedForEncodings = 0; 237 | knownRegions = ( 238 | en, 239 | ); 240 | mainGroup = A79EB1F918B49E690096FED6; 241 | productRefGroup = A79EB20318B49E690096FED6 /* Products */; 242 | projectDirPath = ""; 243 | projectRoot = ""; 244 | targets = ( 245 | A79EB20118B49E690096FED6 /* SLExpandableTableViewTests */, 246 | A7A7E95318B49F05000DB6D9 /* Tests */, 247 | ); 248 | }; 249 | /* End PBXProject section */ 250 | 251 | /* Begin PBXResourcesBuildPhase section */ 252 | A79EB20018B49E690096FED6 /* Resources */ = { 253 | isa = PBXResourcesBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | A79EB21018B49E690096FED6 /* InfoPlist.strings in Resources */, 257 | A79EB21818B49E690096FED6 /* Images.xcassets in Resources */, 258 | ); 259 | runOnlyForDeploymentPostprocessing = 0; 260 | }; 261 | A7A7E95218B49F05000DB6D9 /* Resources */ = { 262 | isa = PBXResourcesBuildPhase; 263 | buildActionMask = 2147483647; 264 | files = ( 265 | A7A7E95E18B49F05000DB6D9 /* InfoPlist.strings in Resources */, 266 | ); 267 | runOnlyForDeploymentPostprocessing = 0; 268 | }; 269 | /* End PBXResourcesBuildPhase section */ 270 | 271 | /* Begin PBXShellScriptBuildPhase section */ 272 | 174BEE5E47F745299AFE4B18 /* Copy Pods Resources */ = { 273 | isa = PBXShellScriptBuildPhase; 274 | buildActionMask = 2147483647; 275 | files = ( 276 | ); 277 | inputPaths = ( 278 | ); 279 | name = "Copy Pods Resources"; 280 | outputPaths = ( 281 | ); 282 | runOnlyForDeploymentPostprocessing = 0; 283 | shellPath = /bin/sh; 284 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; 285 | showEnvVarsInLog = 0; 286 | }; 287 | 36A3C9BB24924104BF978D8E /* Check Pods Manifest.lock */ = { 288 | isa = PBXShellScriptBuildPhase; 289 | buildActionMask = 2147483647; 290 | files = ( 291 | ); 292 | inputPaths = ( 293 | ); 294 | name = "Check Pods Manifest.lock"; 295 | outputPaths = ( 296 | ); 297 | runOnlyForDeploymentPostprocessing = 0; 298 | shellPath = /bin/sh; 299 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 300 | showEnvVarsInLog = 0; 301 | }; 302 | A798C6EB3AC94B2DB9BEE4C8 /* Check Pods Manifest.lock */ = { 303 | isa = PBXShellScriptBuildPhase; 304 | buildActionMask = 2147483647; 305 | files = ( 306 | ); 307 | inputPaths = ( 308 | ); 309 | name = "Check Pods Manifest.lock"; 310 | outputPaths = ( 311 | ); 312 | runOnlyForDeploymentPostprocessing = 0; 313 | shellPath = /bin/sh; 314 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 315 | showEnvVarsInLog = 0; 316 | }; 317 | B0B145C3B6704B5B987307CA /* Copy Pods Resources */ = { 318 | isa = PBXShellScriptBuildPhase; 319 | buildActionMask = 2147483647; 320 | files = ( 321 | ); 322 | inputPaths = ( 323 | ); 324 | name = "Copy Pods Resources"; 325 | outputPaths = ( 326 | ); 327 | runOnlyForDeploymentPostprocessing = 0; 328 | shellPath = /bin/sh; 329 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Tests/Pods-Tests-resources.sh\"\n"; 330 | showEnvVarsInLog = 0; 331 | }; 332 | /* End PBXShellScriptBuildPhase section */ 333 | 334 | /* Begin PBXSourcesBuildPhase section */ 335 | A79EB1FE18B49E690096FED6 /* Sources */ = { 336 | isa = PBXSourcesBuildPhase; 337 | buildActionMask = 2147483647; 338 | files = ( 339 | A79EB21218B49E690096FED6 /* main.m in Sources */, 340 | A7A7E96918B4A037000DB6D9 /* SLExpandableTableViewController.m in Sources */, 341 | A79EB21618B49E690096FED6 /* SLAppDelegate.m in Sources */, 342 | ); 343 | runOnlyForDeploymentPostprocessing = 0; 344 | }; 345 | A7A7E95018B49F05000DB6D9 /* Sources */ = { 346 | isa = PBXSourcesBuildPhase; 347 | buildActionMask = 2147483647; 348 | files = ( 349 | A7A7E96018B49F05000DB6D9 /* Tests.m in Sources */, 350 | ); 351 | runOnlyForDeploymentPostprocessing = 0; 352 | }; 353 | /* End PBXSourcesBuildPhase section */ 354 | 355 | /* Begin PBXTargetDependency section */ 356 | A7A7E96318B49F05000DB6D9 /* PBXTargetDependency */ = { 357 | isa = PBXTargetDependency; 358 | target = A79EB20118B49E690096FED6 /* SLExpandableTableViewTests */; 359 | targetProxy = A7A7E96218B49F05000DB6D9 /* PBXContainerItemProxy */; 360 | }; 361 | /* End PBXTargetDependency section */ 362 | 363 | /* Begin PBXVariantGroup section */ 364 | A79EB20E18B49E690096FED6 /* InfoPlist.strings */ = { 365 | isa = PBXVariantGroup; 366 | children = ( 367 | A79EB20F18B49E690096FED6 /* en */, 368 | ); 369 | name = InfoPlist.strings; 370 | sourceTree = ""; 371 | }; 372 | A7A7E95C18B49F05000DB6D9 /* InfoPlist.strings */ = { 373 | isa = PBXVariantGroup; 374 | children = ( 375 | A7A7E95D18B49F05000DB6D9 /* en */, 376 | ); 377 | name = InfoPlist.strings; 378 | sourceTree = ""; 379 | }; 380 | /* End PBXVariantGroup section */ 381 | 382 | /* Begin XCBuildConfiguration section */ 383 | A79EB22C18B49E6A0096FED6 /* Debug */ = { 384 | isa = XCBuildConfiguration; 385 | buildSettings = { 386 | ALWAYS_SEARCH_USER_PATHS = NO; 387 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 388 | CLANG_CXX_LIBRARY = "libc++"; 389 | CLANG_ENABLE_MODULES = YES; 390 | CLANG_ENABLE_OBJC_ARC = YES; 391 | CLANG_WARN_BOOL_CONVERSION = YES; 392 | CLANG_WARN_CONSTANT_CONVERSION = YES; 393 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 394 | CLANG_WARN_EMPTY_BODY = YES; 395 | CLANG_WARN_ENUM_CONVERSION = YES; 396 | CLANG_WARN_INT_CONVERSION = YES; 397 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 398 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 399 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 400 | COPY_PHASE_STRIP = NO; 401 | GCC_C_LANGUAGE_STANDARD = gnu99; 402 | GCC_DYNAMIC_NO_PIC = NO; 403 | GCC_OPTIMIZATION_LEVEL = 0; 404 | GCC_PREPROCESSOR_DEFINITIONS = ( 405 | "DEBUG=1", 406 | "$(inherited)", 407 | ); 408 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 409 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 410 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 411 | GCC_WARN_UNDECLARED_SELECTOR = YES; 412 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 413 | GCC_WARN_UNUSED_FUNCTION = YES; 414 | GCC_WARN_UNUSED_VARIABLE = YES; 415 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 416 | ONLY_ACTIVE_ARCH = YES; 417 | SDKROOT = iphoneos; 418 | }; 419 | name = Debug; 420 | }; 421 | A79EB22D18B49E6A0096FED6 /* Release */ = { 422 | isa = XCBuildConfiguration; 423 | buildSettings = { 424 | ALWAYS_SEARCH_USER_PATHS = NO; 425 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 426 | CLANG_CXX_LIBRARY = "libc++"; 427 | CLANG_ENABLE_MODULES = YES; 428 | CLANG_ENABLE_OBJC_ARC = YES; 429 | CLANG_WARN_BOOL_CONVERSION = YES; 430 | CLANG_WARN_CONSTANT_CONVERSION = YES; 431 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 432 | CLANG_WARN_EMPTY_BODY = YES; 433 | CLANG_WARN_ENUM_CONVERSION = YES; 434 | CLANG_WARN_INT_CONVERSION = YES; 435 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 436 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 437 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 438 | COPY_PHASE_STRIP = YES; 439 | ENABLE_NS_ASSERTIONS = NO; 440 | GCC_C_LANGUAGE_STANDARD = gnu99; 441 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 442 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 443 | GCC_WARN_UNDECLARED_SELECTOR = YES; 444 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 445 | GCC_WARN_UNUSED_FUNCTION = YES; 446 | GCC_WARN_UNUSED_VARIABLE = YES; 447 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 448 | SDKROOT = iphoneos; 449 | VALIDATE_PRODUCT = YES; 450 | }; 451 | name = Release; 452 | }; 453 | A79EB22F18B49E6A0096FED6 /* Debug */ = { 454 | isa = XCBuildConfiguration; 455 | baseConfigurationReference = C8B884721DAED5AB48717FD3 /* Pods.debug.xcconfig */; 456 | buildSettings = { 457 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 458 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 459 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 460 | GCC_PREFIX_HEADER = "SLExpandableTableViewTests/SLExpandableTableViewTests-Prefix.pch"; 461 | INFOPLIST_FILE = "SLExpandableTableViewTests/SLExpandableTableViewTests-Info.plist"; 462 | PRODUCT_NAME = "$(TARGET_NAME)"; 463 | WRAPPER_EXTENSION = app; 464 | }; 465 | name = Debug; 466 | }; 467 | A79EB23018B49E6A0096FED6 /* Release */ = { 468 | isa = XCBuildConfiguration; 469 | baseConfigurationReference = 121C0153E379DD9C45CB6EB5 /* Pods.release.xcconfig */; 470 | buildSettings = { 471 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 472 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 473 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 474 | GCC_PREFIX_HEADER = "SLExpandableTableViewTests/SLExpandableTableViewTests-Prefix.pch"; 475 | INFOPLIST_FILE = "SLExpandableTableViewTests/SLExpandableTableViewTests-Info.plist"; 476 | PRODUCT_NAME = "$(TARGET_NAME)"; 477 | WRAPPER_EXTENSION = app; 478 | }; 479 | name = Release; 480 | }; 481 | A7A7E96518B49F05000DB6D9 /* Debug */ = { 482 | isa = XCBuildConfiguration; 483 | baseConfigurationReference = B683BF6DD56641479C120152 /* Pods-Tests.debug.xcconfig */; 484 | buildSettings = { 485 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SLExpandableTableViewTests.app/SLExpandableTableViewTests"; 486 | FRAMEWORK_SEARCH_PATHS = ( 487 | "$(SDKROOT)/Developer/Library/Frameworks", 488 | "$(inherited)", 489 | "$(DEVELOPER_FRAMEWORKS_DIR)", 490 | ); 491 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 492 | GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; 493 | GCC_PREPROCESSOR_DEFINITIONS = ( 494 | "DEBUG=1", 495 | "$(inherited)", 496 | ); 497 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 498 | INFOPLIST_FILE = "Tests/Tests-Info.plist"; 499 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 500 | PRODUCT_NAME = "$(TARGET_NAME)"; 501 | TEST_HOST = "$(BUNDLE_LOADER)"; 502 | }; 503 | name = Debug; 504 | }; 505 | A7A7E96618B49F05000DB6D9 /* Release */ = { 506 | isa = XCBuildConfiguration; 507 | baseConfigurationReference = 6AB63B64346A95A094DDBA1C /* Pods-Tests.release.xcconfig */; 508 | buildSettings = { 509 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SLExpandableTableViewTests.app/SLExpandableTableViewTests"; 510 | FRAMEWORK_SEARCH_PATHS = ( 511 | "$(SDKROOT)/Developer/Library/Frameworks", 512 | "$(inherited)", 513 | "$(DEVELOPER_FRAMEWORKS_DIR)", 514 | ); 515 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 516 | GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; 517 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 518 | INFOPLIST_FILE = "Tests/Tests-Info.plist"; 519 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 520 | PRODUCT_NAME = "$(TARGET_NAME)"; 521 | TEST_HOST = "$(BUNDLE_LOADER)"; 522 | }; 523 | name = Release; 524 | }; 525 | /* End XCBuildConfiguration section */ 526 | 527 | /* Begin XCConfigurationList section */ 528 | A79EB1FD18B49E690096FED6 /* Build configuration list for PBXProject "SLExpandableTableViewTests" */ = { 529 | isa = XCConfigurationList; 530 | buildConfigurations = ( 531 | A79EB22C18B49E6A0096FED6 /* Debug */, 532 | A79EB22D18B49E6A0096FED6 /* Release */, 533 | ); 534 | defaultConfigurationIsVisible = 0; 535 | defaultConfigurationName = Release; 536 | }; 537 | A79EB22E18B49E6A0096FED6 /* Build configuration list for PBXNativeTarget "SLExpandableTableViewTests" */ = { 538 | isa = XCConfigurationList; 539 | buildConfigurations = ( 540 | A79EB22F18B49E6A0096FED6 /* Debug */, 541 | A79EB23018B49E6A0096FED6 /* Release */, 542 | ); 543 | defaultConfigurationIsVisible = 0; 544 | defaultConfigurationName = Release; 545 | }; 546 | A7A7E96418B49F05000DB6D9 /* Build configuration list for PBXNativeTarget "Tests" */ = { 547 | isa = XCConfigurationList; 548 | buildConfigurations = ( 549 | A7A7E96518B49F05000DB6D9 /* Debug */, 550 | A7A7E96618B49F05000DB6D9 /* Release */, 551 | ); 552 | defaultConfigurationIsVisible = 0; 553 | defaultConfigurationName = Release; 554 | }; 555 | /* End XCConfigurationList section */ 556 | }; 557 | rootObject = A79EB1FA18B49E690096FED6 /* Project object */; 558 | } 559 | -------------------------------------------------------------------------------- /Tests/SLExpandableTableViewTests.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tests/SLExpandableTableViewTests.xcodeproj/project.xcworkspace/xcshareddata/SLExpandableTableViewTests.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 15C601B6-C59F-4710-A29F-9F20EA8C8D8B 9 | IDESourceControlProjectName 10 | SLExpandableTableViewTests 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 87BD5117-4285-4FBB-86C7-A96307966D11 14 | ssh://github.com/OliverLetterer/UIExpandableTableView.git 15 | 16 | IDESourceControlProjectPath 17 | Tests/SLExpandableTableViewTests/SLExpandableTableViewTests.xcodeproj/project.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 87BD5117-4285-4FBB-86C7-A96307966D11 21 | ../../../.. 22 | 23 | IDESourceControlProjectURL 24 | ssh://github.com/OliverLetterer/UIExpandableTableView.git 25 | IDESourceControlProjectVersion 26 | 110 27 | IDESourceControlProjectWCCIdentifier 28 | 87BD5117-4285-4FBB-86C7-A96307966D11 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 87BD5117-4285-4FBB-86C7-A96307966D11 36 | IDESourceControlWCCName 37 | SLExpandableTableView 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Tests/SLExpandableTableViewTests.xcodeproj/xcshareddata/xcschemes/SLExpandableTableViewTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /Tests/SLExpandableTableViewTests/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Tests/SLExpandableTableViewTests/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Tests/SLExpandableTableViewTests/SLAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // SLAppDelegate.h 3 | // SLExpandableTableViewTests 4 | // 5 | // Created by Oliver Letterer on 19.02.14. 6 | // Copyright (c) 2014 Sparrow-Labs. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SLAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Tests/SLExpandableTableViewTests/SLAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // SLAppDelegate.m 3 | // SLExpandableTableViewTests 4 | // 5 | // Created by Oliver Letterer on 19.02.14. 6 | // Copyright (c) 2014 Sparrow-Labs. All rights reserved. 7 | // 8 | 9 | #import "SLAppDelegate.h" 10 | #import "SLExpandableTableViewController.h" 11 | 12 | @implementation SLAppDelegate 13 | 14 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 15 | { 16 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 17 | self.window.backgroundColor = [UIColor whiteColor]; 18 | SLExpandableTableViewController *viewController = [[SLExpandableTableViewController alloc] initWithStyle:UITableViewStylePlain]; 19 | self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:viewController]; 20 | [self.window makeKeyAndVisible]; 21 | 22 | return YES; 23 | } 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /Tests/SLExpandableTableViewTests/SLExpandableTableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SLExpandableTableViewController.h 3 | // SLExpandableTableViewTests 4 | // 5 | // Created by Oliver Letterer on 19.02.14. 6 | // Copyright 2014 Sparrow-Labs. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | 13 | /** 14 | @abstract <#abstract comment#> 15 | */ 16 | @interface SLExpandableTableViewController : UITableViewController 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Tests/SLExpandableTableViewTests/SLExpandableTableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SLExpandableTableViewController.m 3 | // SLExpandableTableViewTests 4 | // 5 | // Created by Oliver Letterer on 19.02.14. 6 | // Copyright 2014 Sparrow-Labs. All rights reserved. 7 | // 8 | 9 | #import "SLExpandableTableViewController.h" 10 | 11 | @interface SLExpandableTableViewControllerHeaderCell : UITableViewCell 12 | 13 | @property (nonatomic, assign, getter = isLoading) BOOL loading; 14 | 15 | @property (nonatomic, readonly) UIExpansionStyle expansionStyle; 16 | - (void)setExpansionStyle:(UIExpansionStyle)expansionStyle animated:(BOOL)animated; 17 | 18 | @end 19 | 20 | @implementation SLExpandableTableViewControllerHeaderCell 21 | 22 | - (NSString *)accessibilityLabel 23 | { 24 | return self.textLabel.text; 25 | } 26 | 27 | - (void)setLoading:(BOOL)loading 28 | { 29 | if (loading != _loading) { 30 | _loading = loading; 31 | [self _updateDetailTextLabel]; 32 | } 33 | } 34 | 35 | - (void)setExpansionStyle:(UIExpansionStyle)expansionStyle animated:(BOOL)animated 36 | { 37 | if (expansionStyle != _expansionStyle) { 38 | _expansionStyle = expansionStyle; 39 | [self _updateDetailTextLabel]; 40 | } 41 | } 42 | 43 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 44 | { 45 | if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { 46 | [self _updateDetailTextLabel]; 47 | self.backgroundColor = [UIColor yellowColor]; 48 | } 49 | return self; 50 | } 51 | 52 | - (void)_updateDetailTextLabel 53 | { 54 | if (self.isLoading) { 55 | self.detailTextLabel.text = @"Loading data"; 56 | } else { 57 | switch (self.expansionStyle) { 58 | case UIExpansionStyleExpanded: 59 | self.detailTextLabel.text = @"Click to collapse"; 60 | break; 61 | case UIExpansionStyleCollapsed: 62 | self.detailTextLabel.text = @"Click to expand"; 63 | break; 64 | } 65 | } 66 | } 67 | 68 | @end 69 | 70 | 71 | @interface SLExpandableTableViewController () 72 | 73 | @property (nonatomic, strong) NSArray *firstSectionStrings; 74 | @property (nonatomic, strong) NSArray *secondSectionStrings; 75 | 76 | @property (nonatomic, strong) NSMutableArray *sectionsArray; 77 | 78 | @property (nonatomic, strong) NSMutableIndexSet *expandableSections; 79 | 80 | @end 81 | 82 | 83 | 84 | @implementation SLExpandableTableViewController 85 | 86 | #pragma mark - setters and getters 87 | 88 | #pragma mark - Initialization 89 | 90 | - (id)initWithStyle:(UITableViewStyle)style 91 | { 92 | if (self = [super initWithStyle:style]) { 93 | _firstSectionStrings = @[ @"Section 0 Row 0", @"Section 0 Row 1", @"Section 0 Row 2", @"Section 0 Row 3" ]; 94 | _secondSectionStrings = @[ @"Section 1 Row 0", @"Section 1 Row 1", @"Section 1 Row 2", @"Section 1 Row 3", @"Section 1 Row 4" ]; 95 | 96 | _sectionsArray = @[ _firstSectionStrings, _secondSectionStrings ].mutableCopy; 97 | _expandableSections = [NSMutableIndexSet indexSet]; 98 | } 99 | return self; 100 | } 101 | 102 | #pragma mark - Memory management 103 | 104 | - (void)didReceiveMemoryWarning 105 | { 106 | [super didReceiveMemoryWarning]; 107 | 108 | } 109 | 110 | #pragma mark - View lifecycle 111 | 112 | - (void)loadView 113 | { 114 | SLExpandableTableView *tableView = [[SLExpandableTableView alloc] initWithFrame:[UIScreen mainScreen].bounds style:UITableViewStylePlain]; 115 | tableView.dataSource = self; 116 | tableView.delegate = self; 117 | tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 118 | self.view = tableView; 119 | } 120 | 121 | #pragma mark - SLExpandableTableViewDatasource 122 | 123 | - (BOOL)tableView:(SLExpandableTableView *)tableView canExpandSection:(NSInteger)section 124 | { 125 | return YES; 126 | } 127 | 128 | - (BOOL)tableView:(SLExpandableTableView *)tableView needsToDownloadDataForExpandableSection:(NSInteger)section 129 | { 130 | return ![self.expandableSections containsIndex:section]; 131 | } 132 | 133 | - (UITableViewCell *)tableView:(SLExpandableTableView *)tableView expandingCellForSection:(NSInteger)section 134 | { 135 | static NSString *CellIdentifier = @"SLExpandableTableViewControllerHeaderCell"; 136 | SLExpandableTableViewControllerHeaderCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 137 | 138 | if (!cell) { 139 | cell = [[SLExpandableTableViewControllerHeaderCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier]; 140 | } 141 | 142 | cell.textLabel.text = [NSString stringWithFormat:@"Section %ld", (long)section]; 143 | 144 | return cell; 145 | } 146 | 147 | #pragma mark - SLExpandableTableViewDelegate 148 | 149 | - (void)tableView:(SLExpandableTableView *)tableView downloadDataForExpandableSection:(NSInteger)section 150 | { 151 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 152 | [self.expandableSections addIndex:section]; 153 | [tableView expandSection:section animated:YES]; 154 | }); 155 | } 156 | 157 | - (void)tableView:(SLExpandableTableView *)tableView didCollapseSection:(NSUInteger)section animated:(BOOL)animated 158 | { 159 | [self.expandableSections removeIndex:section]; 160 | } 161 | 162 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 163 | { 164 | if (indexPath.section > 0) { 165 | return 44.0 * 2.0; 166 | } 167 | 168 | return UITableViewAutomaticDimension; 169 | } 170 | 171 | #pragma mark - UITableViewDataSource 172 | 173 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 174 | { 175 | return self.sectionsArray.count; 176 | } 177 | 178 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 179 | { 180 | NSArray *dataArray = self.sectionsArray[section]; 181 | return dataArray.count + 1; 182 | } 183 | 184 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 185 | { 186 | static NSString *CellIdentifier = @"Cell"; 187 | 188 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 189 | if (cell == nil) { 190 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; 191 | } 192 | 193 | NSArray *dataArray = self.sectionsArray[indexPath.section]; 194 | cell.textLabel.text = dataArray[indexPath.row - 1]; 195 | 196 | return cell; 197 | } 198 | 199 | - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath 200 | { 201 | return YES; 202 | } 203 | 204 | - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath 205 | { 206 | if (indexPath.row == 0) { 207 | [self.sectionsArray removeObjectAtIndex:indexPath.section]; 208 | [tableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationAutomatic]; 209 | } 210 | } 211 | 212 | #pragma mark - UITableViewDelegate 213 | 214 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 215 | { 216 | [tableView deselectRowAtIndexPath:indexPath animated:NO]; 217 | } 218 | 219 | #pragma mark - Private category implementation () 220 | 221 | @end 222 | -------------------------------------------------------------------------------- /Tests/SLExpandableTableViewTests/SLExpandableTableViewTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | de.sparrow-labs.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Tests/SLExpandableTableViewTests/SLExpandableTableViewTests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_3_0 10 | #warning "This project uses features only available in iOS SDK 3.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /Tests/SLExpandableTableViewTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Tests/SLExpandableTableViewTests/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // SLExpandableTableViewTests 4 | // 5 | // Created by Oliver Letterer on 19.02.14. 6 | // Copyright (c) 2014 Sparrow-Labs. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "SLAppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([SLAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/Tests/Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | de.sparrow-labs.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/Tests/Tests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #import 10 | #endif 11 | -------------------------------------------------------------------------------- /Tests/Tests/Tests.m: -------------------------------------------------------------------------------- 1 | // 2 | // Tests.m 3 | // Tests 4 | // 5 | // Created by Oliver Letterer on 19.02.14. 6 | // Copyright (c) 2014 Sparrow-Labs. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "SLExpandableTableViewController.h" 12 | #import 13 | @import ObjectiveC.message; 14 | 15 | @interface Tests : XCTestCase @end 16 | 17 | @implementation Tests 18 | 19 | - (void)testThatOneCanSetTheDelegateToNil 20 | { 21 | SLExpandableTableViewController *viewController = [[SLExpandableTableViewController alloc] initWithStyle:UITableViewStylePlain]; 22 | if (!viewController.isViewLoaded) { 23 | [viewController loadView]; 24 | [viewController viewDidLoad]; 25 | } 26 | 27 | SLExpandableTableView *tableView = [[SLExpandableTableView alloc] initWithFrame:[UIScreen mainScreen].bounds style:UITableViewStylePlain]; 28 | tableView.delegate = viewController; 29 | tableView.dataSource = viewController; 30 | 31 | XCTAssertNotNil(tableView.delegate); 32 | XCTAssertNotNil(tableView.dataSource); 33 | 34 | tableView.delegate = nil; 35 | tableView.dataSource = nil; 36 | 37 | struct objc_super super = { 38 | .receiver = tableView, 39 | .super_class = [UITableView class] 40 | }; 41 | ((void(*)(struct objc_super *, SEL, id))objc_msgSendSuper)(&super, @selector(setDelegate:), nil); 42 | ((void(*)(struct objc_super *, SEL, id))objc_msgSendSuper)(&super, @selector(setDataSource:), nil); 43 | } 44 | 45 | - (void)testThatUserCanExpandAndCollapseSection0 46 | { 47 | [tester tapViewWithAccessibilityLabel:@"Section 0"]; 48 | [tester waitForViewWithAccessibilityLabel:@"Section 0 Row 1"]; 49 | [tester tapViewWithAccessibilityLabel:@"Section 0"]; 50 | [tester waitForAbsenceOfViewWithAccessibilityLabel:@"Section 0 Row 1"]; 51 | } 52 | 53 | - (void)testThatUserCanExpandAndCollapseSection0And1 54 | { 55 | [tester tapViewWithAccessibilityLabel:@"Section 0"]; 56 | [tester tapViewWithAccessibilityLabel:@"Section 1"]; 57 | [tester waitForViewWithAccessibilityLabel:@"Section 0 Row 1"]; 58 | [tester waitForViewWithAccessibilityLabel:@"Section 1 Row 1"]; 59 | 60 | [tester tapViewWithAccessibilityLabel:@"Section 0"]; 61 | [tester waitForAbsenceOfViewWithAccessibilityLabel:@"Section 0 Row 1"]; 62 | 63 | [tester tapViewWithAccessibilityLabel:@"Section 1"]; 64 | [tester waitForAbsenceOfViewWithAccessibilityLabel:@"Section 1 Row 1"]; 65 | } 66 | 67 | - (void)testThatUserCanExpandAndCollapseSection0And1ThenDelete 68 | { 69 | [tester tapViewWithAccessibilityLabel:@"Section 0"]; 70 | [tester waitForViewWithAccessibilityLabel:@"Section 0 Row 1"]; 71 | 72 | [tester swipeViewWithAccessibilityLabel:@"Section 0" inDirection:KIFSwipeDirectionLeft]; 73 | [tester tapViewWithAccessibilityLabel:@"Delete"]; 74 | [tester waitForAbsenceOfViewWithAccessibilityLabel:@"Section 0"]; 75 | } 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /Tests/Tests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | --------------------------------------------------------------------------------