├── .gitignore ├── CustomScrollView.xcodeproj └── project.pbxproj ├── CustomScrollView ├── AppDelegate.h ├── AppDelegate.m ├── CSCDynamicItem.h ├── CSCDynamicItem.m ├── CustomScrollView-Info.plist ├── CustomScrollView-Prefix.pch ├── CustomScrollView.h ├── CustomScrollView.m ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── ViewController.h ├── ViewController.m ├── en.lproj │ └── InfoPlist.strings └── main.m ├── CustomScrollViewTests ├── CustomScrollViewTests-Info.plist ├── CustomScrollViewTests.m └── en.lproj │ └── InfoPlist.strings └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build/ 3 | *.pbxuser 4 | !default.pbxuser 5 | *.mode1v3 6 | !default.mode1v3 7 | *.mode2v3 8 | !default.mode2v3 9 | *.perspectivev3 10 | !default.perspectivev3 11 | *.xcworkspace 12 | !default.xcworkspace 13 | xcuserdata 14 | profile 15 | *.moved-aside 16 | DerivedData 17 | .idea/ 18 | Pods/ 19 | -------------------------------------------------------------------------------- /CustomScrollView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5D3A782818FF1912006CCAB3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D3A782718FF1912006CCAB3 /* Foundation.framework */; }; 11 | 5D3A782A18FF1912006CCAB3 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D3A782918FF1912006CCAB3 /* CoreGraphics.framework */; }; 12 | 5D3A782C18FF1912006CCAB3 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D3A782B18FF1912006CCAB3 /* UIKit.framework */; }; 13 | 5D3A783218FF1912006CCAB3 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5D3A783018FF1912006CCAB3 /* InfoPlist.strings */; }; 14 | 5D3A783418FF1912006CCAB3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D3A783318FF1912006CCAB3 /* main.m */; }; 15 | 5D3A783818FF1912006CCAB3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D3A783718FF1912006CCAB3 /* AppDelegate.m */; }; 16 | 5D3A783E18FF1912006CCAB3 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D3A783D18FF1912006CCAB3 /* ViewController.m */; }; 17 | 5D3A784018FF1912006CCAB3 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5D3A783F18FF1912006CCAB3 /* Images.xcassets */; }; 18 | 5D3A784718FF1912006CCAB3 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D3A784618FF1912006CCAB3 /* XCTest.framework */; }; 19 | 5D3A784818FF1912006CCAB3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D3A782718FF1912006CCAB3 /* Foundation.framework */; }; 20 | 5D3A784918FF1912006CCAB3 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D3A782B18FF1912006CCAB3 /* UIKit.framework */; }; 21 | 5D3A785118FF1912006CCAB3 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5D3A784F18FF1912006CCAB3 /* InfoPlist.strings */; }; 22 | 5D3A785318FF1912006CCAB3 /* CustomScrollViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D3A785218FF1912006CCAB3 /* CustomScrollViewTests.m */; }; 23 | 5D3A785E18FF1923006CCAB3 /* CustomScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D3A785D18FF1923006CCAB3 /* CustomScrollView.m */; }; 24 | A6DF62EA1965A244008F895E /* CSCDynamicItem.m in Sources */ = {isa = PBXBuildFile; fileRef = A6DF62E91965A244008F895E /* CSCDynamicItem.m */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXContainerItemProxy section */ 28 | 5D3A784A18FF1912006CCAB3 /* PBXContainerItemProxy */ = { 29 | isa = PBXContainerItemProxy; 30 | containerPortal = 5D3A781C18FF1912006CCAB3 /* Project object */; 31 | proxyType = 1; 32 | remoteGlobalIDString = 5D3A782318FF1912006CCAB3; 33 | remoteInfo = CustomScrollView; 34 | }; 35 | /* End PBXContainerItemProxy section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | 5D3A782418FF1912006CCAB3 /* CustomScrollView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CustomScrollView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | 5D3A782718FF1912006CCAB3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 40 | 5D3A782918FF1912006CCAB3 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 41 | 5D3A782B18FF1912006CCAB3 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 42 | 5D3A782F18FF1912006CCAB3 /* CustomScrollView-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "CustomScrollView-Info.plist"; sourceTree = ""; }; 43 | 5D3A783118FF1912006CCAB3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 44 | 5D3A783318FF1912006CCAB3 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 45 | 5D3A783518FF1912006CCAB3 /* CustomScrollView-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CustomScrollView-Prefix.pch"; sourceTree = ""; }; 46 | 5D3A783618FF1912006CCAB3 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 47 | 5D3A783718FF1912006CCAB3 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 48 | 5D3A783C18FF1912006CCAB3 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 49 | 5D3A783D18FF1912006CCAB3 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 50 | 5D3A783F18FF1912006CCAB3 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 51 | 5D3A784518FF1912006CCAB3 /* CustomScrollViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CustomScrollViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 5D3A784618FF1912006CCAB3 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 53 | 5D3A784E18FF1912006CCAB3 /* CustomScrollViewTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "CustomScrollViewTests-Info.plist"; sourceTree = ""; }; 54 | 5D3A785018FF1912006CCAB3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 55 | 5D3A785218FF1912006CCAB3 /* CustomScrollViewTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CustomScrollViewTests.m; sourceTree = ""; }; 56 | 5D3A785C18FF1923006CCAB3 /* CustomScrollView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomScrollView.h; sourceTree = ""; }; 57 | 5D3A785D18FF1923006CCAB3 /* CustomScrollView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomScrollView.m; sourceTree = ""; }; 58 | A6DF62E81965A244008F895E /* CSCDynamicItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSCDynamicItem.h; sourceTree = ""; }; 59 | A6DF62E91965A244008F895E /* CSCDynamicItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSCDynamicItem.m; sourceTree = ""; }; 60 | /* End PBXFileReference section */ 61 | 62 | /* Begin PBXFrameworksBuildPhase section */ 63 | 5D3A782118FF1912006CCAB3 /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | 5D3A782A18FF1912006CCAB3 /* CoreGraphics.framework in Frameworks */, 68 | 5D3A782C18FF1912006CCAB3 /* UIKit.framework in Frameworks */, 69 | 5D3A782818FF1912006CCAB3 /* Foundation.framework in Frameworks */, 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | 5D3A784218FF1912006CCAB3 /* Frameworks */ = { 74 | isa = PBXFrameworksBuildPhase; 75 | buildActionMask = 2147483647; 76 | files = ( 77 | 5D3A784718FF1912006CCAB3 /* XCTest.framework in Frameworks */, 78 | 5D3A784918FF1912006CCAB3 /* UIKit.framework in Frameworks */, 79 | 5D3A784818FF1912006CCAB3 /* Foundation.framework in Frameworks */, 80 | ); 81 | runOnlyForDeploymentPostprocessing = 0; 82 | }; 83 | /* End PBXFrameworksBuildPhase section */ 84 | 85 | /* Begin PBXGroup section */ 86 | 5D3A781B18FF1912006CCAB3 = { 87 | isa = PBXGroup; 88 | children = ( 89 | 5D3A782D18FF1912006CCAB3 /* CustomScrollView */, 90 | 5D3A784C18FF1912006CCAB3 /* CustomScrollViewTests */, 91 | 5D3A782618FF1912006CCAB3 /* Frameworks */, 92 | 5D3A782518FF1912006CCAB3 /* Products */, 93 | ); 94 | sourceTree = ""; 95 | }; 96 | 5D3A782518FF1912006CCAB3 /* Products */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 5D3A782418FF1912006CCAB3 /* CustomScrollView.app */, 100 | 5D3A784518FF1912006CCAB3 /* CustomScrollViewTests.xctest */, 101 | ); 102 | name = Products; 103 | sourceTree = ""; 104 | }; 105 | 5D3A782618FF1912006CCAB3 /* Frameworks */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 5D3A782718FF1912006CCAB3 /* Foundation.framework */, 109 | 5D3A782918FF1912006CCAB3 /* CoreGraphics.framework */, 110 | 5D3A782B18FF1912006CCAB3 /* UIKit.framework */, 111 | 5D3A784618FF1912006CCAB3 /* XCTest.framework */, 112 | ); 113 | name = Frameworks; 114 | sourceTree = ""; 115 | }; 116 | 5D3A782D18FF1912006CCAB3 /* CustomScrollView */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 5D3A783618FF1912006CCAB3 /* AppDelegate.h */, 120 | 5D3A783718FF1912006CCAB3 /* AppDelegate.m */, 121 | A6DF62E81965A244008F895E /* CSCDynamicItem.h */, 122 | A6DF62E91965A244008F895E /* CSCDynamicItem.m */, 123 | 5D3A785C18FF1923006CCAB3 /* CustomScrollView.h */, 124 | 5D3A785D18FF1923006CCAB3 /* CustomScrollView.m */, 125 | 5D3A783F18FF1912006CCAB3 /* Images.xcassets */, 126 | 5D3A782E18FF1912006CCAB3 /* Supporting Files */, 127 | 5D3A783C18FF1912006CCAB3 /* ViewController.h */, 128 | 5D3A783D18FF1912006CCAB3 /* ViewController.m */, 129 | ); 130 | path = CustomScrollView; 131 | sourceTree = ""; 132 | }; 133 | 5D3A782E18FF1912006CCAB3 /* Supporting Files */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 5D3A782F18FF1912006CCAB3 /* CustomScrollView-Info.plist */, 137 | 5D3A783018FF1912006CCAB3 /* InfoPlist.strings */, 138 | 5D3A783318FF1912006CCAB3 /* main.m */, 139 | 5D3A783518FF1912006CCAB3 /* CustomScrollView-Prefix.pch */, 140 | ); 141 | name = "Supporting Files"; 142 | sourceTree = ""; 143 | }; 144 | 5D3A784C18FF1912006CCAB3 /* CustomScrollViewTests */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | 5D3A785218FF1912006CCAB3 /* CustomScrollViewTests.m */, 148 | 5D3A784D18FF1912006CCAB3 /* Supporting Files */, 149 | ); 150 | path = CustomScrollViewTests; 151 | sourceTree = ""; 152 | }; 153 | 5D3A784D18FF1912006CCAB3 /* Supporting Files */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | 5D3A784E18FF1912006CCAB3 /* CustomScrollViewTests-Info.plist */, 157 | 5D3A784F18FF1912006CCAB3 /* InfoPlist.strings */, 158 | ); 159 | name = "Supporting Files"; 160 | sourceTree = ""; 161 | }; 162 | /* End PBXGroup section */ 163 | 164 | /* Begin PBXNativeTarget section */ 165 | 5D3A782318FF1912006CCAB3 /* CustomScrollView */ = { 166 | isa = PBXNativeTarget; 167 | buildConfigurationList = 5D3A785618FF1912006CCAB3 /* Build configuration list for PBXNativeTarget "CustomScrollView" */; 168 | buildPhases = ( 169 | 5D3A782018FF1912006CCAB3 /* Sources */, 170 | 5D3A782118FF1912006CCAB3 /* Frameworks */, 171 | 5D3A782218FF1912006CCAB3 /* Resources */, 172 | ); 173 | buildRules = ( 174 | ); 175 | dependencies = ( 176 | ); 177 | name = CustomScrollView; 178 | productName = CustomScrollView; 179 | productReference = 5D3A782418FF1912006CCAB3 /* CustomScrollView.app */; 180 | productType = "com.apple.product-type.application"; 181 | }; 182 | 5D3A784418FF1912006CCAB3 /* CustomScrollViewTests */ = { 183 | isa = PBXNativeTarget; 184 | buildConfigurationList = 5D3A785918FF1912006CCAB3 /* Build configuration list for PBXNativeTarget "CustomScrollViewTests" */; 185 | buildPhases = ( 186 | 5D3A784118FF1912006CCAB3 /* Sources */, 187 | 5D3A784218FF1912006CCAB3 /* Frameworks */, 188 | 5D3A784318FF1912006CCAB3 /* Resources */, 189 | ); 190 | buildRules = ( 191 | ); 192 | dependencies = ( 193 | 5D3A784B18FF1912006CCAB3 /* PBXTargetDependency */, 194 | ); 195 | name = CustomScrollViewTests; 196 | productName = CustomScrollViewTests; 197 | productReference = 5D3A784518FF1912006CCAB3 /* CustomScrollViewTests.xctest */; 198 | productType = "com.apple.product-type.bundle.unit-test"; 199 | }; 200 | /* End PBXNativeTarget section */ 201 | 202 | /* Begin PBXProject section */ 203 | 5D3A781C18FF1912006CCAB3 /* Project object */ = { 204 | isa = PBXProject; 205 | attributes = { 206 | LastUpgradeCheck = 0510; 207 | ORGANIZATIONNAME = "Ole Begemann"; 208 | TargetAttributes = { 209 | 5D3A784418FF1912006CCAB3 = { 210 | TestTargetID = 5D3A782318FF1912006CCAB3; 211 | }; 212 | }; 213 | }; 214 | buildConfigurationList = 5D3A781F18FF1912006CCAB3 /* Build configuration list for PBXProject "CustomScrollView" */; 215 | compatibilityVersion = "Xcode 3.2"; 216 | developmentRegion = English; 217 | hasScannedForEncodings = 0; 218 | knownRegions = ( 219 | en, 220 | Base, 221 | ); 222 | mainGroup = 5D3A781B18FF1912006CCAB3; 223 | productRefGroup = 5D3A782518FF1912006CCAB3 /* Products */; 224 | projectDirPath = ""; 225 | projectRoot = ""; 226 | targets = ( 227 | 5D3A782318FF1912006CCAB3 /* CustomScrollView */, 228 | 5D3A784418FF1912006CCAB3 /* CustomScrollViewTests */, 229 | ); 230 | }; 231 | /* End PBXProject section */ 232 | 233 | /* Begin PBXResourcesBuildPhase section */ 234 | 5D3A782218FF1912006CCAB3 /* Resources */ = { 235 | isa = PBXResourcesBuildPhase; 236 | buildActionMask = 2147483647; 237 | files = ( 238 | 5D3A784018FF1912006CCAB3 /* Images.xcassets in Resources */, 239 | 5D3A783218FF1912006CCAB3 /* InfoPlist.strings in Resources */, 240 | ); 241 | runOnlyForDeploymentPostprocessing = 0; 242 | }; 243 | 5D3A784318FF1912006CCAB3 /* Resources */ = { 244 | isa = PBXResourcesBuildPhase; 245 | buildActionMask = 2147483647; 246 | files = ( 247 | 5D3A785118FF1912006CCAB3 /* InfoPlist.strings in Resources */, 248 | ); 249 | runOnlyForDeploymentPostprocessing = 0; 250 | }; 251 | /* End PBXResourcesBuildPhase section */ 252 | 253 | /* Begin PBXSourcesBuildPhase section */ 254 | 5D3A782018FF1912006CCAB3 /* Sources */ = { 255 | isa = PBXSourcesBuildPhase; 256 | buildActionMask = 2147483647; 257 | files = ( 258 | 5D3A783E18FF1912006CCAB3 /* ViewController.m in Sources */, 259 | 5D3A783818FF1912006CCAB3 /* AppDelegate.m in Sources */, 260 | 5D3A785E18FF1923006CCAB3 /* CustomScrollView.m in Sources */, 261 | 5D3A783418FF1912006CCAB3 /* main.m in Sources */, 262 | A6DF62EA1965A244008F895E /* CSCDynamicItem.m in Sources */, 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | }; 266 | 5D3A784118FF1912006CCAB3 /* Sources */ = { 267 | isa = PBXSourcesBuildPhase; 268 | buildActionMask = 2147483647; 269 | files = ( 270 | 5D3A785318FF1912006CCAB3 /* CustomScrollViewTests.m in Sources */, 271 | ); 272 | runOnlyForDeploymentPostprocessing = 0; 273 | }; 274 | /* End PBXSourcesBuildPhase section */ 275 | 276 | /* Begin PBXTargetDependency section */ 277 | 5D3A784B18FF1912006CCAB3 /* PBXTargetDependency */ = { 278 | isa = PBXTargetDependency; 279 | target = 5D3A782318FF1912006CCAB3 /* CustomScrollView */; 280 | targetProxy = 5D3A784A18FF1912006CCAB3 /* PBXContainerItemProxy */; 281 | }; 282 | /* End PBXTargetDependency section */ 283 | 284 | /* Begin PBXVariantGroup section */ 285 | 5D3A783018FF1912006CCAB3 /* InfoPlist.strings */ = { 286 | isa = PBXVariantGroup; 287 | children = ( 288 | 5D3A783118FF1912006CCAB3 /* en */, 289 | ); 290 | name = InfoPlist.strings; 291 | sourceTree = ""; 292 | }; 293 | 5D3A784F18FF1912006CCAB3 /* InfoPlist.strings */ = { 294 | isa = PBXVariantGroup; 295 | children = ( 296 | 5D3A785018FF1912006CCAB3 /* en */, 297 | ); 298 | name = InfoPlist.strings; 299 | sourceTree = ""; 300 | }; 301 | /* End PBXVariantGroup section */ 302 | 303 | /* Begin XCBuildConfiguration section */ 304 | 5D3A785418FF1912006CCAB3 /* Debug */ = { 305 | isa = XCBuildConfiguration; 306 | buildSettings = { 307 | ALWAYS_SEARCH_USER_PATHS = NO; 308 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 309 | CLANG_CXX_LIBRARY = "libc++"; 310 | CLANG_ENABLE_MODULES = YES; 311 | CLANG_ENABLE_OBJC_ARC = YES; 312 | CLANG_WARN_BOOL_CONVERSION = YES; 313 | CLANG_WARN_CONSTANT_CONVERSION = YES; 314 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 315 | CLANG_WARN_EMPTY_BODY = YES; 316 | CLANG_WARN_ENUM_CONVERSION = YES; 317 | CLANG_WARN_INT_CONVERSION = YES; 318 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 319 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 320 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 321 | COPY_PHASE_STRIP = NO; 322 | GCC_C_LANGUAGE_STANDARD = gnu99; 323 | GCC_DYNAMIC_NO_PIC = NO; 324 | GCC_OPTIMIZATION_LEVEL = 0; 325 | GCC_PREPROCESSOR_DEFINITIONS = ( 326 | "DEBUG=1", 327 | "$(inherited)", 328 | ); 329 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 330 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 331 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 332 | GCC_WARN_UNDECLARED_SELECTOR = YES; 333 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 334 | GCC_WARN_UNUSED_FUNCTION = YES; 335 | GCC_WARN_UNUSED_VARIABLE = YES; 336 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 337 | ONLY_ACTIVE_ARCH = YES; 338 | SDKROOT = iphoneos; 339 | }; 340 | name = Debug; 341 | }; 342 | 5D3A785518FF1912006CCAB3 /* Release */ = { 343 | isa = XCBuildConfiguration; 344 | buildSettings = { 345 | ALWAYS_SEARCH_USER_PATHS = NO; 346 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 347 | CLANG_CXX_LIBRARY = "libc++"; 348 | CLANG_ENABLE_MODULES = YES; 349 | CLANG_ENABLE_OBJC_ARC = YES; 350 | CLANG_WARN_BOOL_CONVERSION = YES; 351 | CLANG_WARN_CONSTANT_CONVERSION = YES; 352 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 353 | CLANG_WARN_EMPTY_BODY = YES; 354 | CLANG_WARN_ENUM_CONVERSION = YES; 355 | CLANG_WARN_INT_CONVERSION = YES; 356 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 357 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 358 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 359 | COPY_PHASE_STRIP = YES; 360 | ENABLE_NS_ASSERTIONS = NO; 361 | GCC_C_LANGUAGE_STANDARD = gnu99; 362 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 363 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 364 | GCC_WARN_UNDECLARED_SELECTOR = YES; 365 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 366 | GCC_WARN_UNUSED_FUNCTION = YES; 367 | GCC_WARN_UNUSED_VARIABLE = YES; 368 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 369 | SDKROOT = iphoneos; 370 | VALIDATE_PRODUCT = YES; 371 | }; 372 | name = Release; 373 | }; 374 | 5D3A785718FF1912006CCAB3 /* Debug */ = { 375 | isa = XCBuildConfiguration; 376 | buildSettings = { 377 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 378 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 379 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 380 | GCC_PREFIX_HEADER = "CustomScrollView/CustomScrollView-Prefix.pch"; 381 | INFOPLIST_FILE = "CustomScrollView/CustomScrollView-Info.plist"; 382 | PRODUCT_NAME = "$(TARGET_NAME)"; 383 | WRAPPER_EXTENSION = app; 384 | }; 385 | name = Debug; 386 | }; 387 | 5D3A785818FF1912006CCAB3 /* Release */ = { 388 | isa = XCBuildConfiguration; 389 | buildSettings = { 390 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 391 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 392 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 393 | GCC_PREFIX_HEADER = "CustomScrollView/CustomScrollView-Prefix.pch"; 394 | INFOPLIST_FILE = "CustomScrollView/CustomScrollView-Info.plist"; 395 | PRODUCT_NAME = "$(TARGET_NAME)"; 396 | WRAPPER_EXTENSION = app; 397 | }; 398 | name = Release; 399 | }; 400 | 5D3A785A18FF1912006CCAB3 /* Debug */ = { 401 | isa = XCBuildConfiguration; 402 | buildSettings = { 403 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/CustomScrollView.app/CustomScrollView"; 404 | FRAMEWORK_SEARCH_PATHS = ( 405 | "$(SDKROOT)/Developer/Library/Frameworks", 406 | "$(inherited)", 407 | "$(DEVELOPER_FRAMEWORKS_DIR)", 408 | ); 409 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 410 | GCC_PREFIX_HEADER = "CustomScrollView/CustomScrollView-Prefix.pch"; 411 | GCC_PREPROCESSOR_DEFINITIONS = ( 412 | "DEBUG=1", 413 | "$(inherited)", 414 | ); 415 | INFOPLIST_FILE = "CustomScrollViewTests/CustomScrollViewTests-Info.plist"; 416 | PRODUCT_NAME = "$(TARGET_NAME)"; 417 | TEST_HOST = "$(BUNDLE_LOADER)"; 418 | WRAPPER_EXTENSION = xctest; 419 | }; 420 | name = Debug; 421 | }; 422 | 5D3A785B18FF1912006CCAB3 /* Release */ = { 423 | isa = XCBuildConfiguration; 424 | buildSettings = { 425 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/CustomScrollView.app/CustomScrollView"; 426 | FRAMEWORK_SEARCH_PATHS = ( 427 | "$(SDKROOT)/Developer/Library/Frameworks", 428 | "$(inherited)", 429 | "$(DEVELOPER_FRAMEWORKS_DIR)", 430 | ); 431 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 432 | GCC_PREFIX_HEADER = "CustomScrollView/CustomScrollView-Prefix.pch"; 433 | INFOPLIST_FILE = "CustomScrollViewTests/CustomScrollViewTests-Info.plist"; 434 | PRODUCT_NAME = "$(TARGET_NAME)"; 435 | TEST_HOST = "$(BUNDLE_LOADER)"; 436 | WRAPPER_EXTENSION = xctest; 437 | }; 438 | name = Release; 439 | }; 440 | /* End XCBuildConfiguration section */ 441 | 442 | /* Begin XCConfigurationList section */ 443 | 5D3A781F18FF1912006CCAB3 /* Build configuration list for PBXProject "CustomScrollView" */ = { 444 | isa = XCConfigurationList; 445 | buildConfigurations = ( 446 | 5D3A785418FF1912006CCAB3 /* Debug */, 447 | 5D3A785518FF1912006CCAB3 /* Release */, 448 | ); 449 | defaultConfigurationIsVisible = 0; 450 | defaultConfigurationName = Release; 451 | }; 452 | 5D3A785618FF1912006CCAB3 /* Build configuration list for PBXNativeTarget "CustomScrollView" */ = { 453 | isa = XCConfigurationList; 454 | buildConfigurations = ( 455 | 5D3A785718FF1912006CCAB3 /* Debug */, 456 | 5D3A785818FF1912006CCAB3 /* Release */, 457 | ); 458 | defaultConfigurationIsVisible = 0; 459 | defaultConfigurationName = Release; 460 | }; 461 | 5D3A785918FF1912006CCAB3 /* Build configuration list for PBXNativeTarget "CustomScrollViewTests" */ = { 462 | isa = XCConfigurationList; 463 | buildConfigurations = ( 464 | 5D3A785A18FF1912006CCAB3 /* Debug */, 465 | 5D3A785B18FF1912006CCAB3 /* Release */, 466 | ); 467 | defaultConfigurationIsVisible = 0; 468 | defaultConfigurationName = Release; 469 | }; 470 | /* End XCConfigurationList section */ 471 | }; 472 | rootObject = 5D3A781C18FF1912006CCAB3 /* Project object */; 473 | } 474 | -------------------------------------------------------------------------------- /CustomScrollView/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // CustomScrollView 4 | // 5 | // Created by Ole Begemann on 16.04.14. 6 | // Copyright (c) 2014 Ole Begemann. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /CustomScrollView/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // CustomScrollView 4 | // 5 | // Created by Ole Begemann on 16.04.14. 6 | // Copyright (c) 2014 Ole Begemann. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "ViewController.h" 11 | 12 | @implementation AppDelegate 13 | 14 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 15 | { 16 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 17 | self.window.backgroundColor = [UIColor whiteColor]; 18 | 19 | ViewController *viewController = [[ViewController alloc] init]; 20 | self.window.rootViewController = viewController; 21 | 22 | [self.window makeKeyAndVisible]; 23 | return YES; 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /CustomScrollView/CSCDynamicItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // CSCDynamicItem.h 3 | // CustomScrollView 4 | // 5 | // Created by Arkadiusz on 03-07-14. 6 | // Copyright (c) 2014 Ole Begemann. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CSCDynamicItem : NSObject 12 | 13 | @property (nonatomic, readwrite) CGPoint center; 14 | @property (nonatomic, readonly) CGRect bounds; 15 | @property (nonatomic, readwrite) CGAffineTransform transform; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /CustomScrollView/CSCDynamicItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // CSCDynamicItem.m 3 | // CustomScrollView 4 | // 5 | // Created by Arkadiusz on 03-07-14. 6 | // Copyright (c) 2014 Ole Begemann. All rights reserved. 7 | // 8 | 9 | #import "CSCDynamicItem.h" 10 | 11 | @implementation CSCDynamicItem 12 | 13 | - (instancetype)init { 14 | self = [super init]; 15 | 16 | if (self) { 17 | // Sets non-zero `bounds`, because otherwise Dynamics throws an exception. 18 | _bounds = CGRectMake(0, 0, 1, 1); 19 | } 20 | 21 | return self; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /CustomScrollView/CustomScrollView-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.olebegemann.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /CustomScrollView/CustomScrollView-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 | -------------------------------------------------------------------------------- /CustomScrollView/CustomScrollView.h: -------------------------------------------------------------------------------- 1 | // 2 | // CustomScrollView.h 3 | // CustomScrollView 4 | // 5 | // Created by Ole Begemann on 16.04.14. 6 | // Copyright (c) 2014 Ole Begemann. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CustomScrollView : UIView 12 | 13 | @property (nonatomic) CGSize contentSize; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /CustomScrollView/CustomScrollView.m: -------------------------------------------------------------------------------- 1 | // 2 | // CustomScrollView.m 3 | // CustomScrollView 4 | // 5 | // Created by Ole Begemann on 16.04.14. 6 | // Copyright (c) 2014 Ole Begemann. All rights reserved. 7 | // Parts of the class are based on https://github.com/grp/CustomScrollView/blob/custom-scroll-with-pop/CustomScrollView/CustomScrollView.m 8 | 9 | #import "CustomScrollView.h" 10 | #import "CSCDynamicItem.h" 11 | 12 | static CGFloat rubberBandDistance(CGFloat offset, CGFloat dimension) { 13 | 14 | const CGFloat constant = 0.55f; 15 | CGFloat result = (constant * abs(offset) * dimension) / (dimension + constant * abs(offset)); 16 | // The algorithm expects a positive offset, so we have to negate the result if the offset was negative. 17 | return offset < 0.0f ? -result : result; 18 | } 19 | 20 | @interface CustomScrollView () 21 | @property CGRect startBounds; 22 | @property (nonatomic, strong) UIDynamicAnimator *animator; 23 | @property (nonatomic, weak) UIDynamicItemBehavior *decelerationBehavior; 24 | @property (nonatomic, weak) UIAttachmentBehavior *springBehavior; 25 | @property (nonatomic, strong) CSCDynamicItem *dynamicItem; 26 | @property (nonatomic) CGPoint lastPointInBounds; 27 | @end 28 | 29 | @implementation CustomScrollView 30 | 31 | - (id)initWithFrame:(CGRect)frame 32 | { 33 | self = [super initWithFrame:frame]; 34 | if (self == nil) { 35 | return nil; 36 | } 37 | 38 | [self commonInitForCustomScrollView]; 39 | return self; 40 | } 41 | 42 | - (id)initWithCoder:(NSCoder *)decoder 43 | { 44 | self = [super initWithCoder:decoder]; 45 | if (self == nil) { 46 | return nil; 47 | } 48 | 49 | [self commonInitForCustomScrollView]; 50 | return self; 51 | } 52 | 53 | - (void)commonInitForCustomScrollView 54 | { 55 | UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; 56 | [self addGestureRecognizer:panGestureRecognizer]; 57 | 58 | self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self]; 59 | self.dynamicItem = [[CSCDynamicItem alloc] init]; 60 | } 61 | 62 | - (void)handlePanGesture:(UIPanGestureRecognizer *)panGestureRecognizer 63 | { 64 | switch (panGestureRecognizer.state) { 65 | case UIGestureRecognizerStateBegan: 66 | { 67 | self.startBounds = self.bounds; 68 | [self.animator removeAllBehaviors]; 69 | } 70 | // fall through 71 | 72 | case UIGestureRecognizerStateChanged: 73 | { 74 | CGPoint translation = [panGestureRecognizer translationInView:self]; 75 | CGRect bounds = self.startBounds; 76 | 77 | if (!self.scrollHorizontal) { 78 | translation.x = 0.0; 79 | } 80 | if (!self.scrollVertical) { 81 | translation.y = 0.0; 82 | } 83 | 84 | CGFloat newBoundsOriginX = bounds.origin.x - translation.x; 85 | CGFloat minBoundsOriginX = 0.0; 86 | CGFloat maxBoundsOriginX = self.contentSize.width - bounds.size.width; 87 | CGFloat constrainedBoundsOriginX = fmax(minBoundsOriginX, fmin(newBoundsOriginX, maxBoundsOriginX)); 88 | CGFloat rubberBandedX = rubberBandDistance(newBoundsOriginX - constrainedBoundsOriginX, CGRectGetWidth(self.bounds)); 89 | bounds.origin.x = constrainedBoundsOriginX + rubberBandedX; 90 | 91 | CGFloat newBoundsOriginY = bounds.origin.y - translation.y; 92 | CGFloat minBoundsOriginY = 0.0; 93 | CGFloat maxBoundsOriginY = self.contentSize.height - bounds.size.height; 94 | CGFloat constrainedBoundsOriginY = fmax(minBoundsOriginY, fmin(newBoundsOriginY, maxBoundsOriginY)); 95 | CGFloat rubberBandedY = rubberBandDistance(newBoundsOriginY - constrainedBoundsOriginY, CGRectGetHeight(self.bounds)); 96 | bounds.origin.y = constrainedBoundsOriginY + rubberBandedY; 97 | 98 | self.bounds = bounds; 99 | } 100 | break; 101 | case UIGestureRecognizerStateEnded: 102 | { 103 | CGPoint velocity = [panGestureRecognizer velocityInView:self]; 104 | velocity.x = -velocity.x; 105 | velocity.y = -velocity.y; 106 | 107 | if (![self scrollHorizontal] || [self outsideBoundsMinimum] || [self outsideBoundsMaximum]) { 108 | velocity.x = 0; 109 | } 110 | if (![self scrollVertical] || [self outsideBoundsMinimum] || [self outsideBoundsMaximum]) { 111 | velocity.y = 0; 112 | } 113 | 114 | self.dynamicItem.center = self.bounds.origin; 115 | UIDynamicItemBehavior *decelerationBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.dynamicItem]]; 116 | [decelerationBehavior addLinearVelocity:velocity forItem:self.dynamicItem]; 117 | decelerationBehavior.resistance = 2.0; 118 | 119 | __weak typeof(self)weakSelf = self; 120 | decelerationBehavior.action = ^{ 121 | // IMPORTANT: If the deceleration behavior is removed, the bounds' origin will stop updating. See other possible ways of updating origin in the accompanying blog post. 122 | CGRect bounds = weakSelf.bounds; 123 | bounds.origin = weakSelf.dynamicItem.center; 124 | weakSelf.bounds = bounds; 125 | }; 126 | 127 | [self.animator addBehavior:decelerationBehavior]; 128 | self.decelerationBehavior = decelerationBehavior; 129 | } 130 | break; 131 | 132 | default: 133 | break; 134 | } 135 | } 136 | 137 | - (void)setBounds:(CGRect)bounds 138 | { 139 | [super setBounds:bounds]; 140 | 141 | if (([self outsideBoundsMinimum] || [self outsideBoundsMaximum]) && 142 | (self.decelerationBehavior && !self.springBehavior)) { 143 | 144 | CGPoint target = [self anchor]; 145 | 146 | UIAttachmentBehavior *springBehavior = [[UIAttachmentBehavior alloc] initWithItem:self.dynamicItem attachedToAnchor:target]; 147 | // Has to be equal to zero, because otherwise the bounds.origin wouldn't exactly match the target's position. 148 | springBehavior.length = 0; 149 | // These two values were chosen by trial and error. 150 | springBehavior.damping = 1; 151 | springBehavior.frequency = 2; 152 | 153 | [self.animator addBehavior:springBehavior]; 154 | self.springBehavior = springBehavior; 155 | } 156 | 157 | if (![self outsideBoundsMinimum] && ![self outsideBoundsMaximum]) { 158 | self.lastPointInBounds = bounds.origin; 159 | } 160 | } 161 | 162 | - (BOOL)scrollVertical 163 | { 164 | return self.contentSize.height > CGRectGetHeight(self.bounds); 165 | } 166 | 167 | - (BOOL)scrollHorizontal 168 | { 169 | return self.contentSize.width > CGRectGetWidth(self.bounds); 170 | } 171 | 172 | - (CGPoint)maxBoundsOrigin 173 | { 174 | return CGPointMake(self.contentSize.width - self.bounds.size.width, 175 | self.contentSize.height - self.bounds.size.height); 176 | } 177 | 178 | - (BOOL)outsideBoundsMinimum 179 | { 180 | return self.bounds.origin.x < 0.0 || self.bounds.origin.y < 0.0; 181 | } 182 | 183 | - (BOOL)outsideBoundsMaximum 184 | { 185 | CGPoint maxBoundsOrigin = [self maxBoundsOrigin]; 186 | return self.bounds.origin.x > maxBoundsOrigin.x || self.bounds.origin.y > maxBoundsOrigin.y; 187 | } 188 | 189 | - (CGPoint)anchor 190 | { 191 | CGRect bounds = self.bounds; 192 | CGPoint maxBoundsOrigin = [self maxBoundsOrigin]; 193 | 194 | CGFloat deltaX = self.lastPointInBounds.x - bounds.origin.x; 195 | CGFloat deltaY = self.lastPointInBounds.y - bounds.origin.y; 196 | 197 | // solves a system of equations: y_1 = ax_1 + b and y_2 = ax_2 + b 198 | CGFloat a = deltaY / deltaX; 199 | CGFloat b = self.lastPointInBounds.y - self.lastPointInBounds.x * a; 200 | 201 | CGFloat leftBending = -bounds.origin.x; 202 | CGFloat topBending = -bounds.origin.y; 203 | CGFloat rightBending = bounds.origin.x - maxBoundsOrigin.x; 204 | CGFloat bottomBending = bounds.origin.y - maxBoundsOrigin.y; 205 | 206 | // Updates anchor's `y` based on already set `x`, i.e. y = f(x) 207 | void(^solveForY)(CGPoint*) = ^(CGPoint *anchor) { 208 | // Updates `y` only if there was a vertical movement. Otherwise `y` based on current `bounds.origin` is already correct. 209 | if (deltaY != 0) { 210 | anchor->y = a * anchor->x + b; 211 | } 212 | }; 213 | // Updates anchor's `x` based on already set `y`, i.e. x = f^(-1)(y) 214 | void(^solveForX)(CGPoint*) = ^(CGPoint *anchor) { 215 | if (deltaX != 0) { 216 | anchor->x = (anchor->y - b) / a; 217 | } 218 | }; 219 | 220 | CGPoint anchor = bounds.origin; 221 | 222 | if (bounds.origin.x < 0.0 && leftBending > topBending && leftBending > bottomBending) { 223 | anchor.x = 0; 224 | solveForY(&anchor); 225 | } else if (bounds.origin.y < 0.0 && topBending > leftBending && topBending > rightBending) { 226 | anchor.y = 0; 227 | solveForX(&anchor); 228 | } else if (bounds.origin.x > maxBoundsOrigin.x && rightBending > topBending && rightBending > bottomBending) { 229 | anchor.x = maxBoundsOrigin.x; 230 | solveForY(&anchor); 231 | } else if (bounds.origin.y > maxBoundsOrigin.y) { 232 | anchor.y = maxBoundsOrigin.y; 233 | solveForX(&anchor); 234 | } 235 | 236 | return anchor; 237 | } 238 | 239 | @end 240 | -------------------------------------------------------------------------------- /CustomScrollView/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 | } -------------------------------------------------------------------------------- /CustomScrollView/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 | } -------------------------------------------------------------------------------- /CustomScrollView/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // CustomScrollView 4 | // 5 | // Created by Ole Begemann on 16.04.14. 6 | // Copyright (c) 2014 Ole Begemann. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /CustomScrollView/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // CustomScrollView 4 | // 5 | // Created by Ole Begemann on 16.04.14. 6 | // Copyright (c) 2014 Ole Begemann. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "CustomScrollView.h" 11 | 12 | @interface ViewController () 13 | 14 | @property (nonatomic) CustomScrollView *customScrollView; 15 | 16 | @end 17 | 18 | 19 | @implementation ViewController 20 | 21 | - (void)viewDidLoad 22 | { 23 | [super viewDidLoad]; 24 | 25 | 26 | self.customScrollView = [[CustomScrollView alloc] initWithFrame:self.view.bounds]; 27 | self.customScrollView.contentSize = CGSizeMake(self.view.bounds.size.width, 1000); 28 | 29 | UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(20, 20, 100, 100)]; 30 | UIView *greenView = [[UIView alloc] initWithFrame:CGRectMake(150, 160, 150, 200)]; 31 | UIView *blueView = [[UIView alloc] initWithFrame:CGRectMake(40, 400, 200, 150)]; 32 | UIView *yellowView = [[UIView alloc] initWithFrame:CGRectMake(100, 600, 180, 150)]; 33 | 34 | redView.backgroundColor = [UIColor colorWithRed:0.815 green:0.007 blue:0.105 alpha:1]; 35 | greenView.backgroundColor = [UIColor colorWithRed:0.494 green:0.827 blue:0.129 alpha:1]; 36 | blueView.backgroundColor = [UIColor colorWithRed:0.29 green:0.564 blue:0.886 alpha:1]; 37 | yellowView.backgroundColor = [UIColor colorWithRed:0.972 green:0.905 blue:0.109 alpha:1]; 38 | 39 | [self.customScrollView addSubview:redView]; 40 | [self.customScrollView addSubview:greenView]; 41 | [self.customScrollView addSubview:blueView]; 42 | [self.customScrollView addSubview:yellowView]; 43 | 44 | 45 | 46 | UIView *redView1 = [[UIView alloc] initWithFrame:CGRectMake(20, 500+20, 100, 100)]; 47 | UIView *greenView1 = [[UIView alloc] initWithFrame:CGRectMake(150, 500+160, 150, 200)]; 48 | UIView *blueView1 = [[UIView alloc] initWithFrame:CGRectMake(40, 500+400, 200, 150)]; 49 | UIView *yellowView1 = [[UIView alloc] initWithFrame:CGRectMake(100, 500+600, 180, 150)]; 50 | 51 | redView1.backgroundColor = [UIColor purpleColor]; 52 | greenView1.backgroundColor = [UIColor redColor]; 53 | blueView1.backgroundColor = [UIColor grayColor]; 54 | yellowView1.backgroundColor = [UIColor blackColor]; 55 | 56 | [self.customScrollView addSubview:redView1]; 57 | [self.customScrollView addSubview:greenView1]; 58 | [self.customScrollView addSubview:blueView1]; 59 | [self.customScrollView addSubview:yellowView1]; 60 | 61 | 62 | [self.view addSubview:self.customScrollView];} 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /CustomScrollView/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /CustomScrollView/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CustomScrollView 4 | // 5 | // Created by Ole Begemann on 16.04.14. 6 | // Copyright (c) 2014 Ole Begemann. 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 | -------------------------------------------------------------------------------- /CustomScrollViewTests/CustomScrollViewTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.olebegemann.${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 | -------------------------------------------------------------------------------- /CustomScrollViewTests/CustomScrollViewTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CustomScrollViewTests.m 3 | // CustomScrollViewTests 4 | // 5 | // Created by Ole Begemann on 16.04.14. 6 | // Copyright (c) 2014 Ole Begemann. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CustomScrollViewTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation CustomScrollViewTests 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 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /CustomScrollViewTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CustomScrollView 2 | 3 | A very simple implementation of a scroll view on iOS. Not meant for production (use `UIScrollView` instead), just for learning purposes. Please check out my [accompanying blog post](http://oleb.net/blog/2014/04/understanding-uiscrollview/) for more information. 4 | 5 | Written by [Ole Begemann](http://oleb.net), April 2014. 6 | 7 | ## Additional Features 8 | 9 | Extended with additional features (Inertial Scrolling, Bouncing and Rubber-Banding) using UIKit Dynamics by [Arkadiusz Holko](https://twitter.com/arekholko). See the [follow-up blog post](http://holko.pl/2014/07/06/inertia-bouncing-rubber-banding-uikit-dynamics/). 10 | --------------------------------------------------------------------------------