├── .swift-version ├── Example ├── tvOSExample │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── App Icon & Top Shelf Image.brandassets │ │ │ ├── App Icon - Large.imagestack │ │ │ │ ├── Back.imagestacklayer │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Front.imagestacklayer │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Middle.imagestacklayer │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── App Icon - Small.imagestack │ │ │ │ ├── Back.imagestacklayer │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Front.imagestacklayer │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Middle.imagestacklayer │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Top Shelf Image.imageset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Info.plist │ ├── ViewController.swift │ └── Base.lproj │ │ └── Main.storyboard ├── SplitflapExample.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── xcshareddata │ │ └── xcschemes │ │ │ ├── SplitflapTests.xcscheme │ │ │ └── Splitflap.xcscheme │ └── project.pbxproj ├── SplitflapTests │ └── Info.plist ├── Splitflap │ ├── Info.plist │ └── Splitflap.h └── SplitflapExample │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Info.plist │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── AppDelegate.swift │ └── ViewController.swift ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── .gitignore ├── .travis.yml ├── LICENSE ├── Splitflap.podspec ├── Package.swift ├── Tests ├── TileViewTests.swift ├── XCTTestCaseTemplate.swift ├── FlapViewTests.swift ├── SplitflapDataSourceDatasourceTests.swift ├── StoryboardTests.storyboard ├── SplitflapDelegateTests.swift ├── TokenGeneratorTests.swift ├── TokenParserTests.swift └── SplitflapTests.swift ├── Sources ├── TokenParser.swift ├── SplitflapTokens.swift ├── TokenGenerator.swift ├── SplitflapDataSource.swift ├── SplitflapDelegate.swift ├── FlapViewBuilder.swift ├── TileView.swift ├── Splitflap.swift └── FlapView.swift ├── CHANGELOG.md └── README.md /.swift-version: -------------------------------------------------------------------------------- 1 | 4.2 2 | -------------------------------------------------------------------------------- /Example/tvOSExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/SplitflapExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | *.xccheckout 19 | Carthage/** 20 | -------------------------------------------------------------------------------- /Example/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/SplitflapExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/tvOSExample/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "landscape", 5 | "idiom" : "tv", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "9.0", 8 | "scale" : "1x" 9 | } 10 | ], 11 | "info" : { 12 | "version" : 1, 13 | "author" : "xcode" 14 | } 15 | } -------------------------------------------------------------------------------- /Example/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Example/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode10 3 | before_install: 4 | - gem install xcpretty 5 | script: 6 | - cd Example 7 | - xcodebuild -version 8 | - xcodebuild -project SplitflapExample.xcodeproj -scheme SplitflapTests -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 8" -configuration Release GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES ONLY_ACTIVE_ARCH=YES test | xcpretty -c 9 | after_success: 10 | - bash <(curl -s https://codecov.io/bash) 11 | -------------------------------------------------------------------------------- /Example/tvOSExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // tvOSExample 4 | // 5 | // Created by Yannick LORIOT on 24/11/15. 6 | // Copyright © 2015 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /Example/tvOSExample/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "size" : "1280x768", 5 | "idiom" : "tv", 6 | "filename" : "App Icon - Large.imagestack", 7 | "role" : "primary-app-icon" 8 | }, 9 | { 10 | "size" : "400x240", 11 | "idiom" : "tv", 12 | "filename" : "App Icon - Small.imagestack", 13 | "role" : "primary-app-icon" 14 | }, 15 | { 16 | "size" : "1920x720", 17 | "idiom" : "tv", 18 | "filename" : "Top Shelf Image.imageset", 19 | "role" : "top-shelf-image" 20 | } 21 | ], 22 | "info" : { 23 | "version" : 1, 24 | "author" : "xcode" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Example/SplitflapTests/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 | -------------------------------------------------------------------------------- /Example/Splitflap/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 4.1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/tvOSExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 4.1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | arm64 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present Yannick Loriot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Splitflap.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'Splitflap' 3 | s.version = '4.1.0' 4 | s.license = 'MIT' 5 | s.summary = 'A simple split-flap display for your Swift applications' 6 | s.description = <<-DESC 7 | Splitflap is a simple to use component to present changeable alphanumeric text like often used as a public transport timetable in airports or railway stations or with some flip clocks. 8 | DESC 9 | s.homepage = 'https://github.com/yannickl/Splitflap.git' 10 | s.social_media_url = 'https://twitter.com/yannickloriot' 11 | s.authors = { 'Yannick Loriot' => 'contact@yannickloriot.com' } 12 | s.source = { :git => 'https://github.com/yannickl/Splitflap.git', :tag => s.version } 13 | s.screenshot = 'http://yannickloriot.com/resources/splitflap-logo.gif' 14 | 15 | s.ios.deployment_target = '8.0' 16 | s.tvos.deployment_target = '9.0' 17 | 18 | s.ios.frameworks = 'UIKit', 'QuartzCore' 19 | s.tvos.frameworks = 'UIKit', 'QuartzCore' 20 | 21 | s.source_files = 'Sources/*.swift' 22 | s.requires_arc = true 23 | end 24 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Splitflap", 8 | platforms: [ 9 | .macOS(.v10_12), 10 | .iOS(.v8), 11 | .tvOS(.v9) 12 | ], 13 | products: [ 14 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 15 | .library( 16 | name: "Splitflap", 17 | targets: ["Splitflap"]), 18 | ], 19 | dependencies: [ 20 | // Dependencies declare other packages that this package depends on. 21 | // .package(url: /* package url */, from: "1.0.0"), 22 | ], 23 | targets: [ 24 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 25 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 26 | .target( 27 | name: "Splitflap", 28 | dependencies: [], 29 | path: "Sources" 30 | ) 31 | ] 32 | ) 33 | -------------------------------------------------------------------------------- /Tests/TileViewTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Splitflap 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import UIKit 28 | import XCTest 29 | 30 | class TileViewTests: XCTTestCaseTemplate { 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Example/SplitflapExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Tests/XCTTestCaseTemplate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Splitflap 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import UIKit 28 | import XCTest 29 | 30 | class XCTTestCaseTemplate: XCTestCase { 31 | override func setUp() { 32 | super.setUp() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Tests/FlapViewTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Splitflap 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import UIKit 28 | import XCTest 29 | 30 | class FlapViewTests: XCTTestCaseTemplate { 31 | func testInitWithCoder() { 32 | let storyboard = UIStoryboard(name: "StoryboardTests", bundle: Bundle(for: type(of: self))) 33 | 34 | let vc = storyboard.instantiateInitialViewController() 35 | 36 | XCTAssertNotNil(vc) 37 | XCTAssertEqual(vc?.view.subviews.count, 4) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Example/Splitflap/Splitflap.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Splitflap 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | #import 28 | 29 | //! Project version number for Splitflap. 30 | FOUNDATION_EXPORT double SplitflapVersionNumber; 31 | 32 | //! Project version string for Splitflap. 33 | FOUNDATION_EXPORT const unsigned char SplitflapVersionString[]; 34 | 35 | // In this header, you should import all the public headers of your framework using statements like #import 36 | 37 | 38 | -------------------------------------------------------------------------------- /Example/SplitflapExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 4.1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Example/SplitflapExample/Base.lproj/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 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Example/SplitflapExample.xcodeproj/xcshareddata/xcschemes/SplitflapTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 16 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 40 | 41 | 42 | 43 | 49 | 50 | 52 | 53 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /Example/SplitflapExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SplitflapExample 4 | // 5 | // Created by Yannick LORIOT on 10/11/15. 6 | // Copyright © 2015 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Example/tvOSExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // tvOSExample 4 | // 5 | // Created by Yannick LORIOT on 24/11/15. 6 | // Copyright © 2015 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController, SplitflapDataSource, SplitflapDelegate { 12 | @IBOutlet weak var splitflap: Splitflap! 13 | @IBOutlet weak var actionButton: UIButton! 14 | 15 | private let words = ["Hey you", "Bonsoir", "12h15", "Arrival"] 16 | private var currentIndex = 0 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | splitflap.datasource = self 22 | splitflap.delegate = self 23 | splitflap.reload() 24 | } 25 | 26 | override func viewDidAppear(_ animated: Bool) { 27 | super.viewDidAppear(animated) 28 | 29 | updateSplitFlapAction(actionButton) 30 | } 31 | 32 | // MARK: - Action Methods 33 | 34 | @IBAction func updateSplitFlapAction(_ sender: AnyObject) { 35 | splitflap.setText(words[currentIndex], animated: true, completionBlock: { 36 | print("Display finished!") 37 | }) 38 | 39 | currentIndex = (currentIndex + 1) % words.count 40 | 41 | updateButtonWithTitle(words[currentIndex]) 42 | } 43 | 44 | private func updateButtonWithTitle(_ title: String) { 45 | actionButton.setTitle("Say \(words[currentIndex])!", for: .normal) 46 | } 47 | 48 | // MARK: - Splitflap DataSource Methods 49 | 50 | func numberOfFlapsInSplitflap(_ splitflap: Splitflap) -> Int { 51 | return 7 52 | } 53 | 54 | func tokensInSplitflap(_ splitflap: Splitflap, flap: Int) -> [String] { 55 | return SplitflapTokens.AlphanumericAndSpace 56 | } 57 | 58 | // MARK: - Splitflap Delegate Methods 59 | 60 | func splitflap(splitflap: Splitflap, rotationDurationForFlapAtIndex index: Int) -> Double { 61 | return 0.2 62 | } 63 | 64 | func splitflap(splitflap: Splitflap, builderForFlapAtIndex index: Int) -> FlapViewBuilder { 65 | return FlapViewBuilder { builder in 66 | builder.backgroundColor = UIColor.black 67 | builder.cornerRadius = 5 68 | builder.font = UIFont(name: "Courier", size: 80) 69 | builder.textAlignment = .center 70 | builder.textColor = UIColor.white 71 | builder.lineColor = UIColor.darkGray 72 | } 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /Example/SplitflapExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SplitflapExample 4 | // 5 | // Created by Yannick LORIOT on 10/11/15. 6 | // Copyright © 2015 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController, SplitflapDataSource, SplitflapDelegate { 12 | @IBOutlet weak var splitflap: Splitflap! 13 | @IBOutlet weak var actionButton: UIButton! 14 | 15 | fileprivate let words = ["Hey you", "Bonsoir", "12h15", "Arrival"] 16 | fileprivate var currentIndex = 0 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | splitflap.datasource = self 22 | splitflap.delegate = self 23 | splitflap.reload() 24 | } 25 | 26 | override func viewDidAppear(_ animated: Bool) { 27 | super.viewDidAppear(animated) 28 | 29 | updateSplitFlapAction(actionButton) 30 | } 31 | 32 | // MARK: - Action Methods 33 | 34 | @IBAction func updateSplitFlapAction(_ sender: AnyObject) { 35 | splitflap.setText(words[currentIndex], animated: true, completionBlock: { 36 | print("Display finished!") 37 | }) 38 | 39 | currentIndex = (currentIndex + 1) % words.count 40 | 41 | updateButtonWithTitle(words[currentIndex]) 42 | } 43 | 44 | fileprivate func updateButtonWithTitle(_ title: String) { 45 | actionButton.setTitle("Say \(words[currentIndex])!", for: UIControl.State()) 46 | } 47 | 48 | // MARK: - Splitflap DataSource Methods 49 | 50 | func numberOfFlapsInSplitflap(_ splitflap: Splitflap) -> Int { 51 | return 7 52 | } 53 | 54 | func tokensInSplitflap(_ splitflap: Splitflap, flap: Int) -> [String] { 55 | if flap == 0 { 56 | return SplitflapTokens.Alphanumeric 57 | } 58 | return SplitflapTokens.AlphanumericAndSpace 59 | } 60 | 61 | // MARK: - Splitflap Delegate Methods 62 | 63 | func splitflap(_ splitflap: Splitflap, rotationDurationForFlapAtIndex index: Int) -> Double { 64 | return 0.2 65 | } 66 | 67 | func splitflap(_ splitflap: Splitflap, builderForFlapAtIndex index: Int) -> FlapViewBuilder { 68 | return FlapViewBuilder { builder in 69 | builder.backgroundColor = .black 70 | builder.cornerRadius = 5 71 | builder.font = UIFont(name: "Courier", size: 50) 72 | builder.textAlignment = .center 73 | builder.textColor = .white 74 | builder.lineColor = .darkGray 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Tests/SplitflapDataSourceDatasourceTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Splitflap 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import UIKit 28 | import XCTest 29 | 30 | class SplitflapDataSourceDatasourceTests: XCTTestCaseTemplate { 31 | func testDefaultImplementation() { 32 | class DataSourceMock: SplitflapDataSource { 33 | func numberOfFlapsInSplitflap(_ splitflap: Splitflap) -> Int { 34 | return 0 35 | } 36 | } 37 | 38 | let datasourceMock = DataSourceMock() 39 | let splitflap = Splitflap() 40 | 41 | XCTAssertEqual(datasourceMock.numberOfFlapsInSplitflap(splitflap), 0) 42 | XCTAssertEqual(datasourceMock.tokensInSplitflap(splitflap, flap:0), SplitflapTokens.Alphanumeric) 43 | } 44 | 45 | func testCustomImplementation() { 46 | class DataSourceMock: SplitflapDataSource { 47 | func numberOfFlapsInSplitflap(_ splitflap: Splitflap) -> Int { 48 | return 5 49 | } 50 | 51 | func tokensInSplitflap(_ splitflap: Splitflap, flap: Int) -> [String] { 52 | return SplitflapTokens.Numeric 53 | } 54 | } 55 | 56 | let datasourceMock = DataSourceMock() 57 | let splitflap = Splitflap() 58 | 59 | XCTAssertEqual(datasourceMock.numberOfFlapsInSplitflap(splitflap), 5) 60 | XCTAssertEqual(datasourceMock.tokensInSplitflap(splitflap, flap:0), SplitflapTokens.Numeric) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/TokenParser.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Splitflap 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import Foundation 28 | 29 | /** 30 | The TokenParser parses a given string into a token list. 31 | */ 32 | final class TokenParser { 33 | let tokens: [String: Bool] 34 | 35 | required init(tokens: [String]) { 36 | // Transforms a list into a dictionary to improve the search 37 | self.tokens = tokens.reduce([:]) { (dict, elem) in 38 | var dict = dict 39 | dict[elem] = true 40 | 41 | return dict 42 | } 43 | } 44 | 45 | // MARK: - Parsing Inputs 46 | 47 | /** 48 | Parse a given string to find tokens. 49 | 50 | - parameter string: A string to parse. 51 | - returns: A list of token. An empty list if the given string does not 52 | contains token. 53 | */ 54 | func parseString(_ string: String) -> [String] { 55 | var tokensFound: [String] = [] 56 | 57 | var word: String = "" 58 | 59 | for character in string { 60 | word += String(character) 61 | 62 | if isToken(word) { 63 | tokensFound.append(word) 64 | 65 | word = "" 66 | } 67 | } 68 | 69 | return tokensFound 70 | } 71 | 72 | // MARK: - Checking Token Validity 73 | 74 | /** 75 | Checks whether the given word is a token and returns true if it the case. 76 | 77 | - parameter word: A word as String. 78 | */ 79 | fileprivate func isToken(_ word: String) -> Bool { 80 | return tokens[word] != nil 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/SplitflapTokens.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Splitflap 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import Foundation 28 | 29 | /** 30 | The SplitflapTokens defines a collection of token string ready to use for 31 | split-flap view. 32 | 33 | A token is a character, a symbol or a text that is displays by the flap view. A 34 | flap view manages a stack a token in order to display them in the good order when 35 | it needs to animate its token change. 36 | */ 37 | open class SplitflapTokens { 38 | /// Numeric characters. 39 | public static let Numeric = (0 ... 9).map { String($0) } 40 | 41 | /// Alphabetic characters (lower and upper cases). 42 | public static let Alphabetic = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".map { String($0) } 43 | 44 | /// Combination of alphabetic (lower and upper cases) and numeric characters. 45 | public static let Alphanumeric = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".map { String($0) } 46 | 47 | /// Combination of alphabetic (lower and upper cases) and numeric characters plus the space. 48 | public static let AlphanumericAndSpace = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".map { String($0) } 49 | 50 | /// The 12-hour clock characters (from 1 to 12). 51 | public static let TwelveHourClock = (1 ... 12).map { String($0) } 52 | 53 | /// The 24-hour clock characters (from 00 to 23). 54 | public static let TwentyFourHourClock = (0 ... 23).map { String(format:"%02d", $0) } 55 | 56 | /// The minute/second characters (from 00 to 59). 57 | public static let MinuteAndSecond = (0 ... 59).map { String(format:"%02d", $0) } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/TokenGenerator.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Splitflap 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import Foundation 28 | 29 | /** 30 | A TokenGenerator helps the flap view by choosing the right token rescpecting the 31 | initial order. 32 | */ 33 | final class TokenGenerator: IteratorProtocol { 34 | typealias Element = String 35 | 36 | let tokens: [String] 37 | 38 | required init(tokens: [String]) { 39 | self.tokens = tokens 40 | } 41 | 42 | // MARK: - Implementing GeneratorType 43 | 44 | /// Current element index 45 | fileprivate var currentIndex = 0 46 | 47 | /// Returns the current element of the generator, nil otherwise. 48 | var currentElement: Element? { 49 | get { 50 | if currentIndex < tokens.count { 51 | return tokens[currentIndex] 52 | } 53 | 54 | return nil 55 | } 56 | set(newValue) { 57 | if let value = newValue { 58 | currentIndex = tokens.firstIndex(of: value) ?? currentIndex 59 | } 60 | else { 61 | currentIndex = 0 62 | } 63 | } 64 | } 65 | 66 | /// Advance to the next element and return it, or `nil` if no next. 67 | /// element exists. 68 | @discardableResult 69 | func next() -> Element? { 70 | let tokenCount = tokens.count 71 | 72 | guard tokenCount > 0 else { 73 | return nil 74 | } 75 | 76 | currentIndex = (currentIndex + 1) % tokens.count 77 | 78 | return tokens[currentIndex] 79 | } 80 | 81 | // MARK: - Convenience Methods 82 | 83 | /// Returns the first token, or `nil` if no token. 84 | var firstToken: String? { 85 | get { 86 | return tokens.first 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/SplitflapDataSource.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Splitflap 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import UIKit 28 | 29 | /** 30 | The SplitflapDataSource protocol must be adopted by an object that mediates 31 | between a Splitflap object and your application’s data model for that split-flap 32 | view. The data source provides the split-flap view with the number of flaps for 33 | displaying the split-flap view data. 34 | */ 35 | public protocol SplitflapDataSource: class { 36 | // MARK: - Providing Counts for the Splitflap View 37 | 38 | /** 39 | Called by the split-flap view when it needs the number of flaps. 40 | - parameter splitflap: The split-flap view requesting the data. 41 | - returns: The number of flaps. 42 | */ 43 | func numberOfFlapsInSplitflap(_ splitflap: Splitflap) -> Int 44 | 45 | // MARK: - Managing Supported Tokens for the Splitflap Components 46 | 47 | /** 48 | Called by the split-flap view when it needs to update the token strings that 49 | each flaps must display. 50 | 51 | If you don't implement this method the split-flap view will use the 52 | `Alphanumeric` token list. 53 | - parameter splitflap: The split-flap view requesting the data. 54 | - returns: A list of token string used by each flaps to manage their stack of 55 | token. 56 | */ 57 | func tokensInSplitflap(_ splitflap: Splitflap, flap: Int) -> [String] 58 | } 59 | 60 | /// Default implementation of SplitflapDataSource 61 | public extension SplitflapDataSource { 62 | /// Returns by default the Alphanumeric token list. 63 | func tokensInSplitflap(_ splitflap: Splitflap, flap: Int) -> [String] { 64 | return SplitflapTokens.Alphanumeric 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Tests/StoryboardTests.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 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ## [Version 4.1.0](https://github.com/yannickl/Splitflap/releases/tag/4.1.0) 4 | *Released on 2018-11-24.* 5 | 6 | - [FIX] Don't show lines when color is set to nil 7 | 8 | ## [Version 4.0.0](https://github.com/yannickl/Splitflap/releases/tag/4.0.0) 9 | *Released on 2018-09-25.* 10 | 11 | **Swift 4.2 Supports** 12 | 13 | ## [Version 3.0.1](https://github.com/yannickl/Splitflap/releases/tag/3.0.1) 14 | *Released on 2017-12-10.* 15 | 16 | - [FIX] Updating to last swift syntax. 17 | 18 | ## [Version 3.0.0](https://github.com/yannickl/Splitflap/releases/tag/3.0.0) 19 | *Released on 2017-09-22.* 20 | 21 | **Swift 4 Supports** 22 | 23 | ## [Version 2.1.0](https://github.com/yannickl/Splitflap/releases/tag/2.1.0) 24 | *Released on 2017-04-01.* 25 | 26 | **Swift 3.1 Supports** 27 | 28 | ## [Version 2.0.0](https://github.com/yannickl/Splitflap/releases/tag/2.0.0) 29 | *Released on 2016-09-15.* 30 | 31 | **Swift 3 Supports** 32 | 33 | - [ADD] Swift Package Manager supports 34 | 35 | ## [Version 1.1.1](https://github.com/yannickl/Splitflap/releases/tag/1.1.1) 36 | Released on 2015-12-14. 37 | 38 | - [FIX] flap animation continue when new text is set [#2](https://github.com/yannickl/Splitflap/issues/2) 39 | 40 | ## [Version 1.1.0](https://github.com/yannickl/Splitflap/releases/tag/1.1.0) 41 | Released on 2015-11-27. 42 | 43 | - [ADD] `TwelveHourClock`, `TwentyFourHourClock` and `MinuteAndSecond` tokens 44 | - [ADD] tvOS supports 45 | - [FIX] Keep text displayed when reload data 46 | 47 | ## [Version 1.0.1](https://github.com/yannickl/Splitflap/releases/tag/1.0.1) 48 | Released on 2015-11-17. 49 | 50 | - [FIX] Builder properties access control ([#1](https://github.com/yannickl/Splitflap/issues/1)) 51 | 52 | ## [Version 1.0.0](https://github.com/yannickl/Splitflap/releases/tag/1.0.0) 53 | Released on 2015-11-13. 54 | 55 | - [UPDATE] Rename `supportedTokensInSplitflap:` method to `tokensInSplitflap:` 56 | - [ADD] `setText:animated:completionBlock:` method to know when an animation finished 57 | - [ADD] Test cases 58 | 59 | ## [Version 0.2.0](https://github.com/yannickl/Splitflap/releases/tag/0.2.0) 60 | Released on 2015-11-12. 61 | 62 | - [FIX] Parse characters and words 63 | - [ADD] `FlapViewBuilder` to make flap configuration easier 64 | - [ADD] `splitflap:builderForFlapAtIndex:` method to customize the each flap individually: 65 | - `backgroundColor`, `cornerRadius`, `font`, `textAlignment`, `textColor`, `lineColor` 66 | 67 | ## [Version 0.1.0](https://github.com/yannickl/Splitflap/releases/tag/0.1.0) 68 | Released on 2015-11-11. 69 | 70 | - `flapSpacing` property to configure the spacing between flaps 71 | - `supportedTokensInSplitflap:` method to define the "characters" used by flaps 72 | - `numberOfFlapsInSplitflap:` to set the number of flaps 73 | - `splitflap(splitflap:rotationDurationForFlapAtIndex:` method to change the rotation duration of each flaps 74 | - Cocoapods support 75 | - Carthage support 76 | -------------------------------------------------------------------------------- /Tests/SplitflapDelegateTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Splitflap 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import UIKit 28 | import XCTest 29 | 30 | class SplitflapDelegateTests: XCTTestCaseTemplate { 31 | func testDefaultImplementation() { 32 | class DelegateMock: SplitflapDelegate {} 33 | 34 | let delegateMock = DelegateMock() 35 | let splitflap = Splitflap() 36 | 37 | XCTAssertEqual(delegateMock.splitflap(splitflap, rotationDurationForFlapAtIndex: 0), 0.2) 38 | } 39 | 40 | func testCustomImplementation() { 41 | class DelegateMock: SplitflapDelegate { 42 | func splitflap(_ splitflap: Splitflap, rotationDurationForFlapAtIndex index: Int) -> Double { 43 | return 1 44 | } 45 | 46 | func splitflap(_ splitflap: Splitflap, builderForFlapAtIndex index: Int) -> FlapViewBuilder { 47 | return FlapViewBuilder { builder in 48 | builder.backgroundColor = UIColor.red 49 | builder.cornerRadius = 0 50 | builder.font = UIFont(name: "HelveticaNeue", size: 50) 51 | builder.textAlignment = .left 52 | builder.textColor = UIColor.yellow 53 | builder.lineColor = UIColor.green 54 | } 55 | } 56 | } 57 | 58 | let delegateMock = DelegateMock() 59 | let splitflap = Splitflap() 60 | 61 | XCTAssertEqual(delegateMock.splitflap(splitflap, rotationDurationForFlapAtIndex: 0), 1) 62 | 63 | let builder = delegateMock.splitflap(splitflap, builderForFlapAtIndex: 0) 64 | XCTAssertEqual(builder.backgroundColor, UIColor.red) 65 | XCTAssertEqual(builder.cornerRadius, 0) 66 | XCTAssertEqual(builder.font, UIFont(name: "HelveticaNeue", size: 50)) 67 | XCTAssertEqual(builder.textAlignment, NSTextAlignment.left) 68 | XCTAssertEqual(builder.textColor, UIColor.yellow) 69 | XCTAssertEqual(builder.lineColor, UIColor.green) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Sources/SplitflapDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Splitflap 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import UIKit 28 | 29 | /** 30 | The delegate of a Splitflap object should adopt this protocol and implement at 31 | least some of its methods to provide the split-flap view with the data it needs 32 | to construct itself. 33 | */ 34 | public protocol SplitflapDelegate: class { 35 | // MARK: - Setting the Rotation Duration of Flaps 36 | 37 | /** 38 | Called by the split-flap when it needs to rotate a flap at a given index. 39 | 40 | The default value for each flap is 0.2 seconds. 41 | 42 | - parameter splitflap: The split-flap view requesting the data. 43 | - parameter index: A zero-indexed number identifying a flap. The index starts 44 | at 0 for the leftmost flap. 45 | - returns: The duration of the flap rotation in seconds. 46 | */ 47 | func splitflap(_ splitflap: Splitflap, rotationDurationForFlapAtIndex index: Int) -> Double 48 | 49 | // MARK: - Configuring the Label of Flaps 50 | 51 | /** 52 | Called by the split-flap when it needs to create its flap subviews. 53 | 54 | - parameter splitflap: The split-flap view requesting the data. 55 | - parameter index: A zero-indexed number identifying a flap. The index starts 56 | at 0 for the leftmost flap. 57 | - returns: A FlapView builder object to create custom flaps. 58 | */ 59 | func splitflap(_ splitflap: Splitflap, builderForFlapAtIndex index: Int) -> FlapViewBuilder 60 | } 61 | 62 | /// Default implementation of SplitflapDelegate 63 | public extension SplitflapDelegate { 64 | /// Returns by default 0.2 seconds. 65 | func splitflap(_ splitflap: Splitflap, rotationDurationForFlapAtIndex index: Int) -> Double { 66 | return 0.2 67 | } 68 | 69 | /// Returns the default FlapViewBuilder configuration by default. 70 | func splitflap(_ splitflap: Splitflap, builderForFlapAtIndex index: Int) -> FlapViewBuilder { 71 | return FlapViewBuilder() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Example/SplitflapExample.xcodeproj/xcshareddata/xcschemes/Splitflap.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 | -------------------------------------------------------------------------------- /Tests/TokenGeneratorTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Splitflap 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import UIKit 28 | import XCTest 29 | 30 | class TokenGeneratorTests: XCTTestCaseTemplate { 31 | func testCurrentElement() { 32 | let emptyGenerator = TokenGenerator(tokens: []) 33 | XCTAssertNil(emptyGenerator.currentElement) 34 | 35 | let oneElementGenerator = TokenGenerator(tokens: ["a"]) 36 | XCTAssertEqual(oneElementGenerator.currentElement, "a") 37 | 38 | let nElementsGenerator = TokenGenerator(tokens: ["a", "b", "c", "d"]) 39 | XCTAssertEqual(nElementsGenerator.currentElement, "a") 40 | 41 | // Set current element 42 | nElementsGenerator.currentElement = "c" 43 | XCTAssertEqual(nElementsGenerator.currentElement, "c") 44 | 45 | // Set invalid element 46 | nElementsGenerator.currentElement = "l" 47 | XCTAssertEqual(nElementsGenerator.currentElement, "c") 48 | 49 | // Set no element 50 | nElementsGenerator.currentElement = nil 51 | XCTAssertEqual(nElementsGenerator.currentElement, "a") 52 | } 53 | 54 | func testNext() { 55 | let emptyGenerator = TokenGenerator(tokens: []) 56 | XCTAssertNil(emptyGenerator.currentElement) 57 | 58 | let nilElement = emptyGenerator.next() 59 | XCTAssertNil(nilElement) 60 | XCTAssertNil(emptyGenerator.currentElement) 61 | 62 | let oneElementGenerator = TokenGenerator(tokens: ["a"]) 63 | let aElement = oneElementGenerator.next() 64 | XCTAssertEqual(aElement, "a") 65 | XCTAssertEqual(oneElementGenerator.currentElement, "a") 66 | 67 | let twoElementsGenerator = TokenGenerator(tokens: ["a", "b"]) 68 | var twoElement = twoElementsGenerator.next() 69 | XCTAssertEqual(twoElement, "b") 70 | XCTAssertEqual(twoElementsGenerator.currentElement, "b") 71 | 72 | twoElement = twoElementsGenerator.next() 73 | XCTAssertEqual(twoElement, "a") 74 | XCTAssertEqual(twoElementsGenerator.currentElement, "a") 75 | } 76 | 77 | func testFirstToken() { 78 | let emptyGenerator = TokenGenerator(tokens: []) 79 | XCTAssertNil(emptyGenerator.firstToken) 80 | 81 | let oneElementGenerator = TokenGenerator(tokens: ["a"]) 82 | XCTAssertEqual(oneElementGenerator.firstToken, "a") 83 | 84 | let nElementsGenerator = TokenGenerator(tokens: ["a", "b", "c", "d"]) 85 | nElementsGenerator.next() 86 | XCTAssertEqual(nElementsGenerator.firstToken, "a") 87 | } 88 | } -------------------------------------------------------------------------------- /Tests/TokenParserTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Splitflap 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import UIKit 28 | import XCTest 29 | 30 | class TokenParserTests: XCTTestCaseTemplate { 31 | func testParseEmptyString() { 32 | let parser = TokenParser(tokens: []) 33 | 34 | XCTAssertEqual(parser.parseString(""), []) 35 | XCTAssertEqual(parser.parseString(" "), []) 36 | XCTAssertEqual(parser.parseString("hello"), []) 37 | } 38 | 39 | func testParseOneSimpleTokenString() { 40 | let parser = TokenParser(tokens: ["a"]) 41 | 42 | XCTAssertEqual(parser.parseString(""), []) 43 | XCTAssertEqual(parser.parseString("hello"), []) 44 | XCTAssertEqual(parser.parseString("a"), ["a"]) 45 | XCTAssertEqual(parser.parseString("aaaaa"), ["a", "a", "a", "a", "a"]) 46 | XCTAssertEqual(parser.parseString("abracadabra"), ["a"]) 47 | XCTAssertEqual(parser.parseString("ba_alababa"), []) 48 | } 49 | 50 | func testParseAlphabeticTokenString() { 51 | let parser = TokenParser(tokens: SplitflapTokens.Alphabetic) 52 | 53 | XCTAssertEqual(parser.parseString(""), []) 54 | XCTAssertEqual(parser.parseString("hello"), ["h", "e", "l", "l", "o"]) 55 | XCTAssertEqual(parser.parseString("Hello"), ["H", "e", "l", "l", "o"]) 56 | XCTAssertEqual(parser.parseString("h0ello"), ["h"]) 57 | XCTAssertEqual(parser.parseString(" hello"), []) 58 | } 59 | 60 | func testParseOneComplexTokenString() { 61 | let parser = TokenParser(tokens: ["foo"]) 62 | 63 | XCTAssertEqual(parser.parseString(""), []) 64 | XCTAssertEqual(parser.parseString("f"), []) 65 | XCTAssertEqual(parser.parseString("fo"), []) 66 | XCTAssertEqual(parser.parseString("foo"), ["foo"]) 67 | XCTAssertEqual(parser.parseString("foof"), ["foo"]) 68 | XCTAssertEqual(parser.parseString("foofo"), ["foo"]) 69 | XCTAssertEqual(parser.parseString("foofoo"), ["foo", "foo"]) 70 | XCTAssertEqual(parser.parseString("footfoo"), ["foo"]) 71 | XCTAssertEqual(parser.parseString("playfoot"), []) 72 | } 73 | 74 | func testParseComplexTokenString() { 75 | let parser = TokenParser(tokens: ["foo", "bar", "pop"]) 76 | 77 | XCTAssertEqual(parser.parseString(""), []) 78 | XCTAssertEqual(parser.parseString("foo"), ["foo"]) 79 | XCTAssertEqual(parser.parseString("foopop"), ["foo", "pop"]) 80 | XCTAssertEqual(parser.parseString("popbarfoopop"), ["pop", "bar", "foo", "pop"]) 81 | XCTAssertEqual(parser.parseString("afoo"), []) 82 | } 83 | } -------------------------------------------------------------------------------- /Sources/FlapViewBuilder.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Splitflap 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import UIKit 28 | 29 | /** 30 | The FlapViewBuilder aims to create a simple configuration object for the Flap 31 | view. It allows you to define the *backgroundColor*, the *font*, the 32 | *cornerRadius*, etc. It is based on the builder pattern (for more information 33 | take a look at https://github.com/ochococo/Design-Patterns-In-Swift#-builder) 34 | */ 35 | public final class FlapViewBuilder { 36 | // MARK: - Customizing Flaps 37 | 38 | /** 39 | The builder block. 40 | 41 | The block gives a reference of builder you can configure. 42 | */ 43 | public typealias FlapViewBuilderBlock = (_ builder: FlapViewBuilder) -> Void 44 | 45 | /** 46 | The flap's background color. 47 | 48 | If the value is nil, it results in a transparent background color. The 49 | default value is black. 50 | */ 51 | public var backgroundColor: UIColor? = .black 52 | 53 | /** 54 | The radius to use when drawing rounded corners for the flap’s background. 55 | 56 | Setting the radius to a value greater than 0.0 causes the flap to begin 57 | drawing rounded corners on its background. 58 | 59 | The default value of this property is 5.0. 60 | */ 61 | public var cornerRadius: CGFloat = 5 62 | 63 | /** 64 | The font of the flap. 65 | 66 | If the font is nil, the flap uses its internal default *Courier* font. 67 | 68 | The default value of this property is nil. 69 | */ 70 | public var font: UIFont? 71 | 72 | /** 73 | The technique to use for aligning the text. 74 | 75 | The default value of this property is NSTextAlignment.Center. 76 | */ 77 | public var textAlignment: NSTextAlignment = .center 78 | 79 | /** 80 | The color of the text. 81 | 82 | Uses the white color by default. 83 | */ 84 | public var textColor: UIColor = UIColor.white 85 | 86 | /** 87 | The flap's middle line color. 88 | 89 | If the value is nil, it results in a transparent line color. The default 90 | value is dark gray. 91 | */ 92 | public var lineColor: UIColor? = UIColor.darkGray 93 | 94 | /** 95 | The flap's middle line height relative to the default. The default value 96 | id 1.0. 97 | */ 98 | public var flipPointHeightFactor: CGFloat = 1.0 99 | 100 | /** 101 | The on/off toggle for a label's property "adjustsFontSizeToFitWidth" inside every flap 102 | By defaul the value is false, but it could be changed while initing a builder 103 | */ 104 | public var adjustsFontSizeToFitWidth: Bool = false 105 | // MARK: - Initializing a Flap View 106 | 107 | /** 108 | Initialize a FlapView builder with default values. 109 | */ 110 | public init() {} 111 | 112 | /** 113 | Initialize a FlapView builder with default values. 114 | 115 | - parameter buildBlock: A FlapView builder block to configure itself. 116 | */ 117 | public init(buildBlock: FlapViewBuilderBlock) { 118 | buildBlock(self) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Example/SplitflapExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 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 | -------------------------------------------------------------------------------- /Example/tvOSExample/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 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Tests/SplitflapTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Splitflap 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import UIKit 28 | import XCTest 29 | 30 | class SplitflapTests: XCTTestCaseTemplate { 31 | func testDefaultSplitflap() { 32 | let splitflap = Splitflap() 33 | 34 | XCTAssertNil(splitflap.datasource) 35 | XCTAssertNil(splitflap.delegate) 36 | XCTAssertEqual(splitflap.numberOfFlaps, 0) 37 | XCTAssertEqual(splitflap.tokens, []) 38 | XCTAssertEqual(splitflap.flapSpacing, 2) 39 | XCTAssertNil(splitflap.text) 40 | 41 | // 'didMoveToWindow' calls the 'reload' method 42 | splitflap.didMoveToWindow() 43 | 44 | XCTAssertNil(splitflap.datasource) 45 | XCTAssertNil(splitflap.delegate) 46 | XCTAssertEqual(splitflap.numberOfFlaps, 0) 47 | XCTAssertEqual(splitflap.tokens, SplitflapTokens.Alphanumeric) 48 | XCTAssertEqual(splitflap.flapSpacing, 2) 49 | XCTAssertNil(splitflap.text) 50 | } 51 | 52 | func testText() { 53 | class DataSourceMock: SplitflapDataSource { 54 | func numberOfFlapsInSplitflap(_ splitflap: Splitflap) -> Int { 55 | return 5 56 | } 57 | } 58 | 59 | // By default, length is 0 60 | let splitflap = Splitflap() 61 | XCTAssertNil(splitflap.text) 62 | 63 | splitflap.text = "Alongtext" 64 | XCTAssertNil(splitflap.text) 65 | 66 | // String with length 5 67 | let datasourceMock = DataSourceMock() 68 | splitflap.datasource = datasourceMock 69 | splitflap.reload() 70 | 71 | XCTAssertNil(splitflap.text) 72 | 73 | splitflap.text = "hello" 74 | XCTAssertEqual(splitflap.text, "hello") 75 | 76 | splitflap.text = "helloworld" 77 | XCTAssertEqual(splitflap.text, "hello") 78 | 79 | splitflap.text = "$invalid!" 80 | XCTAssertNil(splitflap.text) 81 | } 82 | 83 | func testSetText() { 84 | class DataSourceMock: SplitflapDataSource { 85 | func numberOfFlapsInSplitflap(_ splitflap: Splitflap) -> Int { 86 | return 9 87 | } 88 | } 89 | 90 | class DelegateMock: SplitflapDelegate { 91 | private func splitflap(splitflap: Splitflap, rotationDurationForFlapAtIndex index: Int) -> Double { 92 | return 0.01 93 | } 94 | } 95 | 96 | // By default, length is 0 97 | let splitflap = Splitflap() 98 | XCTAssertNil(splitflap.text) 99 | 100 | splitflap.setText("Alongtext", animated: true) 101 | XCTAssertNil(splitflap.text) 102 | 103 | // String with length 9 104 | let datasourceMock = DataSourceMock() 105 | let delegateMock = DelegateMock() 106 | splitflap.datasource = datasourceMock 107 | splitflap.delegate = delegateMock 108 | splitflap.reload() 109 | 110 | var expect = expectation(description: "Block completed immediatly when no animation") 111 | splitflap.setText("Alongtext", animated: false, completionBlock: { 112 | expect.fulfill() 113 | }) 114 | waitForExpectations(timeout: 0.1, handler:nil) 115 | 116 | expect = expectation(description: "Block animation completed") 117 | splitflap.setText("Alongtext", animated: true, completionBlock: { 118 | expect.fulfill() 119 | }) 120 | XCTAssertEqual(splitflap.text, "Alongtext") 121 | waitForExpectations(timeout: 2.0, handler:nil) 122 | 123 | expect = expectation(description: "Block animation completed even with invalid text") 124 | splitflap.setText("$invalid!", animated: true, completionBlock: { 125 | expect.fulfill() 126 | }) 127 | XCTAssertNil(splitflap.text) 128 | waitForExpectations(timeout: 2.0, handler:nil) 129 | } 130 | 131 | func testReload() { 132 | class DataSourceMock: SplitflapDataSource { 133 | func numberOfFlapsInSplitflap(_ splitflap: Splitflap) -> Int { 134 | return 2 135 | } 136 | } 137 | 138 | let datasourceMock = DataSourceMock() 139 | let splitflap = Splitflap() 140 | splitflap.datasource = datasourceMock 141 | 142 | splitflap.reload() 143 | XCTAssertEqual(splitflap.numberOfFlaps, 2) 144 | 145 | splitflap.reload() 146 | XCTAssertEqual(splitflap.numberOfFlaps, 2) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Sources/TileView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Splitflap 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import UIKit 28 | 29 | /** 30 | A Tile is an half view representing the flap's leaf. A tile can represents the 31 | top or the bottom of a leaf. 32 | */ 33 | final class TileView: UIView { 34 | fileprivate let digitLabel = UILabel() 35 | fileprivate let mainLineView = UIView() 36 | fileprivate let secondaryLineView = UIView() 37 | 38 | /// Defines the position and by the same time appearance of the tiles. 39 | enum Position { 40 | /// Tile positioned as a top leaf. 41 | case top 42 | /// Tile positioned as a bottom leaf. 43 | case bottom 44 | } 45 | 46 | let position: Position 47 | 48 | // MARK: - Setting Symbols 49 | 50 | /** 51 | Set the given symbol as text. 52 | 53 | - parameter symbol: An optional symbol string. 54 | */ 55 | func setSymbol(_ symbol: String?) { 56 | digitLabel.text = symbol 57 | } 58 | 59 | // MARK: - Configuring the Label 60 | 61 | /// The font of the tile's text. 62 | fileprivate var font: UIFont? 63 | 64 | /** 65 | The radii size to use when drawing rounded corners. 66 | */ 67 | fileprivate let cornerRadii: CGSize 68 | 69 | // MARK: - Initializing a Flap View 70 | 71 | required init(builder: FlapViewBuilder, position: Position) { 72 | self.cornerRadii = CGSize(width: builder.cornerRadius, height: builder.cornerRadius) 73 | self.position = position 74 | 75 | super.init(frame: CGRect.zero) 76 | 77 | setupViewsWithBuilder(builder) 78 | } 79 | 80 | required init?(coder aDecoder: NSCoder) { 81 | cornerRadii = CGSize(width: 0, height: 0) 82 | position = .top 83 | 84 | super.init(coder: aDecoder) 85 | } 86 | 87 | // MARK: - Layout the View 88 | 89 | fileprivate var flipPointHeightFactor: CGFloat = 1.0 90 | 91 | /// Setup the views helping by the given builder. 92 | fileprivate func setupViewsWithBuilder(_ builder: FlapViewBuilder) { 93 | font = builder.font 94 | 95 | layer.masksToBounds = true 96 | backgroundColor = builder.backgroundColor 97 | 98 | digitLabel.textAlignment = builder.textAlignment 99 | digitLabel.textColor = builder.textColor 100 | digitLabel.backgroundColor = builder.backgroundColor 101 | digitLabel.adjustsFontSizeToFitWidth = builder.adjustsFontSizeToFitWidth 102 | flipPointHeightFactor = builder.flipPointHeightFactor 103 | 104 | addSubview(digitLabel) 105 | 106 | // Don't add the line if the color was set to nil 107 | if let lineColor = builder.lineColor { 108 | mainLineView.backgroundColor = lineColor 109 | secondaryLineView.backgroundColor = builder.backgroundColor 110 | addSubview(mainLineView) 111 | addSubview(secondaryLineView) 112 | } 113 | } 114 | 115 | // MARK: - Laying out Subviews 116 | 117 | override func layoutSubviews() { 118 | super.layoutSubviews() 119 | 120 | // Round corners 121 | let path: UIBezierPath 122 | 123 | if position == .top { 124 | path = UIBezierPath(roundedRect:bounds, byRoundingCorners:[.topLeft, .topRight], cornerRadii: cornerRadii) 125 | } 126 | else { 127 | path = UIBezierPath(roundedRect:bounds, byRoundingCorners:[.bottomLeft, .bottomRight], cornerRadii: cornerRadii) 128 | } 129 | 130 | let maskLayer = CAShapeLayer() 131 | maskLayer.path = path.cgPath 132 | layer.mask = maskLayer 133 | 134 | // Position elements 135 | var digitLabelFrame = bounds 136 | var mainLineViewFrame = bounds 137 | var secondaryLineViewFrame = bounds 138 | 139 | if position == .top { 140 | digitLabelFrame.size.height = digitLabelFrame.height * 2 141 | digitLabelFrame.origin.y = 0 142 | mainLineViewFrame = CGRect(x: 0, y: bounds.height - (2 * flipPointHeightFactor), width: bounds.width, height: 4 * flipPointHeightFactor) 143 | secondaryLineViewFrame = CGRect(x: 0, y: bounds.height - (1 * flipPointHeightFactor), width: bounds.width, height: 2 * flipPointHeightFactor) 144 | } 145 | else { 146 | digitLabelFrame.size.height = digitLabelFrame.height * 2 147 | digitLabelFrame.origin.y = -digitLabelFrame.height / 2 148 | mainLineViewFrame = CGRect(x: 0, y: -2 * flipPointHeightFactor, width: bounds.width, height: 3 * flipPointHeightFactor) 149 | secondaryLineViewFrame = CGRect(x: 0, y: -2 * flipPointHeightFactor, width: bounds.width, height: 2 * flipPointHeightFactor) 150 | } 151 | 152 | digitLabel.frame = digitLabelFrame 153 | digitLabel.font = font ?? UIFont(name: "Courier", size: bounds.width) 154 | mainLineView.frame = mainLineViewFrame 155 | secondaryLineView.frame = secondaryLineViewFrame 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Splitflap 3 |

4 | 5 |

6 | Supported Platforms 7 | Version 8 | Carthage compatible 9 | Swift Package Manager compatible 10 | Build status 11 | Code coverage status 12 |

13 | 14 | ***🛫 Splitflap*** is a simple to use component to present changeable alphanumeric text like often used as a public transport timetable in airports or railway stations or with some flip clocks. 15 | 16 |

17 | RequirementsUsageInstallationContributionContactLicense 18 |

19 | 20 | ## Requirements 21 | 22 | - iOS 8.0+ / tvOS 9.0+ 23 | - Xcode 9.0+ 24 | - Swift 4.2+ 25 | 26 | ## Usage 27 | 28 | ### Hello World 29 | 30 | The first example is the simplest way to use the `Splitflap` component. Here how to display this "Hello" text: 31 | 32 | ![Hello](http://yannickloriot.com/resources/splitflap-hello.gif) 33 | 34 | ```swift 35 | import Splitflap 36 | 37 | let splitflapView = Splitflap(frame: CGRect(x: 0, y: 0, width: 370, height: 53)) 38 | splitflapView.datasource = self 39 | 40 | // Set the text to display by animating the flaps 41 | splitflapView.setText("Hello", animated: true) 42 | 43 | // MARK: - Splitflap DataSource Methods 44 | 45 | // Defines the number of flaps that will be used to display the text 46 | func numberOfFlapsInSplitflap(_ splitflap: Splitflap) -> Int { 47 | return 5 48 | } 49 | 50 | ``` 51 | 52 | ### Theming 53 | 54 | `Splitflap` allows you to customize each flap individually by providing a `splitflap:builderForFlapAtIndex:` delegate method: 55 | 56 | ![Theming](http://yannickloriot.com/resources/splitflap-theming.gif) 57 | 58 | ```swift 59 | let splitflapView = Splitflap(frame: CGRect(x: 0, y: 0, width: 370, height: 53)) 60 | splitflapView.delegate = self 61 | splitflapView.datasource = self 62 | 63 | // Set the text with an emoji 64 | splitflap.text = "Cat \u{1F63B}" 65 | 66 | // MARK: - Splitflap Delegate Methods 67 | 68 | // Configure the appearance for each flaps 69 | func splitflap(_ splitflap: Splitflap, builderForFlapAtIndex index: Int) -> FlapViewBuilder { 70 | return FlapViewBuilder { builder in 71 | builder.backgroundColor = UIColor(red: 251/255, green: 249/255, blue: 243/255, alpha: 1) 72 | builder.cornerRadius = 5 73 | builder.font = UIFont(name: "Avenir-Black", size:45) 74 | builder.textAlignment = .center 75 | builder.textColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5) 76 | builder.lineColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.3) 77 | } 78 | } 79 | 80 | // MARK: - Splitflap DataSource Methods 81 | 82 | func tokensInSplitflap(_ splitflap: Splitflap) -> [String] { 83 | return " Cat\u{1F63B}".characters.map { String($0) } 84 | } 85 | ``` 86 | ### And many more... 87 | 88 | To go further, take a look at the documentation and the example project. 89 | 90 | *Note: All contributions are welcome* 91 | 92 | ## Installation 93 | 94 | #### CocoaPods 95 | 96 | Install CocoaPods if not already available: 97 | 98 | ``` bash 99 | $ [sudo] gem install cocoapods 100 | $ pod setup 101 | ``` 102 | Go to the directory of your Xcode project, and Create and Edit your Podfile and add _Splitflap_: 103 | 104 | ``` bash 105 | $ cd /path/to/MyProject 106 | $ touch Podfile 107 | $ edit Podfile 108 | source 'https://github.com/CocoaPods/Specs.git' 109 | platform :ios, '8.0' 110 | 111 | use_frameworks! 112 | pod 'Splitflap', '~> 4.1.0' 113 | ``` 114 | 115 | Install into your project: 116 | 117 | ``` bash 118 | $ pod install 119 | ``` 120 | 121 | Open your project in Xcode from the .xcworkspace file (not the usual project file): 122 | 123 | ``` bash 124 | $ open MyProject.xcworkspace 125 | ``` 126 | 127 | You can now `import Splitflap` framework into your files. 128 | 129 | #### Carthage 130 | 131 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that automates the process of adding frameworks to your Cocoa application. 132 | 133 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command: 134 | 135 | ```bash 136 | $ brew update 137 | $ brew install carthage 138 | ``` 139 | 140 | To integrate `Splitflap` into your Xcode project using Carthage, specify it in your `Cartfile` file: 141 | 142 | ```ogdl 143 | github "yannickl/Splitflap" >= 4.1.0 144 | ``` 145 | 146 | #### Swift Package Manager 147 | 148 | You can use [The Swift Package Manager](https://swift.org/package-manager) to install `Splitflap` by adding the proper description to your `Package.swift` file: 149 | ```swift 150 | import PackageDescription 151 | 152 | let package = Package( 153 | name: "YOUR_PROJECT_NAME", 154 | targets: [], 155 | dependencies: [ 156 | .Package(url: "https://github.com/yannickl/Splitflap.git", versions: "4.1.0" ..< Version.max) 157 | ] 158 | ) 159 | ``` 160 | 161 | Note that the [Swift Package Manager](https://swift.org/package-manager) is still in early design and development, for more information checkout its [GitHub Page](https://github.com/apple/swift-package-manager). 162 | 163 | #### Manually 164 | 165 | [Download](https://github.com/YannickL/Splitflap/archive/master.zip) the project and copy the `Splitflap` folder into your project to use it in. 166 | 167 | ## Contribution 168 | 169 | Contributions are welcomed and encouraged *♡*. 170 | 171 | ## Contact 172 | 173 | Yannick Loriot 174 | - [https://21.co/yannickl/](https://21.co/yannickl/) 175 | - [https://twitter.com/yannickloriot](https://twitter.com/yannickloriot) 176 | 177 | ## License (MIT) 178 | 179 | Copyright (c) 2015-present - Yannick Loriot 180 | 181 | Permission is hereby granted, free of charge, to any person obtaining a copy 182 | of this software and associated documentation files (the "Software"), to deal 183 | in the Software without restriction, including without limitation the rights 184 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 185 | copies of the Software, and to permit persons to whom the Software is 186 | furnished to do so, subject to the following conditions: 187 | 188 | The above copyright notice and this permission notice shall be included in 189 | all copies or substantial portions of the Software. 190 | 191 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 192 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 193 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 194 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 195 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 196 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 197 | THE SOFTWARE. 198 | -------------------------------------------------------------------------------- /Sources/Splitflap.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Splitflap 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import UIKit 28 | 29 | /** 30 | A split-flap display component that presents changeable alphanumeric text often 31 | used as a public transport timetable in airports or railway stations and with 32 | some flip clocks. 33 | */ 34 | @IBDesignable open class Splitflap: UIView { 35 | // MARK: - Specifying the Data Source 36 | 37 | /** 38 | The data source for the split-flap view. 39 | 40 | The data source must adopt the SplitflapDataSource protocol and implement the 41 | required methods to return the number of flaps. 42 | */ 43 | open weak var datasource: SplitflapDataSource? 44 | 45 | // MARK: - Specifying the Delegate 46 | 47 | /** 48 | The delegate for the split-flap view. 49 | 50 | The delegate must adopt the SplitflapDelegate protocol and implement the 51 | required methods to specify the flap rotation for example. 52 | */ 53 | open weak var delegate: SplitflapDelegate? 54 | 55 | // MARK: - Getting Flaps 56 | 57 | /** 58 | Gets the number of flaps for the split-flap view. 59 | 60 | A Splitflap object fetches the value of this property from the data source and 61 | and caches it. The default value is zero. 62 | */ 63 | open fileprivate(set) var numberOfFlaps: Int = 0 64 | 65 | /// The flap views used the the split-flap component to display text. 66 | fileprivate var flaps: [FlapView] = [] { 67 | didSet { 68 | for flap in oldValue { 69 | flap.removeFromSuperview() 70 | } 71 | } 72 | } 73 | 74 | // MARK: - Configuring the Flap Spacing 75 | 76 | /** 77 | Specifies the spacing to use between flaps. 78 | 79 | The default value of this property is 2.0. 80 | */ 81 | @IBInspectable open var flapSpacing: CGFloat = 2 82 | 83 | // MARK: - Accessing the Text Attributes 84 | 85 | /// The current displayed text. 86 | fileprivate var textAsToken: String? 87 | 88 | /** 89 | The text displayed by the split-flap. 90 | 91 | Setting the text with this property is equilavent to call the setText:animated: 92 | methods with the animated attribute as false. This string is nil by default. 93 | 94 | - seealso: setText:animated: 95 | */ 96 | open var text: String? { 97 | get { 98 | return textAsToken 99 | } 100 | set(newValue) { 101 | setText(newValue, animated: false) 102 | } 103 | } 104 | 105 | /** 106 | Displayed the given text in the split-flap. 107 | 108 | - parameter text: The text to display by the split-flap. 109 | - parameter animated: *true* to animate the text change by rotating the flaps 110 | (component) to the new value; if you specify *false*, the new text is shown 111 | immediately. 112 | - parameter completionBlock: A block called when the animation did finished. 113 | If the text update is not animated the block is called immediately. 114 | */ 115 | open func setText(_ text: String?, animated: Bool, completionBlock: (() -> Void)? = nil) { 116 | let completionGroup = DispatchGroup() 117 | let target = (delegate ?? self) 118 | let delay = animated ? 0.181 : 0 119 | 120 | textAsToken = nil 121 | 122 | for (index, flap) in flaps.enumerated() { 123 | var tokens = self.datasource?.tokensInSplitflap(self, flap: index) ?? [] 124 | let parser = TokenParser(tokens: tokens) 125 | if let string = text { 126 | tokens = parser.parseString(string) 127 | } 128 | 129 | let token: String? = index < tokens.count ? tokens[index] : nil 130 | let rotationDuration = animated ? target.splitflap(self, rotationDurationForFlapAtIndex: index) : 0 131 | 132 | if let t = token { 133 | textAsToken = textAsToken ?? "" 134 | textAsToken?.append(t) 135 | } 136 | 137 | if animated { 138 | var flapBlock: (() -> ())? 139 | 140 | if completionBlock != nil { 141 | completionGroup.enter() 142 | 143 | flapBlock = { 144 | completionGroup.leave() 145 | } 146 | } 147 | 148 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(index) * Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: { 149 | flap.displayToken(token, rotationDuration: rotationDuration, completionBlock: flapBlock) 150 | }) 151 | } 152 | else { 153 | flap.displayToken(token, rotationDuration: rotationDuration) 154 | } 155 | } 156 | 157 | completionGroup.notify(queue: DispatchQueue.main, execute: { 158 | completionBlock?() 159 | }) 160 | } 161 | 162 | // MARK: - Observing View-Related Changes 163 | 164 | /// Tells the view that its window object changed. 165 | open override func didMoveToWindow() { 166 | reload() 167 | } 168 | 169 | // MARK: - Laying out Subviews 170 | 171 | /// Lay out subviews. 172 | open override func layoutSubviews() { 173 | super.layoutSubviews() 174 | 175 | let fNumberOfFlaps = CGFloat(numberOfFlaps) 176 | let widthPerFlap = (bounds.width - flapSpacing * (fNumberOfFlaps - 1)) / fNumberOfFlaps 177 | 178 | for (index, flap) in flaps.enumerated() { 179 | let fIndex = CGFloat(index) 180 | flap.frame = CGRect(x: fIndex * widthPerFlap + flapSpacing * fIndex, y: 0, width: widthPerFlap, height: bounds.height) 181 | } 182 | } 183 | 184 | /// Rebuild and layout the split-flap view. 185 | fileprivate func updateAndLayoutView() { 186 | let targetDelegate = (delegate ?? self) 187 | 188 | var tmp: [FlapView] = [] 189 | 190 | for index in 0 ..< numberOfFlaps { 191 | let flap = FlapView(tokens: self.datasource?.tokensInSplitflap(self, flap: index) ?? [], builder: targetDelegate.splitflap(self, builderForFlapAtIndex: index)) 192 | 193 | tmp.append(flap) 194 | addSubview(flap) 195 | } 196 | 197 | flaps = tmp 198 | 199 | layoutIfNeeded() 200 | 201 | setText(text, animated: false) 202 | } 203 | 204 | // MARK: - Reloading the Splitflap 205 | 206 | /** 207 | Reloads the split-flap. 208 | 209 | Call this method to reload all the data that is used to construct the split-flap 210 | view. It should not be called in during a animation. 211 | */ 212 | open func reload() { 213 | let target = (datasource ?? self) 214 | 215 | numberOfFlaps = target.numberOfFlapsInSplitflap(self) 216 | 217 | updateAndLayoutView() 218 | } 219 | } 220 | 221 | /// Default implementation of SplitflapDataSource 222 | extension Splitflap: SplitflapDataSource { 223 | /// By default the Splitflap object does not have flaps, so returns 0. 224 | public func numberOfFlapsInSplitflap(_ splitflap: Splitflap) -> Int { 225 | return 0 226 | } 227 | } 228 | 229 | /// Default implementation of SplitflapDelegate 230 | extension Splitflap: SplitflapDelegate { 231 | } 232 | -------------------------------------------------------------------------------- /Sources/FlapView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Splitflap 3 | * 4 | * Copyright 2015-present Yannick Loriot. 5 | * http://yannickloriot.com 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | import UIKit 28 | import QuartzCore 29 | 30 | /** 31 | A Flap view aims to display given tokens with by rotating its tiles to show the 32 | desired character or graphic. 33 | */ 34 | final class FlapView: UIView, CAAnimationDelegate { 35 | // The tiles used to display and animate the flaps 36 | fileprivate let topTicTile: TileView 37 | fileprivate let bottomTicTile: TileView 38 | fileprivate let topTacTile: TileView 39 | fileprivate let bottomTacTile: TileView 40 | 41 | // MARK: - Working With Tokens 42 | 43 | let tokens: [String] 44 | fileprivate let tokenGenerator:TokenGenerator 45 | fileprivate var targetToken: String? 46 | fileprivate var targetCompletionBlock: (() -> ())? { 47 | didSet { 48 | oldValue?() 49 | } 50 | } 51 | 52 | // MARK: - Initializing a Flap View 53 | 54 | required init(tokens: [String], builder: FlapViewBuilder) { 55 | self.topTicTile = TileView(builder: builder, position: .top) 56 | self.bottomTicTile = TileView(builder: builder, position: .bottom) 57 | self.topTacTile = TileView(builder: builder, position: .top) 58 | self.bottomTacTile = TileView(builder: builder, position: .bottom) 59 | 60 | self.tokens = tokens 61 | self.tokenGenerator = TokenGenerator(tokens: tokens) 62 | 63 | super.init(frame: CGRect.zero) 64 | 65 | setupViews() 66 | setupAnimations() 67 | } 68 | 69 | required init?(coder aDecoder: NSCoder) { 70 | topTicTile = TileView(builder: FlapViewBuilder(), position: .top) 71 | bottomTicTile = TileView(builder: FlapViewBuilder(), position: .bottom) 72 | topTacTile = TileView(builder: FlapViewBuilder(), position: .top) 73 | bottomTacTile = TileView(builder: FlapViewBuilder(), position: .bottom) 74 | tokens = [] 75 | tokenGenerator = TokenGenerator(tokens: []) 76 | 77 | super.init(coder: aDecoder) 78 | } 79 | 80 | // MARK: - Laying out Subviews 81 | 82 | override func layoutSubviews() { 83 | super.layoutSubviews() 84 | 85 | let topLeafFrame = CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height / 2) 86 | let bottomLeafFrame = CGRect(x: 0, y: bounds.height / 2, width: bounds.width, height: bounds.height / 2) 87 | 88 | topTicTile.frame = topLeafFrame 89 | bottomTicTile.frame = bottomLeafFrame 90 | topTacTile.frame = topLeafFrame 91 | bottomTacTile.frame = bottomLeafFrame 92 | } 93 | 94 | // MARK: - Initializing the Flap View 95 | 96 | fileprivate func setupViews() { 97 | addSubview(topTicTile) 98 | addSubview(bottomTicTile) 99 | addSubview(topTacTile) 100 | addSubview(bottomTacTile) 101 | 102 | topTicTile.layer.anchorPoint = CGPoint(x: 0.5, y: 1.0) 103 | bottomTicTile.layer.anchorPoint = CGPoint(x: 0.5, y: 0) 104 | topTacTile.layer.anchorPoint = CGPoint(x: 0.5, y: 1.0) 105 | bottomTacTile.layer.anchorPoint = CGPoint(x: 0.5, y: 0) 106 | 107 | updateWithToken(tokenGenerator.firstToken, animated: false) 108 | } 109 | 110 | // MARK: - Settings the Animations 111 | 112 | /// Defines the current time of the animation to know which tile to display. 113 | fileprivate enum AnimationTime { 114 | /// Tic time. 115 | case tic 116 | /// Tac time. 117 | case tac 118 | } 119 | 120 | fileprivate var animationTime = AnimationTime.tac 121 | fileprivate let topAnim = CABasicAnimation(keyPath: "transform") 122 | fileprivate let bottomAnim = CABasicAnimation(keyPath: "transform") 123 | 124 | fileprivate func setupAnimations() { 125 | // Set the perspective 126 | let zDepth: CGFloat = 1000 127 | var skewedIdentityTransform = CATransform3DIdentity 128 | skewedIdentityTransform.m34 = 1 / -zDepth 129 | 130 | // Predefine the animation 131 | topAnim.fromValue = NSValue(caTransform3D: skewedIdentityTransform) 132 | topAnim.toValue = NSValue(caTransform3D: CATransform3DRotate(skewedIdentityTransform, CGFloat.pi / -2, 1, 0, 0)) 133 | topAnim.isRemovedOnCompletion = false 134 | topAnim.fillMode = CAMediaTimingFillMode.forwards 135 | topAnim.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn) 136 | 137 | bottomAnim.fromValue = NSValue(caTransform3D: CATransform3DRotate(skewedIdentityTransform, CGFloat.pi / 2, 1, 0, 0)) 138 | bottomAnim.toValue = NSValue(caTransform3D: skewedIdentityTransform) 139 | bottomAnim.delegate = self 140 | bottomAnim.isRemovedOnCompletion = true 141 | bottomAnim.fillMode = CAMediaTimingFillMode.both 142 | bottomAnim.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut) 143 | } 144 | 145 | // MARK: - Animating the Flap View 146 | 147 | /** 148 | Display the given token. 149 | 150 | - parameter token: A token string. 151 | - parameter rotationDuration: If upper than 0, it animates the change. 152 | - parameter completionBlock: A block called when the animation did finished. 153 | If the text update is not animated the block is called immediately. 154 | */ 155 | func displayToken(_ token: String?, rotationDuration: Double, completionBlock: (() -> Void)? = nil) { 156 | let sanitizedToken = token ?? tokenGenerator.firstToken 157 | 158 | if rotationDuration > 0 { 159 | topAnim.duration = rotationDuration / 4 * 3 160 | bottomAnim.duration = rotationDuration / 4 161 | 162 | let animating = targetToken != nil 163 | 164 | targetToken = sanitizedToken 165 | targetCompletionBlock = completionBlock 166 | 167 | if !animating { 168 | displayNextToken() 169 | } 170 | } 171 | else { 172 | tokenGenerator.currentElement = sanitizedToken 173 | 174 | updateWithToken(sanitizedToken, animated: false) 175 | 176 | completionBlock?() 177 | } 178 | } 179 | 180 | /** 181 | Method used in conjunction with the `animationDidStop:finished:` callback in 182 | order to display all the tokens between the current one and the target one. 183 | */ 184 | fileprivate func displayNextToken() { 185 | guard tokenGenerator.currentElement != targetToken && targetToken != nil else { 186 | targetToken = nil 187 | targetCompletionBlock = nil 188 | 189 | return 190 | } 191 | 192 | if let token = tokenGenerator.next() { 193 | updateWithToken(token, animated: true) 194 | } 195 | } 196 | 197 | /// Display the given token. If animated it rotate the flaps. 198 | fileprivate func updateWithToken(_ token: String?, animated: Bool) { 199 | let topBack = animationTime == .tic ? topTicTile : topTacTile 200 | let bottomBack = animationTime == .tic ? bottomTicTile : bottomTacTile 201 | let topFront = animationTime == .tic ? topTacTile : topTicTile 202 | 203 | topBack.setSymbol(token) 204 | bottomBack.setSymbol(token) 205 | 206 | topBack.layer.removeAllAnimations() 207 | bottomBack.layer.removeAllAnimations() 208 | topFront.layer.removeAllAnimations() 209 | 210 | if animated { 211 | bringSubviewToFront(topFront) 212 | bringSubviewToFront(bottomBack) 213 | 214 | // Animation 215 | topAnim.beginTime = CACurrentMediaTime() 216 | topFront.layer.add(topAnim, forKey: "topDownFlip") 217 | 218 | bottomAnim.beginTime = topAnim.beginTime + topAnim.duration 219 | bottomBack.layer.add(bottomAnim, forKey: "bottomDownFlip") 220 | } 221 | else { 222 | bringSubviewToFront(topBack) 223 | bringSubviewToFront(bottomBack) 224 | 225 | animationTime = animationTime == .tic ? .tac : .tic 226 | } 227 | } 228 | 229 | // MARK: - CAAnimation Delegate Methods 230 | 231 | func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { 232 | animationTime = animationTime == .tic ? .tac : .tic 233 | 234 | displayNextToken() 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /Example/SplitflapExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | CE50405D1C050024007D9E9F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE50405C1C050024007D9E9F /* AppDelegate.swift */; }; 11 | CE50405F1C050024007D9E9F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE50405E1C050024007D9E9F /* ViewController.swift */; }; 12 | CE5040621C050024007D9E9F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE5040601C050024007D9E9F /* Main.storyboard */; }; 13 | CE5040641C050024007D9E9F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CE5040631C050024007D9E9F /* Assets.xcassets */; }; 14 | CE525D8B1BF3833700429200 /* Splitflap.h in Headers */ = {isa = PBXBuildFile; fileRef = CE525D8A1BF3833700429200 /* Splitflap.h */; settings = {ATTRIBUTES = (Public, ); }; }; 15 | CE525D8F1BF3833700429200 /* Splitflap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE525D881BF3833700429200 /* Splitflap.framework */; }; 16 | CE525D901BF3833700429200 /* Splitflap.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE525D881BF3833700429200 /* Splitflap.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 17 | CEB139A41BF266DD00DE6BA9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEB139A31BF266DD00DE6BA9 /* AppDelegate.swift */; }; 18 | CEB139A61BF266DD00DE6BA9 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEB139A51BF266DD00DE6BA9 /* ViewController.swift */; }; 19 | CEB139A91BF266DD00DE6BA9 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CEB139A71BF266DD00DE6BA9 /* Main.storyboard */; }; 20 | CEB139AB1BF266DD00DE6BA9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CEB139AA1BF266DD00DE6BA9 /* Assets.xcassets */; }; 21 | CEB139AE1BF266DD00DE6BA9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CEB139AC1BF266DD00DE6BA9 /* LaunchScreen.storyboard */; }; 22 | CEE3408D1D8AEA4600FF580D /* FlapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340841D8AEA4600FF580D /* FlapView.swift */; }; 23 | CEE3408E1D8AEA4600FF580D /* FlapViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340851D8AEA4600FF580D /* FlapViewBuilder.swift */; }; 24 | CEE3408F1D8AEA4600FF580D /* Splitflap.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340861D8AEA4600FF580D /* Splitflap.swift */; }; 25 | CEE340901D8AEA4600FF580D /* SplitflapDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340871D8AEA4600FF580D /* SplitflapDataSource.swift */; }; 26 | CEE340911D8AEA4600FF580D /* SplitflapDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340881D8AEA4600FF580D /* SplitflapDelegate.swift */; }; 27 | CEE340921D8AEA4600FF580D /* SplitflapTokens.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340891D8AEA4600FF580D /* SplitflapTokens.swift */; }; 28 | CEE340931D8AEA4600FF580D /* TileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE3408A1D8AEA4600FF580D /* TileView.swift */; }; 29 | CEE340941D8AEA4600FF580D /* TokenGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE3408B1D8AEA4600FF580D /* TokenGenerator.swift */; }; 30 | CEE340951D8AEA4600FF580D /* TokenParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE3408C1D8AEA4600FF580D /* TokenParser.swift */; }; 31 | CEE340961D8AEA4900FF580D /* FlapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340841D8AEA4600FF580D /* FlapView.swift */; }; 32 | CEE340971D8AEA4900FF580D /* FlapViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340851D8AEA4600FF580D /* FlapViewBuilder.swift */; }; 33 | CEE340981D8AEA4900FF580D /* Splitflap.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340861D8AEA4600FF580D /* Splitflap.swift */; }; 34 | CEE340991D8AEA4900FF580D /* SplitflapDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340871D8AEA4600FF580D /* SplitflapDataSource.swift */; }; 35 | CEE3409A1D8AEA4900FF580D /* SplitflapDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340881D8AEA4600FF580D /* SplitflapDelegate.swift */; }; 36 | CEE3409B1D8AEA4900FF580D /* SplitflapTokens.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340891D8AEA4600FF580D /* SplitflapTokens.swift */; }; 37 | CEE3409C1D8AEA4900FF580D /* TileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE3408A1D8AEA4600FF580D /* TileView.swift */; }; 38 | CEE3409D1D8AEA4900FF580D /* TokenGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE3408B1D8AEA4600FF580D /* TokenGenerator.swift */; }; 39 | CEE3409E1D8AEA4900FF580D /* TokenParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE3408C1D8AEA4600FF580D /* TokenParser.swift */; }; 40 | CEE3409F1D8AEA4A00FF580D /* FlapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340841D8AEA4600FF580D /* FlapView.swift */; }; 41 | CEE340A01D8AEA4A00FF580D /* FlapViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340851D8AEA4600FF580D /* FlapViewBuilder.swift */; }; 42 | CEE340A11D8AEA4A00FF580D /* Splitflap.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340861D8AEA4600FF580D /* Splitflap.swift */; }; 43 | CEE340A21D8AEA4A00FF580D /* SplitflapDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340871D8AEA4600FF580D /* SplitflapDataSource.swift */; }; 44 | CEE340A31D8AEA4A00FF580D /* SplitflapDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340881D8AEA4600FF580D /* SplitflapDelegate.swift */; }; 45 | CEE340A41D8AEA4A00FF580D /* SplitflapTokens.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340891D8AEA4600FF580D /* SplitflapTokens.swift */; }; 46 | CEE340A51D8AEA4A00FF580D /* TileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE3408A1D8AEA4600FF580D /* TileView.swift */; }; 47 | CEE340A61D8AEA4A00FF580D /* TokenGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE3408B1D8AEA4600FF580D /* TokenGenerator.swift */; }; 48 | CEE340A71D8AEA4A00FF580D /* TokenParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE3408C1D8AEA4600FF580D /* TokenParser.swift */; }; 49 | CEE340A81D8AEA4A00FF580D /* FlapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340841D8AEA4600FF580D /* FlapView.swift */; }; 50 | CEE340A91D8AEA4A00FF580D /* FlapViewBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340851D8AEA4600FF580D /* FlapViewBuilder.swift */; }; 51 | CEE340AA1D8AEA4A00FF580D /* Splitflap.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340861D8AEA4600FF580D /* Splitflap.swift */; }; 52 | CEE340AB1D8AEA4A00FF580D /* SplitflapDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340871D8AEA4600FF580D /* SplitflapDataSource.swift */; }; 53 | CEE340AC1D8AEA4A00FF580D /* SplitflapDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340881D8AEA4600FF580D /* SplitflapDelegate.swift */; }; 54 | CEE340AD1D8AEA4A00FF580D /* SplitflapTokens.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340891D8AEA4600FF580D /* SplitflapTokens.swift */; }; 55 | CEE340AE1D8AEA4A00FF580D /* TileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE3408A1D8AEA4600FF580D /* TileView.swift */; }; 56 | CEE340AF1D8AEA4A00FF580D /* TokenGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE3408B1D8AEA4600FF580D /* TokenGenerator.swift */; }; 57 | CEE340B01D8AEA4A00FF580D /* TokenParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE3408C1D8AEA4600FF580D /* TokenParser.swift */; }; 58 | CEE340BA1D8AEA5F00FF580D /* FlapViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340B11D8AEA5F00FF580D /* FlapViewTests.swift */; }; 59 | CEE340BB1D8AEA5F00FF580D /* SplitflapDataSourceDatasourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340B21D8AEA5F00FF580D /* SplitflapDataSourceDatasourceTests.swift */; }; 60 | CEE340BC1D8AEA5F00FF580D /* SplitflapDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340B31D8AEA5F00FF580D /* SplitflapDelegateTests.swift */; }; 61 | CEE340BD1D8AEA5F00FF580D /* SplitflapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340B41D8AEA5F00FF580D /* SplitflapTests.swift */; }; 62 | CEE340BE1D8AEA5F00FF580D /* StoryboardTests.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CEE340B51D8AEA5F00FF580D /* StoryboardTests.storyboard */; }; 63 | CEE340BF1D8AEA5F00FF580D /* TileViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340B61D8AEA5F00FF580D /* TileViewTests.swift */; }; 64 | CEE340C01D8AEA5F00FF580D /* TokenGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340B71D8AEA5F00FF580D /* TokenGeneratorTests.swift */; }; 65 | CEE340C11D8AEA5F00FF580D /* TokenParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340B81D8AEA5F00FF580D /* TokenParserTests.swift */; }; 66 | CEE340C21D8AEA5F00FF580D /* XCTTestCaseTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE340B91D8AEA5F00FF580D /* XCTTestCaseTemplate.swift */; }; 67 | /* End PBXBuildFile section */ 68 | 69 | /* Begin PBXContainerItemProxy section */ 70 | CE525D8D1BF3833700429200 /* PBXContainerItemProxy */ = { 71 | isa = PBXContainerItemProxy; 72 | containerPortal = CEB139981BF266DD00DE6BA9 /* Project object */; 73 | proxyType = 1; 74 | remoteGlobalIDString = CE525D871BF3833700429200; 75 | remoteInfo = Splitflap; 76 | }; 77 | CE525DB21BF4FBC800429200 /* PBXContainerItemProxy */ = { 78 | isa = PBXContainerItemProxy; 79 | containerPortal = CEB139981BF266DD00DE6BA9 /* Project object */; 80 | proxyType = 1; 81 | remoteGlobalIDString = CEB1399F1BF266DD00DE6BA9; 82 | remoteInfo = SplitflapExample; 83 | }; 84 | /* End PBXContainerItemProxy section */ 85 | 86 | /* Begin PBXCopyFilesBuildPhase section */ 87 | CE525D941BF3833700429200 /* Embed Frameworks */ = { 88 | isa = PBXCopyFilesBuildPhase; 89 | buildActionMask = 2147483647; 90 | dstPath = ""; 91 | dstSubfolderSpec = 10; 92 | files = ( 93 | CE525D901BF3833700429200 /* Splitflap.framework in Embed Frameworks */, 94 | ); 95 | name = "Embed Frameworks"; 96 | runOnlyForDeploymentPostprocessing = 0; 97 | }; 98 | /* End PBXCopyFilesBuildPhase section */ 99 | 100 | /* Begin PBXFileReference section */ 101 | CE50405A1C050024007D9E9F /* tvOSExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tvOSExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 102 | CE50405C1C050024007D9E9F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 103 | CE50405E1C050024007D9E9F /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 104 | CE5040611C050024007D9E9F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 105 | CE5040631C050024007D9E9F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 106 | CE5040651C050024007D9E9F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 107 | CE525D881BF3833700429200 /* Splitflap.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Splitflap.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 108 | CE525D8A1BF3833700429200 /* Splitflap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Splitflap.h; sourceTree = ""; }; 109 | CE525D8C1BF3833700429200 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 110 | CE525DAD1BF4FBC800429200 /* SplitflapTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SplitflapTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 111 | CE525DB11BF4FBC800429200 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 112 | CEB139A01BF266DD00DE6BA9 /* SplitflapExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SplitflapExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 113 | CEB139A31BF266DD00DE6BA9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 114 | CEB139A51BF266DD00DE6BA9 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 115 | CEB139A81BF266DD00DE6BA9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 116 | CEB139AA1BF266DD00DE6BA9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 117 | CEB139AD1BF266DD00DE6BA9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 118 | CEB139AF1BF266DD00DE6BA9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 119 | CEE340841D8AEA4600FF580D /* FlapView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlapView.swift; sourceTree = ""; }; 120 | CEE340851D8AEA4600FF580D /* FlapViewBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlapViewBuilder.swift; sourceTree = ""; }; 121 | CEE340861D8AEA4600FF580D /* Splitflap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Splitflap.swift; sourceTree = ""; }; 122 | CEE340871D8AEA4600FF580D /* SplitflapDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplitflapDataSource.swift; sourceTree = ""; }; 123 | CEE340881D8AEA4600FF580D /* SplitflapDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplitflapDelegate.swift; sourceTree = ""; }; 124 | CEE340891D8AEA4600FF580D /* SplitflapTokens.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplitflapTokens.swift; sourceTree = ""; }; 125 | CEE3408A1D8AEA4600FF580D /* TileView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TileView.swift; sourceTree = ""; }; 126 | CEE3408B1D8AEA4600FF580D /* TokenGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenGenerator.swift; sourceTree = ""; }; 127 | CEE3408C1D8AEA4600FF580D /* TokenParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenParser.swift; sourceTree = ""; }; 128 | CEE340B11D8AEA5F00FF580D /* FlapViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FlapViewTests.swift; path = ../../Tests/FlapViewTests.swift; sourceTree = ""; }; 129 | CEE340B21D8AEA5F00FF580D /* SplitflapDataSourceDatasourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SplitflapDataSourceDatasourceTests.swift; path = ../../Tests/SplitflapDataSourceDatasourceTests.swift; sourceTree = ""; }; 130 | CEE340B31D8AEA5F00FF580D /* SplitflapDelegateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SplitflapDelegateTests.swift; path = ../../Tests/SplitflapDelegateTests.swift; sourceTree = ""; }; 131 | CEE340B41D8AEA5F00FF580D /* SplitflapTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SplitflapTests.swift; path = ../../Tests/SplitflapTests.swift; sourceTree = ""; }; 132 | CEE340B51D8AEA5F00FF580D /* StoryboardTests.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = StoryboardTests.storyboard; path = ../../Tests/StoryboardTests.storyboard; sourceTree = ""; }; 133 | CEE340B61D8AEA5F00FF580D /* TileViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TileViewTests.swift; path = ../../Tests/TileViewTests.swift; sourceTree = ""; }; 134 | CEE340B71D8AEA5F00FF580D /* TokenGeneratorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TokenGeneratorTests.swift; path = ../../Tests/TokenGeneratorTests.swift; sourceTree = ""; }; 135 | CEE340B81D8AEA5F00FF580D /* TokenParserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TokenParserTests.swift; path = ../../Tests/TokenParserTests.swift; sourceTree = ""; }; 136 | CEE340B91D8AEA5F00FF580D /* XCTTestCaseTemplate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XCTTestCaseTemplate.swift; path = ../../Tests/XCTTestCaseTemplate.swift; sourceTree = ""; }; 137 | /* End PBXFileReference section */ 138 | 139 | /* Begin PBXFrameworksBuildPhase section */ 140 | CE5040571C050024007D9E9F /* Frameworks */ = { 141 | isa = PBXFrameworksBuildPhase; 142 | buildActionMask = 2147483647; 143 | files = ( 144 | ); 145 | runOnlyForDeploymentPostprocessing = 0; 146 | }; 147 | CE525D841BF3833700429200 /* Frameworks */ = { 148 | isa = PBXFrameworksBuildPhase; 149 | buildActionMask = 2147483647; 150 | files = ( 151 | ); 152 | runOnlyForDeploymentPostprocessing = 0; 153 | }; 154 | CE525DAA1BF4FBC800429200 /* Frameworks */ = { 155 | isa = PBXFrameworksBuildPhase; 156 | buildActionMask = 2147483647; 157 | files = ( 158 | ); 159 | runOnlyForDeploymentPostprocessing = 0; 160 | }; 161 | CEB1399D1BF266DD00DE6BA9 /* Frameworks */ = { 162 | isa = PBXFrameworksBuildPhase; 163 | buildActionMask = 2147483647; 164 | files = ( 165 | CE525D8F1BF3833700429200 /* Splitflap.framework in Frameworks */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXFrameworksBuildPhase section */ 170 | 171 | /* Begin PBXGroup section */ 172 | CE50405B1C050024007D9E9F /* tvOSExample */ = { 173 | isa = PBXGroup; 174 | children = ( 175 | CE50405C1C050024007D9E9F /* AppDelegate.swift */, 176 | CE50405E1C050024007D9E9F /* ViewController.swift */, 177 | CE5040601C050024007D9E9F /* Main.storyboard */, 178 | CE5040631C050024007D9E9F /* Assets.xcassets */, 179 | CE5040651C050024007D9E9F /* Info.plist */, 180 | ); 181 | path = tvOSExample; 182 | sourceTree = ""; 183 | }; 184 | CE525D891BF3833700429200 /* Splitflap */ = { 185 | isa = PBXGroup; 186 | children = ( 187 | CE525D8A1BF3833700429200 /* Splitflap.h */, 188 | CE525D8C1BF3833700429200 /* Info.plist */, 189 | ); 190 | path = Splitflap; 191 | sourceTree = ""; 192 | }; 193 | CE525DAE1BF4FBC800429200 /* SplitflapTests */ = { 194 | isa = PBXGroup; 195 | children = ( 196 | CE525DB11BF4FBC800429200 /* Info.plist */, 197 | CEE340B11D8AEA5F00FF580D /* FlapViewTests.swift */, 198 | CEE340B21D8AEA5F00FF580D /* SplitflapDataSourceDatasourceTests.swift */, 199 | CEE340B31D8AEA5F00FF580D /* SplitflapDelegateTests.swift */, 200 | CEE340B41D8AEA5F00FF580D /* SplitflapTests.swift */, 201 | CEE340B51D8AEA5F00FF580D /* StoryboardTests.storyboard */, 202 | CEE340B61D8AEA5F00FF580D /* TileViewTests.swift */, 203 | CEE340B71D8AEA5F00FF580D /* TokenGeneratorTests.swift */, 204 | CEE340B81D8AEA5F00FF580D /* TokenParserTests.swift */, 205 | CEE340B91D8AEA5F00FF580D /* XCTTestCaseTemplate.swift */, 206 | ); 207 | path = SplitflapTests; 208 | sourceTree = ""; 209 | }; 210 | CEB139971BF266DD00DE6BA9 = { 211 | isa = PBXGroup; 212 | children = ( 213 | CEE340831D8AEA4600FF580D /* Splitflap */, 214 | CEB139A21BF266DD00DE6BA9 /* SplitflapExample */, 215 | CE525D891BF3833700429200 /* Splitflap */, 216 | CE525DAE1BF4FBC800429200 /* SplitflapTests */, 217 | CE50405B1C050024007D9E9F /* tvOSExample */, 218 | CEB139A11BF266DD00DE6BA9 /* Products */, 219 | ); 220 | sourceTree = ""; 221 | }; 222 | CEB139A11BF266DD00DE6BA9 /* Products */ = { 223 | isa = PBXGroup; 224 | children = ( 225 | CEB139A01BF266DD00DE6BA9 /* SplitflapExample.app */, 226 | CE525D881BF3833700429200 /* Splitflap.framework */, 227 | CE525DAD1BF4FBC800429200 /* SplitflapTests.xctest */, 228 | CE50405A1C050024007D9E9F /* tvOSExample.app */, 229 | ); 230 | name = Products; 231 | sourceTree = ""; 232 | }; 233 | CEB139A21BF266DD00DE6BA9 /* SplitflapExample */ = { 234 | isa = PBXGroup; 235 | children = ( 236 | CEB139A31BF266DD00DE6BA9 /* AppDelegate.swift */, 237 | CEB139A51BF266DD00DE6BA9 /* ViewController.swift */, 238 | CEB139A71BF266DD00DE6BA9 /* Main.storyboard */, 239 | CEB139AA1BF266DD00DE6BA9 /* Assets.xcassets */, 240 | CEB139AC1BF266DD00DE6BA9 /* LaunchScreen.storyboard */, 241 | CEB139AF1BF266DD00DE6BA9 /* Info.plist */, 242 | ); 243 | path = SplitflapExample; 244 | sourceTree = ""; 245 | }; 246 | CEE340831D8AEA4600FF580D /* Splitflap */ = { 247 | isa = PBXGroup; 248 | children = ( 249 | CEE340841D8AEA4600FF580D /* FlapView.swift */, 250 | CEE340851D8AEA4600FF580D /* FlapViewBuilder.swift */, 251 | CEE340861D8AEA4600FF580D /* Splitflap.swift */, 252 | CEE340871D8AEA4600FF580D /* SplitflapDataSource.swift */, 253 | CEE340881D8AEA4600FF580D /* SplitflapDelegate.swift */, 254 | CEE340891D8AEA4600FF580D /* SplitflapTokens.swift */, 255 | CEE3408A1D8AEA4600FF580D /* TileView.swift */, 256 | CEE3408B1D8AEA4600FF580D /* TokenGenerator.swift */, 257 | CEE3408C1D8AEA4600FF580D /* TokenParser.swift */, 258 | ); 259 | name = Splitflap; 260 | path = ../Sources; 261 | sourceTree = ""; 262 | }; 263 | /* End PBXGroup section */ 264 | 265 | /* Begin PBXHeadersBuildPhase section */ 266 | CE525D851BF3833700429200 /* Headers */ = { 267 | isa = PBXHeadersBuildPhase; 268 | buildActionMask = 2147483647; 269 | files = ( 270 | CE525D8B1BF3833700429200 /* Splitflap.h in Headers */, 271 | ); 272 | runOnlyForDeploymentPostprocessing = 0; 273 | }; 274 | /* End PBXHeadersBuildPhase section */ 275 | 276 | /* Begin PBXNativeTarget section */ 277 | CE5040591C050024007D9E9F /* tvOSExample */ = { 278 | isa = PBXNativeTarget; 279 | buildConfigurationList = CE5040681C050024007D9E9F /* Build configuration list for PBXNativeTarget "tvOSExample" */; 280 | buildPhases = ( 281 | CE5040561C050024007D9E9F /* Sources */, 282 | CE5040571C050024007D9E9F /* Frameworks */, 283 | CE5040581C050024007D9E9F /* Resources */, 284 | ); 285 | buildRules = ( 286 | ); 287 | dependencies = ( 288 | ); 289 | name = tvOSExample; 290 | productName = tvOSExample; 291 | productReference = CE50405A1C050024007D9E9F /* tvOSExample.app */; 292 | productType = "com.apple.product-type.application"; 293 | }; 294 | CE525D871BF3833700429200 /* Splitflap */ = { 295 | isa = PBXNativeTarget; 296 | buildConfigurationList = CE525D911BF3833700429200 /* Build configuration list for PBXNativeTarget "Splitflap" */; 297 | buildPhases = ( 298 | CE525D831BF3833700429200 /* Sources */, 299 | CE525D841BF3833700429200 /* Frameworks */, 300 | CE525D851BF3833700429200 /* Headers */, 301 | CE525D861BF3833700429200 /* Resources */, 302 | ); 303 | buildRules = ( 304 | ); 305 | dependencies = ( 306 | ); 307 | name = Splitflap; 308 | productName = Splitflap; 309 | productReference = CE525D881BF3833700429200 /* Splitflap.framework */; 310 | productType = "com.apple.product-type.framework"; 311 | }; 312 | CE525DAC1BF4FBC800429200 /* SplitflapTests */ = { 313 | isa = PBXNativeTarget; 314 | buildConfigurationList = CE525DB61BF4FBC800429200 /* Build configuration list for PBXNativeTarget "SplitflapTests" */; 315 | buildPhases = ( 316 | CE525DA91BF4FBC800429200 /* Sources */, 317 | CE525DAA1BF4FBC800429200 /* Frameworks */, 318 | CE525DAB1BF4FBC800429200 /* Resources */, 319 | ); 320 | buildRules = ( 321 | ); 322 | dependencies = ( 323 | CE525DB31BF4FBC800429200 /* PBXTargetDependency */, 324 | ); 325 | name = SplitflapTests; 326 | productName = SplitflapTests; 327 | productReference = CE525DAD1BF4FBC800429200 /* SplitflapTests.xctest */; 328 | productType = "com.apple.product-type.bundle.unit-test"; 329 | }; 330 | CEB1399F1BF266DD00DE6BA9 /* SplitflapExample */ = { 331 | isa = PBXNativeTarget; 332 | buildConfigurationList = CEB139B21BF266DD00DE6BA9 /* Build configuration list for PBXNativeTarget "SplitflapExample" */; 333 | buildPhases = ( 334 | CEB1399C1BF266DD00DE6BA9 /* Sources */, 335 | CEB1399D1BF266DD00DE6BA9 /* Frameworks */, 336 | CEB1399E1BF266DD00DE6BA9 /* Resources */, 337 | CE525D941BF3833700429200 /* Embed Frameworks */, 338 | ); 339 | buildRules = ( 340 | ); 341 | dependencies = ( 342 | CE525D8E1BF3833700429200 /* PBXTargetDependency */, 343 | ); 344 | name = SplitflapExample; 345 | productName = SplitflapExample; 346 | productReference = CEB139A01BF266DD00DE6BA9 /* SplitflapExample.app */; 347 | productType = "com.apple.product-type.application"; 348 | }; 349 | /* End PBXNativeTarget section */ 350 | 351 | /* Begin PBXProject section */ 352 | CEB139981BF266DD00DE6BA9 /* Project object */ = { 353 | isa = PBXProject; 354 | attributes = { 355 | LastSwiftUpdateCheck = 0710; 356 | LastUpgradeCheck = 1020; 357 | ORGANIZATIONNAME = "Yannick LORIOT"; 358 | TargetAttributes = { 359 | CE5040591C050024007D9E9F = { 360 | CreatedOnToolsVersion = 7.1.1; 361 | LastSwiftMigration = 0800; 362 | }; 363 | CE525D871BF3833700429200 = { 364 | CreatedOnToolsVersion = 7.1; 365 | LastSwiftMigration = 1020; 366 | }; 367 | CE525DAC1BF4FBC800429200 = { 368 | CreatedOnToolsVersion = 7.1; 369 | LastSwiftMigration = 0800; 370 | }; 371 | CEB1399F1BF266DD00DE6BA9 = { 372 | CreatedOnToolsVersion = 7.1; 373 | LastSwiftMigration = 1020; 374 | }; 375 | }; 376 | }; 377 | buildConfigurationList = CEB1399B1BF266DD00DE6BA9 /* Build configuration list for PBXProject "SplitflapExample" */; 378 | compatibilityVersion = "Xcode 3.2"; 379 | developmentRegion = en; 380 | hasScannedForEncodings = 0; 381 | knownRegions = ( 382 | en, 383 | Base, 384 | ); 385 | mainGroup = CEB139971BF266DD00DE6BA9; 386 | productRefGroup = CEB139A11BF266DD00DE6BA9 /* Products */; 387 | projectDirPath = ""; 388 | projectRoot = ""; 389 | targets = ( 390 | CEB1399F1BF266DD00DE6BA9 /* SplitflapExample */, 391 | CE525DAC1BF4FBC800429200 /* SplitflapTests */, 392 | CE525D871BF3833700429200 /* Splitflap */, 393 | CE5040591C050024007D9E9F /* tvOSExample */, 394 | ); 395 | }; 396 | /* End PBXProject section */ 397 | 398 | /* Begin PBXResourcesBuildPhase section */ 399 | CE5040581C050024007D9E9F /* Resources */ = { 400 | isa = PBXResourcesBuildPhase; 401 | buildActionMask = 2147483647; 402 | files = ( 403 | CE5040641C050024007D9E9F /* Assets.xcassets in Resources */, 404 | CE5040621C050024007D9E9F /* Main.storyboard in Resources */, 405 | ); 406 | runOnlyForDeploymentPostprocessing = 0; 407 | }; 408 | CE525D861BF3833700429200 /* Resources */ = { 409 | isa = PBXResourcesBuildPhase; 410 | buildActionMask = 2147483647; 411 | files = ( 412 | ); 413 | runOnlyForDeploymentPostprocessing = 0; 414 | }; 415 | CE525DAB1BF4FBC800429200 /* Resources */ = { 416 | isa = PBXResourcesBuildPhase; 417 | buildActionMask = 2147483647; 418 | files = ( 419 | CEE340BE1D8AEA5F00FF580D /* StoryboardTests.storyboard in Resources */, 420 | ); 421 | runOnlyForDeploymentPostprocessing = 0; 422 | }; 423 | CEB1399E1BF266DD00DE6BA9 /* Resources */ = { 424 | isa = PBXResourcesBuildPhase; 425 | buildActionMask = 2147483647; 426 | files = ( 427 | CEB139AE1BF266DD00DE6BA9 /* LaunchScreen.storyboard in Resources */, 428 | CEB139AB1BF266DD00DE6BA9 /* Assets.xcassets in Resources */, 429 | CEB139A91BF266DD00DE6BA9 /* Main.storyboard in Resources */, 430 | ); 431 | runOnlyForDeploymentPostprocessing = 0; 432 | }; 433 | /* End PBXResourcesBuildPhase section */ 434 | 435 | /* Begin PBXSourcesBuildPhase section */ 436 | CE5040561C050024007D9E9F /* Sources */ = { 437 | isa = PBXSourcesBuildPhase; 438 | buildActionMask = 2147483647; 439 | files = ( 440 | CE50405F1C050024007D9E9F /* ViewController.swift in Sources */, 441 | CEE340A91D8AEA4A00FF580D /* FlapViewBuilder.swift in Sources */, 442 | CEE340AC1D8AEA4A00FF580D /* SplitflapDelegate.swift in Sources */, 443 | CEE340AD1D8AEA4A00FF580D /* SplitflapTokens.swift in Sources */, 444 | CEE340AE1D8AEA4A00FF580D /* TileView.swift in Sources */, 445 | CEE340AF1D8AEA4A00FF580D /* TokenGenerator.swift in Sources */, 446 | CEE340B01D8AEA4A00FF580D /* TokenParser.swift in Sources */, 447 | CEE340AB1D8AEA4A00FF580D /* SplitflapDataSource.swift in Sources */, 448 | CE50405D1C050024007D9E9F /* AppDelegate.swift in Sources */, 449 | CEE340A81D8AEA4A00FF580D /* FlapView.swift in Sources */, 450 | CEE340AA1D8AEA4A00FF580D /* Splitflap.swift in Sources */, 451 | ); 452 | runOnlyForDeploymentPostprocessing = 0; 453 | }; 454 | CE525D831BF3833700429200 /* Sources */ = { 455 | isa = PBXSourcesBuildPhase; 456 | buildActionMask = 2147483647; 457 | files = ( 458 | CEE340A11D8AEA4A00FF580D /* Splitflap.swift in Sources */, 459 | CEE340A51D8AEA4A00FF580D /* TileView.swift in Sources */, 460 | CEE3409F1D8AEA4A00FF580D /* FlapView.swift in Sources */, 461 | CEE340A21D8AEA4A00FF580D /* SplitflapDataSource.swift in Sources */, 462 | CEE340A71D8AEA4A00FF580D /* TokenParser.swift in Sources */, 463 | CEE340A01D8AEA4A00FF580D /* FlapViewBuilder.swift in Sources */, 464 | CEE340A31D8AEA4A00FF580D /* SplitflapDelegate.swift in Sources */, 465 | CEE340A41D8AEA4A00FF580D /* SplitflapTokens.swift in Sources */, 466 | CEE340A61D8AEA4A00FF580D /* TokenGenerator.swift in Sources */, 467 | ); 468 | runOnlyForDeploymentPostprocessing = 0; 469 | }; 470 | CE525DA91BF4FBC800429200 /* Sources */ = { 471 | isa = PBXSourcesBuildPhase; 472 | buildActionMask = 2147483647; 473 | files = ( 474 | CEE340C01D8AEA5F00FF580D /* TokenGeneratorTests.swift in Sources */, 475 | CEE3409B1D8AEA4900FF580D /* SplitflapTokens.swift in Sources */, 476 | CEE340C11D8AEA5F00FF580D /* TokenParserTests.swift in Sources */, 477 | CEE340981D8AEA4900FF580D /* Splitflap.swift in Sources */, 478 | CEE340BD1D8AEA5F00FF580D /* SplitflapTests.swift in Sources */, 479 | CEE340991D8AEA4900FF580D /* SplitflapDataSource.swift in Sources */, 480 | CEE340BA1D8AEA5F00FF580D /* FlapViewTests.swift in Sources */, 481 | CEE340971D8AEA4900FF580D /* FlapViewBuilder.swift in Sources */, 482 | CEE3409A1D8AEA4900FF580D /* SplitflapDelegate.swift in Sources */, 483 | CEE3409D1D8AEA4900FF580D /* TokenGenerator.swift in Sources */, 484 | CEE340961D8AEA4900FF580D /* FlapView.swift in Sources */, 485 | CEE340BC1D8AEA5F00FF580D /* SplitflapDelegateTests.swift in Sources */, 486 | CEE340BF1D8AEA5F00FF580D /* TileViewTests.swift in Sources */, 487 | CEE3409C1D8AEA4900FF580D /* TileView.swift in Sources */, 488 | CEE340C21D8AEA5F00FF580D /* XCTTestCaseTemplate.swift in Sources */, 489 | CEE340BB1D8AEA5F00FF580D /* SplitflapDataSourceDatasourceTests.swift in Sources */, 490 | CEE3409E1D8AEA4900FF580D /* TokenParser.swift in Sources */, 491 | ); 492 | runOnlyForDeploymentPostprocessing = 0; 493 | }; 494 | CEB1399C1BF266DD00DE6BA9 /* Sources */ = { 495 | isa = PBXSourcesBuildPhase; 496 | buildActionMask = 2147483647; 497 | files = ( 498 | CEB139A61BF266DD00DE6BA9 /* ViewController.swift in Sources */, 499 | CEE3408E1D8AEA4600FF580D /* FlapViewBuilder.swift in Sources */, 500 | CEE340911D8AEA4600FF580D /* SplitflapDelegate.swift in Sources */, 501 | CEE340921D8AEA4600FF580D /* SplitflapTokens.swift in Sources */, 502 | CEE340931D8AEA4600FF580D /* TileView.swift in Sources */, 503 | CEE340941D8AEA4600FF580D /* TokenGenerator.swift in Sources */, 504 | CEE340951D8AEA4600FF580D /* TokenParser.swift in Sources */, 505 | CEE340901D8AEA4600FF580D /* SplitflapDataSource.swift in Sources */, 506 | CEB139A41BF266DD00DE6BA9 /* AppDelegate.swift in Sources */, 507 | CEE3408D1D8AEA4600FF580D /* FlapView.swift in Sources */, 508 | CEE3408F1D8AEA4600FF580D /* Splitflap.swift in Sources */, 509 | ); 510 | runOnlyForDeploymentPostprocessing = 0; 511 | }; 512 | /* End PBXSourcesBuildPhase section */ 513 | 514 | /* Begin PBXTargetDependency section */ 515 | CE525D8E1BF3833700429200 /* PBXTargetDependency */ = { 516 | isa = PBXTargetDependency; 517 | target = CE525D871BF3833700429200 /* Splitflap */; 518 | targetProxy = CE525D8D1BF3833700429200 /* PBXContainerItemProxy */; 519 | }; 520 | CE525DB31BF4FBC800429200 /* PBXTargetDependency */ = { 521 | isa = PBXTargetDependency; 522 | target = CEB1399F1BF266DD00DE6BA9 /* SplitflapExample */; 523 | targetProxy = CE525DB21BF4FBC800429200 /* PBXContainerItemProxy */; 524 | }; 525 | /* End PBXTargetDependency section */ 526 | 527 | /* Begin PBXVariantGroup section */ 528 | CE5040601C050024007D9E9F /* Main.storyboard */ = { 529 | isa = PBXVariantGroup; 530 | children = ( 531 | CE5040611C050024007D9E9F /* Base */, 532 | ); 533 | name = Main.storyboard; 534 | sourceTree = ""; 535 | }; 536 | CEB139A71BF266DD00DE6BA9 /* Main.storyboard */ = { 537 | isa = PBXVariantGroup; 538 | children = ( 539 | CEB139A81BF266DD00DE6BA9 /* Base */, 540 | ); 541 | name = Main.storyboard; 542 | sourceTree = ""; 543 | }; 544 | CEB139AC1BF266DD00DE6BA9 /* LaunchScreen.storyboard */ = { 545 | isa = PBXVariantGroup; 546 | children = ( 547 | CEB139AD1BF266DD00DE6BA9 /* Base */, 548 | ); 549 | name = LaunchScreen.storyboard; 550 | sourceTree = ""; 551 | }; 552 | /* End PBXVariantGroup section */ 553 | 554 | /* Begin XCBuildConfiguration section */ 555 | CE5040661C050024007D9E9F /* Debug */ = { 556 | isa = XCBuildConfiguration; 557 | buildSettings = { 558 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; 559 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 560 | INFOPLIST_FILE = tvOSExample/Info.plist; 561 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 562 | PRODUCT_BUNDLE_IDENTIFIER = com.yannickloriot.tvOSExample; 563 | PRODUCT_NAME = "$(TARGET_NAME)"; 564 | SDKROOT = appletvos; 565 | SWIFT_VERSION = 4.2; 566 | TARGETED_DEVICE_FAMILY = 3; 567 | TVOS_DEPLOYMENT_TARGET = 9.0; 568 | }; 569 | name = Debug; 570 | }; 571 | CE5040671C050024007D9E9F /* Release */ = { 572 | isa = XCBuildConfiguration; 573 | buildSettings = { 574 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; 575 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 576 | INFOPLIST_FILE = tvOSExample/Info.plist; 577 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 578 | PRODUCT_BUNDLE_IDENTIFIER = com.yannickloriot.tvOSExample; 579 | PRODUCT_NAME = "$(TARGET_NAME)"; 580 | SDKROOT = appletvos; 581 | SWIFT_VERSION = 4.2; 582 | TARGETED_DEVICE_FAMILY = 3; 583 | TVOS_DEPLOYMENT_TARGET = 9.0; 584 | }; 585 | name = Release; 586 | }; 587 | CE525D921BF3833700429200 /* Debug */ = { 588 | isa = XCBuildConfiguration; 589 | buildSettings = { 590 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 591 | CURRENT_PROJECT_VERSION = 1; 592 | DEFINES_MODULE = YES; 593 | DYLIB_COMPATIBILITY_VERSION = 1; 594 | DYLIB_CURRENT_VERSION = 1; 595 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 596 | GCC_GENERATE_TEST_COVERAGE_FILES = YES; 597 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; 598 | INFOPLIST_FILE = Splitflap/Info.plist; 599 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 600 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 601 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 602 | PRODUCT_BUNDLE_IDENTIFIER = com.yannickloriot.Splitflap; 603 | PRODUCT_NAME = "$(TARGET_NAME)"; 604 | SKIP_INSTALL = YES; 605 | SWIFT_VERSION = 5.0; 606 | VERSIONING_SYSTEM = "apple-generic"; 607 | VERSION_INFO_PREFIX = ""; 608 | }; 609 | name = Debug; 610 | }; 611 | CE525D931BF3833700429200 /* Release */ = { 612 | isa = XCBuildConfiguration; 613 | buildSettings = { 614 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 615 | CURRENT_PROJECT_VERSION = 1; 616 | DEFINES_MODULE = YES; 617 | DYLIB_COMPATIBILITY_VERSION = 1; 618 | DYLIB_CURRENT_VERSION = 1; 619 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 620 | GCC_GENERATE_TEST_COVERAGE_FILES = YES; 621 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; 622 | INFOPLIST_FILE = Splitflap/Info.plist; 623 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 624 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 625 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 626 | PRODUCT_BUNDLE_IDENTIFIER = com.yannickloriot.Splitflap; 627 | PRODUCT_NAME = "$(TARGET_NAME)"; 628 | SKIP_INSTALL = YES; 629 | SWIFT_VERSION = 5.0; 630 | VERSIONING_SYSTEM = "apple-generic"; 631 | VERSION_INFO_PREFIX = ""; 632 | }; 633 | name = Release; 634 | }; 635 | CE525DB41BF4FBC800429200 /* Debug */ = { 636 | isa = XCBuildConfiguration; 637 | buildSettings = { 638 | INFOPLIST_FILE = SplitflapTests/Info.plist; 639 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 640 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 641 | PRODUCT_BUNDLE_IDENTIFIER = com.yannickloriot.SplitflapTests; 642 | PRODUCT_NAME = "$(TARGET_NAME)"; 643 | SWIFT_VERSION = 4.2; 644 | }; 645 | name = Debug; 646 | }; 647 | CE525DB51BF4FBC800429200 /* Release */ = { 648 | isa = XCBuildConfiguration; 649 | buildSettings = { 650 | INFOPLIST_FILE = SplitflapTests/Info.plist; 651 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 652 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 653 | PRODUCT_BUNDLE_IDENTIFIER = com.yannickloriot.SplitflapTests; 654 | PRODUCT_NAME = "$(TARGET_NAME)"; 655 | SWIFT_VERSION = 4.2; 656 | }; 657 | name = Release; 658 | }; 659 | CEB139B01BF266DD00DE6BA9 /* Debug */ = { 660 | isa = XCBuildConfiguration; 661 | buildSettings = { 662 | ALWAYS_SEARCH_USER_PATHS = NO; 663 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 664 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 665 | CLANG_CXX_LIBRARY = "libc++"; 666 | CLANG_ENABLE_MODULES = YES; 667 | CLANG_ENABLE_OBJC_ARC = YES; 668 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 669 | CLANG_WARN_BOOL_CONVERSION = YES; 670 | CLANG_WARN_COMMA = YES; 671 | CLANG_WARN_CONSTANT_CONVERSION = YES; 672 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 673 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 674 | CLANG_WARN_EMPTY_BODY = YES; 675 | CLANG_WARN_ENUM_CONVERSION = YES; 676 | CLANG_WARN_INFINITE_RECURSION = YES; 677 | CLANG_WARN_INT_CONVERSION = YES; 678 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 679 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 680 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 681 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 682 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 683 | CLANG_WARN_STRICT_PROTOTYPES = YES; 684 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 685 | CLANG_WARN_UNREACHABLE_CODE = YES; 686 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 687 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 688 | COPY_PHASE_STRIP = NO; 689 | DEBUG_INFORMATION_FORMAT = dwarf; 690 | ENABLE_STRICT_OBJC_MSGSEND = YES; 691 | ENABLE_TESTABILITY = YES; 692 | GCC_C_LANGUAGE_STANDARD = gnu99; 693 | GCC_DYNAMIC_NO_PIC = NO; 694 | GCC_NO_COMMON_BLOCKS = YES; 695 | GCC_OPTIMIZATION_LEVEL = 0; 696 | GCC_PREPROCESSOR_DEFINITIONS = ( 697 | "DEBUG=1", 698 | "$(inherited)", 699 | ); 700 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 701 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 702 | GCC_WARN_UNDECLARED_SELECTOR = YES; 703 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 704 | GCC_WARN_UNUSED_FUNCTION = YES; 705 | GCC_WARN_UNUSED_VARIABLE = YES; 706 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 707 | MTL_ENABLE_DEBUG_INFO = YES; 708 | ONLY_ACTIVE_ARCH = YES; 709 | SDKROOT = iphoneos; 710 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 711 | SWIFT_VERSION = 4.2; 712 | TARGETED_DEVICE_FAMILY = "1,2"; 713 | }; 714 | name = Debug; 715 | }; 716 | CEB139B11BF266DD00DE6BA9 /* Release */ = { 717 | isa = XCBuildConfiguration; 718 | buildSettings = { 719 | ALWAYS_SEARCH_USER_PATHS = NO; 720 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 721 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 722 | CLANG_CXX_LIBRARY = "libc++"; 723 | CLANG_ENABLE_MODULES = YES; 724 | CLANG_ENABLE_OBJC_ARC = YES; 725 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 726 | CLANG_WARN_BOOL_CONVERSION = YES; 727 | CLANG_WARN_COMMA = YES; 728 | CLANG_WARN_CONSTANT_CONVERSION = YES; 729 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 730 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 731 | CLANG_WARN_EMPTY_BODY = YES; 732 | CLANG_WARN_ENUM_CONVERSION = YES; 733 | CLANG_WARN_INFINITE_RECURSION = YES; 734 | CLANG_WARN_INT_CONVERSION = YES; 735 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 736 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 737 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 738 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 739 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 740 | CLANG_WARN_STRICT_PROTOTYPES = YES; 741 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 742 | CLANG_WARN_UNREACHABLE_CODE = YES; 743 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 744 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 745 | COPY_PHASE_STRIP = NO; 746 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 747 | ENABLE_NS_ASSERTIONS = NO; 748 | ENABLE_STRICT_OBJC_MSGSEND = YES; 749 | GCC_C_LANGUAGE_STANDARD = gnu99; 750 | GCC_NO_COMMON_BLOCKS = YES; 751 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 752 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 753 | GCC_WARN_UNDECLARED_SELECTOR = YES; 754 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 755 | GCC_WARN_UNUSED_FUNCTION = YES; 756 | GCC_WARN_UNUSED_VARIABLE = YES; 757 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 758 | MTL_ENABLE_DEBUG_INFO = NO; 759 | SDKROOT = iphoneos; 760 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 761 | SWIFT_VERSION = 4.2; 762 | TARGETED_DEVICE_FAMILY = "1,2"; 763 | VALIDATE_PRODUCT = YES; 764 | }; 765 | name = Release; 766 | }; 767 | CEB139B31BF266DD00DE6BA9 /* Debug */ = { 768 | isa = XCBuildConfiguration; 769 | buildSettings = { 770 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 771 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 772 | INFOPLIST_FILE = SplitflapExample/Info.plist; 773 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 774 | PRODUCT_BUNDLE_IDENTIFIER = com.yannickloriot.SplitflapExample; 775 | PRODUCT_NAME = "$(TARGET_NAME)"; 776 | SWIFT_VERSION = 5.0; 777 | }; 778 | name = Debug; 779 | }; 780 | CEB139B41BF266DD00DE6BA9 /* Release */ = { 781 | isa = XCBuildConfiguration; 782 | buildSettings = { 783 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 784 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 785 | INFOPLIST_FILE = SplitflapExample/Info.plist; 786 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 787 | PRODUCT_BUNDLE_IDENTIFIER = com.yannickloriot.SplitflapExample; 788 | PRODUCT_NAME = "$(TARGET_NAME)"; 789 | SWIFT_VERSION = 5.0; 790 | }; 791 | name = Release; 792 | }; 793 | /* End XCBuildConfiguration section */ 794 | 795 | /* Begin XCConfigurationList section */ 796 | CE5040681C050024007D9E9F /* Build configuration list for PBXNativeTarget "tvOSExample" */ = { 797 | isa = XCConfigurationList; 798 | buildConfigurations = ( 799 | CE5040661C050024007D9E9F /* Debug */, 800 | CE5040671C050024007D9E9F /* Release */, 801 | ); 802 | defaultConfigurationIsVisible = 0; 803 | defaultConfigurationName = Release; 804 | }; 805 | CE525D911BF3833700429200 /* Build configuration list for PBXNativeTarget "Splitflap" */ = { 806 | isa = XCConfigurationList; 807 | buildConfigurations = ( 808 | CE525D921BF3833700429200 /* Debug */, 809 | CE525D931BF3833700429200 /* Release */, 810 | ); 811 | defaultConfigurationIsVisible = 0; 812 | defaultConfigurationName = Release; 813 | }; 814 | CE525DB61BF4FBC800429200 /* Build configuration list for PBXNativeTarget "SplitflapTests" */ = { 815 | isa = XCConfigurationList; 816 | buildConfigurations = ( 817 | CE525DB41BF4FBC800429200 /* Debug */, 818 | CE525DB51BF4FBC800429200 /* Release */, 819 | ); 820 | defaultConfigurationIsVisible = 0; 821 | defaultConfigurationName = Release; 822 | }; 823 | CEB1399B1BF266DD00DE6BA9 /* Build configuration list for PBXProject "SplitflapExample" */ = { 824 | isa = XCConfigurationList; 825 | buildConfigurations = ( 826 | CEB139B01BF266DD00DE6BA9 /* Debug */, 827 | CEB139B11BF266DD00DE6BA9 /* Release */, 828 | ); 829 | defaultConfigurationIsVisible = 0; 830 | defaultConfigurationName = Release; 831 | }; 832 | CEB139B21BF266DD00DE6BA9 /* Build configuration list for PBXNativeTarget "SplitflapExample" */ = { 833 | isa = XCConfigurationList; 834 | buildConfigurations = ( 835 | CEB139B31BF266DD00DE6BA9 /* Debug */, 836 | CEB139B41BF266DD00DE6BA9 /* Release */, 837 | ); 838 | defaultConfigurationIsVisible = 0; 839 | defaultConfigurationName = Release; 840 | }; 841 | /* End XCConfigurationList section */ 842 | }; 843 | rootObject = CEB139981BF266DD00DE6BA9 /* Project object */; 844 | } 845 | --------------------------------------------------------------------------------