├── .gitignore ├── .slather.yml ├── .travis.yml ├── LICENCE ├── Podfile ├── README.md ├── SWTableViewCell.podspec ├── SWTableViewCell.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ └── SWTableViewCell.xcscheme ├── SWTableViewCell ├── AppDelegate.h ├── AppDelegate.m ├── Default-568h@2x.png ├── Default.png ├── Default@2x.png ├── MI.png ├── PodFiles │ ├── NSMutableArray+SWUtilityButtons.h │ ├── NSMutableArray+SWUtilityButtons.m │ ├── SWCellScrollView.h │ ├── SWCellScrollView.m │ ├── SWLongPressGestureRecognizer.h │ ├── SWLongPressGestureRecognizer.m │ ├── SWTableViewCell.h │ ├── SWTableViewCell.m │ ├── SWUtilityButtonTapGestureRecognizer.h │ ├── SWUtilityButtonTapGestureRecognizer.m │ ├── SWUtilityButtonView.h │ └── SWUtilityButtonView.m ├── SWTableViewCell-Info.plist ├── SWTableViewCell-Prefix.pch ├── UMTableViewCell.h ├── UMTableViewCell.m ├── ViewController.h ├── ViewController.m ├── check@2x.png ├── clock@2x.png ├── cross@2x.png ├── en.lproj │ ├── InfoPlist.strings │ └── MainStoryboard.storyboard ├── list@2x.png ├── main.m └── um.png ├── SWTableViewCellTests ├── Info.plist └── SWTableViewCellTests.m └── github-assets ├── example1.gif ├── example2.gif ├── example3.gif └── example4.gif /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build/* 3 | *.pbxuser 4 | *.xcworkspace 5 | xcuserdata 6 | profile 7 | *.moved-aside 8 | xcuserdata/ 9 | project.xcworkspace/ 10 | Podfile.lock 11 | Pods 12 | -------------------------------------------------------------------------------- /.slather.yml: -------------------------------------------------------------------------------- 1 | ci_service: travis_ci 2 | coverage_service: coveralls 3 | xcodeproj: SWTableViewCell.xcodeproj 4 | source_directory: SWTableViewCell/PodFiles 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | cache: cocoapods 3 | xcode_workspace: SWTableViewCell.xcworkspace 4 | xcode_scheme: SWTableViewCell 5 | xcode_sdk: iphonesimulator 6 | 7 | before_install: 8 | - gem install cocoapods slather -N 9 | 10 | after_success: slather 11 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Christopher Wendel 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '6.0' 3 | 4 | target 'SWTableViewCell' do 5 | 6 | end 7 | 8 | target 'SWTableViewCellTests' do 9 | pod 'FBSnapshotTestCase' 10 | pod 'Expecta+Snapshots' 11 | pod 'Specta' 12 | pod 'Expecta' 13 | pod 'OCMock' 14 | end 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SWTableViewCell 2 | =============== 3 | 4 | [![Build Status](https://travis-ci.org/addoshi/SWTableViewCell.svg?branch=master)](https://travis-ci.org/addoshi/SWTableViewCell) 5 | [![Coverage Status](https://coveralls.io/repos/addoshi/SWTableViewCell/badge.svg)](https://coveralls.io/r/addoshi/SWTableViewCell) 6 | 7 |

8 | 9 | An easy-to-use UITableViewCell subclass that implements a swipeable content view which exposes utility buttons (similar to iOS 7 Mail Application) 10 | 11 | ##Usage 12 | In your Podfile: 13 |
pod 'SWTableViewCell', '~> 0.3.7'
14 | 15 | Or just clone this repo and manually add source to project 16 | 17 | ##Functionality 18 | ###Right Utility Buttons 19 | Utility buttons that become visible on the right side of the Table View Cell when the user swipes left. This behavior is similar to that seen in the iOS apps Mail and Reminders. 20 | 21 |

22 | 23 | ###Left Utility Buttons 24 | Utility buttons that become visible on the left side of the Table View Cell when the user swipes right. 25 | 26 |

27 | 28 | ###Features 29 | * Dynamic utility button scalling. As you add more buttons to a cell, the other buttons on that side get smaller to make room 30 | * Smart selection: The cell will pick up touch events and either scroll the cell back to center or fire the delegate method `- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath` 31 |

32 | So the cell will not be considered selected when the user touches the cell while utility buttons are visible, instead the cell will slide back into place (same as iOS 7 Mail App functionality) 33 | * Create utilty buttons with either a title or an icon along with a RGB color 34 | * Tested on iOS 6.1 and above, including iOS 7 35 | 36 | ##Usage 37 | 38 | ###Standard Table View Cells 39 | 40 | In your `tableView:cellForRowAtIndexPath:` method you set up the SWTableView cell and add an arbitrary amount of utility buttons to it using the included `NSMutableArray+SWUtilityButtons` category. 41 | 42 | ```objc 43 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 44 | static NSString *cellIdentifier = @"Cell"; 45 | 46 | SWTableViewCell *cell = (SWTableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 47 | 48 | if (cell == nil) { 49 | cell = [[SWTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier]; 50 | cell.leftUtilityButtons = [self leftButtons]; 51 | cell.rightUtilityButtons = [self rightButtons]; 52 | cell.delegate = self; 53 | } 54 | 55 | NSDate *dateObject = _testArray[indexPath.row]; 56 | cell.textLabel.text = [dateObject description]; 57 | cell.detailTextLabel.text = @"Some detail text"; 58 | 59 | return cell; 60 | } 61 | 62 | - (NSArray *)rightButtons 63 | { 64 | NSMutableArray *rightUtilityButtons = [NSMutableArray new]; 65 | [rightUtilityButtons sw_addUtilityButtonWithColor: 66 | [UIColor colorWithRed:0.78f green:0.78f blue:0.8f alpha:1.0] 67 | title:@"More"]; 68 | [rightUtilityButtons sw_addUtilityButtonWithColor: 69 | [UIColor colorWithRed:1.0f green:0.231f blue:0.188 alpha:1.0f] 70 | title:@"Delete"]; 71 | 72 | return rightUtilityButtons; 73 | } 74 | 75 | - (NSArray *)leftButtons 76 | { 77 | NSMutableArray *leftUtilityButtons = [NSMutableArray new]; 78 | 79 | [leftUtilityButtons sw_addUtilityButtonWithColor: 80 | [UIColor colorWithRed:0.07 green:0.75f blue:0.16f alpha:1.0] 81 | icon:[UIImage imageNamed:@"check.png"]]; 82 | [leftUtilityButtons sw_addUtilityButtonWithColor: 83 | [UIColor colorWithRed:1.0f green:1.0f blue:0.35f alpha:1.0] 84 | icon:[UIImage imageNamed:@"clock.png"]]; 85 | [leftUtilityButtons sw_addUtilityButtonWithColor: 86 | [UIColor colorWithRed:1.0f green:0.231f blue:0.188f alpha:1.0] 87 | icon:[UIImage imageNamed:@"cross.png"]]; 88 | [leftUtilityButtons sw_addUtilityButtonWithColor: 89 | [UIColor colorWithRed:0.55f green:0.27f blue:0.07f alpha:1.0] 90 | icon:[UIImage imageNamed:@"list.png"]]; 91 | 92 | return leftUtilityButtons; 93 | } 94 | ``` 95 | 96 | ###Custom Table View Cells 97 | 98 | Thanks to [Matt Bowman](https://github.com/MattCBowman) you can now create custom table view cells using Interface Builder that have the capabilities of an SWTableViewCell 99 | 100 | The first step is to design your cell either in a standalone nib or inside of a 101 | table view using prototype cells. Make sure to set the custom class on the 102 | cell in interface builder to the subclass you made for it: 103 | 104 |

105 | 106 | Then set the cell reuse identifier: 107 | 108 |

109 | 110 | When writing your custom table view cell's code, make sure your cell is a 111 | subclass of SWTableViewCell: 112 | 113 | ```objc 114 | 115 | #import 116 | 117 | @interface MyCustomTableViewCell : SWTableViewCell 118 | 119 | @property (weak, nonatomic) UILabel *customLabel; 120 | @property (weak, nonatomic) UIImageView *customImageView; 121 | 122 | @end 123 | 124 | ``` 125 | 126 | If you are using a separate nib and not a prototype cell, you'll need to be sure to register the nib in your table view: 127 | 128 | ```objc 129 | 130 | - (void)viewDidLoad 131 | { 132 | [super viewDidLoad]; 133 | 134 | [self.tableView registerNib:[UINib nibWithNibName:@"MyCustomTableViewCellNibFileName" bundle:nil] forCellReuseIdentifier:@"MyCustomCell"]; 135 | } 136 | 137 | ``` 138 | 139 | Then, in the `tableView:cellForRowAtIndexPath:` method of your `UITableViewDataSource` (usually your view controller), initialize your custom cell: 140 | 141 | ```objc 142 | - (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath 143 | { 144 | static NSString *cellIdentifier = @"MyCustomCell"; 145 | 146 | MyCustomTableViewCell *cell = (MyCustomTableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier 147 | forIndexPath:indexPath]; 148 | 149 | cell.leftUtilityButtons = [self leftButtons]; 150 | cell.rightUtilityButtons = [self rightButtons]; 151 | cell.delegate = self; 152 | 153 | cell.customLabel.text = @"Some Text"; 154 | cell.customImageView.image = [UIImage imageNamed:@"MyAwesomeTableCellImage"]; 155 | [cell setCellHeight:cell.frame.size.height]; 156 | return cell; 157 | } 158 | ``` 159 | 160 | ###Delegate 161 | 162 | The delegate `SWTableViewCellDelegate` is used by the developer to find out which button was pressed. There are five methods: 163 | 164 | ```objc 165 | // click event on left utility button 166 | - (void)swipeableTableViewCell:(SWTableViewCell *)cell didTriggerLeftUtilityButtonWithIndex:(NSInteger)index; 167 | 168 | // click event on right utility button 169 | - (void)swipeableTableViewCell:(SWTableViewCell *)cell didTriggerRightUtilityButtonWithIndex:(NSInteger)index; 170 | 171 | // utility button open/close event 172 | - (void)swipeableTableViewCell:(SWTableViewCell *)cell scrollingToState:(SWCellState)state; 173 | 174 | // prevent multiple cells from showing utilty buttons simultaneously 175 | - (BOOL)swipeableTableViewCellShouldHideUtilityButtonsOnSwipe:(SWTableViewCell *)cell; 176 | 177 | // prevent cell(s) from displaying left/right utility buttons 178 | - (BOOL)swipeableTableViewCell:(SWTableViewCell *)cell canSwipeToState:(SWCellState)state; 179 | ``` 180 | 181 | The index signifies which utility button the user pressed, for each side the button indices are ordered from right to left 0...n 182 | 183 | ####Example 184 | 185 | ```objc 186 | #pragma mark - SWTableViewDelegate 187 | 188 | - (void)swipeableTableViewCell:(SWTableViewCell *)cell didTriggerLeftUtilityButtonWithIndex:(NSInteger)index { 189 | switch (index) { 190 | case 0: 191 | NSLog(@"check button was pressed"); 192 | break; 193 | case 1: 194 | NSLog(@"clock button was pressed"); 195 | break; 196 | case 2: 197 | NSLog(@"cross button was pressed"); 198 | break; 199 | case 3: 200 | NSLog(@"list button was pressed"); 201 | default: 202 | break; 203 | } 204 | } 205 | 206 | - (void)swipeableTableViewCell:(SWTableViewCell *)cell didTriggerRightUtilityButtonWithIndex:(NSInteger)index { 207 | switch (index) { 208 | case 0: 209 | NSLog(@"More button was pressed"); 210 | break; 211 | case 1: 212 | { 213 | // Delete button was pressed 214 | NSIndexPath *cellIndexPath = [self.tableView indexPathForCell:cell]; 215 | 216 | [_testArray removeObjectAtIndex:cellIndexPath.row]; 217 | [self.tableView deleteRowsAtIndexPaths:@[cellIndexPath] 218 | withRowAnimation:UITableViewRowAnimationAutomatic]; 219 | break; 220 | } 221 | default: 222 | break; 223 | } 224 | } 225 | ``` 226 | 227 | (This is all code from the included example project) 228 | 229 | ###Gotchas 230 | 231 | #### Seperator Insets 232 | * If you have left utility button on iOS 7, I recommend changing your Table View's seperatorInset so the seperator stretches the length of the screen 233 |
 tableView.separatorInset = UIEdgeInsetsMake(0, 0, 0, 0); 
234 | 235 | 236 | ##Contributing 237 | Use [Github issues](https://github.com/cewendel/SWTableViewCell/issues) to track bugs and feature requests. 238 | 239 | ##Contact 240 | 241 | Chris Wendel 242 | 243 | - http://twitter.com/CEWendel 244 | 245 | ## Licence 246 | 247 | MIT 248 | 249 | 250 | 251 | 252 | 253 | -------------------------------------------------------------------------------- /SWTableViewCell.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'SWTableViewCell' 3 | s.version = '0.3.7' 4 | s.author = { 'Chris Wendel' => 'chriwend@umich.edu' } 5 | s.homepage = 'https://github.com/CEWendel/SWTableViewCell' 6 | s.summary = 'UITableViewCell subclass that implements a swipeable content view which exposes utility buttons.' 7 | s.license = 'MIT' 8 | s.source = { :git => 'https://github.com/CEWendel/SWTableViewCell.git', :tag => s.version.to_s } 9 | s.source_files = 'SWTableViewCell/PodFiles/*.{h,m}' 10 | s.platform = :ios 11 | s.ios.deployment_target = '6.0' 12 | s.requires_arc = true 13 | end 14 | -------------------------------------------------------------------------------- /SWTableViewCell.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 523AF8131189D5910D9959B7 /* libPods-SWTableViewCellTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 92B6FCB0D9F5F5976482C755 /* libPods-SWTableViewCellTests.a */; }; 11 | 76D732681AE2F50200909802 /* SWTableViewCellTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 76D732671AE2F50200909802 /* SWTableViewCellTests.m */; }; 12 | 810308911846579B00C378F0 /* NSMutableArray+SWUtilityButtons.m in Sources */ = {isa = PBXBuildFile; fileRef = 810308861846579B00C378F0 /* NSMutableArray+SWUtilityButtons.m */; }; 13 | 810308921846579B00C378F0 /* SWCellScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = 810308881846579B00C378F0 /* SWCellScrollView.m */; }; 14 | 810308931846579B00C378F0 /* SWLongPressGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8103088A1846579B00C378F0 /* SWLongPressGestureRecognizer.m */; }; 15 | 810308941846579B00C378F0 /* SWTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 8103088C1846579B00C378F0 /* SWTableViewCell.m */; }; 16 | 810308951846579B00C378F0 /* SWUtilityButtonTapGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8103088E1846579B00C378F0 /* SWUtilityButtonTapGestureRecognizer.m */; }; 17 | 810308961846579B00C378F0 /* SWUtilityButtonView.m in Sources */ = {isa = PBXBuildFile; fileRef = 810308901846579B00C378F0 /* SWUtilityButtonView.m */; }; 18 | 810308A2184D682700C378F0 /* um.png in Resources */ = {isa = PBXBuildFile; fileRef = 810308A1184D682700C378F0 /* um.png */; }; 19 | 810308A5184D688D00C378F0 /* UMTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 810308A4184D688D00C378F0 /* UMTableViewCell.m */; }; 20 | AF28B0F117F77DA300A77ABB /* clock@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AF28B0F017F77DA300A77ABB /* clock@2x.png */; }; 21 | AF28B0F317F77DA600A77ABB /* cross@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AF28B0F217F77DA600A77ABB /* cross@2x.png */; }; 22 | AF28B0F517F77DB000A77ABB /* check@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AF28B0F417F77DB000A77ABB /* check@2x.png */; }; 23 | AF28B0F717F77F1100A77ABB /* list@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AF28B0F617F77F1100A77ABB /* list@2x.png */; }; 24 | AF34B76917DEE2B200BD9082 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF34B76817DEE2B200BD9082 /* UIKit.framework */; }; 25 | AF34B76B17DEE2B200BD9082 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF34B76A17DEE2B200BD9082 /* Foundation.framework */; }; 26 | AF34B76D17DEE2B200BD9082 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF34B76C17DEE2B200BD9082 /* CoreGraphics.framework */; }; 27 | AF34B77317DEE2B400BD9082 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AF34B77117DEE2B400BD9082 /* InfoPlist.strings */; }; 28 | AF34B77517DEE2B400BD9082 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AF34B77417DEE2B400BD9082 /* main.m */; }; 29 | AF34B77917DEE2B400BD9082 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AF34B77817DEE2B400BD9082 /* AppDelegate.m */; }; 30 | AF34B77B17DEE2B600BD9082 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = AF34B77A17DEE2B600BD9082 /* Default.png */; }; 31 | AF34B77D17DEE2B600BD9082 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AF34B77C17DEE2B600BD9082 /* Default@2x.png */; }; 32 | AF34B77F17DEE2B600BD9082 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = AF34B77E17DEE2B600BD9082 /* Default-568h@2x.png */; }; 33 | AF34B78217DEE2B800BD9082 /* MainStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AF34B78017DEE2B700BD9082 /* MainStoryboard.storyboard */; }; 34 | AF34B78517DEE2B800BD9082 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AF34B78417DEE2B800BD9082 /* ViewController.m */; }; 35 | AFF15D1717F35E46007F5746 /* MI.png in Resources */ = {isa = PBXBuildFile; fileRef = AFF15D1617F35E46007F5746 /* MI.png */; }; 36 | /* End PBXBuildFile section */ 37 | 38 | /* Begin PBXContainerItemProxy section */ 39 | 76D732691AE2F50200909802 /* PBXContainerItemProxy */ = { 40 | isa = PBXContainerItemProxy; 41 | containerPortal = AF34B75D17DEE2AE00BD9082 /* Project object */; 42 | proxyType = 1; 43 | remoteGlobalIDString = AF34B76417DEE2B100BD9082; 44 | remoteInfo = SWTableViewCell; 45 | }; 46 | /* End PBXContainerItemProxy section */ 47 | 48 | /* Begin PBXFileReference section */ 49 | 561A417CAF3000B9397EB62B /* Pods-SWTableViewCellTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SWTableViewCellTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SWTableViewCellTests/Pods-SWTableViewCellTests.debug.xcconfig"; sourceTree = ""; }; 50 | 76D732631AE2F50100909802 /* SWTableViewCellTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SWTableViewCellTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 76D732661AE2F50200909802 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52 | 76D732671AE2F50200909802 /* SWTableViewCellTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SWTableViewCellTests.m; sourceTree = ""; }; 53 | 810308851846579B00C378F0 /* NSMutableArray+SWUtilityButtons.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableArray+SWUtilityButtons.h"; sourceTree = ""; }; 54 | 810308861846579B00C378F0 /* NSMutableArray+SWUtilityButtons.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableArray+SWUtilityButtons.m"; sourceTree = ""; }; 55 | 810308871846579B00C378F0 /* SWCellScrollView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SWCellScrollView.h; sourceTree = ""; }; 56 | 810308881846579B00C378F0 /* SWCellScrollView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SWCellScrollView.m; sourceTree = ""; }; 57 | 810308891846579B00C378F0 /* SWLongPressGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SWLongPressGestureRecognizer.h; sourceTree = ""; }; 58 | 8103088A1846579B00C378F0 /* SWLongPressGestureRecognizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SWLongPressGestureRecognizer.m; sourceTree = ""; }; 59 | 8103088B1846579B00C378F0 /* SWTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SWTableViewCell.h; sourceTree = ""; }; 60 | 8103088C1846579B00C378F0 /* SWTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SWTableViewCell.m; sourceTree = ""; }; 61 | 8103088D1846579B00C378F0 /* SWUtilityButtonTapGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SWUtilityButtonTapGestureRecognizer.h; sourceTree = ""; }; 62 | 8103088E1846579B00C378F0 /* SWUtilityButtonTapGestureRecognizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SWUtilityButtonTapGestureRecognizer.m; sourceTree = ""; }; 63 | 8103088F1846579B00C378F0 /* SWUtilityButtonView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SWUtilityButtonView.h; sourceTree = ""; }; 64 | 810308901846579B00C378F0 /* SWUtilityButtonView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SWUtilityButtonView.m; sourceTree = ""; }; 65 | 810308A1184D682700C378F0 /* um.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = um.png; sourceTree = ""; }; 66 | 810308A3184D688D00C378F0 /* UMTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UMTableViewCell.h; sourceTree = ""; }; 67 | 810308A4184D688D00C378F0 /* UMTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UMTableViewCell.m; sourceTree = ""; }; 68 | 92B6FCB0D9F5F5976482C755 /* libPods-SWTableViewCellTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SWTableViewCellTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 69 | AF28B0F017F77DA300A77ABB /* clock@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "clock@2x.png"; sourceTree = ""; }; 70 | AF28B0F217F77DA600A77ABB /* cross@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "cross@2x.png"; sourceTree = ""; }; 71 | AF28B0F417F77DB000A77ABB /* check@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "check@2x.png"; sourceTree = ""; }; 72 | AF28B0F617F77F1100A77ABB /* list@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "list@2x.png"; sourceTree = ""; }; 73 | AF2B0DA7183F029000334859 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 74 | AF34B76517DEE2B200BD9082 /* SWTableViewCell.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SWTableViewCell.app; sourceTree = BUILT_PRODUCTS_DIR; }; 75 | AF34B76817DEE2B200BD9082 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 76 | AF34B76A17DEE2B200BD9082 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 77 | AF34B76C17DEE2B200BD9082 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 78 | AF34B77017DEE2B300BD9082 /* SWTableViewCell-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SWTableViewCell-Info.plist"; sourceTree = ""; }; 79 | AF34B77217DEE2B400BD9082 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 80 | AF34B77417DEE2B400BD9082 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 81 | AF34B77617DEE2B400BD9082 /* SWTableViewCell-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SWTableViewCell-Prefix.pch"; sourceTree = ""; }; 82 | AF34B77717DEE2B400BD9082 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 83 | AF34B77817DEE2B400BD9082 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 84 | AF34B77A17DEE2B600BD9082 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; 85 | AF34B77C17DEE2B600BD9082 /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = ""; }; 86 | AF34B77E17DEE2B600BD9082 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 87 | AF34B78117DEE2B700BD9082 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/MainStoryboard.storyboard; sourceTree = ""; }; 88 | AF34B78317DEE2B800BD9082 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 89 | AF34B78417DEE2B800BD9082 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 90 | AFF15D1617F35E46007F5746 /* MI.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = MI.png; sourceTree = ""; }; 91 | E9AD27C1AFAEBD8EA1DEF5A5 /* Pods-SWTableViewCellTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SWTableViewCellTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-SWTableViewCellTests/Pods-SWTableViewCellTests.release.xcconfig"; sourceTree = ""; }; 92 | /* End PBXFileReference section */ 93 | 94 | /* Begin PBXFrameworksBuildPhase section */ 95 | 76D732601AE2F50100909802 /* Frameworks */ = { 96 | isa = PBXFrameworksBuildPhase; 97 | buildActionMask = 2147483647; 98 | files = ( 99 | 523AF8131189D5910D9959B7 /* libPods-SWTableViewCellTests.a in Frameworks */, 100 | ); 101 | runOnlyForDeploymentPostprocessing = 0; 102 | }; 103 | AF34B76217DEE2B100BD9082 /* Frameworks */ = { 104 | isa = PBXFrameworksBuildPhase; 105 | buildActionMask = 2147483647; 106 | files = ( 107 | AF34B76917DEE2B200BD9082 /* UIKit.framework in Frameworks */, 108 | AF34B76B17DEE2B200BD9082 /* Foundation.framework in Frameworks */, 109 | AF34B76D17DEE2B200BD9082 /* CoreGraphics.framework in Frameworks */, 110 | ); 111 | runOnlyForDeploymentPostprocessing = 0; 112 | }; 113 | /* End PBXFrameworksBuildPhase section */ 114 | 115 | /* Begin PBXGroup section */ 116 | 55277640D2AABB814E20D3F5 /* Pods */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 561A417CAF3000B9397EB62B /* Pods-SWTableViewCellTests.debug.xcconfig */, 120 | E9AD27C1AFAEBD8EA1DEF5A5 /* Pods-SWTableViewCellTests.release.xcconfig */, 121 | ); 122 | name = Pods; 123 | sourceTree = ""; 124 | }; 125 | 76D732641AE2F50100909802 /* SWTableViewCellTests */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 76D732671AE2F50200909802 /* SWTableViewCellTests.m */, 129 | 76D732651AE2F50200909802 /* Supporting Files */, 130 | ); 131 | path = SWTableViewCellTests; 132 | sourceTree = ""; 133 | }; 134 | 76D732651AE2F50200909802 /* Supporting Files */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 76D732661AE2F50200909802 /* Info.plist */, 138 | ); 139 | name = "Supporting Files"; 140 | sourceTree = ""; 141 | }; 142 | 810308841846579B00C378F0 /* PodFiles */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | 810308851846579B00C378F0 /* NSMutableArray+SWUtilityButtons.h */, 146 | 810308861846579B00C378F0 /* NSMutableArray+SWUtilityButtons.m */, 147 | 810308871846579B00C378F0 /* SWCellScrollView.h */, 148 | 810308881846579B00C378F0 /* SWCellScrollView.m */, 149 | 810308891846579B00C378F0 /* SWLongPressGestureRecognizer.h */, 150 | 8103088A1846579B00C378F0 /* SWLongPressGestureRecognizer.m */, 151 | 8103088B1846579B00C378F0 /* SWTableViewCell.h */, 152 | 8103088C1846579B00C378F0 /* SWTableViewCell.m */, 153 | 8103088D1846579B00C378F0 /* SWUtilityButtonTapGestureRecognizer.h */, 154 | 8103088E1846579B00C378F0 /* SWUtilityButtonTapGestureRecognizer.m */, 155 | 8103088F1846579B00C378F0 /* SWUtilityButtonView.h */, 156 | 810308901846579B00C378F0 /* SWUtilityButtonView.m */, 157 | ); 158 | path = PodFiles; 159 | sourceTree = ""; 160 | }; 161 | AF34B75C17DEE2AD00BD9082 = { 162 | isa = PBXGroup; 163 | children = ( 164 | AF34B76E17DEE2B200BD9082 /* SWTableViewCell */, 165 | 76D732641AE2F50100909802 /* SWTableViewCellTests */, 166 | AF34B76717DEE2B200BD9082 /* Frameworks */, 167 | AF34B76617DEE2B200BD9082 /* Products */, 168 | 55277640D2AABB814E20D3F5 /* Pods */, 169 | ); 170 | sourceTree = ""; 171 | }; 172 | AF34B76617DEE2B200BD9082 /* Products */ = { 173 | isa = PBXGroup; 174 | children = ( 175 | AF34B76517DEE2B200BD9082 /* SWTableViewCell.app */, 176 | 76D732631AE2F50100909802 /* SWTableViewCellTests.xctest */, 177 | ); 178 | name = Products; 179 | sourceTree = ""; 180 | }; 181 | AF34B76717DEE2B200BD9082 /* Frameworks */ = { 182 | isa = PBXGroup; 183 | children = ( 184 | AF2B0DA7183F029000334859 /* QuartzCore.framework */, 185 | AF34B76817DEE2B200BD9082 /* UIKit.framework */, 186 | AF34B76A17DEE2B200BD9082 /* Foundation.framework */, 187 | AF34B76C17DEE2B200BD9082 /* CoreGraphics.framework */, 188 | 92B6FCB0D9F5F5976482C755 /* libPods-SWTableViewCellTests.a */, 189 | ); 190 | name = Frameworks; 191 | sourceTree = ""; 192 | }; 193 | AF34B76E17DEE2B200BD9082 /* SWTableViewCell */ = { 194 | isa = PBXGroup; 195 | children = ( 196 | 810308A1184D682700C378F0 /* um.png */, 197 | 810308841846579B00C378F0 /* PodFiles */, 198 | AF34B77717DEE2B400BD9082 /* AppDelegate.h */, 199 | AF34B77817DEE2B400BD9082 /* AppDelegate.m */, 200 | AF34B78017DEE2B700BD9082 /* MainStoryboard.storyboard */, 201 | AF34B78317DEE2B800BD9082 /* ViewController.h */, 202 | AF34B78417DEE2B800BD9082 /* ViewController.m */, 203 | AF34B76F17DEE2B300BD9082 /* Supporting Files */, 204 | 810308A3184D688D00C378F0 /* UMTableViewCell.h */, 205 | 810308A4184D688D00C378F0 /* UMTableViewCell.m */, 206 | ); 207 | path = SWTableViewCell; 208 | sourceTree = ""; 209 | }; 210 | AF34B76F17DEE2B300BD9082 /* Supporting Files */ = { 211 | isa = PBXGroup; 212 | children = ( 213 | AF28B0F617F77F1100A77ABB /* list@2x.png */, 214 | AF28B0F417F77DB000A77ABB /* check@2x.png */, 215 | AF28B0F217F77DA600A77ABB /* cross@2x.png */, 216 | AF28B0F017F77DA300A77ABB /* clock@2x.png */, 217 | AFF15D1617F35E46007F5746 /* MI.png */, 218 | AF34B77017DEE2B300BD9082 /* SWTableViewCell-Info.plist */, 219 | AF34B77117DEE2B400BD9082 /* InfoPlist.strings */, 220 | AF34B77417DEE2B400BD9082 /* main.m */, 221 | AF34B77617DEE2B400BD9082 /* SWTableViewCell-Prefix.pch */, 222 | AF34B77A17DEE2B600BD9082 /* Default.png */, 223 | AF34B77C17DEE2B600BD9082 /* Default@2x.png */, 224 | AF34B77E17DEE2B600BD9082 /* Default-568h@2x.png */, 225 | ); 226 | name = "Supporting Files"; 227 | sourceTree = ""; 228 | }; 229 | /* End PBXGroup section */ 230 | 231 | /* Begin PBXNativeTarget section */ 232 | 76D732621AE2F50100909802 /* SWTableViewCellTests */ = { 233 | isa = PBXNativeTarget; 234 | buildConfigurationList = 76D7326B1AE2F50200909802 /* Build configuration list for PBXNativeTarget "SWTableViewCellTests" */; 235 | buildPhases = ( 236 | C67FB53E1CB021D85E035F1B /* Check Pods Manifest.lock */, 237 | 76D7325F1AE2F50100909802 /* Sources */, 238 | 76D732601AE2F50100909802 /* Frameworks */, 239 | 76D732611AE2F50100909802 /* Resources */, 240 | BE30338A7FDC2C4BE8F57ADB /* Copy Pods Resources */, 241 | ); 242 | buildRules = ( 243 | ); 244 | dependencies = ( 245 | 76D7326A1AE2F50200909802 /* PBXTargetDependency */, 246 | ); 247 | name = SWTableViewCellTests; 248 | productName = SWTableViewCellTests; 249 | productReference = 76D732631AE2F50100909802 /* SWTableViewCellTests.xctest */; 250 | productType = "com.apple.product-type.bundle.unit-test"; 251 | }; 252 | AF34B76417DEE2B100BD9082 /* SWTableViewCell */ = { 253 | isa = PBXNativeTarget; 254 | buildConfigurationList = AF34B78817DEE2B900BD9082 /* Build configuration list for PBXNativeTarget "SWTableViewCell" */; 255 | buildPhases = ( 256 | AF34B76117DEE2B100BD9082 /* Sources */, 257 | AF34B76217DEE2B100BD9082 /* Frameworks */, 258 | AF34B76317DEE2B100BD9082 /* Resources */, 259 | ); 260 | buildRules = ( 261 | ); 262 | dependencies = ( 263 | ); 264 | name = SWTableViewCell; 265 | productName = SWTableViewCell; 266 | productReference = AF34B76517DEE2B200BD9082 /* SWTableViewCell.app */; 267 | productType = "com.apple.product-type.application"; 268 | }; 269 | /* End PBXNativeTarget section */ 270 | 271 | /* Begin PBXProject section */ 272 | AF34B75D17DEE2AE00BD9082 /* Project object */ = { 273 | isa = PBXProject; 274 | attributes = { 275 | LastUpgradeCheck = 0460; 276 | ORGANIZATIONNAME = "Chris Wendel"; 277 | TargetAttributes = { 278 | 76D732621AE2F50100909802 = { 279 | CreatedOnToolsVersion = 6.3; 280 | TestTargetID = AF34B76417DEE2B100BD9082; 281 | }; 282 | AF34B76417DEE2B100BD9082 = { 283 | DevelopmentTeam = CRQMUWXT26; 284 | }; 285 | }; 286 | }; 287 | buildConfigurationList = AF34B76017DEE2AE00BD9082 /* Build configuration list for PBXProject "SWTableViewCell" */; 288 | compatibilityVersion = "Xcode 3.2"; 289 | developmentRegion = English; 290 | hasScannedForEncodings = 0; 291 | knownRegions = ( 292 | en, 293 | ); 294 | mainGroup = AF34B75C17DEE2AD00BD9082; 295 | productRefGroup = AF34B76617DEE2B200BD9082 /* Products */; 296 | projectDirPath = ""; 297 | projectRoot = ""; 298 | targets = ( 299 | AF34B76417DEE2B100BD9082 /* SWTableViewCell */, 300 | 76D732621AE2F50100909802 /* SWTableViewCellTests */, 301 | ); 302 | }; 303 | /* End PBXProject section */ 304 | 305 | /* Begin PBXResourcesBuildPhase section */ 306 | 76D732611AE2F50100909802 /* Resources */ = { 307 | isa = PBXResourcesBuildPhase; 308 | buildActionMask = 2147483647; 309 | files = ( 310 | ); 311 | runOnlyForDeploymentPostprocessing = 0; 312 | }; 313 | AF34B76317DEE2B100BD9082 /* Resources */ = { 314 | isa = PBXResourcesBuildPhase; 315 | buildActionMask = 2147483647; 316 | files = ( 317 | AF34B77317DEE2B400BD9082 /* InfoPlist.strings in Resources */, 318 | AF34B77B17DEE2B600BD9082 /* Default.png in Resources */, 319 | AF28B0F517F77DB000A77ABB /* check@2x.png in Resources */, 320 | AF34B77D17DEE2B600BD9082 /* Default@2x.png in Resources */, 321 | AF28B0F317F77DA600A77ABB /* cross@2x.png in Resources */, 322 | 810308A2184D682700C378F0 /* um.png in Resources */, 323 | AF34B77F17DEE2B600BD9082 /* Default-568h@2x.png in Resources */, 324 | AF28B0F717F77F1100A77ABB /* list@2x.png in Resources */, 325 | AF28B0F117F77DA300A77ABB /* clock@2x.png in Resources */, 326 | AF34B78217DEE2B800BD9082 /* MainStoryboard.storyboard in Resources */, 327 | AFF15D1717F35E46007F5746 /* MI.png in Resources */, 328 | ); 329 | runOnlyForDeploymentPostprocessing = 0; 330 | }; 331 | /* End PBXResourcesBuildPhase section */ 332 | 333 | /* Begin PBXShellScriptBuildPhase section */ 334 | BE30338A7FDC2C4BE8F57ADB /* Copy Pods Resources */ = { 335 | isa = PBXShellScriptBuildPhase; 336 | buildActionMask = 2147483647; 337 | files = ( 338 | ); 339 | inputPaths = ( 340 | ); 341 | name = "Copy Pods Resources"; 342 | outputPaths = ( 343 | ); 344 | runOnlyForDeploymentPostprocessing = 0; 345 | shellPath = /bin/sh; 346 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SWTableViewCellTests/Pods-SWTableViewCellTests-resources.sh\"\n"; 347 | showEnvVarsInLog = 0; 348 | }; 349 | C67FB53E1CB021D85E035F1B /* Check Pods Manifest.lock */ = { 350 | isa = PBXShellScriptBuildPhase; 351 | buildActionMask = 2147483647; 352 | files = ( 353 | ); 354 | inputPaths = ( 355 | ); 356 | name = "Check Pods Manifest.lock"; 357 | outputPaths = ( 358 | ); 359 | runOnlyForDeploymentPostprocessing = 0; 360 | shellPath = /bin/sh; 361 | 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"; 362 | showEnvVarsInLog = 0; 363 | }; 364 | /* End PBXShellScriptBuildPhase section */ 365 | 366 | /* Begin PBXSourcesBuildPhase section */ 367 | 76D7325F1AE2F50100909802 /* Sources */ = { 368 | isa = PBXSourcesBuildPhase; 369 | buildActionMask = 2147483647; 370 | files = ( 371 | 76D732681AE2F50200909802 /* SWTableViewCellTests.m in Sources */, 372 | ); 373 | runOnlyForDeploymentPostprocessing = 0; 374 | }; 375 | AF34B76117DEE2B100BD9082 /* Sources */ = { 376 | isa = PBXSourcesBuildPhase; 377 | buildActionMask = 2147483647; 378 | files = ( 379 | AF34B77517DEE2B400BD9082 /* main.m in Sources */, 380 | AF34B77917DEE2B400BD9082 /* AppDelegate.m in Sources */, 381 | 810308921846579B00C378F0 /* SWCellScrollView.m in Sources */, 382 | 810308911846579B00C378F0 /* NSMutableArray+SWUtilityButtons.m in Sources */, 383 | 810308961846579B00C378F0 /* SWUtilityButtonView.m in Sources */, 384 | 810308A5184D688D00C378F0 /* UMTableViewCell.m in Sources */, 385 | 810308941846579B00C378F0 /* SWTableViewCell.m in Sources */, 386 | AF34B78517DEE2B800BD9082 /* ViewController.m in Sources */, 387 | 810308951846579B00C378F0 /* SWUtilityButtonTapGestureRecognizer.m in Sources */, 388 | 810308931846579B00C378F0 /* SWLongPressGestureRecognizer.m in Sources */, 389 | ); 390 | runOnlyForDeploymentPostprocessing = 0; 391 | }; 392 | /* End PBXSourcesBuildPhase section */ 393 | 394 | /* Begin PBXTargetDependency section */ 395 | 76D7326A1AE2F50200909802 /* PBXTargetDependency */ = { 396 | isa = PBXTargetDependency; 397 | target = AF34B76417DEE2B100BD9082 /* SWTableViewCell */; 398 | targetProxy = 76D732691AE2F50200909802 /* PBXContainerItemProxy */; 399 | }; 400 | /* End PBXTargetDependency section */ 401 | 402 | /* Begin PBXVariantGroup section */ 403 | AF34B77117DEE2B400BD9082 /* InfoPlist.strings */ = { 404 | isa = PBXVariantGroup; 405 | children = ( 406 | AF34B77217DEE2B400BD9082 /* en */, 407 | ); 408 | name = InfoPlist.strings; 409 | sourceTree = ""; 410 | }; 411 | AF34B78017DEE2B700BD9082 /* MainStoryboard.storyboard */ = { 412 | isa = PBXVariantGroup; 413 | children = ( 414 | AF34B78117DEE2B700BD9082 /* en */, 415 | ); 416 | name = MainStoryboard.storyboard; 417 | sourceTree = ""; 418 | }; 419 | /* End PBXVariantGroup section */ 420 | 421 | /* Begin XCBuildConfiguration section */ 422 | 76D7326C1AE2F50200909802 /* Debug */ = { 423 | isa = XCBuildConfiguration; 424 | baseConfigurationReference = 561A417CAF3000B9397EB62B /* Pods-SWTableViewCellTests.debug.xcconfig */; 425 | buildSettings = { 426 | BUNDLE_LOADER = "$(TEST_HOST)"; 427 | CLANG_ENABLE_MODULES = YES; 428 | CLANG_WARN_BOOL_CONVERSION = YES; 429 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 430 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 431 | CLANG_WARN_UNREACHABLE_CODE = YES; 432 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 433 | ENABLE_STRICT_OBJC_MSGSEND = YES; 434 | FRAMEWORK_SEARCH_PATHS = ( 435 | "$(SDKROOT)/Developer/Library/Frameworks", 436 | "$(inherited)", 437 | ); 438 | GCC_NO_COMMON_BLOCKS = YES; 439 | GCC_PREPROCESSOR_DEFINITIONS = ( 440 | "DEBUG=1", 441 | "$(inherited)", 442 | ); 443 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 444 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 445 | GCC_WARN_UNDECLARED_SELECTOR = YES; 446 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 447 | GCC_WARN_UNUSED_FUNCTION = YES; 448 | INFOPLIST_FILE = SWTableViewCellTests/Info.plist; 449 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 450 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 451 | MTL_ENABLE_DEBUG_INFO = YES; 452 | PRODUCT_NAME = "$(TARGET_NAME)"; 453 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SWTableViewCell.app/SWTableViewCell"; 454 | }; 455 | name = Debug; 456 | }; 457 | 76D7326D1AE2F50200909802 /* Release */ = { 458 | isa = XCBuildConfiguration; 459 | baseConfigurationReference = E9AD27C1AFAEBD8EA1DEF5A5 /* Pods-SWTableViewCellTests.release.xcconfig */; 460 | buildSettings = { 461 | BUNDLE_LOADER = "$(TEST_HOST)"; 462 | CLANG_ENABLE_MODULES = YES; 463 | CLANG_WARN_BOOL_CONVERSION = YES; 464 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 465 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 466 | CLANG_WARN_UNREACHABLE_CODE = YES; 467 | COPY_PHASE_STRIP = NO; 468 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 469 | ENABLE_NS_ASSERTIONS = NO; 470 | ENABLE_STRICT_OBJC_MSGSEND = YES; 471 | FRAMEWORK_SEARCH_PATHS = ( 472 | "$(SDKROOT)/Developer/Library/Frameworks", 473 | "$(inherited)", 474 | ); 475 | GCC_NO_COMMON_BLOCKS = YES; 476 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 477 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 478 | GCC_WARN_UNDECLARED_SELECTOR = YES; 479 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 480 | GCC_WARN_UNUSED_FUNCTION = YES; 481 | INFOPLIST_FILE = SWTableViewCellTests/Info.plist; 482 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 483 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 484 | MTL_ENABLE_DEBUG_INFO = NO; 485 | PRODUCT_NAME = "$(TARGET_NAME)"; 486 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SWTableViewCell.app/SWTableViewCell"; 487 | }; 488 | name = Release; 489 | }; 490 | AF34B78617DEE2B800BD9082 /* Debug */ = { 491 | isa = XCBuildConfiguration; 492 | buildSettings = { 493 | ALWAYS_SEARCH_USER_PATHS = NO; 494 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 495 | CLANG_CXX_LIBRARY = "libc++"; 496 | CLANG_ENABLE_OBJC_ARC = YES; 497 | CLANG_WARN_CONSTANT_CONVERSION = YES; 498 | CLANG_WARN_EMPTY_BODY = YES; 499 | CLANG_WARN_ENUM_CONVERSION = YES; 500 | CLANG_WARN_INT_CONVERSION = YES; 501 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 502 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 503 | COPY_PHASE_STRIP = NO; 504 | GCC_C_LANGUAGE_STANDARD = gnu99; 505 | GCC_DYNAMIC_NO_PIC = NO; 506 | GCC_GENERATE_TEST_COVERAGE_FILES = YES; 507 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; 508 | GCC_OPTIMIZATION_LEVEL = 0; 509 | GCC_PREPROCESSOR_DEFINITIONS = ( 510 | "DEBUG=1", 511 | "$(inherited)", 512 | ); 513 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 514 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 515 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 516 | GCC_WARN_UNUSED_VARIABLE = YES; 517 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 518 | ONLY_ACTIVE_ARCH = YES; 519 | SDKROOT = iphoneos; 520 | }; 521 | name = Debug; 522 | }; 523 | AF34B78717DEE2B900BD9082 /* Release */ = { 524 | isa = XCBuildConfiguration; 525 | buildSettings = { 526 | ALWAYS_SEARCH_USER_PATHS = NO; 527 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 528 | CLANG_CXX_LIBRARY = "libc++"; 529 | CLANG_ENABLE_OBJC_ARC = YES; 530 | CLANG_WARN_CONSTANT_CONVERSION = YES; 531 | CLANG_WARN_EMPTY_BODY = YES; 532 | CLANG_WARN_ENUM_CONVERSION = YES; 533 | CLANG_WARN_INT_CONVERSION = YES; 534 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 535 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 536 | COPY_PHASE_STRIP = YES; 537 | GCC_C_LANGUAGE_STANDARD = gnu99; 538 | GCC_GENERATE_TEST_COVERAGE_FILES = YES; 539 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; 540 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 541 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 542 | GCC_WARN_UNUSED_VARIABLE = YES; 543 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 544 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 545 | SDKROOT = iphoneos; 546 | VALIDATE_PRODUCT = YES; 547 | }; 548 | name = Release; 549 | }; 550 | AF34B78917DEE2B900BD9082 /* Debug */ = { 551 | isa = XCBuildConfiguration; 552 | buildSettings = { 553 | CODE_SIGN_IDENTITY = "iPhone Developer"; 554 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 555 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 556 | GCC_PREFIX_HEADER = "SWTableViewCell/SWTableViewCell-Prefix.pch"; 557 | INFOPLIST_FILE = "SWTableViewCell/SWTableViewCell-Info.plist"; 558 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 559 | PRODUCT_NAME = "$(TARGET_NAME)"; 560 | PROVISIONING_PROFILE = ""; 561 | WRAPPER_EXTENSION = app; 562 | }; 563 | name = Debug; 564 | }; 565 | AF34B78A17DEE2B900BD9082 /* Release */ = { 566 | isa = XCBuildConfiguration; 567 | buildSettings = { 568 | CODE_SIGN_IDENTITY = "iPhone Developer"; 569 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 570 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 571 | GCC_PREFIX_HEADER = "SWTableViewCell/SWTableViewCell-Prefix.pch"; 572 | INFOPLIST_FILE = "SWTableViewCell/SWTableViewCell-Info.plist"; 573 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 574 | PRODUCT_NAME = "$(TARGET_NAME)"; 575 | PROVISIONING_PROFILE = ""; 576 | WRAPPER_EXTENSION = app; 577 | }; 578 | name = Release; 579 | }; 580 | /* End XCBuildConfiguration section */ 581 | 582 | /* Begin XCConfigurationList section */ 583 | 76D7326B1AE2F50200909802 /* Build configuration list for PBXNativeTarget "SWTableViewCellTests" */ = { 584 | isa = XCConfigurationList; 585 | buildConfigurations = ( 586 | 76D7326C1AE2F50200909802 /* Debug */, 587 | 76D7326D1AE2F50200909802 /* Release */, 588 | ); 589 | defaultConfigurationIsVisible = 0; 590 | defaultConfigurationName = Release; 591 | }; 592 | AF34B76017DEE2AE00BD9082 /* Build configuration list for PBXProject "SWTableViewCell" */ = { 593 | isa = XCConfigurationList; 594 | buildConfigurations = ( 595 | AF34B78617DEE2B800BD9082 /* Debug */, 596 | AF34B78717DEE2B900BD9082 /* Release */, 597 | ); 598 | defaultConfigurationIsVisible = 0; 599 | defaultConfigurationName = Release; 600 | }; 601 | AF34B78817DEE2B900BD9082 /* Build configuration list for PBXNativeTarget "SWTableViewCell" */ = { 602 | isa = XCConfigurationList; 603 | buildConfigurations = ( 604 | AF34B78917DEE2B900BD9082 /* Debug */, 605 | AF34B78A17DEE2B900BD9082 /* Release */, 606 | ); 607 | defaultConfigurationIsVisible = 0; 608 | defaultConfigurationName = Release; 609 | }; 610 | /* End XCConfigurationList section */ 611 | }; 612 | rootObject = AF34B75D17DEE2AE00BD9082 /* Project object */; 613 | } 614 | -------------------------------------------------------------------------------- /SWTableViewCell.xcodeproj/xcshareddata/xcschemes/SWTableViewCell.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 77 | 83 | 84 | 85 | 86 | 87 | 88 | 94 | 96 | 102 | 103 | 104 | 105 | 107 | 108 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /SWTableViewCell/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // SWTableViewCell 4 | // 5 | // Created by Chris Wendel on 9/10/13. 6 | // Copyright (c) 2013 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /SWTableViewCell/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // SWTableViewCell 4 | // 5 | // Created by Chris Wendel on 9/10/13. 6 | // Copyright (c) 2013 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @implementation AppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | // Override point for customization after application launch. 16 | return YES; 17 | } 18 | 19 | - (void)applicationWillResignActive:(UIApplication *)application 20 | { 21 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 22 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 23 | } 24 | 25 | - (void)applicationDidEnterBackground:(UIApplication *)application 26 | { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | - (void)applicationWillEnterForeground:(UIApplication *)application 32 | { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | - (void)applicationDidBecomeActive:(UIApplication *)application 37 | { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application 42 | { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /SWTableViewCell/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CEWendel/SWTableViewCell/a8a20f833c737270b0710a821cf095fd9f855d7a/SWTableViewCell/Default-568h@2x.png -------------------------------------------------------------------------------- /SWTableViewCell/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CEWendel/SWTableViewCell/a8a20f833c737270b0710a821cf095fd9f855d7a/SWTableViewCell/Default.png -------------------------------------------------------------------------------- /SWTableViewCell/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CEWendel/SWTableViewCell/a8a20f833c737270b0710a821cf095fd9f855d7a/SWTableViewCell/Default@2x.png -------------------------------------------------------------------------------- /SWTableViewCell/MI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CEWendel/SWTableViewCell/a8a20f833c737270b0710a821cf095fd9f855d7a/SWTableViewCell/MI.png -------------------------------------------------------------------------------- /SWTableViewCell/PodFiles/NSMutableArray+SWUtilityButtons.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableArray+SWUtilityButtons.h 3 | // SWTableViewCell 4 | // 5 | // Created by Matt Bowman on 11/27/13. 6 | // Copyright (c) 2013 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSMutableArray (SWUtilityButtons) 12 | 13 | - (void)sw_addUtilityButtonWithColor:(UIColor *)color title:(NSString *)title; 14 | - (void)sw_addUtilityButtonWithColor:(UIColor *)color attributedTitle:(NSAttributedString *)title; 15 | - (void)sw_addUtilityButtonWithColor:(UIColor *)color icon:(UIImage *)icon; 16 | - (void)sw_addUtilityButtonWithColor:(UIColor *)color normalIcon:(UIImage *)normalIcon selectedIcon:(UIImage *)selectedIcon; 17 | 18 | @end 19 | 20 | 21 | @interface NSArray (SWUtilityButtons) 22 | 23 | - (BOOL)sw_isEqualToButtons:(NSArray *)buttons; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /SWTableViewCell/PodFiles/NSMutableArray+SWUtilityButtons.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableArray+SWUtilityButtons.m 3 | // SWTableViewCell 4 | // 5 | // Created by Matt Bowman on 11/27/13. 6 | // Copyright (c) 2013 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import "NSMutableArray+SWUtilityButtons.h" 10 | 11 | @implementation NSMutableArray (SWUtilityButtons) 12 | 13 | - (void)sw_addUtilityButtonWithColor:(UIColor *)color title:(NSString *)title 14 | { 15 | UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 16 | button.backgroundColor = color; 17 | [button setTitle:title forState:UIControlStateNormal]; 18 | [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 19 | [button.titleLabel setAdjustsFontSizeToFitWidth:YES]; 20 | [self addObject:button]; 21 | } 22 | 23 | - (void)sw_addUtilityButtonWithColor:(UIColor *)color attributedTitle:(NSAttributedString *)title 24 | { 25 | UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 26 | button.backgroundColor = color; 27 | [button setAttributedTitle:title forState:UIControlStateNormal]; 28 | [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 29 | [self addObject:button]; 30 | } 31 | 32 | - (void)sw_addUtilityButtonWithColor:(UIColor *)color icon:(UIImage *)icon 33 | { 34 | UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 35 | button.backgroundColor = color; 36 | [button setImage:icon forState:UIControlStateNormal]; 37 | [self addObject:button]; 38 | } 39 | 40 | - (void)sw_addUtilityButtonWithColor:(UIColor *)color normalIcon:(UIImage *)normalIcon selectedIcon:(UIImage *)selectedIcon { 41 | UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 42 | button.backgroundColor = color; 43 | [button setImage:normalIcon forState:UIControlStateNormal]; 44 | [button setImage:selectedIcon forState:UIControlStateHighlighted]; 45 | [button setImage:selectedIcon forState:UIControlStateSelected]; 46 | [self addObject:button]; 47 | } 48 | 49 | @end 50 | 51 | 52 | @implementation NSArray (SWUtilityButtons) 53 | 54 | - (BOOL)sw_isEqualToButtons:(NSArray *)buttons 55 | { 56 | buttons = [buttons copy]; 57 | if (!buttons || self.count != buttons.count) return NO; 58 | 59 | for (NSUInteger idx = 0; idx < self.count; idx++) { 60 | id buttonA = self[idx]; 61 | id buttonB = buttons[idx]; 62 | if (![buttonA isKindOfClass:[UIButton class]] || ![buttonB isKindOfClass:[UIButton class]]) return NO; 63 | if (![[self class] sw_button:buttonA isEqualToButton:buttonB]) return NO; 64 | } 65 | 66 | return YES; 67 | } 68 | 69 | + (BOOL)sw_button:(UIButton *)buttonA isEqualToButton:(UIButton *)buttonB 70 | { 71 | if (!buttonA || !buttonB) return NO; 72 | 73 | UIColor *backgroundColorA = buttonA.backgroundColor; 74 | UIColor *backgroundColorB = buttonB.backgroundColor; 75 | BOOL haveEqualBackgroundColors = (!backgroundColorA && !backgroundColorB) || [backgroundColorA isEqual:backgroundColorB]; 76 | 77 | NSString *titleA = [buttonA titleForState:UIControlStateNormal]; 78 | NSString *titleB = [buttonB titleForState:UIControlStateNormal]; 79 | BOOL haveEqualTitles = (!titleA && !titleB) || [titleA isEqualToString:titleB]; 80 | 81 | UIImage *normalIconA = [buttonA imageForState:UIControlStateNormal]; 82 | UIImage *normalIconB = [buttonB imageForState:UIControlStateNormal]; 83 | BOOL haveEqualNormalIcons = (!normalIconA && !normalIconB) || [normalIconA isEqual:normalIconB]; 84 | 85 | UIImage *selectedIconA = [buttonA imageForState:UIControlStateSelected]; 86 | UIImage *selectedIconB = [buttonB imageForState:UIControlStateSelected]; 87 | BOOL haveEqualSelectedIcons = (!selectedIconA && !selectedIconB) || [selectedIconA isEqual:selectedIconB]; 88 | 89 | return haveEqualBackgroundColors && haveEqualTitles && haveEqualNormalIcons && haveEqualSelectedIcons; 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /SWTableViewCell/PodFiles/SWCellScrollView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SWCellScrollView.h 3 | // SWTableViewCell 4 | // 5 | // Created by Matt Bowman on 11/27/13. 6 | // Copyright (c) 2013 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SWCellScrollView : UIScrollView 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /SWTableViewCell/PodFiles/SWCellScrollView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SWCellScrollView.m 3 | // SWTableViewCell 4 | // 5 | // Created by Matt Bowman on 11/27/13. 6 | // Copyright (c) 2013 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import "SWCellScrollView.h" 10 | 11 | @implementation SWCellScrollView 12 | 13 | - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer 14 | { 15 | if (gestureRecognizer == self.panGestureRecognizer) { 16 | CGPoint translation = [(UIPanGestureRecognizer*)gestureRecognizer translationInView:gestureRecognizer.view]; 17 | return fabs(translation.y) <= fabs(translation.x); 18 | } else { 19 | return YES; 20 | } 21 | } 22 | 23 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { 24 | // Find out if the user is actively scrolling the tableView of which this is a member. 25 | // If they are, return NO, and don't let the gesture recognizers work simultaneously. 26 | // 27 | // This works very well in maintaining user expectations while still allowing for the user to 28 | // scroll the cell sideways when that is their true intent. 29 | if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) { 30 | 31 | // Find the current scrolling velocity in that view, in the Y direction. 32 | CGFloat yVelocity = [(UIPanGestureRecognizer*)gestureRecognizer velocityInView:gestureRecognizer.view].y; 33 | 34 | // Return YES iff the user is not actively scrolling up. 35 | return fabs(yVelocity) <= 0.25; 36 | 37 | } 38 | return YES; 39 | } 40 | 41 | @end 42 | 43 | -------------------------------------------------------------------------------- /SWTableViewCell/PodFiles/SWLongPressGestureRecognizer.h: -------------------------------------------------------------------------------- 1 | // 2 | // SWLongPressGestureRecognizer.h 3 | // SWTableViewCell 4 | // 5 | // Created by Matt Bowman on 11/27/13. 6 | // Copyright (c) 2013 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface SWLongPressGestureRecognizer : UILongPressGestureRecognizer 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /SWTableViewCell/PodFiles/SWLongPressGestureRecognizer.m: -------------------------------------------------------------------------------- 1 | // 2 | // SWLongPressGestureRecognizer.m 3 | // SWTableViewCell 4 | // 5 | // Created by Matt Bowman on 11/27/13. 6 | // Copyright (c) 2013 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import "SWLongPressGestureRecognizer.h" 10 | 11 | @implementation SWLongPressGestureRecognizer 12 | 13 | -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 14 | { 15 | [super touchesBegan:touches withEvent:event]; 16 | } 17 | 18 | -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 19 | { 20 | [super touchesMoved:touches withEvent:event]; 21 | 22 | self.state = UIGestureRecognizerStateFailed; 23 | } 24 | 25 | -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 26 | { 27 | [super touchesEnded:touches withEvent:event]; 28 | 29 | self.state = UIGestureRecognizerStateFailed; 30 | } 31 | 32 | @end 33 | 34 | -------------------------------------------------------------------------------- /SWTableViewCell/PodFiles/SWTableViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // SWTableViewCell.h 3 | // SWTableViewCell 4 | // 5 | // Created by Chris Wendel on 9/10/13. 6 | // Copyright (c) 2013 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "SWCellScrollView.h" 12 | #import "SWLongPressGestureRecognizer.h" 13 | #import "SWUtilityButtonTapGestureRecognizer.h" 14 | #import "NSMutableArray+SWUtilityButtons.h" 15 | 16 | @class SWTableViewCell; 17 | 18 | typedef NS_ENUM(NSInteger, SWCellState) 19 | { 20 | kCellStateCenter, 21 | kCellStateLeft, 22 | kCellStateRight, 23 | }; 24 | 25 | @protocol SWTableViewCellDelegate 26 | 27 | @optional 28 | - (void)swipeableTableViewCell:(SWTableViewCell *)cell didTriggerLeftUtilityButtonWithIndex:(NSInteger)index; 29 | - (void)swipeableTableViewCell:(SWTableViewCell *)cell didTriggerRightUtilityButtonWithIndex:(NSInteger)index; 30 | - (void)swipeableTableViewCell:(SWTableViewCell *)cell scrollingToState:(SWCellState)state; 31 | - (BOOL)swipeableTableViewCellShouldHideUtilityButtonsOnSwipe:(SWTableViewCell *)cell; 32 | - (BOOL)swipeableTableViewCell:(SWTableViewCell *)cell canSwipeToState:(SWCellState)state; 33 | - (void)swipeableTableViewCellDidEndScrolling:(SWTableViewCell *)cell; 34 | - (void)swipeableTableViewCell:(SWTableViewCell *)cell didScroll:(UIScrollView *)scrollView; 35 | 36 | @end 37 | 38 | @interface SWTableViewCell : UITableViewCell 39 | 40 | @property (nonatomic, copy) NSArray *leftUtilityButtons; 41 | @property (nonatomic, copy) NSArray *rightUtilityButtons; 42 | 43 | @property (nonatomic, weak) id delegate; 44 | 45 | - (void)setRightUtilityButtons:(NSArray *)rightUtilityButtons WithButtonWidth:(CGFloat) width; 46 | - (void)setLeftUtilityButtons:(NSArray *)leftUtilityButtons WithButtonWidth:(CGFloat) width; 47 | - (void)hideUtilityButtonsAnimated:(BOOL)animated; 48 | - (void)showLeftUtilityButtonsAnimated:(BOOL)animated; 49 | - (void)showRightUtilityButtonsAnimated:(BOOL)animated; 50 | 51 | - (BOOL)isUtilityButtonsHidden; 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /SWTableViewCell/PodFiles/SWTableViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // SWTableViewCell.m 3 | // SWTableViewCell 4 | // 5 | // Created by Chris Wendel on 9/10/13. 6 | // Copyright (c) 2013 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import "SWTableViewCell.h" 10 | #import "SWUtilityButtonView.h" 11 | 12 | static NSString * const kTableViewCellContentView = @"UITableViewCellContentView"; 13 | 14 | #define kSectionIndexWidth 15 15 | #define kAccessoryTrailingSpace 15 16 | #define kLongPressMinimumDuration 0.16f 17 | 18 | @interface SWTableViewCell () 19 | 20 | @property (nonatomic, weak) UITableView *containingTableView; 21 | 22 | @property (nonatomic, strong) UIPanGestureRecognizer *tableViewPanGestureRecognizer; 23 | 24 | @property (nonatomic, assign) SWCellState cellState; // The state of the cell within the scroll view, can be left, right or middle 25 | @property (nonatomic, assign) CGFloat additionalRightPadding; 26 | 27 | @property (nonatomic, strong) UIScrollView *cellScrollView; 28 | @property (nonatomic, strong) SWUtilityButtonView *leftUtilityButtonsView, *rightUtilityButtonsView; 29 | @property (nonatomic, strong) UIView *leftUtilityClipView, *rightUtilityClipView; 30 | @property (nonatomic, strong) NSLayoutConstraint *leftUtilityClipConstraint, *rightUtilityClipConstraint; 31 | 32 | @property (nonatomic, strong) UILongPressGestureRecognizer *longPressGestureRecognizer; 33 | @property (nonatomic, strong) UITapGestureRecognizer *tapGestureRecognizer; 34 | 35 | - (CGFloat)leftUtilityButtonsWidth; 36 | - (CGFloat)rightUtilityButtonsWidth; 37 | - (CGFloat)utilityButtonsPadding; 38 | 39 | - (CGPoint)contentOffsetForCellState:(SWCellState)state; 40 | - (void)updateCellState; 41 | 42 | - (BOOL)shouldHighlight; 43 | 44 | @end 45 | 46 | @implementation SWTableViewCell { 47 | UIView *_contentCellView; 48 | BOOL layoutUpdating; 49 | } 50 | 51 | #pragma mark Initializers 52 | 53 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 54 | { 55 | self = [super initWithCoder:aDecoder]; 56 | 57 | if (self) 58 | { 59 | [self initializer]; 60 | } 61 | 62 | return self; 63 | } 64 | 65 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 66 | { 67 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 68 | 69 | if (self) 70 | { 71 | [self initializer]; 72 | } 73 | 74 | return self; 75 | } 76 | 77 | - (void)initializer 78 | { 79 | layoutUpdating = NO; 80 | // Set up scroll view that will host our cell content 81 | self.cellScrollView = [[SWCellScrollView alloc] init]; 82 | self.cellScrollView.translatesAutoresizingMaskIntoConstraints = NO; 83 | self.cellScrollView.delegate = self; 84 | self.cellScrollView.showsHorizontalScrollIndicator = NO; 85 | self.cellScrollView.scrollsToTop = NO; 86 | self.cellScrollView.scrollEnabled = YES; 87 | 88 | _contentCellView = [[UIView alloc] init]; 89 | [self.cellScrollView addSubview:_contentCellView]; 90 | 91 | // Add the cell scroll view to the cell 92 | UIView *contentViewParent = self; 93 | UIView *clipViewParent = self.cellScrollView; 94 | if (![NSStringFromClass([[self.subviews objectAtIndex:0] class]) isEqualToString:kTableViewCellContentView]) 95 | { 96 | // iOS 7 97 | contentViewParent = [self.subviews objectAtIndex:0]; 98 | clipViewParent = self; 99 | } 100 | NSArray *cellSubviews = [contentViewParent subviews]; 101 | [self insertSubview:self.cellScrollView atIndex:0]; 102 | for (UIView *subview in cellSubviews) 103 | { 104 | [_contentCellView addSubview:subview]; 105 | } 106 | 107 | // Set scroll view to perpetually have same frame as self. Specifying relative to superview doesn't work, since the latter UITableViewCellScrollView has different behaviour. 108 | [self addConstraints:@[ 109 | [NSLayoutConstraint constraintWithItem:self.cellScrollView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0], 110 | [NSLayoutConstraint constraintWithItem:self.cellScrollView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0], 111 | [NSLayoutConstraint constraintWithItem:self.cellScrollView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0.0], 112 | [NSLayoutConstraint constraintWithItem:self.cellScrollView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1.0 constant:0.0], 113 | ]]; 114 | 115 | self.tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(scrollViewTapped:)]; 116 | self.tapGestureRecognizer.cancelsTouchesInView = NO; 117 | self.tapGestureRecognizer.delegate = self; 118 | [self.cellScrollView addGestureRecognizer:self.tapGestureRecognizer]; 119 | 120 | self.longPressGestureRecognizer = [[SWLongPressGestureRecognizer alloc] initWithTarget:self action:@selector(scrollViewPressed:)]; 121 | self.longPressGestureRecognizer.cancelsTouchesInView = NO; 122 | self.longPressGestureRecognizer.minimumPressDuration = kLongPressMinimumDuration; 123 | self.longPressGestureRecognizer.delegate = self; 124 | [self.cellScrollView addGestureRecognizer:self.longPressGestureRecognizer]; 125 | 126 | // Create the left and right utility button views, as well as vanilla UIViews in which to embed them. We can manipulate the latter in order to effect clipping according to scroll position. 127 | // Such an approach is necessary in order for the utility views to sit on top to get taps, as well as allow the backgroundColor (and private UITableViewCellBackgroundView) to work properly. 128 | 129 | self.leftUtilityClipView = [[UIView alloc] init]; 130 | self.leftUtilityClipConstraint = [NSLayoutConstraint constraintWithItem:self.leftUtilityClipView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0.0]; 131 | self.leftUtilityButtonsView = [[SWUtilityButtonView alloc] initWithUtilityButtons:nil 132 | parentCell:self 133 | utilityButtonSelector:@selector(leftUtilityButtonHandler:)]; 134 | 135 | self.rightUtilityClipView = [[UIView alloc] initWithFrame:self.bounds]; 136 | self.rightUtilityClipConstraint = [NSLayoutConstraint constraintWithItem:self.rightUtilityClipView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1.0 constant:0.0]; 137 | self.rightUtilityButtonsView = [[SWUtilityButtonView alloc] initWithUtilityButtons:nil 138 | parentCell:self 139 | utilityButtonSelector:@selector(rightUtilityButtonHandler:)]; 140 | 141 | 142 | UIView *clipViews[] = { self.rightUtilityClipView, self.leftUtilityClipView }; 143 | NSLayoutConstraint *clipConstraints[] = { self.rightUtilityClipConstraint, self.leftUtilityClipConstraint }; 144 | UIView *buttonViews[] = { self.rightUtilityButtonsView, self.leftUtilityButtonsView }; 145 | NSLayoutAttribute alignmentAttributes[] = { NSLayoutAttributeRight, NSLayoutAttributeLeft }; 146 | 147 | for (NSUInteger i = 0; i < 2; ++i) 148 | { 149 | UIView *clipView = clipViews[i]; 150 | NSLayoutConstraint *clipConstraint = clipConstraints[i]; 151 | UIView *buttonView = buttonViews[i]; 152 | NSLayoutAttribute alignmentAttribute = alignmentAttributes[i]; 153 | 154 | clipConstraint.priority = UILayoutPriorityDefaultHigh; 155 | 156 | clipView.translatesAutoresizingMaskIntoConstraints = NO; 157 | clipView.clipsToBounds = YES; 158 | 159 | [clipViewParent addSubview:clipView]; 160 | [self addConstraints:@[ 161 | // Pin the clipping view to the appropriate outer edges of the cell. 162 | [NSLayoutConstraint constraintWithItem:clipView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0], 163 | [NSLayoutConstraint constraintWithItem:clipView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0], 164 | [NSLayoutConstraint constraintWithItem:clipView attribute:alignmentAttribute relatedBy:NSLayoutRelationEqual toItem:self attribute:alignmentAttribute multiplier:1.0 constant:0.0], 165 | clipConstraint, 166 | ]]; 167 | 168 | [clipView addSubview:buttonView]; 169 | [self addConstraints:@[ 170 | // Pin the button view to the appropriate outer edges of its clipping view. 171 | [NSLayoutConstraint constraintWithItem:buttonView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:clipView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0], 172 | [NSLayoutConstraint constraintWithItem:buttonView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:clipView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0], 173 | [NSLayoutConstraint constraintWithItem:buttonView attribute:alignmentAttribute relatedBy:NSLayoutRelationEqual toItem:clipView attribute:alignmentAttribute multiplier:1.0 constant:0.0], 174 | 175 | // Constrain the maximum button width so that at least a button's worth of contentView is left visible. (The button view will shrink accordingly.) 176 | [NSLayoutConstraint constraintWithItem:buttonView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationLessThanOrEqual toItem:self.contentView attribute:NSLayoutAttributeWidth multiplier:1.0 constant:-kUtilityButtonWidthDefault], 177 | ]]; 178 | } 179 | } 180 | 181 | static NSString * const kTableViewPanState = @"state"; 182 | 183 | - (void)removeOldTableViewPanObserver 184 | { 185 | [_tableViewPanGestureRecognizer removeObserver:self forKeyPath:kTableViewPanState]; 186 | } 187 | 188 | - (void)dealloc 189 | { 190 | _cellScrollView.delegate = nil; 191 | [self removeOldTableViewPanObserver]; 192 | } 193 | 194 | - (void)setContainingTableView:(UITableView *)containingTableView 195 | { 196 | [self removeOldTableViewPanObserver]; 197 | 198 | _tableViewPanGestureRecognizer = containingTableView.panGestureRecognizer; 199 | 200 | _containingTableView = containingTableView; 201 | 202 | if (containingTableView) 203 | { 204 | // Check if the UITableView will display Indices on the right. If that's the case, add a padding 205 | if ([_containingTableView.dataSource respondsToSelector:@selector(sectionIndexTitlesForTableView:)]) 206 | { 207 | NSArray *indices = [_containingTableView.dataSource sectionIndexTitlesForTableView:_containingTableView]; 208 | self.additionalRightPadding = indices == nil ? 0 : kSectionIndexWidth; 209 | } 210 | 211 | _containingTableView.directionalLockEnabled = YES; 212 | 213 | [self.tapGestureRecognizer requireGestureRecognizerToFail:_containingTableView.panGestureRecognizer]; 214 | 215 | [_tableViewPanGestureRecognizer addObserver:self forKeyPath:kTableViewPanState options:0 context:nil]; 216 | } 217 | } 218 | 219 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 220 | { 221 | if([keyPath isEqualToString:kTableViewPanState] && object == _tableViewPanGestureRecognizer) 222 | { 223 | if(_tableViewPanGestureRecognizer.state == UIGestureRecognizerStateBegan) 224 | { 225 | CGPoint locationInTableView = [_tableViewPanGestureRecognizer locationInView:_containingTableView]; 226 | 227 | BOOL inCurrentCell = CGRectContainsPoint(self.frame, locationInTableView); 228 | if(!inCurrentCell && _cellState != kCellStateCenter) 229 | { 230 | if ([self.delegate respondsToSelector:@selector(swipeableTableViewCellShouldHideUtilityButtonsOnSwipe:)]) 231 | { 232 | if([self.delegate swipeableTableViewCellShouldHideUtilityButtonsOnSwipe:self]) 233 | { 234 | [self hideUtilityButtonsAnimated:YES]; 235 | } 236 | } 237 | } 238 | } 239 | } 240 | } 241 | 242 | - (void)setLeftUtilityButtons:(NSArray *)leftUtilityButtons 243 | { 244 | if (![_leftUtilityButtons sw_isEqualToButtons:leftUtilityButtons]) { 245 | _leftUtilityButtons = leftUtilityButtons; 246 | 247 | self.leftUtilityButtonsView.utilityButtons = leftUtilityButtons; 248 | 249 | [self.leftUtilityButtonsView layoutIfNeeded]; 250 | [self layoutIfNeeded]; 251 | } 252 | } 253 | 254 | - (void)setLeftUtilityButtons:(NSArray *)leftUtilityButtons WithButtonWidth:(CGFloat) width 255 | { 256 | _leftUtilityButtons = leftUtilityButtons; 257 | 258 | [self.leftUtilityButtonsView setUtilityButtons:leftUtilityButtons WithButtonWidth:width]; 259 | 260 | [self.leftUtilityButtonsView layoutIfNeeded]; 261 | [self layoutIfNeeded]; 262 | } 263 | 264 | - (void)setRightUtilityButtons:(NSArray *)rightUtilityButtons 265 | { 266 | if (![_rightUtilityButtons sw_isEqualToButtons:rightUtilityButtons]) { 267 | _rightUtilityButtons = rightUtilityButtons; 268 | 269 | self.rightUtilityButtonsView.utilityButtons = rightUtilityButtons; 270 | 271 | [self.rightUtilityButtonsView layoutIfNeeded]; 272 | [self layoutIfNeeded]; 273 | } 274 | } 275 | 276 | - (void)setRightUtilityButtons:(NSArray *)rightUtilityButtons WithButtonWidth:(CGFloat) width 277 | { 278 | _rightUtilityButtons = rightUtilityButtons; 279 | 280 | [self.rightUtilityButtonsView setUtilityButtons:rightUtilityButtons WithButtonWidth:width]; 281 | 282 | [self.rightUtilityButtonsView layoutIfNeeded]; 283 | [self layoutIfNeeded]; 284 | } 285 | 286 | #pragma mark - UITableViewCell overrides 287 | 288 | - (void)didMoveToSuperview 289 | { 290 | self.containingTableView = nil; 291 | UIView *view = self.superview; 292 | 293 | do { 294 | if ([view isKindOfClass:[UITableView class]]) 295 | { 296 | self.containingTableView = (UITableView *)view; 297 | break; 298 | } 299 | } while ((view = view.superview)); 300 | } 301 | 302 | - (void)layoutSubviews 303 | { 304 | [super layoutSubviews]; 305 | 306 | // Offset the contentView origin so that it appears correctly w/rt the enclosing scroll view (to which we moved it). 307 | CGRect frame = self.contentView.frame; 308 | frame.origin.x = [self leftUtilityButtonsWidth]; 309 | _contentCellView.frame = frame; 310 | 311 | self.cellScrollView.contentSize = CGSizeMake(CGRectGetWidth(self.frame) + [self utilityButtonsPadding], CGRectGetHeight(self.frame)); 312 | 313 | if (!self.cellScrollView.isTracking && !self.cellScrollView.isDecelerating) 314 | { 315 | self.cellScrollView.contentOffset = [self contentOffsetForCellState:_cellState]; 316 | } 317 | 318 | [self updateCellState]; 319 | } 320 | 321 | - (void)setFrame:(CGRect)frame 322 | { 323 | layoutUpdating = YES; 324 | // Fix for new screen sizes 325 | // Initially, the cell is still 320 points wide 326 | // We need to layout our subviews again when this changes so our constraints clip to the right width 327 | BOOL widthChanged = (self.frame.size.width != frame.size.width); 328 | 329 | [super setFrame:frame]; 330 | 331 | if (widthChanged) 332 | { 333 | [self layoutIfNeeded]; 334 | } 335 | layoutUpdating = NO; 336 | } 337 | 338 | - (void)prepareForReuse 339 | { 340 | [super prepareForReuse]; 341 | 342 | [self hideUtilityButtonsAnimated:NO]; 343 | } 344 | 345 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated 346 | { 347 | // Work around stupid background-destroying override magic that UITableView seems to perform on contained buttons. 348 | 349 | [self.leftUtilityButtonsView pushBackgroundColors]; 350 | [self.rightUtilityButtonsView pushBackgroundColors]; 351 | 352 | [super setSelected:selected animated:animated]; 353 | 354 | [self.leftUtilityButtonsView popBackgroundColors]; 355 | [self.rightUtilityButtonsView popBackgroundColors]; 356 | } 357 | 358 | - (void)didTransitionToState:(UITableViewCellStateMask)state { 359 | [super didTransitionToState:state]; 360 | 361 | if (state == UITableViewCellStateDefaultMask) { 362 | [self layoutSubviews]; 363 | } 364 | } 365 | 366 | #pragma mark - Selection handling 367 | 368 | - (BOOL)shouldHighlight 369 | { 370 | BOOL shouldHighlight = YES; 371 | 372 | if ([self.containingTableView.delegate respondsToSelector:@selector(tableView:shouldHighlightRowAtIndexPath:)]) 373 | { 374 | NSIndexPath *cellIndexPath = [self.containingTableView indexPathForCell:self]; 375 | 376 | shouldHighlight = [self.containingTableView.delegate tableView:self.containingTableView shouldHighlightRowAtIndexPath:cellIndexPath]; 377 | } 378 | 379 | return shouldHighlight; 380 | } 381 | 382 | - (void)scrollViewPressed:(UIGestureRecognizer *)gestureRecognizer 383 | { 384 | if (gestureRecognizer.state == UIGestureRecognizerStateBegan && !self.isHighlighted && self.shouldHighlight) 385 | { 386 | [self setHighlighted:YES animated:NO]; 387 | } 388 | 389 | else if (gestureRecognizer.state == UIGestureRecognizerStateEnded) 390 | { 391 | // Cell is already highlighted; clearing it temporarily seems to address visual anomaly. 392 | [self setHighlighted:NO animated:NO]; 393 | [self scrollViewTapped:gestureRecognizer]; 394 | } 395 | 396 | else if (gestureRecognizer.state == UIGestureRecognizerStateCancelled) 397 | { 398 | [self setHighlighted:NO animated:NO]; 399 | } 400 | } 401 | 402 | - (void)scrollViewTapped:(UIGestureRecognizer *)gestureRecognizer 403 | { 404 | if (_cellState == kCellStateCenter) 405 | { 406 | if (self.isSelected) 407 | { 408 | [self deselectCell]; 409 | } 410 | else if (self.shouldHighlight) // UITableView refuses selection if highlight is also refused. 411 | { 412 | [self selectCell]; 413 | } 414 | } 415 | else 416 | { 417 | // Scroll back to center 418 | [self hideUtilityButtonsAnimated:YES]; 419 | } 420 | } 421 | 422 | - (void)selectCell 423 | { 424 | if (_cellState == kCellStateCenter) 425 | { 426 | NSIndexPath *cellIndexPath = [self.containingTableView indexPathForCell:self]; 427 | 428 | if ([self.containingTableView.delegate respondsToSelector:@selector(tableView:willSelectRowAtIndexPath:)]) 429 | { 430 | cellIndexPath = [self.containingTableView.delegate tableView:self.containingTableView willSelectRowAtIndexPath:cellIndexPath]; 431 | } 432 | 433 | if (cellIndexPath) 434 | { 435 | [self.containingTableView selectRowAtIndexPath:cellIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone]; 436 | 437 | if ([self.containingTableView.delegate respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]) 438 | { 439 | [self.containingTableView.delegate tableView:self.containingTableView didSelectRowAtIndexPath:cellIndexPath]; 440 | } 441 | } 442 | } 443 | } 444 | 445 | - (void)deselectCell 446 | { 447 | if (_cellState == kCellStateCenter) 448 | { 449 | NSIndexPath *cellIndexPath = [self.containingTableView indexPathForCell:self]; 450 | 451 | if ([self.containingTableView.delegate respondsToSelector:@selector(tableView:willDeselectRowAtIndexPath:)]) 452 | { 453 | cellIndexPath = [self.containingTableView.delegate tableView:self.containingTableView willDeselectRowAtIndexPath:cellIndexPath]; 454 | } 455 | 456 | if (cellIndexPath) 457 | { 458 | [self.containingTableView deselectRowAtIndexPath:cellIndexPath animated:NO]; 459 | 460 | if ([self.containingTableView.delegate respondsToSelector:@selector(tableView:didDeselectRowAtIndexPath:)]) 461 | { 462 | [self.containingTableView.delegate tableView:self.containingTableView didDeselectRowAtIndexPath:cellIndexPath]; 463 | } 464 | } 465 | } 466 | } 467 | 468 | #pragma mark - Utility buttons handling 469 | 470 | - (void)rightUtilityButtonHandler:(id)sender 471 | { 472 | SWUtilityButtonTapGestureRecognizer *utilityButtonTapGestureRecognizer = (SWUtilityButtonTapGestureRecognizer *)sender; 473 | NSUInteger utilityButtonIndex = utilityButtonTapGestureRecognizer.buttonIndex; 474 | if ([self.delegate respondsToSelector:@selector(swipeableTableViewCell:didTriggerRightUtilityButtonWithIndex:)]) 475 | { 476 | [self.delegate swipeableTableViewCell:self didTriggerRightUtilityButtonWithIndex:utilityButtonIndex]; 477 | } 478 | } 479 | 480 | - (void)leftUtilityButtonHandler:(id)sender 481 | { 482 | SWUtilityButtonTapGestureRecognizer *utilityButtonTapGestureRecognizer = (SWUtilityButtonTapGestureRecognizer *)sender; 483 | NSUInteger utilityButtonIndex = utilityButtonTapGestureRecognizer.buttonIndex; 484 | if ([self.delegate respondsToSelector:@selector(swipeableTableViewCell:didTriggerLeftUtilityButtonWithIndex:)]) 485 | { 486 | [self.delegate swipeableTableViewCell:self didTriggerLeftUtilityButtonWithIndex:utilityButtonIndex]; 487 | } 488 | } 489 | 490 | - (void)hideUtilityButtonsAnimated:(BOOL)animated 491 | { 492 | if (_cellState != kCellStateCenter) 493 | { 494 | [self.cellScrollView setContentOffset:[self contentOffsetForCellState:kCellStateCenter] animated:animated]; 495 | 496 | if ([self.delegate respondsToSelector:@selector(swipeableTableViewCell:scrollingToState:)]) 497 | { 498 | [self.delegate swipeableTableViewCell:self scrollingToState:kCellStateCenter]; 499 | } 500 | } 501 | } 502 | 503 | - (void)showLeftUtilityButtonsAnimated:(BOOL)animated { 504 | if (_cellState != kCellStateLeft) 505 | { 506 | [self.cellScrollView setContentOffset:[self contentOffsetForCellState:kCellStateLeft] animated:animated]; 507 | 508 | if ([self.delegate respondsToSelector:@selector(swipeableTableViewCell:scrollingToState:)]) 509 | { 510 | [self.delegate swipeableTableViewCell:self scrollingToState:kCellStateLeft]; 511 | } 512 | } 513 | } 514 | 515 | - (void)showRightUtilityButtonsAnimated:(BOOL)animated { 516 | if (_cellState != kCellStateRight) 517 | { 518 | [self.cellScrollView setContentOffset:[self contentOffsetForCellState:kCellStateRight] animated:animated]; 519 | 520 | if ([self.delegate respondsToSelector:@selector(swipeableTableViewCell:scrollingToState:)]) 521 | { 522 | [self.delegate swipeableTableViewCell:self scrollingToState:kCellStateRight]; 523 | } 524 | } 525 | } 526 | 527 | - (BOOL)isUtilityButtonsHidden { 528 | return _cellState == kCellStateCenter; 529 | } 530 | 531 | 532 | #pragma mark - Geometry helpers 533 | 534 | - (CGFloat)leftUtilityButtonsWidth 535 | { 536 | #if CGFLOAT_IS_DOUBLE 537 | return round(CGRectGetWidth(self.leftUtilityButtonsView.frame)); 538 | #else 539 | return roundf(CGRectGetWidth(self.leftUtilityButtonsView.frame)); 540 | #endif 541 | } 542 | 543 | - (CGFloat)rightUtilityButtonsWidth 544 | { 545 | #if CGFLOAT_IS_DOUBLE 546 | return round(CGRectGetWidth(self.rightUtilityButtonsView.frame) + self.additionalRightPadding); 547 | #else 548 | return roundf(CGRectGetWidth(self.rightUtilityButtonsView.frame) + self.additionalRightPadding); 549 | #endif 550 | } 551 | 552 | - (CGFloat)utilityButtonsPadding 553 | { 554 | #if CGFLOAT_IS_DOUBLE 555 | return round([self leftUtilityButtonsWidth] + [self rightUtilityButtonsWidth]); 556 | #else 557 | return roundf([self leftUtilityButtonsWidth] + [self rightUtilityButtonsWidth]); 558 | #endif 559 | } 560 | 561 | - (CGPoint)contentOffsetForCellState:(SWCellState)state 562 | { 563 | CGPoint scrollPt = CGPointZero; 564 | 565 | switch (state) 566 | { 567 | case kCellStateCenter: 568 | scrollPt.x = [self leftUtilityButtonsWidth]; 569 | break; 570 | 571 | case kCellStateRight: 572 | scrollPt.x = [self utilityButtonsPadding]; 573 | break; 574 | 575 | case kCellStateLeft: 576 | scrollPt.x = 0; 577 | break; 578 | } 579 | 580 | return scrollPt; 581 | } 582 | 583 | - (void)updateCellState 584 | { 585 | if(layoutUpdating == NO) 586 | { 587 | // Update the cell state according to the current scroll view contentOffset. 588 | for (NSNumber *numState in @[ 589 | @(kCellStateCenter), 590 | @(kCellStateLeft), 591 | @(kCellStateRight), 592 | ]) 593 | { 594 | SWCellState cellState = numState.integerValue; 595 | 596 | if (CGPointEqualToPoint(self.cellScrollView.contentOffset, [self contentOffsetForCellState:cellState])) 597 | { 598 | _cellState = cellState; 599 | break; 600 | } 601 | } 602 | 603 | // Update the clipping on the utility button views according to the current position. 604 | CGRect frame = [self.contentView.superview convertRect:self.contentView.frame toView:self]; 605 | frame.size.width = CGRectGetWidth(self.frame); 606 | 607 | self.leftUtilityClipConstraint.constant = MAX(0, CGRectGetMinX(frame) - CGRectGetMinX(self.frame)); 608 | self.rightUtilityClipConstraint.constant = MIN(0, CGRectGetMaxX(frame) - CGRectGetMaxX(self.frame)); 609 | 610 | if (self.isEditing) { 611 | self.leftUtilityClipConstraint.constant = 0; 612 | self.cellScrollView.contentOffset = CGPointMake([self leftUtilityButtonsWidth], 0); 613 | _cellState = kCellStateCenter; 614 | } 615 | 616 | self.leftUtilityClipView.hidden = (self.leftUtilityClipConstraint.constant == 0); 617 | self.rightUtilityClipView.hidden = (self.rightUtilityClipConstraint.constant == 0); 618 | 619 | if (self.accessoryType != UITableViewCellAccessoryNone && !self.editing) { 620 | UIView *accessory = [self.cellScrollView.superview.subviews lastObject]; 621 | 622 | CGRect accessoryFrame = accessory.frame; 623 | accessoryFrame.origin.x = CGRectGetWidth(frame) - CGRectGetWidth(accessoryFrame) - kAccessoryTrailingSpace + CGRectGetMinX(frame); 624 | accessory.frame = accessoryFrame; 625 | } 626 | 627 | // Enable or disable the gesture recognizers according to the current mode. 628 | if (!self.cellScrollView.isDragging && !self.cellScrollView.isDecelerating) 629 | { 630 | self.tapGestureRecognizer.enabled = YES; 631 | self.longPressGestureRecognizer.enabled = (_cellState == kCellStateCenter); 632 | } 633 | else 634 | { 635 | self.tapGestureRecognizer.enabled = NO; 636 | self.longPressGestureRecognizer.enabled = NO; 637 | } 638 | 639 | self.cellScrollView.scrollEnabled = !self.isEditing; 640 | } 641 | } 642 | 643 | #pragma mark - UIScrollViewDelegate 644 | 645 | - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset 646 | { 647 | if (velocity.x >= 0.5f) 648 | { 649 | if (_cellState == kCellStateLeft || !self.rightUtilityButtons || self.rightUtilityButtonsWidth == 0.0) 650 | { 651 | _cellState = kCellStateCenter; 652 | } 653 | else 654 | { 655 | _cellState = kCellStateRight; 656 | } 657 | } 658 | else if (velocity.x <= -0.5f) 659 | { 660 | if (_cellState == kCellStateRight || !self.leftUtilityButtons || self.leftUtilityButtonsWidth == 0.0) 661 | { 662 | _cellState = kCellStateCenter; 663 | } 664 | else 665 | { 666 | _cellState = kCellStateLeft; 667 | } 668 | } 669 | else 670 | { 671 | CGFloat leftThreshold = [self contentOffsetForCellState:kCellStateLeft].x + (self.leftUtilityButtonsWidth / 2); 672 | CGFloat rightThreshold = [self contentOffsetForCellState:kCellStateRight].x - (self.rightUtilityButtonsWidth / 2); 673 | 674 | if (targetContentOffset->x > rightThreshold) 675 | { 676 | _cellState = kCellStateRight; 677 | } 678 | else if (targetContentOffset->x < leftThreshold) 679 | { 680 | _cellState = kCellStateLeft; 681 | } 682 | else 683 | { 684 | _cellState = kCellStateCenter; 685 | } 686 | } 687 | 688 | if ([self.delegate respondsToSelector:@selector(swipeableTableViewCell:scrollingToState:)]) 689 | { 690 | [self.delegate swipeableTableViewCell:self scrollingToState:_cellState]; 691 | } 692 | 693 | if (_cellState != kCellStateCenter) 694 | { 695 | if ([self.delegate respondsToSelector:@selector(swipeableTableViewCellShouldHideUtilityButtonsOnSwipe:)]) 696 | { 697 | for (SWTableViewCell *cell in [self.containingTableView visibleCells]) { 698 | if (cell != self && [cell isKindOfClass:[SWTableViewCell class]] && [self.delegate swipeableTableViewCellShouldHideUtilityButtonsOnSwipe:cell]) { 699 | [cell hideUtilityButtonsAnimated:YES]; 700 | } 701 | } 702 | } 703 | } 704 | 705 | *targetContentOffset = [self contentOffsetForCellState:_cellState]; 706 | } 707 | 708 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView 709 | { 710 | if (scrollView.contentOffset.x > [self leftUtilityButtonsWidth]) 711 | { 712 | if ([self rightUtilityButtonsWidth] > 0) 713 | { 714 | if (self.delegate && [self.delegate respondsToSelector:@selector(swipeableTableViewCell:canSwipeToState:)]) 715 | { 716 | BOOL shouldScroll = [self.delegate swipeableTableViewCell:self canSwipeToState:kCellStateRight]; 717 | if (!shouldScroll) 718 | { 719 | scrollView.contentOffset = CGPointMake([self leftUtilityButtonsWidth], 0); 720 | } 721 | } 722 | } 723 | else 724 | { 725 | [scrollView setContentOffset:CGPointMake([self leftUtilityButtonsWidth], 0)]; 726 | self.tapGestureRecognizer.enabled = YES; 727 | } 728 | } 729 | else 730 | { 731 | // Expose the left button view 732 | if ([self leftUtilityButtonsWidth] > 0) 733 | { 734 | if (self.delegate && [self.delegate respondsToSelector:@selector(swipeableTableViewCell:canSwipeToState:)]) 735 | { 736 | BOOL shouldScroll = [self.delegate swipeableTableViewCell:self canSwipeToState:kCellStateLeft]; 737 | if (!shouldScroll) 738 | { 739 | scrollView.contentOffset = CGPointMake([self leftUtilityButtonsWidth], 0); 740 | } 741 | } 742 | } 743 | else 744 | { 745 | [scrollView setContentOffset:CGPointMake(0, 0)]; 746 | self.tapGestureRecognizer.enabled = YES; 747 | } 748 | } 749 | 750 | [self updateCellState]; 751 | 752 | if (self.delegate && [self.delegate respondsToSelector:@selector(swipeableTableViewCell:didScroll:)]) { 753 | [self.delegate swipeableTableViewCell:self didScroll:scrollView]; 754 | } 755 | } 756 | 757 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 758 | { 759 | [self updateCellState]; 760 | 761 | if (self.delegate && [self.delegate respondsToSelector:@selector(swipeableTableViewCellDidEndScrolling:)]) { 762 | [self.delegate swipeableTableViewCellDidEndScrolling:self]; 763 | } 764 | } 765 | 766 | - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView 767 | { 768 | [self updateCellState]; 769 | 770 | if (self.delegate && [self.delegate respondsToSelector:@selector(swipeableTableViewCellDidEndScrolling:)]) { 771 | [self.delegate swipeableTableViewCellDidEndScrolling:self]; 772 | } 773 | } 774 | 775 | -(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate 776 | { 777 | if (!decelerate) 778 | { 779 | self.tapGestureRecognizer.enabled = YES; 780 | } 781 | 782 | } 783 | 784 | #pragma mark - UIGestureRecognizerDelegate 785 | 786 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 787 | { 788 | if ((gestureRecognizer == self.containingTableView.panGestureRecognizer && otherGestureRecognizer == self.longPressGestureRecognizer) 789 | || (gestureRecognizer == self.longPressGestureRecognizer && otherGestureRecognizer == self.containingTableView.panGestureRecognizer)) 790 | { 791 | // Return YES so the pan gesture of the containing table view is not cancelled by the long press recognizer 792 | return YES; 793 | } 794 | else 795 | { 796 | return NO; 797 | } 798 | } 799 | 800 | -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch 801 | { 802 | return ![touch.view isKindOfClass:[UIControl class]]; 803 | } 804 | 805 | @end 806 | -------------------------------------------------------------------------------- /SWTableViewCell/PodFiles/SWUtilityButtonTapGestureRecognizer.h: -------------------------------------------------------------------------------- 1 | // 2 | // SWUtilityButtonTapGestureRecognizer.h 3 | // SWTableViewCell 4 | // 5 | // Created by Matt Bowman on 11/27/13. 6 | // Copyright (c) 2013 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface SWUtilityButtonTapGestureRecognizer : UITapGestureRecognizer 13 | 14 | @property (nonatomic) NSUInteger buttonIndex; 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /SWTableViewCell/PodFiles/SWUtilityButtonTapGestureRecognizer.m: -------------------------------------------------------------------------------- 1 | // 2 | // SWUtilityButtonTapGestureRecognizer.m 3 | // SWTableViewCell 4 | // 5 | // Created by Matt Bowman on 11/27/13. 6 | // Copyright (c) 2013 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import "SWUtilityButtonTapGestureRecognizer.h" 10 | 11 | @implementation SWUtilityButtonTapGestureRecognizer 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /SWTableViewCell/PodFiles/SWUtilityButtonView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SWUtilityButtonView.h 3 | // SWTableViewCell 4 | // 5 | // Created by Matt Bowman on 11/27/13. 6 | // Copyright (c) 2013 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import 10 | @class SWTableViewCell; 11 | 12 | #define kUtilityButtonWidthDefault 90 13 | 14 | @interface SWUtilityButtonView : UIView 15 | 16 | - (id)initWithUtilityButtons:(NSArray *)utilityButtons parentCell:(SWTableViewCell *)parentCell utilityButtonSelector:(SEL)utilityButtonSelector; 17 | - (id)initWithFrame:(CGRect)frame utilityButtons:(NSArray *)utilityButtons parentCell:(SWTableViewCell *)parentCell utilityButtonSelector:(SEL)utilityButtonSelector; 18 | 19 | @property (nonatomic, weak, readonly) SWTableViewCell *parentCell; 20 | @property (nonatomic, copy) NSArray *utilityButtons; 21 | @property (nonatomic, assign) SEL utilityButtonSelector; 22 | 23 | - (void)setUtilityButtons:(NSArray *)utilityButtons WithButtonWidth:(CGFloat)width; 24 | - (void)pushBackgroundColors; 25 | - (void)popBackgroundColors; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /SWTableViewCell/PodFiles/SWUtilityButtonView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SWUtilityButtonView.m 3 | // SWTableViewCell 4 | // 5 | // Created by Matt Bowman on 11/27/13. 6 | // Copyright (c) 2013 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import "SWUtilityButtonView.h" 10 | #import "SWUtilityButtonTapGestureRecognizer.h" 11 | 12 | @interface SWUtilityButtonView() 13 | 14 | @property (nonatomic, strong) NSLayoutConstraint *widthConstraint; 15 | @property (nonatomic, strong) NSMutableArray *buttonBackgroundColors; 16 | 17 | @end 18 | 19 | @implementation SWUtilityButtonView 20 | 21 | #pragma mark - SWUtilityButonView initializers 22 | 23 | - (id)initWithUtilityButtons:(NSArray *)utilityButtons parentCell:(SWTableViewCell *)parentCell utilityButtonSelector:(SEL)utilityButtonSelector 24 | { 25 | self = [self initWithFrame:CGRectZero utilityButtons:utilityButtons parentCell:parentCell utilityButtonSelector:utilityButtonSelector]; 26 | 27 | return self; 28 | } 29 | 30 | - (id)initWithFrame:(CGRect)frame utilityButtons:(NSArray *)utilityButtons parentCell:(SWTableViewCell *)parentCell utilityButtonSelector:(SEL)utilityButtonSelector 31 | { 32 | self = [super initWithFrame:frame]; 33 | 34 | if (self) { 35 | self.translatesAutoresizingMaskIntoConstraints = NO; 36 | 37 | self.widthConstraint = [NSLayoutConstraint constraintWithItem:self 38 | attribute:NSLayoutAttributeWidth 39 | relatedBy:NSLayoutRelationEqual 40 | toItem:nil 41 | attribute:NSLayoutAttributeNotAnAttribute 42 | multiplier:1.0 43 | constant:0.0]; // constant will be adjusted dynamically in -setUtilityButtons:. 44 | self.widthConstraint.priority = UILayoutPriorityDefaultHigh; 45 | [self addConstraint:self.widthConstraint]; 46 | 47 | _parentCell = parentCell; 48 | self.utilityButtonSelector = utilityButtonSelector; 49 | self.utilityButtons = utilityButtons; 50 | } 51 | 52 | return self; 53 | } 54 | 55 | #pragma mark Populating utility buttons 56 | 57 | - (void)setUtilityButtons:(NSArray *)utilityButtons 58 | { 59 | // if no width specified, use the default width 60 | [self setUtilityButtons:utilityButtons WithButtonWidth:kUtilityButtonWidthDefault]; 61 | } 62 | 63 | - (void)setUtilityButtons:(NSArray *)utilityButtons WithButtonWidth:(CGFloat)width 64 | { 65 | for (UIButton *button in _utilityButtons) 66 | { 67 | [button removeFromSuperview]; 68 | } 69 | 70 | _utilityButtons = [utilityButtons copy]; 71 | 72 | if (utilityButtons.count) 73 | { 74 | NSUInteger utilityButtonsCounter = 0; 75 | UIView *precedingView = nil; 76 | 77 | for (UIButton *button in _utilityButtons) 78 | { 79 | [self addSubview:button]; 80 | button.translatesAutoresizingMaskIntoConstraints = NO; 81 | 82 | if (!precedingView) 83 | { 84 | // First button; pin it to the left edge. 85 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[button]" 86 | options:0L 87 | metrics:nil 88 | views:NSDictionaryOfVariableBindings(button)]]; 89 | } 90 | else 91 | { 92 | // Subsequent button; pin it to the right edge of the preceding one, with equal width. 93 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[precedingView][button(==precedingView)]" 94 | options:0L 95 | metrics:nil 96 | views:NSDictionaryOfVariableBindings(precedingView, button)]]; 97 | } 98 | 99 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[button]|" 100 | options:0L 101 | metrics:nil 102 | views:NSDictionaryOfVariableBindings(button)]]; 103 | 104 | 105 | SWUtilityButtonTapGestureRecognizer *utilityButtonTapGestureRecognizer = [[SWUtilityButtonTapGestureRecognizer alloc] initWithTarget:_parentCell action:_utilityButtonSelector]; 106 | utilityButtonTapGestureRecognizer.buttonIndex = utilityButtonsCounter; 107 | [button addGestureRecognizer:utilityButtonTapGestureRecognizer]; 108 | 109 | utilityButtonsCounter++; 110 | precedingView = button; 111 | } 112 | 113 | // Pin the last button to the right edge. 114 | [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"[precedingView]|" 115 | options:0L 116 | metrics:nil 117 | views:NSDictionaryOfVariableBindings(precedingView)]]; 118 | } 119 | 120 | self.widthConstraint.constant = (width * utilityButtons.count); 121 | 122 | [self setNeedsLayout]; 123 | 124 | return; 125 | } 126 | 127 | #pragma mark - 128 | 129 | - (void)pushBackgroundColors 130 | { 131 | self.buttonBackgroundColors = [[NSMutableArray alloc] init]; 132 | 133 | for (UIButton *button in self.utilityButtons) 134 | { 135 | [self.buttonBackgroundColors addObject:button.backgroundColor]; 136 | } 137 | } 138 | 139 | - (void)popBackgroundColors 140 | { 141 | NSEnumerator *e = self.utilityButtons.objectEnumerator; 142 | 143 | for (UIColor *color in self.buttonBackgroundColors) 144 | { 145 | UIButton *button = [e nextObject]; 146 | button.backgroundColor = color; 147 | } 148 | 149 | self.buttonBackgroundColors = nil; 150 | } 151 | 152 | @end 153 | 154 | -------------------------------------------------------------------------------- /SWTableViewCell/SWTableViewCell-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.ChrisWendel.$(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 | UILaunchStoryboardName 28 | MainStoryboard 29 | UIMainStoryboardFile 30 | MainStoryboard 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | UIInterfaceOrientationPortraitUpsideDown 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /SWTableViewCell/SWTableViewCell-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'SWTableViewCell' target in the 'SWTableViewCell' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_5_0 8 | #warning "This project uses features only available in iOS SDK 5.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /SWTableViewCell/UMTableViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // UMTableViewCell.h 3 | // SWTableViewCell 4 | // 5 | // Created by Matt Bowman on 12/2/13. 6 | // Copyright (c) 2013 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SWTableViewCell.h" 11 | 12 | /* 13 | * Example of a custom cell built in Storyboard 14 | */ 15 | @interface UMTableViewCell : SWTableViewCell 16 | 17 | @property (weak, nonatomic) IBOutlet UIImageView *image; 18 | @property (weak, nonatomic) IBOutlet UILabel *label; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /SWTableViewCell/UMTableViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // UMTableViewCell.m 3 | // SWTableViewCell 4 | // 5 | // Created by Matt Bowman on 12/2/13. 6 | // Copyright (c) 2013 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import "UMTableViewCell.h" 10 | 11 | @implementation UMTableViewCell 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /SWTableViewCell/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // SWTableViewCell 4 | // 5 | // Created by Chris Wendel on 9/10/13. 6 | // Copyright (c) 2013 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SWTableViewCell.h" 11 | 12 | @interface ViewController : UITableViewController 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /SWTableViewCell/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // SWTableViewCell 4 | // 5 | // Created by Chris Wendel on 9/10/13. 6 | // Copyright (c) 2013 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "SWTableViewCell.h" 11 | #import "UMTableViewCell.h" 12 | 13 | @interface ViewController () { 14 | NSArray *_sections; 15 | NSMutableArray *_testArray; 16 | } 17 | 18 | @property (nonatomic, weak) IBOutlet UITableView *tableView; 19 | @property (nonatomic) BOOL useCustomCells; 20 | @property (nonatomic, weak) UIRefreshControl *refreshControl; 21 | 22 | @end 23 | 24 | @implementation ViewController 25 | 26 | - (void)viewDidLoad { 27 | [super viewDidLoad]; 28 | 29 | self.navigationItem.leftBarButtonItem = self.editButtonItem; 30 | self.tableView.rowHeight = 90; 31 | 32 | self.navigationItem.title = @"Pull to Toggle Cell Type"; 33 | 34 | // Setup refresh control for example app 35 | UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init]; 36 | [refreshControl addTarget:self action:@selector(toggleCells:) forControlEvents:UIControlEventValueChanged]; 37 | refreshControl.tintColor = [UIColor blueColor]; 38 | 39 | self.refreshControl = refreshControl; 40 | 41 | // If you set the seperator inset on iOS 6 you get a NSInvalidArgumentException...weird 42 | if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7) { 43 | self.tableView.separatorInset = UIEdgeInsetsMake(0, 0, 0, 0); // Makes the horizontal row seperator stretch the entire length of the table view 44 | } 45 | 46 | _sections = [[UILocalizedIndexedCollation currentCollation] sectionIndexTitles]; 47 | 48 | _testArray = [[NSMutableArray alloc] init]; 49 | 50 | self.useCustomCells = NO; 51 | 52 | for (int i = 0; i < _sections.count; ++i) { 53 | [_testArray addObject:[NSMutableArray array]]; 54 | } 55 | 56 | for (int i = 0; i < 100; ++i) { 57 | NSString *string = [NSString stringWithFormat:@"%d", i]; 58 | [_testArray[i % _sections.count] addObject:string]; 59 | } 60 | } 61 | 62 | #pragma mark UITableViewDataSource 63 | 64 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 65 | return _testArray.count; 66 | } 67 | 68 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 69 | return [_testArray[section] count]; 70 | } 71 | 72 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 73 | NSLog(@"cell selected at index path %ld:%ld", (long)indexPath.section, (long)indexPath.row); 74 | NSLog(@"selected cell index path is %@", [self.tableView indexPathForSelectedRow]); 75 | 76 | if (!tableView.isEditing) { 77 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 78 | } 79 | } 80 | 81 | - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { 82 | return _sections[section]; 83 | } 84 | 85 | // Show index titles 86 | 87 | //- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView { 88 | // return [[UILocalizedIndexedCollation currentCollation] sectionIndexTitles]; 89 | //} 90 | // 91 | //- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { 92 | // return [[UILocalizedIndexedCollation currentCollation] sectionForSectionIndexTitleAtIndex:index]; 93 | //} 94 | 95 | #pragma mark - UIRefreshControl Selector 96 | 97 | - (void)toggleCells:(UIRefreshControl*)refreshControl 98 | { 99 | [refreshControl beginRefreshing]; 100 | self.useCustomCells = !self.useCustomCells; 101 | if (self.useCustomCells) 102 | { 103 | self.refreshControl.tintColor = [UIColor yellowColor]; 104 | } 105 | else 106 | { 107 | self.refreshControl.tintColor = [UIColor blueColor]; 108 | } 109 | [self.tableView reloadData]; 110 | [refreshControl endRefreshing]; 111 | } 112 | 113 | #pragma mark - UIScrollViewDelegate 114 | 115 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 116 | 117 | if (self.useCustomCells) 118 | { 119 | UMTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"UMCell" forIndexPath:indexPath]; 120 | 121 | // optionally specify a width that each set of utility buttons will share 122 | [cell setLeftUtilityButtons:[self leftButtons] WithButtonWidth:32.0f]; 123 | [cell setRightUtilityButtons:[self rightButtons] WithButtonWidth:58.0f]; 124 | cell.delegate = self; 125 | 126 | cell.label.text = [NSString stringWithFormat:@"Section: %ld, Seat: %ld", (long)indexPath.section, (long)indexPath.row]; 127 | 128 | return cell; 129 | } 130 | else 131 | { 132 | static NSString *cellIdentifier = @"Cell"; 133 | 134 | SWTableViewCell *cell = (SWTableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 135 | 136 | if (cell == nil) { 137 | 138 | cell = [[SWTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier]; 139 | 140 | cell.leftUtilityButtons = [self leftButtons]; 141 | cell.rightUtilityButtons = [self rightButtons]; 142 | cell.delegate = self; 143 | } 144 | 145 | NSDate *dateObject = _testArray[indexPath.section][indexPath.row]; 146 | cell.textLabel.text = [dateObject description]; 147 | cell.textLabel.backgroundColor = [UIColor whiteColor]; 148 | cell.detailTextLabel.backgroundColor = [UIColor whiteColor]; 149 | cell.detailTextLabel.text = @"Some detail text"; 150 | 151 | return cell; 152 | } 153 | 154 | } 155 | 156 | - (NSArray *)rightButtons 157 | { 158 | NSMutableArray *rightUtilityButtons = [NSMutableArray new]; 159 | [rightUtilityButtons sw_addUtilityButtonWithColor: 160 | [UIColor colorWithRed:0.78f green:0.78f blue:0.8f alpha:1.0] 161 | title:@"More"]; 162 | [rightUtilityButtons sw_addUtilityButtonWithColor: 163 | [UIColor colorWithRed:1.0f green:0.231f blue:0.188 alpha:1.0f] 164 | title:@"Delete"]; 165 | 166 | return rightUtilityButtons; 167 | } 168 | 169 | - (NSArray *)leftButtons 170 | { 171 | NSMutableArray *leftUtilityButtons = [NSMutableArray new]; 172 | 173 | [leftUtilityButtons sw_addUtilityButtonWithColor: 174 | [UIColor colorWithRed:0.07 green:0.75f blue:0.16f alpha:1.0] 175 | icon:[UIImage imageNamed:@"check.png"]]; 176 | [leftUtilityButtons sw_addUtilityButtonWithColor: 177 | [UIColor colorWithRed:1.0f green:1.0f blue:0.35f alpha:1.0] 178 | icon:[UIImage imageNamed:@"clock.png"]]; 179 | [leftUtilityButtons sw_addUtilityButtonWithColor: 180 | [UIColor colorWithRed:1.0f green:0.231f blue:0.188f alpha:1.0] 181 | icon:[UIImage imageNamed:@"cross.png"]]; 182 | [leftUtilityButtons sw_addUtilityButtonWithColor: 183 | [UIColor colorWithRed:0.55f green:0.27f blue:0.07f alpha:1.0] 184 | icon:[UIImage imageNamed:@"list.png"]]; 185 | 186 | return leftUtilityButtons; 187 | } 188 | 189 | // Set row height on an individual basis 190 | 191 | //- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ 192 | // return [self rowHeightForIndexPath:indexPath]; 193 | //} 194 | // 195 | //- (CGFloat)rowHeightForIndexPath:(NSIndexPath *)indexPath { 196 | // return ([indexPath row] * 10) + 60; 197 | //} 198 | 199 | - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { 200 | // Set background color of cell here if you don't want default white 201 | } 202 | 203 | #pragma mark - SWTableViewDelegate 204 | 205 | - (void)swipeableTableViewCell:(SWTableViewCell *)cell scrollingToState:(SWCellState)state 206 | { 207 | switch (state) { 208 | case 0: 209 | NSLog(@"utility buttons closed"); 210 | break; 211 | case 1: 212 | NSLog(@"left utility buttons open"); 213 | break; 214 | case 2: 215 | NSLog(@"right utility buttons open"); 216 | break; 217 | default: 218 | break; 219 | } 220 | } 221 | 222 | - (void)swipeableTableViewCell:(SWTableViewCell *)cell didTriggerLeftUtilityButtonWithIndex:(NSInteger)index 223 | { 224 | switch (index) { 225 | case 0: 226 | NSLog(@"left button 0 was pressed"); 227 | break; 228 | case 1: 229 | NSLog(@"left button 1 was pressed"); 230 | break; 231 | case 2: 232 | NSLog(@"left button 2 was pressed"); 233 | break; 234 | case 3: 235 | NSLog(@"left btton 3 was pressed"); 236 | default: 237 | break; 238 | } 239 | } 240 | 241 | - (void)swipeableTableViewCell:(SWTableViewCell *)cell didTriggerRightUtilityButtonWithIndex:(NSInteger)index 242 | { 243 | switch (index) { 244 | case 0: 245 | { 246 | NSLog(@"More button was pressed"); 247 | UIAlertView *alertTest = [[UIAlertView alloc] initWithTitle:@"Hello" message:@"More more more" delegate:nil cancelButtonTitle:@"cancel" otherButtonTitles: nil]; 248 | [alertTest show]; 249 | 250 | [cell hideUtilityButtonsAnimated:YES]; 251 | break; 252 | } 253 | case 1: 254 | { 255 | // Delete button was pressed 256 | NSIndexPath *cellIndexPath = [self.tableView indexPathForCell:cell]; 257 | 258 | [_testArray[cellIndexPath.section] removeObjectAtIndex:cellIndexPath.row]; 259 | [self.tableView deleteRowsAtIndexPaths:@[cellIndexPath] withRowAnimation:UITableViewRowAnimationLeft]; 260 | break; 261 | } 262 | default: 263 | break; 264 | } 265 | } 266 | 267 | - (BOOL)swipeableTableViewCellShouldHideUtilityButtonsOnSwipe:(SWTableViewCell *)cell 268 | { 269 | // allow just one cell's utility button to be open at once 270 | return YES; 271 | } 272 | 273 | - (BOOL)swipeableTableViewCell:(SWTableViewCell *)cell canSwipeToState:(SWCellState)state 274 | { 275 | switch (state) { 276 | case 1: 277 | // set to NO to disable all left utility buttons appearing 278 | return YES; 279 | break; 280 | case 2: 281 | // set to NO to disable all right utility buttons appearing 282 | return YES; 283 | break; 284 | default: 285 | break; 286 | } 287 | 288 | return YES; 289 | } 290 | 291 | 292 | @end -------------------------------------------------------------------------------- /SWTableViewCell/check@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CEWendel/SWTableViewCell/a8a20f833c737270b0710a821cf095fd9f855d7a/SWTableViewCell/check@2x.png -------------------------------------------------------------------------------- /SWTableViewCell/clock@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CEWendel/SWTableViewCell/a8a20f833c737270b0710a821cf095fd9f855d7a/SWTableViewCell/clock@2x.png -------------------------------------------------------------------------------- /SWTableViewCell/cross@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CEWendel/SWTableViewCell/a8a20f833c737270b0710a821cf095fd9f855d7a/SWTableViewCell/cross@2x.png -------------------------------------------------------------------------------- /SWTableViewCell/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /SWTableViewCell/en.lproj/MainStoryboard.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /SWTableViewCell/list@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CEWendel/SWTableViewCell/a8a20f833c737270b0710a821cf095fd9f855d7a/SWTableViewCell/list@2x.png -------------------------------------------------------------------------------- /SWTableViewCell/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // SWTableViewCell 4 | // 5 | // Created by Chris Wendel on 9/10/13. 6 | // Copyright (c) 2013 Chris Wendel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /SWTableViewCell/um.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CEWendel/SWTableViewCell/a8a20f833c737270b0710a821cf095fd9f855d7a/SWTableViewCell/um.png -------------------------------------------------------------------------------- /SWTableViewCellTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.ChrisWendel.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SWTableViewCellTests/SWTableViewCellTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import 5 | #import 6 | #import "SWTableViewCell.h" 7 | 8 | SpecBegin(SWTableViewCell) 9 | 10 | __block NSArray *rightButtons; 11 | __block NSArray *leftButtons; 12 | 13 | before(^{ 14 | NSMutableArray *rightUtilityButtons = [NSMutableArray new]; 15 | [rightUtilityButtons sw_addUtilityButtonWithColor: 16 | [UIColor colorWithRed:0.78f green:0.78f blue:0.8f alpha:1.0] 17 | title:@"More"]; 18 | [rightUtilityButtons sw_addUtilityButtonWithColor: 19 | [UIColor colorWithRed:1.0f green:0.231f blue:0.188 alpha:1.0f] 20 | title:@"Delete"]; 21 | 22 | rightButtons = rightUtilityButtons; 23 | 24 | 25 | NSMutableArray *leftUtilityButtons = [NSMutableArray new]; 26 | 27 | [leftUtilityButtons sw_addUtilityButtonWithColor: 28 | [UIColor colorWithRed:0.07 green:0.75f blue:0.16f alpha:1.0] 29 | icon:[UIImage imageNamed:@"check.png"]]; 30 | [leftUtilityButtons sw_addUtilityButtonWithColor: 31 | [UIColor colorWithRed:1.0f green:1.0f blue:0.35f alpha:1.0] 32 | icon:[UIImage imageNamed:@"clock.png"]]; 33 | [leftUtilityButtons sw_addUtilityButtonWithColor: 34 | [UIColor colorWithRed:1.0f green:0.231f blue:0.188f alpha:1.0] 35 | icon:[UIImage imageNamed:@"cross.png"]]; 36 | [leftUtilityButtons sw_addUtilityButtonWithColor: 37 | [UIColor colorWithRed:0.55f green:0.27f blue:0.07f alpha:1.0] 38 | icon:[UIImage imageNamed:@"list.png"]]; 39 | 40 | leftButtons = leftUtilityButtons; 41 | 42 | }); 43 | 44 | describe(@"init", ^{ 45 | it(@"should init with cell style UITableViewStyleDefault", ^{ 46 | SWTableViewCell *cell = [[SWTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; 47 | expect(cell).toNot.beNil; 48 | }); 49 | 50 | it(@"should init with cell style UITableViewStyleSubtitle", ^{ 51 | SWTableViewCell *cell = [[SWTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil]; 52 | expect(cell).toNot.beNil; 53 | }); 54 | }); 55 | 56 | describe(@"buttons", ^{ 57 | __block SWTableViewCell *cell; 58 | 59 | before(^{ 60 | cell = [[SWTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; 61 | [cell setRightUtilityButtons:rightButtons WithButtonWidth:44.0f]; 62 | [cell setLeftUtilityButtons:leftButtons WithButtonWidth:44.0f]; 63 | }); 64 | 65 | it(@"should have two right buttons", ^{ 66 | expect(cell.rightUtilityButtons.count).to.equal(2); 67 | }); 68 | 69 | it(@"should have four left buttons", ^{ 70 | expect(cell.leftUtilityButtons.count).to.equal(4); 71 | }); 72 | }); 73 | 74 | 75 | SpecEnd -------------------------------------------------------------------------------- /github-assets/example1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CEWendel/SWTableViewCell/a8a20f833c737270b0710a821cf095fd9f855d7a/github-assets/example1.gif -------------------------------------------------------------------------------- /github-assets/example2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CEWendel/SWTableViewCell/a8a20f833c737270b0710a821cf095fd9f855d7a/github-assets/example2.gif -------------------------------------------------------------------------------- /github-assets/example3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CEWendel/SWTableViewCell/a8a20f833c737270b0710a821cf095fd9f855d7a/github-assets/example3.gif -------------------------------------------------------------------------------- /github-assets/example4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CEWendel/SWTableViewCell/a8a20f833c737270b0710a821cf095fd9f855d7a/github-assets/example4.gif --------------------------------------------------------------------------------