├── .gitignore ├── .swift-version ├── ACTabScrollView.podspec ├── ACTabScrollView.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── ACTabScrollView ├── AppDelegate.swift ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── ContentViewController.swift ├── DemoViewController.swift ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── thumbnail-1.imageset │ │ ├── 1-1.jpg │ │ └── Contents.json │ ├── thumbnail-10.imageset │ │ ├── -10.png │ │ └── Contents.json │ ├── thumbnail-11.imageset │ │ ├── -11.jpg │ │ └── Contents.json │ ├── thumbnail-12.imageset │ │ ├── -12.jpg │ │ └── Contents.json │ ├── thumbnail-13.imageset │ │ ├── -13.jpg │ │ └── Contents.json │ ├── thumbnail-2.imageset │ │ ├── -2.jpg │ │ └── Contents.json │ ├── thumbnail-3.imageset │ │ ├── -3.jpg │ │ └── Contents.json │ ├── thumbnail-4.imageset │ │ ├── -4.jpg │ │ └── Contents.json │ ├── thumbnail-5.imageset │ │ ├── 1-2.jpg │ │ └── Contents.json │ ├── thumbnail-6.imageset │ │ ├── -6.jpg │ │ └── Contents.json │ ├── thumbnail-7.imageset │ │ ├── -7.jpg │ │ └── Contents.json │ ├── thumbnail-8.imageset │ │ ├── -8.jpg │ │ └── Contents.json │ └── thumbnail-9.imageset │ │ ├── -9.jpg │ │ └── Contents.json ├── Info.plist ├── MockData.swift └── NewsViewController.swift ├── ACTabScrollViewTests ├── ACTabScrollViewTests.swift └── Info.plist ├── LICENSE.md ├── README.md ├── Screenshots ├── demo-1.gif ├── demo-2.gif ├── demo-3.gif └── usage-1.png └── Sources ├── ACTabScrollView+Protocol.swift └── ACTabScrollView.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | 29 | ## Playgrounds 30 | timeline.xctimeline 31 | playground.xcworkspace 32 | 33 | # Swift Package Manager 34 | # 35 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 36 | # Packages/ 37 | .build/ 38 | 39 | # CocoaPods 40 | # 41 | # We recommend against adding the Pods directory to your .gitignore. However 42 | # you should judge for yourself, the pros and cons are mentioned at: 43 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 44 | # 45 | # Pods/ 46 | 47 | # Carthage 48 | # 49 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 50 | # Carthage/Checkouts 51 | 52 | Carthage/Build 53 | 54 | # fastlane 55 | # 56 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 57 | # screenshots whenever they are needed. 58 | # For more information about the recommended setup visit: 59 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 60 | 61 | fastlane/report.xml 62 | fastlane/screenshots 63 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /ACTabScrollView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "ACTabScrollView" 4 | s.version = "0.3.0" 5 | s.summary = "A fancy `Menu` and `Pager` UI extends `UIScrollView` with elegant, smooth and synchronized scrolling `tabs`." 6 | 7 | s.description = <<-DESC 8 | A fancy `Menu` and `Pager` UI extends `UIScrollView` with elegant, smooth and synchronized scrolling `tabs`. 9 | DESC 10 | 11 | s.homepage = "https://github.com/azurechen/ACTabScrollView" 12 | s.license = "MIT" 13 | s.author = { "Azure Chen" => "azure517981@gmail.com" } 14 | s.source = { :git => "https://github.com/azurechen/ACTabScrollView.git", :tag => "v0.3.0" } 15 | s.platforms = { :ios => "8.0" } 16 | 17 | s.source_files = "Sources/**/*.swift" 18 | 19 | end 20 | -------------------------------------------------------------------------------- /ACTabScrollView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 530068331CEDDE7600A80F09 /* ContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 530068321CEDDE7600A80F09 /* ContentViewController.swift */; }; 11 | 530068351CF0512F00A80F09 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 530068341CF0512F00A80F09 /* MockData.swift */; }; 12 | 53887DDB1CE8AD3D001E6FD3 /* DemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53887DDA1CE8AD3D001E6FD3 /* DemoViewController.swift */; }; 13 | 6DB0CD261B8462B90069AE39 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB0CD251B8462B90069AE39 /* AppDelegate.swift */; }; 14 | 6DB0CD281B8462B90069AE39 /* NewsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB0CD271B8462B90069AE39 /* NewsViewController.swift */; }; 15 | 6DB0CD2B1B8462B90069AE39 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6DB0CD291B8462B90069AE39 /* Main.storyboard */; }; 16 | 6DB0CD2D1B8462B90069AE39 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6DB0CD2C1B8462B90069AE39 /* Images.xcassets */; }; 17 | 6DB0CD301B8462B90069AE39 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6DB0CD2E1B8462B90069AE39 /* LaunchScreen.xib */; }; 18 | 6DB0CD3C1B8462B90069AE39 /* ACTabScrollViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB0CD3B1B8462B90069AE39 /* ACTabScrollViewTests.swift */; }; 19 | 6DB0CD471B8464FC0069AE39 /* ACTabScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB0CD461B8464FC0069AE39 /* ACTabScrollView.swift */; }; 20 | 98A418CC1CC8BE3700718CA2 /* ACTabScrollView+Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A418CB1CC8BE3700718CA2 /* ACTabScrollView+Protocol.swift */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | 6DB0CD361B8462B90069AE39 /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = 6DB0CD181B8462B90069AE39 /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = 6DB0CD1F1B8462B90069AE39; 29 | remoteInfo = ACTabScrollView; 30 | }; 31 | /* End PBXContainerItemProxy section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 530068321CEDDE7600A80F09 /* ContentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentViewController.swift; sourceTree = ""; }; 35 | 530068341CF0512F00A80F09 /* MockData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockData.swift; sourceTree = ""; }; 36 | 53887DDA1CE8AD3D001E6FD3 /* DemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoViewController.swift; sourceTree = ""; }; 37 | 6DB0CD201B8462B90069AE39 /* ACTabScrollView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ACTabScrollView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 6DB0CD241B8462B90069AE39 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | 6DB0CD251B8462B90069AE39 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 40 | 6DB0CD271B8462B90069AE39 /* NewsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsViewController.swift; sourceTree = ""; }; 41 | 6DB0CD2A1B8462B90069AE39 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | 6DB0CD2C1B8462B90069AE39 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 43 | 6DB0CD2F1B8462B90069AE39 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 44 | 6DB0CD351B8462B90069AE39 /* ACTabScrollView.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ACTabScrollView.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | 6DB0CD3A1B8462B90069AE39 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46 | 6DB0CD3B1B8462B90069AE39 /* ACTabScrollViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACTabScrollViewTests.swift; sourceTree = ""; }; 47 | 6DB0CD461B8464FC0069AE39 /* ACTabScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ACTabScrollView.swift; sourceTree = ""; }; 48 | 98A418CB1CC8BE3700718CA2 /* ACTabScrollView+Protocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ACTabScrollView+Protocol.swift"; sourceTree = ""; }; 49 | /* End PBXFileReference section */ 50 | 51 | /* Begin PBXFrameworksBuildPhase section */ 52 | 6DB0CD1D1B8462B90069AE39 /* Frameworks */ = { 53 | isa = PBXFrameworksBuildPhase; 54 | buildActionMask = 2147483647; 55 | files = ( 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | 6DB0CD321B8462B90069AE39 /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | /* End PBXFrameworksBuildPhase section */ 67 | 68 | /* Begin PBXGroup section */ 69 | 6DB0CD171B8462B90069AE39 = { 70 | isa = PBXGroup; 71 | children = ( 72 | 6DB0CD451B8464700069AE39 /* Sources */, 73 | 6DB0CD221B8462B90069AE39 /* ACTabScrollView */, 74 | 6DB0CD381B8462B90069AE39 /* ACTabScrollViewTests */, 75 | 6DB0CD211B8462B90069AE39 /* Products */, 76 | ); 77 | sourceTree = ""; 78 | }; 79 | 6DB0CD211B8462B90069AE39 /* Products */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 6DB0CD201B8462B90069AE39 /* ACTabScrollView.app */, 83 | 6DB0CD351B8462B90069AE39 /* ACTabScrollView.xctest */, 84 | ); 85 | name = Products; 86 | sourceTree = ""; 87 | }; 88 | 6DB0CD221B8462B90069AE39 /* ACTabScrollView */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 6DB0CD251B8462B90069AE39 /* AppDelegate.swift */, 92 | 530068341CF0512F00A80F09 /* MockData.swift */, 93 | 6DB0CD271B8462B90069AE39 /* NewsViewController.swift */, 94 | 530068321CEDDE7600A80F09 /* ContentViewController.swift */, 95 | 53887DDA1CE8AD3D001E6FD3 /* DemoViewController.swift */, 96 | 6DB0CD291B8462B90069AE39 /* Main.storyboard */, 97 | 6DB0CD2C1B8462B90069AE39 /* Images.xcassets */, 98 | 6DB0CD2E1B8462B90069AE39 /* LaunchScreen.xib */, 99 | 6DB0CD231B8462B90069AE39 /* Supporting Files */, 100 | ); 101 | path = ACTabScrollView; 102 | sourceTree = ""; 103 | }; 104 | 6DB0CD231B8462B90069AE39 /* Supporting Files */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 6DB0CD241B8462B90069AE39 /* Info.plist */, 108 | ); 109 | name = "Supporting Files"; 110 | sourceTree = ""; 111 | }; 112 | 6DB0CD381B8462B90069AE39 /* ACTabScrollViewTests */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 6DB0CD3B1B8462B90069AE39 /* ACTabScrollViewTests.swift */, 116 | 6DB0CD391B8462B90069AE39 /* Supporting Files */, 117 | ); 118 | path = ACTabScrollViewTests; 119 | sourceTree = ""; 120 | }; 121 | 6DB0CD391B8462B90069AE39 /* Supporting Files */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 6DB0CD3A1B8462B90069AE39 /* Info.plist */, 125 | ); 126 | name = "Supporting Files"; 127 | sourceTree = ""; 128 | }; 129 | 6DB0CD451B8464700069AE39 /* Sources */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | 6DB0CD461B8464FC0069AE39 /* ACTabScrollView.swift */, 133 | 98A418CB1CC8BE3700718CA2 /* ACTabScrollView+Protocol.swift */, 134 | ); 135 | path = Sources; 136 | sourceTree = ""; 137 | }; 138 | /* End PBXGroup section */ 139 | 140 | /* Begin PBXNativeTarget section */ 141 | 6DB0CD1F1B8462B90069AE39 /* ACTabScrollView */ = { 142 | isa = PBXNativeTarget; 143 | buildConfigurationList = 6DB0CD3F1B8462B90069AE39 /* Build configuration list for PBXNativeTarget "ACTabScrollView" */; 144 | buildPhases = ( 145 | 6DB0CD1C1B8462B90069AE39 /* Sources */, 146 | 6DB0CD1D1B8462B90069AE39 /* Frameworks */, 147 | 6DB0CD1E1B8462B90069AE39 /* Resources */, 148 | ); 149 | buildRules = ( 150 | ); 151 | dependencies = ( 152 | ); 153 | name = ACTabScrollView; 154 | productName = ACTabScrollView; 155 | productReference = 6DB0CD201B8462B90069AE39 /* ACTabScrollView.app */; 156 | productType = "com.apple.product-type.application"; 157 | }; 158 | 6DB0CD341B8462B90069AE39 /* ACTabScrollViewTests */ = { 159 | isa = PBXNativeTarget; 160 | buildConfigurationList = 6DB0CD421B8462B90069AE39 /* Build configuration list for PBXNativeTarget "ACTabScrollViewTests" */; 161 | buildPhases = ( 162 | 6DB0CD311B8462B90069AE39 /* Sources */, 163 | 6DB0CD321B8462B90069AE39 /* Frameworks */, 164 | 6DB0CD331B8462B90069AE39 /* Resources */, 165 | ); 166 | buildRules = ( 167 | ); 168 | dependencies = ( 169 | 6DB0CD371B8462B90069AE39 /* PBXTargetDependency */, 170 | ); 171 | name = ACTabScrollViewTests; 172 | productName = ACTabScrollViewTests; 173 | productReference = 6DB0CD351B8462B90069AE39 /* ACTabScrollView.xctest */; 174 | productType = "com.apple.product-type.bundle.unit-test"; 175 | }; 176 | /* End PBXNativeTarget section */ 177 | 178 | /* Begin PBXProject section */ 179 | 6DB0CD181B8462B90069AE39 /* Project object */ = { 180 | isa = PBXProject; 181 | attributes = { 182 | LastSwiftMigration = 0700; 183 | LastSwiftUpdateCheck = 0700; 184 | LastUpgradeCheck = 0910; 185 | ORGANIZATIONNAME = AzureChen; 186 | TargetAttributes = { 187 | 6DB0CD1F1B8462B90069AE39 = { 188 | CreatedOnToolsVersion = 6.4; 189 | DevelopmentTeam = AFJ2WGE2QB; 190 | LastSwiftMigration = 0800; 191 | }; 192 | 6DB0CD341B8462B90069AE39 = { 193 | CreatedOnToolsVersion = 6.4; 194 | LastSwiftMigration = 0910; 195 | TestTargetID = 6DB0CD1F1B8462B90069AE39; 196 | }; 197 | }; 198 | }; 199 | buildConfigurationList = 6DB0CD1B1B8462B90069AE39 /* Build configuration list for PBXProject "ACTabScrollView" */; 200 | compatibilityVersion = "Xcode 3.2"; 201 | developmentRegion = English; 202 | hasScannedForEncodings = 0; 203 | knownRegions = ( 204 | en, 205 | Base, 206 | ); 207 | mainGroup = 6DB0CD171B8462B90069AE39; 208 | productRefGroup = 6DB0CD211B8462B90069AE39 /* Products */; 209 | projectDirPath = ""; 210 | projectRoot = ""; 211 | targets = ( 212 | 6DB0CD1F1B8462B90069AE39 /* ACTabScrollView */, 213 | 6DB0CD341B8462B90069AE39 /* ACTabScrollViewTests */, 214 | ); 215 | }; 216 | /* End PBXProject section */ 217 | 218 | /* Begin PBXResourcesBuildPhase section */ 219 | 6DB0CD1E1B8462B90069AE39 /* Resources */ = { 220 | isa = PBXResourcesBuildPhase; 221 | buildActionMask = 2147483647; 222 | files = ( 223 | 6DB0CD2B1B8462B90069AE39 /* Main.storyboard in Resources */, 224 | 6DB0CD301B8462B90069AE39 /* LaunchScreen.xib in Resources */, 225 | 6DB0CD2D1B8462B90069AE39 /* Images.xcassets in Resources */, 226 | ); 227 | runOnlyForDeploymentPostprocessing = 0; 228 | }; 229 | 6DB0CD331B8462B90069AE39 /* Resources */ = { 230 | isa = PBXResourcesBuildPhase; 231 | buildActionMask = 2147483647; 232 | files = ( 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | }; 236 | /* End PBXResourcesBuildPhase section */ 237 | 238 | /* Begin PBXSourcesBuildPhase section */ 239 | 6DB0CD1C1B8462B90069AE39 /* Sources */ = { 240 | isa = PBXSourcesBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | 98A418CC1CC8BE3700718CA2 /* ACTabScrollView+Protocol.swift in Sources */, 244 | 6DB0CD281B8462B90069AE39 /* NewsViewController.swift in Sources */, 245 | 6DB0CD471B8464FC0069AE39 /* ACTabScrollView.swift in Sources */, 246 | 6DB0CD261B8462B90069AE39 /* AppDelegate.swift in Sources */, 247 | 530068331CEDDE7600A80F09 /* ContentViewController.swift in Sources */, 248 | 530068351CF0512F00A80F09 /* MockData.swift in Sources */, 249 | 53887DDB1CE8AD3D001E6FD3 /* DemoViewController.swift in Sources */, 250 | ); 251 | runOnlyForDeploymentPostprocessing = 0; 252 | }; 253 | 6DB0CD311B8462B90069AE39 /* Sources */ = { 254 | isa = PBXSourcesBuildPhase; 255 | buildActionMask = 2147483647; 256 | files = ( 257 | 6DB0CD3C1B8462B90069AE39 /* ACTabScrollViewTests.swift in Sources */, 258 | ); 259 | runOnlyForDeploymentPostprocessing = 0; 260 | }; 261 | /* End PBXSourcesBuildPhase section */ 262 | 263 | /* Begin PBXTargetDependency section */ 264 | 6DB0CD371B8462B90069AE39 /* PBXTargetDependency */ = { 265 | isa = PBXTargetDependency; 266 | target = 6DB0CD1F1B8462B90069AE39 /* ACTabScrollView */; 267 | targetProxy = 6DB0CD361B8462B90069AE39 /* PBXContainerItemProxy */; 268 | }; 269 | /* End PBXTargetDependency section */ 270 | 271 | /* Begin PBXVariantGroup section */ 272 | 6DB0CD291B8462B90069AE39 /* Main.storyboard */ = { 273 | isa = PBXVariantGroup; 274 | children = ( 275 | 6DB0CD2A1B8462B90069AE39 /* Base */, 276 | ); 277 | name = Main.storyboard; 278 | sourceTree = ""; 279 | }; 280 | 6DB0CD2E1B8462B90069AE39 /* LaunchScreen.xib */ = { 281 | isa = PBXVariantGroup; 282 | children = ( 283 | 6DB0CD2F1B8462B90069AE39 /* Base */, 284 | ); 285 | name = LaunchScreen.xib; 286 | sourceTree = ""; 287 | }; 288 | /* End PBXVariantGroup section */ 289 | 290 | /* Begin XCBuildConfiguration section */ 291 | 6DB0CD3D1B8462B90069AE39 /* Debug */ = { 292 | isa = XCBuildConfiguration; 293 | buildSettings = { 294 | ALWAYS_SEARCH_USER_PATHS = NO; 295 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 296 | CLANG_CXX_LIBRARY = "libc++"; 297 | CLANG_ENABLE_MODULES = YES; 298 | CLANG_ENABLE_OBJC_ARC = YES; 299 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 300 | CLANG_WARN_BOOL_CONVERSION = YES; 301 | CLANG_WARN_COMMA = YES; 302 | CLANG_WARN_CONSTANT_CONVERSION = YES; 303 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 304 | CLANG_WARN_EMPTY_BODY = YES; 305 | CLANG_WARN_ENUM_CONVERSION = YES; 306 | CLANG_WARN_INFINITE_RECURSION = YES; 307 | CLANG_WARN_INT_CONVERSION = YES; 308 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 309 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 310 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 311 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 312 | CLANG_WARN_STRICT_PROTOTYPES = YES; 313 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 314 | CLANG_WARN_UNREACHABLE_CODE = YES; 315 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 316 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 317 | COPY_PHASE_STRIP = NO; 318 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 319 | ENABLE_STRICT_OBJC_MSGSEND = YES; 320 | ENABLE_TESTABILITY = YES; 321 | GCC_C_LANGUAGE_STANDARD = gnu99; 322 | GCC_DYNAMIC_NO_PIC = NO; 323 | GCC_NO_COMMON_BLOCKS = YES; 324 | GCC_OPTIMIZATION_LEVEL = 0; 325 | GCC_PREPROCESSOR_DEFINITIONS = ( 326 | "DEBUG=1", 327 | "$(inherited)", 328 | ); 329 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 330 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 331 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 332 | GCC_WARN_UNDECLARED_SELECTOR = YES; 333 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 334 | GCC_WARN_UNUSED_FUNCTION = YES; 335 | GCC_WARN_UNUSED_VARIABLE = YES; 336 | IPHONEOS_DEPLOYMENT_TARGET = 8.4; 337 | MTL_ENABLE_DEBUG_INFO = YES; 338 | ONLY_ACTIVE_ARCH = YES; 339 | SDKROOT = iphoneos; 340 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 341 | TARGETED_DEVICE_FAMILY = "1,2"; 342 | }; 343 | name = Debug; 344 | }; 345 | 6DB0CD3E1B8462B90069AE39 /* Release */ = { 346 | isa = XCBuildConfiguration; 347 | buildSettings = { 348 | ALWAYS_SEARCH_USER_PATHS = NO; 349 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 350 | CLANG_CXX_LIBRARY = "libc++"; 351 | CLANG_ENABLE_MODULES = YES; 352 | CLANG_ENABLE_OBJC_ARC = YES; 353 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 354 | CLANG_WARN_BOOL_CONVERSION = YES; 355 | CLANG_WARN_COMMA = YES; 356 | CLANG_WARN_CONSTANT_CONVERSION = YES; 357 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 358 | CLANG_WARN_EMPTY_BODY = YES; 359 | CLANG_WARN_ENUM_CONVERSION = YES; 360 | CLANG_WARN_INFINITE_RECURSION = YES; 361 | CLANG_WARN_INT_CONVERSION = YES; 362 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 363 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 364 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 365 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 366 | CLANG_WARN_STRICT_PROTOTYPES = YES; 367 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 368 | CLANG_WARN_UNREACHABLE_CODE = YES; 369 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 370 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 371 | COPY_PHASE_STRIP = NO; 372 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 373 | ENABLE_NS_ASSERTIONS = NO; 374 | ENABLE_STRICT_OBJC_MSGSEND = YES; 375 | GCC_C_LANGUAGE_STANDARD = gnu99; 376 | GCC_NO_COMMON_BLOCKS = YES; 377 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 378 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 379 | GCC_WARN_UNDECLARED_SELECTOR = YES; 380 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 381 | GCC_WARN_UNUSED_FUNCTION = YES; 382 | GCC_WARN_UNUSED_VARIABLE = YES; 383 | IPHONEOS_DEPLOYMENT_TARGET = 8.4; 384 | MTL_ENABLE_DEBUG_INFO = NO; 385 | SDKROOT = iphoneos; 386 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 387 | TARGETED_DEVICE_FAMILY = "1,2"; 388 | VALIDATE_PRODUCT = YES; 389 | }; 390 | name = Release; 391 | }; 392 | 6DB0CD401B8462B90069AE39 /* Debug */ = { 393 | isa = XCBuildConfiguration; 394 | buildSettings = { 395 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 396 | CODE_SIGN_IDENTITY = "iPhone Developer"; 397 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 398 | DEVELOPMENT_TEAM = AFJ2WGE2QB; 399 | INFOPLIST_FILE = ACTabScrollView/Info.plist; 400 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 401 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 402 | PRODUCT_BUNDLE_IDENTIFIER = com.azurechen.ACTabScrollView; 403 | PRODUCT_NAME = ACTabScrollView; 404 | PROVISIONING_PROFILE = ""; 405 | SWIFT_VERSION = 4.0; 406 | }; 407 | name = Debug; 408 | }; 409 | 6DB0CD411B8462B90069AE39 /* Release */ = { 410 | isa = XCBuildConfiguration; 411 | buildSettings = { 412 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 413 | CODE_SIGN_IDENTITY = "iPhone Developer"; 414 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 415 | DEVELOPMENT_TEAM = AFJ2WGE2QB; 416 | INFOPLIST_FILE = ACTabScrollView/Info.plist; 417 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 418 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 419 | PRODUCT_BUNDLE_IDENTIFIER = com.azurechen.ACTabScrollView; 420 | PRODUCT_NAME = ACTabScrollView; 421 | PROVISIONING_PROFILE = ""; 422 | SWIFT_VERSION = 4.0; 423 | }; 424 | name = Release; 425 | }; 426 | 6DB0CD431B8462B90069AE39 /* Debug */ = { 427 | isa = XCBuildConfiguration; 428 | buildSettings = { 429 | BUNDLE_LOADER = "$(TEST_HOST)"; 430 | FRAMEWORK_SEARCH_PATHS = ( 431 | "$(SDKROOT)/Developer/Library/Frameworks", 432 | "$(inherited)", 433 | ); 434 | GCC_PREPROCESSOR_DEFINITIONS = ( 435 | "DEBUG=1", 436 | "$(inherited)", 437 | ); 438 | INFOPLIST_FILE = ACTabScrollViewTests/Info.plist; 439 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 440 | PRODUCT_BUNDLE_IDENTIFIER = "com.azurechen.$(PRODUCT_NAME:rfc1034identifier)"; 441 | PRODUCT_NAME = ACTabScrollView; 442 | SWIFT_VERSION = 4.0; 443 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ACTabScrollView.app/ACTabScrollView"; 444 | }; 445 | name = Debug; 446 | }; 447 | 6DB0CD441B8462B90069AE39 /* Release */ = { 448 | isa = XCBuildConfiguration; 449 | buildSettings = { 450 | BUNDLE_LOADER = "$(TEST_HOST)"; 451 | FRAMEWORK_SEARCH_PATHS = ( 452 | "$(SDKROOT)/Developer/Library/Frameworks", 453 | "$(inherited)", 454 | ); 455 | INFOPLIST_FILE = ACTabScrollViewTests/Info.plist; 456 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 457 | PRODUCT_BUNDLE_IDENTIFIER = "com.azurechen.$(PRODUCT_NAME:rfc1034identifier)"; 458 | PRODUCT_NAME = ACTabScrollView; 459 | SWIFT_VERSION = 4.0; 460 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ACTabScrollView.app/ACTabScrollView"; 461 | }; 462 | name = Release; 463 | }; 464 | /* End XCBuildConfiguration section */ 465 | 466 | /* Begin XCConfigurationList section */ 467 | 6DB0CD1B1B8462B90069AE39 /* Build configuration list for PBXProject "ACTabScrollView" */ = { 468 | isa = XCConfigurationList; 469 | buildConfigurations = ( 470 | 6DB0CD3D1B8462B90069AE39 /* Debug */, 471 | 6DB0CD3E1B8462B90069AE39 /* Release */, 472 | ); 473 | defaultConfigurationIsVisible = 0; 474 | defaultConfigurationName = Release; 475 | }; 476 | 6DB0CD3F1B8462B90069AE39 /* Build configuration list for PBXNativeTarget "ACTabScrollView" */ = { 477 | isa = XCConfigurationList; 478 | buildConfigurations = ( 479 | 6DB0CD401B8462B90069AE39 /* Debug */, 480 | 6DB0CD411B8462B90069AE39 /* Release */, 481 | ); 482 | defaultConfigurationIsVisible = 0; 483 | defaultConfigurationName = Release; 484 | }; 485 | 6DB0CD421B8462B90069AE39 /* Build configuration list for PBXNativeTarget "ACTabScrollViewTests" */ = { 486 | isa = XCConfigurationList; 487 | buildConfigurations = ( 488 | 6DB0CD431B8462B90069AE39 /* Debug */, 489 | 6DB0CD441B8462B90069AE39 /* Release */, 490 | ); 491 | defaultConfigurationIsVisible = 0; 492 | defaultConfigurationName = Release; 493 | }; 494 | /* End XCConfigurationList section */ 495 | }; 496 | rootObject = 6DB0CD181B8462B90069AE39 /* Project object */; 497 | } 498 | -------------------------------------------------------------------------------- /ACTabScrollView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ACTabScrollView/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ACTabScrollView 4 | // 5 | // Created by AzureChen on 2015/8/19. 6 | // Copyright (c) 2015年 AzureChen. 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: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // 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. 24 | // 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. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // 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. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 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 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // 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. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /ACTabScrollView/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 | -------------------------------------------------------------------------------- /ACTabScrollView/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | -------------------------------------------------------------------------------- /ACTabScrollView/ContentViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentViewController.swift 3 | // ACTabScrollView 4 | // 5 | // Created by Azure Chen on 5/19/16. 6 | // Copyright © 2016 AzureChen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ContentViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { 12 | 13 | @IBOutlet weak var tableView: UITableView! 14 | 15 | var category: NewsCategory? { 16 | didSet { 17 | for news in MockData.newsArray { 18 | if (news.category == category || category == .all) { 19 | newsArray.append(news) 20 | } 21 | } 22 | } 23 | } 24 | var newsArray: [News] = [] 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | 29 | tableView.rowHeight = UITableViewAutomaticDimension 30 | tableView.estimatedRowHeight = 44 31 | tableView.delegate = self 32 | tableView.dataSource = self 33 | } 34 | 35 | func numberOfSections(in tableView: UITableView) -> Int { 36 | return 1 37 | } 38 | 39 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 40 | return newsArray.count 41 | } 42 | 43 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 44 | let news = newsArray[(indexPath as NSIndexPath).row] 45 | 46 | // set the cell 47 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! ContentTableViewCell 48 | cell.thumbnailImageView.image = UIImage(named: "thumbnail-\(news.id)") 49 | cell.thumbnailImageView.layer.cornerRadius = 4 50 | cell.titleLabel.text = news.title 51 | cell.categoryLabel.text = String(describing: news.category) 52 | cell.categoryView.layer.backgroundColor = UIColor.white.cgColor 53 | cell.categoryView.layer.cornerRadius = 4 54 | cell.categoryView.layer.borderWidth = 1 55 | cell.categoryView.layer.borderColor = UIColor(red: 238.0 / 255, green: 238.0 / 255, blue: 238.0 / 255, alpha: 1.0).cgColor 56 | 57 | return cell 58 | } 59 | 60 | func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 61 | return 48 62 | } 63 | 64 | func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 65 | let view = UIView() 66 | view.backgroundColor = UIColor(red: 61.0 / 255, green: 66.0 / 255, blue: 77.0 / 255, alpha: 1.0) 67 | 68 | let label = UILabel() 69 | label.text = "Today" 70 | label.textColor = UIColor.white 71 | if #available(iOS 8.2, *) { 72 | label.font = UIFont.systemFont(ofSize: 17, weight: UIFont.Weight.thin) 73 | } else { 74 | label.font = UIFont.systemFont(ofSize: 17) 75 | } 76 | label.sizeToFit() 77 | label.frame.origin = CGPoint(x: 18, y: 13) 78 | 79 | view.addSubview(label) 80 | 81 | return view 82 | } 83 | 84 | } 85 | 86 | class ContentTableViewCell: UITableViewCell { 87 | 88 | @IBOutlet weak var thumbnailImageView: UIImageView! 89 | @IBOutlet weak var titleLabel: UILabel! 90 | @IBOutlet weak var categoryView: UIView! 91 | @IBOutlet weak var categoryLabel: UILabel! 92 | 93 | override func awakeFromNib() { 94 | self.selectionStyle = .none 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /ACTabScrollView/DemoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoViewController.swift 3 | // ACTabScrollView 4 | // 5 | // Created by AzureChen on 2015/8/19. 6 | // Copyright (c) 2015年 AzureChen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DemoViewController: UIViewController, ACTabScrollViewDelegate, ACTabScrollViewDataSource { 12 | 13 | @IBOutlet weak var tabScrollView: ACTabScrollView! 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | tabScrollView.defaultPage = 1 19 | tabScrollView.tabSectionHeight = 60 20 | tabScrollView.pagingEnabled = true 21 | tabScrollView.cachedPageLimit = 3 22 | 23 | tabScrollView.delegate = self 24 | tabScrollView.dataSource = self 25 | } 26 | 27 | // MARK: ACTabScrollViewDelegate 28 | func tabScrollView(_ tabScrollView: ACTabScrollView, didChangePageTo index: Int) { 29 | print(index) 30 | } 31 | 32 | func tabScrollView(_ tabScrollView: ACTabScrollView, didScrollPageTo index: Int) { 33 | } 34 | 35 | // MARK: ACTabScrollViewDataSource 36 | func numberOfPagesInTabScrollView(_ tabScrollView: ACTabScrollView) -> Int { 37 | return 8 38 | } 39 | 40 | func tabScrollView(_ tabScrollView: ACTabScrollView, tabViewForPageAtIndex index: Int) -> UIView { 41 | let tabView = UIView() 42 | tabView.frame.size = CGSize(width: (index + 1) * 10, height: (index + 1) * 5) 43 | 44 | switch (index % 3) { 45 | case 0: 46 | tabView.backgroundColor = UIColor.red 47 | case 1: 48 | tabView.backgroundColor = UIColor.green 49 | case 2: 50 | tabView.backgroundColor = UIColor.blue 51 | default: 52 | break 53 | } 54 | 55 | return tabView 56 | } 57 | 58 | func tabScrollView(_ tabScrollView: ACTabScrollView, contentViewForPageAtIndex index: Int) -> UIView { 59 | let contentView = UIView() 60 | 61 | switch (index % 3) { 62 | case 0: 63 | contentView.backgroundColor = UIColor.red 64 | case 1: 65 | contentView.backgroundColor = UIColor.green 66 | case 2: 67 | contentView.backgroundColor = UIColor.blue 68 | default: 69 | break 70 | } 71 | 72 | return contentView 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-1.imageset/1-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azurechen/ACTabScrollView/e7dbd22bb047c58753ff5a940552333f9b106b60/ACTabScrollView/Images.xcassets/thumbnail-1.imageset/1-1.jpg -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "1-1.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-10.imageset/-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azurechen/ACTabScrollView/e7dbd22bb047c58753ff5a940552333f9b106b60/ACTabScrollView/Images.xcassets/thumbnail-10.imageset/-10.png -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-10.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "-10.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-11.imageset/-11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azurechen/ACTabScrollView/e7dbd22bb047c58753ff5a940552333f9b106b60/ACTabScrollView/Images.xcassets/thumbnail-11.imageset/-11.jpg -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-11.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "-11.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-12.imageset/-12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azurechen/ACTabScrollView/e7dbd22bb047c58753ff5a940552333f9b106b60/ACTabScrollView/Images.xcassets/thumbnail-12.imageset/-12.jpg -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-12.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "-12.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-13.imageset/-13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azurechen/ACTabScrollView/e7dbd22bb047c58753ff5a940552333f9b106b60/ACTabScrollView/Images.xcassets/thumbnail-13.imageset/-13.jpg -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-13.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "-13.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-2.imageset/-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azurechen/ACTabScrollView/e7dbd22bb047c58753ff5a940552333f9b106b60/ACTabScrollView/Images.xcassets/thumbnail-2.imageset/-2.jpg -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "-2.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-3.imageset/-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azurechen/ACTabScrollView/e7dbd22bb047c58753ff5a940552333f9b106b60/ACTabScrollView/Images.xcassets/thumbnail-3.imageset/-3.jpg -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "-3.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-4.imageset/-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azurechen/ACTabScrollView/e7dbd22bb047c58753ff5a940552333f9b106b60/ACTabScrollView/Images.xcassets/thumbnail-4.imageset/-4.jpg -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "-4.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-5.imageset/1-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azurechen/ACTabScrollView/e7dbd22bb047c58753ff5a940552333f9b106b60/ACTabScrollView/Images.xcassets/thumbnail-5.imageset/1-2.jpg -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "1-2.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-6.imageset/-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azurechen/ACTabScrollView/e7dbd22bb047c58753ff5a940552333f9b106b60/ACTabScrollView/Images.xcassets/thumbnail-6.imageset/-6.jpg -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-6.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "-6.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-7.imageset/-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azurechen/ACTabScrollView/e7dbd22bb047c58753ff5a940552333f9b106b60/ACTabScrollView/Images.xcassets/thumbnail-7.imageset/-7.jpg -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-7.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "-7.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-8.imageset/-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azurechen/ACTabScrollView/e7dbd22bb047c58753ff5a940552333f9b106b60/ACTabScrollView/Images.xcassets/thumbnail-8.imageset/-8.jpg -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-8.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "-8.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-9.imageset/-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azurechen/ACTabScrollView/e7dbd22bb047c58753ff5a940552333f9b106b60/ACTabScrollView/Images.xcassets/thumbnail-9.imageset/-9.jpg -------------------------------------------------------------------------------- /ACTabScrollView/Images.xcassets/thumbnail-9.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "-9.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ACTabScrollView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIViewControllerBasedStatusBarAppearance 6 | 7 | CFBundleDevelopmentRegion 8 | en 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 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /ACTabScrollView/MockData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockData.swift 3 | // ACTabScrollView 4 | // 5 | // Created by Azure Chen on 5/21/16. 6 | // Copyright © 2016 AzureChen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MockData { 12 | 13 | static let newsArray = [ 14 | News(id: 1, category: .entertainment, title: "'Game of Thrones' Kit Harington on falling in love with co-star"), 15 | News(id: 2, category: .tech, title: "How Google plans to bring VR to our homes"), 16 | News(id: 3, category: .sport, title: "Can Nadal rediscover Paris magic?"), 17 | News(id: 4, category: .sport, title: "What makes Bayern so special?"), 18 | News(id: 5, category: .entertainment, title: "Happy 30th anniversary 'Top Gun'!"), 19 | News(id: 6, category: .travel, title: "How to cook like Asia's best female chef"), 20 | News(id: 7, category: .travel, title: "Nine reasons to visit Georgia right now"), 21 | News(id: 8, category: .tech, title: "Look out for self-driving Ubers"), 22 | News(id: 9, category: .style, title: "This house being built into a cliff, thanks to internet"), 23 | News(id: 10, category: .specials, title: "Inside Africa"), 24 | News(id: 11, category: .sport, title: "Hayne named in Fiji's London squad"), 25 | News(id: 12, category: .travel, title: "Airport security: How can terrorist attacks be prevented?"), 26 | News(id: 13, category: .specials, title: "Silk Road"), 27 | ] 28 | 29 | } 30 | 31 | enum NewsCategory { 32 | case entertainment 33 | case tech 34 | case sport 35 | case all 36 | case travel 37 | case style 38 | case specials 39 | 40 | static func allValues() -> [NewsCategory] { 41 | return [.entertainment, .tech, .sport, .all, .travel, .style, .specials] 42 | } 43 | } 44 | 45 | struct News { 46 | let id: Int 47 | let category: NewsCategory 48 | let title: String 49 | } 50 | -------------------------------------------------------------------------------- /ACTabScrollView/NewsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NewsViewController.swift 3 | // ACTabScrollView 4 | // 5 | // Created by AzureChen on 2015/8/19. 6 | // Copyright (c) 2015年 AzureChen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class NewsViewController: UIViewController, ACTabScrollViewDelegate, ACTabScrollViewDataSource { 12 | 13 | @IBOutlet weak var tabScrollView: ACTabScrollView! 14 | 15 | var contentViews: [UIView] = [] 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | // set ACTabScrollView, all the following properties are optional 21 | tabScrollView.defaultPage = 3 22 | tabScrollView.arrowIndicator = true 23 | // tabScrollView.tabSectionHeight = 40 24 | // tabScrollView.tabSectionBackgroundColor = UIColor.whiteColor() 25 | // tabScrollView.contentSectionBackgroundColor = UIColor.whiteColor() 26 | // tabScrollView.tabGradient = true 27 | // tabScrollView.pagingEnabled = true 28 | // tabScrollView.cachedPageLimit = 3 29 | 30 | tabScrollView.delegate = self 31 | tabScrollView.dataSource = self 32 | 33 | // create content views from storyboard 34 | let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main) 35 | for category in NewsCategory.allValues() { 36 | let vc = storyboard.instantiateViewController(withIdentifier: "ContentViewController") as! ContentViewController 37 | vc.category = category 38 | 39 | addChildViewController(vc) // don't forget, it's very important 40 | contentViews.append(vc.view) 41 | } 42 | 43 | // set navigation bar appearance 44 | if let navigationBar = self.navigationController?.navigationBar { 45 | navigationBar.isTranslucent = false 46 | navigationBar.tintColor = UIColor.white 47 | navigationBar.barTintColor = UIColor(red: 38.0 / 255, green: 191.0 / 255, blue: 140.0 / 255, alpha: 1) 48 | navigationBar.titleTextAttributes = NSDictionary(object: UIColor.white, forKey: NSAttributedStringKey.foregroundColor as NSCopying) as? [NSAttributedStringKey : AnyObject] 49 | navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default) 50 | navigationBar.shadowImage = UIImage() 51 | } 52 | UIApplication.shared.statusBarStyle = UIStatusBarStyle.lightContent 53 | } 54 | 55 | // MARK: ACTabScrollViewDelegate 56 | func tabScrollView(_ tabScrollView: ACTabScrollView, didChangePageTo index: Int) { 57 | print(index) 58 | } 59 | 60 | func tabScrollView(_ tabScrollView: ACTabScrollView, didScrollPageTo index: Int) { 61 | } 62 | 63 | // MARK: ACTabScrollViewDataSource 64 | func numberOfPagesInTabScrollView(_ tabScrollView: ACTabScrollView) -> Int { 65 | return NewsCategory.allValues().count 66 | } 67 | 68 | func tabScrollView(_ tabScrollView: ACTabScrollView, tabViewForPageAtIndex index: Int) -> UIView { 69 | // create a label 70 | let label = UILabel() 71 | label.text = String(describing: NewsCategory.allValues()[index]).uppercased() 72 | if #available(iOS 8.2, *) { 73 | label.font = UIFont.systemFont(ofSize: 16, weight: UIFont.Weight.thin) 74 | } else { 75 | label.font = UIFont.systemFont(ofSize: 16) 76 | } 77 | label.textColor = UIColor(red: 77.0 / 255, green: 79.0 / 255, blue: 84.0 / 255, alpha: 1) 78 | label.textAlignment = .center 79 | 80 | // if the size of your tab is not fixed, you can adjust the size by the following way. 81 | label.sizeToFit() // resize the label to the size of content 82 | label.frame.size = CGSize(width: label.frame.size.width + 28, height: label.frame.size.height + 36) // add some paddings 83 | 84 | return label 85 | } 86 | 87 | func tabScrollView(_ tabScrollView: ACTabScrollView, contentViewForPageAtIndex index: Int) -> UIView { 88 | return contentViews[index] 89 | } 90 | } 91 | 92 | -------------------------------------------------------------------------------- /ACTabScrollViewTests/ACTabScrollViewTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ACTabScrollViewTests.swift 3 | // ACTabScrollViewTests 4 | // 5 | // Created by AzureChen on 2015/8/19. 6 | // Copyright (c) 2015年 AzureChen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class ACTabScrollViewTests: 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.measure() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /ACTabScrollViewTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Azure Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ACTabScrollView 2 | =============== 3 | 4 | [![CocoaPods](https://img.shields.io/cocoapods/p/ACTabScrollView.svg)](http://cocoapods.org/pods/ACTabScrollView) 5 | [![CocoaPods](https://img.shields.io/cocoapods/v/ACTabScrollView.svg)](http://cocoapods.org/pods/ACTabScrollView) 6 | [![iOS 7.0+](https://img.shields.io/badge/ios-7.0+-green.svg?style=flat)](https://developer.apple.com/ios/) 7 | [![Swift 2.0+](https://img.shields.io/badge/swift-2.0+-orange.svg?style=flat)](https://developer.apple.com/swift/) 8 | [![Objective-C 2.0+](https://img.shields.io/badge/objective--c-2.0+-red.svg?style=flat)](https://developer.apple.com/reference/objectivec) 9 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/azurechen/ACTabScrollView/blob/master/LICENSE.md) 10 | 11 | 12 | 13 | A fancy `Menu` and `Pager` UI extends `UIScrollView` with elegant, smooth and synchronized scrolling `tabs`. 14 | 15 | DEMO 16 | ---- 17 | 18 | Demo 19 | Demo 20 | Demo 21 | 22 | User can interact with the UI by some different gestures. The `tabs` and `pages` will always scroll `synchronously`. 23 | 24 | * `Swipe` pages normally 25 | * `Drag` tabs can quickly move pages 26 | * `Click` a tab to change to that page 27 | 28 | You can also use `changePageToIndex` method to scroll pages programmatically. 29 | 30 | Usage 31 | ----- 32 | 33 | ### Add an Object of `ACTabScrollView` 34 | 35 | Drag a `UIView` object onto the Interface Builder and set the `Class` to extends `ACTabScrollView ` in `XIB` or `Storyboard`. 36 | 37 | Demo 38 | 39 | You can also set properties at `Interface Builder`. 40 | 41 | And remember to declare the `IBOutlet`. 42 | 43 | ```swift 44 | @IBOutlet weak var tabScrollView: ACTabScrollView! 45 | ``` 46 | 47 | ### Set Properties 48 | 49 | All the following properties are `optional`. It provides more flexibility to customize. But it will be fine if you don't change any property. 50 | 51 | ```swift 52 | override func viewDidLoad() { 53 | super.viewDidLoad() 54 | 55 | // all the following properties are optional 56 | tabScrollView.defaultPage = 3 57 | tabScrollView.arrowIndicator = true 58 | tabScrollView.tabSectionHeight = 40 59 | tabScrollView.tabSectionBackgroundColor = UIColor.whiteColor() 60 | tabScrollView.contentSectionBackgroundColor = UIColor.whiteColor() 61 | tabScrollView.tabGradient = true 62 | tabScrollView.pagingEnabled = true 63 | tabScrollView.cachedPageLimit = 3 64 | 65 | ... 66 | } 67 | ``` 68 | 69 | ### Delegate and DataSource 70 | 71 | Set `Delegate` and `DataSource` first in `viewDidLoad()`, the usage is similar to `UITableView`. 72 | 73 | ```swift 74 | override func viewDidLoad() { 75 | ... 76 | 77 | tabScrollView.delegate = self 78 | tabScrollView.dataSource = self 79 | 80 | ... 81 | } 82 | ``` 83 | 84 | Prepare all the content views in `viewDidLoad()` may be a good idea. We had better not create the content view at each page change because it may cause performance issues. 85 | 86 | ```swift 87 | override func viewDidLoad() { 88 | ... 89 | 90 | // create content views from storyboard 91 | let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()) 92 | for i in 0 ..< /* number of pages */ { 93 | let vc = storyboard.instantiateViewControllerWithIdentifier("ContentViewController") as! ContentViewController 94 | 95 | /* set somethings for vc */ 96 | 97 | addChildViewController(vc) // don't forget, it's very important 98 | contentViews.append(vc.view) 99 | } 100 | 101 | ... 102 | } 103 | ``` 104 | 105 | And implement methods 106 | 107 | ```swift 108 | // MARK: ACTabScrollViewDelegate 109 | func tabScrollView(tabScrollView: ACTabScrollView, didChangePageTo index: Int) { 110 | print(index) 111 | } 112 | 113 | func tabScrollView(tabScrollView: ACTabScrollView, didScrollPageTo index: Int) { 114 | } 115 | 116 | // MARK: ACTabScrollViewDataSource 117 | func numberOfPagesInTabScrollView(tabScrollView: ACTabScrollView) -> Int { 118 | return /* number of pages */ 119 | } 120 | 121 | func tabScrollView(tabScrollView: ACTabScrollView, tabViewForPageAtIndex index: Int) -> UIView { 122 | // create a label 123 | let label = UILabel() 124 | label.text = /* tab title at {index} */ 125 | label.textAlignment = .Center 126 | 127 | // if the size of your tab is not fixed, you can adjust the size by the following way. 128 | label.sizeToFit() // resize the label to the size of content 129 | label.frame.size = CGSize( 130 | width: label.frame.size.width + 28, 131 | height: label.frame.size.height + 36) // add some paddings 132 | 133 | return label 134 | } 135 | 136 | func tabScrollView(tabScrollView: ACTabScrollView, contentViewForPageAtIndex index: Int) -> UIView { 137 | return contentViews[index] 138 | } 139 | ``` 140 | 141 | The usage tutorial is finished, you can see more details and example at `ACTabScrollView/NewsViewController.swift` 142 | 143 | ### Using `ACTabScrollView` in `Objective-C` Project 144 | 145 | It is very very easy if you use the newest version of Xcode. 146 | 147 | First, import the automatically generated header `ACTabScrollView-Swift.h` in your `.m` file. 148 | 149 | ```objective-c 150 | #import "ACTabScrollView-Swift.h" 151 | ``` 152 | 153 | This header is generated automatically, you don't need to change any setting. 154 | 155 | And try to use `ACTabScrollView` in your `ViewController.m` 156 | 157 | ```objective-c 158 | #import "ViewController.h" 159 | #import "ACTabScrollView-Swift.h" 160 | 161 | @interface ViewController () 162 | @end 163 | 164 | @implementation ViewController 165 | 166 | - (void)viewDidLoad { 167 | [super viewDidLoad]; 168 | 169 | ACTabScrollView *tabScrollView = [[ACTabScrollView alloc] init]; 170 | 171 | tabScrollView.defaultPage = 3; 172 | tabScrollView.arrowIndicator = true; 173 | } 174 | 175 | @end 176 | ``` 177 | 178 | Enjoy `ACTabScrollView` in your Objective-C project. 🎉 179 | 180 | How to Install 181 | -------------- 182 | 183 | ### CocoaPods 184 | 185 | If you didn't use [CocoaPods](http://cocoapods.org) before, install it first. 186 | 187 | ```bash 188 | $ gem install cocoapods 189 | $ pod setup 190 | ``` 191 | 192 | Create a file named `Podfile` in your project folder if this file doesn't exist. And append the following line into your `Podfile`. 193 | 194 | ```bash 195 | pod 'ACTabScrollView', :git => 'https://github.com/azurechen/ACTabScrollView.git' 196 | ``` 197 | 198 | Then, run this command. 🎉 199 | 200 | ```bash 201 | $ pod install 202 | ``` 203 | 204 | ### Manual 205 | 206 | Drag these two files into your project. 207 | 208 | * `Sources/ACTabScrollView.swift` 209 | * `Sources/ACTabScrollView+Protocol.swift` 210 | 211 | And you can use `ACTabScrollView`. 🎉 212 | 213 | Inspiration 214 | ----------- 215 | 216 | The [Stackoverflow Question](http://stackoverflow.com/questions/26831662/creation-of-a-horizontal-infinite-scrolling-menu-bar-in-ios) inspired me to create this repo. Although `ACTabScrollView` does not implement all the features it mentions, such as `Infinite Scrolling`, I will keep to implement that. But the idea give me a motivation to do something, and brought `ACTabScrollView` here. 217 | 218 | I use the UI from that question as examples in this project, because that is the best sample to show this concept, and only as an example. I always want to know what the app is but I still don't figure out. 219 | 220 | If you create the app and you don't like me to use it as an example. Contact me and I will immediately remove that. Or I can mention you and your work at README and example files. Thank you very much! 221 | 222 | License 223 | ------- 224 | 225 | The MIT License (MIT) 226 | 227 | Copyright (c) 2016 Azure Chen 228 | 229 | 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: 230 | 231 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 232 | 233 | 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. 234 | -------------------------------------------------------------------------------- /Screenshots/demo-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azurechen/ACTabScrollView/e7dbd22bb047c58753ff5a940552333f9b106b60/Screenshots/demo-1.gif -------------------------------------------------------------------------------- /Screenshots/demo-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azurechen/ACTabScrollView/e7dbd22bb047c58753ff5a940552333f9b106b60/Screenshots/demo-2.gif -------------------------------------------------------------------------------- /Screenshots/demo-3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azurechen/ACTabScrollView/e7dbd22bb047c58753ff5a940552333f9b106b60/Screenshots/demo-3.gif -------------------------------------------------------------------------------- /Screenshots/usage-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azurechen/ACTabScrollView/e7dbd22bb047c58753ff5a940552333f9b106b60/Screenshots/usage-1.png -------------------------------------------------------------------------------- /Sources/ACTabScrollView+Protocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ACTabScrollView+Protocol.swift 3 | // ACTabScrollView 4 | // 5 | // Created by AzureChen on 2016/4/21. 6 | // Copyright © 2016年 AzureChen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol ACTabScrollViewDelegate { 12 | 13 | // triggered by stopping at particular page 14 | func tabScrollView(_ tabScrollView: ACTabScrollView, didChangePageTo index: Int) 15 | 16 | // triggered by scrolling through any pages 17 | func tabScrollView(_ tabScrollView: ACTabScrollView, didScrollPageTo index: Int) 18 | } 19 | 20 | public protocol ACTabScrollViewDataSource { 21 | 22 | // get pages count 23 | func numberOfPagesInTabScrollView(_ tabScrollView: ACTabScrollView) -> Int 24 | 25 | // get the tab at index 26 | func tabScrollView(_ tabScrollView: ACTabScrollView, tabViewForPageAtIndex index: Int) -> UIView 27 | 28 | // get the content at index 29 | func tabScrollView(_ tabScrollView: ACTabScrollView, contentViewForPageAtIndex index: Int) -> UIView 30 | } 31 | -------------------------------------------------------------------------------- /Sources/ACTabScrollView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ACTabScrollView.swift 3 | // ACTabScrollView 4 | // 5 | // Created by AzureChen on 2015/8/19. 6 | // Copyright (c) 2015 AzureChen. All rights reserved. 7 | // 8 | 9 | // TODO: 10 | // 1. Performace improvement 11 | // 2. Test reloadData function 12 | // 3. Tabs in the bottom 13 | // 4. Bottom line or shadow 14 | // 5. Support Carthage 15 | 16 | import UIKit 17 | 18 | @IBDesignable 19 | open class ACTabScrollView: UIView, UIScrollViewDelegate { 20 | 21 | // MARK: Public Variables 22 | @IBInspectable open var defaultPage: Int = 0 23 | @IBInspectable open var tabSectionHeight: CGFloat = -1 24 | @IBInspectable open var tabSectionBackgroundColor: UIColor = UIColor.white 25 | @IBInspectable open var contentSectionBackgroundColor: UIColor = UIColor.white 26 | @IBInspectable open var tabGradient: Bool = true 27 | @IBInspectable open var arrowIndicator: Bool = false 28 | @IBInspectable open var pagingEnabled: Bool = true { 29 | didSet { 30 | contentSectionScrollView.isPagingEnabled = pagingEnabled 31 | } 32 | } 33 | @IBInspectable open var cachedPageLimit: Int = 3 34 | 35 | open var delegate: ACTabScrollViewDelegate? 36 | open var dataSource: ACTabScrollViewDataSource? 37 | 38 | // MARK: Private Variables 39 | fileprivate var tabSectionScrollView: UIScrollView! 40 | fileprivate var contentSectionScrollView: UIScrollView! 41 | fileprivate var arrowView: ArrowView! 42 | 43 | fileprivate var cachedPageTabs: [Int: UIView] = [:] 44 | fileprivate var cachedPageContents: CacheQueue = CacheQueue() 45 | fileprivate var realcachedPageLimit: Int { 46 | var limit = 3 47 | if (cachedPageLimit > 3) { 48 | limit = cachedPageLimit 49 | } else if (cachedPageLimit < 1) { 50 | limit = numberOfPages 51 | } 52 | return limit 53 | } 54 | 55 | fileprivate var isStarted = false 56 | fileprivate var pageIndex: Int! 57 | fileprivate var prevPageIndex: Int? 58 | 59 | fileprivate var isWaitingForPageChangedCallback = false 60 | fileprivate var pageChangedCallback: (() -> Void)? 61 | 62 | // MARK: DataSource 63 | fileprivate var numberOfPages = 0 64 | 65 | fileprivate func widthForTabAtIndex(_ index: Int) -> CGFloat { 66 | return cachedPageTabs[index]?.frame.width ?? 0 67 | } 68 | 69 | fileprivate func tabViewForPageAtIndex(_ index: Int) -> UIView? { 70 | return dataSource?.tabScrollView(self, tabViewForPageAtIndex: index) 71 | } 72 | 73 | fileprivate func contentViewForPageAtIndex(_ index: Int) -> UIView? { 74 | return dataSource?.tabScrollView(self, contentViewForPageAtIndex: index) 75 | } 76 | 77 | // MARK: Init 78 | required public init?(coder aDecoder: NSCoder) { 79 | super.init(coder: aDecoder) 80 | 81 | initialize() 82 | } 83 | 84 | override init(frame: CGRect) { 85 | super.init(frame: frame) 86 | 87 | initialize() 88 | } 89 | 90 | fileprivate func initialize() { 91 | // init views 92 | tabSectionScrollView = UIScrollView() 93 | contentSectionScrollView = UIScrollView() 94 | arrowView = ArrowView(frame: CGRect(x: 0, y: 0, width: 30, height: 10)) 95 | 96 | self.addSubview(tabSectionScrollView) 97 | self.addSubview(contentSectionScrollView) 98 | self.addSubview(arrowView) 99 | 100 | tabSectionScrollView.isPagingEnabled = false 101 | tabSectionScrollView.showsHorizontalScrollIndicator = false 102 | tabSectionScrollView.showsVerticalScrollIndicator = false 103 | tabSectionScrollView.delegate = self 104 | 105 | contentSectionScrollView.isPagingEnabled = pagingEnabled 106 | contentSectionScrollView.showsHorizontalScrollIndicator = false 107 | contentSectionScrollView.showsVerticalScrollIndicator = false 108 | contentSectionScrollView.delegate = self 109 | } 110 | 111 | override open func layoutSubviews() { 112 | super.layoutSubviews() 113 | 114 | // reset status and stop scrolling immediately 115 | if (isStarted) { 116 | isStarted = false 117 | stopScrolling() 118 | } 119 | 120 | // set custom attrs 121 | tabSectionScrollView.backgroundColor = self.tabSectionBackgroundColor 122 | contentSectionScrollView.backgroundColor = self.contentSectionBackgroundColor 123 | arrowView.arrorBackgroundColor = self.tabSectionBackgroundColor 124 | arrowView.isHidden = !arrowIndicator 125 | 126 | // first time setup pages 127 | setupPages() 128 | 129 | // async necessarily 130 | DispatchQueue.main.async { 131 | // first time set defaule pageIndex 132 | self.initWithPageIndex(self.pageIndex ?? self.defaultPage) 133 | self.isStarted = true 134 | 135 | // load pages 136 | self.lazyLoadPages() 137 | } 138 | } 139 | 140 | override open func prepareForInterfaceBuilder() { 141 | let textColor = UIColor(red: 203.0 / 255, green: 203.0 / 255, blue: 203.0 / 255, alpha: 1.0) 142 | let tabSectionHeight = self.tabSectionHeight >= 0 ? self.tabSectionHeight : 64 143 | 144 | // labels 145 | let tabSectionLabel = UILabel(frame: CGRect(x: 0, y: 0, width: self.frame.width, height: tabSectionHeight)) 146 | let contentSectionLabel = UILabel(frame: CGRect(x: 0, y: tabSectionHeight + 1, width: self.frame.width, height: self.frame.height - tabSectionHeight - 1)) 147 | 148 | tabSectionLabel.text = "Tab Section" 149 | tabSectionLabel.textColor = textColor 150 | tabSectionLabel.textAlignment = .center 151 | if #available(iOS 8.2, *) { 152 | tabSectionLabel.font = UIFont.systemFont(ofSize: 27, weight: UIFont.Weight.heavy) 153 | } else { 154 | tabSectionLabel.font = UIFont.systemFont(ofSize: 27) 155 | } 156 | tabSectionLabel.backgroundColor = tabSectionBackgroundColor 157 | contentSectionLabel.text = "Content Section" 158 | contentSectionLabel.textColor = textColor 159 | contentSectionLabel.textAlignment = .center 160 | if #available(iOS 8.2, *) { 161 | contentSectionLabel.font = UIFont.systemFont(ofSize: 27, weight: UIFont.Weight.heavy) 162 | } else { 163 | contentSectionLabel.font = UIFont.systemFont(ofSize: 27) 164 | } 165 | contentSectionLabel.backgroundColor = contentSectionBackgroundColor 166 | 167 | // rect and seperator 168 | let rectView = UIView(frame: CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)) 169 | rectView.layer.borderWidth = 1 170 | rectView.layer.borderColor = textColor.cgColor 171 | 172 | let seperatorView = UIView(frame: CGRect(x: 0, y: tabSectionHeight, width: self.frame.width, height: 1)) 173 | seperatorView.backgroundColor = textColor 174 | 175 | // arrow 176 | arrowView.frame.origin = CGPoint(x: (self.frame.width - arrowView.frame.width) / 2, y: tabSectionHeight) 177 | 178 | // add subviews 179 | self.addSubview(tabSectionLabel) 180 | self.addSubview(contentSectionLabel) 181 | self.addSubview(rectView) 182 | self.addSubview(seperatorView) 183 | self.addSubview(arrowView) 184 | } 185 | 186 | // MARK: - Tab Clicking Control 187 | @objc func tabViewDidClick(_ sensor: UITapGestureRecognizer) { 188 | activedScrollView = tabSectionScrollView 189 | moveToIndex(sensor.view!.tag, animated: true) 190 | } 191 | 192 | @objc func tabSectionScrollViewDidClick(_ sensor: UITapGestureRecognizer) { 193 | activedScrollView = tabSectionScrollView 194 | moveToIndex(pageIndex, animated: true) 195 | } 196 | 197 | // MARK: - Scrolling Control 198 | fileprivate var activedScrollView: UIScrollView? 199 | 200 | // scrolling animation begin by dragging 201 | open func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { 202 | // stop current scrolling before start another scrolling 203 | stopScrolling() 204 | // set the activedScrollView 205 | activedScrollView = scrollView 206 | } 207 | 208 | // scrolling animation stop with decelerating 209 | open func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 210 | moveToIndex(currentPageIndex(), animated: true) 211 | } 212 | 213 | // scrolling animation stop without decelerating 214 | open func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { 215 | if (!decelerate) { 216 | moveToIndex(currentPageIndex(), animated: true) 217 | } 218 | } 219 | 220 | // scrolling animation stop programmatically 221 | open func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { 222 | if (isWaitingForPageChangedCallback) { 223 | isWaitingForPageChangedCallback = false 224 | pageChangedCallback?() 225 | } 226 | } 227 | 228 | // scrolling 229 | open func scrollViewDidScroll(_ scrollView: UIScrollView) { 230 | let currentIndex = currentPageIndex() 231 | 232 | if (scrollView == activedScrollView) { 233 | let speed = self.frame.width / widthForTabAtIndex(currentIndex) 234 | let halfWidth = self.frame.width / 2 235 | 236 | var tabsWidth: CGFloat = 0 237 | var contentsWidth: CGFloat = 0 238 | for i in 0 ..< currentIndex { 239 | tabsWidth += widthForTabAtIndex(i) 240 | contentsWidth += self.frame.width 241 | } 242 | 243 | if (scrollView == tabSectionScrollView) { 244 | contentSectionScrollView.contentOffset.x = ((tabSectionScrollView.contentOffset.x + halfWidth - tabsWidth) * speed) + contentsWidth - halfWidth 245 | } 246 | 247 | if (scrollView == contentSectionScrollView) { 248 | tabSectionScrollView.contentOffset.x = ((contentSectionScrollView.contentOffset.x + halfWidth - contentsWidth) / speed) + tabsWidth - halfWidth 249 | } 250 | updateTabAppearance() 251 | } 252 | 253 | if (isStarted && pageIndex != currentIndex) { 254 | // set index 255 | pageIndex = currentIndex 256 | 257 | // lazy loading 258 | lazyLoadPages() 259 | 260 | // callback 261 | delegate?.tabScrollView(self, didScrollPageTo: currentIndex) 262 | } 263 | } 264 | 265 | // MARK: Public Methods 266 | // func scroll(offsetX: CGFloat) { 267 | // } 268 | 269 | open func reloadData() { 270 | // setup pages 271 | setupPages() 272 | 273 | // load pages 274 | lazyLoadPages() 275 | } 276 | 277 | open func changePageToIndex(_ index: Int, animated: Bool) { 278 | activedScrollView = tabSectionScrollView 279 | moveToIndex(index, animated: animated) 280 | } 281 | 282 | open func changePageToIndex(_ index: Int, animated: Bool, completion: @escaping (() -> Void)) { 283 | isWaitingForPageChangedCallback = true 284 | pageChangedCallback = completion 285 | changePageToIndex(index, animated: animated) 286 | } 287 | 288 | // MARK: Private Methods 289 | fileprivate func stopScrolling() { 290 | tabSectionScrollView.setContentOffset(tabSectionScrollView.contentOffset, animated: false) 291 | contentSectionScrollView.setContentOffset(contentSectionScrollView.contentOffset, animated: false) 292 | } 293 | 294 | fileprivate func initWithPageIndex(_ index: Int) { 295 | // set pageIndex 296 | pageIndex = index 297 | prevPageIndex = pageIndex 298 | 299 | // init UI 300 | if (numberOfPages != 0) { 301 | var tabOffsetX = 0 as CGFloat 302 | var contentOffsetX = 0 as CGFloat 303 | for i in 0 ..< index { 304 | tabOffsetX += widthForTabAtIndex(i) 305 | contentOffsetX += self.frame.width 306 | } 307 | // set default position of tabs and contents 308 | tabSectionScrollView.contentOffset = CGPoint(x: tabOffsetX - (self.frame.width - widthForTabAtIndex(index)) / 2, y: tabSectionScrollView.contentOffset.y) 309 | contentSectionScrollView.contentOffset = CGPoint(x: contentOffsetX, y: contentSectionScrollView.contentOffset.y) 310 | updateTabAppearance(animated: false) 311 | } 312 | } 313 | 314 | fileprivate func currentPageIndex() -> Int { 315 | let width = self.frame.width 316 | var currentPageIndex = Int((contentSectionScrollView.contentOffset.x + (0.5 * width)) / width) 317 | if (currentPageIndex < 0) { 318 | currentPageIndex = 0 319 | } else if (currentPageIndex >= self.numberOfPages) { 320 | currentPageIndex = self.numberOfPages - 1 321 | } 322 | return currentPageIndex 323 | } 324 | 325 | fileprivate func setupPages() { 326 | // reset number of pages 327 | numberOfPages = dataSource?.numberOfPagesInTabScrollView(self) ?? 0 328 | 329 | // clear all caches 330 | cachedPageTabs.removeAll() 331 | for subview in tabSectionScrollView.subviews { 332 | subview.removeFromSuperview() 333 | } 334 | cachedPageContents.removeAll() 335 | for subview in contentSectionScrollView.subviews { 336 | subview.removeFromSuperview() 337 | } 338 | 339 | if (numberOfPages != 0) { 340 | // cache tabs and get the max height 341 | var maxTabHeight: CGFloat = 0 342 | for i in 0 ..< numberOfPages { 343 | if let tabView = tabViewForPageAtIndex(i) { 344 | // get max tab height 345 | if (tabView.frame.height > maxTabHeight) { 346 | maxTabHeight = tabView.frame.height 347 | } 348 | cachedPageTabs[i] = tabView 349 | } 350 | } 351 | 352 | let tabSectionHeight = self.tabSectionHeight >= 0 ? self.tabSectionHeight : maxTabHeight 353 | let contentSectionHeight = self.frame.size.height - tabSectionHeight 354 | 355 | // setup tabs first, and set contents later (lazyLoadPages) 356 | var tabSectionScrollViewContentWidth: CGFloat = 0 357 | for i in 0 ..< numberOfPages { 358 | if let tabView = cachedPageTabs[i] { 359 | tabView.frame = CGRect( 360 | origin: CGPoint( 361 | x: tabSectionScrollViewContentWidth, 362 | y: tabSectionHeight - tabView.frame.height), 363 | size: tabView.frame.size) 364 | 365 | // bind event 366 | tabView.tag = i 367 | tabView.isUserInteractionEnabled = true 368 | tabView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(ACTabScrollView.tabViewDidClick(_:)))) 369 | tabSectionScrollView.addSubview(tabView) 370 | } 371 | tabSectionScrollViewContentWidth += widthForTabAtIndex(i) 372 | } 373 | 374 | // reset the fixed size of tab section 375 | tabSectionScrollView.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: tabSectionHeight) 376 | tabSectionScrollView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(ACTabScrollView.tabSectionScrollViewDidClick(_:)))) 377 | tabSectionScrollView.contentInset = UIEdgeInsets( 378 | top: 0, 379 | left: (self.frame.width / 2) - (widthForTabAtIndex(0) / 2), 380 | bottom: 0, 381 | right: (self.frame.width / 2) - (widthForTabAtIndex(numberOfPages - 1) / 2)) 382 | tabSectionScrollView.contentSize = CGSize(width: tabSectionScrollViewContentWidth, height: tabSectionHeight) 383 | 384 | // reset the fixed size of content section 385 | contentSectionScrollView.frame = CGRect(x: 0, y: tabSectionHeight, width: self.frame.size.width, height: contentSectionHeight) 386 | 387 | // reset the origin of arrow view 388 | arrowView.frame.origin = CGPoint(x: (self.frame.width - arrowView.frame.width) / 2, y: tabSectionHeight) 389 | } 390 | } 391 | 392 | fileprivate func updateTabAppearance(animated: Bool = true) { 393 | if (tabGradient) { 394 | if (numberOfPages != 0) { 395 | for i in 0 ..< numberOfPages { 396 | var alpha: CGFloat = 1.0 397 | 398 | let offset = abs(i - pageIndex) 399 | if (offset > 1) { 400 | alpha = 0.2 401 | } else if (offset > 0) { 402 | alpha = 0.4 403 | } else { 404 | alpha = 1.0 405 | } 406 | 407 | if let tab = self.cachedPageTabs[i] { 408 | if (animated) { 409 | UIView.animate(withDuration: 0.5, delay: 0, options: UIViewAnimationOptions.allowUserInteraction, animations: { 410 | tab.alpha = alpha 411 | return 412 | }, completion: nil) 413 | } else { 414 | tab.alpha = alpha 415 | } 416 | } 417 | } 418 | } 419 | } 420 | } 421 | 422 | fileprivate func moveToIndex(_ index: Int, animated: Bool) { 423 | if (index >= 0 && index < numberOfPages) { 424 | if (pagingEnabled) { 425 | // force stop 426 | stopScrolling() 427 | 428 | if (activedScrollView == nil || activedScrollView == tabSectionScrollView) { 429 | activedScrollView = contentSectionScrollView 430 | contentSectionScrollView.scrollRectToVisible(CGRect( 431 | origin: CGPoint(x: self.frame.width * CGFloat(index), y: 0), 432 | size: self.frame.size), animated: true) 433 | } 434 | } 435 | 436 | if (prevPageIndex != index) { 437 | prevPageIndex = index 438 | // callback 439 | delegate?.tabScrollView(self, didChangePageTo: index) 440 | } 441 | } 442 | } 443 | 444 | fileprivate func lazyLoadPages() { 445 | if (numberOfPages != 0) { 446 | let offset = 1 447 | let leftBoundIndex = pageIndex - offset > 0 ? pageIndex - offset : 0 448 | let rightBoundIndex = pageIndex + offset < numberOfPages ? pageIndex + offset : numberOfPages - 1 449 | 450 | var currentContentWidth: CGFloat = 0.0 451 | for i in 0 ..< numberOfPages { 452 | let width = self.frame.width 453 | if (i >= leftBoundIndex && i <= rightBoundIndex) { 454 | let frame = CGRect( 455 | x: currentContentWidth, 456 | y: 0, 457 | width: width, 458 | height: contentSectionScrollView.frame.size.height) 459 | insertPageAtIndex(i, frame: frame) 460 | } 461 | 462 | currentContentWidth += width 463 | } 464 | contentSectionScrollView.contentSize = CGSize(width: currentContentWidth, height: contentSectionScrollView.frame.height) 465 | 466 | // remove older caches 467 | while (cachedPageContents.count > realcachedPageLimit) { 468 | if let (_, view) = cachedPageContents.popFirst() { 469 | view.removeFromSuperview() 470 | } 471 | } 472 | } 473 | } 474 | 475 | fileprivate func insertPageAtIndex(_ index: Int, frame: CGRect) { 476 | if (cachedPageContents[index] == nil) { 477 | if let view = contentViewForPageAtIndex(index) { 478 | view.frame = frame 479 | cachedPageContents[index] = view 480 | contentSectionScrollView.addSubview(view) 481 | } 482 | } else { 483 | cachedPageContents.awake(index) 484 | } 485 | } 486 | 487 | } 488 | 489 | public struct CacheQueue { 490 | 491 | var keys: Array = [] 492 | var values: Dictionary = [:] 493 | var count: Int { 494 | return keys.count 495 | } 496 | 497 | subscript(key: Key) -> Value? { 498 | get { 499 | return values[key] 500 | } 501 | set { 502 | // key/value pair exists, delete it first 503 | if let index = keys.index(of: key) { 504 | keys.remove(at: index) 505 | } 506 | // append key 507 | if (newValue != nil) { 508 | keys.append(key) 509 | } 510 | // set value 511 | values[key] = newValue 512 | } 513 | } 514 | 515 | mutating func awake(_ key: Key) { 516 | if let index = keys.index(of: key) { 517 | keys.remove(at: index) 518 | keys.append(key) 519 | } 520 | } 521 | 522 | mutating func popFirst() -> (Key, Value)? { 523 | let key = keys.removeFirst() 524 | if let value = values.removeValue(forKey: key) { 525 | return (key, value) 526 | } else { 527 | return nil 528 | } 529 | } 530 | 531 | mutating func removeAll() { 532 | keys.removeAll() 533 | values.removeAll() 534 | } 535 | 536 | } 537 | 538 | class ArrowView : UIView { 539 | 540 | required init?(coder aDecoder: NSCoder) { 541 | fatalError("init(coder:) has not been implemented") 542 | } 543 | 544 | override init(frame: CGRect) { 545 | super.init(frame: frame) 546 | self.backgroundColor = UIColor.clear 547 | } 548 | 549 | var rect: CGRect! 550 | var arrorBackgroundColor: UIColor? 551 | 552 | var midX: CGFloat { return rect.midX } 553 | var midY: CGFloat { return rect.midY } 554 | var maxX: CGFloat { return rect.maxX } 555 | var maxY: CGFloat { return rect.maxY } 556 | 557 | override func draw(_ rect: CGRect) { 558 | self.rect = rect 559 | 560 | let ctx = UIGraphicsGetCurrentContext() 561 | 562 | ctx?.beginPath() 563 | ctx?.move(to: CGPoint(x: 0, y: 0)) 564 | ctx?.addQuadCurve(to: CGPoint(x:maxX * 0.2 , y: maxY * 0.2), control: CGPoint(x: maxX * 0.12, y: 0)) 565 | ctx?.addLine(to: CGPoint(x: midX - maxX * 0.05, y: maxY * 0.9)) 566 | ctx?.addQuadCurve(to: CGPoint(x: midX + maxX * 0.05, y: maxY * 0.9), control: CGPoint(x: midX, y: maxY)) 567 | ctx?.addLine(to: CGPoint(x: maxX * 0.8, y: maxY * 0.2)) 568 | ctx?.addQuadCurve(to: CGPoint(x: maxX, y: 0), control: CGPoint(x: maxX * 0.88, y: 0)) 569 | ctx?.closePath() 570 | 571 | ctx?.setFillColor((arrorBackgroundColor?.cgColor)!) 572 | ctx?.fillPath(); 573 | } 574 | 575 | } 576 | --------------------------------------------------------------------------------