├── FLOPageViewController.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ ├── florianschliep.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ │ └── xiao99xiao.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ ├── florianschliep.xcuserdatad │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ │ ├── Framework.xcscheme │ │ ├── Sample App.xcscheme │ │ └── xcschememanagement.plist │ └── xiao99xiao.xcuserdatad │ └── xcschemes │ ├── FLOPageViewController.xcscheme │ └── xcschememanagement.plist ├── FLOPageViewController ├── ArrowControl.swift ├── FLOPageViewController.h ├── Info.plist ├── PageControl.swift └── PageViewController.swift ├── LICENSE ├── README.md ├── Sample App ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ └── Main.storyboard ├── ColoredView.swift ├── Info.plist └── ViewController.swift ├── UI Tests ├── FLOPageViewControllerUITests.swift └── Info.plist └── screenshot.png /FLOPageViewController.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 131BADC61F9A0E7000A3EF87 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 131BADC41F9A0E7000A3EF87 /* Main.storyboard */; }; 11 | 13773EBC1C4E936A003FF521 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13773EBB1C4E936A003FF521 /* AppDelegate.swift */; }; 12 | 13773EC01C4E936A003FF521 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13773EBF1C4E936A003FF521 /* Assets.xcassets */; }; 13 | 13773ECE1C4E936A003FF521 /* FLOPageViewControllerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13773ECD1C4E936A003FF521 /* FLOPageViewControllerUITests.swift */; }; 14 | 13773EDE1C4E9E59003FF521 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13773EDD1C4E9E59003FF521 /* ViewController.swift */; }; 15 | 138929351F70075300DE6DFB /* FLOPageViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 138929331F70075300DE6DFB /* FLOPageViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 16 | 138929381F70075300DE6DFB /* FLOPageViewController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 138929311F70075200DE6DFB /* FLOPageViewController.framework */; }; 17 | 138929391F70075300DE6DFB /* FLOPageViewController.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 138929311F70075200DE6DFB /* FLOPageViewController.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 18 | 1389293E1F70078000DE6DFB /* PageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13773ED81C4E9522003FF521 /* PageViewController.swift */; }; 19 | 1389293F1F70078000DE6DFB /* PageControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13D69B101C50F8FD00CE3E6E /* PageControl.swift */; }; 20 | 138929401F70078000DE6DFB /* ArrowControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13D69B121C51100A00CE3E6E /* ArrowControl.swift */; }; 21 | 1396A9F61C4FD1E200D89DF1 /* ColoredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1396A9F51C4FD1E200D89DF1 /* ColoredView.swift */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXContainerItemProxy section */ 25 | 13773ECA1C4E936A003FF521 /* PBXContainerItemProxy */ = { 26 | isa = PBXContainerItemProxy; 27 | containerPortal = 13773EB01C4E936A003FF521 /* Project object */; 28 | proxyType = 1; 29 | remoteGlobalIDString = 13773EB71C4E936A003FF521; 30 | remoteInfo = FLOPageViewController; 31 | }; 32 | 138929361F70075300DE6DFB /* PBXContainerItemProxy */ = { 33 | isa = PBXContainerItemProxy; 34 | containerPortal = 13773EB01C4E936A003FF521 /* Project object */; 35 | proxyType = 1; 36 | remoteGlobalIDString = 138929301F70075200DE6DFB; 37 | remoteInfo = FLOPageViewController; 38 | }; 39 | /* End PBXContainerItemProxy section */ 40 | 41 | /* Begin PBXCopyFilesBuildPhase section */ 42 | 1389293D1F70075300DE6DFB /* Embed Frameworks */ = { 43 | isa = PBXCopyFilesBuildPhase; 44 | buildActionMask = 2147483647; 45 | dstPath = ""; 46 | dstSubfolderSpec = 10; 47 | files = ( 48 | 138929391F70075300DE6DFB /* FLOPageViewController.framework in Embed Frameworks */, 49 | ); 50 | name = "Embed Frameworks"; 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | /* End PBXCopyFilesBuildPhase section */ 54 | 55 | /* Begin PBXFileReference section */ 56 | 131BADC51F9A0E7000A3EF87 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 57 | 13773EB81C4E936A003FF521 /* FLOPageViewControllerSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FLOPageViewControllerSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | 13773EBB1C4E936A003FF521 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = "../Sample App/AppDelegate.swift"; sourceTree = ""; }; 59 | 13773EBF1C4E936A003FF521 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = "../Sample App/Assets.xcassets"; sourceTree = ""; }; 60 | 13773EC91C4E936A003FF521 /* FLOPageViewControllerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FLOPageViewControllerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | 13773ECD1C4E936A003FF521 /* FLOPageViewControllerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FLOPageViewControllerUITests.swift; sourceTree = ""; }; 62 | 13773ECF1C4E936A003FF521 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 63 | 13773ED81C4E9522003FF521 /* PageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageViewController.swift; sourceTree = ""; }; 64 | 13773EDD1C4E9E59003FF521 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ViewController.swift; path = "../Sample App/ViewController.swift"; sourceTree = ""; }; 65 | 138929311F70075200DE6DFB /* FLOPageViewController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FLOPageViewController.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 66 | 138929331F70075300DE6DFB /* FLOPageViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLOPageViewController.h; sourceTree = ""; }; 67 | 138929341F70075300DE6DFB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 68 | 1396A9F51C4FD1E200D89DF1 /* ColoredView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ColoredView.swift; path = "../Sample App/ColoredView.swift"; sourceTree = ""; }; 69 | 13D69B101C50F8FD00CE3E6E /* PageControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageControl.swift; sourceTree = ""; }; 70 | 13D69B121C51100A00CE3E6E /* ArrowControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrowControl.swift; sourceTree = ""; }; 71 | 13FB49731F9A086400C1AAFD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = "Sample App/Info.plist"; sourceTree = SOURCE_ROOT; }; 72 | /* End PBXFileReference section */ 73 | 74 | /* Begin PBXFrameworksBuildPhase section */ 75 | 13773EB51C4E936A003FF521 /* Frameworks */ = { 76 | isa = PBXFrameworksBuildPhase; 77 | buildActionMask = 2147483647; 78 | files = ( 79 | 138929381F70075300DE6DFB /* FLOPageViewController.framework in Frameworks */, 80 | ); 81 | runOnlyForDeploymentPostprocessing = 0; 82 | }; 83 | 13773EC61C4E936A003FF521 /* Frameworks */ = { 84 | isa = PBXFrameworksBuildPhase; 85 | buildActionMask = 2147483647; 86 | files = ( 87 | ); 88 | runOnlyForDeploymentPostprocessing = 0; 89 | }; 90 | 1389292D1F70075200DE6DFB /* Frameworks */ = { 91 | isa = PBXFrameworksBuildPhase; 92 | buildActionMask = 2147483647; 93 | files = ( 94 | ); 95 | runOnlyForDeploymentPostprocessing = 0; 96 | }; 97 | /* End PBXFrameworksBuildPhase section */ 98 | 99 | /* Begin PBXGroup section */ 100 | 13773EAF1C4E936A003FF521 = { 101 | isa = PBXGroup; 102 | children = ( 103 | 13773EBA1C4E936A003FF521 /* Sample App */, 104 | 138929321F70075300DE6DFB /* FLOPageViewController */, 105 | 13773ECC1C4E936A003FF521 /* UI Tests */, 106 | 13773EB91C4E936A003FF521 /* Products */, 107 | ); 108 | sourceTree = ""; 109 | }; 110 | 13773EB91C4E936A003FF521 /* Products */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 13773EB81C4E936A003FF521 /* FLOPageViewControllerSample.app */, 114 | 13773EC91C4E936A003FF521 /* FLOPageViewControllerUITests.xctest */, 115 | 138929311F70075200DE6DFB /* FLOPageViewController.framework */, 116 | ); 117 | name = Products; 118 | sourceTree = ""; 119 | }; 120 | 13773EBA1C4E936A003FF521 /* Sample App */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 13773EBB1C4E936A003FF521 /* AppDelegate.swift */, 124 | 13773EDD1C4E9E59003FF521 /* ViewController.swift */, 125 | 1396A9F51C4FD1E200D89DF1 /* ColoredView.swift */, 126 | 13773EBF1C4E936A003FF521 /* Assets.xcassets */, 127 | 131BADC41F9A0E7000A3EF87 /* Main.storyboard */, 128 | 13FB49731F9A086400C1AAFD /* Info.plist */, 129 | ); 130 | path = "Sample App"; 131 | sourceTree = ""; 132 | }; 133 | 13773ECC1C4E936A003FF521 /* UI Tests */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 13773ECD1C4E936A003FF521 /* FLOPageViewControllerUITests.swift */, 137 | 13773ECF1C4E936A003FF521 /* Info.plist */, 138 | ); 139 | path = "UI Tests"; 140 | sourceTree = ""; 141 | }; 142 | 138929321F70075300DE6DFB /* FLOPageViewController */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | 138929331F70075300DE6DFB /* FLOPageViewController.h */, 146 | 13773ED81C4E9522003FF521 /* PageViewController.swift */, 147 | 13D69B101C50F8FD00CE3E6E /* PageControl.swift */, 148 | 13D69B121C51100A00CE3E6E /* ArrowControl.swift */, 149 | 138929341F70075300DE6DFB /* Info.plist */, 150 | ); 151 | path = FLOPageViewController; 152 | sourceTree = ""; 153 | }; 154 | /* End PBXGroup section */ 155 | 156 | /* Begin PBXHeadersBuildPhase section */ 157 | 1389292E1F70075200DE6DFB /* Headers */ = { 158 | isa = PBXHeadersBuildPhase; 159 | buildActionMask = 2147483647; 160 | files = ( 161 | 138929351F70075300DE6DFB /* FLOPageViewController.h in Headers */, 162 | ); 163 | runOnlyForDeploymentPostprocessing = 0; 164 | }; 165 | /* End PBXHeadersBuildPhase section */ 166 | 167 | /* Begin PBXNativeTarget section */ 168 | 13773EB71C4E936A003FF521 /* FLOPageViewControllerSample */ = { 169 | isa = PBXNativeTarget; 170 | buildConfigurationList = 13773ED21C4E936A003FF521 /* Build configuration list for PBXNativeTarget "FLOPageViewControllerSample" */; 171 | buildPhases = ( 172 | 13773EB41C4E936A003FF521 /* Sources */, 173 | 13773EB51C4E936A003FF521 /* Frameworks */, 174 | 13773EB61C4E936A003FF521 /* Resources */, 175 | 1389293D1F70075300DE6DFB /* Embed Frameworks */, 176 | ); 177 | buildRules = ( 178 | ); 179 | dependencies = ( 180 | 138929371F70075300DE6DFB /* PBXTargetDependency */, 181 | ); 182 | name = FLOPageViewControllerSample; 183 | productName = FLOPageViewController; 184 | productReference = 13773EB81C4E936A003FF521 /* FLOPageViewControllerSample.app */; 185 | productType = "com.apple.product-type.application"; 186 | }; 187 | 13773EC81C4E936A003FF521 /* FLOPageViewControllerUITests */ = { 188 | isa = PBXNativeTarget; 189 | buildConfigurationList = 13773ED51C4E936A003FF521 /* Build configuration list for PBXNativeTarget "FLOPageViewControllerUITests" */; 190 | buildPhases = ( 191 | 13773EC51C4E936A003FF521 /* Sources */, 192 | 13773EC61C4E936A003FF521 /* Frameworks */, 193 | 13773EC71C4E936A003FF521 /* Resources */, 194 | ); 195 | buildRules = ( 196 | ); 197 | dependencies = ( 198 | 13773ECB1C4E936A003FF521 /* PBXTargetDependency */, 199 | ); 200 | name = FLOPageViewControllerUITests; 201 | productName = FLOPageViewControllerUITests; 202 | productReference = 13773EC91C4E936A003FF521 /* FLOPageViewControllerUITests.xctest */; 203 | productType = "com.apple.product-type.bundle.ui-testing"; 204 | }; 205 | 138929301F70075200DE6DFB /* FLOPageViewController */ = { 206 | isa = PBXNativeTarget; 207 | buildConfigurationList = 1389293A1F70075300DE6DFB /* Build configuration list for PBXNativeTarget "FLOPageViewController" */; 208 | buildPhases = ( 209 | 1389292C1F70075200DE6DFB /* Sources */, 210 | 1389292D1F70075200DE6DFB /* Frameworks */, 211 | 1389292E1F70075200DE6DFB /* Headers */, 212 | 1389292F1F70075200DE6DFB /* Resources */, 213 | ); 214 | buildRules = ( 215 | ); 216 | dependencies = ( 217 | ); 218 | name = FLOPageViewController; 219 | productName = FLOPageViewController; 220 | productReference = 138929311F70075200DE6DFB /* FLOPageViewController.framework */; 221 | productType = "com.apple.product-type.framework"; 222 | }; 223 | /* End PBXNativeTarget section */ 224 | 225 | /* Begin PBXProject section */ 226 | 13773EB01C4E936A003FF521 /* Project object */ = { 227 | isa = PBXProject; 228 | attributes = { 229 | LastSwiftUpdateCheck = 0720; 230 | LastUpgradeCheck = 0930; 231 | ORGANIZATIONNAME = "Florian Schliep"; 232 | TargetAttributes = { 233 | 13773EB71C4E936A003FF521 = { 234 | CreatedOnToolsVersion = 7.2; 235 | LastSwiftMigration = 0900; 236 | }; 237 | 13773EC81C4E936A003FF521 = { 238 | CreatedOnToolsVersion = 7.2; 239 | LastSwiftMigration = 0900; 240 | TestTargetID = 13773EB71C4E936A003FF521; 241 | }; 242 | 138929301F70075200DE6DFB = { 243 | CreatedOnToolsVersion = 9.0; 244 | ProvisioningStyle = Automatic; 245 | }; 246 | }; 247 | }; 248 | buildConfigurationList = 13773EB31C4E936A003FF521 /* Build configuration list for PBXProject "FLOPageViewController" */; 249 | compatibilityVersion = "Xcode 3.2"; 250 | developmentRegion = English; 251 | hasScannedForEncodings = 0; 252 | knownRegions = ( 253 | English, 254 | en, 255 | Base, 256 | ); 257 | mainGroup = 13773EAF1C4E936A003FF521; 258 | productRefGroup = 13773EB91C4E936A003FF521 /* Products */; 259 | projectDirPath = ""; 260 | projectRoot = ""; 261 | targets = ( 262 | 13773EB71C4E936A003FF521 /* FLOPageViewControllerSample */, 263 | 13773EC81C4E936A003FF521 /* FLOPageViewControllerUITests */, 264 | 138929301F70075200DE6DFB /* FLOPageViewController */, 265 | ); 266 | }; 267 | /* End PBXProject section */ 268 | 269 | /* Begin PBXResourcesBuildPhase section */ 270 | 13773EB61C4E936A003FF521 /* Resources */ = { 271 | isa = PBXResourcesBuildPhase; 272 | buildActionMask = 2147483647; 273 | files = ( 274 | 13773EC01C4E936A003FF521 /* Assets.xcassets in Resources */, 275 | 131BADC61F9A0E7000A3EF87 /* Main.storyboard in Resources */, 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | }; 279 | 13773EC71C4E936A003FF521 /* Resources */ = { 280 | isa = PBXResourcesBuildPhase; 281 | buildActionMask = 2147483647; 282 | files = ( 283 | ); 284 | runOnlyForDeploymentPostprocessing = 0; 285 | }; 286 | 1389292F1F70075200DE6DFB /* Resources */ = { 287 | isa = PBXResourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | ); 291 | runOnlyForDeploymentPostprocessing = 0; 292 | }; 293 | /* End PBXResourcesBuildPhase section */ 294 | 295 | /* Begin PBXSourcesBuildPhase section */ 296 | 13773EB41C4E936A003FF521 /* Sources */ = { 297 | isa = PBXSourcesBuildPhase; 298 | buildActionMask = 2147483647; 299 | files = ( 300 | 13773EDE1C4E9E59003FF521 /* ViewController.swift in Sources */, 301 | 13773EBC1C4E936A003FF521 /* AppDelegate.swift in Sources */, 302 | 1396A9F61C4FD1E200D89DF1 /* ColoredView.swift in Sources */, 303 | ); 304 | runOnlyForDeploymentPostprocessing = 0; 305 | }; 306 | 13773EC51C4E936A003FF521 /* Sources */ = { 307 | isa = PBXSourcesBuildPhase; 308 | buildActionMask = 2147483647; 309 | files = ( 310 | 13773ECE1C4E936A003FF521 /* FLOPageViewControllerUITests.swift in Sources */, 311 | ); 312 | runOnlyForDeploymentPostprocessing = 0; 313 | }; 314 | 1389292C1F70075200DE6DFB /* Sources */ = { 315 | isa = PBXSourcesBuildPhase; 316 | buildActionMask = 2147483647; 317 | files = ( 318 | 1389293F1F70078000DE6DFB /* PageControl.swift in Sources */, 319 | 138929401F70078000DE6DFB /* ArrowControl.swift in Sources */, 320 | 1389293E1F70078000DE6DFB /* PageViewController.swift in Sources */, 321 | ); 322 | runOnlyForDeploymentPostprocessing = 0; 323 | }; 324 | /* End PBXSourcesBuildPhase section */ 325 | 326 | /* Begin PBXTargetDependency section */ 327 | 13773ECB1C4E936A003FF521 /* PBXTargetDependency */ = { 328 | isa = PBXTargetDependency; 329 | target = 13773EB71C4E936A003FF521 /* FLOPageViewControllerSample */; 330 | targetProxy = 13773ECA1C4E936A003FF521 /* PBXContainerItemProxy */; 331 | }; 332 | 138929371F70075300DE6DFB /* PBXTargetDependency */ = { 333 | isa = PBXTargetDependency; 334 | target = 138929301F70075200DE6DFB /* FLOPageViewController */; 335 | targetProxy = 138929361F70075300DE6DFB /* PBXContainerItemProxy */; 336 | }; 337 | /* End PBXTargetDependency section */ 338 | 339 | /* Begin PBXVariantGroup section */ 340 | 131BADC41F9A0E7000A3EF87 /* Main.storyboard */ = { 341 | isa = PBXVariantGroup; 342 | children = ( 343 | 131BADC51F9A0E7000A3EF87 /* Base */, 344 | ); 345 | name = Main.storyboard; 346 | sourceTree = ""; 347 | }; 348 | /* End PBXVariantGroup section */ 349 | 350 | /* Begin XCBuildConfiguration section */ 351 | 13773ED01C4E936A003FF521 /* Debug */ = { 352 | isa = XCBuildConfiguration; 353 | buildSettings = { 354 | ALWAYS_SEARCH_USER_PATHS = NO; 355 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 356 | CLANG_CXX_LIBRARY = "libc++"; 357 | CLANG_ENABLE_MODULES = YES; 358 | CLANG_ENABLE_OBJC_ARC = YES; 359 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 360 | CLANG_WARN_BOOL_CONVERSION = YES; 361 | CLANG_WARN_COMMA = YES; 362 | CLANG_WARN_CONSTANT_CONVERSION = YES; 363 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 364 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 365 | CLANG_WARN_EMPTY_BODY = YES; 366 | CLANG_WARN_ENUM_CONVERSION = YES; 367 | CLANG_WARN_INFINITE_RECURSION = YES; 368 | CLANG_WARN_INT_CONVERSION = YES; 369 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 370 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 371 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 372 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 373 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 374 | CLANG_WARN_STRICT_PROTOTYPES = YES; 375 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 376 | CLANG_WARN_UNREACHABLE_CODE = YES; 377 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 378 | CODE_SIGN_IDENTITY = "-"; 379 | COPY_PHASE_STRIP = NO; 380 | DEBUG_INFORMATION_FORMAT = dwarf; 381 | ENABLE_STRICT_OBJC_MSGSEND = YES; 382 | ENABLE_TESTABILITY = YES; 383 | GCC_C_LANGUAGE_STANDARD = gnu99; 384 | GCC_DYNAMIC_NO_PIC = NO; 385 | GCC_NO_COMMON_BLOCKS = YES; 386 | GCC_OPTIMIZATION_LEVEL = 0; 387 | GCC_PREPROCESSOR_DEFINITIONS = ( 388 | "DEBUG=1", 389 | "$(inherited)", 390 | ); 391 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 392 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 393 | GCC_WARN_UNDECLARED_SELECTOR = YES; 394 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 395 | GCC_WARN_UNUSED_FUNCTION = YES; 396 | GCC_WARN_UNUSED_VARIABLE = YES; 397 | MACOSX_DEPLOYMENT_TARGET = 10.11; 398 | MTL_ENABLE_DEBUG_INFO = YES; 399 | ONLY_ACTIVE_ARCH = YES; 400 | SDKROOT = macosx; 401 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 402 | }; 403 | name = Debug; 404 | }; 405 | 13773ED11C4E936A003FF521 /* Release */ = { 406 | isa = XCBuildConfiguration; 407 | buildSettings = { 408 | ALWAYS_SEARCH_USER_PATHS = NO; 409 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 410 | CLANG_CXX_LIBRARY = "libc++"; 411 | CLANG_ENABLE_MODULES = YES; 412 | CLANG_ENABLE_OBJC_ARC = YES; 413 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 414 | CLANG_WARN_BOOL_CONVERSION = YES; 415 | CLANG_WARN_COMMA = YES; 416 | CLANG_WARN_CONSTANT_CONVERSION = YES; 417 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 418 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 419 | CLANG_WARN_EMPTY_BODY = YES; 420 | CLANG_WARN_ENUM_CONVERSION = YES; 421 | CLANG_WARN_INFINITE_RECURSION = YES; 422 | CLANG_WARN_INT_CONVERSION = YES; 423 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 424 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 425 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 426 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 427 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 428 | CLANG_WARN_STRICT_PROTOTYPES = YES; 429 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 430 | CLANG_WARN_UNREACHABLE_CODE = YES; 431 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 432 | CODE_SIGN_IDENTITY = "-"; 433 | COPY_PHASE_STRIP = NO; 434 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 435 | ENABLE_NS_ASSERTIONS = NO; 436 | ENABLE_STRICT_OBJC_MSGSEND = YES; 437 | GCC_C_LANGUAGE_STANDARD = gnu99; 438 | GCC_NO_COMMON_BLOCKS = YES; 439 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 440 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 441 | GCC_WARN_UNDECLARED_SELECTOR = YES; 442 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 443 | GCC_WARN_UNUSED_FUNCTION = YES; 444 | GCC_WARN_UNUSED_VARIABLE = YES; 445 | MACOSX_DEPLOYMENT_TARGET = 10.11; 446 | MTL_ENABLE_DEBUG_INFO = NO; 447 | SDKROOT = macosx; 448 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 449 | }; 450 | name = Release; 451 | }; 452 | 13773ED31C4E936A003FF521 /* Debug */ = { 453 | isa = XCBuildConfiguration; 454 | buildSettings = { 455 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 456 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 457 | CLANG_WARN_STRICT_PROTOTYPES = NO; 458 | COMBINE_HIDPI_IMAGES = YES; 459 | ENABLE_HARDENED_RUNTIME = YES; 460 | FRAMEWORK_SEARCH_PATHS = ""; 461 | INFOPLIST_FILE = "Sample App/Info.plist"; 462 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 463 | OTHER_LDFLAGS = ""; 464 | PRODUCT_BUNDLE_IDENTIFIER = com.floschliep.FLOPageViewController; 465 | PRODUCT_NAME = "$(TARGET_NAME)"; 466 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 467 | SWIFT_VERSION = 4.0; 468 | }; 469 | name = Debug; 470 | }; 471 | 13773ED41C4E936A003FF521 /* Release */ = { 472 | isa = XCBuildConfiguration; 473 | buildSettings = { 474 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 475 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 476 | CLANG_WARN_STRICT_PROTOTYPES = NO; 477 | COMBINE_HIDPI_IMAGES = YES; 478 | ENABLE_HARDENED_RUNTIME = YES; 479 | FRAMEWORK_SEARCH_PATHS = ""; 480 | INFOPLIST_FILE = "Sample App/Info.plist"; 481 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 482 | OTHER_LDFLAGS = ""; 483 | PRODUCT_BUNDLE_IDENTIFIER = com.floschliep.FLOPageViewController; 484 | PRODUCT_NAME = "$(TARGET_NAME)"; 485 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 486 | SWIFT_VERSION = 4.0; 487 | }; 488 | name = Release; 489 | }; 490 | 13773ED61C4E936A003FF521 /* Debug */ = { 491 | isa = XCBuildConfiguration; 492 | buildSettings = { 493 | COMBINE_HIDPI_IMAGES = YES; 494 | INFOPLIST_FILE = FLOPageViewControllerUITests/Info.plist; 495 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 496 | PRODUCT_BUNDLE_IDENTIFIER = com.floschliep.FLOPageViewControllerUITests; 497 | PRODUCT_NAME = "$(TARGET_NAME)"; 498 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 499 | SWIFT_VERSION = 4.0; 500 | TEST_TARGET_NAME = FLOPageViewController; 501 | USES_XCTRUNNER = YES; 502 | }; 503 | name = Debug; 504 | }; 505 | 13773ED71C4E936A003FF521 /* Release */ = { 506 | isa = XCBuildConfiguration; 507 | buildSettings = { 508 | COMBINE_HIDPI_IMAGES = YES; 509 | INFOPLIST_FILE = FLOPageViewControllerUITests/Info.plist; 510 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 511 | PRODUCT_BUNDLE_IDENTIFIER = com.floschliep.FLOPageViewControllerUITests; 512 | PRODUCT_NAME = "$(TARGET_NAME)"; 513 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 514 | SWIFT_VERSION = 4.0; 515 | TEST_TARGET_NAME = FLOPageViewController; 516 | USES_XCTRUNNER = YES; 517 | }; 518 | name = Release; 519 | }; 520 | 1389293B1F70075300DE6DFB /* Debug */ = { 521 | isa = XCBuildConfiguration; 522 | buildSettings = { 523 | CLANG_ANALYZER_NONNULL = YES; 524 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 525 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 526 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 527 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 528 | CODE_SIGN_IDENTITY = ""; 529 | CODE_SIGN_STYLE = Automatic; 530 | COMBINE_HIDPI_IMAGES = YES; 531 | CURRENT_PROJECT_VERSION = 1; 532 | DEFINES_MODULE = YES; 533 | DYLIB_COMPATIBILITY_VERSION = 1; 534 | DYLIB_CURRENT_VERSION = 1; 535 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 536 | FRAMEWORK_VERSION = A; 537 | GCC_C_LANGUAGE_STANDARD = gnu11; 538 | INFOPLIST_FILE = FLOPageViewController/Info.plist; 539 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 540 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 541 | MACOSX_DEPLOYMENT_TARGET = 10.10; 542 | PRODUCT_BUNDLE_IDENTIFIER = com.appiculous.FLOPageViewController; 543 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 544 | SKIP_INSTALL = YES; 545 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 546 | SWIFT_VERSION = 4.0; 547 | VERSIONING_SYSTEM = "apple-generic"; 548 | VERSION_INFO_PREFIX = ""; 549 | }; 550 | name = Debug; 551 | }; 552 | 1389293C1F70075300DE6DFB /* Release */ = { 553 | isa = XCBuildConfiguration; 554 | buildSettings = { 555 | CLANG_ANALYZER_NONNULL = YES; 556 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 557 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 558 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 559 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 560 | CODE_SIGN_IDENTITY = ""; 561 | CODE_SIGN_STYLE = Automatic; 562 | COMBINE_HIDPI_IMAGES = YES; 563 | CURRENT_PROJECT_VERSION = 1; 564 | DEFINES_MODULE = YES; 565 | DYLIB_COMPATIBILITY_VERSION = 1; 566 | DYLIB_CURRENT_VERSION = 1; 567 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 568 | FRAMEWORK_VERSION = A; 569 | GCC_C_LANGUAGE_STANDARD = gnu11; 570 | INFOPLIST_FILE = FLOPageViewController/Info.plist; 571 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 572 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 573 | MACOSX_DEPLOYMENT_TARGET = 10.10; 574 | PRODUCT_BUNDLE_IDENTIFIER = com.appiculous.FLOPageViewController; 575 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 576 | SKIP_INSTALL = YES; 577 | SWIFT_VERSION = 4.0; 578 | VERSIONING_SYSTEM = "apple-generic"; 579 | VERSION_INFO_PREFIX = ""; 580 | }; 581 | name = Release; 582 | }; 583 | /* End XCBuildConfiguration section */ 584 | 585 | /* Begin XCConfigurationList section */ 586 | 13773EB31C4E936A003FF521 /* Build configuration list for PBXProject "FLOPageViewController" */ = { 587 | isa = XCConfigurationList; 588 | buildConfigurations = ( 589 | 13773ED01C4E936A003FF521 /* Debug */, 590 | 13773ED11C4E936A003FF521 /* Release */, 591 | ); 592 | defaultConfigurationIsVisible = 0; 593 | defaultConfigurationName = Release; 594 | }; 595 | 13773ED21C4E936A003FF521 /* Build configuration list for PBXNativeTarget "FLOPageViewControllerSample" */ = { 596 | isa = XCConfigurationList; 597 | buildConfigurations = ( 598 | 13773ED31C4E936A003FF521 /* Debug */, 599 | 13773ED41C4E936A003FF521 /* Release */, 600 | ); 601 | defaultConfigurationIsVisible = 0; 602 | defaultConfigurationName = Release; 603 | }; 604 | 13773ED51C4E936A003FF521 /* Build configuration list for PBXNativeTarget "FLOPageViewControllerUITests" */ = { 605 | isa = XCConfigurationList; 606 | buildConfigurations = ( 607 | 13773ED61C4E936A003FF521 /* Debug */, 608 | 13773ED71C4E936A003FF521 /* Release */, 609 | ); 610 | defaultConfigurationIsVisible = 0; 611 | defaultConfigurationName = Release; 612 | }; 613 | 1389293A1F70075300DE6DFB /* Build configuration list for PBXNativeTarget "FLOPageViewController" */ = { 614 | isa = XCConfigurationList; 615 | buildConfigurations = ( 616 | 1389293B1F70075300DE6DFB /* Debug */, 617 | 1389293C1F70075300DE6DFB /* Release */, 618 | ); 619 | defaultConfigurationIsVisible = 0; 620 | defaultConfigurationName = Release; 621 | }; 622 | /* End XCConfigurationList section */ 623 | }; 624 | rootObject = 13773EB01C4E936A003FF521 /* Project object */; 625 | } 626 | -------------------------------------------------------------------------------- /FLOPageViewController.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FLOPageViewController.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /FLOPageViewController.xcodeproj/project.xcworkspace/xcuserdata/florianschliep.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/floschliep/FLOPageViewController/7068f99f7b42407683352d573e18394c1f747e7a/FLOPageViewController.xcodeproj/project.xcworkspace/xcuserdata/florianschliep.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /FLOPageViewController.xcodeproj/project.xcworkspace/xcuserdata/xiao99xiao.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/floschliep/FLOPageViewController/7068f99f7b42407683352d573e18394c1f747e7a/FLOPageViewController.xcodeproj/project.xcworkspace/xcuserdata/xiao99xiao.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /FLOPageViewController.xcodeproj/xcuserdata/florianschliep.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /FLOPageViewController.xcodeproj/xcuserdata/florianschliep.xcuserdatad/xcschemes/Framework.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /FLOPageViewController.xcodeproj/xcuserdata/florianschliep.xcuserdatad/xcschemes/Sample App.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /FLOPageViewController.xcodeproj/xcuserdata/florianschliep.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Framework.xcscheme 8 | 9 | orderHint 10 | 1 11 | 12 | Sample App.xcscheme 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 13773EB71C4E936A003FF521 21 | 22 | primary 23 | 24 | 25 | 13773EC81C4E936A003FF521 26 | 27 | primary 28 | 29 | 30 | 138929301F70075200DE6DFB 31 | 32 | primary 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /FLOPageViewController.xcodeproj/xcuserdata/xiao99xiao.xcuserdatad/xcschemes/FLOPageViewController.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /FLOPageViewController.xcodeproj/xcuserdata/xiao99xiao.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | FLOPageViewController.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 13773EB71C4E936A003FF521 16 | 17 | primary 18 | 19 | 20 | 13773EC81C4E936A003FF521 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /FLOPageViewController/ArrowControl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FLOArrowControl.swift 3 | // FLOPageViewController 4 | // 5 | // Created by Florian Schliep on 21.01.16. 6 | // Copyright © 2016 Florian Schliep. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @objc(FLOArrowControl) 12 | public class ArrowControl: NSControl { 13 | 14 | private var mouseDown = false { 15 | didSet { 16 | self.needsDisplay = true 17 | } 18 | } 19 | 20 | // MARK: - Properties 21 | 22 | public enum Direction { 23 | case left 24 | case right 25 | } 26 | 27 | public var direction = Direction.left { 28 | didSet { 29 | self.needsDisplay = true 30 | } 31 | } 32 | 33 | public var color = NSColor.black { 34 | didSet { 35 | self.needsDisplay = true 36 | } 37 | } 38 | 39 | // MARK: - Drawing 40 | 41 | public override func draw(_ dirtyRect: NSRect) { 42 | let drawRightArrow = self.direction == .right 43 | let lineWidth: CGFloat = 4 44 | 45 | let bezierPath = NSBezierPath() 46 | bezierPath.move(to: NSPoint(x: drawRightArrow ? NSMinX(self.bounds) : NSMaxX(self.bounds), y: NSMaxY(self.bounds))) 47 | bezierPath.line(to: NSPoint(x: drawRightArrow ? NSMaxX(self.bounds)-lineWidth*0.5 : NSMinX(self.bounds)+lineWidth*0.5, y: NSMidY(self.bounds))) 48 | bezierPath.line(to: NSPoint(x: drawRightArrow ? NSMinX(self.bounds) : NSMaxX(self.bounds), y: NSMinY(self.bounds))) 49 | bezierPath.lineWidth = lineWidth 50 | bezierPath.lineCapStyle = .roundLineCapStyle 51 | bezierPath.lineJoinStyle = .roundLineJoinStyle 52 | (self.mouseDown ? self.color : self.color.withAlphaComponent(0.33)).setStroke() 53 | bezierPath.stroke() 54 | } 55 | 56 | // MARK: - Mouse 57 | 58 | public override func mouseDown(with theEvent: NSEvent) { 59 | super.mouseDown(with: theEvent) 60 | self.mouseDown = true 61 | } 62 | 63 | public override func mouseUp(with theEvent: NSEvent) { 64 | super.mouseUp(with: theEvent) 65 | self.mouseDown = false 66 | 67 | guard let target = self.target, let action = self.action else { return } 68 | NSApp.sendAction(action, to: target, from: self) 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /FLOPageViewController/FLOPageViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FLOPageViewController.h 3 | // FLOPageViewController 4 | // 5 | // Created by Florian Schliep on 18.09.17. 6 | // Copyright © 2017 Florian Schliep. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for FLOPageViewController. 12 | FOUNDATION_EXPORT double FLOPageViewControllerVersionNumber; 13 | 14 | //! Project version string for FLOPageViewController. 15 | FOUNDATION_EXPORT const unsigned char FLOPageViewControllerVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /FLOPageViewController/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.2 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSHumanReadableCopyright 22 | Copyright © 2017 Florian Schliep. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /FLOPageViewController/PageControl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FLOPageControl.swift 3 | // FLOPageViewController 4 | // 5 | // Created by Florian Schliep on 21.01.16. 6 | // Copyright © 2016 Florian Schliep. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @objc(FLOPageControl) 12 | public class PageControl: NSControl { 13 | 14 | private var needsToRedrawIndicators = false 15 | 16 | // MARK: - Appearance 17 | 18 | public var color = NSColor.black { 19 | didSet { 20 | self.redrawIndicators() 21 | } 22 | } 23 | 24 | public var indicatorSize: CGFloat = 7 { 25 | didSet { 26 | self.redrawIndicators() 27 | } 28 | } 29 | 30 | @objc(FLOPageControlStyle) 31 | public enum Style: Int { 32 | case dot 33 | case circle 34 | } 35 | 36 | public var style = Style.dot { 37 | didSet { 38 | self.redrawIndicators() 39 | } 40 | } 41 | 42 | // MARK: - Pages 43 | 44 | public var numberOfPages: UInt = 0 { 45 | didSet { 46 | self.redrawIndicators() 47 | } 48 | } 49 | 50 | public var selectedPage: UInt = 0 { 51 | didSet { 52 | self.redrawIndicators() 53 | } 54 | } 55 | 56 | // MARK: - NSControl 57 | 58 | public override var frame: NSRect { 59 | willSet { 60 | self.needsToRedrawIndicators = true 61 | } 62 | } 63 | 64 | // MARK: - Drawing 65 | 66 | public override func draw(_ dirtyRect: NSRect) { 67 | guard self.needsToRedrawIndicators else { return } 68 | 69 | if self.numberOfPages > 1 { 70 | for index in 0...self.numberOfPages-1 { 71 | var fill = true 72 | let frame = self.frameOfIndicator(at: index) 73 | let lineWidth: CGFloat = 1 74 | 75 | switch (self.style, index == self.selectedPage) { 76 | case (.dot, true), (.circle, true): 77 | self.color.setFill() 78 | case (.dot, false): 79 | self.color.withAlphaComponent(0.33).setFill() 80 | case (.circle, false): 81 | self.color.setStroke() 82 | fill = false 83 | frame.insetBy(dx: lineWidth*0.5, dy: lineWidth*0.5) 84 | } 85 | 86 | let path = NSBezierPath(ovalIn: frame) 87 | if fill { 88 | path.fill() 89 | } else { 90 | path.lineWidth = lineWidth 91 | path.stroke() 92 | } 93 | } 94 | } 95 | 96 | self.needsToRedrawIndicators = false 97 | } 98 | 99 | // MARK: - Mouse 100 | 101 | public override func mouseDown(with theEvent: NSEvent) { 102 | let location = self.convert(theEvent.locationInWindow, from: nil) 103 | self.highlightIndicator(at: location) 104 | } 105 | 106 | public override func mouseDragged(with theEvent: NSEvent) { 107 | let location = self.convert(theEvent.locationInWindow, from: nil) 108 | self.highlightIndicator(at: location) 109 | } 110 | 111 | public override func mouseUp(with theEvent: NSEvent) { 112 | let location = self.convert(theEvent.locationInWindow, from: nil) 113 | self.highlightIndicator(at: location, sendAction: true) 114 | } 115 | 116 | // MARK: - Helpers 117 | 118 | private func highlightIndicator(at location: NSPoint, sendAction: Bool = false) { 119 | var newPage = self.selectedPage 120 | for index in 0...self.numberOfPages-1 { 121 | if NSPointInRect(location, self.frameOfIndicator(at: index)) { 122 | newPage = index 123 | break 124 | } 125 | } 126 | if self.selectedPage != newPage { 127 | self.selectedPage = newPage 128 | } 129 | 130 | guard sendAction, let target = self.target, let action = self.action else { return } 131 | NSApp.sendAction(action, to: target, from: self) 132 | } 133 | 134 | private func frameOfIndicator(at index: UInt) -> NSRect { 135 | let centerDrawingAroundSpace = (self.numberOfPages % 2 == 0) 136 | let centeredIndex = self.numberOfPages/2 137 | let centeredFrame = NSRect(x: NSMidX(self.bounds) - (centerDrawingAroundSpace ? -self.indicatorSize/2 : self.indicatorSize/2), y: NSMidY(self.bounds) - self.indicatorSize/2, width: self.indicatorSize, height: self.indicatorSize) 138 | let distanceToCenteredIndex = CGFloat(centeredIndex)-CGFloat(index) 139 | 140 | return NSRect(x: NSMinX(centeredFrame) - distanceToCenteredIndex*self.indicatorSize*2, y: NSMidY(self.bounds) - self.indicatorSize/2, width: self.indicatorSize, height: self.indicatorSize) 141 | } 142 | 143 | private func redrawIndicators() { 144 | self.needsToRedrawIndicators = true 145 | self.needsDisplay = true 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /FLOPageViewController/PageViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FLOPageViewController.swift 3 | // FLOPageViewController 4 | // 5 | // Created by Florian Schliep on 19.01.16. 6 | // Copyright © 2016 Florian Schliep. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | private let ArrowSize = NSSize(width: 20, height: 40) 12 | 13 | @objc(FLOPageViewControllerDelegate) 14 | public protocol PageViewControllerDelegate: class { 15 | func pageViewController(_ pageViewController: PageViewController, didSelectPage pageIndex: Int) 16 | } 17 | 18 | @objc(FLOPageViewController) 19 | public class PageViewController: NSViewController { 20 | 21 | @objc public weak var delegate: PageViewControllerDelegate? 22 | 23 | fileprivate weak var pageController: _FLOPageController! 24 | fileprivate weak var pageControl: PageControl? 25 | private weak var leftArrow: ArrowControl? 26 | private weak var rightArrow: ArrowControl? 27 | 28 | private weak var bottomPageControllerConstraint: NSLayoutConstraint? 29 | // we are using left/right instead of leading/trailing b/c of the arrows; in case of an r-l lang, the viewControllers array will be reversed, which is simpler than dealing w/ leading/trailing arrows, as NSPageController doesn't support r-l langs 30 | private weak var leftPageControllerConstraint: NSLayoutConstraint? 31 | private weak var rightPageControllerConstraint: NSLayoutConstraint? 32 | 33 | private var trackingRectTag: NSView.TrackingRectTag? 34 | private var mouseInside = false 35 | 36 | // MARK: - Instantiation 37 | 38 | @objc 39 | public init() { 40 | super.init(nibName: nil, bundle: nil) 41 | self.setUp() 42 | self.view = NSView() 43 | self.viewDidLoad() 44 | } 45 | 46 | public required init?(coder: NSCoder) { 47 | super.init(coder: coder) 48 | self.setUp() 49 | } 50 | 51 | public override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) { 52 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 53 | self.setUp() 54 | } 55 | 56 | private func setUp() { 57 | let pageController = _FLOPageController() 58 | pageController.view = NSView() // we need to create a view here (as we're not loading one from a nib) though we'll override it later 59 | pageController.view.translatesAutoresizingMaskIntoConstraints = false 60 | pageController.delegate = self 61 | pageController.transitionStyle = .horizontalStrip 62 | 63 | self.addChildViewController(pageController) 64 | self.pageController = pageController 65 | } 66 | 67 | // MARK: - NSViewController 68 | 69 | public override func viewDidLoad() { 70 | super.viewDidLoad() 71 | 72 | self.view.wantsLayer = true 73 | self.updateBackgroundColor() 74 | 75 | // changing the view's frame is somehow not enough (NSPageController is weird), so we create a new view 76 | self.pageController.view = NSView(frame: self.view.bounds) 77 | self.pageController.view.translatesAutoresizingMaskIntoConstraints = false 78 | self.view.addSubview(self.pageController.view) 79 | 80 | self.view.addConstraint(NSLayoutConstraint(item: self.pageController.view, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 0)) // we don't ever need to modify the top constraint 81 | self.leftPageControllerConstraint = NSLayoutConstraint(item: self.pageController.view, attribute: .left, relatedBy: .equal, toItem: self.view, attribute: .left, multiplier: 1, constant: 0) 82 | self.view.addConstraint(self.leftPageControllerConstraint!) 83 | self.rightPageControllerConstraint = NSLayoutConstraint(item: self.pageController.view, attribute: .right, relatedBy: .equal, toItem: self.view, attribute: .right, multiplier: 1, constant: 0) 84 | self.view.addConstraint(self.rightPageControllerConstraint!) 85 | self.bottomPageControllerConstraint = NSLayoutConstraint(item: self.pageController.view, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1, constant: 0) 86 | self.view.addConstraint(self.bottomPageControllerConstraint!) 87 | 88 | self.updatePageControl() 89 | self.updateArrowControls() 90 | } 91 | 92 | public override func viewDidLayout() { 93 | super.viewDidLayout() 94 | 95 | for vc in self.viewControllers { 96 | vc.view.frame = self.view.bounds 97 | } 98 | } 99 | 100 | // MARK: - View Controller Management 101 | 102 | @objc public var viewControllers: [NSViewController] = [] { 103 | didSet { 104 | let reverse = (NSApp.userInterfaceLayoutDirection == .rightToLeft && self.viewControllers.count > 1) 105 | 106 | if reverse { 107 | self.viewControllers.reverseInPlace() 108 | } 109 | self.pageController.arrangedObjects = self.viewControllers.map({ NSNumber(value: self.viewControllers.index(of: $0)!) }) 110 | self.pageController.scrollingEnabled = (self.viewControllers.count > 1) 111 | 112 | if reverse { 113 | self.pageController.selectedIndex = self.viewControllers.count-1 114 | } 115 | self.hideArrowControls(false) 116 | self.updatePages() 117 | } 118 | } 119 | 120 | @objc 121 | public func loadViewControllers(_ viewControllerIdentifiers: [String], from storyboard: NSStoryboard) { 122 | self.viewControllers = viewControllerIdentifiers.map({ 123 | storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier($0)) as! NSViewController 124 | }) 125 | } 126 | 127 | // MARK: - Page Control 128 | 129 | @objc 130 | public var selectedPage: Int { 131 | get { 132 | return self.pageController.selectedIndex 133 | } 134 | set { 135 | self.pageController.animator().selectedIndex = newValue 136 | self.pageControl?.selectedPage = UInt(newValue) 137 | } 138 | } 139 | 140 | @objc public var showPageControl = true { 141 | didSet { 142 | self.updatePageControl() 143 | } 144 | } 145 | 146 | @objc public var pageIndicatorStyle = PageControl.Style.dot { 147 | didSet { 148 | self.pageControl?.style = self.pageIndicatorStyle 149 | } 150 | } 151 | 152 | private func updatePageControl() { 153 | if self.showPageControl == true && self.pageControl == nil { 154 | let pageControl = PageControl() 155 | pageControl.target = self 156 | pageControl.action = #selector(pageControlDidChangeSelection(_:)) 157 | pageControl.color = self.tintColor 158 | pageControl.style = self.pageIndicatorStyle 159 | pageControl.translatesAutoresizingMaskIntoConstraints = false 160 | pageControl.wantsLayer = true 161 | pageControl.layer!.zPosition = 1000 162 | self.view.addSubview(pageControl) 163 | self.pageControl = pageControl 164 | self.hidePageControl(false) 165 | self.updatePages() 166 | 167 | self.view.addConstraint(NSLayoutConstraint(item: pageControl, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1, constant: 0)) 168 | self.view.addConstraint(NSLayoutConstraint(item: pageControl, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailing, multiplier: 1, constant: 0)) 169 | self.view.addConstraint(NSLayoutConstraint(item: pageControl, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1, constant: -pageControl.indicatorSize)) 170 | self.view.addConstraint(NSLayoutConstraint(item: pageControl, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: pageControl.indicatorSize)) 171 | self.updateBottomConstraint() 172 | } else if self.showPageControl == false, let pageControl = self.pageControl { 173 | pageControl.removeFromSuperview() 174 | self.updateBottomConstraint() 175 | } 176 | } 177 | 178 | private func updateBottomConstraint() { 179 | guard let bottomConstraint = self.bottomPageControllerConstraint else { return } 180 | 181 | if let pageControl = self.pageControl { 182 | if self.overlayControls { 183 | bottomConstraint.constant = 0 184 | } else { 185 | bottomConstraint.constant = -pageControl.indicatorSize*3 186 | } 187 | } else { 188 | bottomConstraint.constant = 0 189 | } 190 | } 191 | 192 | @objc 193 | public func pageControlDidChangeSelection(_ sender: PageControl) { 194 | self.selectedPage = Int(sender.selectedPage) 195 | self.notifyDelegate() 196 | } 197 | 198 | // MARK: - Arrow Controls 199 | 200 | @objc public var showArrowControls = false { 201 | didSet { 202 | self.updateArrowControls() 203 | } 204 | } 205 | 206 | private func updateArrowControls() { 207 | if self.showArrowControls == true && self.leftArrow == nil { 208 | let leftArrow = ArrowControl() 209 | leftArrow.target = self 210 | leftArrow.action = #selector(didPressArrowControl(_:)) 211 | leftArrow.color = self.tintColor 212 | leftArrow.translatesAutoresizingMaskIntoConstraints = false 213 | leftArrow.wantsLayer = true 214 | leftArrow.layer!.zPosition = 1000 215 | self.view.addSubview(leftArrow) 216 | self.leftArrow = leftArrow 217 | 218 | let rightArrow = ArrowControl() 219 | rightArrow.target = self 220 | rightArrow.action = #selector(didPressArrowControl(_:)) 221 | rightArrow.direction = .right 222 | rightArrow.color = self.tintColor 223 | rightArrow.translatesAutoresizingMaskIntoConstraints = false 224 | rightArrow.wantsLayer = true 225 | rightArrow.layer!.zPosition = 1000 226 | self.view.addSubview(rightArrow) 227 | self.rightArrow = rightArrow 228 | self.hideArrowControls(false) 229 | 230 | self.view.addConstraint(NSLayoutConstraint(item: leftArrow, attribute: .left, relatedBy: .equal, toItem: self.view, attribute: .left, multiplier: 1, constant: ArrowSize.width)) 231 | self.view.addConstraint(NSLayoutConstraint(item: leftArrow, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1, constant: 0)) 232 | self.view.addConstraint(NSLayoutConstraint(item: leftArrow, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: ArrowSize.width)) 233 | self.view.addConstraint(NSLayoutConstraint(item: leftArrow, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: ArrowSize.height)) 234 | 235 | self.view.addConstraint(NSLayoutConstraint(item: rightArrow, attribute: .right, relatedBy: .equal, toItem: self.view, attribute: .right, multiplier: 1, constant: -ArrowSize.width)) 236 | self.view.addConstraint(NSLayoutConstraint(item: rightArrow, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1, constant: 0)) 237 | self.view.addConstraint(NSLayoutConstraint(item: rightArrow, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: ArrowSize.width)) 238 | self.view.addConstraint(NSLayoutConstraint(item: rightArrow, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: ArrowSize.height)) 239 | 240 | self.updateSideConstraints() 241 | } else if self.showArrowControls == false && self.leftArrow != nil { 242 | self.leftArrow!.removeFromSuperview() 243 | self.leftArrow = nil 244 | 245 | self.rightArrow!.removeFromSuperview() 246 | self.rightArrow = nil 247 | 248 | self.updateSideConstraints() 249 | } 250 | } 251 | 252 | private func updateSideConstraints() { 253 | guard let leftConstraint = self.leftPageControllerConstraint, let rightConstraint = self.rightPageControllerConstraint else { return } 254 | 255 | if self.leftArrow != nil && !self.overlayControls { 256 | leftConstraint.constant = ArrowSize.width*3 257 | rightConstraint.constant = -ArrowSize.width*3 258 | } else { 259 | leftConstraint.constant = 0 260 | rightConstraint.constant = 0 261 | } 262 | } 263 | 264 | @objc 265 | public func didPressArrowControl(_ sender: ArrowControl) { 266 | switch sender.direction { 267 | case .left: 268 | self.pageController.navigateBack(nil) 269 | case .right: 270 | self.pageController.navigateForward(nil) 271 | } 272 | self.notifyDelegate() 273 | } 274 | 275 | // MARK: - Appearance + Behavior 276 | 277 | @objc public var pageControlRequiresMouseOver = false { 278 | didSet { 279 | self.updateMouseTracking() 280 | self.hidePageControl(self.pageControlRequiresMouseOver) 281 | } 282 | } 283 | 284 | @objc public var arrowControlsRequireMouseOver = false { 285 | didSet { 286 | self.updateMouseTracking() 287 | self.hideArrowControls(self.arrowControlsRequireMouseOver) 288 | } 289 | } 290 | 291 | @objc public var overlayControls = true { 292 | didSet { 293 | self.updateBottomConstraint() 294 | self.updateSideConstraints() 295 | } 296 | } 297 | 298 | @objc public var tintColor = NSColor.black { 299 | didSet { 300 | self.pageControl?.color = self.tintColor 301 | self.leftArrow?.color = self.tintColor 302 | self.rightArrow?.color = self.tintColor 303 | } 304 | } 305 | 306 | @objc public var backgroundColor: NSColor? { 307 | didSet { 308 | self.updateBackgroundColor() 309 | } 310 | } 311 | 312 | // MARK: - Mouse 313 | 314 | public override func mouseEntered(with theEvent: NSEvent) { 315 | super.mouseEntered(with: theEvent) 316 | guard theEvent.trackingNumber == self.trackingRectTag else { return } 317 | 318 | self.mouseInside = true 319 | if self.pageControlRequiresMouseOver { 320 | self.hidePageControl(false) 321 | } 322 | if self.arrowControlsRequireMouseOver { 323 | self.hideArrowControls(false) 324 | } 325 | } 326 | 327 | public override func mouseExited(with theEvent: NSEvent) { 328 | super.mouseExited(with: theEvent) 329 | guard theEvent.trackingNumber == self.trackingRectTag else { return } 330 | 331 | self.mouseInside = false 332 | if self.pageControlRequiresMouseOver { 333 | self.hidePageControl() 334 | } 335 | if self.arrowControlsRequireMouseOver { 336 | self.hideArrowControls() 337 | } 338 | } 339 | 340 | // MARK: - Helpers 341 | 342 | private func updateMouseTracking() { 343 | if (self.pageControlRequiresMouseOver || self.arrowControlsRequireMouseOver) && self.trackingRectTag == nil { 344 | self.trackingRectTag = self.view.addTrackingRect(self.view.bounds, owner: self, userData: nil, assumeInside: false) 345 | } else if (!self.pageControlRequiresMouseOver && !self.arrowControlsRequireMouseOver) && self.trackingRectTag != nil { 346 | self.view.removeTrackingRect(self.trackingRectTag!) 347 | self.trackingRectTag = nil 348 | } 349 | } 350 | 351 | private func hidePageControl(_ flag: Bool = true) { 352 | if self.pageControlRequiresMouseOver { 353 | self.pageControl?.isHidden = flag ? true : !self.mouseInside 354 | } else { 355 | self.pageControl?.isHidden = flag 356 | } 357 | 358 | } 359 | 360 | fileprivate func hideArrowControls(_ flag: Bool = true) { 361 | let hideLeftArrow = (self.pageController.selectedIndex == 0) 362 | let hideRightArrow = (self.pageController.selectedIndex == self.viewControllers.count-1) 363 | if self.arrowControlsRequireMouseOver { 364 | self.leftArrow?.isHidden = (flag || hideLeftArrow) ? true : !self.mouseInside 365 | self.rightArrow?.isHidden = (flag || hideRightArrow) ? true : !self.mouseInside 366 | } else { 367 | self.leftArrow?.isHidden = (flag || hideLeftArrow) 368 | self.rightArrow?.isHidden = (flag || hideRightArrow) 369 | } 370 | } 371 | 372 | private func updateBackgroundColor() { 373 | self.view.layer?.backgroundColor = self.backgroundColor?.cgColor 374 | } 375 | 376 | private func updatePages() { 377 | self.pageControl?.numberOfPages = UInt(self.viewControllers.count) 378 | self.pageControl?.selectedPage = UInt(self.pageController.selectedIndex) 379 | } 380 | 381 | fileprivate func notifyDelegate() { 382 | self.delegate?.pageViewController(self, didSelectPage: self.selectedPage) 383 | } 384 | 385 | // MARK: - Navigation 386 | 387 | private var needsLeftToRightNavigation: Bool { 388 | return (NSApp.userInterfaceLayoutDirection == .leftToRight) 389 | } 390 | 391 | @objc 392 | public func navigateForward() { 393 | if self.needsLeftToRightNavigation { 394 | self.pageController.navigateForward(nil) 395 | } else { 396 | self.pageController.navigateBack(nil) 397 | } 398 | self.notifyDelegate() 399 | } 400 | 401 | @objc 402 | public func navigateBack() { 403 | if self.needsLeftToRightNavigation { 404 | self.pageController.navigateBack(nil) 405 | } else { 406 | self.pageController.navigateForward(nil) 407 | } 408 | self.notifyDelegate() 409 | } 410 | 411 | @objc 412 | public var isAtStart: Bool { 413 | if self.needsLeftToRightNavigation { 414 | return (self.selectedPage == 0) 415 | } else { 416 | return (self.selectedPage == self.viewControllers.count-1) 417 | } 418 | } 419 | 420 | @objc 421 | public var isAtEnd: Bool { 422 | if self.needsLeftToRightNavigation { 423 | return (self.selectedPage == self.viewControllers.count-1) 424 | } else { 425 | return (self.selectedPage == 0) 426 | } 427 | } 428 | 429 | } 430 | 431 | extension PageViewController: NSPageControllerDelegate { 432 | 433 | public func pageController(_ pageController: NSPageController, identifierFor object: Any) -> NSPageController.ObjectIdentifier { 434 | guard let number = object as? NSNumber else { fatalError("The arrangedObjects array has been changed manually. This is not allowed! Please use the viewControllers array to manage the pages.") } 435 | return NSPageController.ObjectIdentifier(number.stringValue) 436 | } 437 | 438 | public func pageController(_ pageController: NSPageController, viewControllerForIdentifier identifier: NSPageController.ObjectIdentifier) -> NSViewController { 439 | let index = (identifier as NSString).integerValue 440 | return self.viewControllers[index] 441 | } 442 | 443 | public func pageController(_ pageController: NSPageController, didTransitionTo object: Any) { 444 | let identifier = self.pageController(pageController, identifierFor: object) 445 | let viewController = self.pageController(pageController, viewControllerForIdentifier: identifier) 446 | guard let index = self.viewControllers.index(of: viewController) else { return } 447 | 448 | self.pageControl?.selectedPage = UInt(index) 449 | self.hideArrowControls(false) 450 | self.notifyDelegate() 451 | } 452 | 453 | public func pageControllerDidEndLiveTransition(_ pageController: NSPageController) { 454 | self.pageController.completeTransition() // we need to do this, see docs 455 | } 456 | 457 | } 458 | 459 | extension PageViewController { 460 | 461 | func debugQuickLookObject() -> AnyObject { 462 | return self.view 463 | } 464 | 465 | var pageSize: NSSize { 466 | guard !self.overlayControls && (self.showPageControl || self.showArrowControls) else { return self.view.bounds.size } 467 | 468 | var size = self.view.bounds.size 469 | if self.showPageControl { 470 | size.height -= self.pageControl!.indicatorSize*3 471 | } 472 | if self.showArrowControls { 473 | size.width -= ArrowSize.width*6 474 | } 475 | 476 | return size 477 | } 478 | 479 | } 480 | 481 | extension Array { 482 | mutating func reverseInPlace() { 483 | self = self.reversed() 484 | } 485 | } 486 | 487 | private class _FLOPageController: NSPageController { 488 | var scrollingEnabled = true 489 | 490 | override func scrollWheel(with event: NSEvent) { 491 | guard self.scrollingEnabled else { return } 492 | super.scrollWheel(with: event) 493 | } 494 | } 495 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Florian Schliep 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 | # FLOPageViewController 2 | `FLOPageViewController` is an easy to use page view controller for macOS, similar to `UIPageViewController` for iOS. It uses `NSPageController` under the hood, but is much simpler to use and provides customizable UI elements for navigation. 3 | 4 | ![](screenshot.png) 5 | 6 | ## Usage 7 | `FLOPageViewController` is a subclass of `NSViewController` and NOT `NSPageController`. Thus, you can use a `FLOPageViewController` as your window's content view controller. 8 | 9 | Basically, you just need to pass an array of `NSViewController`s to your `FLOPageViewController` instance using the `viewControllers` property. 10 | If you're working with storyboards, you might want to use the `loadViewControllers(:from:)` convenience method. It will load `NSViewController`s from an `NSStoryboard` using the given `identifiers` array. 11 | 12 | ## Behavior + Appearance 13 | 14 | #### Page Control 15 | The `pageControl` is visible by default and supports two different styles, `.dot` and `.circle`. Set the `showPageControl` property to `false` in order to hide it. If you only want to show it when the mouse is inside, set the `pageControlRequiresMouseOver` property to `true`. 16 | 17 | To change the size, override the default value of `indicatorSize` in `FLOPageControl.swift`. 18 | 19 | #### Arrow Controls 20 | The arrow controls are hidden by default. Set the `showArrowControls` property to `true` in order to show them. Arrows will be hidden automatically depending on if there is a previous/next page. If you only want to show them when the mouse is inside, set the `arrowControlsRequireMouseOver` property to `true`. 21 | 22 | To change the size, change the `ArrowSize` constant in `FLOPageViewController.swift`. To change the thickness, override the default value of `lineWidth` in `FLOArrowControl`'s `draw(:)` method. 23 | 24 | #### Overlay 25 | By default, all controls will appear above the pages. You can change this behavior by setting the `overlayControls` property to `false`. If you do this, you may want to read the `pageSize` property to be able to size your pages appropriately. 26 | 27 | #### Colors 28 | The `tintColor` property controls the color of the page indicators and arrow controls. This color should have an alpha value of `1.0`. 29 | 30 | The (optional) `backgroundColor` property is the color displayed behind the pages. By default, no color is being displayed. 31 | 32 | #### Gestures 33 | As `FLOPageViewController` uses `NSPageController` under the hood, swipe gestures are supported by default. 34 | 35 | ## Requirements + Compatibility 36 | `FLOPageViewController` requires Swift 4.0. It is compatible with macOS 10.10 and later. I've tested it on 10.10, 10.11, 10.12 and 10.13. 37 | 38 | ## Installation 39 | Download the repository, build the Xcode project and add the `FLOPageViewController.framework` build product to your project. 40 | 41 | ## Localization 42 | `FLOPageViewController` supports R-L languages out of the box. In case of an R-L language, the `viewControllers` array will be reversed and the `pageControl`'s `selectedPage`will be set to the last index. 43 | 44 | ## To Do 45 | - [ ] Support for varying page sizes 46 | - [ ] Accessibility support 47 | - [ ] UI tests 48 | - [ ] Properties to change the appearance of the navigation controls 49 | 50 | Feel free to create pull requests based on this list or suggest even more improvements. 51 | 52 | ## Contact 53 | Florian Schliep 54 | 55 | - [github.com/floschliep](https://github.com/floschliep) 56 | - [twitter.com/floschliep](https://twitter.com/floschliep) 57 | - [floschliep.com](http://floschliep.com) 58 | 59 | ## License 60 | `FLOPageViewController` is available under the MIT license. See the LICENSE file for more info. 61 | -------------------------------------------------------------------------------- /Sample App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // FLOPageViewController 4 | // 5 | // Created by Florian Schliep on 19.01.16. 6 | // Copyright © 2016 Florian Schliep. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | 15 | 16 | func applicationDidFinishLaunching(_ aNotification: Notification) { 17 | // Insert code here to initialize your application 18 | } 19 | 20 | func applicationWillTerminate(_ aNotification: Notification) { 21 | // Insert code here to tear down your application 22 | } 23 | 24 | 25 | } 26 | 27 | -------------------------------------------------------------------------------- /Sample App/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Sample App/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 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 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 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | Default 511 | 512 | 513 | 514 | 515 | 516 | 517 | Left to Right 518 | 519 | 520 | 521 | 522 | 523 | 524 | Right to Left 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | Default 536 | 537 | 538 | 539 | 540 | 541 | 542 | Left to Right 543 | 544 | 545 | 546 | 547 | 548 | 549 | Right to Left 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 690 | 701 | 712 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | 902 | 903 | 904 | -------------------------------------------------------------------------------- /Sample App/ColoredView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColoredView.swift 3 | // FLOPageViewController 4 | // 5 | // Created by Florian Schliep on 20.01.16. 6 | // Copyright © 2016 Florian Schliep. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @IBDesignable class ColoredView: NSView { 12 | 13 | @IBInspectable var backgroundColor: NSColor = .windowBackgroundColor { 14 | didSet { 15 | self.needsDisplay = true 16 | } 17 | } 18 | 19 | override func draw(_ dirtyRect: NSRect) { 20 | self.backgroundColor.setFill() 21 | self.bounds.fill() 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Sample App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 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 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2016 Florian Schliep. All rights reserved. 29 | NSMainStoryboardFile 30 | Main 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /Sample App/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // FLOPageViewController 4 | // 5 | // Created by Florian Schliep on 19.01.16. 6 | // Copyright © 2016 Florian Schliep. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import FLOPageViewController 11 | 12 | class ViewController: NSViewController { 13 | 14 | fileprivate weak var pageViewController: PageViewController? 15 | 16 | // MARK: - NSViewController 17 | 18 | override func prepare(for segue: NSStoryboardSegue, sender: Any?) { 19 | super.prepare(for: segue, sender: sender) 20 | 21 | guard let pageViewController = segue.destinationController as? PageViewController else { return } 22 | let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil) 23 | pageViewController.loadViewControllers(["1", "2", "3"], from: storyboard) 24 | self.pageViewController = pageViewController 25 | } 26 | 27 | // MARK: - Page View Controller Settings 28 | 29 | @IBAction func didChangePageControlState(_ sender: NSButton) { 30 | self.pageViewController?.showPageControl = (sender.state == .on) 31 | } 32 | 33 | @IBAction func didChangeArrowControlState(_ sender: NSButton) { 34 | self.pageViewController?.showArrowControls = (sender.state == .on) 35 | } 36 | 37 | @IBAction func didChangePageControlMouseOverState(_ sender: NSButton) { 38 | self.pageViewController?.pageControlRequiresMouseOver = (sender.state == .on) 39 | } 40 | 41 | @IBAction func didChangeArrowControlsMouseOverState(_ sender: NSButton) { 42 | self.pageViewController?.arrowControlsRequireMouseOver = (sender.state == .on) 43 | } 44 | 45 | @IBAction func didChangeOverlayState(_ sender: NSButton) { 46 | self.pageViewController?.overlayControls = (sender.state == .on) 47 | } 48 | 49 | @IBAction func didSelectTintColor(_ sender: NSColorWell) { 50 | self.pageViewController?.tintColor = sender.color 51 | } 52 | 53 | @IBAction func didChangeCircleIndicatorState(_ sender: NSButton) { 54 | self.pageViewController?.pageIndicatorStyle = (sender.state == .on) ? .circle : .dot 55 | } 56 | 57 | @IBAction func didSelectBackgroundColor(_ sender: NSColorWell) { 58 | self.pageViewController?.backgroundColor = sender.color 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /UI Tests/FLOPageViewControllerUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FLOPageViewControllerUITests.swift 3 | // FLOPageViewControllerUITests 4 | // 5 | // Created by Florian Schliep on 19.01.16. 6 | // Copyright © 2016 Florian Schliep. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class FLOPageViewControllerUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /UI Tests/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 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/floschliep/FLOPageViewController/7068f99f7b42407683352d573e18394c1f747e7a/screenshot.png --------------------------------------------------------------------------------