├── .gitignore ├── JYSlideSegmentController.podspec ├── JYSlideSegmentController.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── JYSlideSegmentController ├── App │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── LaunchScreen.storyboard │ ├── ViewController.h │ ├── ViewController.m │ └── ViewController.xib ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── JYSlideSegmentController-Info.plist ├── JYSlideSegmentController-Prefix.pch ├── Source │ ├── JYSlideSegmentController.h │ └── JYSlideSegmentController.m ├── en.lproj │ └── InfoPlist.strings └── main.m ├── LICENSE ├── README.md └── demo.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by http://www.gitignore.io 2 | 3 | ### Xcode ### 4 | build/ 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | profile 16 | *.moved-aside 17 | DerivedData 18 | *.hmap 19 | *.ipa 20 | 21 | 22 | -------------------------------------------------------------------------------- /JYSlideSegmentController.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'JYSlideSegmentController' 3 | s.version = '2.2.3' 4 | s.license = 'MIT' 5 | s.summary = 'JYSlideSegmentController is a view controllers container, just like the UITabBarController, with smooth gesture.' 6 | s.homepage = 'https://github.com/HisaMasa/JYSlideSegmentController' 7 | s.authors = { 'jinyu' => 'goldfish.jinyu@gmail.com' } 8 | s.source = { :git => 'https://github.com/HisaMasa/JYSlideSegmentController.git', 9 | :tag => s.version.to_s 10 | } 11 | s.requires_arc = true 12 | s.source_files = 'JYSlideSegmentController/Source/**/*.{h,m}' 13 | s.platform = :ios, '8.0' 14 | end 15 | 16 | -------------------------------------------------------------------------------- /JYSlideSegmentController.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 71431B66215B89C200454F2A /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 71431B64215B89C200454F2A /* ViewController.m */; }; 11 | 71431B67215B89C200454F2A /* ViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 71431B65215B89C200454F2A /* ViewController.xib */; }; 12 | B76AFD671C36E89100935120 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B76AFD661C36E89100935120 /* LaunchScreen.storyboard */; }; 13 | E12DF8DF18E94BFC006B8543 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E12DF8DE18E94BFC006B8543 /* Foundation.framework */; }; 14 | E12DF8E118E94BFC006B8543 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E12DF8E018E94BFC006B8543 /* CoreGraphics.framework */; }; 15 | E12DF8E318E94BFC006B8543 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E12DF8E218E94BFC006B8543 /* UIKit.framework */; }; 16 | E12DF8E918E94BFC006B8543 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E12DF8E718E94BFC006B8543 /* InfoPlist.strings */; }; 17 | E12DF8EB18E94BFC006B8543 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E12DF8EA18E94BFC006B8543 /* main.m */; }; 18 | E12DF8FA18E94BFC006B8543 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E12DF8F918E94BFC006B8543 /* Images.xcassets */; }; 19 | E12DF90118E94BFD006B8543 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E12DF90018E94BFD006B8543 /* XCTest.framework */; }; 20 | E12DF90218E94BFD006B8543 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E12DF8DE18E94BFC006B8543 /* Foundation.framework */; }; 21 | E12DF90318E94BFD006B8543 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E12DF8E218E94BFC006B8543 /* UIKit.framework */; }; 22 | E15A7BEE18EA9FBD00BD23E5 /* JYSlideSegmentController.m in Sources */ = {isa = PBXBuildFile; fileRef = E15A7BED18EA9FBD00BD23E5 /* JYSlideSegmentController.m */; }; 23 | E15A7BF218EA9FD700BD23E5 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E15A7BF118EA9FD700BD23E5 /* AppDelegate.m */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | E12DF90418E94BFD006B8543 /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = E12DF8D318E94BFC006B8543 /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = E12DF8DA18E94BFC006B8543; 32 | remoteInfo = JYSlideSegmentController; 33 | }; 34 | /* End PBXContainerItemProxy section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | 71431B63215B89C200454F2A /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 38 | 71431B64215B89C200454F2A /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 39 | 71431B65215B89C200454F2A /* ViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ViewController.xib; sourceTree = ""; }; 40 | B76AFD661C36E89100935120 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 41 | E12DF8DB18E94BFC006B8543 /* JYSlideSegmentController.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JYSlideSegmentController.app; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | E12DF8DE18E94BFC006B8543 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 43 | E12DF8E018E94BFC006B8543 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 44 | E12DF8E218E94BFC006B8543 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 45 | E12DF8E618E94BFC006B8543 /* JYSlideSegmentController-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "JYSlideSegmentController-Info.plist"; sourceTree = ""; }; 46 | E12DF8E818E94BFC006B8543 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 47 | E12DF8EA18E94BFC006B8543 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 48 | E12DF8EC18E94BFC006B8543 /* JYSlideSegmentController-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JYSlideSegmentController-Prefix.pch"; sourceTree = ""; }; 49 | E12DF8F918E94BFC006B8543 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 50 | E12DF8FF18E94BFD006B8543 /* JYSlideSegmentControllerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JYSlideSegmentControllerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | E12DF90018E94BFD006B8543 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 52 | E15A7BEC18EA9FBD00BD23E5 /* JYSlideSegmentController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JYSlideSegmentController.h; sourceTree = ""; }; 53 | E15A7BED18EA9FBD00BD23E5 /* JYSlideSegmentController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JYSlideSegmentController.m; sourceTree = ""; }; 54 | E15A7BF018EA9FD700BD23E5 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 55 | E15A7BF118EA9FD700BD23E5 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 56 | /* End PBXFileReference section */ 57 | 58 | /* Begin PBXFrameworksBuildPhase section */ 59 | E12DF8D818E94BFC006B8543 /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | E12DF8E118E94BFC006B8543 /* CoreGraphics.framework in Frameworks */, 64 | E12DF8E318E94BFC006B8543 /* UIKit.framework in Frameworks */, 65 | E12DF8DF18E94BFC006B8543 /* Foundation.framework in Frameworks */, 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | E12DF8FC18E94BFD006B8543 /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | E12DF90118E94BFD006B8543 /* XCTest.framework in Frameworks */, 74 | E12DF90318E94BFD006B8543 /* UIKit.framework in Frameworks */, 75 | E12DF90218E94BFD006B8543 /* Foundation.framework in Frameworks */, 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | /* End PBXFrameworksBuildPhase section */ 80 | 81 | /* Begin PBXGroup section */ 82 | E12DF8D218E94BFC006B8543 = { 83 | isa = PBXGroup; 84 | children = ( 85 | E12DF8E418E94BFC006B8543 /* JYSlideSegmentController */, 86 | E12DF8DD18E94BFC006B8543 /* Frameworks */, 87 | E12DF8DC18E94BFC006B8543 /* Products */, 88 | ); 89 | sourceTree = ""; 90 | }; 91 | E12DF8DC18E94BFC006B8543 /* Products */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | E12DF8DB18E94BFC006B8543 /* JYSlideSegmentController.app */, 95 | E12DF8FF18E94BFD006B8543 /* JYSlideSegmentControllerTests.xctest */, 96 | ); 97 | name = Products; 98 | sourceTree = ""; 99 | }; 100 | E12DF8DD18E94BFC006B8543 /* Frameworks */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | E12DF8DE18E94BFC006B8543 /* Foundation.framework */, 104 | E12DF8E018E94BFC006B8543 /* CoreGraphics.framework */, 105 | E12DF8E218E94BFC006B8543 /* UIKit.framework */, 106 | E12DF90018E94BFD006B8543 /* XCTest.framework */, 107 | ); 108 | name = Frameworks; 109 | sourceTree = ""; 110 | }; 111 | E12DF8E418E94BFC006B8543 /* JYSlideSegmentController */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | E15A7BEF18EA9FD700BD23E5 /* App */, 115 | E15A7BEB18EA9FBD00BD23E5 /* Source */, 116 | E12DF8F918E94BFC006B8543 /* Images.xcassets */, 117 | E12DF8E518E94BFC006B8543 /* Supporting Files */, 118 | ); 119 | path = JYSlideSegmentController; 120 | sourceTree = ""; 121 | }; 122 | E12DF8E518E94BFC006B8543 /* Supporting Files */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | E12DF8E618E94BFC006B8543 /* JYSlideSegmentController-Info.plist */, 126 | E12DF8E718E94BFC006B8543 /* InfoPlist.strings */, 127 | E12DF8EA18E94BFC006B8543 /* main.m */, 128 | E12DF8EC18E94BFC006B8543 /* JYSlideSegmentController-Prefix.pch */, 129 | ); 130 | name = "Supporting Files"; 131 | sourceTree = ""; 132 | }; 133 | E15A7BEB18EA9FBD00BD23E5 /* Source */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | E15A7BEC18EA9FBD00BD23E5 /* JYSlideSegmentController.h */, 137 | E15A7BED18EA9FBD00BD23E5 /* JYSlideSegmentController.m */, 138 | ); 139 | path = Source; 140 | sourceTree = ""; 141 | }; 142 | E15A7BEF18EA9FD700BD23E5 /* App */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | E15A7BF018EA9FD700BD23E5 /* AppDelegate.h */, 146 | E15A7BF118EA9FD700BD23E5 /* AppDelegate.m */, 147 | 71431B63215B89C200454F2A /* ViewController.h */, 148 | 71431B64215B89C200454F2A /* ViewController.m */, 149 | 71431B65215B89C200454F2A /* ViewController.xib */, 150 | B76AFD661C36E89100935120 /* LaunchScreen.storyboard */, 151 | ); 152 | path = App; 153 | sourceTree = ""; 154 | }; 155 | /* End PBXGroup section */ 156 | 157 | /* Begin PBXNativeTarget section */ 158 | E12DF8DA18E94BFC006B8543 /* JYSlideSegmentController */ = { 159 | isa = PBXNativeTarget; 160 | buildConfigurationList = E12DF91018E94BFD006B8543 /* Build configuration list for PBXNativeTarget "JYSlideSegmentController" */; 161 | buildPhases = ( 162 | E12DF8D718E94BFC006B8543 /* Sources */, 163 | E12DF8D818E94BFC006B8543 /* Frameworks */, 164 | E12DF8D918E94BFC006B8543 /* Resources */, 165 | ); 166 | buildRules = ( 167 | ); 168 | dependencies = ( 169 | ); 170 | name = JYSlideSegmentController; 171 | productName = JYSlideSegmentController; 172 | productReference = E12DF8DB18E94BFC006B8543 /* JYSlideSegmentController.app */; 173 | productType = "com.apple.product-type.application"; 174 | }; 175 | E12DF8FE18E94BFD006B8543 /* JYSlideSegmentControllerTests */ = { 176 | isa = PBXNativeTarget; 177 | buildConfigurationList = E12DF91318E94BFD006B8543 /* Build configuration list for PBXNativeTarget "JYSlideSegmentControllerTests" */; 178 | buildPhases = ( 179 | E12DF8FB18E94BFD006B8543 /* Sources */, 180 | E12DF8FC18E94BFD006B8543 /* Frameworks */, 181 | E12DF8FD18E94BFD006B8543 /* Resources */, 182 | ); 183 | buildRules = ( 184 | ); 185 | dependencies = ( 186 | E12DF90518E94BFD006B8543 /* PBXTargetDependency */, 187 | ); 188 | name = JYSlideSegmentControllerTests; 189 | productName = JYSlideSegmentControllerTests; 190 | productReference = E12DF8FF18E94BFD006B8543 /* JYSlideSegmentControllerTests.xctest */; 191 | productType = "com.apple.product-type.bundle.unit-test"; 192 | }; 193 | /* End PBXNativeTarget section */ 194 | 195 | /* Begin PBXProject section */ 196 | E12DF8D318E94BFC006B8543 /* Project object */ = { 197 | isa = PBXProject; 198 | attributes = { 199 | LastUpgradeCheck = 1010; 200 | ORGANIZATIONNAME = Alvin; 201 | TargetAttributes = { 202 | E12DF8FE18E94BFD006B8543 = { 203 | TestTargetID = E12DF8DA18E94BFC006B8543; 204 | }; 205 | }; 206 | }; 207 | buildConfigurationList = E12DF8D618E94BFC006B8543 /* Build configuration list for PBXProject "JYSlideSegmentController" */; 208 | compatibilityVersion = "Xcode 3.2"; 209 | developmentRegion = English; 210 | hasScannedForEncodings = 0; 211 | knownRegions = ( 212 | en, 213 | Base, 214 | ); 215 | mainGroup = E12DF8D218E94BFC006B8543; 216 | productRefGroup = E12DF8DC18E94BFC006B8543 /* Products */; 217 | projectDirPath = ""; 218 | projectRoot = ""; 219 | targets = ( 220 | E12DF8DA18E94BFC006B8543 /* JYSlideSegmentController */, 221 | E12DF8FE18E94BFD006B8543 /* JYSlideSegmentControllerTests */, 222 | ); 223 | }; 224 | /* End PBXProject section */ 225 | 226 | /* Begin PBXResourcesBuildPhase section */ 227 | E12DF8D918E94BFC006B8543 /* Resources */ = { 228 | isa = PBXResourcesBuildPhase; 229 | buildActionMask = 2147483647; 230 | files = ( 231 | B76AFD671C36E89100935120 /* LaunchScreen.storyboard in Resources */, 232 | E12DF8FA18E94BFC006B8543 /* Images.xcassets in Resources */, 233 | 71431B67215B89C200454F2A /* ViewController.xib in Resources */, 234 | E12DF8E918E94BFC006B8543 /* InfoPlist.strings in Resources */, 235 | ); 236 | runOnlyForDeploymentPostprocessing = 0; 237 | }; 238 | E12DF8FD18E94BFD006B8543 /* Resources */ = { 239 | isa = PBXResourcesBuildPhase; 240 | buildActionMask = 2147483647; 241 | files = ( 242 | ); 243 | runOnlyForDeploymentPostprocessing = 0; 244 | }; 245 | /* End PBXResourcesBuildPhase section */ 246 | 247 | /* Begin PBXSourcesBuildPhase section */ 248 | E12DF8D718E94BFC006B8543 /* Sources */ = { 249 | isa = PBXSourcesBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | E15A7BEE18EA9FBD00BD23E5 /* JYSlideSegmentController.m in Sources */, 253 | E12DF8EB18E94BFC006B8543 /* main.m in Sources */, 254 | 71431B66215B89C200454F2A /* ViewController.m in Sources */, 255 | E15A7BF218EA9FD700BD23E5 /* AppDelegate.m in Sources */, 256 | ); 257 | runOnlyForDeploymentPostprocessing = 0; 258 | }; 259 | E12DF8FB18E94BFD006B8543 /* Sources */ = { 260 | isa = PBXSourcesBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | }; 266 | /* End PBXSourcesBuildPhase section */ 267 | 268 | /* Begin PBXTargetDependency section */ 269 | E12DF90518E94BFD006B8543 /* PBXTargetDependency */ = { 270 | isa = PBXTargetDependency; 271 | target = E12DF8DA18E94BFC006B8543 /* JYSlideSegmentController */; 272 | targetProxy = E12DF90418E94BFD006B8543 /* PBXContainerItemProxy */; 273 | }; 274 | /* End PBXTargetDependency section */ 275 | 276 | /* Begin PBXVariantGroup section */ 277 | E12DF8E718E94BFC006B8543 /* InfoPlist.strings */ = { 278 | isa = PBXVariantGroup; 279 | children = ( 280 | E12DF8E818E94BFC006B8543 /* en */, 281 | ); 282 | name = InfoPlist.strings; 283 | sourceTree = ""; 284 | }; 285 | /* End PBXVariantGroup section */ 286 | 287 | /* Begin XCBuildConfiguration section */ 288 | E12DF90E18E94BFD006B8543 /* Debug */ = { 289 | isa = XCBuildConfiguration; 290 | buildSettings = { 291 | ALWAYS_SEARCH_USER_PATHS = NO; 292 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 293 | CLANG_CXX_LIBRARY = "libc++"; 294 | CLANG_ENABLE_MODULES = YES; 295 | CLANG_ENABLE_OBJC_ARC = YES; 296 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 297 | CLANG_WARN_BOOL_CONVERSION = YES; 298 | CLANG_WARN_COMMA = YES; 299 | CLANG_WARN_CONSTANT_CONVERSION = YES; 300 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 301 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 302 | CLANG_WARN_EMPTY_BODY = YES; 303 | CLANG_WARN_ENUM_CONVERSION = YES; 304 | CLANG_WARN_INFINITE_RECURSION = YES; 305 | CLANG_WARN_INT_CONVERSION = YES; 306 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 307 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 308 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 309 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 310 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 311 | CLANG_WARN_STRICT_PROTOTYPES = YES; 312 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 313 | CLANG_WARN_UNREACHABLE_CODE = YES; 314 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 315 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 316 | COPY_PHASE_STRIP = NO; 317 | ENABLE_STRICT_OBJC_MSGSEND = YES; 318 | ENABLE_TESTABILITY = YES; 319 | GCC_C_LANGUAGE_STANDARD = gnu99; 320 | GCC_DYNAMIC_NO_PIC = NO; 321 | GCC_NO_COMMON_BLOCKS = YES; 322 | GCC_OPTIMIZATION_LEVEL = 0; 323 | GCC_PREPROCESSOR_DEFINITIONS = ( 324 | "DEBUG=1", 325 | "$(inherited)", 326 | ); 327 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 328 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 329 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 330 | GCC_WARN_UNDECLARED_SELECTOR = YES; 331 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 332 | GCC_WARN_UNUSED_FUNCTION = YES; 333 | GCC_WARN_UNUSED_VARIABLE = YES; 334 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 335 | ONLY_ACTIVE_ARCH = YES; 336 | SDKROOT = iphoneos; 337 | TARGETED_DEVICE_FAMILY = "1,2"; 338 | }; 339 | name = Debug; 340 | }; 341 | E12DF90F18E94BFD006B8543 /* Release */ = { 342 | isa = XCBuildConfiguration; 343 | buildSettings = { 344 | ALWAYS_SEARCH_USER_PATHS = NO; 345 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 346 | CLANG_CXX_LIBRARY = "libc++"; 347 | CLANG_ENABLE_MODULES = YES; 348 | CLANG_ENABLE_OBJC_ARC = YES; 349 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 350 | CLANG_WARN_BOOL_CONVERSION = YES; 351 | CLANG_WARN_COMMA = YES; 352 | CLANG_WARN_CONSTANT_CONVERSION = YES; 353 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 354 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 355 | CLANG_WARN_EMPTY_BODY = YES; 356 | CLANG_WARN_ENUM_CONVERSION = YES; 357 | CLANG_WARN_INFINITE_RECURSION = YES; 358 | CLANG_WARN_INT_CONVERSION = YES; 359 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 360 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 361 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 362 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 363 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 364 | CLANG_WARN_STRICT_PROTOTYPES = YES; 365 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 366 | CLANG_WARN_UNREACHABLE_CODE = YES; 367 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 368 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 369 | COPY_PHASE_STRIP = YES; 370 | ENABLE_NS_ASSERTIONS = NO; 371 | ENABLE_STRICT_OBJC_MSGSEND = YES; 372 | GCC_C_LANGUAGE_STANDARD = gnu99; 373 | GCC_NO_COMMON_BLOCKS = YES; 374 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 375 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 376 | GCC_WARN_UNDECLARED_SELECTOR = YES; 377 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 378 | GCC_WARN_UNUSED_FUNCTION = YES; 379 | GCC_WARN_UNUSED_VARIABLE = YES; 380 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 381 | SDKROOT = iphoneos; 382 | TARGETED_DEVICE_FAMILY = "1,2"; 383 | VALIDATE_PRODUCT = YES; 384 | }; 385 | name = Release; 386 | }; 387 | E12DF91118E94BFD006B8543 /* Debug */ = { 388 | isa = XCBuildConfiguration; 389 | buildSettings = { 390 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 391 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 392 | FRAMEWORK_SEARCH_PATHS = ( 393 | "$(inherited)", 394 | "$(PROJECT_DIR)", 395 | ); 396 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 397 | GCC_PREFIX_HEADER = "JYSlideSegmentController/JYSlideSegmentController-Prefix.pch"; 398 | INFOPLIST_FILE = "JYSlideSegmentController/JYSlideSegmentController-Info.plist"; 399 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 400 | OTHER_CODE_SIGN_FLAGS = ""; 401 | OTHER_LDFLAGS = "-ObjC"; 402 | PRODUCT_BUNDLE_IDENTIFIER = com.jinyu.JYSlideSegmentController; 403 | PRODUCT_NAME = "$(TARGET_NAME)"; 404 | WRAPPER_EXTENSION = app; 405 | }; 406 | name = Debug; 407 | }; 408 | E12DF91218E94BFD006B8543 /* Release */ = { 409 | isa = XCBuildConfiguration; 410 | buildSettings = { 411 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 412 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 413 | FRAMEWORK_SEARCH_PATHS = ( 414 | "$(inherited)", 415 | "$(PROJECT_DIR)", 416 | ); 417 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 418 | GCC_PREFIX_HEADER = "JYSlideSegmentController/JYSlideSegmentController-Prefix.pch"; 419 | INFOPLIST_FILE = "JYSlideSegmentController/JYSlideSegmentController-Info.plist"; 420 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 421 | OTHER_CODE_SIGN_FLAGS = ""; 422 | OTHER_LDFLAGS = "-ObjC"; 423 | PRODUCT_BUNDLE_IDENTIFIER = com.jinyu.JYSlideSegmentController; 424 | PRODUCT_NAME = "$(TARGET_NAME)"; 425 | WRAPPER_EXTENSION = app; 426 | }; 427 | name = Release; 428 | }; 429 | E12DF91418E94BFD006B8543 /* Debug */ = { 430 | isa = XCBuildConfiguration; 431 | buildSettings = { 432 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/JYSlideSegmentController.app/JYSlideSegmentController"; 433 | FRAMEWORK_SEARCH_PATHS = ( 434 | "$(SDKROOT)/Developer/Library/Frameworks", 435 | "$(inherited)", 436 | "$(DEVELOPER_FRAMEWORKS_DIR)", 437 | ); 438 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 439 | GCC_PREFIX_HEADER = "JYSlideSegmentController/JYSlideSegmentController-Prefix.pch"; 440 | GCC_PREPROCESSOR_DEFINITIONS = ( 441 | "DEBUG=1", 442 | "$(inherited)", 443 | ); 444 | INFOPLIST_FILE = "JYSlideSegmentControllerTests/JYSlideSegmentControllerTests-Info.plist"; 445 | PRODUCT_NAME = "$(TARGET_NAME)"; 446 | TEST_HOST = "$(BUNDLE_LOADER)"; 447 | WRAPPER_EXTENSION = xctest; 448 | }; 449 | name = Debug; 450 | }; 451 | E12DF91518E94BFD006B8543 /* Release */ = { 452 | isa = XCBuildConfiguration; 453 | buildSettings = { 454 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/JYSlideSegmentController.app/JYSlideSegmentController"; 455 | FRAMEWORK_SEARCH_PATHS = ( 456 | "$(SDKROOT)/Developer/Library/Frameworks", 457 | "$(inherited)", 458 | "$(DEVELOPER_FRAMEWORKS_DIR)", 459 | ); 460 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 461 | GCC_PREFIX_HEADER = "JYSlideSegmentController/JYSlideSegmentController-Prefix.pch"; 462 | INFOPLIST_FILE = "JYSlideSegmentControllerTests/JYSlideSegmentControllerTests-Info.plist"; 463 | PRODUCT_NAME = "$(TARGET_NAME)"; 464 | TEST_HOST = "$(BUNDLE_LOADER)"; 465 | WRAPPER_EXTENSION = xctest; 466 | }; 467 | name = Release; 468 | }; 469 | /* End XCBuildConfiguration section */ 470 | 471 | /* Begin XCConfigurationList section */ 472 | E12DF8D618E94BFC006B8543 /* Build configuration list for PBXProject "JYSlideSegmentController" */ = { 473 | isa = XCConfigurationList; 474 | buildConfigurations = ( 475 | E12DF90E18E94BFD006B8543 /* Debug */, 476 | E12DF90F18E94BFD006B8543 /* Release */, 477 | ); 478 | defaultConfigurationIsVisible = 0; 479 | defaultConfigurationName = Release; 480 | }; 481 | E12DF91018E94BFD006B8543 /* Build configuration list for PBXNativeTarget "JYSlideSegmentController" */ = { 482 | isa = XCConfigurationList; 483 | buildConfigurations = ( 484 | E12DF91118E94BFD006B8543 /* Debug */, 485 | E12DF91218E94BFD006B8543 /* Release */, 486 | ); 487 | defaultConfigurationIsVisible = 0; 488 | defaultConfigurationName = Release; 489 | }; 490 | E12DF91318E94BFD006B8543 /* Build configuration list for PBXNativeTarget "JYSlideSegmentControllerTests" */ = { 491 | isa = XCConfigurationList; 492 | buildConfigurations = ( 493 | E12DF91418E94BFD006B8543 /* Debug */, 494 | E12DF91518E94BFD006B8543 /* Release */, 495 | ); 496 | defaultConfigurationIsVisible = 0; 497 | defaultConfigurationName = Release; 498 | }; 499 | /* End XCConfigurationList section */ 500 | }; 501 | rootObject = E12DF8D318E94BFC006B8543 /* Project object */; 502 | } 503 | -------------------------------------------------------------------------------- /JYSlideSegmentController.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JYSlideSegmentController.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /JYSlideSegmentController/App/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // JYSlideSegmentController 4 | // 5 | // Created by Alvin on 14-3-31. 6 | // Copyright (c) 2014年 Alvin. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "JYSlideSegmentController.h" 11 | 12 | @interface AppDelegate : UIResponder 13 | 14 | @property (strong, nonatomic) UIWindow *window; 15 | @property (strong, nonatomic) JYSlideSegmentController *slideSegmentController; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /JYSlideSegmentController/App/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // JYSlideSegmentController 4 | // 5 | // Created by Alvin on 14-3-31. 6 | // Copyright (c) 2014年 Alvin. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "ViewController.h" 11 | 12 | @implementation AppDelegate 13 | { 14 | BOOL _changeFlag; 15 | } 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 18 | { 19 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 20 | self.slideSegmentController = [[JYSlideSegmentController alloc] initWithViewControllers:[self vcs] 21 | startIndex:0]; 22 | self.slideSegmentController.title = @"JYSlideSegmentController"; 23 | self.slideSegmentController.indicatorInsets = UIEdgeInsetsMake(0, 0, 2, 0); 24 | self.slideSegmentController.segmentWidth = 80; 25 | self.slideSegmentController.indicatorWidth = 22; 26 | UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:self.slideSegmentController]; 27 | self.window.rootViewController = navi; 28 | 29 | UIBarButtonItem *changeVCsItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh 30 | target:self 31 | action:@selector(changeVCs)]; 32 | self.slideSegmentController.navigationItem.rightBarButtonItem = changeVCsItem; 33 | [self.window makeKeyAndVisible]; 34 | return YES; 35 | } 36 | 37 | - (void)changeVCs 38 | { 39 | // self.slideSegmentController.startIndex = 0; 40 | _changeFlag = !_changeFlag; 41 | self.slideSegmentController.viewControllers = [self vcs]; 42 | } 43 | 44 | - (NSArray *)vcs 45 | { 46 | NSMutableArray *vcs = [NSMutableArray array]; 47 | for (int i = 0; i < 10; i++) { 48 | ViewController *vc = [[ViewController alloc] initWithNibName:nil bundle:nil]; 49 | vc.title = _changeFlag ? [NSString stringWithFormat:@"vc%d", i] : [NSString stringWithFormat:@"%d", i]; 50 | if (_changeFlag ? (i % 2 == 0) : (i % 2 != 0)) { 51 | vc.view.backgroundColor = [UIColor blueColor]; 52 | } else { 53 | vc.view.backgroundColor = [UIColor redColor]; 54 | } 55 | [vcs addObject:vc]; 56 | } 57 | return [vcs copy]; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /JYSlideSegmentController/App/LaunchScreen.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 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /JYSlideSegmentController/App/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // JYSlideSegmentController 4 | // 5 | // Created by jinyu on 2018/9/26. 6 | // Copyright © 2018 Alvin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | @property (weak, nonatomic) IBOutlet UILabel *label; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /JYSlideSegmentController/App/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // JYSlideSegmentController 4 | // 5 | // Created by jinyu on 2018/9/26. 6 | // Copyright © 2018 Alvin. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | @interface ViewController () 12 | 13 | @end 14 | 15 | @implementation ViewController 16 | 17 | - (void)viewDidLoad 18 | { 19 | [super viewDidLoad]; 20 | self.label.text = self.title; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /JYSlideSegmentController/App/ViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /JYSlideSegmentController/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 | } -------------------------------------------------------------------------------- /JYSlideSegmentController/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "orientation" : "portrait", 20 | "idiom" : "ipad", 21 | "extent" : "full-screen", 22 | "minimum-system-version" : "7.0", 23 | "scale" : "1x" 24 | }, 25 | { 26 | "orientation" : "landscape", 27 | "idiom" : "ipad", 28 | "extent" : "full-screen", 29 | "minimum-system-version" : "7.0", 30 | "scale" : "1x" 31 | }, 32 | { 33 | "orientation" : "portrait", 34 | "idiom" : "ipad", 35 | "extent" : "full-screen", 36 | "minimum-system-version" : "7.0", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "orientation" : "landscape", 41 | "idiom" : "ipad", 42 | "extent" : "full-screen", 43 | "minimum-system-version" : "7.0", 44 | "scale" : "2x" 45 | } 46 | ], 47 | "info" : { 48 | "version" : 1, 49 | "author" : "xcode" 50 | } 51 | } -------------------------------------------------------------------------------- /JYSlideSegmentController/JYSlideSegmentController-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 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 | -------------------------------------------------------------------------------- /JYSlideSegmentController/JYSlideSegmentController-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /JYSlideSegmentController/Source/JYSlideSegmentController.h: -------------------------------------------------------------------------------- 1 | // 2 | // JYSlideSegmentController.h 3 | // JYSlideSegmentController 4 | // 5 | // Created by Alvin on 14-3-16. 6 | // Copyright (c) 2014年 Alvin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | extern NSString *const segmentBarItemID; 12 | 13 | /** 14 | segmentBar vertical position. 15 | */ 16 | typedef NS_ENUM(NSInteger, JYSlideSegmentBarPosition) { 17 | JYSlideSegmentBarPositionTop = 0, 18 | JYSlideSegmentBarPositionBottom = 1, 19 | }; 20 | 21 | @class JYSlideSegmentController; 22 | 23 | /** 24 | * Need to be implemented this methods for custom UI of segment button 25 | */ 26 | @protocol JYSlideSegmentDataSource 27 | @required 28 | 29 | - (NSInteger)slideSegment:(UICollectionView *)segmentBar 30 | numberOfItemsInSection:(NSInteger)section; 31 | 32 | - (UICollectionViewCell *)slideSegment:(UICollectionView *)segmentBar 33 | cellForItemAtIndexPath:(NSIndexPath *)indexPath; 34 | 35 | - (CGSize)slideSegment:(UICollectionView *)segmentBar 36 | layout:(UICollectionViewLayout *)segmentBarViewLayout 37 | sizeForItemAtIndexPath:(NSIndexPath *)indexPath; 38 | 39 | 40 | @optional 41 | - (NSInteger)numberOfSectionsInslideSegment:(UICollectionView *)segmentBar; 42 | 43 | @end 44 | 45 | @protocol JYSlideSegmentDelegate 46 | @optional 47 | - (void)didSelectViewController:(UIViewController *)viewController; 48 | - (void)didFullyShowViewController:(UIViewController *)viewController; 49 | - (BOOL)shouldSelectViewController:(UIViewController *)viewController; 50 | - (void)slideViewDidScroll:(UIScrollView *)slideView; 51 | 52 | /** 53 | * scrollView delegate for segmentBar 54 | */ 55 | - (void)slideSegmentDidScroll:(UIScrollView *)segmentBar; 56 | - (void)slideSegmentDidEndDecelerating:(UIScrollView *)segmentBar; 57 | - (void)slideSegmentDidEndDragging:(UIScrollView *)segmentBar willDecelerate:(BOOL)decelerate; 58 | - (void)slideSegment:(UICollectionView *)segmentBar didSelectItemAtIndexPath:(NSIndexPath *)indexPath; 59 | - (void)slideDidEndScrollingAnimation:(UIScrollView *)segmentBar; 60 | - (void)slideWillBeginDragging:(UIScrollView *)segmentBar; 61 | 62 | /** 63 | * progress of switching index, range -1 to 1, is influnced by slideView contentOffset. 64 | * 0 - 1: fromIndex < toIndex (switching to right), 0 - -1: fromIndex > toIndex (switching to left) 65 | */ 66 | - (void)slideViewMovingProgress:(CGFloat)progress fromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex; 67 | @end 68 | 69 | @protocol JYSlideViewDelegate 70 | 71 | @optional 72 | - (BOOL)slideViewPanGestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer; 73 | - (BOOL)slideViewPanGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 74 | shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer; 75 | @end 76 | 77 | @interface JYSlideView : UICollectionView 78 | 79 | @property (nonatomic, weak) id slideDelegate; 80 | 81 | @end 82 | 83 | @interface JYSlideSegmentController : UIViewController 84 | 85 | /** 86 | * Child viewControllers of SlideSegmentController 87 | * it will reset the selectedIndex to start index, after you setting this property, 88 | * if you want to change this performance, you should set startIndex before setting viewControllers 89 | */ 90 | @property (nonatomic, copy) NSArray *viewControllers; 91 | @property (nonatomic, assign) NSInteger startIndex; 92 | 93 | @property (nonatomic, strong, readonly) UICollectionView *segmentBar; 94 | @property (nonatomic, strong, readonly) JYSlideView *slideView; 95 | 96 | @property (nonatomic, weak, readonly) UIViewController *selectedViewController; 97 | @property (nonatomic, assign, readonly) NSInteger selectedIndex; 98 | 99 | /** 100 | * Custom UI 101 | */ 102 | @property (nonatomic, assign) CGFloat segmentWidth; 103 | @property (nonatomic, assign) CGFloat segmentHeight; 104 | @property (nonatomic, assign) UIEdgeInsets segmentInsets; // segmentBar layout sectionInset 105 | 106 | /** 107 | The segmentBar position in container. Default is JYSlideSegmentBarPositionTop. 108 | */ 109 | @property (nonatomic, assign) JYSlideSegmentBarPosition segmentBarPosition; 110 | 111 | @property (nonatomic, strong) UIView *indicator; 112 | 113 | @property (nonatomic, assign) UIEdgeInsets indicatorInsets; 114 | 115 | @property (nonatomic, assign) CGFloat indicatorWidth; 116 | @property (nonatomic, assign) CGFloat indicatorHeight; 117 | 118 | @property (nonatomic, strong) UIColor *separatorColor; 119 | @property (nonatomic, assign) CGFloat separatorHeight; 120 | 121 | /** 122 | * By default segmentBar use viewController's title for segment's button title 123 | * You should implement JYSlideSegmentDataSource & JYSlideSegmentDelegate instead of segmentBar delegate & datasource 124 | */ 125 | @property (nonatomic, weak) id delegate; 126 | @property (nonatomic, weak) id dataSource; 127 | 128 | - (instancetype)initWithViewControllers:(NSArray *)viewControllers; 129 | - (instancetype)initWithViewControllers:(NSArray *)viewControllers 130 | startIndex:(NSInteger)startIndex; 131 | 132 | - (void)scrollToViewWithIndex:(NSInteger)index animated:(BOOL)animated; 133 | 134 | @end 135 | -------------------------------------------------------------------------------- /JYSlideSegmentController/Source/JYSlideSegmentController.m: -------------------------------------------------------------------------------- 1 | // 2 | // JYSlideSegmentController.m 3 | // JYSlideSegmentController 4 | // 5 | // Created by Alvin on 14-3-16. 6 | // Copyright (c) 2014年 Alvin. All rights reserved. 7 | // 8 | 9 | #import "JYSlideSegmentController.h" 10 | 11 | NSString * const JYSegmentBarItemID = @"JYSegmentBarItem"; 12 | NSString * const JYSlideViewItemID = @"JYSlideViewItemID"; 13 | 14 | @interface JYSegmentBarItem : UICollectionViewCell 15 | 16 | @property (nonatomic, strong) UILabel *titleLabel; 17 | 18 | @end 19 | 20 | 21 | @implementation JYSegmentBarItem 22 | 23 | - (id)initWithFrame:(CGRect)frame 24 | { 25 | self = [super initWithFrame:frame]; 26 | if (self) { 27 | [self.contentView addSubview:self.titleLabel]; 28 | [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.titleLabel 29 | attribute:NSLayoutAttributeCenterX 30 | relatedBy:NSLayoutRelationEqual 31 | toItem:self.contentView 32 | attribute:NSLayoutAttributeCenterX 33 | multiplier:1 34 | constant:0]]; 35 | [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.titleLabel 36 | attribute:NSLayoutAttributeCenterY 37 | relatedBy:NSLayoutRelationEqual 38 | toItem:self.contentView 39 | attribute:NSLayoutAttributeCenterY 40 | multiplier:1 41 | constant:0]]; 42 | } 43 | return self; 44 | } 45 | 46 | - (UILabel *)titleLabel 47 | { 48 | if (!_titleLabel) { 49 | _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 50 | _titleLabel.textAlignment = NSTextAlignmentCenter; 51 | _titleLabel.translatesAutoresizingMaskIntoConstraints = NO; 52 | } 53 | return _titleLabel; 54 | } 55 | 56 | @end 57 | 58 | @implementation JYSlideView 59 | 60 | - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer 61 | { 62 | if (gestureRecognizer == self.panGestureRecognizer) { 63 | if ([self.slideDelegate 64 | respondsToSelector: 65 | @selector(slideViewPanGestureRecognizerShouldBegin:)]) { 66 | return [self.slideDelegate 67 | slideViewPanGestureRecognizerShouldBegin:gestureRecognizer]; 68 | } 69 | } 70 | return YES; 71 | } 72 | 73 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 74 | shouldRecognizeSimultaneouslyWithGestureRecognizer: 75 | (UIGestureRecognizer *)otherGestureRecognizer 76 | { 77 | if (gestureRecognizer == self.panGestureRecognizer) { 78 | if ([self.slideDelegate 79 | respondsToSelector: 80 | @selector(slideViewPanGestureRecognizer: 81 | shouldRecognizeSimultaneouslyWithGestureRecognizer:)]) { 82 | return [self.slideDelegate slideViewPanGestureRecognizer:gestureRecognizer 83 | shouldRecognizeSimultaneouslyWithGestureRecognizer: 84 | otherGestureRecognizer]; 85 | } 86 | } 87 | return YES; 88 | } 89 | 90 | @end 91 | 92 | @interface JYSlideSegmentController () 93 | 94 | 95 | @property (nonatomic, strong, readwrite) UICollectionView *segmentBar; 96 | @property (nonatomic, strong, readwrite) JYSlideView *slideView; 97 | 98 | @property (nonatomic, strong) UIView *separator; 99 | 100 | @property (nonatomic, strong) UICollectionViewFlowLayout *segmentBarLayout; 101 | @property (nonatomic, strong) UICollectionViewFlowLayout *slideViewLayout; 102 | 103 | @property (nonatomic, assign) CGPoint lastContentOffset; 104 | @property (nonatomic, assign) NSInteger beforeTransitionIndex; 105 | 106 | @property (nonatomic, assign) BOOL hasShown; 107 | @property (nonatomic, assign) BOOL hasAddedObservers; 108 | 109 | @property (nonatomic, assign, readwrite) NSInteger selectedIndex; 110 | 111 | - (void)reset; 112 | 113 | @end 114 | 115 | @implementation JYSlideSegmentController 116 | @synthesize separatorColor = _separatorColor; 117 | @synthesize selectedIndex = _selectedIndex; 118 | 119 | - (instancetype)initWithViewControllers:(NSArray *)viewControllers 120 | { 121 | return [self initWithViewControllers:viewControllers startIndex:0]; 122 | } 123 | 124 | - (instancetype)initWithViewControllers:(NSArray *)viewControllers 125 | startIndex:(NSInteger)startIndex 126 | { 127 | NSParameterAssert(startIndex < viewControllers.count); 128 | self = [super initWithNibName:nil bundle:nil]; 129 | if (self) { 130 | _viewControllers = [viewControllers copy]; 131 | _startIndex = startIndex; 132 | _indicatorWidth = 20; 133 | _lastContentOffset = CGPointZero; 134 | _beforeTransitionIndex = NSNotFound; 135 | _indicatorInsets = UIEdgeInsetsZero; 136 | _segmentBarPosition = JYSlideSegmentBarPositionTop; 137 | } 138 | return self; 139 | } 140 | 141 | - (void)viewDidLoad 142 | { 143 | [super viewDidLoad]; 144 | [self configSubviews]; 145 | [self configObservers]; 146 | } 147 | 148 | - (void)viewDidLayoutSubviews 149 | { 150 | [super viewDidLayoutSubviews]; 151 | // scroll to start index, when slide view did layout (bounds.size.width > 0), 152 | // otherwise slide view scroll methods can't be called. 153 | if (!self.hasShown && self.slideView.bounds.size.width > 0) { 154 | [self scrollToViewWithIndex:self.startIndex animated:NO]; 155 | self.hasShown = YES; 156 | } 157 | } 158 | 159 | - (void)dealloc 160 | { 161 | if (_hasAddedObservers) { 162 | [self removeObserver:self forKeyPath:@"slideView.contentOffset"]; 163 | [self removeObserver:self forKeyPath:@"slideView.contentSize"]; 164 | [self removeObserver:self forKeyPath:@"segmentBar.contentOffset"]; 165 | } 166 | } 167 | 168 | #pragma mark - Setup 169 | - (void)configSubviews 170 | { 171 | [self.view addSubview:self.segmentBar]; 172 | [self.view addSubview:self.slideView]; 173 | [self.view addSubview:self.separator]; 174 | [self.view addSubview:self.indicator]; 175 | 176 | UIView *topView; 177 | UIView *bottomView; 178 | if (self.segmentBarPosition == JYSlideSegmentBarPositionBottom) { 179 | topView = self.slideView; 180 | bottomView = self.segmentBar; 181 | } else { 182 | topView = self.segmentBar; 183 | bottomView = self.slideView; 184 | } 185 | 186 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:topView 187 | attribute:NSLayoutAttributeLeft 188 | relatedBy:NSLayoutRelationEqual 189 | toItem:self.view 190 | attribute:NSLayoutAttributeLeft 191 | multiplier:1 192 | constant:0]]; 193 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:topView 194 | attribute:NSLayoutAttributeRight 195 | relatedBy:NSLayoutRelationEqual 196 | toItem:self.view 197 | attribute:NSLayoutAttributeRight 198 | multiplier:1 199 | constant:0]]; 200 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:topView 201 | attribute:NSLayoutAttributeTop 202 | relatedBy:NSLayoutRelationEqual 203 | toItem:self.view 204 | attribute:NSLayoutAttributeTop 205 | multiplier:1 206 | constant:0]]; 207 | [self.segmentBar addConstraint:[NSLayoutConstraint constraintWithItem:self.segmentBar 208 | attribute:NSLayoutAttributeHeight 209 | relatedBy:NSLayoutRelationEqual 210 | toItem:nil 211 | attribute:NSLayoutAttributeNotAnAttribute 212 | multiplier:1 213 | constant:self.segmentHeight]]; 214 | 215 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:bottomView 216 | attribute:NSLayoutAttributeLeft 217 | relatedBy:NSLayoutRelationEqual 218 | toItem:self.view 219 | attribute:NSLayoutAttributeLeft 220 | multiplier:1 221 | constant:0]]; 222 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:bottomView 223 | attribute:NSLayoutAttributeRight 224 | relatedBy:NSLayoutRelationEqual 225 | toItem:self.view 226 | attribute:NSLayoutAttributeRight 227 | multiplier:1 228 | constant:0]]; 229 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:bottomView 230 | attribute:NSLayoutAttributeBottom 231 | relatedBy:NSLayoutRelationEqual 232 | toItem:self.view 233 | attribute:NSLayoutAttributeBottom 234 | multiplier:1 235 | constant:0]]; 236 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:topView 237 | attribute:NSLayoutAttributeBottom 238 | relatedBy:NSLayoutRelationEqual 239 | toItem:bottomView 240 | attribute:NSLayoutAttributeTop 241 | multiplier:1 242 | constant:0]]; 243 | 244 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.separator 245 | attribute:NSLayoutAttributeLeft 246 | relatedBy:NSLayoutRelationEqual 247 | toItem:topView 248 | attribute:NSLayoutAttributeLeft 249 | multiplier:1 250 | constant:0]]; 251 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.separator 252 | attribute:NSLayoutAttributeRight 253 | relatedBy:NSLayoutRelationEqual 254 | toItem:topView 255 | attribute:NSLayoutAttributeRight 256 | multiplier:1 257 | constant:0]]; 258 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.separator 259 | attribute:NSLayoutAttributeBottom 260 | relatedBy:NSLayoutRelationEqual 261 | toItem:topView 262 | attribute:NSLayoutAttributeBottom 263 | multiplier:1 264 | constant:0]]; 265 | [self.separator addConstraint:[NSLayoutConstraint constraintWithItem:self.separator 266 | attribute:NSLayoutAttributeHeight 267 | relatedBy:NSLayoutRelationEqual 268 | toItem:nil 269 | attribute:NSLayoutAttributeNotAnAttribute 270 | multiplier:1 271 | constant:self.separatorHeight]]; 272 | 273 | [self.segmentBar registerClass:[JYSegmentBarItem class] forCellWithReuseIdentifier:JYSegmentBarItemID]; 274 | [self.slideView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:JYSlideViewItemID]; 275 | [self.separator setBackgroundColor:self.separatorColor]; 276 | 277 | self.automaticallyAdjustsScrollViewInsets = NO; 278 | self.edgesForExtendedLayout = UIRectEdgeNone; 279 | } 280 | 281 | - (void)configObservers 282 | { 283 | [self addObserver:self forKeyPath:@"slideView.contentOffset" options:NSKeyValueObservingOptionNew context:nil]; 284 | [self addObserver:self forKeyPath:@"slideView.contentSize" options:NSKeyValueObservingOptionNew context:nil]; 285 | [self addObserver:self forKeyPath:@"segmentBar.contentOffset" options:NSKeyValueObservingOptionNew context:nil]; 286 | self.hasAddedObservers = YES; 287 | } 288 | 289 | #pragma mark - KVO 290 | 291 | - (void)observeValueForKeyPath:(NSString *)keyPath 292 | ofObject:(id)object 293 | change:(NSDictionary *)change 294 | context :(void *)context 295 | { 296 | [self adjustIndicatorFrame]; 297 | if ([keyPath isEqualToString:@"slideView.contentOffset"]) { 298 | // recoard last content offset to adjust direction 299 | self.lastContentOffset = self.slideView.contentOffset; 300 | } 301 | } 302 | 303 | - (void)adjustIndicatorFrame 304 | { 305 | CGPoint contentOffset = self.slideView.contentOffset; 306 | CGFloat direction = contentOffset.x - self.lastContentOffset.x > 0 ? 1.0 : -1.0; 307 | CGFloat slideViewWidth = self.slideView.frame.size.width; 308 | CGRect indicatorFrame = self.indicator.frame; 309 | 310 | NSArray * indexPaths = [self.slideView indexPathsForVisibleItems]; 311 | if (indexPaths.count <= 1) { 312 | NSInteger index = 0; 313 | if (indexPaths.count == 0) { 314 | if (self.viewControllers.count <= self.selectedIndex) { 315 | return; 316 | } 317 | index = self.selectedIndex; 318 | } else { 319 | index = [self.slideView indexPathForItemAtPoint:[self.view convertPoint:self.slideView.center toView:self.slideView]].item; 320 | } 321 | UICollectionViewLayoutAttributes *segmentlayoutAttributes = [self.segmentBar layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0]]; 322 | CGFloat x = [self.segmentBar convertPoint:segmentlayoutAttributes.center toView:self.view].x; 323 | CGFloat y = CGRectGetMaxY(self.segmentBar.frame) - self.indicatorHeight - self.indicatorInsets.bottom; 324 | CGFloat w = self.indicatorWidth; 325 | CGFloat h = self.indicatorHeight; 326 | indicatorFrame.size.width = w; 327 | indicatorFrame.size.height = h; 328 | self.indicator.frame = indicatorFrame; 329 | self.indicator.center = CGPointMake(x, y); 330 | } else { 331 | // offset didn't change 332 | if (CGPointEqualToPoint(contentOffset, self.lastContentOffset) && !CGPointEqualToPoint(self.lastContentOffset, CGPointZero)) { 333 | return; 334 | } 335 | NSInteger larger = MAX(indexPaths.lastObject.item, indexPaths.firstObject.item); 336 | NSInteger smaller = MIN(indexPaths.lastObject.item, indexPaths.firstObject.item); 337 | NSInteger fromIndex = direction > 0 ? smaller : larger; 338 | NSInteger toIndex = direction > 0 ? larger : smaller; 339 | 340 | CGFloat progress = slideViewWidth == 0 ? 0 : (contentOffset.x - fromIndex * slideViewWidth) / slideViewWidth; 341 | 342 | UICollectionViewLayoutAttributes *fromSegmentlayoutAttributes = [self.segmentBar layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForRow:fromIndex inSection:0]]; 343 | UICollectionViewLayoutAttributes *toSegmentlayoutAttributes = [self.segmentBar layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForRow:toIndex inSection:0]]; 344 | 345 | CGPoint fromSegmentCenter = [self.segmentBar convertPoint:fromSegmentlayoutAttributes.center toView:self.view]; 346 | CGPoint toSegmentCenter = [self.segmentBar convertPoint:toSegmentlayoutAttributes.center toView:self.view]; 347 | CGFloat distance = ABS(toSegmentCenter.x - fromSegmentCenter.x); 348 | 349 | CGFloat x = fromSegmentCenter.x + progress * distance; 350 | CGFloat y = CGRectGetMaxY(self.segmentBar.frame) - self.indicatorHeight - self.indicatorInsets.bottom; 351 | CGFloat w = self.indicatorWidth; 352 | CGFloat h = self.indicatorHeight; 353 | 354 | CGPoint indicatorCenter = CGPointMake(x, y); 355 | 356 | if (0 < progress && progress < 0.5) { 357 | // stretch to right 358 | w += progress * distance; 359 | } else if(progress >= 0.5 && progress < 1) { 360 | // shrink to right 361 | w += (1 - progress) * distance; 362 | } else if (-0.5 < progress && progress < 0) { 363 | // stretch to left 364 | w -= progress * distance; 365 | } else if (-1.0 < progress && progress <= -0.5) { 366 | // shrink to left 367 | w += (1 + progress) * distance; 368 | } 369 | 370 | indicatorFrame.size.width = w; 371 | indicatorFrame.size.height = h; 372 | self.indicator.frame = indicatorFrame; 373 | self.indicator.center = indicatorCenter; 374 | if ([_delegate respondsToSelector:@selector(slideViewMovingProgress:fromIndex:toIndex:)]) { 375 | [_delegate slideViewMovingProgress:progress fromIndex:fromIndex toIndex:toIndex]; 376 | } 377 | } 378 | } 379 | 380 | #pragma mark - Property 381 | - (JYSlideView *)slideView 382 | { 383 | if (!_slideView) { 384 | _slideView = [[JYSlideView alloc] initWithFrame:CGRectZero collectionViewLayout:self.slideViewLayout]; 385 | _slideView.scrollEnabled = _viewControllers.count > 1 ? YES : NO; 386 | _slideView.scrollsToTop = NO; 387 | [_slideView setShowsHorizontalScrollIndicator:NO]; 388 | [_slideView setShowsVerticalScrollIndicator:NO]; 389 | [_slideView setPagingEnabled:YES]; 390 | [_slideView setBounces:NO]; 391 | [_slideView setDelegate:self]; 392 | [_slideView setDataSource:self]; 393 | [_slideView setTranslatesAutoresizingMaskIntoConstraints:NO]; 394 | _slideView.backgroundColor = [UIColor whiteColor]; 395 | } 396 | return _slideView; 397 | } 398 | 399 | - (UICollectionView *)segmentBar 400 | { 401 | if (!_segmentBar) { 402 | _segmentBar = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.segmentBarLayout]; 403 | _segmentBar.backgroundColor = [UIColor whiteColor]; 404 | _segmentBar.delegate = self; 405 | _segmentBar.dataSource = self; 406 | _segmentBar.showsHorizontalScrollIndicator = NO; 407 | _segmentBar.showsVerticalScrollIndicator = NO; 408 | _segmentBar.scrollsToTop = NO; 409 | _segmentBar.translatesAutoresizingMaskIntoConstraints = NO; 410 | } 411 | return _segmentBar; 412 | } 413 | 414 | - (UIView *)indicator 415 | { 416 | if (!_indicator) { 417 | _indicator = [[UIView alloc] initWithFrame:CGRectZero]; 418 | _indicator.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; 419 | _indicator.backgroundColor = [UIColor yellowColor]; 420 | } 421 | return _indicator; 422 | } 423 | 424 | - (UIView *)separator 425 | { 426 | if (!_separator) { 427 | _separator = [[UIView alloc] initWithFrame:CGRectZero]; 428 | [_separator setTranslatesAutoresizingMaskIntoConstraints:NO]; 429 | } 430 | return _separator; 431 | } 432 | 433 | - (CGFloat)indicatorHeight 434 | { 435 | if (!_indicatorHeight) { 436 | _indicatorHeight = 3; 437 | } 438 | return _indicatorHeight; 439 | } 440 | 441 | - (CGFloat)segmentHeight 442 | { 443 | if (!_segmentHeight) { 444 | _segmentHeight = 40; 445 | } 446 | return _segmentHeight; 447 | } 448 | 449 | - (CGFloat)separatorHeight 450 | { 451 | if (!_separatorHeight) { 452 | _separatorHeight = 1.0 / [UIScreen mainScreen].scale; 453 | } 454 | return _separatorHeight; 455 | } 456 | 457 | - (CGFloat)segmentWidth 458 | { 459 | if (!_segmentWidth) { 460 | _segmentWidth = self.view.frame.size.width / self.viewControllers.count; 461 | } 462 | return _segmentWidth; 463 | } 464 | 465 | - (UIColor *)separatorColor 466 | { 467 | if (!_separatorColor) { 468 | _separatorColor = [UIColor lightGrayColor]; 469 | } 470 | return _separatorColor; 471 | } 472 | 473 | - (void)setSeparatorColor:(UIColor *)separatorColor 474 | { 475 | _separatorColor = separatorColor; 476 | _separator.backgroundColor = _separatorColor; 477 | } 478 | 479 | - (UICollectionViewFlowLayout *)segmentBarLayout 480 | { 481 | if (!_segmentBarLayout) { 482 | _segmentBarLayout = [[UICollectionViewFlowLayout alloc] init]; 483 | _segmentBarLayout.sectionInset = _segmentInsets; 484 | _segmentBarLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 485 | _segmentBarLayout.minimumLineSpacing = 0; 486 | _segmentBarLayout.minimumInteritemSpacing = 0; 487 | } 488 | return _segmentBarLayout; 489 | } 490 | 491 | - (UICollectionViewFlowLayout *)slideViewLayout 492 | { 493 | if (!_slideViewLayout) { 494 | _slideViewLayout = [[UICollectionViewFlowLayout alloc] init]; 495 | _slideViewLayout.sectionInset = UIEdgeInsetsZero; 496 | _slideViewLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 497 | _slideViewLayout.minimumLineSpacing = 0; 498 | _slideViewLayout.minimumInteritemSpacing = 0; 499 | } 500 | return _slideViewLayout; 501 | } 502 | 503 | - (void)setSelectedIndex:(NSInteger)selectedIndex 504 | { 505 | // just for kvo, do not call this method directly, call scrollToViewWithIndex:animated: for instead 506 | _selectedIndex = selectedIndex; 507 | } 508 | 509 | - (NSInteger)selectedIndex 510 | { 511 | return floor(self.slideView.contentOffset.x / MAX(self.slideView.bounds.size.width, self.view.bounds.size.width)); 512 | } 513 | 514 | - (void)setViewControllers:(NSArray *)viewControllers 515 | { 516 | // Need remove previous viewControllers 517 | for (UIViewController *vc in _viewControllers) { 518 | [vc willMoveToParentViewController:nil]; 519 | if (vc.parentViewController) { 520 | [vc.view removeFromSuperview]; 521 | } 522 | [vc removeFromParentViewController]; 523 | [vc didMoveToParentViewController:nil]; 524 | } 525 | _viewControllers = [viewControllers copy]; 526 | [self reset]; 527 | } 528 | 529 | - (UIViewController *)selectedViewController 530 | { 531 | return [self viewControllerAtIndex:self.selectedIndex]; 532 | } 533 | 534 | - (UIViewController *)viewControllerAtIndex:(NSInteger)index 535 | { 536 | NSParameterAssert(index >= 0 && index < self.viewControllers.count); 537 | 538 | if (index < self.viewControllers.count && index >= 0) { 539 | return self.viewControllers[index]; 540 | } 541 | return nil; 542 | } 543 | 544 | 545 | #pragma mark - UICollectionViewDataSource 546 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView 547 | { 548 | if ([_dataSource respondsToSelector:@selector(numberOfSectionsInslideSegment:)]) { 549 | return [_dataSource numberOfSectionsInslideSegment:collectionView]; 550 | } 551 | return 1; 552 | } 553 | 554 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section 555 | { 556 | if ([_dataSource respondsToSelector:@selector(slideSegment:numberOfItemsInSection:)]) { 557 | return [_dataSource slideSegment:collectionView numberOfItemsInSection:section]; 558 | } 559 | return self.viewControllers.count; 560 | } 561 | 562 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView 563 | cellForItemAtIndexPath:(NSIndexPath *)indexPath 564 | { 565 | if (collectionView == self.segmentBar) { 566 | if ([_dataSource respondsToSelector:@selector(slideSegment:cellForItemAtIndexPath:)]) { 567 | return [_dataSource slideSegment:collectionView cellForItemAtIndexPath:indexPath]; 568 | } 569 | 570 | JYSegmentBarItem *segmentBarItem = [collectionView dequeueReusableCellWithReuseIdentifier:JYSegmentBarItemID 571 | forIndexPath:indexPath]; 572 | UIViewController *vc = [self viewControllerAtIndex:indexPath.row]; 573 | segmentBarItem.titleLabel.text = vc.title; 574 | return segmentBarItem; 575 | } 576 | // slide 577 | UICollectionViewCell *slideViewItemCell = [collectionView dequeueReusableCellWithReuseIdentifier:JYSlideViewItemID 578 | forIndexPath:indexPath]; 579 | return slideViewItemCell; 580 | } 581 | 582 | - (CGSize)collectionView:(UICollectionView *)collectionView 583 | layout:(UICollectionViewLayout *)collectionViewLayout 584 | sizeForItemAtIndexPath:(NSIndexPath *)indexPath 585 | { 586 | if (collectionView == self.segmentBar) { 587 | if ([_dataSource respondsToSelector:@selector(slideSegment:layout:sizeForItemAtIndexPath:)]) { 588 | return [_dataSource slideSegment:collectionView layout:collectionViewLayout sizeForItemAtIndexPath:indexPath]; 589 | } 590 | return CGSizeMake(self.segmentWidth, self.segmentHeight); 591 | } 592 | // sub vc frame 593 | return self.slideView.bounds.size; 594 | } 595 | 596 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath 597 | { 598 | if (collectionView == self.slideView) { 599 | return; 600 | } 601 | UIViewController *toSelectController = [self viewControllerAtIndex:indexPath.row]; 602 | if (toSelectController == nil) { 603 | return; 604 | } 605 | 606 | if ([_delegate respondsToSelector:@selector(slideSegment:didSelectItemAtIndexPath:)]) { 607 | [_delegate slideSegment:collectionView didSelectItemAtIndexPath:indexPath]; 608 | } 609 | if ([_delegate respondsToSelector:@selector(didSelectViewController:)]) { 610 | 611 | [_delegate didSelectViewController:toSelectController]; 612 | } 613 | 614 | [self scrollToViewWithIndex:indexPath.row animated:YES]; 615 | } 616 | 617 | - (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath 618 | { 619 | if (collectionView == self.slideView) { 620 | return NO; 621 | } 622 | UIViewController *vc = [self viewControllerAtIndex:indexPath.row]; 623 | if (vc == nil) { 624 | return NO; 625 | } 626 | 627 | BOOL flag = YES; 628 | 629 | if ([_delegate respondsToSelector:@selector(shouldSelectViewController:)]) { 630 | flag = [_delegate shouldSelectViewController:vc]; 631 | } 632 | return flag; 633 | } 634 | 635 | - (void)collectionView:(UICollectionView *)collectionView 636 | willDisplayCell:(UICollectionViewCell *)cell 637 | forItemAtIndexPath:(NSIndexPath *)indexPath 638 | { 639 | if (collectionView == self.segmentBar) { 640 | return; 641 | } 642 | 643 | UIViewController *toSelectController = [self viewControllerAtIndex:indexPath.row]; 644 | if (toSelectController == nil) { 645 | return; 646 | } 647 | 648 | if (!toSelectController.parentViewController) { 649 | // fix viewWillAppear not called on iOS 10 650 | // https://stackoverflow.com/questions/18235284/uiviewcontroller-viewwillappear-not-called-when-adding-as-subview 651 | if (@available(iOS 11, *)) { 652 | [self addChildViewController:toSelectController]; 653 | toSelectController.view.frame = cell.contentView.bounds; 654 | [cell.contentView addSubview:toSelectController.view]; 655 | } else { 656 | toSelectController.view.frame = cell.contentView.bounds; 657 | [cell.contentView addSubview:toSelectController.view]; 658 | [self addChildViewController:toSelectController]; 659 | } 660 | [toSelectController.view setTranslatesAutoresizingMaskIntoConstraints:NO]; 661 | [cell.contentView addConstraint:[NSLayoutConstraint constraintWithItem:toSelectController.view 662 | attribute:NSLayoutAttributeLeft 663 | relatedBy:NSLayoutRelationEqual 664 | toItem:cell.contentView 665 | attribute:NSLayoutAttributeLeft 666 | multiplier:1 667 | constant:0]]; 668 | [cell.contentView addConstraint:[NSLayoutConstraint constraintWithItem:toSelectController.view 669 | attribute:NSLayoutAttributeRight 670 | relatedBy:NSLayoutRelationEqual 671 | toItem:cell.contentView 672 | attribute:NSLayoutAttributeRight 673 | multiplier:1 674 | constant:0]]; 675 | [cell.contentView addConstraint:[NSLayoutConstraint constraintWithItem:toSelectController.view 676 | attribute:NSLayoutAttributeTop 677 | relatedBy:NSLayoutRelationEqual 678 | toItem:cell.contentView 679 | attribute:NSLayoutAttributeTop 680 | multiplier:1 681 | constant:0]]; 682 | [cell.contentView addConstraint:[NSLayoutConstraint constraintWithItem:toSelectController.view 683 | attribute:NSLayoutAttributeBottom 684 | relatedBy:NSLayoutRelationEqual 685 | toItem:cell.contentView 686 | attribute:NSLayoutAttributeBottom 687 | multiplier:1 688 | constant:0]]; 689 | [toSelectController didMoveToParentViewController:self]; 690 | if ([_delegate respondsToSelector:@selector(didSelectViewController:)]) { 691 | [_delegate didSelectViewController:toSelectController]; 692 | } 693 | } 694 | } 695 | 696 | - (void)collectionView:(UICollectionView *)collectionView 697 | didEndDisplayingCell:(UICollectionViewCell *)cell 698 | forItemAtIndexPath:(NSIndexPath *)indexPath 699 | { 700 | if (collectionView == self.segmentBar) { 701 | return; 702 | } 703 | /* 在更换数据源viewControllers时候,若新数据源数量 < 老数据源数量,会触发方法viewControllerAtIndex的断言。 */ 704 | if (self.viewControllers.count <= indexPath.row) { 705 | return; 706 | } 707 | 708 | UIViewController *previousViewController = [self viewControllerAtIndex:indexPath.row]; 709 | if (previousViewController == nil) { 710 | return; 711 | } 712 | 713 | if (previousViewController && previousViewController.parentViewController) { 714 | [previousViewController willMoveToParentViewController:nil]; 715 | [previousViewController.view removeFromSuperview]; 716 | [previousViewController removeFromParentViewController]; 717 | } 718 | } 719 | 720 | - (CGPoint)collectionView:(UICollectionView *)collectionView 721 | targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset 722 | { 723 | if (collectionView == self.slideView) { 724 | return self.beforeTransitionIndex == NSNotFound ? proposedContentOffset : CGPointMake(self.beforeTransitionIndex * self.slideView.bounds.size.width, 0); 725 | } 726 | return proposedContentOffset; 727 | } 728 | 729 | #pragma mark - UIScrollViewDelegate 730 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView 731 | { 732 | if (scrollView == self.slideView) { 733 | if ([_delegate respondsToSelector:@selector(slideViewDidScroll:)]) { 734 | [_delegate slideViewDidScroll:scrollView]; 735 | } 736 | } else if (scrollView == self.segmentBar) { 737 | if ([_delegate respondsToSelector:@selector(slideSegmentDidScroll:)]) { 738 | [_delegate slideSegmentDidScroll:scrollView]; 739 | } 740 | } 741 | } 742 | 743 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 744 | { 745 | if (scrollView == self.slideView) { 746 | [self segmentBarScrollToIndex:self.selectedIndex animated:YES]; 747 | [self setSelectedIndex:self.selectedIndex]; 748 | if ([_delegate respondsToSelector:@selector(didFullyShowViewController:)]) { 749 | [_delegate didFullyShowViewController:self.selectedViewController]; 750 | } 751 | } else if (scrollView == self.segmentBar) { 752 | if ([_delegate respondsToSelector:@selector(slideSegmentDidEndDecelerating:)]) { 753 | [_delegate slideSegmentDidEndDecelerating:scrollView]; 754 | } 755 | } 756 | } 757 | 758 | - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate 759 | { 760 | if (scrollView == self.segmentBar) { 761 | if ([_delegate respondsToSelector:@selector(slideSegmentDidEndDragging:willDecelerate:)]) { 762 | [_delegate slideSegmentDidEndDragging:scrollView willDecelerate:decelerate]; 763 | } 764 | } 765 | } 766 | 767 | - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView 768 | { 769 | if (scrollView == self.slideView) { 770 | [self segmentBarScrollToIndex:self.selectedIndex animated:YES]; 771 | [self setSelectedIndex:self.selectedIndex]; 772 | if ([_delegate respondsToSelector:@selector(didFullyShowViewController:)]) { 773 | [_delegate didFullyShowViewController:self.selectedViewController]; 774 | } 775 | } else if (scrollView == self.segmentBar) { 776 | if ([_delegate respondsToSelector:@selector(slideDidEndScrollingAnimation:)]) { 777 | [_delegate slideDidEndScrollingAnimation:scrollView]; 778 | } 779 | } 780 | } 781 | 782 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView 783 | { 784 | if (scrollView == self.segmentBar) { 785 | if ([_delegate respondsToSelector:@selector(slideWillBeginDragging:)]) { 786 | [_delegate slideWillBeginDragging:scrollView]; 787 | } 788 | } 789 | } 790 | 791 | #pragma mark - Action 792 | - (void)scrollToViewWithIndex:(NSInteger)index animated:(BOOL)animated 793 | { 794 | NSParameterAssert(index >= 0 && index < self.viewControllers.count); 795 | if (index < 0 || index >= self.viewControllers.count) { 796 | return; 797 | } 798 | 799 | if (!self.hasShown) { 800 | //if set selectedIndex before shown, means current index is start 801 | self.startIndex = index; 802 | } 803 | 804 | [self.slideView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0] 805 | atScrollPosition:UICollectionViewScrollPositionLeft 806 | animated:NO]; 807 | [self segmentBarScrollToIndex:index animated:animated]; 808 | 809 | [self setSelectedIndex:index]; 810 | 811 | if ([_delegate respondsToSelector:@selector(didFullyShowViewController:)]) { 812 | [_delegate didFullyShowViewController:self.selectedViewController]; 813 | } 814 | 815 | } 816 | 817 | - (void)reset 818 | { 819 | self.lastContentOffset = CGPointZero; 820 | self.beforeTransitionIndex = NSNotFound; 821 | [self.segmentBar reloadData]; 822 | [self.slideView reloadData]; 823 | // reset to start index, if want to change index to 0, you should set startIndex before set viewControllers 824 | if (self.hasShown) { 825 | NSInteger startIndex = self.startIndex >= self.viewControllers.count ? 0 : self.startIndex; 826 | [self scrollToViewWithIndex:startIndex animated:NO]; 827 | } 828 | } 829 | 830 | - (void)segmentBarScrollToIndex:(NSInteger)index animated:(BOOL)animated 831 | { 832 | [self.segmentBar 833 | selectItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0] 834 | animated:animated 835 | scrollPosition:UICollectionViewScrollPositionCenteredHorizontally]; 836 | } 837 | 838 | #pragma mark - UIContentContainer 839 | - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator 840 | { 841 | [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; 842 | // record index to support targetContentOffsetForProposedContentOffset 843 | // https://stackoverflow.com/questions/41639968/uicollectionview-contentoffset-after-device-rotation 844 | self.beforeTransitionIndex = self.selectedIndex; 845 | // unset datasource temporarily, to prevent showing unexpected view controllers when animating 846 | self.slideView.dataSource = nil; 847 | [self.slideView.collectionViewLayout invalidateLayout]; 848 | [self.segmentBar.collectionViewLayout invalidateLayout]; 849 | [coordinator animateAlongsideTransition:nil completion:^(id _Nonnull context) { 850 | self.slideView.dataSource = self; 851 | if (self.view.window) { 852 | [self.slideView reloadData]; 853 | [self.segmentBar reloadData]; 854 | if (@available(iOS 11, *)) { 855 | } else { 856 | // hack for iOS8+, collectionView:targetContentOffsetForProposedContentOffset: can't be called after routation 857 | [self scrollToViewWithIndex:self.beforeTransitionIndex animated:NO]; 858 | } 859 | } 860 | }]; 861 | } 862 | 863 | @end 864 | -------------------------------------------------------------------------------- /JYSlideSegmentController/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /JYSlideSegmentController/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // JYSlideSegmentController 4 | // 5 | // Created by Alvin on 14-3-31. 6 | // Copyright (c) 2014年 Alvin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 JYSlideSegmentController 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JYSlideSegmentController 2 | ======================== 3 | 4 | ![pods](https://img.shields.io/cocoapods/v/JYSlideSegmentController.svg) 5 | ![platforms](https://img.shields.io/badge/platforms-iOS-orange.svg) 6 | ![language](https://img.shields.io/badge/languages-ObjC-orange.svg) 7 | ![total-downloads](https://img.shields.io/cocoapods/dt/JYSlideSegmentController.svg?label=Total%20Downloads&colorB=28B9FE) 8 | ![app-using](https://img.shields.io/cocoapods/at/JYSlideSegmentController.svg?label=app-using&colorB=28B9FE) 9 | ![license](https://img.shields.io/cocoapods/l/JYSlideSegmentController.svg) 10 | 11 | 12 | JYSlideSegmentController is a view controllers container, just like the UITabBarController, with smooth gesture. 13 | 14 | ### Demo 15 | 16 | 17 | 18 | ### Installation with CocoaPods 19 | 20 | Podfile 21 | 22 | ```ruby 23 | platform :ios, '8.0' 24 | pod "JYSlideSegmentController" 25 | ``` 26 | 27 | ## License 28 | 29 | JYSlideSegmentController is available under the MIT license. See the LICENSE file for more info. 30 | 31 | ## ChangeLog 32 | 33 | 2.2.1 34 | 35 | - Fix selectedIndex overflow 36 | 37 | 2.1.1 38 | 39 | - Add scroll delegate for segmentBar 40 | 41 | 2.1.0 42 | 43 | - Add changing JYSlideSegmentBarPosition support 44 | 45 | 2.0.3 46 | 47 | - Fix selectedIndex KVO bug 48 | 49 | 2.0.2 50 | 51 | - fix indicator layout 52 | 53 | 2.0.1 54 | 55 | - fix bug of start index 56 | 57 | 2.0.0 58 | 59 | - refract slide view to collection view; 60 | - update logic of indicator and animation; 61 | - add slideViewMovingProgress:fromIndex:toIndex: delegate method 62 | 63 | 1.3.7 64 | 65 | - Fix crash & logic of handling orientation notification 66 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HisaMasa/JYSlideSegmentController/708fe662d4415feacc39145826a67f8101866c1b/demo.gif --------------------------------------------------------------------------------