├── .swift-version ├── codecov.yml ├── Example ├── TVExample │ ├── 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 │ └── Base.lproj │ │ └── Main.storyboard ├── DynamicButtonExample │ ├── Images.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── MyCustomLine.swift │ ├── DynamicButtonCellView.swift │ ├── Info.plist │ ├── ViewController.swift │ └── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard ├── DynamicButtonExample.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ ├── DynamicButtonTests.xcscheme │ │ ├── DynamicButton.xcscheme │ │ └── DynamicButtonUITests.xcscheme ├── DynamicButton │ ├── DynamicButton.h │ └── Info.plist ├── DynamicButtonExampleTests │ ├── Info.plist │ ├── XCTTestCaseTemplate.swift │ └── DynamicButtonExampleTests.swift └── DynamicButtonExampleUITests │ ├── Info.plist │ └── DynamicButtonExampleUITests.swift ├── .gitignore ├── Package.swift ├── .travis.yml ├── DynamicButton.podspec ├── LICENSE ├── Sources ├── DynamicButtonStyles │ ├── DynamicButtonStyleLocation.swift │ ├── DynamicButtonStyleDot.swift │ ├── DynamicButtonStyleVerticalLine.swift │ ├── DynamicButtonStyleHorizontalLine.swift │ ├── DynamicButtonStyleClose.swift │ ├── DynamicButtonStylePlus.swift │ ├── DynamicButtonStyleCirclePlus.swift │ ├── DynamicButtonStyleCircleClose.swift │ ├── DynamicButtonStylePause.swift │ ├── DynamicButtonStyleHamburger.swift │ ├── DynamicButtonStyleNone.swift │ ├── DynamicButtonStyleCheckMark.swift │ ├── DynamicButtonStyleArrowUp.swift │ ├── DynamicButtonStyleArrowLeft.swift │ ├── DynamicButtonStyleCaretUp.swift │ ├── DynamicButtonStyleStop.swift │ ├── DynamicButtonStyleArrowDown.swift │ ├── DynamicButtonStyleCaretDown.swift │ ├── DynamicButtonStyleCaretLeft.swift │ ├── DynamicButtonStyleArrowRight.swift │ ├── DynamicButtonStyleCaretRight.swift │ ├── DynamicButtonStylePlay.swift │ ├── DynamicButtonStyleVerticalMoreOptions.swift │ ├── DynamicButtonStyleHorizontalMoreOptions.swift │ ├── DynamicButtonStyleReload.swift │ ├── DynamicButtonStyleDownload.swift │ ├── DynamicButtonStyleRewind.swift │ └── DynamicButtonStyleFastForward.swift ├── DynamicButtonPathVector.swift ├── DynamicButtonBuildableStyle.swift ├── PathHelper.swift ├── DynamicButtonStyle.swift └── DynamicButton.swift ├── CHANGELOG.md └── README.md /.swift-version: -------------------------------------------------------------------------------- 1 | 5 2 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | ignore: 3 | - Example/* 4 | - Tests/* 5 | -------------------------------------------------------------------------------- /Example/TVExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/DynamicButtonExample/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/TVExample/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/TVExample/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/TVExample/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/TVExample/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/TVExample/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/TVExample/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/DynamicButtonExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/TVExample/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 | *.swiftpm 20 | -------------------------------------------------------------------------------- /Example/TVExample/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/TVExample/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/TVExample/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/TVExample/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/TVExample/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/TVExample/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/DynamicButtonExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/TVExample/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 | } -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "DynamicButton", 6 | products: [ 7 | .library(name: "DynamicButton", targets: ["DynamicButton"]), 8 | ], 9 | targets: [ 10 | .target( 11 | name: "DynamicButton", 12 | dependencies: [], 13 | path: "Sources"), 14 | ] 15 | ) 16 | -------------------------------------------------------------------------------- /Example/TVExample/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/TVExample/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.2 3 | script: 4 | - xcodebuild -version 5 | # - xcodebuild -project Example/DynamicButtonExample.xcodeproj -scheme DynamicButtonUITests -sdk iphonesimulator -destination "OS=11.4,name=iPhone 8" -configuration Release ONLY_ACTIVE_ARCH=YES -enableCodeCoverage YES test 6 | - xcodebuild -project Example/DynamicButtonExample.xcodeproj -scheme DynamicButtonTests -sdk iphonesimulator -destination "OS=12.0,name=iPad Air" -configuration Release ONLY_ACTIVE_ARCH=YES -enableCodeCoverage YES test 7 | after_success: 8 | - bash <(curl -s https://codecov.io/bash) -cF ios 9 | -------------------------------------------------------------------------------- /Example/TVExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TVExample 4 | // 5 | // Created by Yannick LORIOT on 31/10/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/DynamicButton/DynamicButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicButton.h 3 | // DynamicButton 4 | // 5 | // Created by Yannick LORIOT on 07/10/15. 6 | // Copyright © 2015 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for DynamicButton. 12 | FOUNDATION_EXPORT double DynamicButtonVersionNumber; 13 | 14 | //! Project version string for DynamicButton. 15 | FOUNDATION_EXPORT const unsigned char DynamicButtonVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Example/DynamicButtonExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DynamicButtonExample 4 | // 5 | // Created by Yannick LORIOT on 06/09/15. 6 | // Copyright (c) 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/TVExample/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 | -------------------------------------------------------------------------------- /DynamicButton.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'DynamicButton' 3 | s.version = '6.2.1' 4 | s.license = 'MIT' 5 | s.summary = 'Yet another animated flat buttons in Swift' 6 | s.homepage = 'https://github.com/yannickl/DynamicButton.git' 7 | s.social_media_url = 'https://twitter.com/yannickloriot' 8 | s.authors = { 'Yannick Loriot' => 'contact@yannickloriot.com' } 9 | s.source = { :git => 'https://github.com/yannickl/DynamicButton.git', :tag => s.version } 10 | s.screenshot = 'http://yannickloriot.com/resources/dynamicbutton.gif' 11 | 12 | s.ios.deployment_target = '8.0' 13 | s.tvos.deployment_target = '9.0' 14 | 15 | s.ios.framework = 'UIKit' 16 | s.tvos.framework = 'UIKit' 17 | 18 | s.source_files = 'Sources/**/*.swift' 19 | s.requires_arc = true 20 | end 21 | -------------------------------------------------------------------------------- /Example/DynamicButtonExampleTests/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/DynamicButtonExampleUITests/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/DynamicButton/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 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/DynamicButtonExample/MyCustomLine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomButtonStyle.swift 3 | // DynamicButtonExample 4 | // 5 | // Created by Yannick LORIOT on 31/03/2017. 6 | // Copyright © 2017 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Diagonal line style: \ 12 | struct MyCustomLine: DynamicButtonBuildableStyle { 13 | let pathVector: DynamicButtonPathVector 14 | 15 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 16 | let r = size / 2 17 | let c = cos(CGFloat.pi * 0.3) 18 | let s = sin(CGFloat.pi * 0.3) 19 | 20 | let p1 = CGMutablePath() 21 | p1.move(to: CGPoint(x: center.x + r * c, y: center.y + r * s)) 22 | p1.addLine(to: CGPoint(x: center.x - r * c, y: center.y - r * s)) 23 | 24 | pathVector = DynamicButtonPathVector(p1: p1, p2: p1, p3: p1, p4: p1) 25 | } 26 | 27 | /// "MyCustomLine" style. 28 | static var styleName: String { 29 | return "MyCustomLine" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Example/TVExample/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 | $(MARKETING_VERSION) 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 | -------------------------------------------------------------------------------- /Example/DynamicButtonExampleTests/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 | -------------------------------------------------------------------------------- /Example/DynamicButtonExample/DynamicButtonCellView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicButtonCellView.swift 3 | // DynamicButtonExample 4 | // 5 | // Created by Yannick LORIOT on 13/09/15. 6 | // Copyright (c) 2015 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol DynamicButtonCellDelegate: class { 12 | func styleDidSelected(style: DynamicButton.Style) 13 | } 14 | 15 | class DynamicButtonCellView: UICollectionViewCell { 16 | @IBOutlet weak var dynamicButton: DynamicButton! 17 | 18 | weak var delegate: DynamicButtonCellDelegate? 19 | 20 | var buttonStyle: DynamicButton.Style = .hamburger { 21 | didSet { 22 | dynamicButton.setStyle(buttonStyle, animated: false) 23 | } 24 | } 25 | 26 | var lineWidth: CGFloat = 2 { 27 | didSet { 28 | dynamicButton.lineWidth = lineWidth 29 | } 30 | } 31 | 32 | var strokeColor: UIColor = .black { 33 | didSet { 34 | dynamicButton.strokeColor = strokeColor 35 | } 36 | } 37 | 38 | var highlightStokeColor: UIColor? = nil { 39 | didSet { 40 | dynamicButton.highlightStokeColor = highlightStokeColor 41 | } 42 | } 43 | 44 | // MARK: - UIFocusEnvironment Methods 45 | weak override var preferredFocusedView: UIView? { 46 | get { 47 | return dynamicButton 48 | } 49 | } 50 | 51 | // MARK: - Action Methods 52 | 53 | @IBAction func dynamicButtonAction(_ sender: AnyObject) { 54 | delegate?.styleDidSelected(style: buttonStyle) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Example/DynamicButtonExample/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 | $(MARKETING_VERSION) 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/DynamicButtonExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // DynamicButtonExample 4 | // 5 | // Created by Yannick LORIOT on 06/09/15. 6 | // Copyright (c) 2015 Yannick LORIOT. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, DynamicButtonCellDelegate { 12 | @IBOutlet weak var dynamicButtonCollectionView: UICollectionView! 13 | @IBOutlet weak var dynamicButton: DynamicButton! 14 | 15 | private let CellIdentifier = "DynamicButtonCell" 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | dynamicButton.style = .custom(MyCustomLine.self) 21 | } 22 | 23 | override func viewDidLayoutSubviews() { 24 | super.viewDidLayoutSubviews() 25 | 26 | dynamicButton.layer.cornerRadius = dynamicButton.bounds.width / 2 27 | } 28 | 29 | // MARK: - UICollectionView DataSource Methods 30 | 31 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 32 | return DynamicButton.Style.all.count 33 | } 34 | 35 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 36 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CellIdentifier, for: indexPath) as! DynamicButtonCellView 37 | 38 | cell.buttonStyle = DynamicButton.Style.all[indexPath.row] 39 | cell.delegate = self 40 | 41 | return cell 42 | } 43 | 44 | func collectionView(_ collectionView: UICollectionView, canFocusItemAt indexPath: IndexPath) -> Bool { 45 | return true 46 | } 47 | 48 | // MARK: - DynamicButtonCell Delegate Methods 49 | 50 | func styleDidSelected(style: DynamicButton.Style) { 51 | dynamicButton.setStyle(style, animated: true) 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleLocation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicButtonStyleLocation.swift 3 | // DynamicButton 4 | // 5 | // Created by VICTOR WENG on 2018-10-22. 6 | // 7 | 8 | import UIKit 9 | 10 | /// Location symbol 11 | struct DynamicButtonStyleLocation: DynamicButtonBuildableStyle { 12 | let pathVector: DynamicButtonPathVector 13 | 14 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 15 | let ratio = size/60 16 | let p3 = PathHelper.circle(atCenter: CGPoint(x:offset.x+30*ratio, y:offset.y+23*ratio), radius: size / 5 - lineWidth) 17 | 18 | let p1 = UIBezierPath() 19 | p1.move(to: CGPoint(x: offset.x+30*ratio, y: offset.y+2.0*ratio)) 20 | p1.addCurve(to: CGPoint(x: offset.x+7.3*ratio, y: offset.y+23*ratio), controlPoint1: CGPoint(x: offset.x+17.5*ratio, y: offset.y+2.0*ratio), controlPoint2: CGPoint(x: offset.x+7.3*ratio, y: offset.y+10.4*ratio)) 21 | p1.addCurve(to: CGPoint(x: offset.x+30*ratio, y: offset.y+58.0*ratio), controlPoint1: CGPoint(x: offset.x+7.3*ratio, y: offset.y+35.6*ratio), controlPoint2: CGPoint(x: offset.x+30*ratio, y: offset.y+58.0*ratio)) 22 | p1.addCurve(to: CGPoint(x: offset.x+53.1*ratio, y: offset.y+23*ratio), controlPoint1: CGPoint(x: offset.x+30*ratio, y: offset.y+58.0*ratio), controlPoint2: CGPoint(x: offset.x+53.1*ratio, y: offset.y+35.7*ratio)) 23 | p1.addCurve(to: CGPoint(x: offset.x+30*ratio, y: offset.y+2.0*ratio), controlPoint1: CGPoint(x: offset.x+53.1*ratio, y: offset.y+10.3*ratio), controlPoint2: CGPoint(x: offset.x+42.9*ratio, y: offset.y+2.0*ratio)) 24 | p1.close() 25 | 26 | pathVector = DynamicButtonPathVector(p1: p3, p2: p1.cgPath, p3: p3, p4: p3) 27 | } 28 | 29 | /// "Location" style. 30 | static var styleName: String { 31 | return "Location" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleDot.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Dot symbol style: . 30 | struct DynamicButtonStyleDot: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let p1 = UIBezierPath(roundedRect: CGRect(x: center.x - lineWidth / 2, y: center.y - lineWidth / 2, width: lineWidth, height: lineWidth), cornerRadius: size / 2).cgPath 35 | 36 | pathVector = DynamicButtonPathVector(p1: p1, p2: p1, p3: p1, p4: p1) 37 | } 38 | 39 | /// "Dot" style. 40 | static var styleName: String { 41 | return "Dot" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleVerticalLine.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Vertical line style: | 30 | struct DynamicButtonStyleVerticalLine: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let midSize = size / 2 - lineWidth 35 | let p1 = PathHelper.line(atCenter: center, radius: midSize, angle: .pi / 2) 36 | 37 | pathVector = DynamicButtonPathVector(p1: p1, p2: p1, p3: p1, p4: p1) 38 | } 39 | 40 | /// "Vertical Line" style. 41 | static var styleName: String { 42 | return "Line - Vertical" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleHorizontalLine.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Horizontal line style: ― 30 | struct DynamicButtonStyleHorizontalLine: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let midSize = size / 2 - lineWidth 35 | let p1 = PathHelper.line(atCenter: center, radius: midSize, angle: 0) 36 | 37 | pathVector = DynamicButtonPathVector(p1: p1, p2: p1, p3: p1, p4: p1) 38 | } 39 | 40 | /// "Horizontal Line" style. 41 | static var styleName: String { 42 | return "Line - Horizontal" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleClose.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Close symbol style: X 30 | struct DynamicButtonStyleClose: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let halfSize = size / 2 35 | 36 | let p12 = PathHelper.line(atCenter: center, radius: halfSize, angle: .pi / 4) 37 | let p34 = PathHelper.line(atCenter: center, radius: halfSize, angle: .pi / -4) 38 | 39 | pathVector = DynamicButtonPathVector(p1: p12, p2: p12, p3: p34, p4: p34) 40 | } 41 | 42 | /// "Close" style. 43 | static var styleName: String { 44 | return "Close" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStylePlus.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Plus symbol style: + 30 | struct DynamicButtonStylePlus: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let halfSize = size / 2 - lineWidth / 2 35 | 36 | let p12 = PathHelper.line(atCenter: center, radius: halfSize, angle: .pi / 2) 37 | let p34 = PathHelper.line(atCenter: center, radius: halfSize, angle: 0) 38 | 39 | pathVector = DynamicButtonPathVector(p1: p12, p2: p12, p3: p34, p4: p34) 40 | } 41 | 42 | /// "Plus" style. 43 | static var styleName: String { 44 | return "Plus" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleCirclePlus.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Plus symbol surrounded by a circle style 30 | struct DynamicButtonStyleCirclePlus: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let p1 = PathHelper.line(atCenter: center, radius: size / 3.2, angle: .pi / 2) 35 | let p2 = PathHelper.line(atCenter: center, radius: size / 3.2, angle: 0) 36 | let p3 = PathHelper.circle(atCenter: center, radius: size / 2 - lineWidth) 37 | 38 | pathVector = DynamicButtonPathVector(p1: p1, p2: p1, p3: p2, p4: p3) 39 | } 40 | 41 | /// "Circle Plus" style. 42 | static var styleName: String { 43 | return "Circle Plus" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleCircleClose.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Close symbol surrounded by a circle style 30 | struct DynamicButtonStyleCircleClose: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let p1 = PathHelper.line(atCenter: center, radius: size / 3.2, angle: .pi / 4) 35 | let p2 = PathHelper.line(atCenter: center, radius: size / 3.2, angle: .pi / -4) 36 | let p3 = PathHelper.circle(atCenter: center, radius: size / 2 - lineWidth) 37 | 38 | pathVector = DynamicButtonPathVector(p1: p1, p2: p1, p3: p2, p4: p3) 39 | } 40 | 41 | /// "Circle Close" style. 42 | static var styleName: String { 43 | return "Circle Close" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Example/DynamicButtonExample/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStylePause.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Pause symbol style: ‖ 30 | struct DynamicButtonStylePause: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let size = size / 3 35 | 36 | let leftOffset = CGPoint(x: size / -2, y: 0) 37 | let rightOffset = CGPoint(x: size / 2, y: 0) 38 | 39 | let p12 = PathHelper.line(atCenter: center, radius: size, angle: .pi / 2, offset: leftOffset) 40 | let p34 = PathHelper.line(atCenter: center, radius: size, angle: .pi / 2, offset: rightOffset) 41 | 42 | pathVector = DynamicButtonPathVector(p1: p12, p2: p12, p3: p34, p4: p34) 43 | } 44 | 45 | /// "Pause" style. 46 | static var styleName: String { 47 | return "Player - Pause" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleHamburger.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Hamburger button style: ≡ 30 | struct DynamicButtonStyleHamburger: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let midSize = size / 2 - lineWidth 35 | 36 | let p1 = PathHelper.line(atCenter: center, radius: midSize, angle: 0, offset: CGPoint(x: 0, y: size / -3.2)) 37 | let p2 = PathHelper.line(atCenter: center, radius: midSize, angle: 0) 38 | let p3 = PathHelper.line(atCenter: center, radius: midSize, angle: 0, offset: CGPoint(x: 0, y: size / 3.2)) 39 | 40 | pathVector = DynamicButtonPathVector(p1: p1, p2: p2, p3: p3, p4: p2) 41 | } 42 | 43 | /// "Hamburger" style. 44 | static var styleName: String { 45 | return "Hamburger" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/DynamicButtonPathVector.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 path vector is a structure compound of 4 paths (p1, p2, p3, p4). It defines the geometric shape used to draw a `DynamicButton`. 31 | */ 32 | public struct DynamicButtonPathVector { 33 | /// The path p1. 34 | public let p1: CGPath 35 | 36 | /// The path p2. 37 | public let p2: CGPath 38 | 39 | /// The path p3. 40 | public let p3: CGPath 41 | 42 | /// The path p4. 43 | public let p4: CGPath 44 | 45 | /// Default constructor. 46 | public init(p1 : CGPath, p2 : CGPath, p3 : CGPath, p4 : CGPath) { 47 | self.p1 = p1 48 | self.p2 = p2 49 | self.p3 = p3 50 | self.p4 = p4 51 | } 52 | 53 | /// The path vectore whose each path are equals to zero. 54 | public static let zero: DynamicButtonPathVector = DynamicButtonPathVector(p1: CGMutablePath(), p2: CGMutablePath(), p3: CGMutablePath(), p4: CGMutablePath()) 55 | } 56 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleNone.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// No style 30 | struct DynamicButtonStyleNone: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let p1 = UIBezierPath(rect: CGRect(x: center.x - size, y: center.y - size, width: 0, height: 0)).cgPath 35 | let p2 = UIBezierPath(rect: CGRect(x: center.x + size, y: center.y - size, width: 0, height: 0)).cgPath 36 | let p3 = UIBezierPath(rect: CGRect(x: center.x - size, y: center.y + size, width: 0, height: 0)).cgPath 37 | let p4 = UIBezierPath(rect: CGRect(x: center.x + size, y: center.y + size, width: 0, height: 0)).cgPath 38 | 39 | pathVector = DynamicButtonPathVector(p1: p1, p2: p2, p3: p3, p4: p4) 40 | } 41 | 42 | /// "None" style. 43 | static var styleName: String { 44 | return "" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleCheckMark.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Check mark style: ✓ 30 | struct DynamicButtonStyleCheckMark: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let headPoint = CGPoint(x: center.x, y: center.y) 35 | let leftPoint = CGPoint(x: offset.x + size / 4, y: offset.y + size / 4) 36 | let rightPoint = CGPoint(x: offset.x + size, y: offset.y) 37 | let offsetPoint = CGPoint(x: -size / 8, y: size / 4) 38 | 39 | let p1 = PathHelper.line(from: headPoint, to: leftPoint, offset: offsetPoint) 40 | let p2 = PathHelper.line(from: headPoint, to: rightPoint, offset: offsetPoint) 41 | 42 | pathVector = DynamicButtonPathVector(p1: p1, p2: p1, p3: p2, p4: p2) 43 | } 44 | 45 | /// "Check Mark" style. 46 | static var styleName: String { 47 | return "Check Mark" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Example/DynamicButtonExample.xcodeproj/xcshareddata/xcschemes/DynamicButtonTests.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 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleArrowUp.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Upwards arrow style: ↑ 30 | struct DynamicButtonStyleArrowUp: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let bottomPoint = CGPoint(x: center.x, y: offset.y + size - lineWidth / 2) 35 | let headPoint = CGPoint(x: center.x, y: offset.y + lineWidth) 36 | let leftPoint = CGPoint(x: center.x - size / 3.2, y: offset.y + size / 3.2) 37 | let rightPoint = CGPoint(x: center.x + size / 3.2, y: offset.y + size / 3.2) 38 | 39 | let p1 = PathHelper.line(from: bottomPoint, to: headPoint) 40 | let p2 = PathHelper.line(from: headPoint, to: leftPoint) 41 | let p3 = PathHelper.line(from: headPoint, to: rightPoint) 42 | 43 | pathVector = DynamicButtonPathVector(p1: p1, p2: p2, p3: p3, p4: p1) 44 | } 45 | 46 | /// "Arrow Up" style. 47 | static var styleName: String { 48 | return "Arrow Up" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleArrowLeft.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Leftwards arrow style: ← 30 | struct DynamicButtonStyleArrowLeft: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let rightPoint = CGPoint(x: offset.x + size - lineWidth / 2, y: center.y) 35 | let headPoint = CGPoint(x: offset.x + lineWidth, y: center.y) 36 | let topPoint = CGPoint(x: offset.x + size / 3.2, y: center.y + size / 3.2) 37 | let bottomPoint = CGPoint(x: offset.x + size / 3.2, y: center.y - size / 3.2) 38 | 39 | let p1 = PathHelper.line(from: rightPoint, to: headPoint) 40 | let p2 = PathHelper.line(from: headPoint, to: topPoint) 41 | let p3 = PathHelper.line(from: headPoint, to: bottomPoint) 42 | 43 | pathVector = DynamicButtonPathVector(p1: p1, p2: p2, p3: p3, p4: p1) 44 | } 45 | 46 | /// "Arrow Left" style. 47 | static var styleName: String { 48 | return "Arrow Left" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleCaretUp.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Up caret style: ⌃ 30 | struct DynamicButtonStyleCaretUp: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let thirdSize = size / 3 35 | let sixthSize = size / 6 36 | 37 | let a = CGPoint(x: center.x, y: center.y - sixthSize) 38 | let b = CGPoint(x: center.x - thirdSize, y: center.y + sixthSize) 39 | let c = CGPoint(x: center.x + thirdSize, y: center.y + sixthSize) 40 | 41 | let offsetFromCenter = PathHelper.gravityPointOffset(fromCenter: center, a: a, b: b, c: c) 42 | 43 | let p12 = PathHelper.line(from: a, to: b, offset: offsetFromCenter) 44 | let p34 = PathHelper.line(from: a, to: c, offset: offsetFromCenter) 45 | 46 | pathVector = DynamicButtonPathVector(p1: p12, p2: p12, p3: p34, p4: p34) 47 | } 48 | 49 | /// "Caret Up" style. 50 | static var styleName: String { 51 | return "Caret Up" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleStop.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Stop symbol style: ◼ \{U+2588} 30 | struct DynamicButtonStyleStop: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let thirdSize = size / 3 35 | 36 | let a = CGPoint(x: center.x - thirdSize, y: center.y - thirdSize) 37 | let b = CGPoint(x: center.x - thirdSize, y: center.y + thirdSize) 38 | let c = CGPoint(x: center.x + thirdSize, y: center.y + thirdSize) 39 | let d = CGPoint(x: center.x + thirdSize, y: center.y - thirdSize) 40 | 41 | let p1 = PathHelper.line(from: a, to: b) 42 | let p2 = PathHelper.line(from: b, to: c) 43 | let p3 = PathHelper.line(from: c, to: d) 44 | let p4 = PathHelper.line(from: d, to: a) 45 | 46 | pathVector = DynamicButtonPathVector(p1: p1, p2: p2, p3: p3, p4: p4) 47 | } 48 | 49 | /// "Stop" style. 50 | static var styleName: String { 51 | return "Player - Stop" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleArrowDown.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Downwards arrow style: ↓ 30 | struct DynamicButtonStyleArrowDown: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let topPoint = CGPoint(x: center.x, y: offset.y + lineWidth / 2) 35 | let headPoint = CGPoint(x: center.x, y: offset.y + size - lineWidth) 36 | let leftPoint = CGPoint(x: center.x - size / 3.2, y: offset.y + size - size / 3.2) 37 | let rightPoint = CGPoint(x: center.x + size / 3.2, y: offset.y + size - size / 3.2) 38 | 39 | let p1 = PathHelper.line(from: topPoint, to: headPoint) 40 | let p2 = PathHelper.line(from: headPoint, to: leftPoint) 41 | let p3 = PathHelper.line(from: headPoint, to: rightPoint) 42 | 43 | pathVector = DynamicButtonPathVector(p1: p1, p2: p2, p3: p3, p4: p1) 44 | } 45 | 46 | /// "Arrow Down" style. 47 | static var styleName: String { 48 | return "Arrow Down" 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleCaretDown.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Down caret style: ⌄ 30 | struct DynamicButtonStyleCaretDown: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let thirdSize = size / 3 35 | let sixthSize = size / 6 36 | 37 | let a = CGPoint(x: center.x, y: center.y + sixthSize) 38 | let b = CGPoint(x: center.x - thirdSize, y: center.y - sixthSize) 39 | let c = CGPoint(x: center.x + thirdSize, y: center.y - sixthSize) 40 | 41 | let offsetFromCenter = PathHelper.gravityPointOffset(fromCenter: center, a: a, b: b, c: c) 42 | 43 | let p12 = PathHelper.line(from: a, to: b, offset: offsetFromCenter) 44 | let p34 = PathHelper.line(from: a, to: c, offset: offsetFromCenter) 45 | 46 | pathVector = DynamicButtonPathVector(p1: p12, p2: p12, p3: p34, p4: p34) 47 | } 48 | 49 | /// "Caret Down" style. 50 | static var styleName: String { 51 | return "Caret Down" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleCaretLeft.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Left caret style: ‹ 30 | struct DynamicButtonStyleCaretLeft: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let thirdSize = size / 3 35 | let sixthSize = size / 6 36 | 37 | let a = CGPoint(x: center.x - sixthSize, y: center.y) 38 | let b = CGPoint(x: center.x + sixthSize, y: center.y + thirdSize) 39 | let c = CGPoint(x: center.x + sixthSize, y: center.y - thirdSize) 40 | 41 | let offsetFromCenter = PathHelper.gravityPointOffset(fromCenter: center, a: a, b: b, c: c) 42 | 43 | let p12 = PathHelper.line(from: a, to: b, offset: offsetFromCenter) 44 | let p34 = PathHelper.line(from: a, to: c, offset: offsetFromCenter) 45 | 46 | pathVector = DynamicButtonPathVector(p1: p12, p2: p12, p3: p34, p4: p34) 47 | } 48 | 49 | /// "Caret Left" style. 50 | static var styleName: String { 51 | return "Caret Left" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleArrowRight.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Rightwards arrow style: → 30 | struct DynamicButtonStyleArrowRight: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let leftPoint = CGPoint(x: offset.x + lineWidth / 2, y: center.y) 35 | let headPoint = CGPoint(x: offset.x + size - lineWidth, y: center.y) 36 | let topPoint = CGPoint(x: offset.x + size - size / 3.2, y: center.y + size / 3.2) 37 | let bottomPoint = CGPoint(x: offset.x + size - size / 3.2, y: center.y - size / 3.2) 38 | 39 | let p1 = PathHelper.line(from: leftPoint, to: headPoint) 40 | let p2 = PathHelper.line(from: headPoint, to: topPoint) 41 | let p3 = PathHelper.line(from: headPoint, to: bottomPoint) 42 | 43 | pathVector = DynamicButtonPathVector(p1: p1, p2: p2, p3: p3, p4: p1) 44 | } 45 | 46 | /// "Arrow Right" style. 47 | static var styleName: String { 48 | return "Arrow Right" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleCaretRight.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Left caret style: › 30 | struct DynamicButtonStyleCaretRight: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let thirdSize = size / 3 35 | let sixthSize = size / 6 36 | 37 | let a = CGPoint(x: center.x + sixthSize, y: center.y) 38 | let b = CGPoint(x: center.x - sixthSize, y: center.y + thirdSize) 39 | let c = CGPoint(x: center.x - sixthSize, y: center.y - thirdSize) 40 | 41 | let offsetFromCenter = PathHelper.gravityPointOffset(fromCenter: center, a: a, b: b, c: c) 42 | 43 | let p12 = PathHelper.line(from: a, to: b, offset: offsetFromCenter) 44 | let p34 = PathHelper.line(from: a, to: c, offset: offsetFromCenter) 45 | 46 | pathVector = DynamicButtonPathVector(p1: p12, p2: p12, p3: p34, p4: p34) 47 | } 48 | 49 | /// "Caret Right" style. 50 | static var styleName: String { 51 | return "Caret Right" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStylePlay.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Play symbol style: ► \{U+25B6} 30 | struct DynamicButtonStylePlay: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let thirdSize = size / 3 35 | let sixthSize = size / 6 36 | 37 | let a = CGPoint(x: center.x - thirdSize, y: center.y - thirdSize) 38 | let b = CGPoint(x: center.x - thirdSize, y: center.y + thirdSize) 39 | let c = CGPoint(x: center.x + sixthSize, y: center.y) 40 | 41 | let ofc = PathHelper.gravityPointOffset(fromCenter: center, a: a, b: b, c: c) 42 | 43 | let p1 = PathHelper.line(from: a, to: b, offset: ofc) 44 | let p2 = PathHelper.line(from: b, to: c, offset: ofc) 45 | let p3 = PathHelper.line(from: a, to: c, offset: ofc) 46 | 47 | pathVector = DynamicButtonPathVector(p1: p1, p2: p2, p3: p3, p4: p3) 48 | } 49 | 50 | /// "Play" style. 51 | static var styleName: String { 52 | return "Player - Play" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleVerticalMoreOptions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Vertical more options style: ⋮ 30 | struct DynamicButtonStyleVerticalMoreOptions: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let y = center.y - lineWidth / 2 35 | let midSize = size / 2 36 | 37 | let p1 = UIBezierPath(roundedRect: CGRect(x: center.x - lineWidth / 2, y: y - midSize + lineWidth, width: lineWidth, height: lineWidth), cornerRadius: lineWidth / 2).cgPath 38 | let p2 = UIBezierPath(roundedRect: CGRect(x: center.x - lineWidth / 2, y: y, width: lineWidth, height: lineWidth), cornerRadius: lineWidth / 2).cgPath 39 | let p3 = UIBezierPath(roundedRect: CGRect(x: center.x - lineWidth / 2, y: y + midSize - lineWidth, width: lineWidth, height: lineWidth), cornerRadius: lineWidth / 2).cgPath 40 | 41 | pathVector = DynamicButtonPathVector(p1: p1, p2: p2, p3: p3, p4: p2) 42 | } 43 | 44 | /// "Vertical More Options" style. 45 | static var styleName: String { 46 | return "More Options - Vertical" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleHorizontalMoreOptions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Horizontal more options style: … 30 | struct DynamicButtonStyleHorizontalMoreOptions: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let x = center.x - lineWidth / 2 35 | let midSize = size / 2 36 | 37 | let p1 = UIBezierPath(roundedRect: CGRect(x: x - midSize + lineWidth, y: center.y - lineWidth / 2, width: lineWidth, height: lineWidth), cornerRadius: lineWidth / 2).cgPath 38 | let p2 = UIBezierPath(roundedRect: CGRect(x: x, y: center.y - lineWidth / 2, width: lineWidth, height: lineWidth), cornerRadius: lineWidth / 2).cgPath 39 | let p3 = UIBezierPath(roundedRect: CGRect(x: x + midSize - lineWidth, y: center.y - lineWidth / 2, width: lineWidth, height: lineWidth), cornerRadius: lineWidth / 2).cgPath 40 | 41 | pathVector = DynamicButtonPathVector(p1: p1, p2: p2, p3: p3, p4: p2) 42 | } 43 | 44 | /// "Horizontal More Options" style. 45 | static var styleName: String { 46 | return "More Options - Horizontal" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleReload.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Reload symbol style: ↻ 30 | struct DynamicButtonStyleReload: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let sixthSize = size / 6 35 | let fifthPi = CGFloat.pi / 5.5 36 | 37 | let endAngle = ((3 * CGFloat.pi) / 2) - fifthPi 38 | let endPoint = PathHelper.point(fromCenter: center, radius: size / 2 - lineWidth, angle: endAngle) 39 | 40 | let curveBezierPath = UIBezierPath(arcCenter: center, radius: size / 2 - lineWidth, startAngle: -fifthPi, endAngle: endAngle, clockwise: true) 41 | 42 | let p1 = PathHelper.line(from: endPoint, to: PathHelper.point(fromCenter: endPoint, radius: sixthSize, angle: .pi)) 43 | let p2 = PathHelper.line(from: endPoint, to: PathHelper.point(fromCenter: endPoint, radius: sixthSize, angle: CGFloat.pi / 2)) 44 | let p34 = curveBezierPath.cgPath 45 | 46 | pathVector = DynamicButtonPathVector(p1: p1, p2: p2, p3: p34, p4: p34) 47 | } 48 | 49 | /// "Reload" style. 50 | static var styleName: String { 51 | return "Reload" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleDownload.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Downwards triangle-headed arrow to bar style: ⤓ 30 | struct DynamicButtonStyleDownload: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let topPoint = CGPoint(x: center.x, y: offset.y + lineWidth / 2) 35 | let headPoint = CGPoint(x: center.x, y: offset.y + size - lineWidth) 36 | let leftPoint = CGPoint(x: center.x - size / 3.2, y: offset.y + size - lineWidth) 37 | let rightPoint = CGPoint(x: center.x + size / 3.2, y: offset.y + size - lineWidth) 38 | 39 | let p1 = PathHelper.line(from: topPoint, to: headPoint) 40 | let p2 = PathHelper.line(from: headPoint, to: CGPoint(x: center.x - size / 3.2, y: offset.y + size - size / 3.2)) 41 | let p3 = PathHelper.line(from: headPoint, to: CGPoint(x: center.x + size / 3.2, y: offset.y + size - size / 3.2)) 42 | let p4 = PathHelper.line(from: leftPoint, to: rightPoint) 43 | 44 | pathVector = DynamicButtonPathVector(p1: p1, p2: p2, p3: p3, p4: p4) 45 | } 46 | 47 | /// "Download" style. 48 | static var styleName: String { 49 | return "Download" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleRewind.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Rewind symbol style: ≪ 30 | struct DynamicButtonStyleRewind: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let thirdSize = size / 3 35 | let sixthSize = size / 6 36 | 37 | let a = CGPoint(x: center.x - sixthSize, y: center.y) 38 | let b = CGPoint(x: center.x + sixthSize, y: center.y + thirdSize) 39 | let c = CGPoint(x: center.x + sixthSize, y: center.y - thirdSize) 40 | 41 | let ofc = PathHelper.gravityPointOffset(fromCenter: center, a: a, b: b, c: c) 42 | 43 | let p1 = PathHelper.line(from: a, to: b, offset: CGPoint(x: ofc.x - sixthSize, y: ofc.y)) 44 | let p2 = PathHelper.line(from: a, to: b, offset: CGPoint(x: ofc.x + sixthSize, y: ofc.y)) 45 | let p3 = PathHelper.line(from: a, to: c, offset: CGPoint(x: ofc.x - sixthSize, y: ofc.y)) 46 | let p4 = PathHelper.line(from: a, to: c, offset: CGPoint(x: ofc.x + sixthSize, y: ofc.y)) 47 | 48 | pathVector = DynamicButtonPathVector(p1: p1, p2: p2, p3: p3, p4: p4) 49 | } 50 | 51 | /// "Rewind" style. 52 | static var styleName: String { 53 | return "Player - Rewind" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyles/DynamicButtonStyleFastForward.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Fast forward style: ≫ 30 | struct DynamicButtonStyleFastForward: DynamicButtonBuildableStyle { 31 | let pathVector: DynamicButtonPathVector 32 | 33 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 34 | let thirdSize = size / 3 35 | let sixthSize = size / 6 36 | 37 | let a = CGPoint(x: center.x + sixthSize, y: center.y) 38 | let b = CGPoint(x: center.x - sixthSize, y: center.y + thirdSize) 39 | let c = CGPoint(x: center.x - sixthSize, y: center.y - thirdSize) 40 | 41 | let ofc = PathHelper.gravityPointOffset(fromCenter: center, a: a, b: b, c: c) 42 | 43 | let p1 = PathHelper.line(from: a, to: b, offset: CGPoint(x: ofc.x + sixthSize, y: ofc.y)) 44 | let p2 = PathHelper.line(from: a, to: b, offset: CGPoint(x: ofc.x - sixthSize, y: ofc.y)) 45 | let p3 = PathHelper.line(from: a, to: c, offset: CGPoint(x: ofc.x + sixthSize, y: ofc.y)) 46 | let p4 = PathHelper.line(from: a, to: c, offset: CGPoint(x: ofc.x - sixthSize, y: ofc.y)) 47 | 48 | pathVector = DynamicButtonPathVector(p1: p1, p2: p2, p3: p3, p4: p4) 49 | } 50 | 51 | /// "Fast Forward" style. 52 | static var styleName: String { 53 | return "Player - Fast Forward" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/DynamicButtonBuildableStyle.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 dynamic button style must adopt the buildable protocol. It allows the `DynamicButton` to know how build and display the style's shape. 31 | 32 | A `DynamicButtonBuildableStyle` provides a `pathVector` vector property and an `init` method to build it. 33 | */ 34 | public protocol DynamicButtonBuildableStyle: CustomStringConvertible { 35 | /// The path vector used by the `DynamicButton` to display the shape. 36 | var pathVector: DynamicButtonPathVector { get } 37 | 38 | /** 39 | The init provides some informations in order to build the `pathVector`. 40 | 41 | - parameter center: The center point of the area where path must be drawn. 42 | - parameter size: The size of the area. This is a float because the area is always a square. 43 | - parameter offset: The offset point of the button. 44 | - parameter lineWidth: The line width used to draw the stroke. 45 | */ 46 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) 47 | 48 | /// The style name as a string. 49 | static var styleName: String { get } 50 | } 51 | 52 | extension DynamicButtonBuildableStyle { 53 | internal func animationConfigurations(_ layer1: CAShapeLayer, layer2: CAShapeLayer, layer3: CAShapeLayer, layer4: CAShapeLayer) -> [(keyPath: String, layer: CAShapeLayer, newValue: CGPath?, key: String)] { 54 | return [(keyPath: "path", layer: layer4, newValue: pathVector.p4, key: "animateLine4Path"), 55 | (keyPath: "path", layer: layer1, newValue: pathVector.p1, key: "animateLine1Path"), 56 | (keyPath: "path", layer: layer2, newValue: pathVector.p2, key: "animateLine2Path"), 57 | (keyPath: "path", layer: layer3, newValue: pathVector.p3, key: "animateLine3Path")] 58 | } 59 | 60 | public var description: String { 61 | return Self.styleName 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Example/DynamicButtonExample.xcodeproj/xcshareddata/xcschemes/DynamicButton.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 | -------------------------------------------------------------------------------- /Sources/PathHelper.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | /// Helper methods to manipulate paths. 30 | internal class PathHelper { 31 | // MARK: - Representing the Button as Drawing Lines 32 | 33 | /// Creates a circle from a given center point and a radius. 34 | class func circle(atCenter center: CGPoint, radius: CGFloat) -> CGPath { 35 | let path = CGMutablePath() 36 | 37 | path.move(to: CGPoint(x: center.x + radius, y: center.y)) 38 | path.addArc(center: CGPoint(x:center.x, y:center.y), radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: false) 39 | 40 | return path 41 | } 42 | 43 | /// Creates an oblique line using a center point, a radius and an angle. 44 | class func line(atCenter center: CGPoint, radius: CGFloat, angle: CGFloat, offset: CGPoint = .zero) -> CGPath { 45 | let path = CGMutablePath() 46 | 47 | let c = cos(angle) 48 | let s = sin(angle) 49 | 50 | path.move(to: CGPoint(x: center.x + offset.x + radius * c, y: center.y + offset.y + radius * s)) 51 | path.addLine(to: CGPoint(x: center.x + offset.x - radius * c, y: center.y + offset.y - radius * s)) 52 | 53 | return path 54 | } 55 | 56 | /// Creates a line between two point. 57 | class func line(from startPoint: CGPoint, to endPoint: CGPoint, offset: CGPoint = .zero) -> CGPath { 58 | let path = CGMutablePath() 59 | 60 | path.move(to: CGPoint(x: offset.x + startPoint.x, y: offset.y + startPoint.y)) 61 | path.addLine(to: CGPoint(x: offset.x + endPoint.x, y: offset.y + endPoint.y)) 62 | 63 | return path 64 | } 65 | 66 | // MARK: - Trigonometry Methods 67 | 68 | /// Compute the gravity center from 3 points. 69 | class func gravityPointOffset(fromCenter center: CGPoint, a: CGPoint, b: CGPoint, c: CGPoint) -> CGPoint { 70 | let gravityCenter = CGPoint(x: (a.x + b.x + c.x) / 3, y: (a.y + b.y + c.y) / 3) 71 | 72 | return CGPoint(x: center.x - gravityCenter.x, y: center.y - gravityCenter.y) 73 | } 74 | 75 | // Compute the destination point from a center, an angle and a radius 76 | class func point(fromCenter center: CGPoint, radius: CGFloat, angle: CGFloat) -> CGPoint { 77 | return CGPoint(x: center.x + radius * cos(angle), y: center.y + radius * sin(angle)) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Example/DynamicButtonExample/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Example/DynamicButtonExample.xcodeproj/xcshareddata/xcschemes/DynamicButtonUITests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 79 | 80 | 81 | 82 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ## [Version 6.2.0](https://github.com/yannickl/DynamicButton/releases/tag/6.2.0) 4 | Released on 2019-05-27. 5 | 6 | **Swift 5 supports** 7 | 8 | ## [Version 6.1.0](https://github.com/yannickl/DynamicButton/releases/tag/6.1.0) 9 | Released on 2018-11-18. 10 | 11 | - [ADD] `location` button style 12 | 13 | ## [Version 6.0.0](https://github.com/yannickl/DynamicButton/releases/tag/6.0.0) 14 | Released on 2018-09-23. 15 | 16 | **Swift 4.2 supports** 17 | 18 | ## [Version 5.0.1](https://github.com/yannickl/DynamicButton/releases/tag/5.0.1) 19 | Released on 2018-08-21. 20 | 21 | - [FIX] style with rounded tips (#21) 22 | 23 | ## [Version 5.0.0](https://github.com/yannickl/DynamicButton/releases/tag/5.0.0) 24 | Released on 2017-09-20. 25 | 26 | **Swift 4 supports** 27 | 28 | ## [Version 4.0.0](https://github.com/yannickl/DynamicButton/releases/tag/4.0.0) 29 | Released on 2017-04-02. 30 | 31 | **Swift 3.1 supports** 32 | 33 | - [REFACTORING] The `DynamicButtonStyle` is now an enum 34 | - [ADD] Each style must now adopt the `DynamicButtonBuildableStyle` protocol 35 | - [ADD] The `DynamicButtonPathVector` struct 36 | - [ADD] `styleByName` property in the `DynamicButtonStyle` 37 | - The default buildable style access controls are now `internal` 38 | 39 | ## [Version 3.1.0](https://github.com/yannickl/DynamicButton/releases/tag/3.1.0) 40 | Released on 2016-11-05. 41 | 42 | - [ADD] `horizontalMoreOptions` button style 43 | - [ADD] `verticalMoreOptions` button style 44 | 45 | ## [Version 3.0.2](https://github.com/yannickl/DynamicButton/releases/tag/3.0.2) 46 | Released on 2016-10-10. 47 | 48 | - [FIX] `contentEdgeInsets` is now take into account. 49 | 50 | ## [Version 3.0.1](https://github.com/yannickl/DynamicButton/releases/tag/3.0.1) 51 | Released on 2016-09-21. 52 | 53 | - [ADD] New designated `init` 54 | 55 | ## [Version 3.0.0](https://github.com/yannickl/DynamicButton/releases/tag/3.0.0) 56 | Released on 2016-09-14. 57 | 58 | **Swift 3 supports** 59 | 60 | - [REFACTORING] Puts all constants in lowerCamelCase 61 | - [ADD] Swift Package Manager supports 62 | 63 | ## [Version 2.1.0](https://github.com/yannickl/DynamicButton/releases/tag/2.1.0) 64 | Released on 2016-05-03. 65 | 66 | - [ADD] `Reload` button style 67 | 68 | ## [Version 2.0.0](https://github.com/yannickl/DynamicButton/releases/tag/2.0.0) 69 | Released on 2016-04-21. 70 | 71 | - [REFACTORING] Replace the DynamicButton.Style enum with the DynamicButtonStyle class. 72 | - [ADD] Custom symbols are now allowed. 73 | 74 | ## [Version 1.8.0](https://github.com/yannickl/DynamicButton/releases/tag/1.8.0) 75 | Released on 2016-03-25. 76 | 77 | - [ADD] `Play` button style 78 | - [ADD] `Stop` button style 79 | 80 | ## [Version 1.7.1](https://github.com/yannickl/DynamicButton/releases/tag/1.7.1) 81 | Released on 2016-03-23. 82 | 83 | - [FIX] Center of gravity of caret symbols 84 | 85 | ## [Version 1.7.0](https://github.com/yannickl/DynamicButton/releases/tag/1.7.0) 86 | Released on 2016-03-22. 87 | 88 | - Swift 2.2 supports 89 | 90 | ## [Version 1.6.0](https://github.com/yannickl/DynamicButton/releases/tag/1.6.0) 91 | Released on 2016-02-10. 92 | 93 | - [ADD] `bounceButtonOnTouch` property 94 | 95 | ## [Version 1.5.0](https://github.com/yannickl/DynamicButton/releases/tag/1.5.0) 96 | Released on 2016-02-04. 97 | 98 | - [ADD] `highlightBackgroundColor` property 99 | 100 | ## [Version 1.4.0](https://github.com/yannickl/DynamicButton/releases/tag/1.4.0) 101 | Released on 2016-02-04. 102 | 103 | - [ADD] `None` button style 104 | 105 | ## [Version 1.3.0](https://github.com/yannickl/DynamicButton/releases/tag/1.3.0) 106 | Released on 2016-02-03. 107 | 108 | - [ADD] `Dot` button style 109 | 110 | ## [Version 1.2.0](https://github.com/yannickl/DynamicButton/releases/tag/1.2.0) 111 | Released on 2015-11-02. 112 | 113 | - [ADD] tvOS 9 support 114 | 115 | ## [Version 1.1.0](https://github.com/yannickl/DynamicButton/releases/tag/1.1.0) 116 | Released on 2015-10-12. 117 | 118 | - [FIX] iOS 8.x support 119 | 120 | ## [Version 1.0.0](https://github.com/yannickl/DynamicButton/releases/tag/1.0.0) 121 | Released on 2015-10-10. 122 | 123 | - Initialize with style 124 | - Available button styles: 125 | - `ArrowDown` 126 | - `ArrowLeft` 127 | - `ArrowRight` 128 | - `ArrowUp` 129 | - `CaretDown` 130 | - `CaretLeft` 131 | - `CaretRight` 132 | - `CaretUp` 133 | - `CheckMark` 134 | - `CircleClose` 135 | - `CirclePlus` 136 | - `Close` 137 | - `Download` 138 | - `FastForward` 139 | - `Hamburger` 140 | - `HorizontalLine` 141 | - `Pause` 142 | - `Plus` 143 | - `Rewind` 144 | - `VerticalLine` 145 | - `lineWidth` property 146 | - `strokeColor` property 147 | - `highlightStokeColor` property 148 | - Cocoapods support 149 | - Carthage support 150 | -------------------------------------------------------------------------------- /Example/DynamicButtonExampleUITests/DynamicButtonExampleUITests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 XCTest 28 | 29 | class DynamicButtonExampleUITests: XCTestCase { 30 | override func setUp() { 31 | super.setUp() 32 | 33 | // Put setup code here. This method is called before the invocation of each test method in the class. 34 | 35 | // In UI tests it is usually best to stop immediately when a failure occurs. 36 | continueAfterFailure = false 37 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 38 | XCUIApplication().launch() 39 | 40 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 41 | XCUIDevice.shared.orientation = .landscapeRight 42 | } 43 | 44 | func testStyleSelection() { 45 | let collectionViewsQuery = XCUIApplication().collectionViews 46 | 47 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Arrow Down"]/*[[".cells.buttons[\"Arrow Down\"]",".buttons[\"Arrow Down\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 48 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Arrow Left"]/*[[".cells.buttons[\"Arrow Left\"]",".buttons[\"Arrow Left\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 49 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Arrow Right"]/*[[".cells.buttons[\"Arrow Right\"]",".buttons[\"Arrow Right\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 50 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Arrow Up"]/*[[".cells.buttons[\"Arrow Up\"]",".buttons[\"Arrow Up\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 51 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Caret Down"]/*[[".cells.buttons[\"Caret Down\"]",".buttons[\"Caret Down\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 52 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Caret Left"]/*[[".cells.buttons[\"Caret Left\"]",".buttons[\"Caret Left\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 53 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Caret Right"]/*[[".cells.buttons[\"Caret Right\"]",".buttons[\"Caret Right\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 54 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Caret Up"]/*[[".cells.buttons[\"Caret Up\"]",".buttons[\"Caret Up\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 55 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Check Mark"]/*[[".cells.buttons[\"Check Mark\"]",".buttons[\"Check Mark\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 56 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Circle Close"].press(forDuration: 0.7);/*[[".cells.buttons[\"Circle Close\"]",".tap()",".press(forDuration: 0.7);",".buttons[\"Circle Close\"]"],[[[-1,3,1],[-1,0,1]],[[-1,2],[-1,1]]],[0,0]]@END_MENU_TOKEN@*/ 57 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Circle Plus"].press(forDuration: 1.0);/*[[".cells.buttons[\"Circle Plus\"]",".tap()",".press(forDuration: 1.0);",".buttons[\"Circle Plus\"]"],[[[-1,3,1],[-1,0,1]],[[-1,2],[-1,1]]],[0,0]]@END_MENU_TOKEN@*/ 58 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Close"].press(forDuration: 0.8);/*[[".cells.buttons[\"Close\"]",".tap()",".press(forDuration: 0.8);",".buttons[\"Close\"]"],[[[-1,3,1],[-1,0,1]],[[-1,2],[-1,1]]],[0,0]]@END_MENU_TOKEN@*/ 59 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Dot"]/*[[".cells.buttons[\"Dot\"]",".buttons[\"Dot\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 60 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Download"]/*[[".cells.buttons[\"Download\"]",".buttons[\"Download\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 61 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Hamburger"]/*[[".cells.buttons[\"Hamburger\"]",".buttons[\"Hamburger\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 62 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Line - Horizontal"]/*[[".cells.buttons[\"Line - Horizontal\"]",".buttons[\"Line - Horizontal\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 63 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Line - Vertical"]/*[[".cells.buttons[\"Line - Vertical\"]",".buttons[\"Line - Vertical\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 64 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["More Options - Horizontal"]/*[[".cells.buttons[\"More Options - Horizontal\"]",".buttons[\"More Options - Horizontal\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 65 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["More Options - Vertical"]/*[[".cells.buttons[\"More Options - Vertical\"]",".buttons[\"More Options - Vertical\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 66 | collectionViewsQuery/*@START_MENU_TOKEN@*/.cells.buttons["Player - Fast Forward"]/*[[".cells.buttons[\"Player - Fast Forward\"]",".buttons[\"Player - Fast Forward\"]"],[[[-1,1],[-1,0]]],[1]]@END_MENU_TOKEN@*/.tap() 67 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Player - Pause"]/*[[".cells.buttons[\"Player - Pause\"]",".buttons[\"Player - Pause\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 68 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Player - Play"]/*[[".cells.buttons[\"Player - Play\"]",".buttons[\"Player - Play\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 69 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Player - Rewind"]/*[[".cells.buttons[\"Player - Rewind\"]",".buttons[\"Player - Rewind\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 70 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Player - Stop"]/*[[".cells.buttons[\"Player - Stop\"]",".buttons[\"Player - Stop\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 71 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Plus"]/*[[".cells.buttons[\"Plus\"]",".buttons[\"Plus\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 72 | collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["Reload"]/*[[".cells.buttons[\"Reload\"]",".buttons[\"Reload\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() 73 | 74 | XCUIApplication().collectionViews.children(matching: .cell).element(boundBy: 0).otherElements.children(matching: .button).element.tap() 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Sources/DynamicButtonStyle.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | extension DynamicButton { 30 | /** 31 | A collection of predefined buton styles. 32 | */ 33 | public enum Style { 34 | /// Downwards arrow: ↓ 35 | case arrowDown 36 | /// Leftwards arrow: ← 37 | case arrowLeft 38 | /// Rightwards arrow: → 39 | case arrowRight 40 | /// Upwards arrow: ↑ 41 | case arrowUp 42 | /// Down caret: ⌄ 43 | case caretDown 44 | /// Left caret: ‹ 45 | case caretLeft 46 | /// Left caret: › 47 | case caretRight 48 | /// Up caret: ⌃ 49 | case caretUp 50 | /// Check mark: ✓ 51 | case checkMark 52 | /// Close symbol surrounded by a circle 53 | case circleClose 54 | /// Plus symbol surrounded by a circle 55 | case circlePlus 56 | /// Close symbol: X 57 | case close 58 | /// Dot symbol: . 59 | case dot 60 | /// Downwards triangle-headed arrow to bar: ⤓ 61 | case download 62 | /// Fast forward: ≫ 63 | case fastForward 64 | /// Hamburger button: ≡ 65 | case hamburger 66 | /// Horizontal line: ― 67 | case horizontalLine 68 | /// Horizontal more options: … 69 | case horizontalMoreOptions 70 | /// No style 71 | case none 72 | /// Pause symbol: ‖ 73 | case pause 74 | /// Play symbol: ► \{U+25B6} 75 | case play 76 | /// Plus symbol: + 77 | case plus 78 | /// Reload symbol: ↻ 79 | case reload 80 | /// Rewind symbol: ≪ 81 | case rewind 82 | /// Stop symbol: ◼ \{U+2588} 83 | case stop 84 | /// Vertical line: | 85 | case verticalLine 86 | /// Vertical more options: ⋮ 87 | case verticalMoreOptions 88 | /// Location: 89 | case location 90 | /// Custom style with its associate buildable style 91 | case custom(DynamicButtonBuildableStyle.Type) 92 | 93 | /// Returns all the available styles 94 | public static let all: [Style] = Style.buildableByStyle.map { $0 }.sorted { $0.value.styleName < $1.value.styleName }.map { $0.0 } 95 | 96 | // MARK: - Building Button Styles 97 | 98 | public func build(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) -> DynamicButtonBuildableStyle { 99 | switch self { 100 | case .custom(let buildable): 101 | return buildable.init(center: center, size: size, offset: offset, lineWidth: lineWidth) 102 | default: 103 | let buildable = Style.buildableByStyle[self]! 104 | 105 | return buildable.init(center: center, size: size, offset: offset, lineWidth: lineWidth) 106 | } 107 | } 108 | 109 | public var description: String { 110 | let buildable = Style.buildableByStyle[self]! 111 | 112 | return buildable.styleName 113 | } 114 | 115 | // MARK: - Convenient Style Description 116 | 117 | private static let buildableByStyle: [Style: DynamicButtonBuildableStyle.Type] = [ 118 | none: DynamicButtonStyleNone.self, 119 | arrowDown: DynamicButtonStyleArrowDown.self, 120 | arrowLeft: DynamicButtonStyleArrowLeft.self, 121 | arrowRight: DynamicButtonStyleArrowRight.self, 122 | arrowUp: DynamicButtonStyleArrowUp.self, 123 | caretDown: DynamicButtonStyleCaretDown.self, 124 | caretLeft: DynamicButtonStyleCaretLeft.self, 125 | caretRight: DynamicButtonStyleCaretRight.self, 126 | caretUp: DynamicButtonStyleCaretUp.self, 127 | checkMark: DynamicButtonStyleCheckMark.self, 128 | circleClose: DynamicButtonStyleCircleClose.self, 129 | circlePlus: DynamicButtonStyleCirclePlus.self, 130 | close: DynamicButtonStyleClose.self, 131 | plus: DynamicButtonStylePlus.self, 132 | dot: DynamicButtonStyleDot.self, 133 | download: DynamicButtonStyleDownload.self, 134 | reload: DynamicButtonStyleReload.self, 135 | rewind: DynamicButtonStyleRewind.self, 136 | fastForward: DynamicButtonStyleFastForward.self, 137 | play: DynamicButtonStylePlay.self, 138 | pause: DynamicButtonStylePause.self, 139 | stop: DynamicButtonStyleStop.self, 140 | hamburger: DynamicButtonStyleHamburger.self, 141 | horizontalLine: DynamicButtonStyleHorizontalLine.self, 142 | verticalLine: DynamicButtonStyleVerticalLine.self, 143 | horizontalMoreOptions: DynamicButtonStyleHorizontalMoreOptions.self, 144 | verticalMoreOptions: DynamicButtonStyleVerticalMoreOptions.self, 145 | location: DynamicButtonStyleLocation.self 146 | ] 147 | 148 | /** 149 | Returns the style with the given style name. 150 | 151 | - parameter styleName: the style name defined by the `DynamicButtonBuildableStyle` protocol. 152 | */ 153 | public static let styleByName: [String: Style] = Style.buildableByStyle.reduce([:]) { (acc, entry) in 154 | var acc = acc 155 | 156 | acc[entry.1.styleName] = entry.0 157 | 158 | return acc 159 | } 160 | } 161 | } 162 | 163 | extension DynamicButton.Style: Equatable { 164 | public static func ==(lhs: DynamicButton.Style, rhs: DynamicButton.Style) -> Bool { 165 | switch (lhs, rhs) { 166 | case (.none, .none), (.arrowDown, .arrowDown), (.arrowLeft, .arrowLeft), (.arrowRight, .arrowRight), (.arrowUp, .arrowUp), (.caretDown, .caretDown), (.caretLeft, .caretLeft), (.caretRight, .caretRight), (.caretUp, .caretUp), (.checkMark, .checkMark), (.circleClose, .circleClose), (.circlePlus, .circlePlus), (.close, .close), (.plus, .plus), (.dot, .dot), (.download, .download), (.reload, .reload), (.rewind, .rewind), (.fastForward, .fastForward), (.play, .play), (.pause, .pause), (.stop, .stop), (.hamburger, .hamburger), (.horizontalLine, .horizontalLine), (.verticalLine, .verticalLine), (.horizontalMoreOptions, .horizontalMoreOptions), (.verticalMoreOptions, .verticalMoreOptions), (.location, .location): 167 | return true 168 | case (.custom(let b1), .custom(let b2)): 169 | return b1.styleName == b2.styleName 170 | default: 171 | return false 172 | } 173 | } 174 | } 175 | 176 | extension DynamicButton.Style: Hashable { 177 | public func hash(into hasher: inout Hasher) { 178 | switch self { 179 | case .none: 180 | hasher.combine(0) 181 | case .custom(let buildable): 182 | hasher.combine(buildable.styleName) 183 | default: 184 | hasher.combine(1) 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /Example/DynamicButtonExampleTests/DynamicButtonExampleTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 DynamicButtonExampleTests: XCTTestCaseTemplate { 31 | func testDefaultStyle() { 32 | let dynamicButton = DynamicButton() 33 | 34 | XCTAssert(dynamicButton.style == .hamburger) 35 | } 36 | 37 | func testInitWithStyle() { 38 | let hamburger = DynamicButton(style: .hamburger) 39 | XCTAssert(hamburger.style == .hamburger) 40 | 41 | let arrowDown = DynamicButton(style: .arrowDown) 42 | XCTAssert(arrowDown.style == .arrowDown) 43 | 44 | let circlePlus = DynamicButton(style: .circlePlus) 45 | XCTAssert(circlePlus.style == .circlePlus) 46 | } 47 | 48 | func testSetStyle() { 49 | let dynamicButton = DynamicButton() 50 | 51 | dynamicButton.style = .close 52 | XCTAssert(dynamicButton.style == .close) 53 | 54 | dynamicButton.style = .download 55 | XCTAssert(dynamicButton.style == .download) 56 | 57 | dynamicButton.style = .fastForward 58 | XCTAssert(dynamicButton.style == .fastForward) 59 | } 60 | 61 | func testSetStyleAnimated() { 62 | let dynamicButton = DynamicButton() 63 | 64 | dynamicButton.setStyle(.close, animated: true) 65 | XCTAssert(dynamicButton.style == .close) 66 | 67 | dynamicButton.setStyle(.download, animated: false) 68 | XCTAssert(dynamicButton.style == .download) 69 | 70 | dynamicButton.setStyle(.fastForward, animated: true) 71 | XCTAssert(dynamicButton.style == .fastForward) 72 | } 73 | 74 | func testLineWidth() { 75 | let dynamicButton = DynamicButton() 76 | 77 | XCTAssert(dynamicButton.lineWidth == 2, "Default line width should be equal to 2") 78 | XCTAssert(dynamicButton.line1Layer.lineWidth == dynamicButton.lineWidth, "Default line width should be equal to 2") 79 | XCTAssert(dynamicButton.line2Layer.lineWidth == dynamicButton.lineWidth, "Default line width should be equal to 2") 80 | XCTAssert(dynamicButton.line3Layer.lineWidth == dynamicButton.lineWidth, "Default line width should be equal to 2") 81 | XCTAssert(dynamicButton.line4Layer.lineWidth == dynamicButton.lineWidth, "Default line width should be equal to 2") 82 | 83 | dynamicButton.lineWidth = 6 84 | 85 | XCTAssertEqual(dynamicButton.lineWidth, 6, "Line width should be equal to 6") 86 | XCTAssertEqual(dynamicButton.line1Layer.lineWidth, dynamicButton.lineWidth, "Line width should be equal to 6") 87 | XCTAssertEqual(dynamicButton.line2Layer.lineWidth, dynamicButton.lineWidth, "Line width should be equal to 6") 88 | XCTAssertEqual(dynamicButton.line3Layer.lineWidth, dynamicButton.lineWidth, "Line width should be equal to 6") 89 | XCTAssertEqual(dynamicButton.line4Layer.lineWidth, dynamicButton.lineWidth, "Line width should be equal to 6") 90 | } 91 | 92 | func testStrokeColor() { 93 | let dynamicButton = DynamicButton() 94 | let blackColor = UIColor.black 95 | 96 | XCTAssertEqual(dynamicButton.strokeColor, blackColor, "Default color should be black") 97 | XCTAssertEqual(dynamicButton.line1Layer.strokeColor, blackColor.cgColor, "Default color should be black") 98 | XCTAssertEqual(dynamicButton.line2Layer.strokeColor, blackColor.cgColor, "Default color should be black") 99 | XCTAssertEqual(dynamicButton.line3Layer.strokeColor, blackColor.cgColor, "Default color should be black") 100 | XCTAssertEqual(dynamicButton.line4Layer.strokeColor, blackColor.cgColor, "Default color should be black") 101 | 102 | let redColor = UIColor.red 103 | dynamicButton.strokeColor = redColor 104 | 105 | XCTAssertEqual(dynamicButton.strokeColor, redColor, "Stroke color should be red") 106 | XCTAssertEqual(dynamicButton.line1Layer.strokeColor, redColor.cgColor, "Stroke color should be red") 107 | XCTAssertEqual(dynamicButton.line2Layer.strokeColor, redColor.cgColor, "Stroke color should be red") 108 | XCTAssertEqual(dynamicButton.line3Layer.strokeColor, redColor.cgColor, "Stroke color should be red") 109 | XCTAssertEqual(dynamicButton.line4Layer.strokeColor, redColor.cgColor, "Stroke color should be red") 110 | } 111 | 112 | func testHighlightStokeColor() { 113 | let dynamicButton = DynamicButton() 114 | let blackColor = UIColor.black 115 | 116 | dynamicButton.highlightAction() 117 | 118 | XCTAssertNil(dynamicButton.highlightStokeColor, "Default highlight stroke color should be nil") 119 | XCTAssertEqual(dynamicButton.line1Layer.strokeColor, blackColor.cgColor, "Highlight stroke color should be black like the stroke color") 120 | XCTAssertEqual(dynamicButton.line2Layer.strokeColor, blackColor.cgColor, "Highlight stroke color should be black like the stroke color") 121 | XCTAssertEqual(dynamicButton.line3Layer.strokeColor, blackColor.cgColor, "Highlight stroke color should be black like the stroke color") 122 | XCTAssertEqual(dynamicButton.line4Layer.strokeColor, blackColor.cgColor, "Highlight stroke color should be black like the stroke color") 123 | 124 | let redColor = UIColor.red 125 | dynamicButton.highlightStokeColor = redColor 126 | dynamicButton.highlightAction() 127 | 128 | XCTAssertEqual(dynamicButton.highlightStokeColor, redColor, "Default color should be red") 129 | XCTAssertEqual(dynamicButton.line1Layer.strokeColor, redColor.cgColor, "Highlight stroke color should be black like the stroke color") 130 | XCTAssertEqual(dynamicButton.line2Layer.strokeColor, redColor.cgColor, "Highlight stroke color should be black like the stroke color") 131 | XCTAssertEqual(dynamicButton.line3Layer.strokeColor, redColor.cgColor, "Highlight stroke color should be black like the stroke color") 132 | XCTAssertEqual(dynamicButton.line4Layer.strokeColor, redColor.cgColor, "Highlight stroke color should be black like the stroke color") 133 | } 134 | 135 | func testHighlightAction() { 136 | let dynamicButton = DynamicButton() 137 | dynamicButton.highlightStokeColor = UIColor.green 138 | 139 | XCTAssertEqual(dynamicButton.line1Layer.strokeColor, dynamicButton.strokeColor.cgColor, "Stroke color should be green like the stroke color") 140 | 141 | dynamicButton.highlightAction() 142 | 143 | XCTAssertEqual(dynamicButton.line1Layer.strokeColor, UIColor.green.cgColor, "Stroke color should be green like the stroke color") 144 | } 145 | 146 | func testUnHighlightAction() { 147 | let dynamicButton = DynamicButton() 148 | dynamicButton.highlightStokeColor = UIColor.orange 149 | 150 | XCTAssertEqual(dynamicButton.line1Layer.strokeColor, dynamicButton.strokeColor.cgColor, "Stroke color should be green like the stroke color") 151 | 152 | dynamicButton.highlightAction() 153 | 154 | XCTAssertEqual(dynamicButton.line1Layer.strokeColor, UIColor.orange.cgColor, "Stroke color should be orange like the stroke color") 155 | 156 | dynamicButton.unhighlightAction() 157 | 158 | XCTAssertEqual(dynamicButton.line1Layer.strokeColor, dynamicButton.strokeColor.cgColor, "Stroke color should be green like the stroke color") 159 | } 160 | 161 | func testBounceButtonOnTouchDefaultValue() { 162 | let dynamicButton = DynamicButton() 163 | 164 | XCTAssertTrue(dynamicButton.bounceButtonOnTouch) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![DynamicButton](http://yannickloriot.com/resources/dynamicbutton-header.png) 2 | 3 | [![Supported Platforms](https://cocoapod-badges.herokuapp.com/p/DynamicButton/badge.svg)](http://cocoadocs.org/docsets/DynamicButton/) [![Version](https://cocoapod-badges.herokuapp.com/v/DynamicButton/badge.svg)](http://cocoadocs.org/docsets/DynamicButton/) Carthage compatible 4 | Swift Package Manager compatible [![Build Status](https://travis-ci.org/yannickl/DynamicButton.svg?branch=master)](https://travis-ci.org/yannickl/DynamicButton) [![codecov.io](http://codecov.io/github/yannickl/DynamicButton/coverage.svg?branch=master)](http://codecov.io/github/yannickl/DynamicButton?branch=master) [![codebeat badge](https://codebeat.co/badges/ed7210be-6c9d-43ff-87a0-a10c007fe1b4)](https://codebeat.co/projects/github-com-yannickl-dynamicbutton) 5 | 6 | **DynamicButton** is a powerful flat design button written in Swift to display *hamburger button* like with animated transitions between style updates. It also allows you to create your own custom symbol / style buttons! 7 | 8 |

9 | DynamicButton 10 |

11 | 12 |

13 | RequirementsUsageInstallationContributionContactLicense 14 |

15 | 16 | ## Requirements 17 | 18 | - iOS 8.0+ / tvOS 9.0+ 19 | - Xcode 10.0+ 20 | - Swift 5+ 21 | 22 | ## Usage 23 | 24 | ### Basics 25 | 26 | Here is how to create a button and setting its style: 27 | 28 | ```swift 29 | import DynamicButton 30 | 31 | let dynamicButton = DynamicButton(style: .hamburger) 32 | // Equivalent to 33 | // let dynamicButton = DynamicButton() 34 | // dynamicButton.style = .hamburger 35 | 36 | // Animate the style update 37 | dynamicButton.setStyle(.close, animated: true) 38 | ``` 39 | 40 | ### Customization 41 | 42 | Button appearance and behavior can be customized using different properties: 43 | 44 | ```swift 45 | let dynamicButton = DynamicButton() 46 | dynamicButton.lineWidth = 3 47 | dynamicButton.strokeColor = .black 48 | dynamicButton.highlightStokeColor = .gray 49 | ``` 50 | 51 | ### Supported Symbol Styles 52 | 53 | Here is the symbol list (`DynamicButton.Style`) already implemented by the library: 54 | 55 | - `.arrowDown`: downwards arrow `↓` 56 | - `.arrowLeft`: leftwards arrow `←` 57 | - `.arrowRight`: rightwards arrow `→` 58 | - `.arrowUp`: upwards arrow `↑` 59 | - `.caretDown`: down caret `⌄` 60 | - `.caretLeft`: left caret `‹` 61 | - `.caretRight`: left caret `›` 62 | - `.caretUp`: up caret: `⌃` 63 | - `.checkMark`: check mark `✓` 64 | - `.circleClose`: close symbol surrounded by a circle 65 | - `.circlePlus`: plus symbol surrounded by a circle 66 | - `.close`: close symbol `X` 67 | - `.dot`: dot symbol `.` 68 | - `.download`: downwards triangle-headed arrow to bar `⤓` 69 | - `.fastForward`: fast forward `≫` 70 | - `.hamburger`: hamburger button `≡` 71 | - `.horizontalLine`: horizontal line `―` 72 | - `.horizontalMoreOptions`: horizontal more options `…` 73 | - `.none`: no style 74 | - `.pause`: pause symbol `‖` 75 | - `.play`: play symbol `►` 76 | - `.plus`: plus symbol `+` 77 | - `.stop`: stop symbol `◼` 78 | - `.reload`: reload symbol `↻` 79 | - `.rewind`: rewind `≪` 80 | - `.verticalLine`: vertical line `|` 81 | - `.verticalMoreOptions`: vertical more options `⋮` 82 | - `.location`: location symbol 83 | 84 | ### Custom symbols 85 | 86 | To create your own symbols you have to create an object (or struct) that conforms to the `DynamicButtonBuildableStyle` protocol: 87 | 88 | ```swift 89 | /// Diagonal line style: \ 90 | struct MyCustomLine: DynamicButtonBuildableStyle { 91 | let pathVector: DynamicButtonPathVector 92 | 93 | init(center: CGPoint, size: CGFloat, offset: CGPoint, lineWidth: CGFloat) { 94 | let r = size / 2 95 | let c = cos(CGFloat.pi * 0.3) 96 | let s = sin(CGFloat.pi * 0.3) 97 | 98 | let p1 = CGMutablePath() 99 | p1.move(to: CGPoint(x: center.x + r * c, y: center.y + r * s)) 100 | p1.addLine(to: CGPoint(x: center.x - r * c, y: center.y - r * s)) 101 | 102 | pathVector = DynamicButtonPathVector(p1: p1, p2: p1, p3: p1, p4: p1) 103 | } 104 | 105 | /// "MyCustomLine" style. 106 | static var styleName: String { 107 | return "MyCustomLine" 108 | } 109 | } 110 | 111 | myButton.style = .custom(MyCustomLine.self) 112 | ``` 113 | 114 | Note that a symbol can not have more than 4 paths. 115 | 116 | ### And many more... 117 | 118 | To go further, take a look at the example project. 119 | 120 | ## Installation 121 | 122 | #### CocoaPods 123 | 124 | Install CocoaPods if not already available: 125 | 126 | ``` bash 127 | $ [sudo] gem install cocoapods 128 | $ pod setup 129 | ``` 130 | Go to the directory of your Xcode project, and Create and Edit your Podfile and add _DynamicButton_: 131 | 132 | ``` bash 133 | $ cd /path/to/MyProject 134 | $ touch Podfile 135 | $ edit Podfile 136 | source 'https://github.com/CocoaPods/Specs.git' 137 | platform :ios, '8.0' 138 | 139 | use_frameworks! 140 | pod 'DynamicButton', '~> 6.2.1' 141 | ``` 142 | 143 | Install into your project: 144 | 145 | ``` bash 146 | $ pod install 147 | ``` 148 | 149 | Open your project in Xcode from the .xcworkspace file (not the usual project file): 150 | 151 | ``` bash 152 | $ open MyProject.xcworkspace 153 | ``` 154 | 155 | You can now `import DynamicButton` framework into your files. 156 | 157 | #### Carthage 158 | 159 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that automates the process of adding frameworks to your Cocoa application. 160 | 161 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command: 162 | 163 | ```bash 164 | $ brew update 165 | $ brew install carthage 166 | ``` 167 | 168 | To integrate `DynamicButton` into your Xcode project using Carthage, specify it in your `Cartfile` file: 169 | 170 | ```ogdl 171 | github "yannickl/DynamicButton" >= 6.2.1 172 | ``` 173 | 174 | #### Swift Package Manager 175 | 176 | You can use [The Swift Package Manager](https://swift.org/package-manager) to install `DynamicButton` by adding the proper description to your `Package.swift` file: 177 | 178 | ```swift 179 | import PackageDescription 180 | 181 | let package = Package( 182 | name: "YOUR_PROJECT_NAME", 183 | dependencies: [ 184 | .package(url: "https://github.com/yannickl/DynamicButton.git", from: "6.2.1") 185 | ], 186 | // ... 187 | ) 188 | ``` 189 | 190 | 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). 191 | 192 | #### Manually 193 | 194 | [Download](https://github.com/YannickL/DynamicButton/archive/master.zip) the project and copy the `DynamicButton` folder into your project to use it in. 195 | 196 | ## Contribution 197 | 198 | Contributions are welcomed and encouraged *♡*. 199 | 200 | ## Contact 201 | 202 | Yannick Loriot 203 | - [https://21.co/yannickl/](https://21.co/yannickl/) 204 | - [https://twitter.com/yannickloriot](https://twitter.com/yannickloriot) 205 | 206 | ## License (MIT) 207 | 208 | Copyright (c) 2015-present - Yannick Loriot 209 | 210 | Permission is hereby granted, free of charge, to any person obtaining a copy 211 | of this software and associated documentation files (the "Software"), to deal 212 | in the Software without restriction, including without limitation the rights 213 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 214 | copies of the Software, and to permit persons to whom the Software is 215 | furnished to do so, subject to the following conditions: 216 | 217 | The above copyright notice and this permission notice shall be included in 218 | all copies or substantial portions of the Software. 219 | 220 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 221 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 222 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 223 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 224 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 225 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 226 | THE SOFTWARE. 227 | -------------------------------------------------------------------------------- /Example/TVExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /Sources/DynamicButton.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * DynamicButton 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 | Flat design button compounded by several lines to create several symbols like 31 | *arrows*, *checkmark*, *hamburger button*, etc. with animated transitions 32 | between each style changes. 33 | */ 34 | @IBDesignable final public class DynamicButton: UIButton { 35 | let line1Layer = CAShapeLayer() 36 | let line2Layer = CAShapeLayer() 37 | let line3Layer = CAShapeLayer() 38 | let line4Layer = CAShapeLayer() 39 | 40 | private var _style: Style = .hamburger 41 | 42 | lazy var allLayers: [CAShapeLayer] = { 43 | return [self.line1Layer, self.line2Layer, self.line3Layer, self.line4Layer] 44 | }() 45 | 46 | /** 47 | Boolean indicates whether the button can bounce when is touched. 48 | 49 | By default the value is set to true. 50 | */ 51 | public var bounceButtonOnTouch: Bool = true 52 | 53 | /** 54 | Initializes and returns a dynamic button with the specified style. 55 | 56 | You have to think to define its frame because the default one is set to {0, 0, 50, 50}. 57 | 58 | - parameter style: The style of the button. 59 | - returns: An initialized view object or nil if the object couldn't be created. 60 | */ 61 | required public init(style: Style) { 62 | super.init(frame: CGRect(x: 0, y: 0, width: 50, height: 50)) 63 | 64 | _style = style 65 | 66 | setup() 67 | } 68 | 69 | /** 70 | Initializes and returns a newly allocated view object with the specified frame rectangle and with the Hamburger style by default. 71 | 72 | - parameter frame: The frame rectangle for the view, measured in points. The origin of the frame is relative to the superview in which you plan to add it. This method uses the frame rectangle to set the center and bounds properties accordingly. 73 | - returns: An initialized view object or nil if the object couldn't be created. 74 | */ 75 | override public init(frame: CGRect) { 76 | super.init(frame: frame) 77 | 78 | setup() 79 | } 80 | 81 | required public init?(coder aDecoder: NSCoder) { 82 | super.init(coder: aDecoder) 83 | 84 | setup() 85 | } 86 | 87 | public override func layoutSubviews() { 88 | super.layoutSubviews() 89 | 90 | let width = bounds.width - (contentEdgeInsets.left + contentEdgeInsets.right) 91 | let height = bounds.height - (contentEdgeInsets.top + contentEdgeInsets.bottom) 92 | 93 | intrinsicSize = min(width, height) 94 | intrinsicOffset = CGPoint(x: contentEdgeInsets.left + (width - intrinsicSize) / 2, y: contentEdgeInsets.top + (height - intrinsicSize) / 2) 95 | 96 | setStyle(_style, animated: false) 97 | } 98 | 99 | public override func setTitle(_ title: String?, for state: UIControl.State) { 100 | super.setTitle("", for: state) 101 | } 102 | 103 | // MARK: - Managing the Button Setup 104 | 105 | /// Intrinsic square size 106 | var intrinsicSize = CGFloat(0) 107 | /// Intrinsic square offset 108 | var intrinsicOffset = CGPoint.zero 109 | 110 | func setup() { 111 | setTitle("", for: .normal) 112 | 113 | clipsToBounds = true 114 | 115 | addTarget(self, action: #selector(highlightAction), for: .touchDown) 116 | addTarget(self, action: #selector(highlightAction), for: .touchDragEnter) 117 | addTarget(self, action: #selector(unhighlightAction), for: .touchDragExit) 118 | addTarget(self, action: #selector(unhighlightAction), for: .touchUpInside) 119 | addTarget(self, action: #selector(unhighlightAction), for: .touchCancel) 120 | 121 | for sublayer in allLayers { 122 | layer.addSublayer(sublayer) 123 | } 124 | 125 | setupLayerPaths() 126 | } 127 | 128 | func setupLayerPaths() { 129 | for sublayer in allLayers { 130 | sublayer.fillColor = UIColor.clear.cgColor 131 | sublayer.anchorPoint = CGPoint(x: 0, y: 0) 132 | sublayer.lineJoin = CAShapeLayerLineJoin.round 133 | sublayer.lineCap = CAShapeLayerLineCap.round 134 | sublayer.contentsScale = layer.contentsScale 135 | sublayer.path = UIBezierPath().cgPath 136 | sublayer.lineWidth = lineWidth 137 | sublayer.strokeColor = strokeColor.cgColor 138 | } 139 | 140 | setStyle(_style, animated: false) 141 | } 142 | 143 | // MARK: - Configuring Buttons 144 | 145 | /// The button style. The setter is equivalent to the setStyle(, animated:) method with animated value to false. Defaults to Hamburger. 146 | public var style: Style { 147 | get { return _style } 148 | set (newValue) { setStyle(newValue, animated: false) } 149 | } 150 | 151 | /** 152 | Set the style of the button and animate the change if needed. 153 | 154 | - parameter style: The style of the button. 155 | - parameter animated: If true the transition between the old style and the new one is animated. 156 | */ 157 | public func setStyle(_ style: Style, animated: Bool) { 158 | _style = style 159 | 160 | let center = CGPoint(x: intrinsicOffset.x + intrinsicSize / 2, y: intrinsicOffset.y + intrinsicSize / 2) 161 | let buildable = style.build(center: center, size: intrinsicSize, offset: intrinsicOffset, lineWidth: lineWidth) 162 | 163 | applyButtonBuildable(buildable, animated: animated) 164 | } 165 | 166 | func applyButtonBuildable(_ buildable: DynamicButtonBuildableStyle, animated: Bool) { 167 | accessibilityValue = buildable.description 168 | 169 | for config in buildable.animationConfigurations(line1Layer, layer2: line2Layer, layer3: line3Layer, layer4: line4Layer) { 170 | if animated { 171 | let anim = animationWithKeyPath(config.keyPath, damping: 10) 172 | anim.fromValue = config.layer.path 173 | anim.toValue = config.newValue 174 | 175 | config.layer.add(anim, forKey: config.key) 176 | } 177 | else { 178 | config.layer.removeAllAnimations() 179 | } 180 | 181 | config.layer.path = config.newValue 182 | } 183 | } 184 | 185 | /// Specifies the line width used when stroking the button paths. Defaults to two. 186 | @IBInspectable public var lineWidth: CGFloat = 2 { 187 | didSet { setupLayerPaths() } 188 | } 189 | 190 | /// Specifies the color to fill the path's stroked outlines, or nil for no stroking. Defaults to black. 191 | @IBInspectable public var strokeColor: UIColor = .black { 192 | didSet { setupLayerPaths() } 193 | } 194 | 195 | /// Specifies the color to fill the path's stroked outlines when the button is highlighted, or nil to use the strokeColor. Defaults to nil. 196 | @IBInspectable public var highlightStokeColor: UIColor? = nil 197 | 198 | /// Specifies the color to fill the background when the button is highlighted, or nil to use the backgroundColor. Defaults to nil. 199 | @IBInspectable public var highlightBackgroundColor: UIColor? = nil 200 | 201 | // MARK: - Animating Buttons 202 | 203 | func animationWithKeyPath(_ keyPath: String, damping: CGFloat = 10, initialVelocity: CGFloat = 0, stiffness: CGFloat = 100) -> CABasicAnimation { 204 | guard #available(iOS 9, *) else { 205 | let basic = CABasicAnimation(keyPath: keyPath) 206 | basic.duration = 0.16 207 | basic.fillMode = CAMediaTimingFillMode.forwards 208 | basic.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.default) 209 | 210 | return basic 211 | } 212 | 213 | let spring = CASpringAnimation(keyPath: keyPath) 214 | spring.duration = spring.settlingDuration 215 | spring.damping = damping 216 | spring.initialVelocity = initialVelocity 217 | spring.stiffness = stiffness 218 | spring.fillMode = CAMediaTimingFillMode.forwards 219 | spring.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.default) 220 | 221 | return spring 222 | } 223 | 224 | // MARK: - Action Methods 225 | 226 | // Store the background color color variable 227 | var defaultBackgroundColor: UIColor = .clear 228 | 229 | @objc func highlightAction() { 230 | defaultBackgroundColor = backgroundColor ?? .clear 231 | backgroundColor = highlightBackgroundColor ?? defaultBackgroundColor 232 | 233 | for sublayer in allLayers { 234 | sublayer.strokeColor = (highlightStokeColor ?? strokeColor).cgColor 235 | } 236 | 237 | if bounceButtonOnTouch { 238 | let anim = animationWithKeyPath("transform.scale", damping: 20, stiffness: 1000) 239 | anim.isRemovedOnCompletion = false 240 | anim.toValue = 1.2 241 | 242 | layer.add(anim, forKey: "scaleup") 243 | } 244 | } 245 | 246 | @objc func unhighlightAction() { 247 | backgroundColor = defaultBackgroundColor 248 | 249 | for sublayer in allLayers { 250 | sublayer.strokeColor = strokeColor.cgColor 251 | } 252 | 253 | let anim = animationWithKeyPath("transform.scale", damping: 100, initialVelocity: 20) 254 | anim.isRemovedOnCompletion = false 255 | anim.toValue = 1 256 | 257 | layer.add(anim, forKey: "scaledown") 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /Example/DynamicButtonExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | --------------------------------------------------------------------------------