├── .gitignore ├── Assets.xcassets ├── AppIcon.appiconset │ ├── Contents.json │ ├── Icon-29@2x.png │ ├── Icon-40@2x.png │ ├── Icon-60@2x.png │ └── Icon-60@3x.png ├── LaunchImage.launchimage │ └── Contents.json └── user.imageset │ ├── Contents.json │ └── user@2x.png ├── DNSSwipeableCell.xcodeproj └── project.pbxproj ├── DNSSwipeableCell ├── AppDelegate.h ├── AppDelegate.m ├── Base.lproj │ └── Main.storyboard ├── DNSExampleImageViewCell.h ├── DNSExampleImageViewCell.m ├── DNSStoryboardCell.h ├── DNSStoryboardCell.m ├── DNSSwipeableCell-Info.plist ├── DNSSwipeableCell-Prefix.pch ├── DetailViewController.h ├── DetailViewController.m ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── MasterViewController.h ├── MasterViewController.m ├── StoryboardTableViewController.h ├── StoryboardTableViewController.m ├── SwipeableDataSource.h ├── SwipeableDataSource.m ├── en.lproj │ └── InfoPlist.strings ├── instagrams │ ├── annoyed.jpg │ ├── dancer.jpg │ ├── ontivo.jpg │ ├── red.jpg │ ├── sandwichthief.jpg │ └── super.jpg └── main.m ├── DNSSwipeableCellTests ├── DNSSwipeableCellTests-Info.plist ├── DNSSwipeableCellTests.m └── en.lproj │ └── InfoPlist.strings ├── DNSSwipeableTableCell.podspec ├── LICENSE.md ├── Library ├── DNSSwipeableCell.h └── DNSSwipeableCell.m ├── README.md └── swipeable.gif /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.xcuserstate 3 | -------------------------------------------------------------------------------- /Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-29@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "40x40", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-40@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "60x60", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-60@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "60x60", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-60@3x.png", 25 | "scale" : "3x" 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | } 32 | } -------------------------------------------------------------------------------- /Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designatednerd/DNSSwipeableTableCell/45aec721cff84d3254976c6ed54d10e5aff1aa1b/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designatednerd/DNSSwipeableTableCell/45aec721cff84d3254976c6ed54d10e5aff1aa1b/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designatednerd/DNSSwipeableTableCell/45aec721cff84d3254976c6ed54d10e5aff1aa1b/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designatednerd/DNSSwipeableTableCell/45aec721cff84d3254976c6ed54d10e5aff1aa1b/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "extent" : "full-screen", 14 | "minimum-system-version" : "7.0", 15 | "subtype" : "retina4", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Assets.xcassets/user.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "user@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Assets.xcassets/user.imageset/user@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designatednerd/DNSSwipeableTableCell/45aec721cff84d3254976c6ed54d10e5aff1aa1b/Assets.xcassets/user.imageset/user@2x.png -------------------------------------------------------------------------------- /DNSSwipeableCell.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9B2076F51A9FA46C00D08704 /* StoryboardTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B2076F41A9FA46C00D08704 /* StoryboardTableViewController.m */; }; 11 | 9B2076F81A9FA49400D08704 /* DNSStoryboardCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B2076F71A9FA49400D08704 /* DNSStoryboardCell.m */; }; 12 | 9B2076FB1A9FA6D200D08704 /* SwipeableDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B2076FA1A9FA6D200D08704 /* SwipeableDataSource.m */; }; 13 | 9BF394EB18C433CD00E1CB12 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 9BF394EA18C433CD00E1CB12 /* README.md */; }; 14 | 9BF394EE18C58B0600E1CB12 /* DNSExampleImageViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BF394ED18C58B0600E1CB12 /* DNSExampleImageViewCell.m */; }; 15 | 9BF394F618C58D6300E1CB12 /* annoyed.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 9BF394F018C58D6300E1CB12 /* annoyed.jpg */; }; 16 | 9BF394F718C58D6300E1CB12 /* dancer.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 9BF394F118C58D6300E1CB12 /* dancer.jpg */; }; 17 | 9BF394F818C58D6300E1CB12 /* ontivo.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 9BF394F218C58D6300E1CB12 /* ontivo.jpg */; }; 18 | 9BF394F918C58D6300E1CB12 /* red.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 9BF394F318C58D6300E1CB12 /* red.jpg */; }; 19 | 9BF394FA18C58D6300E1CB12 /* sandwichthief.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 9BF394F418C58D6300E1CB12 /* sandwichthief.jpg */; }; 20 | 9BF394FB18C58D6300E1CB12 /* super.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 9BF394F518C58D6300E1CB12 /* super.jpg */; }; 21 | 9BF394FD18C5A0DD00E1CB12 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9BF394FC18C5A0DD00E1CB12 /* Assets.xcassets */; }; 22 | 9BF3950118C5A4A000E1CB12 /* DNSSwipeableCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BF3950018C5A4A000E1CB12 /* DNSSwipeableCell.m */; }; 23 | F5C72C05184771F90019502F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F5C72C04184771F90019502F /* Foundation.framework */; }; 24 | F5C72C07184771F90019502F /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F5C72C06184771F90019502F /* CoreGraphics.framework */; }; 25 | F5C72C09184771F90019502F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F5C72C08184771F90019502F /* UIKit.framework */; }; 26 | F5C72C0F184771F90019502F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = F5C72C0D184771F90019502F /* InfoPlist.strings */; }; 27 | F5C72C11184771F90019502F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F5C72C10184771F90019502F /* main.m */; }; 28 | F5C72C15184771F90019502F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F5C72C14184771F90019502F /* AppDelegate.m */; }; 29 | F5C72C18184771F90019502F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F5C72C16184771F90019502F /* Main.storyboard */; }; 30 | F5C72C1B184771F90019502F /* MasterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F5C72C1A184771F90019502F /* MasterViewController.m */; }; 31 | F5C72C1E184771F90019502F /* DetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F5C72C1D184771F90019502F /* DetailViewController.m */; }; 32 | F5C72C27184771F90019502F /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F5C72C26184771F90019502F /* XCTest.framework */; }; 33 | F5C72C28184771F90019502F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F5C72C04184771F90019502F /* Foundation.framework */; }; 34 | F5C72C29184771F90019502F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F5C72C08184771F90019502F /* UIKit.framework */; }; 35 | F5C72C31184771F90019502F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = F5C72C2F184771F90019502F /* InfoPlist.strings */; }; 36 | F5C72C33184771F90019502F /* DNSSwipeableCellTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F5C72C32184771F90019502F /* DNSSwipeableCellTests.m */; }; 37 | /* End PBXBuildFile section */ 38 | 39 | /* Begin PBXContainerItemProxy section */ 40 | F5C72C2A184771F90019502F /* PBXContainerItemProxy */ = { 41 | isa = PBXContainerItemProxy; 42 | containerPortal = F5C72BF9184771F80019502F /* Project object */; 43 | proxyType = 1; 44 | remoteGlobalIDString = F5C72C00184771F90019502F; 45 | remoteInfo = DNSSwipeableCell; 46 | }; 47 | /* End PBXContainerItemProxy section */ 48 | 49 | /* Begin PBXFileReference section */ 50 | 9B2076F31A9FA46C00D08704 /* StoryboardTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StoryboardTableViewController.h; sourceTree = ""; }; 51 | 9B2076F41A9FA46C00D08704 /* StoryboardTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StoryboardTableViewController.m; sourceTree = ""; }; 52 | 9B2076F61A9FA49400D08704 /* DNSStoryboardCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNSStoryboardCell.h; sourceTree = ""; }; 53 | 9B2076F71A9FA49400D08704 /* DNSStoryboardCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DNSStoryboardCell.m; sourceTree = ""; }; 54 | 9B2076F91A9FA6D200D08704 /* SwipeableDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SwipeableDataSource.h; sourceTree = ""; }; 55 | 9B2076FA1A9FA6D200D08704 /* SwipeableDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SwipeableDataSource.m; sourceTree = ""; }; 56 | 9BF394EA18C433CD00E1CB12 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.md; sourceTree = ""; }; 57 | 9BF394EC18C58B0600E1CB12 /* DNSExampleImageViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNSExampleImageViewCell.h; sourceTree = ""; }; 58 | 9BF394ED18C58B0600E1CB12 /* DNSExampleImageViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DNSExampleImageViewCell.m; sourceTree = ""; }; 59 | 9BF394F018C58D6300E1CB12 /* annoyed.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = annoyed.jpg; sourceTree = ""; }; 60 | 9BF394F118C58D6300E1CB12 /* dancer.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = dancer.jpg; sourceTree = ""; }; 61 | 9BF394F218C58D6300E1CB12 /* ontivo.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = ontivo.jpg; sourceTree = ""; }; 62 | 9BF394F318C58D6300E1CB12 /* red.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = red.jpg; sourceTree = ""; }; 63 | 9BF394F418C58D6300E1CB12 /* sandwichthief.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = sandwichthief.jpg; sourceTree = ""; }; 64 | 9BF394F518C58D6300E1CB12 /* super.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = super.jpg; sourceTree = ""; }; 65 | 9BF394FC18C5A0DD00E1CB12 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = ../Assets.xcassets; sourceTree = ""; }; 66 | 9BF394FF18C5A4A000E1CB12 /* DNSSwipeableCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNSSwipeableCell.h; sourceTree = ""; }; 67 | 9BF3950018C5A4A000E1CB12 /* DNSSwipeableCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DNSSwipeableCell.m; sourceTree = ""; }; 68 | F5C72C01184771F90019502F /* DNSSwipeableCell.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DNSSwipeableCell.app; sourceTree = BUILT_PRODUCTS_DIR; }; 69 | F5C72C04184771F90019502F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 70 | F5C72C06184771F90019502F /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 71 | F5C72C08184771F90019502F /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 72 | F5C72C0C184771F90019502F /* DNSSwipeableCell-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "DNSSwipeableCell-Info.plist"; sourceTree = ""; }; 73 | F5C72C0E184771F90019502F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 74 | F5C72C10184771F90019502F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 75 | F5C72C12184771F90019502F /* DNSSwipeableCell-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DNSSwipeableCell-Prefix.pch"; sourceTree = ""; }; 76 | F5C72C13184771F90019502F /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 77 | F5C72C14184771F90019502F /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 78 | F5C72C17184771F90019502F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 79 | F5C72C19184771F90019502F /* MasterViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MasterViewController.h; sourceTree = ""; }; 80 | F5C72C1A184771F90019502F /* MasterViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MasterViewController.m; sourceTree = ""; }; 81 | F5C72C1C184771F90019502F /* DetailViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DetailViewController.h; sourceTree = ""; }; 82 | F5C72C1D184771F90019502F /* DetailViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DetailViewController.m; sourceTree = ""; }; 83 | F5C72C25184771F90019502F /* DNSSwipeableCellTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DNSSwipeableCellTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 84 | F5C72C26184771F90019502F /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 85 | F5C72C2E184771F90019502F /* DNSSwipeableCellTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "DNSSwipeableCellTests-Info.plist"; sourceTree = ""; }; 86 | F5C72C30184771F90019502F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 87 | F5C72C32184771F90019502F /* DNSSwipeableCellTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DNSSwipeableCellTests.m; sourceTree = ""; }; 88 | /* End PBXFileReference section */ 89 | 90 | /* Begin PBXFrameworksBuildPhase section */ 91 | F5C72BFE184771F90019502F /* Frameworks */ = { 92 | isa = PBXFrameworksBuildPhase; 93 | buildActionMask = 2147483647; 94 | files = ( 95 | F5C72C07184771F90019502F /* CoreGraphics.framework in Frameworks */, 96 | F5C72C09184771F90019502F /* UIKit.framework in Frameworks */, 97 | F5C72C05184771F90019502F /* Foundation.framework in Frameworks */, 98 | ); 99 | runOnlyForDeploymentPostprocessing = 0; 100 | }; 101 | F5C72C22184771F90019502F /* Frameworks */ = { 102 | isa = PBXFrameworksBuildPhase; 103 | buildActionMask = 2147483647; 104 | files = ( 105 | F5C72C27184771F90019502F /* XCTest.framework in Frameworks */, 106 | F5C72C29184771F90019502F /* UIKit.framework in Frameworks */, 107 | F5C72C28184771F90019502F /* Foundation.framework in Frameworks */, 108 | ); 109 | runOnlyForDeploymentPostprocessing = 0; 110 | }; 111 | /* End PBXFrameworksBuildPhase section */ 112 | 113 | /* Begin PBXGroup section */ 114 | 9BF394EF18C58D6300E1CB12 /* instagrams */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | 9BF394F018C58D6300E1CB12 /* annoyed.jpg */, 118 | 9BF394F118C58D6300E1CB12 /* dancer.jpg */, 119 | 9BF394F218C58D6300E1CB12 /* ontivo.jpg */, 120 | 9BF394F318C58D6300E1CB12 /* red.jpg */, 121 | 9BF394F418C58D6300E1CB12 /* sandwichthief.jpg */, 122 | 9BF394F518C58D6300E1CB12 /* super.jpg */, 123 | ); 124 | path = instagrams; 125 | sourceTree = ""; 126 | }; 127 | 9BF394FE18C5A4A000E1CB12 /* Library */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 9BF394FF18C5A4A000E1CB12 /* DNSSwipeableCell.h */, 131 | 9BF3950018C5A4A000E1CB12 /* DNSSwipeableCell.m */, 132 | ); 133 | path = Library; 134 | sourceTree = SOURCE_ROOT; 135 | }; 136 | F5C72BF8184771F80019502F = { 137 | isa = PBXGroup; 138 | children = ( 139 | 9BF394EA18C433CD00E1CB12 /* README.md */, 140 | F5C72C0A184771F90019502F /* DNSSwipeableCell */, 141 | F5C72C2C184771F90019502F /* DNSSwipeableCellTests */, 142 | F5C72C03184771F90019502F /* Frameworks */, 143 | F5C72C02184771F90019502F /* Products */, 144 | ); 145 | sourceTree = ""; 146 | }; 147 | F5C72C02184771F90019502F /* Products */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | F5C72C01184771F90019502F /* DNSSwipeableCell.app */, 151 | F5C72C25184771F90019502F /* DNSSwipeableCellTests.xctest */, 152 | ); 153 | name = Products; 154 | sourceTree = ""; 155 | }; 156 | F5C72C03184771F90019502F /* Frameworks */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | F5C72C04184771F90019502F /* Foundation.framework */, 160 | F5C72C06184771F90019502F /* CoreGraphics.framework */, 161 | F5C72C08184771F90019502F /* UIKit.framework */, 162 | F5C72C26184771F90019502F /* XCTest.framework */, 163 | ); 164 | name = Frameworks; 165 | sourceTree = ""; 166 | }; 167 | F5C72C0A184771F90019502F /* DNSSwipeableCell */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | F5C72C13184771F90019502F /* AppDelegate.h */, 171 | F5C72C14184771F90019502F /* AppDelegate.m */, 172 | F5C72C16184771F90019502F /* Main.storyboard */, 173 | F5C72C19184771F90019502F /* MasterViewController.h */, 174 | F5C72C1A184771F90019502F /* MasterViewController.m */, 175 | 9B2076F31A9FA46C00D08704 /* StoryboardTableViewController.h */, 176 | 9B2076F41A9FA46C00D08704 /* StoryboardTableViewController.m */, 177 | F5C72C1C184771F90019502F /* DetailViewController.h */, 178 | F5C72C1D184771F90019502F /* DetailViewController.m */, 179 | 9B2076F91A9FA6D200D08704 /* SwipeableDataSource.h */, 180 | 9B2076FA1A9FA6D200D08704 /* SwipeableDataSource.m */, 181 | 9BF394EC18C58B0600E1CB12 /* DNSExampleImageViewCell.h */, 182 | 9BF394ED18C58B0600E1CB12 /* DNSExampleImageViewCell.m */, 183 | 9B2076F61A9FA49400D08704 /* DNSStoryboardCell.h */, 184 | 9B2076F71A9FA49400D08704 /* DNSStoryboardCell.m */, 185 | 9BF394FE18C5A4A000E1CB12 /* Library */, 186 | 9BF394EF18C58D6300E1CB12 /* instagrams */, 187 | 9BF394FC18C5A0DD00E1CB12 /* Assets.xcassets */, 188 | F5C72C0B184771F90019502F /* Supporting Files */, 189 | ); 190 | path = DNSSwipeableCell; 191 | sourceTree = ""; 192 | }; 193 | F5C72C0B184771F90019502F /* Supporting Files */ = { 194 | isa = PBXGroup; 195 | children = ( 196 | F5C72C0C184771F90019502F /* DNSSwipeableCell-Info.plist */, 197 | F5C72C0D184771F90019502F /* InfoPlist.strings */, 198 | F5C72C10184771F90019502F /* main.m */, 199 | F5C72C12184771F90019502F /* DNSSwipeableCell-Prefix.pch */, 200 | ); 201 | name = "Supporting Files"; 202 | sourceTree = ""; 203 | }; 204 | F5C72C2C184771F90019502F /* DNSSwipeableCellTests */ = { 205 | isa = PBXGroup; 206 | children = ( 207 | F5C72C32184771F90019502F /* DNSSwipeableCellTests.m */, 208 | F5C72C2D184771F90019502F /* Supporting Files */, 209 | ); 210 | path = DNSSwipeableCellTests; 211 | sourceTree = ""; 212 | }; 213 | F5C72C2D184771F90019502F /* Supporting Files */ = { 214 | isa = PBXGroup; 215 | children = ( 216 | F5C72C2E184771F90019502F /* DNSSwipeableCellTests-Info.plist */, 217 | F5C72C2F184771F90019502F /* InfoPlist.strings */, 218 | ); 219 | name = "Supporting Files"; 220 | sourceTree = ""; 221 | }; 222 | /* End PBXGroup section */ 223 | 224 | /* Begin PBXNativeTarget section */ 225 | F5C72C00184771F90019502F /* DNSSwipeableCell */ = { 226 | isa = PBXNativeTarget; 227 | buildConfigurationList = F5C72C36184771F90019502F /* Build configuration list for PBXNativeTarget "DNSSwipeableCell" */; 228 | buildPhases = ( 229 | F5C72BFD184771F90019502F /* Sources */, 230 | F5C72BFE184771F90019502F /* Frameworks */, 231 | F5C72BFF184771F90019502F /* Resources */, 232 | ); 233 | buildRules = ( 234 | ); 235 | dependencies = ( 236 | ); 237 | name = DNSSwipeableCell; 238 | productName = DNSSwipeableCell; 239 | productReference = F5C72C01184771F90019502F /* DNSSwipeableCell.app */; 240 | productType = "com.apple.product-type.application"; 241 | }; 242 | F5C72C24184771F90019502F /* DNSSwipeableCellTests */ = { 243 | isa = PBXNativeTarget; 244 | buildConfigurationList = F5C72C39184771F90019502F /* Build configuration list for PBXNativeTarget "DNSSwipeableCellTests" */; 245 | buildPhases = ( 246 | F5C72C21184771F90019502F /* Sources */, 247 | F5C72C22184771F90019502F /* Frameworks */, 248 | F5C72C23184771F90019502F /* Resources */, 249 | ); 250 | buildRules = ( 251 | ); 252 | dependencies = ( 253 | F5C72C2B184771F90019502F /* PBXTargetDependency */, 254 | ); 255 | name = DNSSwipeableCellTests; 256 | productName = DNSSwipeableCellTests; 257 | productReference = F5C72C25184771F90019502F /* DNSSwipeableCellTests.xctest */; 258 | productType = "com.apple.product-type.bundle.unit-test"; 259 | }; 260 | /* End PBXNativeTarget section */ 261 | 262 | /* Begin PBXProject section */ 263 | F5C72BF9184771F80019502F /* Project object */ = { 264 | isa = PBXProject; 265 | attributes = { 266 | LastUpgradeCheck = 0510; 267 | ORGANIZATIONNAME = "Designated Nerd Software"; 268 | TargetAttributes = { 269 | F5C72C24184771F90019502F = { 270 | TestTargetID = F5C72C00184771F90019502F; 271 | }; 272 | }; 273 | }; 274 | buildConfigurationList = F5C72BFC184771F80019502F /* Build configuration list for PBXProject "DNSSwipeableCell" */; 275 | compatibilityVersion = "Xcode 3.2"; 276 | developmentRegion = English; 277 | hasScannedForEncodings = 0; 278 | knownRegions = ( 279 | en, 280 | Base, 281 | ); 282 | mainGroup = F5C72BF8184771F80019502F; 283 | productRefGroup = F5C72C02184771F90019502F /* Products */; 284 | projectDirPath = ""; 285 | projectRoot = ""; 286 | targets = ( 287 | F5C72C00184771F90019502F /* DNSSwipeableCell */, 288 | F5C72C24184771F90019502F /* DNSSwipeableCellTests */, 289 | ); 290 | }; 291 | /* End PBXProject section */ 292 | 293 | /* Begin PBXResourcesBuildPhase section */ 294 | F5C72BFF184771F90019502F /* Resources */ = { 295 | isa = PBXResourcesBuildPhase; 296 | buildActionMask = 2147483647; 297 | files = ( 298 | 9BF394F718C58D6300E1CB12 /* dancer.jpg in Resources */, 299 | 9BF394FB18C58D6300E1CB12 /* super.jpg in Resources */, 300 | 9BF394F918C58D6300E1CB12 /* red.jpg in Resources */, 301 | 9BF394F818C58D6300E1CB12 /* ontivo.jpg in Resources */, 302 | 9BF394FD18C5A0DD00E1CB12 /* Assets.xcassets in Resources */, 303 | F5C72C0F184771F90019502F /* InfoPlist.strings in Resources */, 304 | 9BF394EB18C433CD00E1CB12 /* README.md in Resources */, 305 | 9BF394FA18C58D6300E1CB12 /* sandwichthief.jpg in Resources */, 306 | F5C72C18184771F90019502F /* Main.storyboard in Resources */, 307 | 9BF394F618C58D6300E1CB12 /* annoyed.jpg in Resources */, 308 | ); 309 | runOnlyForDeploymentPostprocessing = 0; 310 | }; 311 | F5C72C23184771F90019502F /* Resources */ = { 312 | isa = PBXResourcesBuildPhase; 313 | buildActionMask = 2147483647; 314 | files = ( 315 | F5C72C31184771F90019502F /* InfoPlist.strings in Resources */, 316 | ); 317 | runOnlyForDeploymentPostprocessing = 0; 318 | }; 319 | /* End PBXResourcesBuildPhase section */ 320 | 321 | /* Begin PBXSourcesBuildPhase section */ 322 | F5C72BFD184771F90019502F /* Sources */ = { 323 | isa = PBXSourcesBuildPhase; 324 | buildActionMask = 2147483647; 325 | files = ( 326 | 9BF3950118C5A4A000E1CB12 /* DNSSwipeableCell.m in Sources */, 327 | F5C72C15184771F90019502F /* AppDelegate.m in Sources */, 328 | F5C72C1B184771F90019502F /* MasterViewController.m in Sources */, 329 | 9BF394EE18C58B0600E1CB12 /* DNSExampleImageViewCell.m in Sources */, 330 | 9B2076F81A9FA49400D08704 /* DNSStoryboardCell.m in Sources */, 331 | 9B2076FB1A9FA6D200D08704 /* SwipeableDataSource.m in Sources */, 332 | 9B2076F51A9FA46C00D08704 /* StoryboardTableViewController.m in Sources */, 333 | F5C72C11184771F90019502F /* main.m in Sources */, 334 | F5C72C1E184771F90019502F /* DetailViewController.m in Sources */, 335 | ); 336 | runOnlyForDeploymentPostprocessing = 0; 337 | }; 338 | F5C72C21184771F90019502F /* Sources */ = { 339 | isa = PBXSourcesBuildPhase; 340 | buildActionMask = 2147483647; 341 | files = ( 342 | F5C72C33184771F90019502F /* DNSSwipeableCellTests.m in Sources */, 343 | ); 344 | runOnlyForDeploymentPostprocessing = 0; 345 | }; 346 | /* End PBXSourcesBuildPhase section */ 347 | 348 | /* Begin PBXTargetDependency section */ 349 | F5C72C2B184771F90019502F /* PBXTargetDependency */ = { 350 | isa = PBXTargetDependency; 351 | target = F5C72C00184771F90019502F /* DNSSwipeableCell */; 352 | targetProxy = F5C72C2A184771F90019502F /* PBXContainerItemProxy */; 353 | }; 354 | /* End PBXTargetDependency section */ 355 | 356 | /* Begin PBXVariantGroup section */ 357 | F5C72C0D184771F90019502F /* InfoPlist.strings */ = { 358 | isa = PBXVariantGroup; 359 | children = ( 360 | F5C72C0E184771F90019502F /* en */, 361 | ); 362 | name = InfoPlist.strings; 363 | sourceTree = ""; 364 | }; 365 | F5C72C16184771F90019502F /* Main.storyboard */ = { 366 | isa = PBXVariantGroup; 367 | children = ( 368 | F5C72C17184771F90019502F /* Base */, 369 | ); 370 | name = Main.storyboard; 371 | sourceTree = ""; 372 | }; 373 | F5C72C2F184771F90019502F /* InfoPlist.strings */ = { 374 | isa = PBXVariantGroup; 375 | children = ( 376 | F5C72C30184771F90019502F /* en */, 377 | ); 378 | name = InfoPlist.strings; 379 | sourceTree = ""; 380 | }; 381 | /* End PBXVariantGroup section */ 382 | 383 | /* Begin XCBuildConfiguration section */ 384 | F5C72C34184771F90019502F /* Debug */ = { 385 | isa = XCBuildConfiguration; 386 | buildSettings = { 387 | ALWAYS_SEARCH_USER_PATHS = NO; 388 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 389 | CLANG_CXX_LIBRARY = "libc++"; 390 | CLANG_ENABLE_MODULES = YES; 391 | CLANG_ENABLE_OBJC_ARC = YES; 392 | CLANG_WARN_BOOL_CONVERSION = YES; 393 | CLANG_WARN_CONSTANT_CONVERSION = YES; 394 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 395 | CLANG_WARN_EMPTY_BODY = YES; 396 | CLANG_WARN_ENUM_CONVERSION = YES; 397 | CLANG_WARN_INT_CONVERSION = YES; 398 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 399 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 400 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 401 | COPY_PHASE_STRIP = NO; 402 | GCC_C_LANGUAGE_STANDARD = gnu99; 403 | GCC_DYNAMIC_NO_PIC = NO; 404 | GCC_OPTIMIZATION_LEVEL = 0; 405 | GCC_PREPROCESSOR_DEFINITIONS = ( 406 | "DEBUG=1", 407 | "$(inherited)", 408 | ); 409 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 410 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 411 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 412 | GCC_WARN_UNDECLARED_SELECTOR = YES; 413 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 414 | GCC_WARN_UNUSED_FUNCTION = YES; 415 | GCC_WARN_UNUSED_VARIABLE = YES; 416 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 417 | ONLY_ACTIVE_ARCH = YES; 418 | SDKROOT = iphoneos; 419 | }; 420 | name = Debug; 421 | }; 422 | F5C72C35184771F90019502F /* Release */ = { 423 | isa = XCBuildConfiguration; 424 | buildSettings = { 425 | ALWAYS_SEARCH_USER_PATHS = NO; 426 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 427 | CLANG_CXX_LIBRARY = "libc++"; 428 | CLANG_ENABLE_MODULES = YES; 429 | CLANG_ENABLE_OBJC_ARC = YES; 430 | CLANG_WARN_BOOL_CONVERSION = YES; 431 | CLANG_WARN_CONSTANT_CONVERSION = YES; 432 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 433 | CLANG_WARN_EMPTY_BODY = YES; 434 | CLANG_WARN_ENUM_CONVERSION = YES; 435 | CLANG_WARN_INT_CONVERSION = YES; 436 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 437 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 438 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 439 | COPY_PHASE_STRIP = YES; 440 | ENABLE_NS_ASSERTIONS = NO; 441 | GCC_C_LANGUAGE_STANDARD = gnu99; 442 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 443 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 444 | GCC_WARN_UNDECLARED_SELECTOR = YES; 445 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 446 | GCC_WARN_UNUSED_FUNCTION = YES; 447 | GCC_WARN_UNUSED_VARIABLE = YES; 448 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 449 | SDKROOT = iphoneos; 450 | VALIDATE_PRODUCT = YES; 451 | }; 452 | name = Release; 453 | }; 454 | F5C72C37184771F90019502F /* Debug */ = { 455 | isa = XCBuildConfiguration; 456 | buildSettings = { 457 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 458 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 459 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 460 | GCC_PREFIX_HEADER = "DNSSwipeableCell/DNSSwipeableCell-Prefix.pch"; 461 | INFOPLIST_FILE = "DNSSwipeableCell/DNSSwipeableCell-Info.plist"; 462 | PRODUCT_NAME = "$(TARGET_NAME)"; 463 | WRAPPER_EXTENSION = app; 464 | }; 465 | name = Debug; 466 | }; 467 | F5C72C38184771F90019502F /* Release */ = { 468 | isa = XCBuildConfiguration; 469 | buildSettings = { 470 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 471 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 472 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 473 | GCC_PREFIX_HEADER = "DNSSwipeableCell/DNSSwipeableCell-Prefix.pch"; 474 | INFOPLIST_FILE = "DNSSwipeableCell/DNSSwipeableCell-Info.plist"; 475 | PRODUCT_NAME = "$(TARGET_NAME)"; 476 | WRAPPER_EXTENSION = app; 477 | }; 478 | name = Release; 479 | }; 480 | F5C72C3A184771F90019502F /* Debug */ = { 481 | isa = XCBuildConfiguration; 482 | buildSettings = { 483 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/DNSSwipeableCell.app/DNSSwipeableCell"; 484 | FRAMEWORK_SEARCH_PATHS = ( 485 | "$(SDKROOT)/Developer/Library/Frameworks", 486 | "$(inherited)", 487 | "$(DEVELOPER_FRAMEWORKS_DIR)", 488 | ); 489 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 490 | GCC_PREFIX_HEADER = "DNSSwipeableCell/DNSSwipeableCell-Prefix.pch"; 491 | GCC_PREPROCESSOR_DEFINITIONS = ( 492 | "DEBUG=1", 493 | "$(inherited)", 494 | ); 495 | INFOPLIST_FILE = "DNSSwipeableCellTests/DNSSwipeableCellTests-Info.plist"; 496 | PRODUCT_NAME = "$(TARGET_NAME)"; 497 | TEST_HOST = "$(BUNDLE_LOADER)"; 498 | WRAPPER_EXTENSION = xctest; 499 | }; 500 | name = Debug; 501 | }; 502 | F5C72C3B184771F90019502F /* Release */ = { 503 | isa = XCBuildConfiguration; 504 | buildSettings = { 505 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/DNSSwipeableCell.app/DNSSwipeableCell"; 506 | FRAMEWORK_SEARCH_PATHS = ( 507 | "$(SDKROOT)/Developer/Library/Frameworks", 508 | "$(inherited)", 509 | "$(DEVELOPER_FRAMEWORKS_DIR)", 510 | ); 511 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 512 | GCC_PREFIX_HEADER = "DNSSwipeableCell/DNSSwipeableCell-Prefix.pch"; 513 | INFOPLIST_FILE = "DNSSwipeableCellTests/DNSSwipeableCellTests-Info.plist"; 514 | PRODUCT_NAME = "$(TARGET_NAME)"; 515 | TEST_HOST = "$(BUNDLE_LOADER)"; 516 | WRAPPER_EXTENSION = xctest; 517 | }; 518 | name = Release; 519 | }; 520 | /* End XCBuildConfiguration section */ 521 | 522 | /* Begin XCConfigurationList section */ 523 | F5C72BFC184771F80019502F /* Build configuration list for PBXProject "DNSSwipeableCell" */ = { 524 | isa = XCConfigurationList; 525 | buildConfigurations = ( 526 | F5C72C34184771F90019502F /* Debug */, 527 | F5C72C35184771F90019502F /* Release */, 528 | ); 529 | defaultConfigurationIsVisible = 0; 530 | defaultConfigurationName = Release; 531 | }; 532 | F5C72C36184771F90019502F /* Build configuration list for PBXNativeTarget "DNSSwipeableCell" */ = { 533 | isa = XCConfigurationList; 534 | buildConfigurations = ( 535 | F5C72C37184771F90019502F /* Debug */, 536 | F5C72C38184771F90019502F /* Release */, 537 | ); 538 | defaultConfigurationIsVisible = 0; 539 | defaultConfigurationName = Release; 540 | }; 541 | F5C72C39184771F90019502F /* Build configuration list for PBXNativeTarget "DNSSwipeableCellTests" */ = { 542 | isa = XCConfigurationList; 543 | buildConfigurations = ( 544 | F5C72C3A184771F90019502F /* Debug */, 545 | F5C72C3B184771F90019502F /* Release */, 546 | ); 547 | defaultConfigurationIsVisible = 0; 548 | defaultConfigurationName = Release; 549 | }; 550 | /* End XCConfigurationList section */ 551 | }; 552 | rootObject = F5C72BF9184771F80019502F /* Project object */; 553 | } 554 | -------------------------------------------------------------------------------- /DNSSwipeableCell/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // DNSSwipeableCell 4 | // 5 | // Created by Ellen Shapiro on 11/28/13. 6 | // Copyright (c) 2013 Designated Nerd Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /DNSSwipeableCell/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // DNSSwipeableCell 4 | // 5 | // Created by Ellen Shapiro on 11/28/13. 6 | // Copyright (c) 2013 Designated Nerd Software. 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 | -------------------------------------------------------------------------------- /DNSSwipeableCell/Base.lproj/Main.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 | 27 | 28 | 29 | 30 | 31 | 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 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /DNSSwipeableCell/DNSExampleImageViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // DNSExampleImageViewCell.h 3 | // DNSSwipeableCell 4 | // 5 | // Created by Ellen Shapiro on 3/3/14. 6 | // Copyright (c) 2014 Designated Nerd Software. All rights reserved. 7 | // 8 | 9 | #import "DNSSwipeableCell.h" 10 | 11 | FOUNDATION_EXPORT CGFloat const kExampleCellHeight; 12 | 13 | /** 14 | * This subclass is an example of how you can subclass DNSSwipeableCell to create your own 15 | * custom cells that can still be properly recycled. 16 | */ 17 | @interface DNSExampleImageViewCell : DNSSwipeableCell 18 | 19 | @property (nonatomic, strong) UIImageView *exampleImageView; 20 | @property (nonatomic, strong) UILabel *exampleLabel; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /DNSSwipeableCell/DNSExampleImageViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // DNSExampleImageViewCell.m 3 | // DNSSwipeableCell 4 | // 5 | // Created by Ellen Shapiro on 3/3/14. 6 | // Copyright (c) 2014 Designated Nerd Software. All rights reserved. 7 | // 8 | 9 | #import "DNSExampleImageViewCell.h" 10 | 11 | //Public constants 12 | CGFloat const kExampleCellHeight = 100.0f; 13 | 14 | //Private constants 15 | static CGFloat const kExampleCellLeftMargin = 15.0f; 16 | static CGFloat const kExampleCellRightMargin = 20.0f; 17 | static CGFloat const kBetweenViewsMargin = 8.0f; 18 | 19 | @interface DNSExampleImageViewCell() 20 | 21 | @end 22 | @implementation DNSExampleImageViewCell 23 | 24 | - (void)commonInit 25 | { 26 | [super commonInit]; 27 | 28 | //Setup the pieces of this cell which will be reused 29 | CGFloat imageHeight = kExampleCellHeight - (kBetweenViewsMargin * 2); 30 | self.exampleImageView = [[UIImageView alloc] initWithFrame:CGRectMake(kExampleCellLeftMargin, kBetweenViewsMargin, imageHeight, imageHeight)]; 31 | [self.myContentView addSubview:self.exampleImageView]; 32 | 33 | CGFloat labelXOrigin = CGRectGetMaxX(self.exampleImageView.frame) + kBetweenViewsMargin; 34 | CGFloat labelWidth = CGRectGetWidth(self.frame) - labelXOrigin - kExampleCellRightMargin; 35 | self.exampleLabel = [[UILabel alloc] initWithFrame:CGRectMake(labelXOrigin, 0, labelWidth, kExampleCellHeight)]; 36 | self.exampleLabel.numberOfLines = 0; 37 | [self.myContentView addSubview:self.exampleLabel]; 38 | } 39 | 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /DNSSwipeableCell/DNSStoryboardCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // DNSStoryboardCell.h 3 | // DNSSwipeableCell 4 | // 5 | // Created by Ellen Shapiro on 2/26/15. 6 | // Copyright (c) 2015 Designated Nerd Software. All rights reserved. 7 | // 8 | 9 | #import "DNSSwipeableCell.h" 10 | 11 | @interface DNSStoryboardCell : DNSSwipeableCell 12 | 13 | + (NSString *)reuseIdentifier; 14 | + (CGFloat)cellHeight; 15 | 16 | @property (nonatomic, weak) IBOutlet UIImageView *storyboardImageView; 17 | @property (nonatomic, weak) IBOutlet UILabel *storyboardExampleLabel; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /DNSSwipeableCell/DNSStoryboardCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // DNSStoryboardCell.m 3 | // DNSSwipeableCell 4 | // 5 | // Created by Ellen Shapiro on 2/26/15. 6 | // Copyright (c) 2015 Designated Nerd Software. All rights reserved. 7 | // 8 | 9 | #import "DNSStoryboardCell.h" 10 | 11 | @implementation DNSStoryboardCell 12 | 13 | - (void)awakeFromNib 14 | { 15 | if (![[NSProcessInfo processInfo] respondsToSelector:@selector(isOperatingSystemAtLeastVersion:)]) { 16 | //We're on iOS 7, and cells are dumb. 17 | [self.contentView setAutoresizingMask:UIViewAutoresizingFlexibleHeight]; 18 | } 19 | 20 | [super awakeFromNib]; 21 | } 22 | 23 | + (NSString *)reuseIdentifier 24 | { 25 | return NSStringFromClass(self); 26 | } 27 | 28 | + (CGFloat)cellHeight 29 | { 30 | return 400.0f; 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /DNSSwipeableCell/DNSSwipeableCell-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.designatednerd.${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 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarTintParameters 34 | 35 | UINavigationBar 36 | 37 | Style 38 | UIBarStyleDefault 39 | Translucent 40 | 41 | 42 | 43 | UISupportedInterfaceOrientations 44 | 45 | UIInterfaceOrientationPortrait 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /DNSSwipeableCell/DNSSwipeableCell-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /DNSSwipeableCell/DetailViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DetailViewController.h 3 | // DNSSwipeableCell 4 | // 5 | // Created by Ellen Shapiro on 11/28/13. 6 | // Copyright (c) 2013 Designated Nerd Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DetailViewController : UIViewController 12 | 13 | @property (nonatomic, strong) UIImage *detailImage; 14 | @property (nonatomic, strong) NSString *detailText; 15 | @end 16 | -------------------------------------------------------------------------------- /DNSSwipeableCell/DetailViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DetailViewController.m 3 | // DNSSwipeableCell 4 | // 5 | // Created by Ellen Shapiro on 11/28/13. 6 | // Copyright (c) 2013 Designated Nerd Software. All rights reserved. 7 | // 8 | 9 | #import "DetailViewController.h" 10 | 11 | @interface DetailViewController () 12 | @property (nonatomic, weak) IBOutlet UIImageView *imageView; 13 | @property (nonatomic, weak) IBOutlet UILabel *detailDescriptionLabel; 14 | 15 | @end 16 | 17 | @implementation DetailViewController 18 | 19 | #pragma mark - Managing the detail item 20 | 21 | - (void)setDetailImage:(UIImage *)detailImage 22 | { 23 | _detailImage = detailImage; 24 | // Update the view. 25 | [self configureView]; 26 | } 27 | 28 | - (void)setDetailText:(NSString *)detailText 29 | { 30 | _detailText = detailText; 31 | [self configureView]; 32 | } 33 | 34 | - (void)configureView 35 | { 36 | // Update the user interface for the detail item. 37 | 38 | if (self.detailImage) { 39 | self.imageView.image = self.detailImage; 40 | } 41 | 42 | if (self.detailText) { 43 | self.detailDescriptionLabel.text = self.detailText; 44 | } 45 | } 46 | 47 | - (void)viewDidLoad 48 | { 49 | [super viewDidLoad]; 50 | // Do any additional setup after loading the view, typically from a nib. 51 | [self configureView]; 52 | } 53 | 54 | - (void)didReceiveMemoryWarning 55 | { 56 | [super didReceiveMemoryWarning]; 57 | // Dispose of any resources that can be recreated. 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /DNSSwipeableCell/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /DNSSwipeableCell/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /DNSSwipeableCell/MasterViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MasterViewController.h 3 | // DNSSwipeableCell 4 | // 5 | // Created by Ellen Shapiro on 11/28/13. 6 | // Copyright (c) 2013 Designated Nerd Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MasterViewController : UITableViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /DNSSwipeableCell/MasterViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MasterViewController.m 3 | // DNSSwipeableCell 4 | // 5 | // Created by Ellen Shapiro on 11/28/13. 6 | // Copyright (c) 2013 Designated Nerd Software. All rights reserved. 7 | // 8 | 9 | #import "MasterViewController.h" 10 | 11 | #import "DetailViewController.h" 12 | #import "DNSExampleImageViewCell.h" 13 | #import "SwipeableDataSource.h" 14 | 15 | @interface MasterViewController () 16 | @property (nonatomic, strong) SwipeableDataSource *dataSource; 17 | @end 18 | 19 | static NSString * const kDNSExampleImageCellIdentifier = @"Cell"; 20 | 21 | @implementation MasterViewController 22 | 23 | #pragma mark - View Lifecycle 24 | 25 | - (void)viewDidLoad 26 | { 27 | [super viewDidLoad]; 28 | 29 | //Register the custom subclass 30 | [self.tableView registerClass:[DNSExampleImageViewCell class] forCellReuseIdentifier:kDNSExampleImageCellIdentifier]; 31 | 32 | //Setup the data source. 33 | self.dataSource = [SwipeableDataSource sourceWithTableView:self.tableView 34 | viewController:self 35 | cellIdentifier:kDNSExampleImageCellIdentifier]; 36 | } 37 | 38 | - (IBAction)showStoryboardVC:(id)sender 39 | { 40 | UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; 41 | UINavigationController *storyboardNav = [storyboard instantiateViewControllerWithIdentifier:@"StoryboardNav"]; 42 | 43 | [self presentViewController:storyboardNav animated:YES completion:nil]; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /DNSSwipeableCell/StoryboardTableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // StoryboardTableViewController.h 3 | // DNSSwipeableCell 4 | // 5 | // Created by Ellen Shapiro on 2/26/15. 6 | // Copyright (c) 2015 Designated Nerd Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface StoryboardTableViewController : UITableViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /DNSSwipeableCell/StoryboardTableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // StoryboardTableViewController.m 3 | // DNSSwipeableCell 4 | // 5 | // Created by Ellen Shapiro on 2/26/15. 6 | // Copyright (c) 2015 Designated Nerd Software. All rights reserved. 7 | // 8 | 9 | #import "StoryboardTableViewController.h" 10 | 11 | #import "SwipeableDataSource.h" 12 | #import "DNSStoryboardCell.h" 13 | 14 | @interface StoryboardTableViewController () 15 | @property (nonatomic, strong) SwipeableDataSource *dataSource; 16 | @end 17 | 18 | @implementation StoryboardTableViewController 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | 23 | self.title = @"Cells from the storyboard!"; 24 | 25 | self.dataSource = [SwipeableDataSource sourceWithTableView:self.tableView 26 | viewController:self 27 | cellIdentifier:[DNSStoryboardCell reuseIdentifier]]; 28 | } 29 | 30 | - (IBAction)close:(id)sender 31 | { 32 | [self dismissViewControllerAnimated:YES completion:nil]; 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /DNSSwipeableCell/SwipeableDataSource.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwipeableDataSource.h 3 | // DNSSwipeableCell 4 | // 5 | // Created by Ellen Shapiro on 2/26/15. 6 | // Copyright (c) 2015 Designated Nerd Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SwipeableDataSource : NSObject 12 | 13 | 14 | /** 15 | * Instantiator 16 | * 17 | * @param tableView The table view to use, with nibs and classes already registered with cell identiiers. 18 | * @param viewController The current view controller which should present/dismiss new views. 19 | * @param cellIdentifier The cell identifier to use to dequeue cells. 20 | * 21 | * @return The instantiated data source. 22 | */ 23 | + (SwipeableDataSource *)sourceWithTableView:(UITableView *)tableView 24 | viewController:(UIViewController *)viewController 25 | cellIdentifier:(NSString *)cellIdentifier; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /DNSSwipeableCell/SwipeableDataSource.m: -------------------------------------------------------------------------------- 1 | // 2 | // SwipeableDataSource.m 3 | // DNSSwipeableCell 4 | // 5 | // Created by Ellen Shapiro on 2/26/15. 6 | // Copyright (c) 2015 Designated Nerd Software. All rights reserved. 7 | // 8 | 9 | #import "SwipeableDataSource.h" 10 | #import "DNSSwipeableCell.h" 11 | #import "DNSExampleImageViewCell.h" 12 | #import "DNSStoryboardCell.h" 13 | #import "DetailViewController.h" 14 | 15 | @interface SwipeableDataSource() 16 | @property (nonatomic, strong) NSMutableArray *cellsCurrentlyEditing; 17 | @property (nonatomic, strong) NSMutableArray *itemTitles; 18 | @property (nonatomic, strong) NSArray *backgroundColors; 19 | @property (nonatomic, strong) NSArray *textColors; 20 | @property (nonatomic, strong) NSArray *imageNames; 21 | @property (nonatomic, weak) UITableView *tableView; 22 | @property (nonatomic, weak) UIViewController *currentViewController; 23 | @property (nonatomic) NSString *cellIdentifier; 24 | 25 | @end 26 | 27 | static NSInteger kSelectedRowInsteadOfButton = -1; 28 | 29 | @implementation SwipeableDataSource 30 | 31 | + (SwipeableDataSource *)sourceWithTableView:(UITableView *)tableView 32 | viewController:(UIViewController *)viewController 33 | cellIdentifier:(NSString *)cellIdentifier 34 | { 35 | SwipeableDataSource *dataSource = [[SwipeableDataSource alloc] init]; 36 | dataSource.tableView = tableView; 37 | dataSource.tableView.dataSource = dataSource; 38 | dataSource.tableView.delegate = dataSource; 39 | dataSource.cellIdentifier = cellIdentifier; 40 | dataSource.currentViewController = viewController; 41 | 42 | 43 | 44 | //Create an array of image names 45 | dataSource.imageNames = @[ @"annoyed.jpg", 46 | @"dancer.jpg", 47 | @"ontivo.jpg", 48 | @"red.jpg", 49 | @"sandwichthief.jpg", 50 | @"super.jpg"]; 51 | 52 | //Create arrays of random background and text colors 53 | dataSource.backgroundColors = @[[UIColor blueColor], 54 | [UIColor greenColor], 55 | [UIColor orangeColor], 56 | [UIColor darkGrayColor], 57 | [UIColor purpleColor], 58 | [UIColor lightGrayColor], 59 | ]; 60 | 61 | dataSource.textColors = @[[UIColor whiteColor], 62 | [UIColor blackColor]]; 63 | 64 | //Initialize the mutable array so you can add stuff to it. 65 | dataSource.itemTitles = [NSMutableArray array]; 66 | dataSource.cellsCurrentlyEditing = [NSMutableArray array]; 67 | 68 | //Create a whole bunch of string objects, and add them to the array. 69 | NSInteger numberOfItems = 30; 70 | for (NSInteger i = 1; i <= numberOfItems; i++) { 71 | NSString *item = [NSString stringWithFormat:@"Longer Title Item #%@", @(i)]; 72 | [dataSource.itemTitles addObject:item]; 73 | } 74 | 75 | return dataSource; 76 | } 77 | 78 | #pragma mark - Table View Data Source 79 | 80 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 81 | { 82 | return 1; 83 | } 84 | 85 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 86 | { 87 | if ([self.cellIdentifier isEqualToString:[DNSStoryboardCell reuseIdentifier]]) { 88 | return [DNSStoryboardCell cellHeight]; 89 | } else { 90 | return kExampleCellHeight; 91 | } 92 | } 93 | 94 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 95 | { 96 | return self.itemTitles.count; 97 | } 98 | 99 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 100 | { 101 | //Recycle! 102 | DNSSwipeableCell *cell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifier forIndexPath:indexPath]; 103 | NSString *textItem = self.itemTitles[indexPath.row]; 104 | NSString *imageName = self.imageNames[indexPath.row % self.imageNames.count]; 105 | UIImage *image = [UIImage imageNamed:imageName]; 106 | 107 | if ([cell isKindOfClass:[DNSExampleImageViewCell class]]) { 108 | [self configureProgrammaticCell:(DNSExampleImageViewCell *)cell 109 | withTextItem:textItem 110 | andImage:image]; 111 | } else if ([cell isKindOfClass:[DNSStoryboardCell class]]) { 112 | [self configureStoryboardCell:(DNSStoryboardCell *)cell 113 | withTextItem:textItem 114 | andImage:image]; 115 | } else { 116 | NSAssert(NO, @"Unexpected class of cell %@", NSStringFromClass([cell class])); 117 | } 118 | 119 | 120 | 121 | //Set up the buttons 122 | cell.dataSource = self; 123 | cell.delegate = self; 124 | 125 | [cell setNeedsUpdateConstraints]; 126 | 127 | //Reopen the cell if it was already editing 128 | if ([self.cellsCurrentlyEditing containsObject:indexPath]) { 129 | [cell openCell:NO]; 130 | } 131 | 132 | return cell; 133 | } 134 | 135 | - (void)configureProgrammaticCell:(DNSExampleImageViewCell *)cell withTextItem:(NSString *)textItem andImage:(UIImage *)image 136 | { 137 | //Setup the label and image 138 | cell.exampleLabel.text = textItem; 139 | cell.exampleImageView.image = image; 140 | } 141 | 142 | - (void)configureStoryboardCell:(DNSStoryboardCell *)cell withTextItem:(NSString *)textItem andImage:(UIImage *)image 143 | { 144 | cell.storyboardExampleLabel.text = textItem; 145 | cell.storyboardImageView.image = image; 146 | } 147 | 148 | - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath 149 | { 150 | //This needs to return NO or you'll only get the stock delete button. 151 | return NO; 152 | } 153 | 154 | - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath 155 | { 156 | if (editingStyle == UITableViewCellEditingStyleDelete) { 157 | //Deletes the object from the array 158 | [_itemTitles removeObjectAtIndex:indexPath.row]; 159 | 160 | //Deletes the row from the tableView. 161 | [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; 162 | } else { 163 | //This is something that hasn't been set up yet - add a log to determine 164 | //what sort of editing style you also need to handle. 165 | NSLog(@"Unhandled editing style! %@", @(editingStyle)); 166 | } 167 | 168 | [self.tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.5]; 169 | } 170 | 171 | #pragma mark - UITableViewDelegate 172 | 173 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 174 | { 175 | [self showDetailForIndexPath:indexPath fromDelegateButtonAtIndex:kSelectedRowInsteadOfButton]; 176 | } 177 | 178 | #pragma mark - DNSSwipeableCellDataSource 179 | 180 | #pragma mark Required Methods 181 | 182 | - (NSInteger)numberOfButtonsInSwipeableCell:(DNSSwipeableCell *)cell 183 | { 184 | NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:cell.center]; 185 | 186 | if (indexPath.row % 2 == 0) { 187 | //Even rows have 2 options 188 | return 2; 189 | } else { 190 | //Odd rows 3 options 191 | return 3; 192 | } 193 | } 194 | 195 | #pragma mark Optional Methods 196 | 197 | ////Uncomment this optional styling method to muck around with the font. 198 | //- (UIFont *)swipeableCell:(DNSSwipeableCell *)cell fontForButtonAtIndex:(NSInteger)index 199 | //{ 200 | // //List of fonts available on iOS 7: http://support.apple.com/kb/HT5878 201 | // return [UIFont fontWithName:@"AmericanTypewriter" size:14.0f]; 202 | //} 203 | 204 | //Uncomment to show fully custom button 205 | //- (UIButton *)swipeableCell:(DNSSwipeableCell *)cell buttonForIndex:(NSInteger)index 206 | //{ 207 | // UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 208 | // [button setTitle:@"Custom" forState:UIControlStateNormal]; 209 | // [button setFrame:CGRectMake(0.0f, 0.0f, 100.0f, 10.0f)]; 210 | // [button setBackgroundColor:[UIColor redColor]]; 211 | // 212 | // return button; 213 | //} 214 | 215 | - (NSString *)swipeableCell:(DNSSwipeableCell *)cell titleForButtonAtIndex:(NSInteger)index 216 | { 217 | NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:cell.center]; 218 | switch (index) { 219 | case 0: 220 | return NSLocalizedString(@"Delete", @"Delete"); 221 | break; 222 | case 1: 223 | return (indexPath.row == 0) ? @"" : NSLocalizedString(@"Option 1", @"Option 1"); 224 | break; 225 | case 2: 226 | return NSLocalizedString(@"Option 2", @"Option 2"); 227 | break; 228 | default: 229 | break; 230 | } 231 | 232 | return nil; 233 | } 234 | 235 | - (UIImage *)swipeableCell:(DNSSwipeableCell *)cell imageForButtonAtIndex:(NSInteger)index 236 | { 237 | NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:cell.center]; 238 | 239 | //Return a user image for the first row only. 240 | if (indexPath.row == 0 && index == 1) { 241 | return [UIImage imageNamed:@"user"]; 242 | } else { 243 | return nil; 244 | } 245 | } 246 | 247 | - (UIColor *)swipeableCell:(DNSSwipeableCell *)cell backgroundColorForButtonAtIndex:(NSInteger)index 248 | { 249 | switch (index) { 250 | case 0: 251 | return [UIColor redColor]; 252 | break; 253 | default: { 254 | //Note that the random index means colors won't persist after recycling. 255 | NSInteger randomIndex = floorf (arc4random() % self.backgroundColors.count); 256 | return self.backgroundColors[randomIndex]; 257 | } 258 | break; 259 | } 260 | } 261 | 262 | - (UIColor *)swipeableCell:(DNSSwipeableCell *)cell tintColorForButtonAtIndex:(NSInteger)index 263 | { 264 | switch (index) { 265 | case 0: 266 | return self.textColors[0]; 267 | break; 268 | default: { 269 | //Note that the random index means colors won't persist after recycling. 270 | NSInteger randomIndex = floorf(arc4random() % self.textColors.count); 271 | return self.textColors[randomIndex]; 272 | } 273 | break; 274 | } 275 | } 276 | 277 | #pragma mark - DNSSwipeableCellDelegate 278 | 279 | - (void)swipeableCell:(DNSSwipeableCell *)cell didSelectButtonAtIndex:(NSInteger)index 280 | { 281 | NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:cell.center]; 282 | 283 | if (index == 0) { 284 | [self.cellsCurrentlyEditing removeObject:indexPath]; 285 | [self tableView:self.tableView commitEditingStyle:UITableViewCellEditingStyleDelete forRowAtIndexPath:indexPath]; 286 | } else { 287 | [self showDetailForIndexPath:indexPath fromDelegateButtonAtIndex:index]; 288 | } 289 | } 290 | 291 | - (void)swipeableCellDidOpen:(DNSSwipeableCell *)cell 292 | { 293 | NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:cell.center]; 294 | [self.cellsCurrentlyEditing addObject:indexPath]; 295 | } 296 | 297 | - (void)swipeableCellDidClose:(DNSSwipeableCell *)cell 298 | { 299 | NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:cell.center]; 300 | [self.cellsCurrentlyEditing removeObject:indexPath]; 301 | } 302 | 303 | #pragma mark - Detail handling. 304 | 305 | - (void)showDetailForIndexPath:(NSIndexPath *)indexPath fromDelegateButtonAtIndex:(NSInteger)buttonIndex 306 | { 307 | DNSSwipeableCell *cell = (DNSSwipeableCell*)[self.tableView cellForRowAtIndexPath:indexPath]; 308 | 309 | //Instantiate the DetailVC out of the storyboard. 310 | UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; 311 | DetailViewController *detail = [storyboard instantiateViewControllerWithIdentifier:@"DetailViewController"]; 312 | NSString *title = self.itemTitles[indexPath.row]; 313 | if (buttonIndex != kSelectedRowInsteadOfButton) { 314 | NSString *textForCellButton = [self swipeableCell:cell titleForButtonAtIndex:buttonIndex]; 315 | title = [NSString stringWithFormat:@"%@: %@", title, textForCellButton]; 316 | } else { 317 | title = self.itemTitles[indexPath.row]; 318 | } 319 | 320 | detail.detailText = title; 321 | NSString *imageName = self.imageNames[indexPath.row % self.imageNames.count]; 322 | detail.detailImage = [UIImage imageNamed:imageName]; 323 | 324 | if (buttonIndex == kSelectedRowInsteadOfButton) { 325 | detail.title = @"Selected!"; 326 | [self.currentViewController.navigationController pushViewController:detail animated:YES]; 327 | } else { 328 | //Present modally 329 | detail.title = @"In the delegate!"; 330 | 331 | //Setup nav controller to contain the detail vc. 332 | UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:detail]; 333 | 334 | //Setup button to close the detail VC (will call the method below. 335 | UIBarButtonItem *done = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(closeModal)]; 336 | [detail.navigationItem setRightBarButtonItem:done]; 337 | [self.currentViewController presentViewController:navController animated:YES completion:nil]; 338 | } 339 | } 340 | 341 | - (void)closeModal 342 | { 343 | [self.currentViewController dismissViewControllerAnimated:YES completion:nil]; 344 | } 345 | 346 | @end 347 | -------------------------------------------------------------------------------- /DNSSwipeableCell/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /DNSSwipeableCell/instagrams/annoyed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designatednerd/DNSSwipeableTableCell/45aec721cff84d3254976c6ed54d10e5aff1aa1b/DNSSwipeableCell/instagrams/annoyed.jpg -------------------------------------------------------------------------------- /DNSSwipeableCell/instagrams/dancer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designatednerd/DNSSwipeableTableCell/45aec721cff84d3254976c6ed54d10e5aff1aa1b/DNSSwipeableCell/instagrams/dancer.jpg -------------------------------------------------------------------------------- /DNSSwipeableCell/instagrams/ontivo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designatednerd/DNSSwipeableTableCell/45aec721cff84d3254976c6ed54d10e5aff1aa1b/DNSSwipeableCell/instagrams/ontivo.jpg -------------------------------------------------------------------------------- /DNSSwipeableCell/instagrams/red.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designatednerd/DNSSwipeableTableCell/45aec721cff84d3254976c6ed54d10e5aff1aa1b/DNSSwipeableCell/instagrams/red.jpg -------------------------------------------------------------------------------- /DNSSwipeableCell/instagrams/sandwichthief.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designatednerd/DNSSwipeableTableCell/45aec721cff84d3254976c6ed54d10e5aff1aa1b/DNSSwipeableCell/instagrams/sandwichthief.jpg -------------------------------------------------------------------------------- /DNSSwipeableCell/instagrams/super.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designatednerd/DNSSwipeableTableCell/45aec721cff84d3254976c6ed54d10e5aff1aa1b/DNSSwipeableCell/instagrams/super.jpg -------------------------------------------------------------------------------- /DNSSwipeableCell/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // DNSSwipeableCell 4 | // 5 | // Created by Ellen Shapiro on 11/28/13. 6 | // Copyright (c) 2013 Designated Nerd Software. 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 | -------------------------------------------------------------------------------- /DNSSwipeableCellTests/DNSSwipeableCellTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.designatednerd.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /DNSSwipeableCellTests/DNSSwipeableCellTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // DNSSwipeableCellTests.m 3 | // DNSSwipeableCellTests 4 | // 5 | // Created by Ellen Shapiro on 11/28/13. 6 | // Copyright (c) 2013 Designated Nerd Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DNSSwipeableCellTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation DNSSwipeableCellTests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /DNSSwipeableCellTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /DNSSwipeableTableCell.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "DNSSwipeableTableCell" 3 | s.version = "1.2.1" 4 | s.summary = "A swipeable UITableViewCell for iOS 7+." 5 | s.description = "UITableViewCell subclass to add multiple buttons to a swipe-under menu like iOS 7 Mail." 6 | s.homepage = "https://github.com/designatednerd/DNSSwipeableTableCell" 7 | s.screenshots = "https://raw.githubusercontent.com/designatednerd/DNSSwipeableTableCell/master/swipeable.gif" 8 | s.license = { :type => 'MIT', :file => 'LICENSE.md' } 9 | s.author = "Ellen Shapiro" 10 | s.platform = :ios, '7.0' 11 | s.source = { :git => "https://github.com/designatednerd/DNSSwipeableTableCell.git", :tag => "v#{s.version}" } 12 | s.source_files = 'Library' 13 | s.requires_arc = true 14 | end 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Ellen Shapiro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Library/DNSSwipeableCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // DNSSwipeableCell.h 3 | // DNSSwipeableCell 4 | // 5 | // Created by Ellen Shapiro on 12/29/13. 6 | // Copyright (c) 2013 Designated Nerd Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class DNSSwipeableCell; 12 | 13 | #pragma mark - Data Source 14 | 15 | @protocol DNSSwipeableCellDataSource 16 | 17 | @required 18 | 19 | /** 20 | * The number of buttons which should be built for a particular swipeable cell 21 | * 22 | * @param cell - The cell for which to determine the button count 23 | * @return The number of buttons for said cell. 24 | */ 25 | - (NSInteger)numberOfButtonsInSwipeableCell:(DNSSwipeableCell *)cell; 26 | 27 | @optional 28 | 29 | /** 30 | * The button at a given index in a particular swipeable cell. 31 | * 32 | * REMEMBER: Button indexes will be right to left since that's the way the cell 33 | * slides open - for example | index 2 index 1 index 0 |. 34 | * 35 | * @param cell - The cell for which to find the button title 36 | * @param index - The index of the button for which this string should be the title. 37 | * @return The UIButton to display 38 | */ 39 | - (UIButton *)swipeableCell:(DNSSwipeableCell *)cell buttonForIndex:(NSInteger)index; 40 | 41 | /** 42 | * The title for the button at a given index in a particular swipeable cell. Defaults to 43 | * an empty string. 44 | * 45 | * REMEMBER: Button indexes will be right to left since that's the way the cell 46 | * slides open - for example | index 2 index 1 index 0 |. 47 | * 48 | * Does nothing if -swipeableCell: buttonForIndex: implemented. 49 | * 50 | * @param cell - The cell for which to find the button title 51 | * @param index - The index of the button for which this string should be the title. 52 | * @return The title string to display. 53 | */ 54 | - (NSString *)swipeableCell:(DNSSwipeableCell *)cell titleForButtonAtIndex:(NSInteger)index; 55 | 56 | /** 57 | * The color for the background of a button at the given index in a swipeable cell at 58 | * the given index path. Defaults to [UIColor redColor] for index 0 and [UIColor 59 | * lightGrayColor] for all other indexes 60 | * 61 | * REMEMBER: Button indexes will be right to left since that's the way the cell 62 | * slides open - for example | index 2 index 1 index 0 |. 63 | * 64 | * Does nothing if -swipeableCell: buttonForIndex: implemented. 65 | * 66 | * @param cell - The cell for which to find the button background 67 | * @param index - The index of the button for which this color should be the backgroundColor. 68 | * @return The background color you wish to display. 69 | */ 70 | - (UIColor *)swipeableCell:(DNSSwipeableCell *)cell backgroundColorForButtonAtIndex:(NSInteger)index; 71 | 72 | /** 73 | * The color for the text of a button at the given index in a swipeable cell at 74 | * the given index path. Defaults to [UIColor whiteColor] 75 | * 76 | * REMEMBER: Button indexes will be right to left since that's the way the cell 77 | * slides open - for example | index 2 index 1 index 0 |. 78 | * 79 | * Does nothing if -swipeableCell: buttonForIndex: implemented. 80 | * 81 | * @param cell - The cell for which to find the button tintColor 82 | * @param index - The index of the button for which this color should be the text color. 83 | * @return The text color you wish to display. 84 | */ 85 | - (UIColor *)swipeableCell:(DNSSwipeableCell *)cell tintColorForButtonAtIndex:(NSInteger)index; 86 | 87 | /** 88 | * Configures the image for a button at a given index. Note that you should not set both 89 | * an image and a title for the same cell. Defaults to null. 90 | * 91 | * REMEMBER: Button indexes will be right to left since that's the way the cell 92 | * slides open - for example | index 2 index 1 index 0 |. 93 | * 94 | * Does nothing if -swipeableCell: buttonForIndex: implemented. 95 | * 96 | * @param cell - The cell for which you wish to find the button image. 97 | * @param index - The index of the button for which this should be the image. 98 | * @return The Image you wish to display. 99 | */ 100 | - (UIImage *)swipeableCell:(DNSSwipeableCell *)cell imageForButtonAtIndex:(NSInteger)index; 101 | 102 | /** 103 | * Configures the font for a button at a given index. Defaults to the UIButton font 104 | * provided by the system. 105 | * 106 | * REMEMBER: Button indexes will be right to left since that's the way the cell 107 | * slides open - for example | index 2 index 1 index 0 |. 108 | * 109 | * Does nothing if -swipeableCell: buttonForIndex: implemented. 110 | * 111 | * @param cell - The cell for which you wish to find the font. 112 | * @param index - The index of the button for which this should be the font. 113 | * @return The font you wish to display. 114 | */ 115 | - (UIFont *)swipeableCell:(DNSSwipeableCell *)cell fontForButtonAtIndex:(NSInteger)index; 116 | 117 | @end 118 | 119 | #pragma mark - Delegate 120 | @protocol DNSSwipeableCellDelegate 121 | 122 | /** 123 | * Notifies the delegate that a particular button was selected. 124 | * 125 | * REMEMBER: Button indexes will be right to left since that's the way the cell 126 | * slides open - for example | index 2 index 1 index 0 |. 127 | * 128 | * @param cell - The cell sending this message 129 | * @param index - The index of the button selected. 130 | */ 131 | - (void)swipeableCell:(DNSSwipeableCell *)cell didSelectButtonAtIndex:(NSInteger)index; 132 | 133 | /** 134 | * Notifies the delegate that a particular cell did open, to facilitate the delegate's 135 | * management of which cells are open and which cells are closed. 136 | * @param cell - The swipeable cell which opened. 137 | */ 138 | - (void)swipeableCellDidOpen:(DNSSwipeableCell *)cell; 139 | 140 | /** 141 | * Notifies the delegate that a particular cell did close, to facilitate the delegate's 142 | * management of which cells are open and which cells are closed. 143 | * @param cell - The swipeable cell which closed. 144 | */ 145 | - (void)swipeableCellDidClose:(DNSSwipeableCell *)cell; 146 | 147 | @end 148 | 149 | 150 | #pragma mark - Actual Class 151 | /** 152 | * ZOMG THE ACTUAL CLASS 153 | */ 154 | @interface DNSSwipeableCell : UITableViewCell 155 | 156 | @property (nonatomic, weak) IBOutlet UIView *myContentView; 157 | @property (nonatomic, weak) IBOutlet NSLayoutConstraint *contentViewRightConstraint; 158 | @property (nonatomic, weak) IBOutlet NSLayoutConstraint *contentViewLeftConstraint; 159 | 160 | //The delegate and datasource. 161 | @property (nonatomic, weak) id delegate; 162 | @property (nonatomic, weak) id dataSource; 163 | 164 | /** 165 | * Slides the cell to all the way open. 166 | * @param animated - YES if the cell opening should be animated, NO if not. 167 | */ 168 | - (void)openCell:(BOOL)animated; 169 | 170 | /** 171 | * Slides the cell all the way closed. 172 | * @param animated - YES if the cell closing should be animated, NO if not. 173 | */ 174 | - (void)closeCell:(BOOL)animated; 175 | 176 | /** 177 | * Initialization methods which are called no matter which initializer is called. Should 178 | * only be overridden by subclasses and not called directly. 179 | */ 180 | - (void)commonInit; 181 | 182 | @end 183 | -------------------------------------------------------------------------------- /Library/DNSSwipeableCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // DNSSwipeableCell.m 3 | // DNSSwipeableCell 4 | // 5 | // Created by Ellen Shapiro on 12/29/13. 6 | // Copyright (c) 2013 Designated Nerd Software. All rights reserved. 7 | // 8 | 9 | #import "DNSSwipeableCell.h" 10 | 11 | @interface DNSSwipeableCell() 12 | 13 | @property (nonatomic, strong) NSMutableArray *buttons; 14 | @property (nonatomic, assign) CGPoint panStartPoint; 15 | @property (nonatomic, assign) CGFloat startingRightLayoutConstraintConstant; 16 | @property (nonatomic) UITableViewCellAccessoryType myAccessoryType; 17 | @property (nonatomic) BOOL isMoving; 18 | 19 | @end 20 | 21 | @implementation DNSSwipeableCell 22 | 23 | #pragma mark - Initialization 24 | - (void)commonInit 25 | { 26 | self.selectionStyle = UITableViewCellSelectionStyleNone; 27 | 28 | //Setup button array 29 | self.buttons = [NSMutableArray array]; 30 | 31 | //setup content view if one doesn't exist. 32 | if (!self.myContentView) { 33 | UIView *contentView = [[UIView alloc] init]; 34 | contentView.userInteractionEnabled = YES; 35 | contentView.clipsToBounds = YES; 36 | 37 | contentView.backgroundColor = [UIColor whiteColor]; 38 | contentView.translatesAutoresizingMaskIntoConstraints = NO; 39 | 40 | [self.contentView addSubview:contentView]; 41 | 42 | self.myContentView = contentView; 43 | } 44 | 45 | //Setup pan gesture recognizer 46 | UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panThisCell:)]; 47 | panRecognizer.delegate = self; 48 | [self.myContentView addGestureRecognizer:panRecognizer]; 49 | 50 | 51 | [self layoutIfNeeded]; 52 | } 53 | 54 | - (id)init 55 | { 56 | if (self = [super init]) { 57 | [self commonInit]; 58 | } 59 | 60 | return self; 61 | } 62 | 63 | - (void)awakeFromNib 64 | { 65 | [super awakeFromNib]; 66 | 67 | //Note: Storyboard-based views must use awakeFromNib instead of initWithCoder: 68 | //because otherwise none of the 69 | [self commonInit]; 70 | } 71 | 72 | - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 73 | { 74 | if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { 75 | [self commonInit]; 76 | } 77 | 78 | return self; 79 | } 80 | 81 | #pragma mark - Setter Overrides 82 | 83 | - (void)setAccessoryType:(UITableViewCellAccessoryType)accessoryType 84 | { 85 | [super setAccessoryType:accessoryType]; 86 | 87 | if (!self.isMoving) { 88 | //If we're not moving, save the cell accessory type for when we are. 89 | self.myAccessoryType = accessoryType; 90 | } 91 | } 92 | 93 | - (void)setIsMoving:(BOOL)isMoving 94 | { 95 | _isMoving = isMoving; 96 | 97 | if (_isMoving) { 98 | //Set up to not have an accessory when moving so it doesn't cover buttons. 99 | self.accessoryType = UITableViewCellAccessoryNone; 100 | } else { 101 | //Replace the accessory if we're closed. 102 | if (self.contentViewRightConstraint.constant == 0) { 103 | self.accessoryType = self.myAccessoryType; 104 | } 105 | } 106 | } 107 | 108 | #pragma mark - Cell Lifecycle 109 | 110 | - (void)updateConstraints 111 | { 112 | [super updateConstraints]; 113 | if (!self.contentViewLeftConstraint) { 114 | //Pin content view to left and right of screen, grab left and right constraints. 115 | NSDictionary *views = @{ @"myContentView" : self.myContentView }; 116 | 117 | NSArray *verticalConstraints = [NSLayoutConstraint 118 | constraintsWithVisualFormat:@"V:|[myContentView]|" 119 | options:0 120 | metrics:nil 121 | views:views]; 122 | [self.contentView addConstraints:verticalConstraints]; 123 | 124 | NSArray *horizontalConstraints = [NSLayoutConstraint 125 | constraintsWithVisualFormat:@"H:|[myContentView]|" 126 | options:0 127 | metrics:0 128 | views:views]; 129 | [self.contentView addConstraints:horizontalConstraints]; 130 | 131 | //Save constraints once they've been added to the view. 132 | self.contentViewLeftConstraint = horizontalConstraints[0]; 133 | self.contentViewRightConstraint = horizontalConstraints[1]; 134 | } 135 | } 136 | 137 | - (void)prepareForReuse 138 | { 139 | [super prepareForReuse]; 140 | 141 | [self resetConstraintContstantsToZero:NO notifyDelegateDidClose:NO]; 142 | 143 | //Reset buttons 144 | [self removeButtonSubviews]; 145 | } 146 | 147 | #pragma mark - Button Config 148 | 149 | - (void)configureButtons 150 | { 151 | CGFloat previousMinX = CGRectGetWidth(self.frame); 152 | NSInteger buttons = [self.dataSource numberOfButtonsInSwipeableCell:self]; 153 | for (NSInteger i = 0; i < buttons; i++) { 154 | UIButton *button = [self buttonForIndex:i previousButtonMinX:previousMinX]; 155 | [self.buttons addObject:button]; 156 | previousMinX -= CGRectGetWidth(button.frame); 157 | [self.contentView addSubview:button]; 158 | } 159 | 160 | [self.contentView bringSubviewToFront:self.myContentView]; 161 | } 162 | 163 | - (void)configureButtonsIfNeeded 164 | { 165 | if (self.buttons.count == 0) { 166 | [self configureButtons]; 167 | } 168 | } 169 | 170 | - (void)removeButtonSubviews 171 | { 172 | for (UIButton *button in self.buttons) { 173 | [button removeFromSuperview]; 174 | } 175 | 176 | [self.buttons removeAllObjects]; 177 | } 178 | 179 | - (UIButton *)buttonForIndex:(NSInteger)index previousButtonMinX:(CGFloat)previousMinX 180 | { 181 | UIButton *button = nil; 182 | 183 | /* The datasource implements buttonForIndex. Let's use custom buttom */ 184 | if ([self.dataSource respondsToSelector:@selector(swipeableCell:buttonForIndex:)]) { 185 | 186 | button = [self.dataSource swipeableCell:self buttonForIndex:index]; 187 | 188 | /* Lets generate the button */ 189 | } else { 190 | 191 | button = [UIButton buttonWithType:UIButtonTypeCustom]; 192 | 193 | if ([self.dataSource respondsToSelector:@selector(swipeableCell:titleForButtonAtIndex:)]) { 194 | //use given title 195 | [button setTitle:[self.dataSource swipeableCell:self titleForButtonAtIndex:index] forState:UIControlStateNormal]; 196 | } else { 197 | //Default to empty title 198 | [button setTitle:@"" forState:UIControlStateNormal]; 199 | } 200 | 201 | if ([self.dataSource respondsToSelector:@selector(swipeableCell:imageForButtonAtIndex:)]) { 202 | //Use the image, if it exists 203 | UIImage *iconImage = [self.dataSource swipeableCell:self imageForButtonAtIndex:index]; 204 | if (iconImage) { 205 | [button setImage:[iconImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal]; 206 | } 207 | } 208 | 209 | if ([self.dataSource respondsToSelector:@selector(swipeableCell:tintColorForButtonAtIndex:)]) { 210 | //Use the given tint color. 211 | button.tintColor = [self.dataSource swipeableCell:self tintColorForButtonAtIndex:index]; 212 | } else { 213 | //Default to white 214 | button.tintColor = [UIColor whiteColor]; 215 | } 216 | 217 | //Add 8pt of padding on the left and right. 218 | [button setContentEdgeInsets:UIEdgeInsetsMake(0, 8, 0, 8)]; 219 | 220 | if ([self.dataSource respondsToSelector:@selector(swipeableCell:fontForButtonAtIndex:)]) { 221 | //Set font if provided. 222 | button.titleLabel.font = [self.dataSource swipeableCell:self fontForButtonAtIndex:index]; 223 | } 224 | 225 | //Size the button to fit the contents 226 | [button sizeToFit]; 227 | 228 | CGFloat appleRecommendedMinimumTouchPointWidth = 44.0f; 229 | if (button.frame.size.width < appleRecommendedMinimumTouchPointWidth) { 230 | CGRect frame = button.frame; 231 | frame.size.width = appleRecommendedMinimumTouchPointWidth; 232 | button.frame = frame; 233 | } 234 | 235 | if ([self.dataSource respondsToSelector:@selector(swipeableCell:backgroundColorForButtonAtIndex:)]) { 236 | //Set background color from data source 237 | button.backgroundColor = [self.dataSource swipeableCell:self backgroundColorForButtonAtIndex:index]; 238 | } else { 239 | //Use default colors 240 | if (index == 0) { 241 | button.backgroundColor = [UIColor redColor]; 242 | } else { 243 | button.backgroundColor = [UIColor lightGrayColor]; 244 | } 245 | } 246 | } 247 | 248 | //Update the origin and size to make sure that everything is the size it needs to be 249 | CGFloat xOrigin = previousMinX - CGRectGetWidth(button.frame); 250 | button.frame = CGRectMake(xOrigin, 0, CGRectGetWidth(button.frame), CGRectGetHeight(self.contentView.frame)); 251 | 252 | //Add tap target 253 | [button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside]; 254 | 255 | if (xOrigin < 40) { 256 | NSLog(@"***ATTENTION!*** Button at index %@ is going to leave less than 40 points of space! That's going to be hard to close.", @(index)); 257 | } 258 | 259 | return button; 260 | } 261 | 262 | - (IBAction)buttonClicked:(id)sender 263 | { 264 | if ([sender isKindOfClass:[UIButton class] ]) { 265 | NSInteger index = [self.buttons indexOfObject:sender]; 266 | if (index != NSNotFound) { 267 | NSLog(@"Clicked button at index %@", @(index)); 268 | [self.delegate swipeableCell:self didSelectButtonAtIndex:index]; 269 | } else { 270 | NSAssert(NO, @"Index not in the list of buttons!"); 271 | } 272 | } else { 273 | NSLog(@"NOT A BUTTON!"); 274 | } 275 | } 276 | 277 | #pragma mark - Measurement convenience methods 278 | 279 | - (CGFloat)halfOfFirstButtonWidth 280 | { 281 | UIButton *firstButton = [self.buttons firstObject]; 282 | return (CGRectGetWidth(firstButton.frame) / 2); 283 | } 284 | 285 | - (CGFloat)halfOfLastButtonXPosition 286 | { 287 | UIButton *lastButton = [self.buttons lastObject]; 288 | CGFloat halfOfLastButton = CGRectGetWidth(lastButton.frame) / 2; 289 | return [self buttonTotalWidth] - halfOfLastButton; 290 | } 291 | 292 | - (CGFloat)buttonTotalWidth 293 | { 294 | //Add all the widths together 295 | CGFloat buttonWidth = 0; 296 | for (UIButton *button in self.buttons) { 297 | buttonWidth += CGRectGetWidth(button.frame); 298 | } 299 | 300 | return buttonWidth; 301 | } 302 | 303 | #pragma mark - Constraint animation 304 | 305 | #pragma mark Public 306 | - (void)openCell:(BOOL)animated 307 | { 308 | [self configureButtonsIfNeeded]; 309 | [self setConstraintsToShowAllButtons:animated notifyDelegateDidOpen:NO]; 310 | } 311 | 312 | - (void)closeCell:(BOOL)animated 313 | { 314 | [self configureButtonsIfNeeded]; 315 | [self resetConstraintContstantsToZero:animated notifyDelegateDidClose:NO]; 316 | } 317 | 318 | #pragma mark Private 319 | 320 | - (void)setConstraintsToShowAllButtons:(BOOL)animated notifyDelegateDidOpen:(BOOL)notifyDelegate 321 | { 322 | if (notifyDelegate) { 323 | [self.delegate swipeableCellDidOpen:self]; 324 | } 325 | 326 | CGFloat buttonTotalWidth = [self buttonTotalWidth]; 327 | if (self.startingRightLayoutConstraintConstant == buttonTotalWidth && 328 | self.contentViewRightConstraint.constant == buttonTotalWidth) { 329 | self.isMoving = NO; 330 | //Already all the way open, no bounce necessary 331 | return; 332 | } 333 | 334 | self.contentViewLeftConstraint.constant = -buttonTotalWidth; 335 | self.contentViewRightConstraint.constant = buttonTotalWidth; 336 | 337 | [self updateConstraintsIfNeeded:animated completion:^(BOOL finished) { 338 | self.startingRightLayoutConstraintConstant = self.contentViewRightConstraint.constant; 339 | self.isMoving = NO; 340 | }]; 341 | } 342 | 343 | - (void)resetConstraintContstantsToZero:(BOOL)animated notifyDelegateDidClose:(BOOL)notifyDelegate 344 | { 345 | if (notifyDelegate) { 346 | [self.delegate swipeableCellDidClose:self]; 347 | } 348 | if (self.startingRightLayoutConstraintConstant == 0 && 349 | self.contentViewRightConstraint.constant == 0) { 350 | 351 | //Remove the button subviews so they don't show through if the user selects this cell or has an accessory. 352 | [self removeButtonSubviews]; 353 | self.isMoving = NO; 354 | 355 | //Already all the way closed, no bounce necessary 356 | return; 357 | } 358 | 359 | self.contentViewRightConstraint.constant = 0; 360 | self.contentViewLeftConstraint.constant = 0; 361 | [self updateConstraintsIfNeeded:animated completion:^(BOOL finished) { 362 | self.startingRightLayoutConstraintConstant = self.contentViewRightConstraint.constant; 363 | //Remove the button subviews so they don't show through if the user selects this cell or has an accessory. 364 | [self removeButtonSubviews]; 365 | self.isMoving = NO; 366 | }]; 367 | } 368 | 369 | - (void)updateConstraintsIfNeeded:(BOOL)animated completion:(void (^)(BOOL finished))completion; 370 | { 371 | float duration = 0; 372 | if (animated) { 373 | duration = 0.4; 374 | } 375 | 376 | [UIView animateWithDuration:duration 377 | delay:0 378 | usingSpringWithDamping:0.6 379 | initialSpringVelocity:0 380 | options:UIViewAnimationOptionCurveEaseOut 381 | animations:^{ 382 | [self layoutIfNeeded]; 383 | } completion:completion]; 384 | } 385 | 386 | #pragma mark - UIGestureRecognizerDelegate 387 | 388 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 389 | { 390 | if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) { 391 | UIPanGestureRecognizer *panGesture = (UIPanGestureRecognizer*)gestureRecognizer; 392 | CGPoint velocity = [panGesture velocityInView:self.myContentView]; 393 | if (velocity.x > 0) { 394 | return YES; 395 | } else if (fabs(velocity.x) > fabs(velocity.y)) { 396 | return NO; 397 | } 398 | } 399 | 400 | return YES; 401 | } 402 | 403 | #pragma mark - Gesture Recognizer target 404 | 405 | - (void)panThisCell:(UIPanGestureRecognizer *)recognizer 406 | { 407 | 408 | CGPoint currentPoint = [recognizer translationInView:self.myContentView]; 409 | CGPoint velocity = [recognizer velocityInView:self.myContentView]; 410 | 411 | //Check what direction the swipe is moving by checking the velocity 412 | BOOL movingHorizontally = fabs(velocity.y) < fabs(velocity.x); 413 | 414 | switch (recognizer.state) { 415 | case UIGestureRecognizerStateBegan: 416 | self.panStartPoint = currentPoint; 417 | self.startingRightLayoutConstraintConstant = self.contentViewRightConstraint.constant; 418 | break; 419 | case UIGestureRecognizerStateChanged: { 420 | if(movingHorizontally) { 421 | self.isMoving = YES; 422 | if (!self.selected) { 423 | [self configureButtonsIfNeeded]; 424 | } 425 | 426 | // Started by moving horizontally 427 | CGFloat deltaX = currentPoint.x - self.panStartPoint.x; 428 | BOOL panningLeft = NO; 429 | if (currentPoint.x < self.panStartPoint.x) { 430 | panningLeft = YES; 431 | } 432 | 433 | CGFloat adjustment = self.startingRightLayoutConstraintConstant - deltaX; 434 | if (!panningLeft) { 435 | CGFloat constant = MAX(adjustment, 0); 436 | if (constant == 0) { 437 | [self resetConstraintContstantsToZero:YES notifyDelegateDidClose:NO]; 438 | } else { 439 | self.contentViewRightConstraint.constant = constant; 440 | } 441 | } else { 442 | CGFloat constant = MIN(adjustment, [self buttonTotalWidth]); 443 | if (constant == [self buttonTotalWidth]) { 444 | [self setConstraintsToShowAllButtons:YES notifyDelegateDidOpen:NO]; 445 | } else { 446 | self.contentViewRightConstraint.constant = constant; 447 | } 448 | } 449 | 450 | self.contentViewLeftConstraint.constant = -self.contentViewRightConstraint.constant; 451 | } 452 | } 453 | break; 454 | case UIGestureRecognizerStateEnded: 455 | if (self.startingRightLayoutConstraintConstant == 0) { 456 | //We were opening 457 | CGFloat halfWidth = [self halfOfFirstButtonWidth]; 458 | if (halfWidth != 0 && //Handle case where cell is already offscreen. 459 | self.contentViewRightConstraint.constant >= halfWidth) { 460 | //Open all the way 461 | [self setConstraintsToShowAllButtons:YES notifyDelegateDidOpen:YES]; 462 | } else { 463 | //Re-close 464 | [self resetConstraintContstantsToZero:YES notifyDelegateDidClose:YES]; 465 | } 466 | } else { 467 | //We were closing 468 | if (self.contentViewRightConstraint.constant >= [self halfOfLastButtonXPosition]) { 469 | //Re-open all the way 470 | [self setConstraintsToShowAllButtons:YES notifyDelegateDidOpen:YES]; 471 | } else { 472 | //Close 473 | [self resetConstraintContstantsToZero:YES notifyDelegateDidClose:YES]; 474 | } 475 | } 476 | break; 477 | case UIGestureRecognizerStateCancelled: 478 | // Started by moving horizontally 479 | if (self.startingRightLayoutConstraintConstant == 0) { 480 | //We were closed - reset everything to 0 481 | [self resetConstraintContstantsToZero:YES notifyDelegateDidClose:YES]; 482 | } else { 483 | //We were open - reset to the open state 484 | [self setConstraintsToShowAllButtons:YES notifyDelegateDidOpen:YES]; 485 | } 486 | break; 487 | 488 | default: 489 | break; 490 | } 491 | } 492 | 493 | @end 494 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DNSSwipeableTableCell 2 | ==== 3 | ---- 4 | 5 | ![DNSSwipeableTableCell demo](swipeable.gif) 6 | 7 | This is an extension of [a tutorial I wrote](http://www.raywenderlich.com/62435/make-swipeable-table-view-cell-actions-without-going-nuts-scroll-views) for [RayWenderlich.com](http://www.raywenderlich.com) on how to create a swipeable UITableViewCell for iOS 7 without driving yourself completley insane with UIScrollViews. (Drive yourself insane with constraints instead!) 8 | 9 | The crux of the problem is that while adding a delete button is super-easy, adding a delete button AND another button is a total nightmare because of the way the cells are constructed. 10 | 11 | I've refactored the code that was included in the tutorial, which is is more meant as an exercise in helping n00bs dive into figuring out how Apple's code works under the hood, into this library, which I'm hoping will be helpful for anyone dealing with this in production code. 12 | 13 | ## The Biggest Changes 14 | * Removed dependency on the storyboard (though you can still use one if you like) 15 | * Made the `myContentView` public so the cell could be subclassed and recycled more easily. 16 | * Beefed up delegate to handle an arbitrary number of buttons 17 | * Added datasource to allow tons of user-configurable options 18 | * Tweaked the animation so it uses spring damping instead of trying to do that manually 19 | * Added a ton of documentation to the swipeable cell class 20 | * Cleaned up the sample app quite a bit 21 | * Added a sample subclass. 22 | * Added a bunch of photos of my cat, because the internet loves cats. 23 | 24 | # Notes on Usage 25 | 26 | * If you're using an accessory view via `accessoryType`, that view will automatically be removed and replaced when the cell is opened and closed (respectively). 27 | * If you're using a custom background color for your `myContentView` along with an accessory, remember to set that same color as the background color for the cell itself, or the accessory's going to have a different background color. 28 | * If you're using a cell from a storyboard or a `.xib`, you have to add a `myContentView` view, and hook up the `NSLayoutConstraint` outlets to the left and right constraints. See the sample project for an example of how to set this up. 29 | * If you're using a cell from a storyboard or a `.xib`, make sure you're sending any calls to the superclass during setup which need `myContentView` to not be `nil` through `awakeFromNib` instead of `initWithCoder:`, since the `IBOutlet` won't be hooked up until `awakeFromNib` has fired. 30 | 31 | ## //TODOs 32 | * Support for swiping in both directions ([issue](https://github.com/designatednerd/DNSSwipeableTableCell/issues/7)) 33 | * Support for iOS 8 style cell actions ([issue](https://github.com/designatednerd/DNSSwipeableTableCell/issues/15)) 34 | * ??? - File an issue! 35 | 36 | ## Additional Contributors 37 | * [Mark Flowers](https://github.com/markflowers) - Improved fixes for tableview slop, removed index path hack, defaults for numerous previously required methods, and better delegate methods. 38 | 39 | 40 | ## Photos 41 | All photos Copyright Ellen Shapiro. If you want to see more photos of my jerkface cat, Chaplin, follow me on [Instagram](http://instagram.com/loudguitars). -------------------------------------------------------------------------------- /swipeable.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designatednerd/DNSSwipeableTableCell/45aec721cff84d3254976c6ed54d10e5aff1aa1b/swipeable.gif --------------------------------------------------------------------------------