├── .gitignore ├── .swift-version ├── ASHorizontalScrollView.podspec ├── ASHorizontalScrollViewForObjectiveC.podspec ├── AppStoreStyleHorizontalScrollView.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── AppStoreStyleHorizontalScrollView.xcscheme ├── README.md ├── ScrollViewSample ├── ScrollViewSample.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── ScrollViewSample.xccheckout │ │ └── xcuserdata │ │ │ ├── firestorm996.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ │ │ └── terencechen.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ ├── firestorm996.xcuserdatad │ │ ├── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ │ ├── ScrollViewSample.xcscheme │ │ │ └── xcschememanagement.plist │ │ └── terencechen.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ ├── ScrollViewSample.xcscheme │ │ └── xcschememanagement.plist ├── ScrollViewSample │ ├── ASHorizontalScrollView │ │ ├── ASHorizontalScrollView.h │ │ ├── ASHorizontalScrollView.m │ │ ├── ASHorizontalScrollViewDelegate.h │ │ ├── ASHorizontalScrollViewDelegate.m │ │ └── license.txt │ ├── Base.lproj │ │ ├── Main_iPad.storyboard │ │ └── Main_iPhone.storyboard │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── SAAppDelegate.h │ ├── SAAppDelegate.m │ ├── SAViewController.h │ ├── SAViewController.m │ ├── ScrollViewSample-Info.plist │ ├── ScrollViewSample-Prefix.pch │ ├── en.lproj │ │ └── InfoPlist.strings │ └── main.m └── ScrollViewSampleTests │ ├── ScrollViewSampleTests-Info.plist │ ├── ScrollViewSampleTests.m │ └── en.lproj │ └── InfoPlist.strings ├── Sources ├── ASHorizontalScrollView.swift ├── ASHorizontalScrollView │ ├── ASHorizontalScrollView.h │ ├── ASHorizontalScrollView.m │ ├── ASHorizontalScrollViewDelegate.h │ └── ASHorizontalScrollViewDelegate.m └── Info.plist ├── images ├── thumbookr1.gif └── thumbookr2.gif └── sampleScorllViewInSwift ├── sampleScorllViewInSwift.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── sampleScorllViewInSwift.xccheckout │ └── xcuserdata │ │ ├── firestorm996.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ │ └── terencechen.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ ├── firestorm996.xcuserdatad │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ │ ├── sampleScorllViewInSwift.xcscheme │ │ └── xcschememanagement.plist │ └── terencechen.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── sampleScorllViewInSwift.xcscheme │ └── xcschememanagement.plist └── sampleScorllViewInSwift ├── ASHorizontalScrollView.swift ├── AppDelegate.swift ├── Base.lproj └── Main.storyboard ├── Images.xcassets ├── AppIcon.appiconset │ └── Contents.json └── LaunchImage.launchimage │ └── Contents.json ├── Info.plist └── ViewController.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Build generated. 2 | build/ 3 | DerivedData/ 4 | 5 | # Mac OS X. 6 | .DS_Store 7 | 8 | # Xcode settings. 9 | !default.mode1v3 10 | !default.mode2v3 11 | !default.pbxuser 12 | !default.perspectivev3 13 | *.hmap 14 | *.ipa 15 | *.mode1v3 16 | *.mode2v3 17 | *.moved-aside 18 | *.pbxuser 19 | *.perspectivev3 20 | *.xccheckout 21 | *.xcscmblueprint 22 | *.xcuserstate 23 | xcuserdata/ 24 | 25 | sampleScorllViewInSwift/sampleScorllViewInSwift.xcodeproj/project.xcworkspace/xcuserdata/firestorm996.xcuserdatad/UserInterfaceState.xcuserstate 26 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.2 2 | -------------------------------------------------------------------------------- /ASHorizontalScrollView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "ASHorizontalScrollView" 3 | s.version = "1.5.2" 4 | s.summary = "App store style horizontal scroll view" 5 | s.description = <<-DESC 6 | It acts similar to apps sliding behaviours in App store. There are both Objective-C and Swift version available and they perform exactly the same function, please find whichever you like. 7 | DESC 8 | s.homepage = "https://github.com/terenceLuffy/AppStoreStyleHorizontalScrollView" 9 | s.license = { :type => 'MIT', :text => <<-LICENSE 10 | * The MIT License (MIT) 11 | * Copyright (C) 2014-Current WEIWEI CHEN 12 | * 13 | * 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: 14 | * 15 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | * 17 | * 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. 18 | LICENSE 19 | } 20 | s.author = { "Weiwei Chen" => "terenceluffy@gmail.com" } 21 | s.platform = :ios, "8.0" 22 | s.ios.deployment_target = "8.0" 23 | s.source = { :git => 'https://github.com/terenceLuffy/AppStoreStyleHorizontalScrollView.git', :tag => "1.5.2"} 24 | s.source_files = 'Sources/ASHorizontalScrollView.swift' 25 | s.frameworks = 'UIKit' 26 | s.requires_arc = true 27 | end 28 | -------------------------------------------------------------------------------- /ASHorizontalScrollViewForObjectiveC.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "ASHorizontalScrollViewForObjectiveC" 3 | s.version = "1.3" 4 | s.summary = "App store style horizontal scroll view Objective-C" 5 | s.description = <<-DESC 6 | It acts similar to apps sliding behaviours in App store. There are both Objective-C and Swift version available and they perform exactly the same function, please find whichever you like. 7 | DESC 8 | s.homepage = "https://github.com/terenceLuffy/AppStoreStyleHorizontalScrollView" 9 | s.license = { :type => 'MIT', :text => <<-LICENSE 10 | * The MIT License (MIT) 11 | * Copyright (C) 2014-2016 WEIWEI CHEN 12 | * 13 | * 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: 14 | * 15 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | * 17 | * 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. 18 | LICENSE 19 | } 20 | s.author = { "Weiwei Chen" => "terenceluffy@gmail.com" } 21 | s.platform = :ios, "8.0" 22 | s.ios.deployment_target = "8.0" 23 | s.source = { :git => 'https://github.com/terenceLuffy/AppStoreStyleHorizontalScrollView.git', :tag => "1.3" } 24 | s.source_files = 'Sources/ASHorizontalScrollView/*' 25 | end 26 | -------------------------------------------------------------------------------- /AppStoreStyleHorizontalScrollView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | BC3514B81C80AD1A004BAAA2 /* ASHorizontalScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC3514B61C80AD1A004BAAA2 /* ASHorizontalScrollView.swift */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXFileReference section */ 14 | BC3514AA1C80AC90004BAAA2 /* AppStoreStyleHorizontalScrollView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppStoreStyleHorizontalScrollView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 15 | BC3514B61C80AD1A004BAAA2 /* ASHorizontalScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ASHorizontalScrollView.swift; path = Sources/ASHorizontalScrollView.swift; sourceTree = ""; }; 16 | BC3514B71C80AD1A004BAAA2 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Sources/Info.plist; sourceTree = ""; }; 17 | /* End PBXFileReference section */ 18 | 19 | /* Begin PBXFrameworksBuildPhase section */ 20 | BC3514A61C80AC90004BAAA2 /* Frameworks */ = { 21 | isa = PBXFrameworksBuildPhase; 22 | buildActionMask = 2147483647; 23 | files = ( 24 | ); 25 | runOnlyForDeploymentPostprocessing = 0; 26 | }; 27 | /* End PBXFrameworksBuildPhase section */ 28 | 29 | /* Begin PBXGroup section */ 30 | BC3514A01C80AC90004BAAA2 = { 31 | isa = PBXGroup; 32 | children = ( 33 | BC3514B51C80AD0A004BAAA2 /* Sources */, 34 | BC3514AB1C80AC90004BAAA2 /* Products */, 35 | ); 36 | sourceTree = ""; 37 | }; 38 | BC3514AB1C80AC90004BAAA2 /* Products */ = { 39 | isa = PBXGroup; 40 | children = ( 41 | BC3514AA1C80AC90004BAAA2 /* AppStoreStyleHorizontalScrollView.framework */, 42 | ); 43 | name = Products; 44 | sourceTree = ""; 45 | }; 46 | BC3514B51C80AD0A004BAAA2 /* Sources */ = { 47 | isa = PBXGroup; 48 | children = ( 49 | BC3514B61C80AD1A004BAAA2 /* ASHorizontalScrollView.swift */, 50 | BC3514B71C80AD1A004BAAA2 /* Info.plist */, 51 | ); 52 | name = Sources; 53 | sourceTree = ""; 54 | }; 55 | /* End PBXGroup section */ 56 | 57 | /* Begin PBXHeadersBuildPhase section */ 58 | BC3514A71C80AC90004BAAA2 /* Headers */ = { 59 | isa = PBXHeadersBuildPhase; 60 | buildActionMask = 2147483647; 61 | files = ( 62 | ); 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | /* End PBXHeadersBuildPhase section */ 66 | 67 | /* Begin PBXNativeTarget section */ 68 | BC3514A91C80AC90004BAAA2 /* AppStoreStyleHorizontalScrollView */ = { 69 | isa = PBXNativeTarget; 70 | buildConfigurationList = BC3514B21C80AC90004BAAA2 /* Build configuration list for PBXNativeTarget "AppStoreStyleHorizontalScrollView" */; 71 | buildPhases = ( 72 | BC3514A51C80AC90004BAAA2 /* Sources */, 73 | BC3514A61C80AC90004BAAA2 /* Frameworks */, 74 | BC3514A71C80AC90004BAAA2 /* Headers */, 75 | BC3514A81C80AC90004BAAA2 /* Resources */, 76 | ); 77 | buildRules = ( 78 | ); 79 | dependencies = ( 80 | ); 81 | name = AppStoreStyleHorizontalScrollView; 82 | productName = AppStoreStyleHorizontalScrollView; 83 | productReference = BC3514AA1C80AC90004BAAA2 /* AppStoreStyleHorizontalScrollView.framework */; 84 | productType = "com.apple.product-type.framework"; 85 | }; 86 | /* End PBXNativeTarget section */ 87 | 88 | /* Begin PBXProject section */ 89 | BC3514A11C80AC90004BAAA2 /* Project object */ = { 90 | isa = PBXProject; 91 | attributes = { 92 | LastUpgradeCheck = 1000; 93 | ORGANIZATIONNAME = "Terence Chen"; 94 | TargetAttributes = { 95 | BC3514A91C80AC90004BAAA2 = { 96 | CreatedOnToolsVersion = 7.2; 97 | LastSwiftMigration = 1000; 98 | }; 99 | }; 100 | }; 101 | buildConfigurationList = BC3514A41C80AC90004BAAA2 /* Build configuration list for PBXProject "AppStoreStyleHorizontalScrollView" */; 102 | compatibilityVersion = "Xcode 3.2"; 103 | developmentRegion = English; 104 | hasScannedForEncodings = 0; 105 | knownRegions = ( 106 | en, 107 | ); 108 | mainGroup = BC3514A01C80AC90004BAAA2; 109 | productRefGroup = BC3514AB1C80AC90004BAAA2 /* Products */; 110 | projectDirPath = ""; 111 | projectRoot = ""; 112 | targets = ( 113 | BC3514A91C80AC90004BAAA2 /* AppStoreStyleHorizontalScrollView */, 114 | ); 115 | }; 116 | /* End PBXProject section */ 117 | 118 | /* Begin PBXResourcesBuildPhase section */ 119 | BC3514A81C80AC90004BAAA2 /* Resources */ = { 120 | isa = PBXResourcesBuildPhase; 121 | buildActionMask = 2147483647; 122 | files = ( 123 | ); 124 | runOnlyForDeploymentPostprocessing = 0; 125 | }; 126 | /* End PBXResourcesBuildPhase section */ 127 | 128 | /* Begin PBXSourcesBuildPhase section */ 129 | BC3514A51C80AC90004BAAA2 /* Sources */ = { 130 | isa = PBXSourcesBuildPhase; 131 | buildActionMask = 2147483647; 132 | files = ( 133 | BC3514B81C80AD1A004BAAA2 /* ASHorizontalScrollView.swift in Sources */, 134 | ); 135 | runOnlyForDeploymentPostprocessing = 0; 136 | }; 137 | /* End PBXSourcesBuildPhase section */ 138 | 139 | /* Begin XCBuildConfiguration section */ 140 | BC3514B01C80AC90004BAAA2 /* Debug */ = { 141 | isa = XCBuildConfiguration; 142 | buildSettings = { 143 | ALWAYS_SEARCH_USER_PATHS = NO; 144 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 145 | CLANG_CXX_LIBRARY = "libc++"; 146 | CLANG_ENABLE_MODULES = YES; 147 | CLANG_ENABLE_OBJC_ARC = YES; 148 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 149 | CLANG_WARN_BOOL_CONVERSION = YES; 150 | CLANG_WARN_COMMA = YES; 151 | CLANG_WARN_CONSTANT_CONVERSION = YES; 152 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 153 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 154 | CLANG_WARN_EMPTY_BODY = YES; 155 | CLANG_WARN_ENUM_CONVERSION = YES; 156 | CLANG_WARN_INFINITE_RECURSION = YES; 157 | CLANG_WARN_INT_CONVERSION = YES; 158 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 159 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 160 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 161 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 162 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 163 | CLANG_WARN_STRICT_PROTOTYPES = YES; 164 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 165 | CLANG_WARN_UNREACHABLE_CODE = YES; 166 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 167 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 168 | COPY_PHASE_STRIP = NO; 169 | CURRENT_PROJECT_VERSION = 1; 170 | DEBUG_INFORMATION_FORMAT = dwarf; 171 | ENABLE_STRICT_OBJC_MSGSEND = YES; 172 | ENABLE_TESTABILITY = YES; 173 | GCC_C_LANGUAGE_STANDARD = gnu99; 174 | GCC_DYNAMIC_NO_PIC = NO; 175 | GCC_NO_COMMON_BLOCKS = YES; 176 | GCC_OPTIMIZATION_LEVEL = 0; 177 | GCC_PREPROCESSOR_DEFINITIONS = ( 178 | "DEBUG=1", 179 | "$(inherited)", 180 | ); 181 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 182 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 183 | GCC_WARN_UNDECLARED_SELECTOR = YES; 184 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 185 | GCC_WARN_UNUSED_FUNCTION = YES; 186 | GCC_WARN_UNUSED_VARIABLE = YES; 187 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 188 | MTL_ENABLE_DEBUG_INFO = YES; 189 | ONLY_ACTIVE_ARCH = YES; 190 | SDKROOT = iphoneos; 191 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 192 | SWIFT_VERSION = 3.0.1; 193 | TARGETED_DEVICE_FAMILY = "1,2"; 194 | VERSIONING_SYSTEM = "apple-generic"; 195 | VERSION_INFO_PREFIX = ""; 196 | }; 197 | name = Debug; 198 | }; 199 | BC3514B11C80AC90004BAAA2 /* Release */ = { 200 | isa = XCBuildConfiguration; 201 | buildSettings = { 202 | ALWAYS_SEARCH_USER_PATHS = NO; 203 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 204 | CLANG_CXX_LIBRARY = "libc++"; 205 | CLANG_ENABLE_MODULES = YES; 206 | CLANG_ENABLE_OBJC_ARC = YES; 207 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 208 | CLANG_WARN_BOOL_CONVERSION = YES; 209 | CLANG_WARN_COMMA = YES; 210 | CLANG_WARN_CONSTANT_CONVERSION = YES; 211 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 212 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 213 | CLANG_WARN_EMPTY_BODY = YES; 214 | CLANG_WARN_ENUM_CONVERSION = YES; 215 | CLANG_WARN_INFINITE_RECURSION = YES; 216 | CLANG_WARN_INT_CONVERSION = YES; 217 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 218 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 219 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 220 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 221 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 222 | CLANG_WARN_STRICT_PROTOTYPES = YES; 223 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 224 | CLANG_WARN_UNREACHABLE_CODE = YES; 225 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 226 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 227 | COPY_PHASE_STRIP = NO; 228 | CURRENT_PROJECT_VERSION = 1; 229 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 230 | ENABLE_NS_ASSERTIONS = NO; 231 | ENABLE_STRICT_OBJC_MSGSEND = YES; 232 | GCC_C_LANGUAGE_STANDARD = gnu99; 233 | GCC_NO_COMMON_BLOCKS = YES; 234 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 235 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 236 | GCC_WARN_UNDECLARED_SELECTOR = YES; 237 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 238 | GCC_WARN_UNUSED_FUNCTION = YES; 239 | GCC_WARN_UNUSED_VARIABLE = YES; 240 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 241 | MTL_ENABLE_DEBUG_INFO = NO; 242 | SDKROOT = iphoneos; 243 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 244 | SWIFT_VERSION = 3.0.1; 245 | TARGETED_DEVICE_FAMILY = "1,2"; 246 | VALIDATE_PRODUCT = YES; 247 | VERSIONING_SYSTEM = "apple-generic"; 248 | VERSION_INFO_PREFIX = ""; 249 | }; 250 | name = Release; 251 | }; 252 | BC3514B31C80AC90004BAAA2 /* Debug */ = { 253 | isa = XCBuildConfiguration; 254 | buildSettings = { 255 | CLANG_ENABLE_MODULES = YES; 256 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 257 | DEFINES_MODULE = YES; 258 | DYLIB_COMPATIBILITY_VERSION = 1; 259 | DYLIB_CURRENT_VERSION = 1; 260 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 261 | INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; 262 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 263 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 264 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 265 | PRODUCT_BUNDLE_IDENTIFIER = com.terencechen.AppStoreStyleHorizontalScrollView; 266 | PRODUCT_NAME = "$(TARGET_NAME)"; 267 | SKIP_INSTALL = YES; 268 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 269 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 270 | SWIFT_VERSION = 4.2; 271 | }; 272 | name = Debug; 273 | }; 274 | BC3514B41C80AC90004BAAA2 /* Release */ = { 275 | isa = XCBuildConfiguration; 276 | buildSettings = { 277 | CLANG_ENABLE_MODULES = YES; 278 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 279 | DEFINES_MODULE = YES; 280 | DYLIB_COMPATIBILITY_VERSION = 1; 281 | DYLIB_CURRENT_VERSION = 1; 282 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 283 | INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; 284 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 285 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 286 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 287 | PRODUCT_BUNDLE_IDENTIFIER = com.terencechen.AppStoreStyleHorizontalScrollView; 288 | PRODUCT_NAME = "$(TARGET_NAME)"; 289 | SKIP_INSTALL = YES; 290 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 291 | SWIFT_VERSION = 4.2; 292 | }; 293 | name = Release; 294 | }; 295 | /* End XCBuildConfiguration section */ 296 | 297 | /* Begin XCConfigurationList section */ 298 | BC3514A41C80AC90004BAAA2 /* Build configuration list for PBXProject "AppStoreStyleHorizontalScrollView" */ = { 299 | isa = XCConfigurationList; 300 | buildConfigurations = ( 301 | BC3514B01C80AC90004BAAA2 /* Debug */, 302 | BC3514B11C80AC90004BAAA2 /* Release */, 303 | ); 304 | defaultConfigurationIsVisible = 0; 305 | defaultConfigurationName = Release; 306 | }; 307 | BC3514B21C80AC90004BAAA2 /* Build configuration list for PBXNativeTarget "AppStoreStyleHorizontalScrollView" */ = { 308 | isa = XCConfigurationList; 309 | buildConfigurations = ( 310 | BC3514B31C80AC90004BAAA2 /* Debug */, 311 | BC3514B41C80AC90004BAAA2 /* Release */, 312 | ); 313 | defaultConfigurationIsVisible = 0; 314 | defaultConfigurationName = Release; 315 | }; 316 | /* End XCConfigurationList section */ 317 | }; 318 | rootObject = BC3514A11C80AC90004BAAA2 /* Project object */; 319 | } 320 | -------------------------------------------------------------------------------- /AppStoreStyleHorizontalScrollView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AppStoreStyleHorizontalScrollView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AppStoreStyleHorizontalScrollView.xcodeproj/xcshareddata/xcschemes/AppStoreStyleHorizontalScrollView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ASHorizontalScrollView 2 | ================================= 3 | 4 | App store style horizontal scroll view 5 | ![Swift Version](https://img.shields.io/badge/Swift-4.2-orange.svg) 6 | ![License MIT](https://img.shields.io/badge/License-MIT-lightgrey.svg) 7 | ![Plaform](https://img.shields.io/badge/Platform-iOS-lightgrey.svg ) 8 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-brightgreen.svg)](https://github.com/Carthage/Carthage) 9 | [![Pod Version](https://img.shields.io/badge/Pod-1.1.0-6193DF.svg)](https://cocoapods.org/) 10 | 11 | It acts similar to apps sliding behaviours in App store. There are both Objective-C (do not update anymore since v1.3) and Swift version available and they perform exactly the same function, please find whichever you like. 12 | 13 | ![](/images/thumbookr1.gif) ![](/images/thumbookr2.gif) 14 | 15 | Please note that the gif is not from the sample project. 16 | 17 | Please note that from now on, the objective-C source won't be updated anymore as I have been using Swift for all my works totally. 18 | 19 | ### Installation 20 | Install using one of the following options: 21 | 22 | 1. Download the source from "Source" folder and drag into your project. 23 | 2. Using [CocoaPods](http://cocoapods.org) 24 | 25 | Swift 26 | ```ruby 27 | pod 'ASHorizontalScrollView', '~> 1.5.2' 28 | ``` 29 | 30 | Objective-C 31 | ```ruby 32 | pod 'ASHorizontalScrollViewForObjectiveC', '~> 1.3' 33 | ``` 34 | 35 | 3. Using [Carthage](https://github.com/Carthage/Carthage) 36 | 37 | Swift 38 | ```shell 39 | github "terenceLuffy/AppStoreStyleHorizontalScrollView" ~> 1.5.2 40 | ``` 41 | 42 | ### How to use it? 43 | Please check in [here](http://terenceluffy.github.io/how-to-use-ASHorizontalScrollView/) (updated for v1.5, please check sample project for usage) 44 | 45 | ### Versions: 46 | 1.0: Initial release 47 | 48 | 1.1: Change to adapt iOS 9 and Swift 2.1 49 | 50 | 1.2: Change C style code to Swift 3 compatible code, fix removeItemAtIndex last index crash bug 51 | 52 | 1.3: Supoort Swift 3 and XCode 8, add support to nib file 53 | 54 | 1.4: Add custom width when judging whether to scroll to next item; add support to all Apple devices screen size, now you can specified different mini margin, mini appear width and left margin for all sorts of screen sizes. 55 | 56 | 1.5: Introduce new properties to allow set number of items per screen for multiple screen sizes instead of setting minimum margins, as well as new properties to center subviews when items are not wide enough to use whole screen width 57 | 58 | 1.5.1: Add checking on items size before removing all items to avoid crash 59 | 60 | 1.5.2: Migrate to Swift 4.2 and add settings for iPhone X, Xs, XR and Xs Max landscape support 61 | 62 | ### iOS Supported Version 63 | iOS 8.0 or above. 64 | 65 | ### Authorization 66 | The MIT License (MIT) 67 | Copyright (C) 2014-Current WEIWEI CHEN 68 | 69 | 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: 70 | 71 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 72 | 73 | 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. 74 | 75 | ### Support 76 | Having bugs? Please send me an email. 77 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7402EDE7193BB82C00B1C37D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7402EDE6193BB82C00B1C37D /* Foundation.framework */; }; 11 | 7402EDE9193BB82C00B1C37D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7402EDE8193BB82C00B1C37D /* CoreGraphics.framework */; }; 12 | 7402EDEB193BB82C00B1C37D /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7402EDEA193BB82C00B1C37D /* UIKit.framework */; }; 13 | 7402EDF1193BB82C00B1C37D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7402EDEF193BB82C00B1C37D /* InfoPlist.strings */; }; 14 | 7402EDF3193BB82C00B1C37D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 7402EDF2193BB82C00B1C37D /* main.m */; }; 15 | 7402EDF7193BB82C00B1C37D /* SAAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7402EDF6193BB82C00B1C37D /* SAAppDelegate.m */; }; 16 | 7402EDFA193BB82C00B1C37D /* Main_iPhone.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7402EDF8193BB82C00B1C37D /* Main_iPhone.storyboard */; }; 17 | 7402EDFD193BB82C00B1C37D /* Main_iPad.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7402EDFB193BB82C00B1C37D /* Main_iPad.storyboard */; }; 18 | 7402EE00193BB82C00B1C37D /* SAViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7402EDFF193BB82C00B1C37D /* SAViewController.m */; }; 19 | 7402EE02193BB82C00B1C37D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7402EE01193BB82C00B1C37D /* Images.xcassets */; }; 20 | 7402EE09193BB82D00B1C37D /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7402EE08193BB82D00B1C37D /* XCTest.framework */; }; 21 | 7402EE0A193BB82D00B1C37D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7402EDE6193BB82C00B1C37D /* Foundation.framework */; }; 22 | 7402EE0B193BB82D00B1C37D /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7402EDEA193BB82C00B1C37D /* UIKit.framework */; }; 23 | 7402EE13193BB82D00B1C37D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7402EE11193BB82D00B1C37D /* InfoPlist.strings */; }; 24 | 7402EE15193BB82D00B1C37D /* ScrollViewSampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7402EE14193BB82D00B1C37D /* ScrollViewSampleTests.m */; }; 25 | 7402EE21193BB93000B1C37D /* ASHorizontalScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7402EE20193BB93000B1C37D /* ASHorizontalScrollView.m */; }; 26 | 743B8456194001FF008167A6 /* license.txt in Resources */ = {isa = PBXBuildFile; fileRef = 743B8455194001FF008167A6 /* license.txt */; }; 27 | 74E54595193D72CE0079E331 /* ASHorizontalScrollViewDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 74E54594193D72CE0079E331 /* ASHorizontalScrollViewDelegate.m */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXContainerItemProxy section */ 31 | 7402EE0C193BB82D00B1C37D /* PBXContainerItemProxy */ = { 32 | isa = PBXContainerItemProxy; 33 | containerPortal = 7402EDDB193BB82C00B1C37D /* Project object */; 34 | proxyType = 1; 35 | remoteGlobalIDString = 7402EDE2193BB82C00B1C37D; 36 | remoteInfo = ScrollViewSample; 37 | }; 38 | /* End PBXContainerItemProxy section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 7402EDE3193BB82C00B1C37D /* ScrollViewSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ScrollViewSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 7402EDE6193BB82C00B1C37D /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 43 | 7402EDE8193BB82C00B1C37D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 44 | 7402EDEA193BB82C00B1C37D /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 45 | 7402EDEE193BB82C00B1C37D /* ScrollViewSample-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ScrollViewSample-Info.plist"; sourceTree = ""; }; 46 | 7402EDF0193BB82C00B1C37D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 47 | 7402EDF2193BB82C00B1C37D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 48 | 7402EDF4193BB82C00B1C37D /* ScrollViewSample-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ScrollViewSample-Prefix.pch"; sourceTree = ""; }; 49 | 7402EDF5193BB82C00B1C37D /* SAAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SAAppDelegate.h; sourceTree = ""; }; 50 | 7402EDF6193BB82C00B1C37D /* SAAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SAAppDelegate.m; sourceTree = ""; }; 51 | 7402EDF9193BB82C00B1C37D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main_iPhone.storyboard; sourceTree = ""; }; 52 | 7402EDFC193BB82C00B1C37D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main_iPad.storyboard; sourceTree = ""; }; 53 | 7402EDFE193BB82C00B1C37D /* SAViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SAViewController.h; sourceTree = ""; }; 54 | 7402EDFF193BB82C00B1C37D /* SAViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SAViewController.m; sourceTree = ""; }; 55 | 7402EE01193BB82C00B1C37D /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 56 | 7402EE07193BB82D00B1C37D /* ScrollViewSampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ScrollViewSampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | 7402EE08193BB82D00B1C37D /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 58 | 7402EE10193BB82D00B1C37D /* ScrollViewSampleTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ScrollViewSampleTests-Info.plist"; sourceTree = ""; }; 59 | 7402EE12193BB82D00B1C37D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 60 | 7402EE14193BB82D00B1C37D /* ScrollViewSampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ScrollViewSampleTests.m; sourceTree = ""; }; 61 | 7402EE1F193BB93000B1C37D /* ASHorizontalScrollView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASHorizontalScrollView.h; sourceTree = ""; }; 62 | 7402EE20193BB93000B1C37D /* ASHorizontalScrollView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASHorizontalScrollView.m; sourceTree = ""; }; 63 | 743B8455194001FF008167A6 /* license.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = license.txt; sourceTree = ""; }; 64 | 74E54593193D72CE0079E331 /* ASHorizontalScrollViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASHorizontalScrollViewDelegate.h; sourceTree = ""; }; 65 | 74E54594193D72CE0079E331 /* ASHorizontalScrollViewDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASHorizontalScrollViewDelegate.m; sourceTree = ""; }; 66 | /* End PBXFileReference section */ 67 | 68 | /* Begin PBXFrameworksBuildPhase section */ 69 | 7402EDE0193BB82C00B1C37D /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | 7402EDE9193BB82C00B1C37D /* CoreGraphics.framework in Frameworks */, 74 | 7402EDEB193BB82C00B1C37D /* UIKit.framework in Frameworks */, 75 | 7402EDE7193BB82C00B1C37D /* Foundation.framework in Frameworks */, 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | 7402EE04193BB82D00B1C37D /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | 7402EE09193BB82D00B1C37D /* XCTest.framework in Frameworks */, 84 | 7402EE0B193BB82D00B1C37D /* UIKit.framework in Frameworks */, 85 | 7402EE0A193BB82D00B1C37D /* Foundation.framework in Frameworks */, 86 | ); 87 | runOnlyForDeploymentPostprocessing = 0; 88 | }; 89 | /* End PBXFrameworksBuildPhase section */ 90 | 91 | /* Begin PBXGroup section */ 92 | 7402EDDA193BB82C00B1C37D = { 93 | isa = PBXGroup; 94 | children = ( 95 | 7402EDEC193BB82C00B1C37D /* ScrollViewSample */, 96 | 7402EE0E193BB82D00B1C37D /* ScrollViewSampleTests */, 97 | 7402EDE5193BB82C00B1C37D /* Frameworks */, 98 | 7402EDE4193BB82C00B1C37D /* Products */, 99 | ); 100 | sourceTree = ""; 101 | }; 102 | 7402EDE4193BB82C00B1C37D /* Products */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 7402EDE3193BB82C00B1C37D /* ScrollViewSample.app */, 106 | 7402EE07193BB82D00B1C37D /* ScrollViewSampleTests.xctest */, 107 | ); 108 | name = Products; 109 | sourceTree = ""; 110 | }; 111 | 7402EDE5193BB82C00B1C37D /* Frameworks */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 7402EDE6193BB82C00B1C37D /* Foundation.framework */, 115 | 7402EDE8193BB82C00B1C37D /* CoreGraphics.framework */, 116 | 7402EDEA193BB82C00B1C37D /* UIKit.framework */, 117 | 7402EE08193BB82D00B1C37D /* XCTest.framework */, 118 | ); 119 | name = Frameworks; 120 | sourceTree = ""; 121 | }; 122 | 7402EDEC193BB82C00B1C37D /* ScrollViewSample */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 7402EE1E193BB8E300B1C37D /* ASHorizontalScrollView */, 126 | 7402EDF5193BB82C00B1C37D /* SAAppDelegate.h */, 127 | 7402EDF6193BB82C00B1C37D /* SAAppDelegate.m */, 128 | 7402EDF8193BB82C00B1C37D /* Main_iPhone.storyboard */, 129 | 7402EDFB193BB82C00B1C37D /* Main_iPad.storyboard */, 130 | 7402EDFE193BB82C00B1C37D /* SAViewController.h */, 131 | 7402EDFF193BB82C00B1C37D /* SAViewController.m */, 132 | 7402EE01193BB82C00B1C37D /* Images.xcassets */, 133 | 7402EDED193BB82C00B1C37D /* Supporting Files */, 134 | ); 135 | path = ScrollViewSample; 136 | sourceTree = ""; 137 | }; 138 | 7402EDED193BB82C00B1C37D /* Supporting Files */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | 7402EDEE193BB82C00B1C37D /* ScrollViewSample-Info.plist */, 142 | 7402EDEF193BB82C00B1C37D /* InfoPlist.strings */, 143 | 7402EDF2193BB82C00B1C37D /* main.m */, 144 | 7402EDF4193BB82C00B1C37D /* ScrollViewSample-Prefix.pch */, 145 | ); 146 | name = "Supporting Files"; 147 | sourceTree = ""; 148 | }; 149 | 7402EE0E193BB82D00B1C37D /* ScrollViewSampleTests */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | 7402EE14193BB82D00B1C37D /* ScrollViewSampleTests.m */, 153 | 7402EE0F193BB82D00B1C37D /* Supporting Files */, 154 | ); 155 | path = ScrollViewSampleTests; 156 | sourceTree = ""; 157 | }; 158 | 7402EE0F193BB82D00B1C37D /* Supporting Files */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | 7402EE10193BB82D00B1C37D /* ScrollViewSampleTests-Info.plist */, 162 | 7402EE11193BB82D00B1C37D /* InfoPlist.strings */, 163 | ); 164 | name = "Supporting Files"; 165 | sourceTree = ""; 166 | }; 167 | 7402EE1E193BB8E300B1C37D /* ASHorizontalScrollView */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | 743B8455194001FF008167A6 /* license.txt */, 171 | 7402EE1F193BB93000B1C37D /* ASHorizontalScrollView.h */, 172 | 7402EE20193BB93000B1C37D /* ASHorizontalScrollView.m */, 173 | 74E54593193D72CE0079E331 /* ASHorizontalScrollViewDelegate.h */, 174 | 74E54594193D72CE0079E331 /* ASHorizontalScrollViewDelegate.m */, 175 | ); 176 | path = ASHorizontalScrollView; 177 | sourceTree = ""; 178 | }; 179 | /* End PBXGroup section */ 180 | 181 | /* Begin PBXNativeTarget section */ 182 | 7402EDE2193BB82C00B1C37D /* ScrollViewSample */ = { 183 | isa = PBXNativeTarget; 184 | buildConfigurationList = 7402EE18193BB82D00B1C37D /* Build configuration list for PBXNativeTarget "ScrollViewSample" */; 185 | buildPhases = ( 186 | 7402EDDF193BB82C00B1C37D /* Sources */, 187 | 7402EDE0193BB82C00B1C37D /* Frameworks */, 188 | 7402EDE1193BB82C00B1C37D /* Resources */, 189 | ); 190 | buildRules = ( 191 | ); 192 | dependencies = ( 193 | ); 194 | name = ScrollViewSample; 195 | productName = ScrollViewSample; 196 | productReference = 7402EDE3193BB82C00B1C37D /* ScrollViewSample.app */; 197 | productType = "com.apple.product-type.application"; 198 | }; 199 | 7402EE06193BB82D00B1C37D /* ScrollViewSampleTests */ = { 200 | isa = PBXNativeTarget; 201 | buildConfigurationList = 7402EE1B193BB82D00B1C37D /* Build configuration list for PBXNativeTarget "ScrollViewSampleTests" */; 202 | buildPhases = ( 203 | 7402EE03193BB82D00B1C37D /* Sources */, 204 | 7402EE04193BB82D00B1C37D /* Frameworks */, 205 | 7402EE05193BB82D00B1C37D /* Resources */, 206 | ); 207 | buildRules = ( 208 | ); 209 | dependencies = ( 210 | 7402EE0D193BB82D00B1C37D /* PBXTargetDependency */, 211 | ); 212 | name = ScrollViewSampleTests; 213 | productName = ScrollViewSampleTests; 214 | productReference = 7402EE07193BB82D00B1C37D /* ScrollViewSampleTests.xctest */; 215 | productType = "com.apple.product-type.bundle.unit-test"; 216 | }; 217 | /* End PBXNativeTarget section */ 218 | 219 | /* Begin PBXProject section */ 220 | 7402EDDB193BB82C00B1C37D /* Project object */ = { 221 | isa = PBXProject; 222 | attributes = { 223 | CLASSPREFIX = SA; 224 | LastUpgradeCheck = 0800; 225 | ORGANIZATIONNAME = Zuse; 226 | TargetAttributes = { 227 | 7402EE06193BB82D00B1C37D = { 228 | TestTargetID = 7402EDE2193BB82C00B1C37D; 229 | }; 230 | }; 231 | }; 232 | buildConfigurationList = 7402EDDE193BB82C00B1C37D /* Build configuration list for PBXProject "ScrollViewSample" */; 233 | compatibilityVersion = "Xcode 3.2"; 234 | developmentRegion = English; 235 | hasScannedForEncodings = 0; 236 | knownRegions = ( 237 | en, 238 | Base, 239 | ); 240 | mainGroup = 7402EDDA193BB82C00B1C37D; 241 | productRefGroup = 7402EDE4193BB82C00B1C37D /* Products */; 242 | projectDirPath = ""; 243 | projectRoot = ""; 244 | targets = ( 245 | 7402EDE2193BB82C00B1C37D /* ScrollViewSample */, 246 | 7402EE06193BB82D00B1C37D /* ScrollViewSampleTests */, 247 | ); 248 | }; 249 | /* End PBXProject section */ 250 | 251 | /* Begin PBXResourcesBuildPhase section */ 252 | 7402EDE1193BB82C00B1C37D /* Resources */ = { 253 | isa = PBXResourcesBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | 7402EDFD193BB82C00B1C37D /* Main_iPad.storyboard in Resources */, 257 | 743B8456194001FF008167A6 /* license.txt in Resources */, 258 | 7402EE02193BB82C00B1C37D /* Images.xcassets in Resources */, 259 | 7402EDFA193BB82C00B1C37D /* Main_iPhone.storyboard in Resources */, 260 | 7402EDF1193BB82C00B1C37D /* InfoPlist.strings in Resources */, 261 | ); 262 | runOnlyForDeploymentPostprocessing = 0; 263 | }; 264 | 7402EE05193BB82D00B1C37D /* Resources */ = { 265 | isa = PBXResourcesBuildPhase; 266 | buildActionMask = 2147483647; 267 | files = ( 268 | 7402EE13193BB82D00B1C37D /* InfoPlist.strings in Resources */, 269 | ); 270 | runOnlyForDeploymentPostprocessing = 0; 271 | }; 272 | /* End PBXResourcesBuildPhase section */ 273 | 274 | /* Begin PBXSourcesBuildPhase section */ 275 | 7402EDDF193BB82C00B1C37D /* Sources */ = { 276 | isa = PBXSourcesBuildPhase; 277 | buildActionMask = 2147483647; 278 | files = ( 279 | 7402EDF7193BB82C00B1C37D /* SAAppDelegate.m in Sources */, 280 | 7402EE00193BB82C00B1C37D /* SAViewController.m in Sources */, 281 | 7402EDF3193BB82C00B1C37D /* main.m in Sources */, 282 | 7402EE21193BB93000B1C37D /* ASHorizontalScrollView.m in Sources */, 283 | 74E54595193D72CE0079E331 /* ASHorizontalScrollViewDelegate.m in Sources */, 284 | ); 285 | runOnlyForDeploymentPostprocessing = 0; 286 | }; 287 | 7402EE03193BB82D00B1C37D /* Sources */ = { 288 | isa = PBXSourcesBuildPhase; 289 | buildActionMask = 2147483647; 290 | files = ( 291 | 7402EE15193BB82D00B1C37D /* ScrollViewSampleTests.m in Sources */, 292 | ); 293 | runOnlyForDeploymentPostprocessing = 0; 294 | }; 295 | /* End PBXSourcesBuildPhase section */ 296 | 297 | /* Begin PBXTargetDependency section */ 298 | 7402EE0D193BB82D00B1C37D /* PBXTargetDependency */ = { 299 | isa = PBXTargetDependency; 300 | target = 7402EDE2193BB82C00B1C37D /* ScrollViewSample */; 301 | targetProxy = 7402EE0C193BB82D00B1C37D /* PBXContainerItemProxy */; 302 | }; 303 | /* End PBXTargetDependency section */ 304 | 305 | /* Begin PBXVariantGroup section */ 306 | 7402EDEF193BB82C00B1C37D /* InfoPlist.strings */ = { 307 | isa = PBXVariantGroup; 308 | children = ( 309 | 7402EDF0193BB82C00B1C37D /* en */, 310 | ); 311 | name = InfoPlist.strings; 312 | sourceTree = ""; 313 | }; 314 | 7402EDF8193BB82C00B1C37D /* Main_iPhone.storyboard */ = { 315 | isa = PBXVariantGroup; 316 | children = ( 317 | 7402EDF9193BB82C00B1C37D /* Base */, 318 | ); 319 | name = Main_iPhone.storyboard; 320 | sourceTree = ""; 321 | }; 322 | 7402EDFB193BB82C00B1C37D /* Main_iPad.storyboard */ = { 323 | isa = PBXVariantGroup; 324 | children = ( 325 | 7402EDFC193BB82C00B1C37D /* Base */, 326 | ); 327 | name = Main_iPad.storyboard; 328 | sourceTree = ""; 329 | }; 330 | 7402EE11193BB82D00B1C37D /* InfoPlist.strings */ = { 331 | isa = PBXVariantGroup; 332 | children = ( 333 | 7402EE12193BB82D00B1C37D /* en */, 334 | ); 335 | name = InfoPlist.strings; 336 | sourceTree = ""; 337 | }; 338 | /* End PBXVariantGroup section */ 339 | 340 | /* Begin XCBuildConfiguration section */ 341 | 7402EE16193BB82D00B1C37D /* Debug */ = { 342 | isa = XCBuildConfiguration; 343 | buildSettings = { 344 | ALWAYS_SEARCH_USER_PATHS = NO; 345 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 346 | CLANG_CXX_LIBRARY = "libc++"; 347 | CLANG_ENABLE_MODULES = YES; 348 | CLANG_ENABLE_OBJC_ARC = YES; 349 | CLANG_WARN_BOOL_CONVERSION = YES; 350 | CLANG_WARN_CONSTANT_CONVERSION = YES; 351 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 352 | CLANG_WARN_EMPTY_BODY = YES; 353 | CLANG_WARN_ENUM_CONVERSION = YES; 354 | CLANG_WARN_INFINITE_RECURSION = YES; 355 | CLANG_WARN_INT_CONVERSION = YES; 356 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 357 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 358 | CLANG_WARN_UNREACHABLE_CODE = YES; 359 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 360 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 361 | COPY_PHASE_STRIP = NO; 362 | ENABLE_STRICT_OBJC_MSGSEND = YES; 363 | ENABLE_TESTABILITY = YES; 364 | GCC_C_LANGUAGE_STANDARD = gnu99; 365 | GCC_DYNAMIC_NO_PIC = NO; 366 | GCC_NO_COMMON_BLOCKS = YES; 367 | GCC_OPTIMIZATION_LEVEL = 0; 368 | GCC_PREPROCESSOR_DEFINITIONS = ( 369 | "DEBUG=1", 370 | "$(inherited)", 371 | ); 372 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 373 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 374 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 375 | GCC_WARN_UNDECLARED_SELECTOR = YES; 376 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 377 | GCC_WARN_UNUSED_FUNCTION = YES; 378 | GCC_WARN_UNUSED_VARIABLE = YES; 379 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 380 | ONLY_ACTIVE_ARCH = YES; 381 | SDKROOT = iphoneos; 382 | TARGETED_DEVICE_FAMILY = "1,2"; 383 | }; 384 | name = Debug; 385 | }; 386 | 7402EE17193BB82D00B1C37D /* Release */ = { 387 | isa = XCBuildConfiguration; 388 | buildSettings = { 389 | ALWAYS_SEARCH_USER_PATHS = NO; 390 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 391 | CLANG_CXX_LIBRARY = "libc++"; 392 | CLANG_ENABLE_MODULES = YES; 393 | CLANG_ENABLE_OBJC_ARC = YES; 394 | CLANG_WARN_BOOL_CONVERSION = YES; 395 | CLANG_WARN_CONSTANT_CONVERSION = YES; 396 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 397 | CLANG_WARN_EMPTY_BODY = YES; 398 | CLANG_WARN_ENUM_CONVERSION = YES; 399 | CLANG_WARN_INFINITE_RECURSION = YES; 400 | CLANG_WARN_INT_CONVERSION = YES; 401 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 402 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 403 | CLANG_WARN_UNREACHABLE_CODE = YES; 404 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 405 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 406 | COPY_PHASE_STRIP = YES; 407 | ENABLE_NS_ASSERTIONS = NO; 408 | ENABLE_STRICT_OBJC_MSGSEND = YES; 409 | GCC_C_LANGUAGE_STANDARD = gnu99; 410 | GCC_NO_COMMON_BLOCKS = YES; 411 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 412 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 413 | GCC_WARN_UNDECLARED_SELECTOR = YES; 414 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 415 | GCC_WARN_UNUSED_FUNCTION = YES; 416 | GCC_WARN_UNUSED_VARIABLE = YES; 417 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 418 | SDKROOT = iphoneos; 419 | TARGETED_DEVICE_FAMILY = "1,2"; 420 | VALIDATE_PRODUCT = YES; 421 | }; 422 | name = Release; 423 | }; 424 | 7402EE19193BB82D00B1C37D /* Debug */ = { 425 | isa = XCBuildConfiguration; 426 | buildSettings = { 427 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 428 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 429 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 430 | GCC_PREFIX_HEADER = "ScrollViewSample/ScrollViewSample-Prefix.pch"; 431 | INFOPLIST_FILE = "ScrollViewSample/ScrollViewSample-Info.plist"; 432 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 433 | PRODUCT_BUNDLE_IDENTIFIER = "Zuse.${PRODUCT_NAME:rfc1034identifier}"; 434 | PRODUCT_NAME = "$(TARGET_NAME)"; 435 | WRAPPER_EXTENSION = app; 436 | }; 437 | name = Debug; 438 | }; 439 | 7402EE1A193BB82D00B1C37D /* Release */ = { 440 | isa = XCBuildConfiguration; 441 | buildSettings = { 442 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 443 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 444 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 445 | GCC_PREFIX_HEADER = "ScrollViewSample/ScrollViewSample-Prefix.pch"; 446 | INFOPLIST_FILE = "ScrollViewSample/ScrollViewSample-Info.plist"; 447 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 448 | PRODUCT_BUNDLE_IDENTIFIER = "Zuse.${PRODUCT_NAME:rfc1034identifier}"; 449 | PRODUCT_NAME = "$(TARGET_NAME)"; 450 | WRAPPER_EXTENSION = app; 451 | }; 452 | name = Release; 453 | }; 454 | 7402EE1C193BB82D00B1C37D /* Debug */ = { 455 | isa = XCBuildConfiguration; 456 | buildSettings = { 457 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/ScrollViewSample.app/ScrollViewSample"; 458 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 459 | GCC_PREFIX_HEADER = "ScrollViewSample/ScrollViewSample-Prefix.pch"; 460 | GCC_PREPROCESSOR_DEFINITIONS = ( 461 | "DEBUG=1", 462 | "$(inherited)", 463 | ); 464 | INFOPLIST_FILE = "ScrollViewSampleTests/ScrollViewSampleTests-Info.plist"; 465 | PRODUCT_BUNDLE_IDENTIFIER = "Zuse.${PRODUCT_NAME:rfc1034identifier}"; 466 | PRODUCT_NAME = "$(TARGET_NAME)"; 467 | TEST_HOST = "$(BUNDLE_LOADER)"; 468 | WRAPPER_EXTENSION = xctest; 469 | }; 470 | name = Debug; 471 | }; 472 | 7402EE1D193BB82D00B1C37D /* Release */ = { 473 | isa = XCBuildConfiguration; 474 | buildSettings = { 475 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/ScrollViewSample.app/ScrollViewSample"; 476 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 477 | GCC_PREFIX_HEADER = "ScrollViewSample/ScrollViewSample-Prefix.pch"; 478 | INFOPLIST_FILE = "ScrollViewSampleTests/ScrollViewSampleTests-Info.plist"; 479 | PRODUCT_BUNDLE_IDENTIFIER = "Zuse.${PRODUCT_NAME:rfc1034identifier}"; 480 | PRODUCT_NAME = "$(TARGET_NAME)"; 481 | TEST_HOST = "$(BUNDLE_LOADER)"; 482 | WRAPPER_EXTENSION = xctest; 483 | }; 484 | name = Release; 485 | }; 486 | /* End XCBuildConfiguration section */ 487 | 488 | /* Begin XCConfigurationList section */ 489 | 7402EDDE193BB82C00B1C37D /* Build configuration list for PBXProject "ScrollViewSample" */ = { 490 | isa = XCConfigurationList; 491 | buildConfigurations = ( 492 | 7402EE16193BB82D00B1C37D /* Debug */, 493 | 7402EE17193BB82D00B1C37D /* Release */, 494 | ); 495 | defaultConfigurationIsVisible = 0; 496 | defaultConfigurationName = Release; 497 | }; 498 | 7402EE18193BB82D00B1C37D /* Build configuration list for PBXNativeTarget "ScrollViewSample" */ = { 499 | isa = XCConfigurationList; 500 | buildConfigurations = ( 501 | 7402EE19193BB82D00B1C37D /* Debug */, 502 | 7402EE1A193BB82D00B1C37D /* Release */, 503 | ); 504 | defaultConfigurationIsVisible = 0; 505 | defaultConfigurationName = Release; 506 | }; 507 | 7402EE1B193BB82D00B1C37D /* Build configuration list for PBXNativeTarget "ScrollViewSampleTests" */ = { 508 | isa = XCConfigurationList; 509 | buildConfigurations = ( 510 | 7402EE1C193BB82D00B1C37D /* Debug */, 511 | 7402EE1D193BB82D00B1C37D /* Release */, 512 | ); 513 | defaultConfigurationIsVisible = 0; 514 | defaultConfigurationName = Release; 515 | }; 516 | /* End XCConfigurationList section */ 517 | }; 518 | rootObject = 7402EDDB193BB82C00B1C37D /* Project object */; 519 | } 520 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample.xcodeproj/project.xcworkspace/xcshareddata/ScrollViewSample.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 83525D6F-F0DB-43F5-A3A3-8B557381452E 9 | IDESourceControlProjectName 10 | ScrollViewSample 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 04AD30FF472F4692B7444AFA771969108DD6AA30 14 | https://github.com/terenceLuffy/AppStoreStyleHorizontalScrollView.git 15 | 16 | IDESourceControlProjectPath 17 | ScrollViewSample/ScrollViewSample.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 04AD30FF472F4692B7444AFA771969108DD6AA30 21 | ../../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/terenceLuffy/AppStoreStyleHorizontalScrollView.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 04AD30FF472F4692B7444AFA771969108DD6AA30 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 04AD30FF472F4692B7444AFA771969108DD6AA30 36 | IDESourceControlWCCName 37 | AppStoreStyleHorizontalScrollView 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample.xcodeproj/project.xcworkspace/xcuserdata/firestorm996.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terenceLuffy/AppStoreStyleHorizontalScrollView/b7bcc134466d923b172d8a96b1e05a0e2240934b/ScrollViewSample/ScrollViewSample.xcodeproj/project.xcworkspace/xcuserdata/firestorm996.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample.xcodeproj/project.xcworkspace/xcuserdata/terencechen.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terenceLuffy/AppStoreStyleHorizontalScrollView/b7bcc134466d923b172d8a96b1e05a0e2240934b/ScrollViewSample/ScrollViewSample.xcodeproj/project.xcworkspace/xcuserdata/terencechen.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample.xcodeproj/xcuserdata/firestorm996.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample.xcodeproj/xcuserdata/firestorm996.xcuserdatad/xcschemes/ScrollViewSample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample.xcodeproj/xcuserdata/firestorm996.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ScrollViewSample.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 7402EDE2193BB82C00B1C37D 16 | 17 | primary 18 | 19 | 20 | 7402EE06193BB82D00B1C37D 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample.xcodeproj/xcuserdata/terencechen.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 20 | 21 | 22 | 24 | 36 | 37 | 38 | 40 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample.xcodeproj/xcuserdata/terencechen.xcuserdatad/xcschemes/ScrollViewSample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample.xcodeproj/xcuserdata/terencechen.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ScrollViewSample.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 7402EDE2193BB82C00B1C37D 16 | 17 | primary 18 | 19 | 20 | 7402EE06193BB82D00B1C37D 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample/ASHorizontalScrollView/ASHorizontalScrollView.h: -------------------------------------------------------------------------------- 1 | /* --------------------------------------------------------- 2 | * ASHorizontalScrollView.h 3 | * The MIT License (MIT) 4 | * Copyright (C) 2014-2016 WEIWEI CHEN 5 | * --------------------------------------------------------- 6 | * History 7 | * Created by WEIWEI CHEN on 14-6-1. 8 | * Edit by WEIWEI CHEN 14-9-21: fix problems to work on xcode 6.0.1 9 | * Edit by WEIWEI CHEN 15-12-09: add comments on functions 10 | * Edit by WEIWEI CHEN 16-05-17: fix removeItemAtIndex last index crash bug 11 | * 12 | * 13 | * 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: 14 | * 15 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | * 17 | * 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. 18 | * -------------------------------------------------------*/ 19 | 20 | #import 21 | #import "ASHorizontalScrollViewDelegate.h" 22 | 23 | @interface ASHorizontalScrollView : UIScrollView 24 | { 25 | //y position of all items 26 | float itemY; 27 | 28 | ASHorizontalScrollViewDelegate *scrollViewdelegate; 29 | } 30 | 31 | /// an array which refer to all added items 32 | @property (strong, nonatomic, readonly) NSMutableArray *items; 33 | 34 | /// the uniform size of all added items, please set it before adding any items, otherwise, default size will be applied 35 | @property (nonatomic)CGSize uniformItemSize; 36 | 37 | /// store the current items' margin 38 | @property (nonatomic, readonly)int itemsMargin; 39 | 40 | /// the margin between left border and first item 41 | @property (nonatomic)float leftMarginPx; 42 | 43 | //the mini margin between items, it is the seed to calculate the actual margin which is not less than 44 | @property (nonatomic)float miniMarginPxBetweenItems; 45 | 46 | /// the mini width appear for last item of current screen, set it 0 if you don't want any part of the last item appear on the right 47 | @property (nonatomic)float miniAppearPxOfLastItem; 48 | 49 | /** 50 | This add a new item into the scrollview 51 | 52 | - parameter item: the item you would like to add, it must not be nil. 53 | */ 54 | - (void) addItem:(UIView*)item; 55 | 56 | /** 57 | This add multi new items into the scrollview 58 | 59 | - parameter items: the items in array you would like to add, it must not be nil. 60 | */ 61 | - (void) addItems:(NSArray*)items; 62 | 63 | /** 64 | It removes the specified item from scrollview 65 | 66 | - parameter item: the item you would like to remove. 67 | 68 | - returns: true if removing successfully. 69 | */ 70 | - (BOOL) removeItem:(UIView*)item; 71 | 72 | /** 73 | It removes the specified item at index from scrollview 74 | 75 | - parameter index: the index of item you would like to remove. 76 | 77 | - returns: true if removing successfully. 78 | */ 79 | - (BOOL) removeItemAtIndex:(NSInteger)index; 80 | 81 | /** 82 | It removes all items from scrollview 83 | 84 | - returns: true if removing successfully. 85 | */ 86 | - (BOOL) removeAllItems; 87 | 88 | /** 89 | It re-calculate the item margin to fit in current view frame 90 | - note: This must be called after changing any size or margin property of this class to get acurrate margin 91 | - seealso: calculateMarginBetweenItems 92 | */ 93 | - (void) setItemsMarginOnce; 94 | @end 95 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample/ASHorizontalScrollView/ASHorizontalScrollView.m: -------------------------------------------------------------------------------- 1 | /* --------------------------------------------------------- 2 | * ASHorizontalScrollView.m 3 | * The MIT License (MIT) 4 | * Copyright (C) 2014-2016 WEIWEI CHEN 5 | * --------------------------------------------------------- 6 | * History 7 | * Created by WEIWEI CHEN on 14-6-1. 8 | * Edit by WEIWEI CHEN 14-9-21: fix problems to work on xcode 6.0.1 9 | * Edit by WEIWEI CHEN 15-12-09: add comments on functions, remove scale when calculating margin, it seems that the behaviour in iOS 9 change the way of align views 10 | * Edit by WEIWEI CHEN 16-05-17: fix removeItemAtIndex last index crash bug 11 | * Edit by WEIWEI CHEN 16-09-15: add support to nib, just change the class on nib file to ASHorizontalScrollView 12 | * 13 | * 14 | * 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: 15 | * 16 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 17 | * 18 | * 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. 19 | * -------------------------------------------------------*/ 20 | 21 | #import "ASHorizontalScrollView.h" 22 | 23 | @implementation ASHorizontalScrollView 24 | 25 | #define kDefaultLeftMargin 5.0f; 26 | #define kMinMarginBetweenItems 10.0f; 27 | #define kMinWidthAppearOfLastItem 20.0f; 28 | 29 | - (id)initWithFrame:(CGRect)frame 30 | { 31 | self = [super initWithFrame:frame]; 32 | if (self) { 33 | [self initView]; 34 | } 35 | return self; 36 | } 37 | 38 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 39 | self = [super initWithCoder:aDecoder]; 40 | if (self) { 41 | [self initView]; 42 | } 43 | return self; 44 | } 45 | 46 | - (void)initView { 47 | _items = [NSMutableArray array]; 48 | 49 | //default item size is 80% of height 50 | _uniformItemSize = CGSizeMake(self.frame.size.height*0.8, self.frame.size.height*0.8); 51 | 52 | //default attributes 53 | _leftMarginPx = kDefaultLeftMargin; 54 | _miniMarginPxBetweenItems = kMinMarginBetweenItems; 55 | _miniAppearPxOfLastItem = kMinWidthAppearOfLastItem; 56 | 57 | //get default item margin 58 | [self setItemsMarginOnce]; 59 | 60 | [self setShowsHorizontalScrollIndicator:NO]; 61 | [self setDecelerationRate:UIScrollViewDecelerationRateFast]; 62 | 63 | scrollViewdelegate = [[ASHorizontalScrollViewDelegate alloc] init]; 64 | self.delegate = scrollViewdelegate; 65 | } 66 | 67 | - (void)setFrame:(CGRect)frame 68 | { 69 | CGRect oldValue = self.frame; 70 | [super setFrame:frame]; 71 | itemY = (frame.size.height-self.uniformItemSize.height)/2; 72 | if(frame.size.width != oldValue.size.width){ 73 | [self refreshSubView]; 74 | } 75 | } 76 | 77 | - (void)setUniformItemSize:(CGSize)uniformItemSize 78 | { 79 | _uniformItemSize = uniformItemSize; 80 | itemY = (self.frame.size.height-uniformItemSize.height)/2; 81 | } 82 | 83 | - (BOOL)touchesShouldCancelInContentView:(UIView *)view { 84 | if ([view isKindOfClass:[UIButton class]]) { 85 | return true; 86 | } 87 | return false; 88 | } 89 | 90 | #pragma mark - add item 91 | - (void)addItem:(UIView*)item 92 | { 93 | //setup new item size and origin 94 | if (self.items.count>0) { 95 | CGRect lastItemRect = ((UIView*)self.items.lastObject).frame; 96 | [item setFrame:CGRectMake(lastItemRect.origin.x + self.uniformItemSize.width + self.itemsMargin, itemY, self.uniformItemSize.width, self.uniformItemSize.height)]; 97 | } 98 | else [item setFrame:CGRectMake(self.leftMarginPx, itemY, self.uniformItemSize.width, self.uniformItemSize.height)]; 99 | 100 | [_items addObject:item]; 101 | [self addSubview:item]; 102 | // set the content size of scroll view to fit new width and with the same margin as left margin 103 | self.contentSize = CGSizeMake(item.frame.origin.x + self.uniformItemSize.width + self.leftMarginPx, self.frame.size.height); 104 | } 105 | 106 | - (void)addItems:(NSArray *)items 107 | { 108 | for (UIView* item in items) { 109 | [self addItem:item]; 110 | } 111 | } 112 | 113 | /// Calculate the exact margin between items 114 | - (int)calculateMarginBetweenItems 115 | { 116 | // CGFloat scale = [UIScreen mainScreen].scale; 117 | //calculate how many items listed on current screen except the last half appearance one 118 | int numberOfItemForCurrentWidth = floorf((self.frame.size.width-self.leftMarginPx-self.miniAppearPxOfLastItem)/(_uniformItemSize.width+self.miniMarginPxBetweenItems)); 119 | 120 | return round((self.frame.size.width-self.leftMarginPx-self.miniAppearPxOfLastItem)/numberOfItemForCurrentWidth - _uniformItemSize.width); 121 | } 122 | 123 | - (void)setItemsMarginOnce 124 | { 125 | _itemsMargin = [self calculateMarginBetweenItems]; 126 | } 127 | 128 | #pragma mark - remove item 129 | - (BOOL) removeAllItems 130 | { 131 | for (long i = self.items.count - 1; i >= 0; i--) { 132 | UIView *item = self.items[i]; 133 | [item removeFromSuperview]; 134 | } 135 | [self.items removeAllObjects]; 136 | self.contentSize = CGSizeMake(self.contentSize.width-self.itemsMargin-self.uniformItemSize.width, self.frame.size.height); 137 | 138 | return true; 139 | } 140 | 141 | - (BOOL)removeItem:(UIView *)item 142 | { 143 | NSInteger index = [self.items indexOfObject:item]; 144 | if (index != NSNotFound) { 145 | return [self removeItemAtIndex:index]; 146 | } 147 | else return false; 148 | } 149 | 150 | - (BOOL)removeItemAtIndex:(NSInteger)index 151 | { 152 | if (index < 0 || index > self.items.count-1) return false; 153 | //set new x position from index to the end 154 | if (index != self.items.count-1) { 155 | for (NSInteger i = self.items.count-1; i > index; i--) { 156 | UIView *item = [self.items objectAtIndex:i]; 157 | item.frame = CGRectMake(CGRectGetMinX(item.frame)-self.itemsMargin-self.uniformItemSize.width, CGRectGetMinY(item.frame), CGRectGetWidth(item.frame), CGRectGetHeight(item.frame)); 158 | } 159 | } 160 | UIView *item = [self.items objectAtIndex:index]; 161 | [item removeFromSuperview]; 162 | [self.items removeObjectAtIndex:index]; 163 | self.contentSize = CGSizeMake(self.contentSize.width-self.itemsMargin-self.uniformItemSize.width, self.frame.size.height); 164 | 165 | return true; 166 | } 167 | 168 | /// Refresh all subviews for changing size of current frame 169 | - (void) refreshSubView 170 | { 171 | [self setItemsMarginOnce]; 172 | float itemX = self.leftMarginPx; 173 | for (UIView *item in self.items) { 174 | item.frame = CGRectMake(itemX, item.frame.origin.y, item.frame.size.width, item.frame.size.height); 175 | itemX += item.frame.size.width + self.itemsMargin; 176 | } 177 | itemX = itemX - self.itemsMargin + self.leftMarginPx; 178 | self.contentSize = CGSizeMake(itemX, self.frame.size.height); 179 | } 180 | 181 | 182 | @end 183 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample/ASHorizontalScrollView/ASHorizontalScrollViewDelegate.h: -------------------------------------------------------------------------------- 1 | /* --------------------------------------------------------- 2 | * ASHorizontalScrollViewDelegate.h 3 | * The MIT License (MIT) 4 | * Copyright (C) 2014-2015 WEIWEI CHEN 5 | * --------------------------------------------------------- 6 | * History 7 | * Created by WEIWEI CHEN on 14-6-2. 8 | * 9 | * 10 | * 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: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | * 14 | * 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. 15 | * -------------------------------------------------------*/ 16 | 17 | #import 18 | 19 | @interface ASHorizontalScrollViewDelegate : NSObject 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample/ASHorizontalScrollView/ASHorizontalScrollViewDelegate.m: -------------------------------------------------------------------------------- 1 | /* --------------------------------------------------------- 2 | * ASHorizontalScrollViewDelegate.m 3 | * The MIT License (MIT) 4 | * Copyright (C) 2014-2015 WEIWEI CHEN 5 | * --------------------------------------------------------- 6 | * History 7 | * Created by WEIWEI CHEN on 14-6-2. 8 | * Edit by WEIWEI CHEN 16-09-15: avoid cloeset index smaller than 0 when only one item showed in screen 9 | * 10 | * 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: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | * 14 | * 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. 15 | * -------------------------------------------------------*/ 16 | 17 | #import "ASHorizontalScrollViewDelegate.h" 18 | #import "ASHorizontalScrollView.h" 19 | 20 | @implementation ASHorizontalScrollViewDelegate 21 | - (void)scrollViewWillEndDragging:(ASHorizontalScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset 22 | { 23 | if (targetContentOffset->x + scrollView.frame.size.width < scrollView.contentSize.width) { 24 | targetContentOffset->x = [self getClosestItemByX:targetContentOffset->x inScrollView:scrollView] - scrollView.leftMarginPx; 25 | } 26 | } 27 | 28 | - (float)getClosestItemByX:(float)xPosition inScrollView:(ASHorizontalScrollView*)scrollView 29 | { 30 | //get current cloest item on the left side 31 | int index = (int)((xPosition - scrollView.leftMarginPx)/(scrollView.itemsMargin+scrollView.uniformItemSize.width)); 32 | if (index < 0) { 33 | index = 0; 34 | } 35 | UIView *item = [scrollView.items objectAtIndex:index]; 36 | //check if target position is over half of current left item, if so, move to next item 37 | if (xPosition-item.frame.origin.x>item.frame.size.width/2) { 38 | item = [scrollView.items objectAtIndex:index+1]; 39 | //check if target position plus scroll view width over content width, if so, move back to last item 40 | if (item.frame.origin.x + scrollView.frame.size.width > scrollView.contentSize.width) { 41 | item = [scrollView.items objectAtIndex:index]; 42 | } 43 | } 44 | 45 | return item.frame.origin.x; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample/ASHorizontalScrollView/license.txt: -------------------------------------------------------------------------------- 1 | ASHorizontalScrollView 2 | Copyright (c) 2014-2015 WEIWEI CHEN 3 | 4 | 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: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | 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. -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample/Base.lproj/Main_iPad.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 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample/Base.lproj/Main_iPhone.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 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample/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 | "idiom" : "ipad", 20 | "size" : "29x29", 21 | "scale" : "1x" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "size" : "29x29", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "size" : "40x40", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "40x40", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "76x76", 41 | "scale" : "1x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "76x76", 46 | "scale" : "2x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample/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 | "orientation" : "portrait", 20 | "idiom" : "ipad", 21 | "extent" : "full-screen", 22 | "minimum-system-version" : "7.0", 23 | "scale" : "1x" 24 | }, 25 | { 26 | "orientation" : "landscape", 27 | "idiom" : "ipad", 28 | "extent" : "full-screen", 29 | "minimum-system-version" : "7.0", 30 | "scale" : "1x" 31 | }, 32 | { 33 | "orientation" : "portrait", 34 | "idiom" : "ipad", 35 | "extent" : "full-screen", 36 | "minimum-system-version" : "7.0", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "orientation" : "landscape", 41 | "idiom" : "ipad", 42 | "extent" : "full-screen", 43 | "minimum-system-version" : "7.0", 44 | "scale" : "2x" 45 | } 46 | ], 47 | "info" : { 48 | "version" : 1, 49 | "author" : "xcode" 50 | } 51 | } -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample/SAAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // SAAppDelegate.h 3 | // ScrollViewSample 4 | // 5 | // Created by Vivien on 2014-06-01. 6 | // Copyright (c) 2014 Zuse. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SAAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample/SAAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // SAAppDelegate.m 3 | // ScrollViewSample 4 | // 5 | // Created by Vivien on 2014-06-01. 6 | // Copyright (c) 2014 Zuse. All rights reserved. 7 | // 8 | 9 | #import "SAAppDelegate.h" 10 | 11 | @implementation SAAppDelegate 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 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample/SAViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SAViewController.h 3 | // ScrollViewSample 4 | // 5 | // Created by Vivien on 2014-06-01. 6 | // Copyright (c) 2014 Zuse. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ASHorizontalScrollView.h" 11 | 12 | @interface SAViewController : UIViewController 13 | { 14 | UITableView *sampleTableView; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample/SAViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SAViewController.m 3 | // ScrollViewSample 4 | // 5 | // Created by Vivien on 2014-06-01. 6 | // Copyright (c) 2014 Zuse. All rights reserved. 7 | // 8 | 9 | #import "SAViewController.h" 10 | 11 | @interface SAViewController () 12 | 13 | @end 14 | 15 | @implementation SAViewController 16 | 17 | const float kCellHeight = 60.0f; 18 | 19 | - (void)viewDidLoad 20 | { 21 | [super viewDidLoad]; 22 | 23 | //create table view to contain ASHorizontalScrollView 24 | sampleTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 20, self.view.frame.size.width, self.view.frame.size.height)]; 25 | sampleTableView.delegate = self; 26 | sampleTableView.dataSource = self; 27 | sampleTableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 28 | [self.view addSubview:sampleTableView]; 29 | 30 | } 31 | 32 | - (void)didReceiveMemoryWarning 33 | { 34 | [super didReceiveMemoryWarning]; 35 | // Dispose of any resources that can be recreated. 36 | } 37 | 38 | - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation 39 | { 40 | [super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; 41 | if(UIDeviceOrientationIsLandscape([[UIDevice currentDevice] orientation]))sampleTableView.frame = CGRectMake(0, 0, sampleTableView.frame.size.width, sampleTableView.frame.size.height); 42 | else sampleTableView.frame = CGRectMake(0, 20, sampleTableView.frame.size.width, sampleTableView.frame.size.height); 43 | 44 | [sampleTableView reloadData]; 45 | } 46 | 47 | #pragma tableview datasource 48 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 49 | { 50 | return 1; 51 | } 52 | 53 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 54 | { 55 | return 2; 56 | } 57 | 58 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 59 | { 60 | static NSString *CellIdentifierPortrait = @"CellPortrait"; 61 | static NSString *CellIdentifierLandscape = @"CellLandscape"; 62 | NSString *indentifier = self.view.frame.size.width > self.view.frame.size.height ? CellIdentifierLandscape : CellIdentifierPortrait; 63 | 64 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:indentifier]; 65 | 66 | if (!cell) 67 | { 68 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:indentifier]; 69 | cell.selectionStyle = UITableViewCellSelectionStyleNone; 70 | 71 | ASHorizontalScrollView *horizontalScrollView = [[ASHorizontalScrollView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, kCellHeight)]; 72 | if (indexPath.row == 0) { 73 | horizontalScrollView.miniAppearPxOfLastItem = 10; 74 | //sample code of how to use this scroll view 75 | horizontalScrollView.uniformItemSize = CGSizeMake(50, 50); 76 | //this must be called after changing any size or margin property of this class to get acurrate margin 77 | [horizontalScrollView setItemsMarginOnce]; 78 | //create 20 buttons for cell 1 79 | NSMutableArray *buttons = [NSMutableArray array]; 80 | for (int i=1; i<20; i++) { 81 | UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero]; 82 | label.backgroundColor = [UIColor blueColor]; 83 | [buttons addObject:label]; 84 | } 85 | [horizontalScrollView addItems:buttons]; 86 | } 87 | else if (indexPath.row == 1) { 88 | horizontalScrollView.miniAppearPxOfLastItem = 20; 89 | //setup the uniform size for all added items 90 | horizontalScrollView.uniformItemSize = CGSizeMake(90, 50); 91 | //this must be called after changing any size or margin property of this class to get acurrate margin 92 | [horizontalScrollView setItemsMarginOnce]; 93 | 94 | NSMutableArray *buttons = [NSMutableArray array]; 95 | for (int i=1; i<21; i++) { 96 | UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero]; 97 | label.backgroundColor = [UIColor purpleColor]; 98 | [buttons addObject:label]; 99 | } 100 | [horizontalScrollView addItems:buttons]; 101 | // [horizontalScrollView removeItemAtIndex:0]; 102 | // [horizontalScrollView removeItemAtIndex:1]; 103 | } 104 | 105 | [cell.contentView addSubview:horizontalScrollView]; 106 | horizontalScrollView.translatesAutoresizingMaskIntoConstraints = false; 107 | [cell.contentView addConstraint:[NSLayoutConstraint constraintWithItem:horizontalScrollView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:kCellHeight]]; 108 | [cell.contentView addConstraint:[NSLayoutConstraint constraintWithItem:horizontalScrollView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:cell.contentView attribute:NSLayoutAttributeWidth multiplier:1 constant:0]]; 109 | } 110 | 111 | 112 | 113 | return cell; 114 | } 115 | 116 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 117 | { 118 | return kCellHeight; 119 | } 120 | 121 | 122 | 123 | @end 124 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample/ScrollViewSample-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | Main_iPad 29 | UIMainStoryboardFile 30 | Main_iPhone 31 | UIMainStoryboardFile~ipad 32 | Main_iPad 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | UISupportedInterfaceOrientations~ipad 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationPortraitUpsideDown 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample/ScrollViewSample-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 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSample/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ScrollViewSample 4 | // 5 | // Created by Vivien on 2014-06-01. 6 | // Copyright (c) 2014 Zuse. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "SAAppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([SAAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSampleTests/ScrollViewSampleTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSampleTests/ScrollViewSampleTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollViewSampleTests.m 3 | // ScrollViewSampleTests 4 | // 5 | // Created by Vivien on 2014-06-01. 6 | // Copyright (c) 2014 Zuse. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ScrollViewSampleTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation ScrollViewSampleTests 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 | -------------------------------------------------------------------------------- /ScrollViewSample/ScrollViewSampleTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Sources/ASHorizontalScrollView.swift: -------------------------------------------------------------------------------- 1 | /* --------------------------------------------------------- 2 | * ASHorizontalScrollView.swift 3 | * The MIT License (MIT) 4 | * Copyright (C) 2014-Current WEIWEI CHEN 5 | * --------------------------------------------------------- 6 | * History 7 | * Created by WEIWEI CHEN on 14-6-8. 8 | * Edit by WEIWEI CHEN 14-9-21: fix problems to work on xcode 6.0.1 9 | * Edit by WEIWEI CHEN 15-12-09: change to adapt Swift 2.1, add comments on functions, remove scale when calculating margin, it seems that the behaviour in iOS 9 change the way of align views 10 | * Edit by WEIWEI CHEN 16-05-17: change C style code to swift 3 format, fix removeItemAtIndex last index crash bug 11 | * Edit by WEIWEI CHEN 16-09-15: Change to adapt Swift 3 with Xcode 8, add support to nib, just change the class on nib file to ASHorizontalScrollView 12 | * Edit by WEIWEI CHEN 16-12-02: When current item scroll to more than specified width, auto scroll to next item (mimic App Store behaviour which is about 1/3); add support to all apple screen sizes, now you can specified different mini margin, mini appear width and left margin for all sorts of screen sizes. 13 | * Edit by WEIWEI CHEN 17-01-24: Introduce new properties to allow set number of items per screen for multiple screen sizes instead of setting minimum margins, as well as new properties to center subviews when items are not wide enough to use whole screen width 14 | * Edit by WEIWEI CHEN 17-03-03: check items size before removing all items 15 | * Edit by WEIWEI CHEN 18-10-17: migrate to Swift 4.2 and add settings for iPhone X, Xs, XR and Xs Max landscape support, thanks to Anton Dryakhlykh help on the migration. 16 | * 17 | * 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: 18 | * 19 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 20 | * 21 | * 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. 22 | * -------------------------------------------------------*/ 23 | 24 | import UIKit 25 | 26 | 27 | /// settings for margins 28 | public struct MarginSettings { 29 | /// the margin between left border and first item 30 | public var leftMargin:CGFloat = 5.0 31 | 32 | /// the mini margin between items, it is the seed to calculate the actual margin which is not less than 33 | public var miniMarginBetweenItems:CGFloat = 10.0 34 | 35 | /// the mini width appear for last item of current screen, set it 0 if you don't want any part of the last item appear on the right 36 | public var miniAppearWidthOfLastItem:CGFloat = 20.0 37 | 38 | /// number of items per screen, it can be integer like 3, that means total 3 items occupy whole screen, 4.5 means total 4 items and one half item show on the right end. Please note that if numberOfItemsPerScreen is more than screen width, the maximum allowed number of items per screen would be calculated by left margin, and last appeared item percentage which is determined by the fractional number of this value 39 | public var numberOfItemsPerScreen:Float = 0 40 | 41 | public init() { } 42 | 43 | 44 | /// Use this to set margin if arrange type is set by frame 45 | /// 46 | /// - Parameters: 47 | /// - leftMargin: the margin between left border and first item 48 | /// - miniMarginBetweenItems: the mini margin between items, it is the seed to calculate the actual margin which is not less than 49 | /// - miniAppearWidthOfLastItem: the mini width appear for last item of current screen, set it 0 if you don't want any part of the last item appear on the right 50 | public init(leftMargin:CGFloat, miniMarginBetweenItems:CGFloat, miniAppearWidthOfLastItem:CGFloat) { 51 | self.leftMargin = leftMargin 52 | self.miniMarginBetweenItems = miniMarginBetweenItems 53 | self.miniAppearWidthOfLastItem = miniAppearWidthOfLastItem 54 | } 55 | 56 | 57 | /// Use this to set margin if arrange type is set by number per screen 58 | /// 59 | /// - Parameters: 60 | /// - leftMargin: the margin between left border and first item 61 | /// - numberOfItemsPerScreen: number of items per screen, it can be integer like 3, that means total 3 items occupy whole screen, 4.5 means total 4 items and one half item show on the right end. 62 | /// - note: if numberOfItemsPerScreen is more than screen width, the maximum allowed number of items per screen would be calculated by left margin, and last appeared item percentage which is determined by the fractional number of this value 63 | public init(leftMargin:CGFloat, numberOfItemsPerScreen:Float) { 64 | self.leftMargin = leftMargin 65 | self.numberOfItemsPerScreen = (numberOfItemsPerScreen >= 0 ? numberOfItemsPerScreen : 0) 66 | } 67 | } 68 | 69 | public enum ArrangeType { 70 | case byFrame 71 | case byNumber 72 | } 73 | 74 | open class ASHorizontalScrollView: UIScrollView, UIScrollViewDelegate { 75 | // MARK: - properties 76 | override open var frame: CGRect{ 77 | didSet{ 78 | itemY = (frame.size.height-self.uniformItemSize.height)/2 79 | //if width changes, then need to get new margin and reset all views 80 | if(frame.width != oldValue.width){ 81 | self.refreshSubView() 82 | } 83 | } 84 | } 85 | 86 | 87 | /// whether to arrange items by frame or by number of items, if set by frame, all margin would be calculated by frame size, otherwise, calculated by number of items per screen 88 | /// - check `numberOfItemsPerScreen` for arranged by number type 89 | open var arrangeType:ArrangeType = .byFrame 90 | /// y position of all items 91 | open var itemY: CGFloat = 0 92 | /// an array which refer to all added items 93 | open var items: [UIView] = [] 94 | /// center subviews when items do not occupy whole screen 95 | public var shouldCenterSubViews:Bool = false 96 | 97 | /// the uniform size of all added items, please set it before adding any items, otherwise, default size will be applied 98 | open var uniformItemSize:CGSize = CGSize.zero { 99 | didSet{ 100 | itemY = (frame.size.height-self.uniformItemSize.height)/2 101 | } 102 | } 103 | /// the width to move to next item when target point which stops at an item is larger or equal than, default value is 1/3 that means, for example, if scrolling stops at half of an item, auto scroll to next item 104 | public var widthToScrollNextItem:CGFloat = 1/3 105 | 106 | public var marginSettings:MarginSettings { 107 | get { 108 | switch UIScreen.main.bounds.width { 109 | //for portrait 110 | case 320: 111 | if let setting = marginSettings_320 { 112 | return setting 113 | } 114 | case 375: 115 | if let setting = marginSettings_375 { 116 | return setting 117 | } 118 | case 414: 119 | if let setting = marginSettings_414 { 120 | return setting 121 | } 122 | case 768: 123 | if let setting = marginSettings_768 { 124 | return setting 125 | } 126 | case 1024: 127 | if let setting = marginSettings_1024 { 128 | return setting 129 | } 130 | //for landscape 131 | case 480: 132 | if let setting = marginSettings_480 { 133 | return setting 134 | } 135 | case 568: 136 | if let setting = marginSettings_568 { 137 | return setting 138 | } 139 | case 667: 140 | if let setting = marginSettings_667 { 141 | return setting 142 | } 143 | case 736: 144 | if let setting = marginSettings_736 { 145 | return setting 146 | } 147 | case 812: 148 | if let setting = marginSettings_812 { 149 | return setting 150 | } 151 | case 896: 152 | if let setting = marginSettings_896 { 153 | return setting 154 | } 155 | case 1366: 156 | if let setting = marginSettings_1366 { 157 | return setting 158 | } 159 | default: 160 | break 161 | } 162 | 163 | return defaultMarginSettings 164 | } 165 | } 166 | 167 | /// the default setting used if you don't set other margin settings for specific screen size 168 | public var defaultMarginSettings = MarginSettings() 169 | /// for iPhone 5s and lower versions in portrait 170 | public var marginSettings_320:MarginSettings? 171 | /// for iPhone 6 and 6s in portrait 172 | public var marginSettings_375:MarginSettings? 173 | /// for iPhone 6 plus and 6s plus in portrait 174 | public var marginSettings_414:MarginSettings? 175 | /// for ipad in portrait 176 | public var marginSettings_768:MarginSettings? 177 | 178 | /// for iPhone 4s and lower versions in landscape 179 | public var marginSettings_480:MarginSettings? 180 | /// for iPhone 5 and 5s in landscape 181 | public var marginSettings_568:MarginSettings? 182 | /// for iPhone 6 and 6s in landscape 183 | public var marginSettings_667:MarginSettings? 184 | /// for iPhone 6 plus and 6s plus in landscape 185 | public var marginSettings_736:MarginSettings? 186 | /// for iPhone X and Xs in landscape 187 | public var marginSettings_812:MarginSettings? 188 | /// for iPhone Xs Max and XR in landscape 189 | public var marginSettings_896:MarginSettings? 190 | /// for ipad and ipad pro 9.7 in landscape and ipad pro 12.9 portrait 191 | public var marginSettings_1024:MarginSettings? 192 | /// for ipad pro 12.9 in landscape 193 | public var marginSettings_1366:MarginSettings? 194 | 195 | /// store the current items' margin 196 | open var itemsMargin:CGFloat = 10.0 197 | 198 | /// the margin between left border and first item 199 | open var leftMarginPx:CGFloat { 200 | get { 201 | return self.marginSettings.leftMargin 202 | } 203 | } 204 | 205 | /// the mini margin between items, it is the seed to calculate the actual margin which is not less than 206 | open var miniMarginPxBetweenItems:CGFloat { 207 | get { 208 | return self.marginSettings.miniMarginBetweenItems 209 | } 210 | } 211 | 212 | /// the mini width appear for last item of current screen, set it 0 if you don't want any part of the last item appear on the right 213 | open var miniAppearPxOfLastItem:CGFloat { 214 | get { 215 | return self.marginSettings.miniAppearWidthOfLastItem 216 | } 217 | } 218 | 219 | /// number of items per screen, it can be integer like 3, that means total 3 items occupy whole screen, 4.5 means total 4 items and one half item show on the right end 220 | open var numberOfItemsPerScreen:Float { 221 | get { 222 | return self.marginSettings.numberOfItemsPerScreen 223 | } 224 | } 225 | 226 | // MARK: - view init 227 | public override init(frame: CGRect) { 228 | super.init(frame: frame) 229 | initView() 230 | } 231 | 232 | required public init?(coder aDecoder: NSCoder) { 233 | super.init(coder: aDecoder) 234 | initView() 235 | } 236 | 237 | fileprivate func initView() { 238 | //default item size is 80% of height 239 | self.uniformItemSize = CGSize(width: frame.size.height*0.8, height: frame.size.height*0.8) 240 | 241 | self.showsHorizontalScrollIndicator = false 242 | self.decelerationRate = UIScrollView.DecelerationRate.fast 243 | self.delegate = self 244 | } 245 | 246 | override open func touchesShouldCancel(in view: UIView) -> Bool { 247 | if view.isKind(of: UIButton.self) { 248 | return true 249 | } 250 | return false 251 | } 252 | // MARK: - methods 253 | /** 254 | It re-calculate the item margin to fit in current view frame 255 | - note: This must be called after changing any size or margin property of this class to get acurrate margin 256 | - seealso: calculateMarginBetweenItems 257 | */ 258 | open func setItemsMarginOnce() 259 | { 260 | self.itemsMargin = self.calculateMarginBetweenItems(); 261 | } 262 | 263 | /// Calculate the exact margin between items 264 | open func calculateMarginBetweenItems() -> CGFloat 265 | { 266 | if self.arrangeType == .byFrame { 267 | return calculateMarginByFrame() 268 | } 269 | else { 270 | return calculateMarginByNumberPerScreen() 271 | } 272 | } 273 | 274 | /// Calculate the exact margin by frame 275 | open func calculateMarginByFrame() -> CGFloat { 276 | //calculate how many items listed on current screen except the last half appearance one 277 | let numberOfItemForCurrentWidth = floorf(Float((self.frame.size.width-self.leftMarginPx-self.miniAppearPxOfLastItem)/(self.uniformItemSize.width+self.miniMarginPxBetweenItems))) 278 | return (self.frame.size.width-self.leftMarginPx-self.miniAppearPxOfLastItem)/CGFloat(numberOfItemForCurrentWidth) - self.uniformItemSize.width 279 | } 280 | 281 | /// Calculate the exact margin by number of items per screen 282 | open func calculateMarginByNumberPerScreen() -> CGFloat { 283 | let numOfFull = Int(self.numberOfItemsPerScreen) 284 | if numOfFull <= 0 {// if margin is not set for this screen width, use calculation by frame instead 285 | return calculateMarginByFrame() 286 | } 287 | let lastItemPercentage = self.numberOfItemsPerScreen - Float(numOfFull) 288 | var margin = (self.frame.size.width-self.leftMarginPx-self.uniformItemSize.width * CGFloat(lastItemPercentage))/CGFloat(numOfFull) - self.uniformItemSize.width 289 | if margin <= 0 {//in such case, the number per screen width is larger than the screen width, calculate the max allowed number per screen using the left margin, fractional value of numberOfItemsPerScreen and mini margin between items 290 | let numberOfItemForCurrentWidth = floorf(Float((self.frame.size.width-self.leftMarginPx-self.uniformItemSize.width * CGFloat(lastItemPercentage))/(self.uniformItemSize.width+self.miniMarginPxBetweenItems))) 291 | margin = (self.frame.size.width-self.leftMarginPx-self.uniformItemSize.width * CGFloat(lastItemPercentage))/CGFloat(numberOfItemForCurrentWidth) - self.uniformItemSize.width 292 | } 293 | 294 | return margin 295 | } 296 | 297 | /** 298 | This add a new item into the scrollview 299 | 300 | - parameter item: the item you would like to add, it must not be nil. 301 | */ 302 | open func addItem(_ item:UIView) 303 | { 304 | //setup new item size and origin 305 | if (self.items.count>0) { 306 | let lastItemRect:CGRect = self.items[self.items.count-1].frame; 307 | item.frame = CGRect(x: lastItemRect.origin.x + self.uniformItemSize.width + self.itemsMargin, y: itemY, width: self.uniformItemSize.width, height: self.uniformItemSize.height) 308 | } 309 | else { 310 | item.frame = CGRect(x: self.leftMarginPx, y: itemY, width: self.uniformItemSize.width, height: self.uniformItemSize.height) 311 | } 312 | 313 | items.append(item); 314 | self.addSubview(item); 315 | // set the content size of scroll view to fit new width and with the same margin as left margin 316 | self.contentSize = CGSize(width: item.frame.origin.x + self.uniformItemSize.width + self.leftMarginPx, height: self.frame.size.height); 317 | } 318 | 319 | /** 320 | This add multi new items into the scrollview 321 | 322 | - parameter items: the items in array you would like to add, it must not be nil. 323 | */ 324 | open func addItems(_ items:[UIView]) 325 | { 326 | for item in items { 327 | self.addItem(item) 328 | } 329 | } 330 | 331 | /** 332 | It removes the specified item from scrollview 333 | 334 | - parameter item: the item you would like to remove. 335 | 336 | - returns: true if removing successfully. 337 | */ 338 | open func removeItem(_ item:UIView) -> Bool 339 | { 340 | guard let index = self.items.index(of: item) else { 341 | return false 342 | } 343 | 344 | return self.removeItemAtIndex(index) 345 | } 346 | 347 | /** 348 | It removes all items from scrollview 349 | 350 | - returns: true if removing successfully. 351 | */ 352 | open func removeAllItems()->Bool 353 | { 354 | if self.items.count == 0 { 355 | return false 356 | } 357 | 358 | for i in (0...self.items.count-1).reversed() { 359 | let item:UIView = self.items[i] 360 | item.removeFromSuperview() 361 | } 362 | self.items.removeAll(keepingCapacity: false) 363 | self.contentSize = self.frame.size 364 | 365 | return true 366 | } 367 | 368 | /** 369 | It removes the specified item at index from scrollview 370 | 371 | - parameter index: the index of item you would like to remove. 372 | 373 | - returns: true if removing successfully. 374 | */ 375 | open func removeItemAtIndex(_ index:Int)->Bool 376 | { 377 | if (index < 0 || index > self.items.count-1) {return false} 378 | //set new x position from index to the end 379 | if index != self.items.count-1{ 380 | for i in (index+1...self.items.count-1).reversed() { 381 | let item:UIView = self.items[i] 382 | item.frame = CGRect(x: item.frame.minX-self.itemsMargin-self.uniformItemSize.width, y: item.frame.minY, width: item.frame.width, height: item.frame.height) 383 | } 384 | } 385 | 386 | let item:UIView = self.items[index] 387 | item.removeFromSuperview() 388 | self.items.remove(at: index) 389 | self.contentSize = CGSize(width: self.contentSize.width-self.itemsMargin-self.uniformItemSize.width, height: self.frame.size.height) 390 | 391 | return true 392 | } 393 | 394 | /// Refresh all subviews for changing size of current frame 395 | public func refreshSubView() 396 | { 397 | self.setItemsMarginOnce(); 398 | var itemX = self.leftMarginPx 399 | if self.shouldCenterSubViews { 400 | itemX = centerSubviews() 401 | } 402 | else { 403 | itemX = self.reorderSubViews() 404 | } 405 | self.contentSize = CGSize(width: itemX, height: self.frame.size.height) 406 | } 407 | 408 | private func reorderSubViews() -> CGFloat { 409 | var itemX = self.leftMarginPx 410 | for item in self.items 411 | { 412 | item.frame = CGRect(x: itemX, y: item.frame.origin.y, width: item.frame.width, height: item.frame.height) 413 | itemX += item.frame.width + self.itemsMargin 414 | } 415 | 416 | return itemX - self.itemsMargin + self.leftMarginPx; 417 | } 418 | 419 | 420 | /// center subviews if all items can not fully occupy whole screen width 421 | /// 422 | /// - Returns: the scroll view content width 423 | public func centerSubviews() -> CGFloat{ 424 | if let itemLastX = self.items.last?.frame.maxX { 425 | if itemLastX + self.leftMarginPx < self.frame.size.width { 426 | let extraGap = (self.frame.size.width - (self.itemsMargin + self.uniformItemSize.width) * CGFloat(self.items.count) + self.itemsMargin - self.leftMarginPx * 2) / 2 427 | var itemX = self.leftMarginPx + extraGap 428 | for item in self.items 429 | { 430 | item.frame = CGRect(x: itemX, y: item.frame.origin.y, width: item.frame.width, height: item.frame.height) 431 | itemX += item.frame.width + self.itemsMargin 432 | } 433 | return itemX - self.itemsMargin + self.leftMarginPx + extraGap; 434 | } 435 | return self.reorderSubViews() 436 | } 437 | return 0 438 | } 439 | 440 | // MARK: - ScrollView delegates 441 | open func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { 442 | let targetOffset:CGPoint = targetContentOffset.pointee; 443 | //move to closest item 444 | if (targetOffset.x + scrollView.frame.size.width < scrollView.contentSize.width) { 445 | targetContentOffset.pointee.x = self.getClosestItemByX(position:targetOffset.x, inScrollView:(scrollView as! ASHorizontalScrollView)) - (scrollView as! ASHorizontalScrollView).leftMarginPx; 446 | } 447 | } 448 | 449 | fileprivate func getClosestItemByX(position xPosition:CGFloat, inScrollView scrollView:ASHorizontalScrollView) -> CGFloat 450 | { 451 | //get current cloest item on the left side 452 | var index = (Int)((xPosition - scrollView.leftMarginPx)/(scrollView.itemsMargin+scrollView.uniformItemSize.width)) 453 | if index < 0 { 454 | index = 0 455 | } 456 | var item:UIView = scrollView.items[index] 457 | //check if target position is over widthToScrollNextItem of current left item, if so, move to next item 458 | if (xPosition-item.frame.origin.x > item.frame.size.width * widthToScrollNextItem) { 459 | item = scrollView.items[index+1] 460 | } 461 | 462 | return item.frame.origin.x 463 | } 464 | 465 | 466 | } 467 | -------------------------------------------------------------------------------- /Sources/ASHorizontalScrollView/ASHorizontalScrollView.h: -------------------------------------------------------------------------------- 1 | /* --------------------------------------------------------- 2 | * ASHorizontalScrollView.h 3 | * The MIT License (MIT) 4 | * Copyright (C) 2014-2016 WEIWEI CHEN 5 | * --------------------------------------------------------- 6 | * History 7 | * Created by WEIWEI CHEN on 14-6-1. 8 | * Edit by WEIWEI CHEN 14-9-21: fix problems to work on xcode 6.0.1 9 | * Edit by WEIWEI CHEN 15-12-09: add comments on functions 10 | * Edit by WEIWEI CHEN 16-05-17: fix removeItemAtIndex last index crash bug 11 | * 12 | * 13 | * 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: 14 | * 15 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | * 17 | * 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. 18 | * -------------------------------------------------------*/ 19 | 20 | #import 21 | #import "ASHorizontalScrollViewDelegate.h" 22 | 23 | @interface ASHorizontalScrollView : UIScrollView 24 | { 25 | //y position of all items 26 | float itemY; 27 | 28 | ASHorizontalScrollViewDelegate *scrollViewdelegate; 29 | } 30 | 31 | /// an array which refer to all added items 32 | @property (strong, nonatomic, readonly) NSMutableArray *items; 33 | 34 | /// the uniform size of all added items, please set it before adding any items, otherwise, default size will be applied 35 | @property (nonatomic)CGSize uniformItemSize; 36 | 37 | /// store the current items' margin 38 | @property (nonatomic, readonly)int itemsMargin; 39 | 40 | /// the margin between left border and first item 41 | @property (nonatomic)float leftMarginPx; 42 | 43 | //the mini margin between items, it is the seed to calculate the actual margin which is not less than 44 | @property (nonatomic)float miniMarginPxBetweenItems; 45 | 46 | /// the mini width appear for last item of current screen, set it 0 if you don't want any part of the last item appear on the right 47 | @property (nonatomic)float miniAppearPxOfLastItem; 48 | 49 | /** 50 | This add a new item into the scrollview 51 | 52 | - parameter item: the item you would like to add, it must not be nil. 53 | */ 54 | - (void) addItem:(UIView*)item; 55 | 56 | /** 57 | This add multi new items into the scrollview 58 | 59 | - parameter items: the items in array you would like to add, it must not be nil. 60 | */ 61 | - (void) addItems:(NSArray*)items; 62 | 63 | /** 64 | It removes the specified item from scrollview 65 | 66 | - parameter item: the item you would like to remove. 67 | 68 | - returns: true if removing successfully. 69 | */ 70 | - (BOOL) removeItem:(UIView*)item; 71 | 72 | /** 73 | It removes the specified item at index from scrollview 74 | 75 | - parameter index: the index of item you would like to remove. 76 | 77 | - returns: true if removing successfully. 78 | */ 79 | - (BOOL) removeItemAtIndex:(NSInteger)index; 80 | 81 | /** 82 | It removes all items from scrollview 83 | 84 | - returns: true if removing successfully. 85 | */ 86 | - (BOOL) removeAllItems; 87 | 88 | /** 89 | It re-calculate the item margin to fit in current view frame 90 | - note: This must be called after changing any size or margin property of this class to get acurrate margin 91 | - seealso: calculateMarginBetweenItems 92 | */ 93 | - (void) setItemsMarginOnce; 94 | @end 95 | -------------------------------------------------------------------------------- /Sources/ASHorizontalScrollView/ASHorizontalScrollView.m: -------------------------------------------------------------------------------- 1 | /* --------------------------------------------------------- 2 | * ASHorizontalScrollView.m 3 | * The MIT License (MIT) 4 | * Copyright (C) 2014-2016 WEIWEI CHEN 5 | * --------------------------------------------------------- 6 | * History 7 | * Created by WEIWEI CHEN on 14-6-1. 8 | * Edit by WEIWEI CHEN 14-9-21: fix problems to work on xcode 6.0.1 9 | * Edit by WEIWEI CHEN 15-12-09: add comments on functions, remove scale when calculating margin, it seems that the behaviour in iOS 9 change the way of align views 10 | * Edit by WEIWEI CHEN 16-05-17: fix removeItemAtIndex last index crash bug 11 | * Edit by WEIWEI CHEN 16-09-15: add support to nib, just change the class on nib file to ASHorizontalScrollView 12 | * 13 | * 14 | * 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: 15 | * 16 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 17 | * 18 | * 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. 19 | * -------------------------------------------------------*/ 20 | 21 | #import "ASHorizontalScrollView.h" 22 | 23 | @implementation ASHorizontalScrollView 24 | 25 | #define kDefaultLeftMargin 5.0f; 26 | #define kMinMarginBetweenItems 10.0f; 27 | #define kMinWidthAppearOfLastItem 20.0f; 28 | 29 | - (id)initWithFrame:(CGRect)frame 30 | { 31 | self = [super initWithFrame:frame]; 32 | if (self) { 33 | [self initView]; 34 | } 35 | return self; 36 | } 37 | 38 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 39 | self = [super initWithCoder:aDecoder]; 40 | if (self) { 41 | [self initView]; 42 | } 43 | return self; 44 | } 45 | 46 | - (void)initView { 47 | _items = [NSMutableArray array]; 48 | 49 | //default item size is 80% of height 50 | _uniformItemSize = CGSizeMake(self.frame.size.height*0.8, self.frame.size.height*0.8); 51 | 52 | //default attributes 53 | _leftMarginPx = kDefaultLeftMargin; 54 | _miniMarginPxBetweenItems = kMinMarginBetweenItems; 55 | _miniAppearPxOfLastItem = kMinWidthAppearOfLastItem; 56 | 57 | //get default item margin 58 | [self setItemsMarginOnce]; 59 | 60 | [self setShowsHorizontalScrollIndicator:NO]; 61 | [self setDecelerationRate:UIScrollViewDecelerationRateFast]; 62 | 63 | scrollViewdelegate = [[ASHorizontalScrollViewDelegate alloc] init]; 64 | self.delegate = scrollViewdelegate; 65 | } 66 | 67 | - (void)setFrame:(CGRect)frame 68 | { 69 | CGRect oldValue = self.frame; 70 | [super setFrame:frame]; 71 | itemY = (frame.size.height-self.uniformItemSize.height)/2; 72 | if(frame.size.width != oldValue.size.width){ 73 | [self refreshSubView]; 74 | } 75 | } 76 | 77 | - (void)setUniformItemSize:(CGSize)uniformItemSize 78 | { 79 | _uniformItemSize = uniformItemSize; 80 | itemY = (self.frame.size.height-uniformItemSize.height)/2; 81 | } 82 | 83 | - (BOOL)touchesShouldCancelInContentView:(UIView *)view { 84 | if ([view isKindOfClass:[UIButton class]]) { 85 | return true; 86 | } 87 | return false; 88 | } 89 | 90 | #pragma mark - add item 91 | - (void)addItem:(UIView*)item 92 | { 93 | //setup new item size and origin 94 | if (self.items.count>0) { 95 | CGRect lastItemRect = ((UIView*)self.items.lastObject).frame; 96 | [item setFrame:CGRectMake(lastItemRect.origin.x + self.uniformItemSize.width + self.itemsMargin, itemY, self.uniformItemSize.width, self.uniformItemSize.height)]; 97 | } 98 | else [item setFrame:CGRectMake(self.leftMarginPx, itemY, self.uniformItemSize.width, self.uniformItemSize.height)]; 99 | 100 | [_items addObject:item]; 101 | [self addSubview:item]; 102 | // set the content size of scroll view to fit new width and with the same margin as left margin 103 | self.contentSize = CGSizeMake(item.frame.origin.x + self.uniformItemSize.width + self.leftMarginPx, self.frame.size.height); 104 | } 105 | 106 | - (void)addItems:(NSArray *)items 107 | { 108 | for (UIView* item in items) { 109 | [self addItem:item]; 110 | } 111 | } 112 | 113 | /// Calculate the exact margin between items 114 | - (int)calculateMarginBetweenItems 115 | { 116 | // CGFloat scale = [UIScreen mainScreen].scale; 117 | //calculate how many items listed on current screen except the last half appearance one 118 | int numberOfItemForCurrentWidth = floorf((self.frame.size.width-self.leftMarginPx-self.miniAppearPxOfLastItem)/(_uniformItemSize.width+self.miniMarginPxBetweenItems)); 119 | 120 | return round((self.frame.size.width-self.leftMarginPx-self.miniAppearPxOfLastItem)/numberOfItemForCurrentWidth - _uniformItemSize.width); 121 | } 122 | 123 | - (void)setItemsMarginOnce 124 | { 125 | _itemsMargin = [self calculateMarginBetweenItems]; 126 | } 127 | 128 | #pragma mark - remove item 129 | - (BOOL) removeAllItems 130 | { 131 | for (long i = self.items.count - 1; i >= 0; i--) { 132 | UIView *item = self.items[i]; 133 | [item removeFromSuperview]; 134 | } 135 | [self.items removeAllObjects]; 136 | self.contentSize = CGSizeMake(self.contentSize.width-self.itemsMargin-self.uniformItemSize.width, self.frame.size.height); 137 | 138 | return true; 139 | } 140 | 141 | - (BOOL)removeItem:(UIView *)item 142 | { 143 | NSInteger index = [self.items indexOfObject:item]; 144 | if (index != NSNotFound) { 145 | return [self removeItemAtIndex:index]; 146 | } 147 | else return false; 148 | } 149 | 150 | - (BOOL)removeItemAtIndex:(NSInteger)index 151 | { 152 | if (index < 0 || index > self.items.count-1) return false; 153 | //set new x position from index to the end 154 | if (index != self.items.count-1) { 155 | for (NSInteger i = self.items.count-1; i > index; i--) { 156 | UIView *item = [self.items objectAtIndex:i]; 157 | item.frame = CGRectMake(CGRectGetMinX(item.frame)-self.itemsMargin-self.uniformItemSize.width, CGRectGetMinY(item.frame), CGRectGetWidth(item.frame), CGRectGetHeight(item.frame)); 158 | } 159 | } 160 | UIView *item = [self.items objectAtIndex:index]; 161 | [item removeFromSuperview]; 162 | [self.items removeObjectAtIndex:index]; 163 | self.contentSize = CGSizeMake(self.contentSize.width-self.itemsMargin-self.uniformItemSize.width, self.frame.size.height); 164 | 165 | return true; 166 | } 167 | 168 | /// Refresh all subviews for changing size of current frame 169 | - (void) refreshSubView 170 | { 171 | [self setItemsMarginOnce]; 172 | float itemX = self.leftMarginPx; 173 | for (UIView *item in self.items) { 174 | item.frame = CGRectMake(itemX, item.frame.origin.y, item.frame.size.width, item.frame.size.height); 175 | itemX += item.frame.size.width + self.itemsMargin; 176 | } 177 | itemX = itemX - self.itemsMargin + self.leftMarginPx; 178 | self.contentSize = CGSizeMake(itemX, self.frame.size.height); 179 | } 180 | 181 | 182 | @end 183 | -------------------------------------------------------------------------------- /Sources/ASHorizontalScrollView/ASHorizontalScrollViewDelegate.h: -------------------------------------------------------------------------------- 1 | /* --------------------------------------------------------- 2 | * ASHorizontalScrollViewDelegate.h 3 | * The MIT License (MIT) 4 | * Copyright (C) 2014-2015 WEIWEI CHEN 5 | * --------------------------------------------------------- 6 | * History 7 | * Created by WEIWEI CHEN on 14-6-2. 8 | * 9 | * 10 | * 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: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | * 14 | * 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. 15 | * -------------------------------------------------------*/ 16 | 17 | #import 18 | 19 | @interface ASHorizontalScrollViewDelegate : NSObject 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /Sources/ASHorizontalScrollView/ASHorizontalScrollViewDelegate.m: -------------------------------------------------------------------------------- 1 | /* --------------------------------------------------------- 2 | * ASHorizontalScrollViewDelegate.m 3 | * The MIT License (MIT) 4 | * Copyright (C) 2014-2015 WEIWEI CHEN 5 | * --------------------------------------------------------- 6 | * History 7 | * Created by WEIWEI CHEN on 14-6-2. 8 | * Edit by WEIWEI CHEN 16-09-15: avoid cloeset index smaller than 0 when only one item showed in screen 9 | * 10 | * 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: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | * 14 | * 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. 15 | * -------------------------------------------------------*/ 16 | 17 | #import "ASHorizontalScrollViewDelegate.h" 18 | #import "ASHorizontalScrollView.h" 19 | 20 | @implementation ASHorizontalScrollViewDelegate 21 | - (void)scrollViewWillEndDragging:(ASHorizontalScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset 22 | { 23 | if (targetContentOffset->x + scrollView.frame.size.width < scrollView.contentSize.width) { 24 | targetContentOffset->x = [self getClosestItemByX:targetContentOffset->x inScrollView:scrollView] - scrollView.leftMarginPx; 25 | } 26 | } 27 | 28 | - (float)getClosestItemByX:(float)xPosition inScrollView:(ASHorizontalScrollView*)scrollView 29 | { 30 | //get current cloest item on the left side 31 | int index = (int)((xPosition - scrollView.leftMarginPx)/(scrollView.itemsMargin+scrollView.uniformItemSize.width)); 32 | if (index < 0) { 33 | index = 0; 34 | } 35 | UIView *item = [scrollView.items objectAtIndex:index]; 36 | //check if target position is over half of current left item, if so, move to next item 37 | if (xPosition-item.frame.origin.x>item.frame.size.width/2) { 38 | item = [scrollView.items objectAtIndex:index+1]; 39 | //check if target position plus scroll view width over content width, if so, move back to last item 40 | if (item.frame.origin.x + scrollView.frame.size.width > scrollView.contentSize.width) { 41 | item = [scrollView.items objectAtIndex:index]; 42 | } 43 | } 44 | 45 | return item.frame.origin.x; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /images/thumbookr1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terenceLuffy/AppStoreStyleHorizontalScrollView/b7bcc134466d923b172d8a96b1e05a0e2240934b/images/thumbookr1.gif -------------------------------------------------------------------------------- /images/thumbookr2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terenceLuffy/AppStoreStyleHorizontalScrollView/b7bcc134466d923b172d8a96b1e05a0e2240934b/images/thumbookr2.gif -------------------------------------------------------------------------------- /sampleScorllViewInSwift/sampleScorllViewInSwift.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7465DEDB19453DB100AA9A5D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7465DEDA19453DB100AA9A5D /* AppDelegate.swift */; }; 11 | 7465DEDD19453DB100AA9A5D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7465DEDC19453DB100AA9A5D /* ViewController.swift */; }; 12 | 7465DEE019453DB100AA9A5D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7465DEDE19453DB100AA9A5D /* Main.storyboard */; }; 13 | 7465DEE219453DB100AA9A5D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7465DEE119453DB100AA9A5D /* Images.xcassets */; }; 14 | 7465DEF819453E5800AA9A5D /* ASHorizontalScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7465DEF719453E5800AA9A5D /* ASHorizontalScrollView.swift */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXFileReference section */ 18 | 7465DED519453DB100AA9A5D /* sampleScorllViewInSwift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = sampleScorllViewInSwift.app; sourceTree = BUILT_PRODUCTS_DIR; }; 19 | 7465DED919453DB100AA9A5D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 20 | 7465DEDA19453DB100AA9A5D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 21 | 7465DEDC19453DB100AA9A5D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 22 | 7465DEDF19453DB100AA9A5D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 23 | 7465DEE119453DB100AA9A5D /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 24 | 7465DEF719453E5800AA9A5D /* ASHorizontalScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASHorizontalScrollView.swift; sourceTree = ""; }; 25 | /* End PBXFileReference section */ 26 | 27 | /* Begin PBXFrameworksBuildPhase section */ 28 | 7465DED219453DB100AA9A5D /* Frameworks */ = { 29 | isa = PBXFrameworksBuildPhase; 30 | buildActionMask = 2147483647; 31 | files = ( 32 | ); 33 | runOnlyForDeploymentPostprocessing = 0; 34 | }; 35 | /* End PBXFrameworksBuildPhase section */ 36 | 37 | /* Begin PBXGroup section */ 38 | 7465DECC19453DB100AA9A5D = { 39 | isa = PBXGroup; 40 | children = ( 41 | 7465DED719453DB100AA9A5D /* sampleScorllViewInSwift */, 42 | 7465DED619453DB100AA9A5D /* Products */, 43 | ); 44 | sourceTree = ""; 45 | }; 46 | 7465DED619453DB100AA9A5D /* Products */ = { 47 | isa = PBXGroup; 48 | children = ( 49 | 7465DED519453DB100AA9A5D /* sampleScorllViewInSwift.app */, 50 | ); 51 | name = Products; 52 | sourceTree = ""; 53 | }; 54 | 7465DED719453DB100AA9A5D /* sampleScorllViewInSwift */ = { 55 | isa = PBXGroup; 56 | children = ( 57 | 7465DEF719453E5800AA9A5D /* ASHorizontalScrollView.swift */, 58 | 7465DEDA19453DB100AA9A5D /* AppDelegate.swift */, 59 | 7465DEDC19453DB100AA9A5D /* ViewController.swift */, 60 | 7465DEDE19453DB100AA9A5D /* Main.storyboard */, 61 | 7465DEE119453DB100AA9A5D /* Images.xcassets */, 62 | 7465DED819453DB100AA9A5D /* Supporting Files */, 63 | ); 64 | path = sampleScorllViewInSwift; 65 | sourceTree = ""; 66 | }; 67 | 7465DED819453DB100AA9A5D /* Supporting Files */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 7465DED919453DB100AA9A5D /* Info.plist */, 71 | ); 72 | name = "Supporting Files"; 73 | sourceTree = ""; 74 | }; 75 | /* End PBXGroup section */ 76 | 77 | /* Begin PBXNativeTarget section */ 78 | 7465DED419453DB100AA9A5D /* sampleScorllViewInSwift */ = { 79 | isa = PBXNativeTarget; 80 | buildConfigurationList = 7465DEF119453DB100AA9A5D /* Build configuration list for PBXNativeTarget "sampleScorllViewInSwift" */; 81 | buildPhases = ( 82 | 7465DED119453DB100AA9A5D /* Sources */, 83 | 7465DED219453DB100AA9A5D /* Frameworks */, 84 | 7465DED319453DB100AA9A5D /* Resources */, 85 | ); 86 | buildRules = ( 87 | ); 88 | dependencies = ( 89 | ); 90 | name = sampleScorllViewInSwift; 91 | productName = sampleScorllViewInSwift; 92 | productReference = 7465DED519453DB100AA9A5D /* sampleScorllViewInSwift.app */; 93 | productType = "com.apple.product-type.application"; 94 | }; 95 | /* End PBXNativeTarget section */ 96 | 97 | /* Begin PBXProject section */ 98 | 7465DECD19453DB100AA9A5D /* Project object */ = { 99 | isa = PBXProject; 100 | attributes = { 101 | LastSwiftMigration = 0710; 102 | LastSwiftUpdateCheck = 0710; 103 | LastUpgradeCheck = 1000; 104 | ORGANIZATIONNAME = Zuse; 105 | TargetAttributes = { 106 | 7465DED419453DB100AA9A5D = { 107 | CreatedOnToolsVersion = 6.0; 108 | LastSwiftMigration = 0810; 109 | }; 110 | }; 111 | }; 112 | buildConfigurationList = 7465DED019453DB100AA9A5D /* Build configuration list for PBXProject "sampleScorllViewInSwift" */; 113 | compatibilityVersion = "Xcode 3.2"; 114 | developmentRegion = English; 115 | hasScannedForEncodings = 0; 116 | knownRegions = ( 117 | en, 118 | Base, 119 | ); 120 | mainGroup = 7465DECC19453DB100AA9A5D; 121 | productRefGroup = 7465DED619453DB100AA9A5D /* Products */; 122 | projectDirPath = ""; 123 | projectRoot = ""; 124 | targets = ( 125 | 7465DED419453DB100AA9A5D /* sampleScorllViewInSwift */, 126 | ); 127 | }; 128 | /* End PBXProject section */ 129 | 130 | /* Begin PBXResourcesBuildPhase section */ 131 | 7465DED319453DB100AA9A5D /* Resources */ = { 132 | isa = PBXResourcesBuildPhase; 133 | buildActionMask = 2147483647; 134 | files = ( 135 | 7465DEE019453DB100AA9A5D /* Main.storyboard in Resources */, 136 | 7465DEE219453DB100AA9A5D /* Images.xcassets in Resources */, 137 | ); 138 | runOnlyForDeploymentPostprocessing = 0; 139 | }; 140 | /* End PBXResourcesBuildPhase section */ 141 | 142 | /* Begin PBXSourcesBuildPhase section */ 143 | 7465DED119453DB100AA9A5D /* Sources */ = { 144 | isa = PBXSourcesBuildPhase; 145 | buildActionMask = 2147483647; 146 | files = ( 147 | 7465DEDD19453DB100AA9A5D /* ViewController.swift in Sources */, 148 | 7465DEDB19453DB100AA9A5D /* AppDelegate.swift in Sources */, 149 | 7465DEF819453E5800AA9A5D /* ASHorizontalScrollView.swift in Sources */, 150 | ); 151 | runOnlyForDeploymentPostprocessing = 0; 152 | }; 153 | /* End PBXSourcesBuildPhase section */ 154 | 155 | /* Begin PBXVariantGroup section */ 156 | 7465DEDE19453DB100AA9A5D /* Main.storyboard */ = { 157 | isa = PBXVariantGroup; 158 | children = ( 159 | 7465DEDF19453DB100AA9A5D /* Base */, 160 | ); 161 | name = Main.storyboard; 162 | sourceTree = ""; 163 | }; 164 | /* End PBXVariantGroup section */ 165 | 166 | /* Begin XCBuildConfiguration section */ 167 | 7465DEEF19453DB100AA9A5D /* Debug */ = { 168 | isa = XCBuildConfiguration; 169 | buildSettings = { 170 | ALWAYS_SEARCH_USER_PATHS = NO; 171 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 172 | CLANG_CXX_LIBRARY = "libc++"; 173 | CLANG_ENABLE_MODULES = YES; 174 | CLANG_ENABLE_OBJC_ARC = YES; 175 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 176 | CLANG_WARN_BOOL_CONVERSION = YES; 177 | CLANG_WARN_COMMA = YES; 178 | CLANG_WARN_CONSTANT_CONVERSION = YES; 179 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 180 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 181 | CLANG_WARN_EMPTY_BODY = YES; 182 | CLANG_WARN_ENUM_CONVERSION = YES; 183 | CLANG_WARN_INFINITE_RECURSION = YES; 184 | CLANG_WARN_INT_CONVERSION = YES; 185 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 186 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 187 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 188 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 189 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 190 | CLANG_WARN_STRICT_PROTOTYPES = YES; 191 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 192 | CLANG_WARN_UNREACHABLE_CODE = YES; 193 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 194 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 195 | COPY_PHASE_STRIP = NO; 196 | ENABLE_STRICT_OBJC_MSGSEND = YES; 197 | ENABLE_TESTABILITY = YES; 198 | GCC_C_LANGUAGE_STANDARD = gnu99; 199 | GCC_DYNAMIC_NO_PIC = NO; 200 | GCC_NO_COMMON_BLOCKS = YES; 201 | GCC_OPTIMIZATION_LEVEL = 0; 202 | GCC_PREPROCESSOR_DEFINITIONS = ( 203 | "DEBUG=1", 204 | "$(inherited)", 205 | ); 206 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 207 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 208 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 209 | GCC_WARN_UNDECLARED_SELECTOR = YES; 210 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 211 | GCC_WARN_UNUSED_FUNCTION = YES; 212 | GCC_WARN_UNUSED_VARIABLE = YES; 213 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 214 | METAL_ENABLE_DEBUG_INFO = YES; 215 | ONLY_ACTIVE_ARCH = YES; 216 | SDKROOT = iphoneos; 217 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 218 | SWIFT_VERSION = 3.0.1; 219 | TARGETED_DEVICE_FAMILY = "1,2"; 220 | }; 221 | name = Debug; 222 | }; 223 | 7465DEF019453DB100AA9A5D /* Release */ = { 224 | isa = XCBuildConfiguration; 225 | buildSettings = { 226 | ALWAYS_SEARCH_USER_PATHS = NO; 227 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 228 | CLANG_CXX_LIBRARY = "libc++"; 229 | CLANG_ENABLE_MODULES = YES; 230 | CLANG_ENABLE_OBJC_ARC = YES; 231 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 232 | CLANG_WARN_BOOL_CONVERSION = YES; 233 | CLANG_WARN_COMMA = YES; 234 | CLANG_WARN_CONSTANT_CONVERSION = YES; 235 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 236 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 237 | CLANG_WARN_EMPTY_BODY = YES; 238 | CLANG_WARN_ENUM_CONVERSION = YES; 239 | CLANG_WARN_INFINITE_RECURSION = YES; 240 | CLANG_WARN_INT_CONVERSION = YES; 241 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 242 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 243 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 244 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 245 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 246 | CLANG_WARN_STRICT_PROTOTYPES = YES; 247 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 248 | CLANG_WARN_UNREACHABLE_CODE = YES; 249 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 250 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 251 | COPY_PHASE_STRIP = YES; 252 | ENABLE_NS_ASSERTIONS = NO; 253 | ENABLE_STRICT_OBJC_MSGSEND = YES; 254 | GCC_C_LANGUAGE_STANDARD = gnu99; 255 | GCC_NO_COMMON_BLOCKS = YES; 256 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 257 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 258 | GCC_WARN_UNDECLARED_SELECTOR = YES; 259 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 260 | GCC_WARN_UNUSED_FUNCTION = YES; 261 | GCC_WARN_UNUSED_VARIABLE = YES; 262 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 263 | METAL_ENABLE_DEBUG_INFO = NO; 264 | SDKROOT = iphoneos; 265 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 266 | SWIFT_VERSION = 3.0.1; 267 | TARGETED_DEVICE_FAMILY = "1,2"; 268 | VALIDATE_PRODUCT = YES; 269 | }; 270 | name = Release; 271 | }; 272 | 7465DEF219453DB100AA9A5D /* Debug */ = { 273 | isa = XCBuildConfiguration; 274 | buildSettings = { 275 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 276 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 277 | INFOPLIST_FILE = sampleScorllViewInSwift/Info.plist; 278 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 279 | PRODUCT_BUNDLE_IDENTIFIER = "Zuse.${PRODUCT_NAME:rfc1034identifier}"; 280 | PRODUCT_NAME = "$(TARGET_NAME)"; 281 | SWIFT_VERSION = 4.2; 282 | }; 283 | name = Debug; 284 | }; 285 | 7465DEF319453DB100AA9A5D /* Release */ = { 286 | isa = XCBuildConfiguration; 287 | buildSettings = { 288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 289 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 290 | INFOPLIST_FILE = sampleScorllViewInSwift/Info.plist; 291 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 292 | PRODUCT_BUNDLE_IDENTIFIER = "Zuse.${PRODUCT_NAME:rfc1034identifier}"; 293 | PRODUCT_NAME = "$(TARGET_NAME)"; 294 | SWIFT_VERSION = 4.2; 295 | }; 296 | name = Release; 297 | }; 298 | /* End XCBuildConfiguration section */ 299 | 300 | /* Begin XCConfigurationList section */ 301 | 7465DED019453DB100AA9A5D /* Build configuration list for PBXProject "sampleScorllViewInSwift" */ = { 302 | isa = XCConfigurationList; 303 | buildConfigurations = ( 304 | 7465DEEF19453DB100AA9A5D /* Debug */, 305 | 7465DEF019453DB100AA9A5D /* Release */, 306 | ); 307 | defaultConfigurationIsVisible = 0; 308 | defaultConfigurationName = Release; 309 | }; 310 | 7465DEF119453DB100AA9A5D /* Build configuration list for PBXNativeTarget "sampleScorllViewInSwift" */ = { 311 | isa = XCConfigurationList; 312 | buildConfigurations = ( 313 | 7465DEF219453DB100AA9A5D /* Debug */, 314 | 7465DEF319453DB100AA9A5D /* Release */, 315 | ); 316 | defaultConfigurationIsVisible = 0; 317 | defaultConfigurationName = Release; 318 | }; 319 | /* End XCConfigurationList section */ 320 | }; 321 | rootObject = 7465DECD19453DB100AA9A5D /* Project object */; 322 | } 323 | -------------------------------------------------------------------------------- /sampleScorllViewInSwift/sampleScorllViewInSwift.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /sampleScorllViewInSwift/sampleScorllViewInSwift.xcodeproj/project.xcworkspace/xcshareddata/sampleScorllViewInSwift.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 9315EF00-061B-4801-BF0E-39D9B5A005A0 9 | IDESourceControlProjectName 10 | sampleScorllViewInSwift 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 04AD30FF472F4692B7444AFA771969108DD6AA30 14 | https://github.com/terenceLuffy/AppStoreStyleHorizontalScrollView.git 15 | 16 | IDESourceControlProjectPath 17 | sampleScorllViewInSwift/sampleScorllViewInSwift.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 04AD30FF472F4692B7444AFA771969108DD6AA30 21 | ../../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/terenceLuffy/AppStoreStyleHorizontalScrollView.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 04AD30FF472F4692B7444AFA771969108DD6AA30 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 04AD30FF472F4692B7444AFA771969108DD6AA30 36 | IDESourceControlWCCName 37 | AppStoreStyleHorizontalScrollView 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /sampleScorllViewInSwift/sampleScorllViewInSwift.xcodeproj/project.xcworkspace/xcuserdata/firestorm996.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terenceLuffy/AppStoreStyleHorizontalScrollView/b7bcc134466d923b172d8a96b1e05a0e2240934b/sampleScorllViewInSwift/sampleScorllViewInSwift.xcodeproj/project.xcworkspace/xcuserdata/firestorm996.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /sampleScorllViewInSwift/sampleScorllViewInSwift.xcodeproj/project.xcworkspace/xcuserdata/terencechen.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terenceLuffy/AppStoreStyleHorizontalScrollView/b7bcc134466d923b172d8a96b1e05a0e2240934b/sampleScorllViewInSwift/sampleScorllViewInSwift.xcodeproj/project.xcworkspace/xcuserdata/terencechen.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /sampleScorllViewInSwift/sampleScorllViewInSwift.xcodeproj/xcuserdata/firestorm996.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 20 | 21 | 22 | 24 | 36 | 37 | 50 | 51 | 64 | 65 | 66 | 67 | 68 | 70 | 82 | 83 | 84 | 86 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /sampleScorllViewInSwift/sampleScorllViewInSwift.xcodeproj/xcuserdata/firestorm996.xcuserdatad/xcschemes/sampleScorllViewInSwift.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /sampleScorllViewInSwift/sampleScorllViewInSwift.xcodeproj/xcuserdata/firestorm996.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | sampleScorllViewInSwift.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 7465DED419453DB100AA9A5D 16 | 17 | primary 18 | 19 | 20 | 7465DEE619453DB100AA9A5D 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /sampleScorllViewInSwift/sampleScorllViewInSwift.xcodeproj/xcuserdata/terencechen.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /sampleScorllViewInSwift/sampleScorllViewInSwift.xcodeproj/xcuserdata/terencechen.xcuserdatad/xcschemes/sampleScorllViewInSwift.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /sampleScorllViewInSwift/sampleScorllViewInSwift.xcodeproj/xcuserdata/terencechen.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | sampleScorllViewInSwift.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 7465DED419453DB100AA9A5D 16 | 17 | primary 18 | 19 | 20 | 7465DEE619453DB100AA9A5D 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /sampleScorllViewInSwift/sampleScorllViewInSwift/ASHorizontalScrollView.swift: -------------------------------------------------------------------------------- 1 | /* --------------------------------------------------------- 2 | * ASHorizontalScrollView.swift 3 | * The MIT License (MIT) 4 | * Copyright (C) 2014-Current WEIWEI CHEN 5 | * --------------------------------------------------------- 6 | * History 7 | * Created by WEIWEI CHEN on 14-6-8. 8 | * Edit by WEIWEI CHEN 14-9-21: fix problems to work on xcode 6.0.1 9 | * Edit by WEIWEI CHEN 15-12-09: change to adapt Swift 2.1, add comments on functions, remove scale when calculating margin, it seems that the behaviour in iOS 9 change the way of align views 10 | * Edit by WEIWEI CHEN 16-05-17: change C style code to swift 3 format, fix removeItemAtIndex last index crash bug 11 | * Edit by WEIWEI CHEN 16-09-15: Change to adapt Swift 3 with Xcode 8, add support to nib, just change the class on nib file to ASHorizontalScrollView 12 | * Edit by WEIWEI CHEN 16-12-02: When current item scroll to more than specified width, auto scroll to next item (mimic App Store behaviour which is about 1/3); add support to all apple screen sizes, now you can specified different mini margin, mini appear width and left margin for all sorts of screen sizes. 13 | * Edit by WEIWEI CHEN 17-01-24: Introduce new properties to allow set number of items per screen for multiple screen sizes instead of setting minimum margins, as well as new properties to center subviews when items are not wide enough to use whole screen width 14 | * Edit by WEIWEI CHEN 17-03-03: check items size before removing all items 15 | * Edit by WEIWEI CHEN 18-10-17: migrate to Swift 4.2 and add settings for iPhone X, Xs, XR and Xs Max landscape support, thanks to Anton Dryakhlykh help on the migration. 16 | * 17 | * 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: 18 | * 19 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 20 | * 21 | * 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. 22 | * -------------------------------------------------------*/ 23 | 24 | import UIKit 25 | 26 | 27 | /// settings for margins 28 | public struct MarginSettings { 29 | /// the margin between left border and first item 30 | public var leftMargin:CGFloat = 5.0 31 | 32 | /// the mini margin between items, it is the seed to calculate the actual margin which is not less than 33 | public var miniMarginBetweenItems:CGFloat = 10.0 34 | 35 | /// the mini width appear for last item of current screen, set it 0 if you don't want any part of the last item appear on the right 36 | public var miniAppearWidthOfLastItem:CGFloat = 20.0 37 | 38 | /// number of items per screen, it can be integer like 3, that means total 3 items occupy whole screen, 4.5 means total 4 items and one half item show on the right end. Please note that if numberOfItemsPerScreen is more than screen width, the maximum allowed number of items per screen would be calculated by left margin, and last appeared item percentage which is determined by the fractional number of this value 39 | public var numberOfItemsPerScreen:Float = 0 40 | 41 | public init() { } 42 | 43 | 44 | /// Use this to set margin if arrange type is set by frame 45 | /// 46 | /// - Parameters: 47 | /// - leftMargin: the margin between left border and first item 48 | /// - miniMarginBetweenItems: the mini margin between items, it is the seed to calculate the actual margin which is not less than 49 | /// - miniAppearWidthOfLastItem: the mini width appear for last item of current screen, set it 0 if you don't want any part of the last item appear on the right 50 | public init(leftMargin:CGFloat, miniMarginBetweenItems:CGFloat, miniAppearWidthOfLastItem:CGFloat) { 51 | self.leftMargin = leftMargin 52 | self.miniMarginBetweenItems = miniMarginBetweenItems 53 | self.miniAppearWidthOfLastItem = miniAppearWidthOfLastItem 54 | } 55 | 56 | 57 | /// Use this to set margin if arrange type is set by number per screen 58 | /// 59 | /// - Parameters: 60 | /// - leftMargin: the margin between left border and first item 61 | /// - numberOfItemsPerScreen: number of items per screen, it can be integer like 3, that means total 3 items occupy whole screen, 4.5 means total 4 items and one half item show on the right end. 62 | /// - note: if numberOfItemsPerScreen is more than screen width, the maximum allowed number of items per screen would be calculated by left margin, and last appeared item percentage which is determined by the fractional number of this value 63 | public init(leftMargin:CGFloat, numberOfItemsPerScreen:Float) { 64 | self.leftMargin = leftMargin 65 | self.numberOfItemsPerScreen = (numberOfItemsPerScreen >= 0 ? numberOfItemsPerScreen : 0) 66 | } 67 | } 68 | 69 | public enum ArrangeType { 70 | case byFrame 71 | case byNumber 72 | } 73 | 74 | open class ASHorizontalScrollView: UIScrollView, UIScrollViewDelegate { 75 | // MARK: - properties 76 | override open var frame: CGRect{ 77 | didSet{ 78 | itemY = (frame.size.height-self.uniformItemSize.height)/2 79 | //if width changes, then need to get new margin and reset all views 80 | if(frame.width != oldValue.width){ 81 | self.refreshSubView() 82 | } 83 | } 84 | } 85 | 86 | 87 | /// whether to arrange items by frame or by number of items, if set by frame, all margin would be calculated by frame size, otherwise, calculated by number of items per screen 88 | /// - check `numberOfItemsPerScreen` for arranged by number type 89 | open var arrangeType:ArrangeType = .byFrame 90 | /// y position of all items 91 | open var itemY: CGFloat = 0 92 | /// an array which refer to all added items 93 | open var items: [UIView] = [] 94 | /// center subviews when items do not occupy whole screen 95 | public var shouldCenterSubViews:Bool = false 96 | 97 | /// the uniform size of all added items, please set it before adding any items, otherwise, default size will be applied 98 | open var uniformItemSize:CGSize = CGSize.zero { 99 | didSet{ 100 | itemY = (frame.size.height-self.uniformItemSize.height)/2 101 | } 102 | } 103 | /// the width to move to next item when target point which stops at an item is larger or equal than, default value is 1/3 that means, for example, if scrolling stops at half of an item, auto scroll to next item 104 | public var widthToScrollNextItem:CGFloat = 1/3 105 | 106 | public var marginSettings:MarginSettings { 107 | get { 108 | switch UIScreen.main.bounds.width { 109 | //for portrait 110 | case 320: 111 | if let setting = marginSettings_320 { 112 | return setting 113 | } 114 | case 375: 115 | if let setting = marginSettings_375 { 116 | return setting 117 | } 118 | case 414: 119 | if let setting = marginSettings_414 { 120 | return setting 121 | } 122 | case 768: 123 | if let setting = marginSettings_768 { 124 | return setting 125 | } 126 | case 1024: 127 | if let setting = marginSettings_1024 { 128 | return setting 129 | } 130 | //for landscape 131 | case 480: 132 | if let setting = marginSettings_480 { 133 | return setting 134 | } 135 | case 568: 136 | if let setting = marginSettings_568 { 137 | return setting 138 | } 139 | case 667: 140 | if let setting = marginSettings_667 { 141 | return setting 142 | } 143 | case 736: 144 | if let setting = marginSettings_736 { 145 | return setting 146 | } 147 | case 812: 148 | if let setting = marginSettings_812 { 149 | return setting 150 | } 151 | case 896: 152 | if let setting = marginSettings_896 { 153 | return setting 154 | } 155 | case 1366: 156 | if let setting = marginSettings_1366 { 157 | return setting 158 | } 159 | default: 160 | break 161 | } 162 | 163 | return defaultMarginSettings 164 | } 165 | } 166 | 167 | /// the default setting used if you don't set other margin settings for specific screen size 168 | public var defaultMarginSettings = MarginSettings() 169 | /// for iPhone 5s and lower versions in portrait 170 | public var marginSettings_320:MarginSettings? 171 | /// for iPhone 6 and 6s in portrait 172 | public var marginSettings_375:MarginSettings? 173 | /// for iPhone 6 plus and 6s plus in portrait 174 | public var marginSettings_414:MarginSettings? 175 | /// for ipad in portrait 176 | public var marginSettings_768:MarginSettings? 177 | 178 | /// for iPhone 4s and lower versions in landscape 179 | public var marginSettings_480:MarginSettings? 180 | /// for iPhone 5 and 5s in landscape 181 | public var marginSettings_568:MarginSettings? 182 | /// for iPhone 6 and 6s in landscape 183 | public var marginSettings_667:MarginSettings? 184 | /// for iPhone 6 plus and 6s plus in landscape 185 | public var marginSettings_736:MarginSettings? 186 | /// for iPhone X and Xs in landscape 187 | public var marginSettings_812:MarginSettings? 188 | /// for iPhone Xs Max and XR in landscape 189 | public var marginSettings_896:MarginSettings? 190 | /// for ipad and ipad pro 9.7 in landscape and ipad pro 12.9 portrait 191 | public var marginSettings_1024:MarginSettings? 192 | /// for ipad pro 12.9 in landscape 193 | public var marginSettings_1366:MarginSettings? 194 | 195 | /// store the current items' margin 196 | open var itemsMargin:CGFloat = 10.0 197 | 198 | /// the margin between left border and first item 199 | open var leftMarginPx:CGFloat { 200 | get { 201 | return self.marginSettings.leftMargin 202 | } 203 | } 204 | 205 | /// the mini margin between items, it is the seed to calculate the actual margin which is not less than 206 | open var miniMarginPxBetweenItems:CGFloat { 207 | get { 208 | return self.marginSettings.miniMarginBetweenItems 209 | } 210 | } 211 | 212 | /// the mini width appear for last item of current screen, set it 0 if you don't want any part of the last item appear on the right 213 | open var miniAppearPxOfLastItem:CGFloat { 214 | get { 215 | return self.marginSettings.miniAppearWidthOfLastItem 216 | } 217 | } 218 | 219 | /// number of items per screen, it can be integer like 3, that means total 3 items occupy whole screen, 4.5 means total 4 items and one half item show on the right end 220 | open var numberOfItemsPerScreen:Float { 221 | get { 222 | return self.marginSettings.numberOfItemsPerScreen 223 | } 224 | } 225 | 226 | // MARK: - view init 227 | public override init(frame: CGRect) { 228 | super.init(frame: frame) 229 | initView() 230 | } 231 | 232 | required public init?(coder aDecoder: NSCoder) { 233 | super.init(coder: aDecoder) 234 | initView() 235 | } 236 | 237 | fileprivate func initView() { 238 | //default item size is 80% of height 239 | self.uniformItemSize = CGSize(width: frame.size.height*0.8, height: frame.size.height*0.8) 240 | 241 | self.showsHorizontalScrollIndicator = false 242 | self.decelerationRate = UIScrollView.DecelerationRate.fast 243 | self.delegate = self 244 | } 245 | 246 | override open func touchesShouldCancel(in view: UIView) -> Bool { 247 | if view.isKind(of: UIButton.self) { 248 | return true 249 | } 250 | return false 251 | } 252 | // MARK: - methods 253 | /** 254 | It re-calculate the item margin to fit in current view frame 255 | - note: This must be called after changing any size or margin property of this class to get acurrate margin 256 | - seealso: calculateMarginBetweenItems 257 | */ 258 | open func setItemsMarginOnce() 259 | { 260 | self.itemsMargin = self.calculateMarginBetweenItems(); 261 | } 262 | 263 | /// Calculate the exact margin between items 264 | open func calculateMarginBetweenItems() -> CGFloat 265 | { 266 | if self.arrangeType == .byFrame { 267 | return calculateMarginByFrame() 268 | } 269 | else { 270 | return calculateMarginByNumberPerScreen() 271 | } 272 | } 273 | 274 | /// Calculate the exact margin by frame 275 | open func calculateMarginByFrame() -> CGFloat { 276 | //calculate how many items listed on current screen except the last half appearance one 277 | let numberOfItemForCurrentWidth = floorf(Float((self.frame.size.width-self.leftMarginPx-self.miniAppearPxOfLastItem)/(self.uniformItemSize.width+self.miniMarginPxBetweenItems))) 278 | return (self.frame.size.width-self.leftMarginPx-self.miniAppearPxOfLastItem)/CGFloat(numberOfItemForCurrentWidth) - self.uniformItemSize.width 279 | } 280 | 281 | /// Calculate the exact margin by number of items per screen 282 | open func calculateMarginByNumberPerScreen() -> CGFloat { 283 | let numOfFull = Int(self.numberOfItemsPerScreen) 284 | if numOfFull <= 0 {// if margin is not set for this screen width, use calculation by frame instead 285 | return calculateMarginByFrame() 286 | } 287 | let lastItemPercentage = self.numberOfItemsPerScreen - Float(numOfFull) 288 | var margin = (self.frame.size.width-self.leftMarginPx-self.uniformItemSize.width * CGFloat(lastItemPercentage))/CGFloat(numOfFull) - self.uniformItemSize.width 289 | if margin <= 0 {//in such case, the number per screen width is larger than the screen width, calculate the max allowed number per screen using the left margin, fractional value of numberOfItemsPerScreen and mini margin between items 290 | let numberOfItemForCurrentWidth = floorf(Float((self.frame.size.width-self.leftMarginPx-self.uniformItemSize.width * CGFloat(lastItemPercentage))/(self.uniformItemSize.width+self.miniMarginPxBetweenItems))) 291 | margin = (self.frame.size.width-self.leftMarginPx-self.uniformItemSize.width * CGFloat(lastItemPercentage))/CGFloat(numberOfItemForCurrentWidth) - self.uniformItemSize.width 292 | } 293 | 294 | return margin 295 | } 296 | 297 | /** 298 | This add a new item into the scrollview 299 | 300 | - parameter item: the item you would like to add, it must not be nil. 301 | */ 302 | open func addItem(_ item:UIView) 303 | { 304 | //setup new item size and origin 305 | if (self.items.count>0) { 306 | let lastItemRect:CGRect = self.items[self.items.count-1].frame; 307 | item.frame = CGRect(x: lastItemRect.origin.x + self.uniformItemSize.width + self.itemsMargin, y: itemY, width: self.uniformItemSize.width, height: self.uniformItemSize.height) 308 | } 309 | else { 310 | item.frame = CGRect(x: self.leftMarginPx, y: itemY, width: self.uniformItemSize.width, height: self.uniformItemSize.height) 311 | } 312 | 313 | items.append(item); 314 | self.addSubview(item); 315 | // set the content size of scroll view to fit new width and with the same margin as left margin 316 | self.contentSize = CGSize(width: item.frame.origin.x + self.uniformItemSize.width + self.leftMarginPx, height: self.frame.size.height); 317 | } 318 | 319 | /** 320 | This add multi new items into the scrollview 321 | 322 | - parameter items: the items in array you would like to add, it must not be nil. 323 | */ 324 | open func addItems(_ items:[UIView]) 325 | { 326 | for item in items { 327 | self.addItem(item) 328 | } 329 | } 330 | 331 | /** 332 | It removes the specified item from scrollview 333 | 334 | - parameter item: the item you would like to remove. 335 | 336 | - returns: true if removing successfully. 337 | */ 338 | open func removeItem(_ item:UIView) -> Bool 339 | { 340 | guard let index = self.items.index(of: item) else { 341 | return false 342 | } 343 | 344 | return self.removeItemAtIndex(index) 345 | } 346 | 347 | /** 348 | It removes all items from scrollview 349 | 350 | - returns: true if removing successfully. 351 | */ 352 | open func removeAllItems()->Bool 353 | { 354 | if self.items.count == 0 { 355 | return false 356 | } 357 | 358 | for i in (0...self.items.count-1).reversed() { 359 | let item:UIView = self.items[i] 360 | item.removeFromSuperview() 361 | } 362 | self.items.removeAll(keepingCapacity: false) 363 | self.contentSize = self.frame.size 364 | 365 | return true 366 | } 367 | 368 | /** 369 | It removes the specified item at index from scrollview 370 | 371 | - parameter index: the index of item you would like to remove. 372 | 373 | - returns: true if removing successfully. 374 | */ 375 | open func removeItemAtIndex(_ index:Int)->Bool 376 | { 377 | if (index < 0 || index > self.items.count-1) {return false} 378 | //set new x position from index to the end 379 | if index != self.items.count-1{ 380 | for i in (index+1...self.items.count-1).reversed() { 381 | let item:UIView = self.items[i] 382 | item.frame = CGRect(x: item.frame.minX-self.itemsMargin-self.uniformItemSize.width, y: item.frame.minY, width: item.frame.width, height: item.frame.height) 383 | } 384 | } 385 | 386 | let item:UIView = self.items[index] 387 | item.removeFromSuperview() 388 | self.items.remove(at: index) 389 | self.contentSize = CGSize(width: self.contentSize.width-self.itemsMargin-self.uniformItemSize.width, height: self.frame.size.height) 390 | 391 | return true 392 | } 393 | 394 | /// Refresh all subviews for changing size of current frame 395 | public func refreshSubView() 396 | { 397 | self.setItemsMarginOnce(); 398 | var itemX = self.leftMarginPx 399 | if self.shouldCenterSubViews { 400 | itemX = centerSubviews() 401 | } 402 | else { 403 | itemX = self.reorderSubViews() 404 | } 405 | self.contentSize = CGSize(width: itemX, height: self.frame.size.height) 406 | } 407 | 408 | private func reorderSubViews() -> CGFloat { 409 | var itemX = self.leftMarginPx 410 | for item in self.items 411 | { 412 | item.frame = CGRect(x: itemX, y: item.frame.origin.y, width: item.frame.width, height: item.frame.height) 413 | itemX += item.frame.width + self.itemsMargin 414 | } 415 | 416 | return itemX - self.itemsMargin + self.leftMarginPx; 417 | } 418 | 419 | 420 | /// center subviews if all items can not fully occupy whole screen width 421 | /// 422 | /// - Returns: the scroll view content width 423 | public func centerSubviews() -> CGFloat{ 424 | if let itemLastX = self.items.last?.frame.maxX { 425 | if itemLastX + self.leftMarginPx < self.frame.size.width { 426 | let extraGap = (self.frame.size.width - (self.itemsMargin + self.uniformItemSize.width) * CGFloat(self.items.count) + self.itemsMargin - self.leftMarginPx * 2) / 2 427 | var itemX = self.leftMarginPx + extraGap 428 | for item in self.items 429 | { 430 | item.frame = CGRect(x: itemX, y: item.frame.origin.y, width: item.frame.width, height: item.frame.height) 431 | itemX += item.frame.width + self.itemsMargin 432 | } 433 | return itemX - self.itemsMargin + self.leftMarginPx + extraGap; 434 | } 435 | return self.reorderSubViews() 436 | } 437 | return 0 438 | } 439 | 440 | // MARK: - ScrollView delegates 441 | open func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { 442 | let targetOffset:CGPoint = targetContentOffset.pointee; 443 | //move to closest item 444 | if (targetOffset.x + scrollView.frame.size.width < scrollView.contentSize.width) { 445 | targetContentOffset.pointee.x = self.getClosestItemByX(position:targetOffset.x, inScrollView:(scrollView as! ASHorizontalScrollView)) - (scrollView as! ASHorizontalScrollView).leftMarginPx; 446 | } 447 | } 448 | 449 | fileprivate func getClosestItemByX(position xPosition:CGFloat, inScrollView scrollView:ASHorizontalScrollView) -> CGFloat 450 | { 451 | //get current cloest item on the left side 452 | var index = (Int)((xPosition - scrollView.leftMarginPx)/(scrollView.itemsMargin+scrollView.uniformItemSize.width)) 453 | if index < 0 { 454 | index = 0 455 | } 456 | var item:UIView = scrollView.items[index] 457 | //check if target position is over widthToScrollNextItem of current left item, if so, move to next item 458 | if (xPosition-item.frame.origin.x > item.frame.size.width * widthToScrollNextItem) { 459 | item = scrollView.items[index+1] 460 | } 461 | 462 | return item.frame.origin.x 463 | } 464 | 465 | 466 | } 467 | -------------------------------------------------------------------------------- /sampleScorllViewInSwift/sampleScorllViewInSwift/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /* --------------------------------------------------------- 2 | * ViewController.swift 3 | * The MIT License (MIT) 4 | * Copyright (C) 2014 WEIWEI CHEN 5 | * --------------------------------------------------------- 6 | * HIstory 7 | * Created by WEIWEI CHEN on 14-6-8. 8 | * Edit by WEIWEI CHEN 14-9-21: fix problems to work on xcode 6.0.1 9 | * Edit by WEIWEI CHEN 16-09-15: Change to adapt Swift 3 with Xcode 8 10 | * 11 | * 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: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | * 15 | * 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. 16 | * -------------------------------------------------------*/ 17 | 18 | import UIKit 19 | 20 | @UIApplicationMain 21 | class AppDelegate: UIResponder, UIApplicationDelegate { 22 | 23 | var window: UIWindow? 24 | 25 | 26 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 27 | return true 28 | } 29 | 30 | func applicationWillResignActive(_ application: UIApplication) { 31 | // 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. 32 | // 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. 33 | } 34 | 35 | func applicationDidEnterBackground(_ application: UIApplication) { 36 | // 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. 37 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 38 | } 39 | 40 | func applicationWillEnterForeground(_ application: UIApplication) { 41 | // 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. 42 | } 43 | 44 | func applicationDidBecomeActive(_ application: UIApplication) { 45 | // 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. 46 | } 47 | 48 | func applicationWillTerminate(_ application: UIApplication) { 49 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 50 | } 51 | 52 | 53 | } 54 | 55 | -------------------------------------------------------------------------------- /sampleScorllViewInSwift/sampleScorllViewInSwift/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 | -------------------------------------------------------------------------------- /sampleScorllViewInSwift/sampleScorllViewInSwift/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 | "idiom" : "ipad", 20 | "size" : "29x29", 21 | "scale" : "1x" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "size" : "29x29", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "size" : "40x40", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "40x40", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "76x76", 41 | "scale" : "1x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "76x76", 46 | "scale" : "2x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /sampleScorllViewInSwift/sampleScorllViewInSwift/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 | "orientation" : "portrait", 20 | "idiom" : "ipad", 21 | "extent" : "full-screen", 22 | "minimum-system-version" : "7.0", 23 | "scale" : "1x" 24 | }, 25 | { 26 | "orientation" : "landscape", 27 | "idiom" : "ipad", 28 | "extent" : "full-screen", 29 | "minimum-system-version" : "7.0", 30 | "scale" : "1x" 31 | }, 32 | { 33 | "orientation" : "portrait", 34 | "idiom" : "ipad", 35 | "extent" : "full-screen", 36 | "minimum-system-version" : "7.0", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "orientation" : "landscape", 41 | "idiom" : "ipad", 42 | "extent" : "full-screen", 43 | "minimum-system-version" : "7.0", 44 | "scale" : "2x" 45 | } 46 | ], 47 | "info" : { 48 | "version" : 1, 49 | "author" : "xcode" 50 | } 51 | } -------------------------------------------------------------------------------- /sampleScorllViewInSwift/sampleScorllViewInSwift/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | Main 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /sampleScorllViewInSwift/sampleScorllViewInSwift/ViewController.swift: -------------------------------------------------------------------------------- 1 | /* --------------------------------------------------------- 2 | * ViewController.swift 3 | * The MIT License (MIT) 4 | * Copyright (C) 2014 WEIWEI CHEN 5 | * --------------------------------------------------------- 6 | * HIstory 7 | * Created by WEIWEI CHEN on 14-6-8. 8 | * Edit by WEIWEI CHEN 14-9-21: fix problems to work on xcode 6.0.1 9 | * Edit by WEIWEI CHEN 15-12-09: change to adapt Swift 2.1 with Xcode 7 & iOS 9.0 10 | * Edit by WEIWEI CHEN 16-09-15: Change to adapt Swift 3 with Xcode 8 11 | * Edit by WEIWEI CHEN 17-01-24: Change to use new properties and center items function 12 | * 13 | * 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: 14 | * 15 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | * 17 | * 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. 18 | * -------------------------------------------------------*/ 19 | 20 | import UIKit 21 | 22 | class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { 23 | let kCellHeight:CGFloat = 60.0 24 | var sampleTableView:UITableView! 25 | 26 | required init?(coder aDecoder: NSCoder) { 27 | super.init(coder: aDecoder) 28 | 29 | } 30 | 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | 34 | self.sampleTableView = UITableView(frame:CGRect(x: 0,y: 0,width: self.view.frame.size.width, height: self.view.frame.size.height), style:.grouped) 35 | sampleTableView.dataSource = self 36 | sampleTableView.delegate = self 37 | sampleTableView.autoresizingMask = [UIView.AutoresizingMask.flexibleWidth, UIView.AutoresizingMask.flexibleHeight] 38 | self.view.addSubview(sampleTableView) 39 | } 40 | 41 | override func didReceiveMemoryWarning() { 42 | super.didReceiveMemoryWarning() 43 | // Dispose of any resources that can be recreated. 44 | } 45 | 46 | override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 47 | super.viewWillTransition(to: size, with: coordinator) 48 | self.sampleTableView.reloadData() 49 | } 50 | 51 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 52 | { 53 | let CellIdentifierPortrait = "CellPortrait"; 54 | let CellIdentifierLandscape = "CellLandscape"; 55 | let indentifier = self.view.frame.width > self.view.frame.height ? CellIdentifierLandscape : CellIdentifierPortrait 56 | var cell = tableView.dequeueReusableCell(withIdentifier: indentifier) 57 | 58 | if (cell == nil) { 59 | cell = UITableViewCell(style: UITableViewCell.CellStyle.subtitle, reuseIdentifier: indentifier) 60 | cell?.selectionStyle = .none 61 | let horizontalScrollView:ASHorizontalScrollView = ASHorizontalScrollView(frame:CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: kCellHeight)) 62 | //for iPhone 5s and lower versions in portrait 63 | horizontalScrollView.marginSettings_320 = MarginSettings(leftMargin: 10, miniMarginBetweenItems: 5, miniAppearWidthOfLastItem: 20) 64 | //for iPhone 4s and lower versions in landscape 65 | horizontalScrollView.marginSettings_480 = MarginSettings(leftMargin: 10, miniMarginBetweenItems: 5, miniAppearWidthOfLastItem: 20) 66 | // for iPhone 6 plus and 6s plus in portrait 67 | horizontalScrollView.marginSettings_414 = MarginSettings(leftMargin: 10, miniMarginBetweenItems: 5, miniAppearWidthOfLastItem: 20) 68 | // for iPhone 6 plus and 6s plus in landscape 69 | horizontalScrollView.marginSettings_736 = MarginSettings(leftMargin: 10, miniMarginBetweenItems: 10, miniAppearWidthOfLastItem: 30) 70 | //for all other screen sizes that doesn't set here, it would use defaultMarginSettings instead 71 | horizontalScrollView.defaultMarginSettings = MarginSettings(leftMargin: 10, miniMarginBetweenItems: 10, miniAppearWidthOfLastItem: 20) 72 | 73 | if indexPath.row == 0{ 74 | //you can set margin for specific item size here 75 | horizontalScrollView.shouldCenterSubViews = true 76 | horizontalScrollView.marginSettings_414?.miniMarginBetweenItems = 20 77 | horizontalScrollView.uniformItemSize = CGSize(width: 50, height: 50) 78 | //this must be called after changing any size or margin property of this class to get acurrate margin 79 | horizontalScrollView.setItemsMarginOnce() 80 | for _ in 1...3{ 81 | let button = UIButton(frame: CGRect.zero) 82 | button.backgroundColor = UIColor.blue 83 | horizontalScrollView.addItem(button) 84 | } 85 | _ = horizontalScrollView.centerSubviews() 86 | } 87 | else if indexPath.row == 1 { 88 | horizontalScrollView.uniformItemSize = CGSize(width: 80, height: 50) 89 | //this must be called after changing any size or margin property of this class to get acurrate margin 90 | horizontalScrollView.setItemsMarginOnce() 91 | for _ in 1...20{ 92 | let button = UIButton(frame: CGRect.zero) 93 | button.backgroundColor = UIColor.purple 94 | horizontalScrollView.addItem(button) 95 | } 96 | } 97 | else if indexPath.row == 2 { 98 | //instead of using frame to determine margin, using number of items per screen to calculate margin maybe eaiser than setting mini margin for multiple screen size 99 | horizontalScrollView.arrangeType = .byNumber 100 | horizontalScrollView.marginSettings_320 = MarginSettings(leftMargin: 10, numberOfItemsPerScreen: 4.25) 101 | horizontalScrollView.marginSettings_480 = MarginSettings(leftMargin: 10, numberOfItemsPerScreen: 5.25) 102 | horizontalScrollView.marginSettings_414 = MarginSettings(leftMargin: 10, numberOfItemsPerScreen: 4.25) 103 | horizontalScrollView.marginSettings_736 = MarginSettings(leftMargin: 10, numberOfItemsPerScreen: 7.375) 104 | //for all the other screen sizes which are not set here, margin would be calculated by frame instead 105 | 106 | horizontalScrollView.uniformItemSize = CGSize(width: 80, height: 50) 107 | //this must be called after changing any size or margin property of this class to get acurrate margin 108 | horizontalScrollView.setItemsMarginOnce() 109 | for _ in 1...20{ 110 | let button = UIButton(frame: CGRect.zero) 111 | button.backgroundColor = UIColor.gray 112 | horizontalScrollView.addItem(button) 113 | } 114 | } 115 | cell?.contentView.addSubview(horizontalScrollView) 116 | horizontalScrollView.translatesAutoresizingMaskIntoConstraints = false 117 | cell?.contentView.addConstraint(NSLayoutConstraint(item: horizontalScrollView, attribute: NSLayoutConstraint.Attribute.left, relatedBy: NSLayoutConstraint.Relation.equal, toItem: cell!.contentView, attribute: NSLayoutConstraint.Attribute.left, multiplier: 1, constant: 0)) 118 | cell?.contentView.addConstraint(NSLayoutConstraint(item: horizontalScrollView, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: cell!.contentView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 0)) 119 | cell?.contentView.addConstraint(NSLayoutConstraint(item: horizontalScrollView, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: kCellHeight)) 120 | cell?.contentView.addConstraint(NSLayoutConstraint(item: horizontalScrollView, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: cell!.contentView, attribute: NSLayoutConstraint.Attribute.width, multiplier: 1, constant: 0)) 121 | } 122 | else if let horizontalScrollView = cell?.contentView.subviews.first(where: { (view) -> Bool in 123 | return view is ASHorizontalScrollView 124 | }) as? ASHorizontalScrollView { 125 | horizontalScrollView.refreshSubView() //refresh view incase orientation changes 126 | } 127 | return cell! 128 | } 129 | 130 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 131 | { 132 | return 3 133 | } 134 | 135 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat 136 | { 137 | return kCellHeight 138 | } 139 | 140 | } 141 | 142 | --------------------------------------------------------------------------------