├── .DS_Store ├── README.md └── ScrollViewDemo ├── .DS_Store ├── ScrollViewDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── ximinyan.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── ximinyan.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── ScrollViewDemo.xcscheme │ └── xcschememanagement.plist ├── ScrollViewDemo ├── .DS_Store ├── AppDelegate.swift ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── HYColor.swift ├── HYInfiniteScrollView.swift ├── HYPageControlView.swift ├── Images.xcassets │ ├── .DS_Store │ ├── 0.imageset │ │ ├── 1.jpg │ │ └── Contents.json │ ├── 1.imageset │ │ ├── 2.jpg │ │ └── Contents.json │ ├── 2.imageset │ │ ├── 3.jpg │ │ └── Contents.json │ ├── 3.imageset │ │ ├── 5.jpg │ │ └── Contents.json │ ├── 4.imageset │ │ ├── 4.jpg │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── NSTimer+Addition.swift ├── ScrollViewController.swift └── snapshot │ ├── .DS_Store │ ├── 1-test.jpg │ └── 2-test.jpg └── ScrollViewDemoTests ├── .DS_Store ├── Info.plist └── ScrollViewDemoTests.swift /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harryyan/InfiniteScrollView/8b39989ee1df8bc6f9bf796c79616e512f1d11e4/.DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # InfiniteScrollView 2 | Swift 无限循环 ScrollView 自定义Page Control 3 | 4 | ##简单介绍 5 | 使用Swift语言和iOS SDK8 实现循环滚动效果;可用于首页banner,展示活动或者广告等信息。自定义UIScrollView, 内嵌UIImageView, 可从网络拉取数据进行填充,方便结合SDWebImage等第三方库进行扩展。本例仅仅是一个小Demo, 所用图片均在ImageSet中, 并未从网络拉取(原版OC实现了网络拉取).同时实现了自定义page control, 不同于自带圆点page control, 本例使用bottom bar做page control, 可自定义颜色;By the way, 定义了UIColorFromRGB函数, 方便直接取16进制颜色代码拷贝。仔细阅读源代码,十分方便扩展,今后也可能会持续更新,完善。 6 | 7 | HYInfiniteScrollView的逻辑较为复杂, 不类似UITableView的内存重用的管理方式(本人还有另一个控件--照片浏览控件,实现了UITableView式的重用机制,稍后分享)。 基本考虑了所有边界情况(1张,2张和多张情况), 原版上线未出bug。 8 | 9 | ##如何使用 10 | 本控件使用时只需要初始化和设定几个参数值即可, 关键代码如下: 11 | 12 | 13 | 14 | let scrollViewRect: CGRect = CGRectMake(0, self.navigationController!.navigationBar.bounds.height, self.view.bounds.width, 200) 15 | 16 | self.scrollView = HYInfiniteScrollView.init(frame: scrollViewRect) 17 | self.scrollView.autoresizingMask = UIViewAutoresizing.FlexibleWidth | 18 | UIViewAutoresizing.FlexibleHeight 19 | self.scrollView.animationEnable = true 20 | self.scrollView.animationDuration = 3.0 21 | self.scrollView.slideBarEnable = true 22 | self.scrollView.delegate = self 23 | 24 | let imgArr: NSArray = [1,2,3,4,5] 25 | self.scrollView.reloadActivityItem(imgArr) 26 | 27 | self.view.addSubview(self.scrollView) 28 | 29 | 30 | 31 | 32 | 33 | reloadActivityItem 这个方法是外界传参的接口, 本Demo传入值很简单,只起到循环作用,原版OC封装了自己的数据类和相应接口,诸位可以自己扩展。 34 | 35 | 另外有些delegate没有实现,但不影响功能。 36 | 37 | 38 | ##截图如下: 39 | 40 | ![Mou icon](http://zhgyw.cn/images/1-test.jpg) 41 | ![Mou icon](http://zhgyw.cn/images/2-test.jpg) -------------------------------------------------------------------------------- /ScrollViewDemo/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harryyan/InfiniteScrollView/8b39989ee1df8bc6f9bf796c79616e512f1d11e4/ScrollViewDemo/.DS_Store -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | AA35152D1A6E380E0009DAF5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA35152C1A6E380E0009DAF5 /* AppDelegate.swift */; }; 11 | AA35152F1A6E380E0009DAF5 /* ScrollViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA35152E1A6E380E0009DAF5 /* ScrollViewController.swift */; }; 12 | AA3515321A6E380E0009DAF5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA3515301A6E380E0009DAF5 /* Main.storyboard */; }; 13 | AA3515371A6E380E0009DAF5 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA3515351A6E380E0009DAF5 /* LaunchScreen.xib */; }; 14 | AA3515431A6E380E0009DAF5 /* ScrollViewDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3515421A6E380E0009DAF5 /* ScrollViewDemoTests.swift */; }; 15 | AA35154E1A6E46000009DAF5 /* HYInfiniteScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA35154D1A6E46000009DAF5 /* HYInfiniteScrollView.swift */; }; 16 | AA3515501A6E46420009DAF5 /* HYPageControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA35154F1A6E46420009DAF5 /* HYPageControlView.swift */; }; 17 | AA3515521A7323910009DAF5 /* NSTimer+Addition.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3515511A7323910009DAF5 /* NSTimer+Addition.swift */; }; 18 | AA78ABA21A7796E500D03F2E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA78ABA11A7796E500D03F2E /* Images.xcassets */; }; 19 | AA80945C1A77AD2F00846ED9 /* HYColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA80945B1A77AD2F00846ED9 /* HYColor.swift */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | AA35153D1A6E380E0009DAF5 /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = AA35151F1A6E380E0009DAF5 /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = AA3515261A6E380E0009DAF5; 28 | remoteInfo = ScrollViewDemo; 29 | }; 30 | /* End PBXContainerItemProxy section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | AA3515271A6E380E0009DAF5 /* ScrollViewDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ScrollViewDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | AA35152B1A6E380E0009DAF5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 35 | AA35152C1A6E380E0009DAF5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 36 | AA35152E1A6E380E0009DAF5 /* ScrollViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewController.swift; sourceTree = ""; }; 37 | AA3515311A6E380E0009DAF5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 38 | AA3515361A6E380E0009DAF5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 39 | AA35153C1A6E380E0009DAF5 /* ScrollViewDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ScrollViewDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | AA3515411A6E380E0009DAF5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 41 | AA3515421A6E380E0009DAF5 /* ScrollViewDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewDemoTests.swift; sourceTree = ""; }; 42 | AA35154D1A6E46000009DAF5 /* HYInfiniteScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HYInfiniteScrollView.swift; sourceTree = ""; }; 43 | AA35154F1A6E46420009DAF5 /* HYPageControlView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HYPageControlView.swift; sourceTree = ""; }; 44 | AA3515511A7323910009DAF5 /* NSTimer+Addition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSTimer+Addition.swift"; sourceTree = ""; }; 45 | AA78ABA11A7796E500D03F2E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 46 | AA80945B1A77AD2F00846ED9 /* HYColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HYColor.swift; sourceTree = ""; }; 47 | /* End PBXFileReference section */ 48 | 49 | /* Begin PBXFrameworksBuildPhase section */ 50 | AA3515241A6E380E0009DAF5 /* Frameworks */ = { 51 | isa = PBXFrameworksBuildPhase; 52 | buildActionMask = 2147483647; 53 | files = ( 54 | ); 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | AA3515391A6E380E0009DAF5 /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | /* End PBXFrameworksBuildPhase section */ 65 | 66 | /* Begin PBXGroup section */ 67 | AA35151E1A6E380E0009DAF5 = { 68 | isa = PBXGroup; 69 | children = ( 70 | AA3515291A6E380E0009DAF5 /* ScrollViewDemo */, 71 | AA35153F1A6E380E0009DAF5 /* ScrollViewDemoTests */, 72 | AA3515281A6E380E0009DAF5 /* Products */, 73 | ); 74 | sourceTree = ""; 75 | }; 76 | AA3515281A6E380E0009DAF5 /* Products */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | AA3515271A6E380E0009DAF5 /* ScrollViewDemo.app */, 80 | AA35153C1A6E380E0009DAF5 /* ScrollViewDemoTests.xctest */, 81 | ); 82 | name = Products; 83 | sourceTree = ""; 84 | }; 85 | AA3515291A6E380E0009DAF5 /* ScrollViewDemo */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | AA35152C1A6E380E0009DAF5 /* AppDelegate.swift */, 89 | AA3515511A7323910009DAF5 /* NSTimer+Addition.swift */, 90 | AA80945B1A77AD2F00846ED9 /* HYColor.swift */, 91 | AA35154C1A6E45C80009DAF5 /* ScrollView */, 92 | AA35152E1A6E380E0009DAF5 /* ScrollViewController.swift */, 93 | AA3515301A6E380E0009DAF5 /* Main.storyboard */, 94 | AA3515351A6E380E0009DAF5 /* LaunchScreen.xib */, 95 | AA35152A1A6E380E0009DAF5 /* Supporting Files */, 96 | ); 97 | path = ScrollViewDemo; 98 | sourceTree = ""; 99 | }; 100 | AA35152A1A6E380E0009DAF5 /* Supporting Files */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | AA78ABA11A7796E500D03F2E /* Images.xcassets */, 104 | AA35152B1A6E380E0009DAF5 /* Info.plist */, 105 | ); 106 | name = "Supporting Files"; 107 | sourceTree = ""; 108 | }; 109 | AA35153F1A6E380E0009DAF5 /* ScrollViewDemoTests */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | AA3515421A6E380E0009DAF5 /* ScrollViewDemoTests.swift */, 113 | AA3515401A6E380E0009DAF5 /* Supporting Files */, 114 | ); 115 | path = ScrollViewDemoTests; 116 | sourceTree = ""; 117 | }; 118 | AA3515401A6E380E0009DAF5 /* Supporting Files */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | AA3515411A6E380E0009DAF5 /* Info.plist */, 122 | ); 123 | name = "Supporting Files"; 124 | sourceTree = ""; 125 | }; 126 | AA35154C1A6E45C80009DAF5 /* ScrollView */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | AA35154D1A6E46000009DAF5 /* HYInfiniteScrollView.swift */, 130 | AA35154F1A6E46420009DAF5 /* HYPageControlView.swift */, 131 | ); 132 | name = ScrollView; 133 | sourceTree = ""; 134 | }; 135 | /* End PBXGroup section */ 136 | 137 | /* Begin PBXNativeTarget section */ 138 | AA3515261A6E380E0009DAF5 /* ScrollViewDemo */ = { 139 | isa = PBXNativeTarget; 140 | buildConfigurationList = AA3515461A6E380E0009DAF5 /* Build configuration list for PBXNativeTarget "ScrollViewDemo" */; 141 | buildPhases = ( 142 | AA3515231A6E380E0009DAF5 /* Sources */, 143 | AA3515241A6E380E0009DAF5 /* Frameworks */, 144 | AA3515251A6E380E0009DAF5 /* Resources */, 145 | ); 146 | buildRules = ( 147 | ); 148 | dependencies = ( 149 | ); 150 | name = ScrollViewDemo; 151 | productName = ScrollViewDemo; 152 | productReference = AA3515271A6E380E0009DAF5 /* ScrollViewDemo.app */; 153 | productType = "com.apple.product-type.application"; 154 | }; 155 | AA35153B1A6E380E0009DAF5 /* ScrollViewDemoTests */ = { 156 | isa = PBXNativeTarget; 157 | buildConfigurationList = AA3515491A6E380E0009DAF5 /* Build configuration list for PBXNativeTarget "ScrollViewDemoTests" */; 158 | buildPhases = ( 159 | AA3515381A6E380E0009DAF5 /* Sources */, 160 | AA3515391A6E380E0009DAF5 /* Frameworks */, 161 | AA35153A1A6E380E0009DAF5 /* Resources */, 162 | ); 163 | buildRules = ( 164 | ); 165 | dependencies = ( 166 | AA35153E1A6E380E0009DAF5 /* PBXTargetDependency */, 167 | ); 168 | name = ScrollViewDemoTests; 169 | productName = ScrollViewDemoTests; 170 | productReference = AA35153C1A6E380E0009DAF5 /* ScrollViewDemoTests.xctest */; 171 | productType = "com.apple.product-type.bundle.unit-test"; 172 | }; 173 | /* End PBXNativeTarget section */ 174 | 175 | /* Begin PBXProject section */ 176 | AA35151F1A6E380E0009DAF5 /* Project object */ = { 177 | isa = PBXProject; 178 | attributes = { 179 | LastUpgradeCheck = 0610; 180 | ORGANIZATIONNAME = " Harry Yan"; 181 | TargetAttributes = { 182 | AA3515261A6E380E0009DAF5 = { 183 | CreatedOnToolsVersion = 6.1.1; 184 | }; 185 | AA35153B1A6E380E0009DAF5 = { 186 | CreatedOnToolsVersion = 6.1.1; 187 | TestTargetID = AA3515261A6E380E0009DAF5; 188 | }; 189 | }; 190 | }; 191 | buildConfigurationList = AA3515221A6E380E0009DAF5 /* Build configuration list for PBXProject "ScrollViewDemo" */; 192 | compatibilityVersion = "Xcode 3.2"; 193 | developmentRegion = English; 194 | hasScannedForEncodings = 0; 195 | knownRegions = ( 196 | en, 197 | Base, 198 | ); 199 | mainGroup = AA35151E1A6E380E0009DAF5; 200 | productRefGroup = AA3515281A6E380E0009DAF5 /* Products */; 201 | projectDirPath = ""; 202 | projectRoot = ""; 203 | targets = ( 204 | AA3515261A6E380E0009DAF5 /* ScrollViewDemo */, 205 | AA35153B1A6E380E0009DAF5 /* ScrollViewDemoTests */, 206 | ); 207 | }; 208 | /* End PBXProject section */ 209 | 210 | /* Begin PBXResourcesBuildPhase section */ 211 | AA3515251A6E380E0009DAF5 /* Resources */ = { 212 | isa = PBXResourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | AA78ABA21A7796E500D03F2E /* Images.xcassets in Resources */, 216 | AA3515321A6E380E0009DAF5 /* Main.storyboard in Resources */, 217 | AA3515371A6E380E0009DAF5 /* LaunchScreen.xib in Resources */, 218 | ); 219 | runOnlyForDeploymentPostprocessing = 0; 220 | }; 221 | AA35153A1A6E380E0009DAF5 /* Resources */ = { 222 | isa = PBXResourcesBuildPhase; 223 | buildActionMask = 2147483647; 224 | files = ( 225 | ); 226 | runOnlyForDeploymentPostprocessing = 0; 227 | }; 228 | /* End PBXResourcesBuildPhase section */ 229 | 230 | /* Begin PBXSourcesBuildPhase section */ 231 | AA3515231A6E380E0009DAF5 /* Sources */ = { 232 | isa = PBXSourcesBuildPhase; 233 | buildActionMask = 2147483647; 234 | files = ( 235 | AA80945C1A77AD2F00846ED9 /* HYColor.swift in Sources */, 236 | AA3515501A6E46420009DAF5 /* HYPageControlView.swift in Sources */, 237 | AA35152F1A6E380E0009DAF5 /* ScrollViewController.swift in Sources */, 238 | AA3515521A7323910009DAF5 /* NSTimer+Addition.swift in Sources */, 239 | AA35154E1A6E46000009DAF5 /* HYInfiniteScrollView.swift in Sources */, 240 | AA35152D1A6E380E0009DAF5 /* AppDelegate.swift in Sources */, 241 | ); 242 | runOnlyForDeploymentPostprocessing = 0; 243 | }; 244 | AA3515381A6E380E0009DAF5 /* Sources */ = { 245 | isa = PBXSourcesBuildPhase; 246 | buildActionMask = 2147483647; 247 | files = ( 248 | AA3515431A6E380E0009DAF5 /* ScrollViewDemoTests.swift in Sources */, 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | }; 252 | /* End PBXSourcesBuildPhase section */ 253 | 254 | /* Begin PBXTargetDependency section */ 255 | AA35153E1A6E380E0009DAF5 /* PBXTargetDependency */ = { 256 | isa = PBXTargetDependency; 257 | target = AA3515261A6E380E0009DAF5 /* ScrollViewDemo */; 258 | targetProxy = AA35153D1A6E380E0009DAF5 /* PBXContainerItemProxy */; 259 | }; 260 | /* End PBXTargetDependency section */ 261 | 262 | /* Begin PBXVariantGroup section */ 263 | AA3515301A6E380E0009DAF5 /* Main.storyboard */ = { 264 | isa = PBXVariantGroup; 265 | children = ( 266 | AA3515311A6E380E0009DAF5 /* Base */, 267 | ); 268 | name = Main.storyboard; 269 | sourceTree = ""; 270 | }; 271 | AA3515351A6E380E0009DAF5 /* LaunchScreen.xib */ = { 272 | isa = PBXVariantGroup; 273 | children = ( 274 | AA3515361A6E380E0009DAF5 /* Base */, 275 | ); 276 | name = LaunchScreen.xib; 277 | sourceTree = ""; 278 | }; 279 | /* End PBXVariantGroup section */ 280 | 281 | /* Begin XCBuildConfiguration section */ 282 | AA3515441A6E380E0009DAF5 /* Debug */ = { 283 | isa = XCBuildConfiguration; 284 | buildSettings = { 285 | ALWAYS_SEARCH_USER_PATHS = NO; 286 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 287 | CLANG_CXX_LIBRARY = "libc++"; 288 | CLANG_ENABLE_MODULES = YES; 289 | CLANG_ENABLE_OBJC_ARC = YES; 290 | CLANG_WARN_BOOL_CONVERSION = YES; 291 | CLANG_WARN_CONSTANT_CONVERSION = YES; 292 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 293 | CLANG_WARN_EMPTY_BODY = YES; 294 | CLANG_WARN_ENUM_CONVERSION = YES; 295 | CLANG_WARN_INT_CONVERSION = YES; 296 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 297 | CLANG_WARN_UNREACHABLE_CODE = YES; 298 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 299 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 300 | COPY_PHASE_STRIP = NO; 301 | ENABLE_STRICT_OBJC_MSGSEND = YES; 302 | GCC_C_LANGUAGE_STANDARD = gnu99; 303 | GCC_DYNAMIC_NO_PIC = NO; 304 | GCC_OPTIMIZATION_LEVEL = 0; 305 | GCC_PREPROCESSOR_DEFINITIONS = ( 306 | "DEBUG=1", 307 | "$(inherited)", 308 | ); 309 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 310 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 311 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 312 | GCC_WARN_UNDECLARED_SELECTOR = YES; 313 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 314 | GCC_WARN_UNUSED_FUNCTION = YES; 315 | GCC_WARN_UNUSED_VARIABLE = YES; 316 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 317 | MTL_ENABLE_DEBUG_INFO = YES; 318 | ONLY_ACTIVE_ARCH = YES; 319 | SDKROOT = iphoneos; 320 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 321 | TARGETED_DEVICE_FAMILY = "1,2"; 322 | }; 323 | name = Debug; 324 | }; 325 | AA3515451A6E380E0009DAF5 /* Release */ = { 326 | isa = XCBuildConfiguration; 327 | buildSettings = { 328 | ALWAYS_SEARCH_USER_PATHS = NO; 329 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 330 | CLANG_CXX_LIBRARY = "libc++"; 331 | CLANG_ENABLE_MODULES = YES; 332 | CLANG_ENABLE_OBJC_ARC = YES; 333 | CLANG_WARN_BOOL_CONVERSION = YES; 334 | CLANG_WARN_CONSTANT_CONVERSION = YES; 335 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 336 | CLANG_WARN_EMPTY_BODY = YES; 337 | CLANG_WARN_ENUM_CONVERSION = YES; 338 | CLANG_WARN_INT_CONVERSION = YES; 339 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 340 | CLANG_WARN_UNREACHABLE_CODE = YES; 341 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 342 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 343 | COPY_PHASE_STRIP = YES; 344 | ENABLE_NS_ASSERTIONS = NO; 345 | ENABLE_STRICT_OBJC_MSGSEND = YES; 346 | GCC_C_LANGUAGE_STANDARD = gnu99; 347 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 348 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 349 | GCC_WARN_UNDECLARED_SELECTOR = YES; 350 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 351 | GCC_WARN_UNUSED_FUNCTION = YES; 352 | GCC_WARN_UNUSED_VARIABLE = YES; 353 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 354 | MTL_ENABLE_DEBUG_INFO = NO; 355 | SDKROOT = iphoneos; 356 | TARGETED_DEVICE_FAMILY = "1,2"; 357 | VALIDATE_PRODUCT = YES; 358 | }; 359 | name = Release; 360 | }; 361 | AA3515471A6E380E0009DAF5 /* Debug */ = { 362 | isa = XCBuildConfiguration; 363 | buildSettings = { 364 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 365 | INFOPLIST_FILE = ScrollViewDemo/Info.plist; 366 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 367 | PRODUCT_NAME = "$(TARGET_NAME)"; 368 | }; 369 | name = Debug; 370 | }; 371 | AA3515481A6E380E0009DAF5 /* Release */ = { 372 | isa = XCBuildConfiguration; 373 | buildSettings = { 374 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 375 | INFOPLIST_FILE = ScrollViewDemo/Info.plist; 376 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 377 | PRODUCT_NAME = "$(TARGET_NAME)"; 378 | }; 379 | name = Release; 380 | }; 381 | AA35154A1A6E380E0009DAF5 /* Debug */ = { 382 | isa = XCBuildConfiguration; 383 | buildSettings = { 384 | BUNDLE_LOADER = "$(TEST_HOST)"; 385 | FRAMEWORK_SEARCH_PATHS = ( 386 | "$(SDKROOT)/Developer/Library/Frameworks", 387 | "$(inherited)", 388 | ); 389 | GCC_PREPROCESSOR_DEFINITIONS = ( 390 | "DEBUG=1", 391 | "$(inherited)", 392 | ); 393 | INFOPLIST_FILE = ScrollViewDemoTests/Info.plist; 394 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 395 | PRODUCT_NAME = "$(TARGET_NAME)"; 396 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ScrollViewDemo.app/ScrollViewDemo"; 397 | }; 398 | name = Debug; 399 | }; 400 | AA35154B1A6E380E0009DAF5 /* Release */ = { 401 | isa = XCBuildConfiguration; 402 | buildSettings = { 403 | BUNDLE_LOADER = "$(TEST_HOST)"; 404 | FRAMEWORK_SEARCH_PATHS = ( 405 | "$(SDKROOT)/Developer/Library/Frameworks", 406 | "$(inherited)", 407 | ); 408 | INFOPLIST_FILE = ScrollViewDemoTests/Info.plist; 409 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 410 | PRODUCT_NAME = "$(TARGET_NAME)"; 411 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ScrollViewDemo.app/ScrollViewDemo"; 412 | }; 413 | name = Release; 414 | }; 415 | /* End XCBuildConfiguration section */ 416 | 417 | /* Begin XCConfigurationList section */ 418 | AA3515221A6E380E0009DAF5 /* Build configuration list for PBXProject "ScrollViewDemo" */ = { 419 | isa = XCConfigurationList; 420 | buildConfigurations = ( 421 | AA3515441A6E380E0009DAF5 /* Debug */, 422 | AA3515451A6E380E0009DAF5 /* Release */, 423 | ); 424 | defaultConfigurationIsVisible = 0; 425 | defaultConfigurationName = Release; 426 | }; 427 | AA3515461A6E380E0009DAF5 /* Build configuration list for PBXNativeTarget "ScrollViewDemo" */ = { 428 | isa = XCConfigurationList; 429 | buildConfigurations = ( 430 | AA3515471A6E380E0009DAF5 /* Debug */, 431 | AA3515481A6E380E0009DAF5 /* Release */, 432 | ); 433 | defaultConfigurationIsVisible = 0; 434 | defaultConfigurationName = Release; 435 | }; 436 | AA3515491A6E380E0009DAF5 /* Build configuration list for PBXNativeTarget "ScrollViewDemoTests" */ = { 437 | isa = XCConfigurationList; 438 | buildConfigurations = ( 439 | AA35154A1A6E380E0009DAF5 /* Debug */, 440 | AA35154B1A6E380E0009DAF5 /* Release */, 441 | ); 442 | defaultConfigurationIsVisible = 0; 443 | defaultConfigurationName = Release; 444 | }; 445 | /* End XCConfigurationList section */ 446 | }; 447 | rootObject = AA35151F1A6E380E0009DAF5 /* Project object */; 448 | } 449 | -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo.xcodeproj/project.xcworkspace/xcuserdata/ximinyan.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harryyan/InfiniteScrollView/8b39989ee1df8bc6f9bf796c79616e512f1d11e4/ScrollViewDemo/ScrollViewDemo.xcodeproj/project.xcworkspace/xcuserdata/ximinyan.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo.xcodeproj/xcuserdata/ximinyan.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo.xcodeproj/xcuserdata/ximinyan.xcuserdatad/xcschemes/ScrollViewDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 94 | 100 | 101 | 102 | 103 | 105 | 106 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo.xcodeproj/xcuserdata/ximinyan.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ScrollViewDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | AA3515261A6E380E0009DAF5 16 | 17 | primary 18 | 19 | 20 | AA35153B1A6E380E0009DAF5 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harryyan/InfiniteScrollView/8b39989ee1df8bc6f9bf796c79616e512f1d11e4/ScrollViewDemo/ScrollViewDemo/.DS_Store -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ScrollViewDemo 4 | // 5 | // Created by Harry Yan on 15/1/20. 6 | // Copyright (c) 2015年 Harry Yan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | 19 | window = UIWindow(frame: UIScreen.mainScreen().bounds) 20 | window?.backgroundColor = UIColor.whiteColor() 21 | var navigation = UINavigationController(rootViewController: ScrollViewController()) 22 | navigation.navigationBar.barTintColor = UIColorFromRGB(0x00b377) 23 | window?.rootViewController = navigation 24 | window?.makeKeyAndVisible() 25 | 26 | return true 27 | } 28 | 29 | func applicationWillResignActive(application: UIApplication) { 30 | // 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. 31 | // 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. 32 | } 33 | 34 | func applicationDidEnterBackground(application: UIApplication) { 35 | // 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. 36 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 37 | } 38 | 39 | func applicationWillEnterForeground(application: UIApplication) { 40 | // 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. 41 | } 42 | 43 | func applicationDidBecomeActive(application: UIApplication) { 44 | // 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. 45 | } 46 | 47 | func applicationWillTerminate(application: UIApplication) { 48 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 49 | } 50 | 51 | 52 | } 53 | 54 | -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/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 | -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/HYColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HYColor.swift 3 | // ScrollViewDemo 4 | // 5 | // Created by Harry Yan on 15/1/27. 6 | // Copyright (c) 2015年 Harry Yan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | func UIColorFromRGB(rgbValue: UInt) -> UIColor { 12 | return UIColor( 13 | red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, 14 | green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, 15 | blue: CGFloat(rgbValue & 0x0000FF) / 255.0, 16 | alpha: CGFloat(1.0) 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/HYInfiniteScrollView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HYInfiniteScrollView.swift 3 | // ScrollViewDemo 4 | // 5 | // Created by Harry Yan on 15/1/20. 6 | // Copyright (c) 2015年 Harry Yan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol HYInfiniteScrollViewDelegate { 12 | func didClickPageAtIndex(scrollView: UIScrollView, pageIndex: Int) 13 | func didSwipeToPage(scrollView: UIScrollView, pageIndex: Int) 14 | } 15 | 16 | class HYInfiniteScrollView: UIView, UIScrollViewDelegate{ 17 | 18 | var isSliderBarControl: Bool = true 19 | var isPagesControl: Bool = false 20 | var isDragging: Bool = false //用户是否拖拽 21 | var currentPageIndex: Int = 0 //当前页面 22 | var items: NSArray? //内容item 23 | var contentViews: NSMutableArray? //页面内容view 24 | var pictureViews: NSMutableArray? //图片数组 25 | var extraImageViews:NSMutableArray? //当total page count为两个时的容器 26 | var scrollView: UIScrollView! //容器 27 | var animationTimer: NSTimer? //定时器 28 | var pageControl: UIPageControl? = nil //系统默认page control 29 | 30 | var delegate: HYInfiniteScrollViewDelegate? //代理 31 | 32 | //是否启用动画 33 | var animationEnable: Bool = true { 34 | willSet { 35 | if newValue { 36 | if (nil != self.animationTimer) { 37 | self.animationTimer?.invalidate(); 38 | self.animationTimer = nil; 39 | } 40 | 41 | animationTimer = NSTimer.scheduledTimerWithTimeInterval(_animationDuration, target: self, selector: "animationTimerDidFired:", userInfo: nil, repeats: true) 42 | 43 | self.animationTimer?.pauseTimer() 44 | } 45 | } 46 | } 47 | 48 | var slideBarEnable: Bool = true { 49 | willSet{ 50 | if newValue { 51 | isSliderBarControl = true 52 | isPagesControl = false 53 | sliderPageControl.hidden = false 54 | }else { 55 | sliderPageControl.hidden = true; 56 | } 57 | } 58 | } 59 | 60 | var _animationDuration: NSTimeInterval = 3.0 //滚动的时间间隔 61 | var animationDuration: NSTimeInterval? { 62 | willSet { 63 | _animationDuration = newValue! 64 | } 65 | } 66 | 67 | var _totalPageCount: Int? = 0 //页面个数 68 | var totalPageCount: Int? { 69 | willSet(totalPage){ 70 | _totalPageCount = totalPage 71 | 72 | if isPagesControl { 73 | pageControl?.numberOfPages = totalPage! 74 | } 75 | 76 | _sliderPageControl?.pageCount = totalPage! 77 | 78 | if _totalPageCount == 1 { 79 | self.configContenViews() 80 | }else if _totalPageCount > 1 { 81 | self.configContenViews() 82 | self.animationTimer?.resumeTimerAfterTimeInterval(_animationDuration) 83 | 84 | } 85 | 86 | self.updateSliderProgress() 87 | } 88 | } 89 | 90 | 91 | var _sliderPageControl:HYPageControlView? = nil 92 | //计算属性 类似OC get方法 93 | var sliderPageControl: HYPageControlView { 94 | get{ 95 | if _sliderPageControl == nil { 96 | let pageRect = CGRectMake(0, self.bounds.size.height - 3, self.bounds.size.width, 3); 97 | _sliderPageControl = HYPageControlView(frame:pageRect) 98 | _sliderPageControl?.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleTopMargin 99 | 100 | self.addSubview(_sliderPageControl!) 101 | } 102 | 103 | return _sliderPageControl! 104 | } 105 | 106 | set{ 107 | //TODO 108 | } 109 | 110 | } 111 | 112 | 113 | //MARK: Life Cycle 114 | 115 | override init(frame: CGRect) { 116 | super.init(frame: frame) 117 | 118 | self.autoresizesSubviews = true 119 | 120 | self.scrollView = UIScrollView(frame: self.bounds) 121 | self.scrollView.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight 122 | self.scrollView.contentMode = UIViewContentMode.Center 123 | self.scrollView.contentSize = CGSizeMake(3 * CGRectGetWidth(self.scrollView!.frame), 0) 124 | self.scrollView.contentOffset = CGPointMake(CGRectGetWidth(self.scrollView!.frame), 0); 125 | self.scrollView.pagingEnabled = true; 126 | self.scrollView.scrollsToTop = false; 127 | self.scrollView.delegate = self 128 | 129 | self.addSubview(self.scrollView) 130 | } 131 | 132 | override func layoutSubviews() { 133 | self.scrollView?.contentSize = CGSizeMake(3 * CGRectGetWidth(self.scrollView!.frame), 0) 134 | self.configContenViews() 135 | } 136 | 137 | 138 | required init(coder aDecoder: NSCoder) { 139 | fatalError("init(coder:) has not been implemented") 140 | } 141 | 142 | 143 | 144 | //MARK: Private 145 | 146 | func configContenViews() { 147 | self.removeSubViewsFromScrollView() 148 | self.configScrollViewContentDataSource() 149 | 150 | var counter: CGFloat = 0 151 | 152 | for contentView in self.contentViews! { 153 | (contentView as UIImageView).userInteractionEnabled = true 154 | var rightRect: CGRect = self.scrollView.bounds; 155 | rightRect.origin = CGPointMake(CGRectGetWidth(self.scrollView.frame) * (counter++), 0) 156 | (contentView as UIImageView).frame = rightRect 157 | 158 | self.scrollView.addSubview(contentView as UIImageView) 159 | } 160 | 161 | if _totalPageCount == 1 { 162 | self.animationEnable = false 163 | self.scrollView.scrollEnabled = false 164 | self.scrollView.contentSize = CGSizeZero 165 | self.scrollView.contentOffset = CGPointZero 166 | }else { 167 | self.scrollView.scrollEnabled = true 168 | self.scrollView.contentOffset = CGPointMake(self.scrollView.bounds.size.width, 0) 169 | } 170 | 171 | } 172 | 173 | 174 | func removeSubViewsFromScrollView() { 175 | let scrollViewSubviews: NSArray = self.scrollView.subviews 176 | 177 | for view in scrollViewSubviews { 178 | view.removeFromSuperview() 179 | } 180 | } 181 | 182 | 183 | func configScrollViewContentDataSource() { 184 | let previousPageIndex: Int = self.turnToValidNextPageWithPageIndex(self.currentPageIndex - 1) 185 | let rearPageIndex: Int = self.turnToValidNextPageWithPageIndex(self.currentPageIndex + 1) 186 | 187 | if self.contentViews == nil { 188 | self.contentViews = NSMutableArray() 189 | } 190 | 191 | self.contentViews?.removeAllObjects() 192 | 193 | //强类型检查 194 | if self.pictureViews?.count > 2 { 195 | self.contentViews?.addObject(self.pictureViewAtPageIndex(previousPageIndex)!) 196 | self.contentViews?.addObject(self.pictureViewAtPageIndex(self.currentPageIndex)!) 197 | self.contentViews?.addObject(self.pictureViewAtPageIndex(rearPageIndex)!) 198 | }else if self.pictureViews?.count == 1 { 199 | self.contentViews?.addObject(self.pictureViews?.firstObject as UIImageView) 200 | }else if self.pictureViews?.count == 2 { 201 | self.contentViews?.addObject(self.pictureViewAtPageIndex(previousPageIndex)!) 202 | self.contentViews?.addObject(self.pictureViewAtPageIndex(self.currentPageIndex)!) 203 | 204 | if rearPageIndex < self.extraImageViews?.count { 205 | self.contentViews?.addObject(self.extraImageViews?.objectAtIndex(rearPageIndex) as UIImageView) 206 | } 207 | } 208 | } 209 | 210 | 211 | func updateSliderProgress() { 212 | if _totalPageCount == 0 { 213 | return 214 | } 215 | 216 | let scrollViewWidth: CGFloat = self.scrollView.frame.width 217 | let totalLength: CGFloat = scrollViewWidth * CGFloat(_totalPageCount!) 218 | var dynamicLength: CGFloat = (CGFloat(self.currentPageIndex - 1) * scrollViewWidth + self.scrollView.contentOffset.x) 219 | var progress: CGFloat = dynamicLength / totalLength 220 | 221 | self.sliderPageControl.slideWithProgress(progress) 222 | } 223 | 224 | 225 | //统一接口入口, 将从网络取下的内容添加到里面 226 | func reloadActivityItem(items: NSArray){ 227 | if (items.count == 0) { 228 | self.scrollView.contentSize = CGSizeMake(0,0); 229 | self.scrollView.scrollEnabled = false; 230 | self.items = nil; 231 | } 232 | 233 | if (items == self.items) { 234 | return; 235 | } 236 | 237 | self.items = items 238 | self.pictureViews = NSMutableArray(capacity: self.items!.count) 239 | 240 | var count: Int = 0 241 | 242 | for item in items { 243 | var imageView: UIImageView = UIImageView(frame: CGRectMake(0,0,self.bounds.size.width,self.scrollView.frame.size.height)) 244 | imageView.autoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth 245 | imageView.contentMode = UIViewContentMode.ScaleAspectFill 246 | imageView.clipsToBounds = true 247 | imageView.image = UIImage(named: "\(count++)") 248 | 249 | self.pictureViews?.addObject(imageView) 250 | } 251 | 252 | count = 0 253 | 254 | if self.pictureViews!.count == 2 { 255 | self.extraImageViews = NSMutableArray() 256 | 257 | for item in items { 258 | var imageView: UIImageView = UIImageView(frame: CGRectMake(0,0,self.bounds.size.width,self.scrollView.frame.size.height)) 259 | imageView.autoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth 260 | imageView.contentMode = UIViewContentMode.ScaleAspectFill 261 | imageView.image = UIImage(named: "\(count++)") 262 | imageView.clipsToBounds = true 263 | 264 | self.extraImageViews?.addObject(imageView) 265 | } 266 | } 267 | 268 | self.currentPageIndex = 0; 269 | self.scrollView.contentOffset = CGPointZero; 270 | self.totalPageCount = self.pictureViews?.count 271 | } 272 | 273 | 274 | func turnToValidNextPageWithPageIndex(currentPageIndex: Int) -> Int { 275 | if(currentPageIndex == -1) { 276 | return _totalPageCount! - 1; 277 | } else if (currentPageIndex == _totalPageCount) { 278 | return 0; 279 | } else { 280 | return currentPageIndex; 281 | } 282 | } 283 | 284 | 285 | func pictureViewAtPageIndex(index: Int) -> UIImageView? { 286 | 287 | if index < 0 || index >= self.pictureViews?.count { 288 | return nil; 289 | } 290 | 291 | return self.pictureViews?.objectAtIndex(index) as? UIImageView 292 | } 293 | 294 | 295 | //MARK: UIScrollView Delegate 296 | 297 | func scrollViewWillBeginDragging(scrollView: UIScrollView) { 298 | self.animationTimer!.pauseTimer() 299 | } 300 | 301 | 302 | func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) { 303 | self.animationTimer!.resumeTimerAfterTimeInterval(_animationDuration) 304 | } 305 | 306 | 307 | func scrollViewDidScroll(scrollView: UIScrollView) { 308 | var contentOffsetX: CGFloat = scrollView.contentOffset.x 309 | var scrollViewWidth: CGFloat = CGRectGetWidth(scrollView.frame) 310 | 311 | if(contentOffsetX >= (2 * scrollViewWidth) && _totalPageCount > 1) { 312 | currentPageIndex = turnToValidNextPageWithPageIndex(currentPageIndex + 1) 313 | configContenViews() 314 | } 315 | if(contentOffsetX <= 0 && _totalPageCount > 1){ 316 | self.currentPageIndex = turnToValidNextPageWithPageIndex(currentPageIndex - 1) 317 | configContenViews() 318 | } 319 | 320 | updateSliderProgress() 321 | } 322 | 323 | func scrollViewDidEndDecelerating(scrollView: UIScrollView) { 324 | self.scrollView.setContentOffset(CGPointMake(self.scrollView.bounds.width, 0), animated: true) 325 | } 326 | 327 | 328 | 329 | //MARK: Action 330 | 331 | func animationTimerDidFired(timer: NSTimer) { 332 | var offsetX: CGFloat = floor(self.scrollView.contentOffset.x / CGRectGetWidth(self.scrollView.frame)) * CGRectGetWidth(self.scrollView.frame); 333 | var newOffset: CGPoint = CGPointMake(offsetX + CGRectGetWidth(self.scrollView.frame), self.scrollView.contentOffset.y); 334 | self.scrollView.setContentOffset(newOffset, animated: true) 335 | } 336 | } 337 | 338 | 339 | -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/HYPageControlView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HYPageControlView.swift 3 | // ScrollViewDemo 4 | // 5 | // Created by Harry Yan on 15/1/20. 6 | // Copyright (c) 2015年 Harry Yan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class HYPageControlView: UIView { 12 | 13 | var pageIndex: Int = 0 14 | var progress: CGFloat = 0.0 15 | 16 | var currentCursor: UIView? 17 | var fakeCursor: UIView? 18 | var tempCursor: UIView? 19 | var bottomBar: UIView? 20 | 21 | var numberOfPages: Int = 0 22 | var pageCount: Int { 23 | get{ 24 | return numberOfPages 25 | } 26 | 27 | set{ 28 | if numberOfPages != newValue { 29 | numberOfPages = newValue 30 | self.setNeedsLayout() 31 | } 32 | } 33 | } 34 | 35 | //MARK: Life Cycle 36 | 37 | override init(frame: CGRect) { 38 | super.init(frame: frame) 39 | 40 | self.backgroundColor = UIColor(white: 0, alpha: 0.3) 41 | self.currentCursor = buildCursorView() 42 | self.fakeCursor = buildCursorView() 43 | 44 | self.addSubview(self.currentCursor!) 45 | self.addSubview(self.fakeCursor!) 46 | 47 | } 48 | 49 | required init(coder aDecoder: NSCoder) { 50 | fatalError("NSCoding not suppport") 51 | } 52 | 53 | 54 | override func layoutSubviews() { 55 | if (1 >= self.numberOfPages) { 56 | self.currentCursor!.frame = CGRectZero; 57 | self.fakeCursor!.frame = CGRectZero; 58 | self.hidden = true; 59 | return; 60 | } 61 | 62 | self.hidden = false; 63 | let cursorWidth: CGFloat = self.frame.size.width / CGFloat(self.numberOfPages); 64 | self.currentCursor!.frame = CGRectMake(CGFloat(self.pageIndex) * cursorWidth, 0, cursorWidth, self.frame.size.height); 65 | self.fakeCursor!.frame = CGRectMake(-cursorWidth, 0, cursorWidth, self.frame.size.height); 66 | } 67 | 68 | //MARK: Public 69 | 70 | func slideWithProgress(progress: CGFloat) { 71 | self.progress = progress 72 | let originX: CGFloat = self.bounds.size.width * progress; 73 | var currentFrame: CGRect = self.currentCursor!.frame; 74 | currentFrame.origin.x = originX; 75 | self.currentCursor!.frame = currentFrame; 76 | 77 | var fakeFrame: CGRect = self.fakeCursor!.frame; 78 | var fakeOriginX: CGFloat = -fakeFrame.origin.x; 79 | 80 | //三种情况 81 | if (originX + currentFrame.size.width > self.bounds.size.width) { //1.右边超出 82 | fakeOriginX = -(self.bounds.size.width - originX); 83 | } else if (originX < 0) { //左边超出 84 | fakeOriginX = self.bounds.size.width + originX; 85 | } else { //在中间, fake应该隐藏掉 86 | fakeOriginX = -self.bounds.size.width; 87 | } 88 | 89 | fakeFrame.origin.x = fakeOriginX; 90 | self.fakeCursor!.frame = fakeFrame; 91 | 92 | //假如fake完全显示了,则与currentCursor交换 93 | if (fakeOriginX > 0 && fakeOriginX < self.bounds.size.width) { 94 | let tempView: UIView = self.currentCursor!; 95 | self.currentCursor = self.fakeCursor; 96 | self.fakeCursor = tempView; 97 | } 98 | } 99 | 100 | 101 | 102 | //MARK: Private 103 | 104 | func buildCursorView() -> UIView { 105 | var cursorView: UIView = UIView(frame: CGRectMake(-10, 0, 10, self.bounds.size.height)) 106 | 107 | //TODO 宏定义颜色值 108 | cursorView.backgroundColor = UIColorFromRGB(0xf26052) 109 | cursorView.layer.cornerRadius = 1.0 110 | cursorView.layer.masksToBounds = true 111 | 112 | return cursorView 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/Images.xcassets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harryyan/InfiniteScrollView/8b39989ee1df8bc6f9bf796c79616e512f1d11e4/ScrollViewDemo/ScrollViewDemo/Images.xcassets/.DS_Store -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/Images.xcassets/0.imageset/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harryyan/InfiniteScrollView/8b39989ee1df8bc6f9bf796c79616e512f1d11e4/ScrollViewDemo/ScrollViewDemo/Images.xcassets/0.imageset/1.jpg -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/Images.xcassets/0.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "1.jpg" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/Images.xcassets/1.imageset/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harryyan/InfiniteScrollView/8b39989ee1df8bc6f9bf796c79616e512f1d11e4/ScrollViewDemo/ScrollViewDemo/Images.xcassets/1.imageset/2.jpg -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/Images.xcassets/1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "2.jpg" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/Images.xcassets/2.imageset/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harryyan/InfiniteScrollView/8b39989ee1df8bc6f9bf796c79616e512f1d11e4/ScrollViewDemo/ScrollViewDemo/Images.xcassets/2.imageset/3.jpg -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/Images.xcassets/2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "3.jpg" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/Images.xcassets/3.imageset/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harryyan/InfiniteScrollView/8b39989ee1df8bc6f9bf796c79616e512f1d11e4/ScrollViewDemo/ScrollViewDemo/Images.xcassets/3.imageset/5.jpg -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/Images.xcassets/3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "5.jpg" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/Images.xcassets/4.imageset/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harryyan/InfiniteScrollView/8b39989ee1df8bc6f9bf796c79616e512f1d11e4/ScrollViewDemo/ScrollViewDemo/Images.xcassets/4.imageset/4.jpg -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/Images.xcassets/4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "4.jpg" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/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" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.harry.$(PRODUCT_NAME:rfc1034identifier) 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 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/NSTimer+Addition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSTimer+Addition.swift 3 | // ScrollViewDemo 4 | // 5 | // Created by Harry Yan on 15/1/24. 6 | // Copyright (c) 2015年 Harry Yan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSTimer{ 12 | 13 | func pauseTimer(){ 14 | if !self.valid { 15 | return 16 | } 17 | 18 | self.fireDate = NSDate.distantFuture() as NSDate 19 | } 20 | 21 | func resumeTimer(){ 22 | if !self.valid { 23 | return 24 | } 25 | 26 | self.fireDate = NSDate.distantPast() as NSDate 27 | } 28 | 29 | func resumeTimerAfterTimeInterval(interVal: NSTimeInterval){ 30 | if !self.valid { 31 | return 32 | } 33 | 34 | self.fireDate = NSDate(timeIntervalSinceNow:interVal) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/ScrollViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // ScrollViewDemo 4 | // 5 | // Created by Harry Yan on 15/1/20. 6 | // Copyright (c) 2015年 Harry Yan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ScrollViewController: UIViewController, HYInfiniteScrollViewDelegate, UIScrollViewDelegate{ 12 | 13 | var scrollView: HYInfiniteScrollView! //无限滑动scrollview 14 | 15 | //MARK : Life Cycle 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | self.title = "ScrollView Demo" 21 | self.navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()] 22 | self.automaticallyAdjustsScrollViewInsets = false 23 | 24 | //初始化ScrollView 25 | let scrollViewRect: CGRect = CGRectMake(0, self.navigationController!.navigationBar.bounds.height, self.view.bounds.width, 200) 26 | self.scrollView = HYInfiniteScrollView.init(frame: scrollViewRect) 27 | self.scrollView.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight 28 | self.scrollView.animationEnable = true 29 | self.scrollView.animationDuration = 3.0 30 | self.scrollView.slideBarEnable = true 31 | self.scrollView.delegate = self 32 | 33 | //这里只是做一个简单的循环, 可扩展成从网络上拉数据,自己封装数据结构传值 34 | let imgArr: NSArray = [1,2,3,4,5] 35 | self.scrollView.reloadActivityItem(imgArr) 36 | 37 | self.view.addSubview(self.scrollView) 38 | } 39 | 40 | override func didReceiveMemoryWarning() { 41 | super.didReceiveMemoryWarning() 42 | // Dispose of any resources that can be recreated. 43 | } 44 | 45 | 46 | 47 | //MARK: Private 48 | 49 | 50 | //Mark: HYInfiniteScrollViewDelegate 51 | 52 | func didClickPageAtIndex(scrollView: UIScrollView, pageIndex: Int) { 53 | 54 | } 55 | 56 | func didSwipeToPage(scrollView: UIScrollView, pageIndex: Int) { 57 | 58 | } 59 | 60 | } 61 | 62 | -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/snapshot/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harryyan/InfiniteScrollView/8b39989ee1df8bc6f9bf796c79616e512f1d11e4/ScrollViewDemo/ScrollViewDemo/snapshot/.DS_Store -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/snapshot/1-test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harryyan/InfiniteScrollView/8b39989ee1df8bc6f9bf796c79616e512f1d11e4/ScrollViewDemo/ScrollViewDemo/snapshot/1-test.jpg -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemo/snapshot/2-test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harryyan/InfiniteScrollView/8b39989ee1df8bc6f9bf796c79616e512f1d11e4/ScrollViewDemo/ScrollViewDemo/snapshot/2-test.jpg -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemoTests/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harryyan/InfiniteScrollView/8b39989ee1df8bc6f9bf796c79616e512f1d11e4/ScrollViewDemo/ScrollViewDemoTests/.DS_Store -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.harry.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ScrollViewDemo/ScrollViewDemoTests/ScrollViewDemoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollViewDemoTests.swift 3 | // ScrollViewDemoTests 4 | // 5 | // Created by Harry Yan on 15/1/20. 6 | // Copyright (c) 2015年 Harry Yan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class ScrollViewDemoTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measureBlock() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | --------------------------------------------------------------------------------