Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c520680100 | |||
| 04e27502df | |||
| 9f211f33af | |||
| 083d952ad7 | |||
| 7254460f60 | |||
| 9dd5b4c82d | |||
| 81c4a16180 | |||
| 4e55227fee | |||
| 3aec47f892 | |||
| b9769e418b | |||
| b29b6ed033 | |||
| 69e60152c8 | |||
| 2d2097033d | |||
| 2e7943b049 | |||
| ef8abb6499 | |||
| d9e9afcc8b | |||
| 9b0d1eded6 | |||
| ec8c1424b3 | |||
| dd75a83797 | |||
| 774cb1cf64 | |||
| c4cf9482bf | |||
| b64cda5541 | |||
| 06df7ff2b1 | |||
| ea82aeff84 | |||
| 69b65edc06 | |||
| 1604f89c72 | |||
| d8d7eaf528 | |||
| 62ad8b528f | |||
| 9a21e80ae8 | |||
| 3f202ec9cf | |||
| a4504a1502 |
@@ -0,0 +1 @@
|
||||
4.0
|
||||
+10
-3
@@ -1,6 +1,13 @@
|
||||
osx_image: xcode8
|
||||
osx_image: xcode9.2
|
||||
language: objective-c
|
||||
|
||||
xcode_project: CircleMenu.xcodeproj
|
||||
xcode_scheme: CircleMenu
|
||||
xcode_sdk: iphonesimulator
|
||||
scheme: CircleMenu
|
||||
xcode_sdk: iphonesimulator11.2
|
||||
|
||||
# SWIFT_VERSION: 4.0
|
||||
|
||||
# whitelist
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'CircleMenu'
|
||||
s.version = '2.0.1'
|
||||
s.version = '3.0.3'
|
||||
s.summary = 'Amazing animation with buttons'
|
||||
s.homepage = 'https://github.com/Ramotion/circle-menu'
|
||||
s.license = 'MIT'
|
||||
|
||||
@@ -259,17 +259,17 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0720;
|
||||
LastUpgradeCheck = 0800;
|
||||
LastUpgradeCheck = 0920;
|
||||
ORGANIZATIONNAME = "Alex K.";
|
||||
TargetAttributes = {
|
||||
8403F5781CFF2C2E007D0BD1 = {
|
||||
CreatedOnToolsVersion = 7.3.1;
|
||||
LastSwiftMigration = 0800;
|
||||
LastSwiftMigration = 0900;
|
||||
};
|
||||
84F248B21C58E65F008F12C1 = {
|
||||
CreatedOnToolsVersion = 7.2;
|
||||
DevelopmentTeam = 34MUF9YXTA;
|
||||
LastSwiftMigration = 0800;
|
||||
LastSwiftMigration = 0900;
|
||||
};
|
||||
84F248C61C58E65F008F12C1 = {
|
||||
CreatedOnToolsVersion = 7.2;
|
||||
@@ -428,7 +428,8 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ramotion.CircleMenu;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
@@ -452,7 +453,8 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ramotion.CircleMenu;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
@@ -467,14 +469,20 @@
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
@@ -502,6 +510,7 @@
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -513,14 +522,20 @@
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
@@ -541,6 +556,7 @@
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 4.0;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
@@ -557,7 +573,8 @@
|
||||
OTHER_SWIFT_FLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ramotion.dev;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 4.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -573,7 +590,8 @@
|
||||
OTHER_SWIFT_FLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ramotion.dev;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 4.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -585,7 +603,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ramotion.CircleMenuTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CircleMenu.app/CircleMenu";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CircleMenuDemo.app/CircleMenuDemo";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -597,7 +615,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ramotion.CircleMenuTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CircleMenu.app/CircleMenu";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CircleMenuDemo.app/CircleMenuDemo";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
LastUpgradeVersion = "0920"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -26,6 +26,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
@@ -36,6 +37,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
@@ -13,32 +13,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
||||
_: UIApplication,
|
||||
didFinishLaunchingWithOptions _: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func applicationWillResignActive(_ application: UIApplication) {
|
||||
|
||||
}
|
||||
|
||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
|
||||
}
|
||||
|
||||
func applicationWillEnterForeground(_ application: UIApplication) {
|
||||
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -39,6 +39,11 @@
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"size" : "1024x1024",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8150" systemVersion="15A204g" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8122"/>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
@@ -13,15 +18,35 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<animations/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2018 Ramotion. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9MI-zG-4vW">
|
||||
<rect key="frame" x="16" y="630" width="343" height="17"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<color key="textColor" red="0.96862745100000003" green="0.96862745100000003" blue="0.96862745100000003" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Circle Menu" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VWz-1T-4Oe">
|
||||
<rect key="frame" x="123" y="319" width="129" height="30"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="25"/>
|
||||
<color key="textColor" red="0.96862745100000003" green="0.96862745100000003" blue="0.96862745100000003" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.058823529409999999" green="0.078431372550000003" blue="0.1529411765" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="9MI-zG-4vW" secondAttribute="trailing" constant="16" id="0Nd-zV-ifm"/>
|
||||
<constraint firstItem="VWz-1T-4Oe" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="AUS-9v-IDH"/>
|
||||
<constraint firstItem="xb3-aO-Qok" firstAttribute="top" secondItem="9MI-zG-4vW" secondAttribute="bottom" constant="20" id="FNC-OT-bh6"/>
|
||||
<constraint firstItem="9MI-zG-4vW" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" constant="16" id="GKi-1N-UzF"/>
|
||||
<constraint firstItem="VWz-1T-4Oe" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="j7f-9j-flx"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
<point key="canvasLocation" x="52" y="374.66266866566718"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
|
||||
@@ -26,5 +26,9 @@
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIStatusBarStyle</key>
|
||||
<string>UIStatusBarStyleLightContent</string>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -9,68 +9,60 @@
|
||||
import UIKit
|
||||
|
||||
extension UIColor {
|
||||
static func color(_ red: Int, green: Int, blue: Int, alpha: Float) -> UIColor {
|
||||
return UIColor(
|
||||
colorLiteralRed: Float(1.0) / Float(255.0) * Float(red),
|
||||
green: Float(1.0) / Float(255.0) * Float(green),
|
||||
blue: Float(1.0) / Float(255.0) * Float(blue),
|
||||
alpha: alpha)
|
||||
}
|
||||
static func color(_ red: Int, green: Int, blue: Int, alpha: Float) -> UIColor {
|
||||
return UIColor(
|
||||
red: 1.0 / 255.0 * CGFloat(red),
|
||||
green: 1.0 / 255.0 * CGFloat(green),
|
||||
blue: 1.0 / 255.0 * CGFloat(blue),
|
||||
alpha: CGFloat(alpha))
|
||||
}
|
||||
}
|
||||
|
||||
class ViewController: UIViewController, CircleMenuDelegate {
|
||||
|
||||
// let colors = [UIColor.redColor(), UIColor.grayColor(), UIColor.greenColor(), UIColor.purpleColor()]
|
||||
let items: [(icon: String, color: UIColor)] = [
|
||||
("icon_home", UIColor(red:0.19, green:0.57, blue:1, alpha:1)),
|
||||
("icon_search", UIColor(red:0.22, green:0.74, blue:0, alpha:1)),
|
||||
("notifications-btn", UIColor(red:0.96, green:0.23, blue:0.21, alpha:1)),
|
||||
("settings-btn", UIColor(red:0.51, green:0.15, blue:1, alpha:1)),
|
||||
("nearby-btn", UIColor(red:1, green:0.39, blue:0, alpha:1)),
|
||||
|
||||
// let colors = [UIColor.redColor(), UIColor.grayColor(), UIColor.greenColor(), UIColor.purpleColor()]
|
||||
let items: [(icon: String, color: UIColor)] = [
|
||||
("icon_home", UIColor(red: 0.19, green: 0.57, blue: 1, alpha: 1)),
|
||||
("icon_search", UIColor(red: 0.22, green: 0.74, blue: 0, alpha: 1)),
|
||||
("notifications-btn", UIColor(red: 0.96, green: 0.23, blue: 0.21, alpha: 1)),
|
||||
("settings-btn", UIColor(red: 0.51, green: 0.15, blue: 1, alpha: 1)),
|
||||
("nearby-btn", UIColor(red: 1, green: 0.39, blue: 0, alpha: 1)),
|
||||
]
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// add button
|
||||
|
||||
// let button = CircleMenu(
|
||||
// frame: CGRect(x: 200, y: 200, width: 50, height: 50),
|
||||
// normalIcon:"icon_menu",
|
||||
// selectedIcon:"icon_close",
|
||||
// buttonsCount: 4,
|
||||
// duration: 4,
|
||||
// distance: 120)
|
||||
// button.backgroundColor = UIColor.lightGrayColor()
|
||||
// button.delegate = self
|
||||
// button.layer.cornerRadius = button.frame.size.width / 2.0
|
||||
// view.addSubview(button)
|
||||
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning() {
|
||||
super.didReceiveMemoryWarning()
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
|
||||
// MARK: <CircleMenuDelegate>
|
||||
|
||||
func circleMenu(_ circleMenu: CircleMenu, willDisplay button: UIButton, atIndex: Int) {
|
||||
button.backgroundColor = items[atIndex].color
|
||||
|
||||
button.setImage(UIImage(named: items[atIndex].icon), for: .normal)
|
||||
|
||||
// set highlited image
|
||||
let highlightedImage = UIImage(named: items[atIndex].icon)?.withRenderingMode(.alwaysTemplate)
|
||||
button.setImage(highlightedImage, for: .highlighted)
|
||||
button.tintColor = UIColor.init(colorLiteralRed: 0, green: 0, blue: 0, alpha: 0.3)
|
||||
}
|
||||
|
||||
func circleMenu(_ circleMenu: CircleMenu, buttonWillSelected button: UIButton, atIndex: Int) {
|
||||
print("button will selected: \(atIndex)")
|
||||
}
|
||||
|
||||
func circleMenu(_ circleMenu: CircleMenu, buttonDidSelected button: UIButton, atIndex: Int) {
|
||||
print("button did selected: \(atIndex)")
|
||||
}
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// add button
|
||||
// let button = CircleMenu(
|
||||
// frame: CGRect(x: 200, y: 200, width: 50, height: 50),
|
||||
// normalIcon:"icon_menu",
|
||||
// selectedIcon:"icon_close",
|
||||
// buttonsCount: 4,
|
||||
// duration: 4,
|
||||
// distance: 120)
|
||||
// button.backgroundColor = UIColor.lightGrayColor()
|
||||
// button.delegate = self
|
||||
// button.layer.cornerRadius = button.frame.size.width / 2.0
|
||||
// view.addSubview(button)
|
||||
}
|
||||
|
||||
// MARK: <CircleMenuDelegate>
|
||||
|
||||
func circleMenu(_: CircleMenu, willDisplay button: UIButton, atIndex: Int) {
|
||||
button.backgroundColor = items[atIndex].color
|
||||
|
||||
button.setImage(UIImage(named: items[atIndex].icon), for: .normal)
|
||||
|
||||
// set highlited image
|
||||
let highlightedImage = UIImage(named: items[atIndex].icon)?.withRenderingMode(.alwaysTemplate)
|
||||
button.setImage(highlightedImage, for: .highlighted)
|
||||
button.tintColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.3)
|
||||
}
|
||||
|
||||
func circleMenu(_: CircleMenu, buttonWillSelected _: UIButton, atIndex: Int) {
|
||||
print("button will selected: \(atIndex)")
|
||||
}
|
||||
|
||||
func circleMenu(_: CircleMenu, buttonDidSelected _: UIButton, atIndex: Int) {
|
||||
print("button did selected: \(atIndex)")
|
||||
}
|
||||
}
|
||||
|
||||
+462
-431
@@ -26,10 +26,9 @@ import UIKit
|
||||
|
||||
// MARK: helpers
|
||||
|
||||
|
||||
func Init<Type>(_ value: Type, block: (_ object: Type) -> Void) -> Type {
|
||||
block(value)
|
||||
return value
|
||||
block(value)
|
||||
return value
|
||||
}
|
||||
|
||||
// MARK: Protocol
|
||||
@@ -38,455 +37,487 @@ func Init<Type>(_ value: Type, block: (_ object: Type) -> Void) -> Type {
|
||||
* CircleMenuDelegate
|
||||
*/
|
||||
@objc public protocol CircleMenuDelegate {
|
||||
|
||||
/**
|
||||
Tells the delegate the circle menu is about to draw a button for a particular index.
|
||||
|
||||
- parameter circleMenu: The circle menu object informing the delegate of this impending event.
|
||||
- parameter button: A circle menu button object that circle menu is going to use when drawing the row. Don't change button.tag
|
||||
- parameter atIndex: An button index.
|
||||
*/
|
||||
@objc optional func circleMenu(_ circleMenu: CircleMenu, willDisplay button: UIButton, atIndex: Int)
|
||||
|
||||
/**
|
||||
Tells the delegate that a specified index is about to be selected.
|
||||
|
||||
- parameter circleMenu: A circle menu object informing the delegate about the impending selection.
|
||||
- parameter button: A selected circle menu button. Don't change button.tag
|
||||
- parameter atIndex: Selected button index
|
||||
*/
|
||||
@objc optional func circleMenu(_ circleMenu: CircleMenu, buttonWillSelected button: UIButton, atIndex: Int)
|
||||
|
||||
/**
|
||||
Tells the delegate that the specified index is now selected.
|
||||
|
||||
- parameter circleMenu: A circle menu object informing the delegate about the new index selection.
|
||||
- parameter button: A selected circle menu button. Don't change button.tag
|
||||
- parameter atIndex: Selected button index
|
||||
*/
|
||||
@objc optional func circleMenu(_ circleMenu: CircleMenu, buttonDidSelected button: UIButton, atIndex: Int)
|
||||
|
||||
/**
|
||||
Tells the delegate that the menu was collapsed - the cancel action.
|
||||
|
||||
- parameter circleMenu: A circle menu object informing the delegate about the new index selection.
|
||||
*/
|
||||
@objc optional func menuCollapsed(_ circleMenu: CircleMenu)
|
||||
|
||||
/**
|
||||
Tells the delegate the circle menu is about to draw a button for a particular index.
|
||||
|
||||
- parameter circleMenu: The circle menu object informing the delegate of this impending event.
|
||||
- parameter button: A circle menu button object that circle menu is going to use when drawing the row. Don't change button.tag
|
||||
- parameter atIndex: An button index.
|
||||
*/
|
||||
@objc optional func circleMenu(_ circleMenu: CircleMenu, willDisplay button: UIButton, atIndex: Int)
|
||||
|
||||
/**
|
||||
Tells the delegate that a specified index is about to be selected.
|
||||
|
||||
- parameter circleMenu: A circle menu object informing the delegate about the impending selection.
|
||||
- parameter button: A selected circle menu button. Don't change button.tag
|
||||
- parameter atIndex: Selected button index
|
||||
*/
|
||||
@objc optional func circleMenu(_ circleMenu: CircleMenu, buttonWillSelected button: UIButton, atIndex: Int)
|
||||
|
||||
/**
|
||||
Tells the delegate that the specified index is now selected.
|
||||
|
||||
- parameter circleMenu: A circle menu object informing the delegate about the new index selection.
|
||||
- parameter button: A selected circle menu button. Don't change button.tag
|
||||
- parameter atIndex: Selected button index
|
||||
*/
|
||||
@objc optional func circleMenu(_ circleMenu: CircleMenu, buttonDidSelected button: UIButton, atIndex: Int)
|
||||
|
||||
/**
|
||||
Tells the delegate that the menu was collapsed - the cancel action.
|
||||
|
||||
- parameter circleMenu: A circle menu object informing the delegate about the new index selection.
|
||||
*/
|
||||
@objc optional func menuCollapsed(_ circleMenu: CircleMenu)
|
||||
}
|
||||
|
||||
// MARK: CircleMenu
|
||||
|
||||
/// A Button object with pop ups buttons
|
||||
open class CircleMenu: UIButton {
|
||||
|
||||
// MARK: properties
|
||||
|
||||
/// Buttons count
|
||||
@IBInspectable open var buttonsCount: Int = 3
|
||||
/// Circle animation duration
|
||||
@IBInspectable open var duration: Double = 2
|
||||
/// Distance between center button and buttons
|
||||
@IBInspectable open var distance: Float = 100
|
||||
/// Delay between show buttons
|
||||
@IBInspectable open var showDelay: Double = 0
|
||||
|
||||
/// The object that acts as the delegate of the circle menu.
|
||||
@IBOutlet weak open var delegate: AnyObject? //CircleMenuDelegate?
|
||||
|
||||
var buttons: [UIButton]?
|
||||
weak var platform: UIView?
|
||||
|
||||
fileprivate var customNormalIconView: UIImageView?
|
||||
fileprivate var customSelectedIconView: UIImageView?
|
||||
|
||||
/**
|
||||
Initializes and returns a circle menu object.
|
||||
|
||||
- parameter frame: A rectangle specifying the initial location and size of the circle menu in its superview’s coordinates.
|
||||
- parameter normalIcon: The image to use for the specified normal state.
|
||||
- parameter selectedIcon: The image to use for the specified selected state.
|
||||
- parameter buttonsCount: The number of buttons.
|
||||
- parameter duration: The duration, in seconds, of the animation.
|
||||
- parameter distance: Distance between center button and sub buttons.
|
||||
|
||||
- returns: A newly created circle menu.
|
||||
*/
|
||||
public init(frame: CGRect,
|
||||
normalIcon: String?,
|
||||
selectedIcon: String?,
|
||||
buttonsCount: Int = 3,
|
||||
duration: Double = 2,
|
||||
distance: Float = 100) {
|
||||
super.init(frame: frame)
|
||||
|
||||
if let icon = normalIcon {
|
||||
setImage(UIImage(named: icon), for: UIControlState())
|
||||
}
|
||||
|
||||
if let icon = selectedIcon {
|
||||
setImage(UIImage(named: icon), for: .selected)
|
||||
}
|
||||
|
||||
self.buttonsCount = buttonsCount
|
||||
self.duration = duration
|
||||
self.distance = distance
|
||||
|
||||
commonInit()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
commonInit()
|
||||
}
|
||||
|
||||
fileprivate func commonInit() {
|
||||
addActions()
|
||||
|
||||
customNormalIconView = addCustomImageView(state: UIControlState())
|
||||
|
||||
customSelectedIconView = addCustomImageView(state: .selected)
|
||||
if customSelectedIconView != nil {
|
||||
customSelectedIconView?.alpha = 0
|
||||
}
|
||||
setImage(UIImage(), for: UIControlState())
|
||||
setImage(UIImage(), for: .selected)
|
||||
|
||||
}
|
||||
|
||||
// MARK: methods
|
||||
|
||||
/**
|
||||
Hide button
|
||||
|
||||
- parameter duration: The duration, in seconds, of the animation.
|
||||
- parameter hideDelay: The time to delay, in seconds.
|
||||
*/
|
||||
open func hideButtons(_ duration: Double, hideDelay: Double = 0) {
|
||||
if buttons == nil {
|
||||
return
|
||||
}
|
||||
|
||||
buttonsAnimationIsShow(isShow: false, duration: duration, hideDelay: hideDelay)
|
||||
|
||||
tapBounceAnimation()
|
||||
tapRotatedAnimation(0.3, isSelected: false)
|
||||
}
|
||||
|
||||
/**
|
||||
Check is sub buttons showed
|
||||
*/
|
||||
open func buttonsIsShown() -> Bool {
|
||||
guard let buttons = self.buttons else {
|
||||
return false
|
||||
}
|
||||
|
||||
for button in buttons {
|
||||
if button.alpha == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: create
|
||||
fileprivate func createButtons(platform: UIView) -> [UIButton] {
|
||||
var buttons = [UIButton]()
|
||||
|
||||
let step: Float = 360.0 / Float(self.buttonsCount)
|
||||
for index in 0..<self.buttonsCount {
|
||||
|
||||
let angle: Float = Float(index) * step
|
||||
let distance = Float(self.bounds.size.height/2.0)
|
||||
let button = Init(CircleMenuButton(size: self.bounds.size, platform: platform, distance:distance, angle: angle)) {
|
||||
$0.tag = index
|
||||
$0.addTarget(self, action: #selector(CircleMenu.buttonHandler(_:)), for: UIControlEvents.touchUpInside)
|
||||
$0.alpha = 0
|
||||
}
|
||||
buttons.append(button)
|
||||
}
|
||||
return buttons
|
||||
}
|
||||
|
||||
fileprivate func addCustomImageView(state: UIControlState) -> UIImageView? {
|
||||
guard let image = image(for: state) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let iconView = Init(UIImageView(image: image)) {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.contentMode = .center
|
||||
$0.isUserInteractionEnabled = false
|
||||
}
|
||||
addSubview(iconView)
|
||||
|
||||
// added constraints
|
||||
iconView.addConstraint(NSLayoutConstraint(item: iconView, attribute: .height, relatedBy: .equal, toItem: nil,
|
||||
attribute: .height, multiplier: 1, constant: bounds.size.height))
|
||||
|
||||
iconView.addConstraint(NSLayoutConstraint(item: iconView, attribute: .width, relatedBy: .equal, toItem: nil,
|
||||
attribute: .width, multiplier: 1, constant: bounds.size.width))
|
||||
|
||||
addConstraint(NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: iconView,
|
||||
attribute: .centerX, multiplier: 1, constant:0))
|
||||
|
||||
addConstraint(NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: iconView,
|
||||
attribute: .centerY, multiplier: 1, constant:0))
|
||||
|
||||
return iconView
|
||||
}
|
||||
|
||||
fileprivate func createPlatform() -> UIView {
|
||||
let platform = Init(UIView(frame: .zero)) {
|
||||
$0.backgroundColor = .clear
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
superview?.insertSubview(platform, belowSubview: self)
|
||||
|
||||
// constraints
|
||||
let sizeConstraints = [NSLayoutAttribute.width, .height].map {
|
||||
NSLayoutConstraint(item: platform,
|
||||
attribute: $0,
|
||||
relatedBy: .equal,
|
||||
toItem: nil,
|
||||
attribute: $0,
|
||||
multiplier: 1,
|
||||
constant: CGFloat(distance * Float(2.0)))
|
||||
}
|
||||
platform.addConstraints(sizeConstraints)
|
||||
|
||||
let centerConstraints = [NSLayoutAttribute.centerX, .centerY].map {
|
||||
NSLayoutConstraint(item: self,
|
||||
attribute: $0,
|
||||
relatedBy: .equal,
|
||||
toItem: platform,
|
||||
attribute: $0,
|
||||
multiplier: 1,
|
||||
constant:0)
|
||||
}
|
||||
superview?.addConstraints(centerConstraints)
|
||||
|
||||
return platform
|
||||
}
|
||||
|
||||
// MARK: configure
|
||||
fileprivate func addActions() {
|
||||
self.addTarget(self, action: #selector(CircleMenu.onTap), for: UIControlEvents.touchUpInside)
|
||||
}
|
||||
|
||||
// MARK: actions
|
||||
|
||||
func onTap() {
|
||||
if buttonsIsShown() == false {
|
||||
let platform = createPlatform()
|
||||
buttons = createButtons(platform: platform)
|
||||
self.platform = platform
|
||||
}
|
||||
let isShow = !buttonsIsShown()
|
||||
let duration = isShow ? 0.5 : 0.2
|
||||
buttonsAnimationIsShow(isShow: isShow, duration: duration)
|
||||
|
||||
tapBounceAnimation()
|
||||
tapRotatedAnimation(0.3, isSelected: isShow)
|
||||
}
|
||||
|
||||
func buttonHandler(_ sender: CircleMenuButton) {
|
||||
guard let platform = self.platform else { return }
|
||||
|
||||
delegate?.circleMenu?(self, buttonWillSelected: sender, atIndex: sender.tag)
|
||||
|
||||
let circle = CircleMenuLoader(radius: CGFloat(distance),
|
||||
strokeWidth: bounds.size.height,
|
||||
platform: platform,
|
||||
color: sender.backgroundColor)
|
||||
|
||||
if let container = sender.container { // rotation animation
|
||||
sender.rotationAnimation(container.angleZ + 360, duration: duration)
|
||||
container.superview?.bringSubview(toFront: container)
|
||||
}
|
||||
|
||||
if let buttons = buttons {
|
||||
circle.fillAnimation(duration, startAngle: -90 + Float(360 / buttons.count) * Float(sender.tag)) { [weak self] _ in
|
||||
self?.buttons?.forEach { $0.alpha = 0 }
|
||||
}
|
||||
circle.hideAnimation(0.5, delay: duration) { [weak self] _ in
|
||||
if self?.platform?.superview != nil { self?.platform?.removeFromSuperview() }
|
||||
}
|
||||
|
||||
hideCenterButton(duration: 0.3)
|
||||
showCenterButton(duration: 0.525, delay: duration)
|
||||
// MARK: properties
|
||||
|
||||
if customNormalIconView != nil && customSelectedIconView != nil {
|
||||
/// Buttons count
|
||||
@IBInspectable open var buttonsCount: Int = 3
|
||||
/// Circle animation duration
|
||||
@IBInspectable open var duration: Double = 2
|
||||
/// Distance between center button and buttons
|
||||
@IBInspectable open var distance: Float = 100
|
||||
/// Delay between show buttons
|
||||
@IBInspectable open var showDelay: Double = 0
|
||||
|
||||
// Pop buttons radius, if nil use center button size
|
||||
open var subButtonsRadius: CGFloat?
|
||||
|
||||
// Show buttons event
|
||||
open var showButtonsEvent: UIControlEvents = UIControlEvents.touchUpInside {
|
||||
didSet {
|
||||
addActions(newEvent: showButtonsEvent, oldEvent: oldValue)
|
||||
}
|
||||
}
|
||||
|
||||
/// The object that acts as the delegate of the circle menu.
|
||||
@IBOutlet open var delegate: AnyObject? // CircleMenuDelegate?
|
||||
|
||||
var buttons: [UIButton]?
|
||||
weak var platform: UIView?
|
||||
|
||||
fileprivate var customNormalIconView: UIImageView?
|
||||
fileprivate var customSelectedIconView: UIImageView?
|
||||
|
||||
/**
|
||||
Initializes and returns a circle menu object.
|
||||
|
||||
- parameter frame: A rectangle specifying the initial location and size of the circle menu in its superview’s coordinates.
|
||||
- parameter normalIcon: The image to use for the specified normal state.
|
||||
- parameter selectedIcon: The image to use for the specified selected state.
|
||||
- parameter buttonsCount: The number of buttons.
|
||||
- parameter duration: The duration, in seconds, of the animation.
|
||||
- parameter distance: Distance between center button and sub buttons.
|
||||
|
||||
- returns: A newly created circle menu.
|
||||
*/
|
||||
public init(frame: CGRect,
|
||||
normalIcon: String?,
|
||||
selectedIcon: String?,
|
||||
buttonsCount: Int = 3,
|
||||
duration: Double = 2,
|
||||
distance: Float = 100) {
|
||||
super.init(frame: frame)
|
||||
|
||||
if let icon = normalIcon {
|
||||
setImage(UIImage(named: icon), for: UIControlState())
|
||||
}
|
||||
|
||||
if let icon = selectedIcon {
|
||||
setImage(UIImage(named: icon), for: .selected)
|
||||
}
|
||||
|
||||
self.buttonsCount = buttonsCount
|
||||
self.duration = duration
|
||||
self.distance = distance
|
||||
|
||||
commonInit()
|
||||
}
|
||||
|
||||
public required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
commonInit()
|
||||
}
|
||||
|
||||
fileprivate func commonInit() {
|
||||
addActions(newEvent: showButtonsEvent)
|
||||
|
||||
customNormalIconView = addCustomImageView(state: UIControlState())
|
||||
|
||||
customSelectedIconView = addCustomImageView(state: .selected)
|
||||
if customSelectedIconView != nil {
|
||||
customSelectedIconView?.alpha = 0
|
||||
}
|
||||
setImage(UIImage(), for: UIControlState())
|
||||
setImage(UIImage(), for: .selected)
|
||||
}
|
||||
|
||||
// MARK: methods
|
||||
|
||||
/**
|
||||
Hide button
|
||||
|
||||
- parameter duration: The duration, in seconds, of the animation.
|
||||
- parameter hideDelay: The time to delay, in seconds.
|
||||
*/
|
||||
open func hideButtons(_ duration: Double, hideDelay: Double = 0) {
|
||||
if buttons == nil {
|
||||
return
|
||||
}
|
||||
|
||||
buttonsAnimationIsShow(isShow: false, duration: duration, hideDelay: hideDelay)
|
||||
|
||||
tapBounceAnimation(duration: 0.5)
|
||||
tapRotatedAnimation(0.3, isSelected: false)
|
||||
}
|
||||
|
||||
/**
|
||||
Check is sub buttons showed
|
||||
*/
|
||||
open func buttonsIsShown() -> Bool {
|
||||
guard let buttons = self.buttons else {
|
||||
return false
|
||||
}
|
||||
|
||||
for button in buttons {
|
||||
if button.alpha == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
open override func removeFromSuperview() {
|
||||
if self.platform?.superview != nil { self.platform?.removeFromSuperview() }
|
||||
super.removeFromSuperview()
|
||||
}
|
||||
|
||||
// MARK: create
|
||||
|
||||
fileprivate func createButtons(platform: UIView) -> [UIButton] {
|
||||
var buttons = [UIButton]()
|
||||
|
||||
let step: Float = 360.0 / Float(buttonsCount)
|
||||
for index in 0 ..< buttonsCount {
|
||||
|
||||
let angle: Float = Float(index) * step
|
||||
let distance = Float(bounds.size.height / 2.0)
|
||||
let buttonSize: CGSize
|
||||
if let subButtonsRadius = self.subButtonsRadius {
|
||||
buttonSize = CGSize(width: subButtonsRadius * 2, height: subButtonsRadius * 2)
|
||||
} else {
|
||||
buttonSize = bounds.size
|
||||
}
|
||||
let button = Init(CircleMenuButton(size: buttonSize, platform: platform, distance: distance, angle: angle)) {
|
||||
$0.tag = index
|
||||
$0.addTarget(self, action: #selector(CircleMenu.buttonHandler(_:)), for: UIControlEvents.touchUpInside)
|
||||
$0.alpha = 0
|
||||
}
|
||||
buttons.append(button)
|
||||
}
|
||||
return buttons
|
||||
}
|
||||
|
||||
fileprivate func addCustomImageView(state: UIControlState) -> UIImageView? {
|
||||
guard let image = image(for: state) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let iconView = Init(UIImageView(image: image)) {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.contentMode = .center
|
||||
$0.isUserInteractionEnabled = false
|
||||
}
|
||||
addSubview(iconView)
|
||||
|
||||
// added constraints
|
||||
iconView.addConstraint(NSLayoutConstraint(item: iconView, attribute: .height, relatedBy: .equal, toItem: nil,
|
||||
attribute: .height, multiplier: 1, constant: bounds.size.height))
|
||||
|
||||
iconView.addConstraint(NSLayoutConstraint(item: iconView, attribute: .width, relatedBy: .equal, toItem: nil,
|
||||
attribute: .width, multiplier: 1, constant: bounds.size.width))
|
||||
|
||||
addConstraint(NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: iconView,
|
||||
attribute: .centerX, multiplier: 1, constant: 0))
|
||||
|
||||
addConstraint(NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: iconView,
|
||||
attribute: .centerY, multiplier: 1, constant: 0))
|
||||
|
||||
return iconView
|
||||
}
|
||||
|
||||
fileprivate func createPlatform() -> UIView {
|
||||
let platform = Init(UIView(frame: .zero)) {
|
||||
$0.backgroundColor = .clear
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
superview?.insertSubview(platform, belowSubview: self)
|
||||
|
||||
// constraints
|
||||
let sizeConstraints = [NSLayoutAttribute.width, .height].map {
|
||||
NSLayoutConstraint(item: platform,
|
||||
attribute: $0,
|
||||
relatedBy: .equal,
|
||||
toItem: nil,
|
||||
attribute: $0,
|
||||
multiplier: 1,
|
||||
constant: CGFloat(distance * Float(2.0)))
|
||||
}
|
||||
platform.addConstraints(sizeConstraints)
|
||||
|
||||
let centerConstraints = [NSLayoutAttribute.centerX, .centerY].map {
|
||||
NSLayoutConstraint(item: self,
|
||||
attribute: $0,
|
||||
relatedBy: .equal,
|
||||
toItem: platform,
|
||||
attribute: $0,
|
||||
multiplier: 1,
|
||||
constant: 0)
|
||||
}
|
||||
superview?.addConstraints(centerConstraints)
|
||||
|
||||
return platform
|
||||
}
|
||||
|
||||
// MARK: configure
|
||||
|
||||
fileprivate func addActions(newEvent: UIControlEvents, oldEvent: UIControlEvents? = nil) {
|
||||
if let oldEvent = oldEvent { removeTarget(self, action: #selector(CircleMenu.onTap), for: oldEvent) }
|
||||
addTarget(self, action: #selector(CircleMenu.onTap), for: newEvent)
|
||||
}
|
||||
|
||||
// MARK: actions
|
||||
|
||||
private var isBounceAnimating: Bool = false
|
||||
|
||||
@objc func onTap() {
|
||||
guard isBounceAnimating == false else { return }
|
||||
isBounceAnimating = true
|
||||
|
||||
if buttonsIsShown() == false {
|
||||
let platform = createPlatform()
|
||||
buttons = createButtons(platform: platform)
|
||||
self.platform = platform
|
||||
}
|
||||
let isShow = !buttonsIsShown()
|
||||
let duration = isShow ? 0.5 : 0.2
|
||||
buttonsAnimationIsShow(isShow: isShow, duration: duration)
|
||||
|
||||
tapBounceAnimation(duration: 0.5) { [weak self] _ in self?.isBounceAnimating = false }
|
||||
tapRotatedAnimation(0.3, isSelected: isShow)
|
||||
}
|
||||
|
||||
@objc func buttonHandler(_ sender: CircleMenuButton) {
|
||||
guard let platform = self.platform else { return }
|
||||
|
||||
delegate?.circleMenu?(self, buttonWillSelected: sender, atIndex: sender.tag)
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + duration, execute: {
|
||||
self.delegate?.circleMenu?(self, buttonDidSelected: sender, atIndex: sender.tag)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: animations
|
||||
|
||||
fileprivate func buttonsAnimationIsShow(isShow: Bool, duration: Double, hideDelay: Double = 0) {
|
||||
guard let buttons = self.buttons else {
|
||||
return
|
||||
}
|
||||
|
||||
let step: Float = 360.0 / Float(self.buttonsCount)
|
||||
for index in 0..<self.buttonsCount {
|
||||
guard case let button as CircleMenuButton = buttons[index] else { continue }
|
||||
let angle: Float = Float(index) * step
|
||||
if isShow == true {
|
||||
delegate?.circleMenu?(self, willDisplay: button, atIndex: index)
|
||||
|
||||
button.rotatedZ(angle: angle, animated: false, delay: Double(index) * showDelay)
|
||||
button.showAnimation(distance: distance, duration: duration, delay: Double(index) * showDelay)
|
||||
} else {
|
||||
button.hideAnimation(
|
||||
distance: Float(self.bounds.size.height / 2.0),
|
||||
duration: duration, delay: hideDelay)
|
||||
self.delegate?.menuCollapsed?(self)
|
||||
}
|
||||
}
|
||||
if isShow == false { // hide buttons and remove
|
||||
self.buttons = nil
|
||||
let strokeWidth: CGFloat
|
||||
if let radius = self.subButtonsRadius {
|
||||
strokeWidth = radius * 2
|
||||
} else {
|
||||
strokeWidth = bounds.size.height
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + duration) {
|
||||
if self.platform?.superview != nil { self.platform?.removeFromSuperview() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func tapBounceAnimation() {
|
||||
self.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
|
||||
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 5,
|
||||
options: UIViewAnimationOptions.curveLinear,
|
||||
animations: { () -> Void in
|
||||
self.transform = CGAffineTransform(scaleX: 1, y: 1)
|
||||
},
|
||||
completion: nil)
|
||||
}
|
||||
|
||||
fileprivate func tapRotatedAnimation(_ duration: Float, isSelected: Bool) {
|
||||
|
||||
let addAnimations: (_ view: UIImageView, _ isShow: Bool) -> () = { (view, isShow) in
|
||||
var toAngle: Float = 180.0
|
||||
var fromAngle: Float = 0
|
||||
var fromScale = 1.0
|
||||
var toScale = 0.2
|
||||
var fromOpacity = 1
|
||||
var toOpacity = 0
|
||||
if isShow == true {
|
||||
toAngle = 0
|
||||
fromAngle = -180
|
||||
fromScale = 0.2
|
||||
toScale = 1.0
|
||||
fromOpacity = 0
|
||||
toOpacity = 1
|
||||
}
|
||||
|
||||
let rotation = Init(CABasicAnimation(keyPath: "transform.rotation")) {
|
||||
$0.duration = TimeInterval(duration)
|
||||
$0.toValue = (toAngle.degrees)
|
||||
$0.fromValue = (fromAngle.degrees)
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
}
|
||||
let fade = Init(CABasicAnimation(keyPath: "opacity")) {
|
||||
$0.duration = TimeInterval(duration)
|
||||
$0.fromValue = fromOpacity
|
||||
$0.toValue = toOpacity
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
$0.fillMode = kCAFillModeForwards
|
||||
$0.isRemovedOnCompletion = false
|
||||
}
|
||||
let scale = Init(CABasicAnimation(keyPath: "transform.scale")) {
|
||||
$0.duration = TimeInterval(duration)
|
||||
$0.toValue = toScale
|
||||
$0.fromValue = fromScale
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
}
|
||||
|
||||
view.layer.add(rotation, forKey: nil)
|
||||
view.layer.add(fade, forKey: nil)
|
||||
view.layer.add(scale, forKey: nil)
|
||||
}
|
||||
|
||||
if let customNormalIconView = self.customNormalIconView {
|
||||
addAnimations(customNormalIconView, !isSelected)
|
||||
}
|
||||
if let customSelectedIconView = self.customSelectedIconView {
|
||||
addAnimations(customSelectedIconView, isSelected)
|
||||
let circle = CircleMenuLoader(radius: CGFloat(distance),
|
||||
strokeWidth: strokeWidth,
|
||||
platform: platform,
|
||||
color: sender.backgroundColor)
|
||||
|
||||
if let container = sender.container { // rotation animation
|
||||
sender.rotationAnimation(container.angleZ + 360, duration: duration)
|
||||
container.superview?.bringSubview(toFront: container)
|
||||
}
|
||||
|
||||
if let buttons = buttons {
|
||||
circle.fillAnimation(duration, startAngle: -90 + Float(360 / buttons.count) * Float(sender.tag)) { [weak self] in
|
||||
self?.buttons?.forEach { $0.alpha = 0 }
|
||||
}
|
||||
circle.hideAnimation(0.5, delay: duration) { [weak self] in
|
||||
if self?.platform?.superview != nil { self?.platform?.removeFromSuperview() }
|
||||
}
|
||||
|
||||
hideCenterButton(duration: 0.3)
|
||||
showCenterButton(duration: 0.525, delay: duration)
|
||||
|
||||
if customNormalIconView != nil && customSelectedIconView != nil {
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + duration, execute: {
|
||||
self.delegate?.circleMenu?(self, buttonDidSelected: sender, atIndex: sender.tag)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.isSelected = isSelected
|
||||
self.alpha = isSelected ? 0.3 : 1
|
||||
}
|
||||
|
||||
fileprivate func hideCenterButton(duration: Double, delay: Double = 0) {
|
||||
UIView.animate( withDuration: TimeInterval(duration), delay: TimeInterval(delay),
|
||||
options: UIViewAnimationOptions.curveEaseOut,
|
||||
animations: { () -> Void in
|
||||
self.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
|
||||
}, completion: nil)
|
||||
}
|
||||
|
||||
fileprivate func showCenterButton(duration: Float, delay: Double) {
|
||||
UIView.animate( withDuration: TimeInterval(duration), delay: TimeInterval(delay), usingSpringWithDamping: 0.78,
|
||||
initialSpringVelocity: 0, options: UIViewAnimationOptions.curveLinear,
|
||||
animations: { () -> Void in
|
||||
self.transform = CGAffineTransform(scaleX: 1, y: 1)
|
||||
self.alpha = 1
|
||||
},
|
||||
completion: nil)
|
||||
|
||||
let rotation = Init(CASpringAnimation(keyPath: "transform.rotation")) {
|
||||
$0.duration = TimeInterval(1.5)
|
||||
$0.toValue = (0)
|
||||
$0.fromValue = (Float(-180).degrees)
|
||||
$0.damping = 10
|
||||
$0.initialVelocity = 0
|
||||
$0.beginTime = CACurrentMediaTime() + delay
|
||||
// MARK: animations
|
||||
|
||||
fileprivate func buttonsAnimationIsShow(isShow: Bool, duration: Double, hideDelay: Double = 0) {
|
||||
guard let buttons = self.buttons else {
|
||||
return
|
||||
}
|
||||
|
||||
let step: Float = 360.0 / Float(buttonsCount)
|
||||
for index in 0 ..< buttonsCount {
|
||||
guard case let button as CircleMenuButton = buttons[index] else { continue }
|
||||
let angle: Float = Float(index) * step
|
||||
if isShow == true {
|
||||
delegate?.circleMenu?(self, willDisplay: button, atIndex: index)
|
||||
|
||||
button.rotatedZ(angle: angle, animated: false, delay: Double(index) * showDelay)
|
||||
button.showAnimation(distance: distance, duration: duration, delay: Double(index) * showDelay)
|
||||
} else {
|
||||
button.hideAnimation(distance: Float(bounds.size.height / 2.0), duration: duration, delay: hideDelay)
|
||||
}
|
||||
}
|
||||
if isShow == false { // hide buttons and remove
|
||||
self.buttons = nil
|
||||
delegate?.menuCollapsed?(self)
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + duration) {
|
||||
if self.platform?.superview != nil { self.platform?.removeFromSuperview() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let fade = Init(CABasicAnimation(keyPath: "opacity")) {
|
||||
$0.duration = TimeInterval(0.01)
|
||||
$0.toValue = 0
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
$0.fillMode = kCAFillModeForwards
|
||||
$0.isRemovedOnCompletion = false
|
||||
$0.beginTime = CACurrentMediaTime() + delay
|
||||
|
||||
fileprivate func tapBounceAnimation(duration: TimeInterval, completion: ((Bool)->())? = nil) {
|
||||
transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
|
||||
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 5,
|
||||
options: UIViewAnimationOptions.curveLinear,
|
||||
animations: { () -> Void in
|
||||
self.transform = CGAffineTransform(scaleX: 1, y: 1)
|
||||
},
|
||||
completion: completion)
|
||||
}
|
||||
let show = Init(CABasicAnimation(keyPath: "opacity")) {
|
||||
$0.duration = TimeInterval(duration)
|
||||
$0.toValue = 1
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
$0.fillMode = kCAFillModeForwards
|
||||
$0.isRemovedOnCompletion = false
|
||||
$0.beginTime = CACurrentMediaTime() + delay
|
||||
|
||||
fileprivate func tapRotatedAnimation(_ duration: Float, isSelected: Bool) {
|
||||
|
||||
let addAnimations: (_ view: UIImageView, _ isShow: Bool) -> Void = { view, isShow in
|
||||
var toAngle: Float = 180.0
|
||||
var fromAngle: Float = 0
|
||||
var fromScale = 1.0
|
||||
var toScale = 0.2
|
||||
var fromOpacity = 1
|
||||
var toOpacity = 0
|
||||
if isShow == true {
|
||||
toAngle = 0
|
||||
fromAngle = -180
|
||||
fromScale = 0.2
|
||||
toScale = 1.0
|
||||
fromOpacity = 0
|
||||
toOpacity = 1
|
||||
}
|
||||
|
||||
let rotation = Init(CABasicAnimation(keyPath: "transform.rotation")) {
|
||||
$0.duration = TimeInterval(duration)
|
||||
$0.toValue = (toAngle.degrees)
|
||||
$0.fromValue = (fromAngle.degrees)
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
}
|
||||
let fade = Init(CABasicAnimation(keyPath: "opacity")) {
|
||||
$0.duration = TimeInterval(duration)
|
||||
$0.fromValue = fromOpacity
|
||||
$0.toValue = toOpacity
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
$0.fillMode = kCAFillModeForwards
|
||||
$0.isRemovedOnCompletion = false
|
||||
}
|
||||
let scale = Init(CABasicAnimation(keyPath: "transform.scale")) {
|
||||
$0.duration = TimeInterval(duration)
|
||||
$0.toValue = toScale
|
||||
$0.fromValue = fromScale
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
}
|
||||
|
||||
view.layer.add(rotation, forKey: nil)
|
||||
view.layer.add(fade, forKey: nil)
|
||||
view.layer.add(scale, forKey: nil)
|
||||
}
|
||||
|
||||
if let customNormalIconView = self.customNormalIconView {
|
||||
addAnimations(customNormalIconView, !isSelected)
|
||||
}
|
||||
if let customSelectedIconView = self.customSelectedIconView {
|
||||
addAnimations(customSelectedIconView, isSelected)
|
||||
}
|
||||
|
||||
self.isSelected = isSelected
|
||||
alpha = isSelected ? 0.3 : 1
|
||||
}
|
||||
|
||||
fileprivate func hideCenterButton(duration: Double, delay: Double = 0) {
|
||||
UIView.animate(withDuration: TimeInterval(duration), delay: TimeInterval(delay),
|
||||
options: UIViewAnimationOptions.curveEaseOut,
|
||||
animations: { () -> Void in
|
||||
self.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
|
||||
}, completion: nil)
|
||||
}
|
||||
|
||||
fileprivate func showCenterButton(duration: Float, delay: Double) {
|
||||
UIView.animate(withDuration: TimeInterval(duration), delay: TimeInterval(delay), usingSpringWithDamping: 0.78,
|
||||
initialSpringVelocity: 0, options: UIViewAnimationOptions.curveLinear,
|
||||
animations: { () -> Void in
|
||||
self.transform = CGAffineTransform(scaleX: 1, y: 1)
|
||||
self.alpha = 1
|
||||
},
|
||||
completion: nil)
|
||||
|
||||
let rotation = Init(CASpringAnimation(keyPath: "transform.rotation")) {
|
||||
$0.duration = TimeInterval(1.5)
|
||||
$0.toValue = 0
|
||||
$0.fromValue = (Float(-180).degrees)
|
||||
$0.damping = 10
|
||||
$0.initialVelocity = 0
|
||||
$0.beginTime = CACurrentMediaTime() + delay
|
||||
}
|
||||
|
||||
let fade = Init(CABasicAnimation(keyPath: "opacity")) {
|
||||
$0.duration = TimeInterval(0.01)
|
||||
$0.toValue = 0
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
$0.fillMode = kCAFillModeForwards
|
||||
$0.isRemovedOnCompletion = false
|
||||
$0.beginTime = CACurrentMediaTime() + delay
|
||||
}
|
||||
let show = Init(CABasicAnimation(keyPath: "opacity")) {
|
||||
$0.duration = TimeInterval(duration)
|
||||
$0.toValue = 1
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
$0.fillMode = kCAFillModeForwards
|
||||
$0.isRemovedOnCompletion = false
|
||||
$0.beginTime = CACurrentMediaTime() + delay
|
||||
}
|
||||
|
||||
customNormalIconView?.layer.add(rotation, forKey: nil)
|
||||
customNormalIconView?.layer.add(show, forKey: nil)
|
||||
|
||||
customSelectedIconView?.layer.add(fade, forKey: nil)
|
||||
}
|
||||
|
||||
customNormalIconView?.layer.add(rotation, forKey: nil)
|
||||
customNormalIconView?.layer.add(show, forKey: nil)
|
||||
|
||||
customSelectedIconView?.layer.add(fade, forKey: nil)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: extension
|
||||
|
||||
internal extension Float {
|
||||
var radians: Float {
|
||||
return self * (Float(180) / Float(M_PI))
|
||||
}
|
||||
|
||||
var degrees: Float {
|
||||
return self * Float(M_PI) / 180.0
|
||||
}
|
||||
var radians: Float {
|
||||
return self * (Float(180) / Float.pi)
|
||||
}
|
||||
|
||||
var degrees: Float {
|
||||
return self * Float.pi / 180.0
|
||||
}
|
||||
}
|
||||
|
||||
internal extension UIView {
|
||||
|
||||
var angleZ: Float {
|
||||
return atan2(Float(self.transform.b), Float(self.transform.a)).radians
|
||||
}
|
||||
|
||||
var angleZ: Float {
|
||||
return atan2(Float(transform.b), Float(transform.a)).radians
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,186 +24,187 @@
|
||||
import UIKit
|
||||
|
||||
internal class CircleMenuButton: UIButton {
|
||||
|
||||
// MARK: properties
|
||||
weak var container: UIView?
|
||||
|
||||
|
||||
// MARK: life cycle
|
||||
|
||||
init(size: CGSize, platform: UIView, distance: Float, angle: Float = 0) {
|
||||
super.init(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: size))
|
||||
|
||||
self.backgroundColor = UIColor(colorLiteralRed: 0.79, green: 0.24, blue: 0.27, alpha: 1)
|
||||
self.layer.cornerRadius = size.height / 2.0
|
||||
|
||||
let aContainer = createContainer(CGSize(width: size.width, height:CGFloat(distance)), platform: platform)
|
||||
|
||||
// hack view for rotate
|
||||
let view = UIView(frame: CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height))
|
||||
view.backgroundColor = UIColor.clear
|
||||
view.addSubview(self)
|
||||
//...
|
||||
|
||||
aContainer.addSubview(view)
|
||||
container = aContainer
|
||||
|
||||
view.layer.transform = CATransform3DMakeRotation(-CGFloat(angle.degrees), 0, 0, 1)
|
||||
|
||||
self.rotatedZ(angle: angle, animated: false)
|
||||
}
|
||||
|
||||
required internal init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK: configure
|
||||
fileprivate func createContainer(_ size: CGSize, platform: UIView) -> UIView {
|
||||
let container = Init(UIView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: size))) {
|
||||
$0.backgroundColor = UIColor.clear
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.layer.anchorPoint = CGPoint(x: 0.5, y: 1)
|
||||
// MARK: properties
|
||||
|
||||
weak var container: UIView?
|
||||
|
||||
// MARK: life cycle
|
||||
|
||||
init(size: CGSize, platform: UIView, distance: Float, angle: Float = 0) {
|
||||
super.init(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: size))
|
||||
|
||||
backgroundColor = UIColor(red: 0.79, green: 0.24, blue: 0.27, alpha: 1)
|
||||
layer.cornerRadius = size.height / 2.0
|
||||
|
||||
let aContainer = createContainer(CGSize(width: size.width, height: CGFloat(distance)), platform: platform)
|
||||
|
||||
// hack view for rotate
|
||||
let view = UIView(frame: CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height))
|
||||
view.backgroundColor = UIColor.clear
|
||||
view.addSubview(self)
|
||||
// ...
|
||||
|
||||
aContainer.addSubview(view)
|
||||
container = aContainer
|
||||
|
||||
view.layer.transform = CATransform3DMakeRotation(-CGFloat(angle.degrees), 0, 0, 1)
|
||||
|
||||
rotatedZ(angle: angle, animated: false)
|
||||
}
|
||||
platform.addSubview(container)
|
||||
|
||||
// added constraints
|
||||
let height = NSLayoutConstraint(item: container,
|
||||
attribute: .height,
|
||||
relatedBy: .equal,
|
||||
toItem: nil,
|
||||
attribute: .height,
|
||||
multiplier: 1,
|
||||
constant: size.height)
|
||||
height.identifier = "height"
|
||||
container.addConstraint(height)
|
||||
|
||||
container.addConstraint(NSLayoutConstraint(item: container,
|
||||
attribute: .width,
|
||||
relatedBy: .equal,
|
||||
toItem: nil,
|
||||
attribute: .width,
|
||||
multiplier: 1,
|
||||
constant: size.width))
|
||||
|
||||
platform.addConstraint(NSLayoutConstraint(item: platform,
|
||||
attribute: .centerX,
|
||||
relatedBy: .equal,
|
||||
toItem: container,
|
||||
attribute: .centerX,
|
||||
multiplier: 1,
|
||||
constant:0))
|
||||
|
||||
platform.addConstraint(NSLayoutConstraint(item: platform,
|
||||
attribute: .centerY,
|
||||
relatedBy: .equal,
|
||||
toItem: container,
|
||||
attribute: .centerY,
|
||||
multiplier: 1,
|
||||
constant:0))
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
// MARK: methods
|
||||
|
||||
internal func rotatedZ(angle: Float, animated: Bool, duration: Double = 0, delay: Double = 0) {
|
||||
guard let container = self.container else {
|
||||
fatalError("contaner don't create")
|
||||
|
||||
internal required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
let rotateTransform = CATransform3DMakeRotation(CGFloat(angle.degrees), 0, 0, 1)
|
||||
if animated {
|
||||
UIView.animate(
|
||||
withDuration: duration,
|
||||
delay: delay,
|
||||
options: UIViewAnimationOptions(),
|
||||
animations: { () -> Void in
|
||||
container.layer.transform = rotateTransform
|
||||
},
|
||||
completion: nil)
|
||||
} else {
|
||||
container.layer.transform = rotateTransform
|
||||
|
||||
// MARK: configure
|
||||
|
||||
fileprivate func createContainer(_ size: CGSize, platform: UIView) -> UIView {
|
||||
let container = Init(UIView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: size))) {
|
||||
$0.backgroundColor = UIColor.clear
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.layer.anchorPoint = CGPoint(x: 0.5, y: 1)
|
||||
}
|
||||
platform.addSubview(container)
|
||||
|
||||
// added constraints
|
||||
let height = NSLayoutConstraint(item: container,
|
||||
attribute: .height,
|
||||
relatedBy: .equal,
|
||||
toItem: nil,
|
||||
attribute: .height,
|
||||
multiplier: 1,
|
||||
constant: size.height)
|
||||
height.identifier = "height"
|
||||
container.addConstraint(height)
|
||||
|
||||
container.addConstraint(NSLayoutConstraint(item: container,
|
||||
attribute: .width,
|
||||
relatedBy: .equal,
|
||||
toItem: nil,
|
||||
attribute: .width,
|
||||
multiplier: 1,
|
||||
constant: size.width))
|
||||
|
||||
platform.addConstraint(NSLayoutConstraint(item: platform,
|
||||
attribute: .centerX,
|
||||
relatedBy: .equal,
|
||||
toItem: container,
|
||||
attribute: .centerX,
|
||||
multiplier: 1,
|
||||
constant: 0))
|
||||
|
||||
platform.addConstraint(NSLayoutConstraint(item: platform,
|
||||
attribute: .centerY,
|
||||
relatedBy: .equal,
|
||||
toItem: container,
|
||||
attribute: .centerY,
|
||||
multiplier: 1,
|
||||
constant: 0))
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
// MARK: methods
|
||||
|
||||
internal func rotatedZ(angle: Float, animated: Bool, duration: Double = 0, delay: Double = 0) {
|
||||
guard let container = self.container else {
|
||||
fatalError("contaner don't create")
|
||||
}
|
||||
|
||||
let rotateTransform = CATransform3DMakeRotation(CGFloat(angle.degrees), 0, 0, 1)
|
||||
if animated {
|
||||
UIView.animate(
|
||||
withDuration: duration,
|
||||
delay: delay,
|
||||
options: UIViewAnimationOptions(),
|
||||
animations: { () -> Void in
|
||||
container.layer.transform = rotateTransform
|
||||
},
|
||||
completion: nil)
|
||||
} else {
|
||||
container.layer.transform = rotateTransform
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Animations
|
||||
|
||||
internal extension CircleMenuButton {
|
||||
|
||||
internal func showAnimation(distance: Float, duration: Double, delay: Double = 0) {
|
||||
guard let heightConstraint = (self.container?.constraints.filter {$0.identifier == "height"})?.first else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
self.transform = CGAffineTransform(scaleX: 0, y: 0)
|
||||
self.container?.superview?.layoutIfNeeded()
|
||||
|
||||
self.alpha = 0
|
||||
|
||||
heightConstraint.constant = CGFloat(distance)
|
||||
UIView.animate(
|
||||
withDuration: duration,
|
||||
delay: delay,
|
||||
usingSpringWithDamping: 0.7,
|
||||
initialSpringVelocity: 0,
|
||||
options: UIViewAnimationOptions.curveLinear,
|
||||
animations: { () -> Void in
|
||||
self.container?.superview?.layoutIfNeeded()
|
||||
self.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
|
||||
self.alpha = 1
|
||||
}, completion: { (success) -> Void in
|
||||
})
|
||||
}
|
||||
|
||||
internal func hideAnimation(distance: Float, duration: Double, delay: Double = 0) {
|
||||
guard let heightConstraint = (self.container?.constraints.filter {$0.identifier == "height"})?.first else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
heightConstraint.constant = CGFloat(distance)
|
||||
UIView.animate(
|
||||
withDuration: duration,
|
||||
delay: delay,
|
||||
options: UIViewAnimationOptions.curveEaseIn,
|
||||
animations: { () -> Void in
|
||||
self.container?.superview?.layoutIfNeeded()
|
||||
self.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
|
||||
}, completion: { (success) -> Void in
|
||||
self.alpha = 0
|
||||
|
||||
if let _ = self.container {
|
||||
self.container?.removeFromSuperview() // remove container
|
||||
|
||||
internal func showAnimation(distance: Float, duration: Double, delay: Double = 0) {
|
||||
guard let heightConstraint = (self.container?.constraints.filter { $0.identifier == "height" })?.first else {
|
||||
fatalError()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
internal func changeDistance(_ distance: CGFloat, animated: Bool, duration: Double = 0, delay: Double = 0) {
|
||||
|
||||
guard let heightConstraint = (self.container?.constraints.filter {$0.identifier == "height"})?.first else {
|
||||
fatalError()
|
||||
|
||||
transform = CGAffineTransform(scaleX: 0, y: 0)
|
||||
container?.superview?.layoutIfNeeded()
|
||||
|
||||
alpha = 0
|
||||
|
||||
heightConstraint.constant = CGFloat(distance)
|
||||
UIView.animate(
|
||||
withDuration: duration,
|
||||
delay: delay,
|
||||
usingSpringWithDamping: 0.7,
|
||||
initialSpringVelocity: 0,
|
||||
options: UIViewAnimationOptions.curveLinear,
|
||||
animations: { () -> Void in
|
||||
self.container?.superview?.layoutIfNeeded()
|
||||
self.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
|
||||
self.alpha = 1
|
||||
}, completion: { (_) -> Void in
|
||||
})
|
||||
}
|
||||
|
||||
heightConstraint.constant = distance
|
||||
|
||||
UIView.animate(
|
||||
withDuration: duration,
|
||||
delay: delay,
|
||||
options: UIViewAnimationOptions.curveEaseIn,
|
||||
animations: { () -> Void in
|
||||
self.container?.superview?.layoutIfNeeded()
|
||||
},
|
||||
completion: nil)
|
||||
}
|
||||
|
||||
// MARK: layer animation
|
||||
|
||||
internal func rotationAnimation(_ angle: Float, duration: Double) {
|
||||
let rotation = Init(CABasicAnimation(keyPath: "transform.rotation")) {
|
||||
$0.duration = TimeInterval(duration)
|
||||
$0.toValue = (angle.degrees)
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
|
||||
internal func hideAnimation(distance: Float, duration: Double, delay: Double = 0) {
|
||||
guard let heightConstraint = (self.container?.constraints.filter { $0.identifier == "height" })?.first else {
|
||||
return
|
||||
}
|
||||
|
||||
heightConstraint.constant = CGFloat(distance)
|
||||
UIView.animate(
|
||||
withDuration: duration,
|
||||
delay: delay,
|
||||
options: UIViewAnimationOptions.curveEaseIn,
|
||||
animations: { () -> Void in
|
||||
self.container?.superview?.layoutIfNeeded()
|
||||
self.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
|
||||
}, completion: { (_) -> Void in
|
||||
self.alpha = 0
|
||||
|
||||
if let _ = self.container {
|
||||
self.container?.removeFromSuperview() // remove container
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
internal func changeDistance(_ distance: CGFloat, animated _: Bool, duration: Double = 0, delay: Double = 0) {
|
||||
|
||||
guard let heightConstraint = (self.container?.constraints.filter { $0.identifier == "height" })?.first else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
heightConstraint.constant = distance
|
||||
|
||||
UIView.animate(
|
||||
withDuration: duration,
|
||||
delay: delay,
|
||||
options: UIViewAnimationOptions.curveEaseIn,
|
||||
animations: { () -> Void in
|
||||
self.container?.superview?.layoutIfNeeded()
|
||||
},
|
||||
completion: nil)
|
||||
}
|
||||
|
||||
// MARK: layer animation
|
||||
|
||||
internal func rotationAnimation(_ angle: Float, duration: Double) {
|
||||
let rotation = Init(CABasicAnimation(keyPath: "transform.rotation")) {
|
||||
$0.duration = TimeInterval(duration)
|
||||
$0.toValue = (angle.degrees)
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
}
|
||||
container?.layer.add(rotation, forKey: "rotation")
|
||||
}
|
||||
container?.layer.add(rotation, forKey: "rotation")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,134 +26,137 @@ import Foundation
|
||||
import UIKit
|
||||
|
||||
internal class CircleMenuLoader: UIView {
|
||||
|
||||
// MARK: properties
|
||||
var circle: CAShapeLayer?
|
||||
|
||||
// MARK: life cycle
|
||||
internal init(radius: CGFloat, strokeWidth: CGFloat, platform: UIView, color: UIColor?) {
|
||||
super.init(frame: CGRect(x: 0, y: 0, width: radius, height: radius))
|
||||
|
||||
platform.addSubview(self)
|
||||
|
||||
circle = createCircle(radius, strokeWidth: strokeWidth, color: color)
|
||||
createConstraints(platform: platform, radius: radius)
|
||||
|
||||
let circleFrame = CGRect(
|
||||
x: radius * 2 - strokeWidth,
|
||||
y: radius - strokeWidth / 2,
|
||||
width: strokeWidth,
|
||||
height: strokeWidth)
|
||||
createRoundView(circleFrame, color: color)
|
||||
|
||||
backgroundColor = UIColor.clear
|
||||
}
|
||||
|
||||
required internal init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK: create
|
||||
fileprivate func createCircle(_ radius: CGFloat, strokeWidth: CGFloat, color: UIColor?) -> CAShapeLayer {
|
||||
|
||||
let circlePath = UIBezierPath(
|
||||
arcCenter: CGPoint(x: radius, y: radius),
|
||||
radius: CGFloat(radius) - strokeWidth / 2.0,
|
||||
startAngle: CGFloat(0),
|
||||
endAngle:CGFloat(M_PI * 2),
|
||||
clockwise: true)
|
||||
|
||||
let circle = Init(CAShapeLayer()) {
|
||||
$0.path = circlePath.cgPath
|
||||
$0.fillColor = UIColor.clear.cgColor
|
||||
$0.strokeColor = color?.cgColor
|
||||
$0.lineWidth = strokeWidth
|
||||
|
||||
// MARK: properties
|
||||
|
||||
var circle: CAShapeLayer?
|
||||
|
||||
// MARK: life cycle
|
||||
|
||||
internal init(radius: CGFloat, strokeWidth: CGFloat, platform: UIView, color: UIColor?) {
|
||||
super.init(frame: CGRect(x: 0, y: 0, width: radius, height: radius))
|
||||
|
||||
platform.addSubview(self)
|
||||
|
||||
circle = createCircle(radius, strokeWidth: strokeWidth, color: color)
|
||||
createConstraints(platform: platform, radius: radius)
|
||||
|
||||
let circleFrame = CGRect(
|
||||
x: radius * 2 - strokeWidth,
|
||||
y: radius - strokeWidth / 2,
|
||||
width: strokeWidth,
|
||||
height: strokeWidth)
|
||||
createRoundView(circleFrame, color: color)
|
||||
|
||||
backgroundColor = UIColor.clear
|
||||
}
|
||||
|
||||
self.layer.addSublayer(circle)
|
||||
return circle
|
||||
}
|
||||
|
||||
fileprivate func createConstraints(platform: UIView, radius: CGFloat) {
|
||||
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
// added constraints
|
||||
let sizeConstraints = [NSLayoutAttribute.width, .height].map {
|
||||
NSLayoutConstraint(item: self,
|
||||
attribute: $0,
|
||||
relatedBy: .equal,
|
||||
toItem: nil,
|
||||
attribute: $0,
|
||||
multiplier: 1,
|
||||
constant: radius * 2.0)
|
||||
|
||||
internal required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
addConstraints(sizeConstraints)
|
||||
|
||||
let centerConstaraints = [NSLayoutAttribute.centerY, .centerX].map {
|
||||
NSLayoutConstraint(item: platform,
|
||||
attribute: $0,
|
||||
relatedBy: .equal,
|
||||
toItem: self,
|
||||
attribute: $0,
|
||||
multiplier: 1,
|
||||
constant:0)
|
||||
|
||||
// MARK: create
|
||||
|
||||
fileprivate func createCircle(_ radius: CGFloat, strokeWidth: CGFloat, color: UIColor?) -> CAShapeLayer {
|
||||
|
||||
let circlePath = UIBezierPath(
|
||||
arcCenter: CGPoint(x: radius, y: radius),
|
||||
radius: CGFloat(radius) - strokeWidth / 2.0,
|
||||
startAngle: CGFloat(0),
|
||||
endAngle: CGFloat.pi * 2,
|
||||
clockwise: true)
|
||||
|
||||
let circle = Init(CAShapeLayer()) {
|
||||
$0.path = circlePath.cgPath
|
||||
$0.fillColor = UIColor.clear.cgColor
|
||||
$0.strokeColor = color?.cgColor
|
||||
$0.lineWidth = strokeWidth
|
||||
}
|
||||
|
||||
layer.addSublayer(circle)
|
||||
return circle
|
||||
}
|
||||
platform.addConstraints(centerConstaraints)
|
||||
}
|
||||
|
||||
internal func createRoundView(_ rect: CGRect, color: UIColor?) {
|
||||
let roundView = Init(UIView(frame: rect)) {
|
||||
$0.backgroundColor = UIColor.black
|
||||
$0.layer.cornerRadius = rect.size.width / 2.0
|
||||
$0.backgroundColor = color
|
||||
|
||||
fileprivate func createConstraints(platform: UIView, radius: CGFloat) {
|
||||
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
// added constraints
|
||||
let sizeConstraints = [NSLayoutAttribute.width, .height].map {
|
||||
NSLayoutConstraint(item: self,
|
||||
attribute: $0,
|
||||
relatedBy: .equal,
|
||||
toItem: nil,
|
||||
attribute: $0,
|
||||
multiplier: 1,
|
||||
constant: radius * 2.0)
|
||||
}
|
||||
addConstraints(sizeConstraints)
|
||||
|
||||
let centerConstaraints = [NSLayoutAttribute.centerY, .centerX].map {
|
||||
NSLayoutConstraint(item: platform,
|
||||
attribute: $0,
|
||||
relatedBy: .equal,
|
||||
toItem: self,
|
||||
attribute: $0,
|
||||
multiplier: 1,
|
||||
constant: 0)
|
||||
}
|
||||
platform.addConstraints(centerConstaraints)
|
||||
}
|
||||
addSubview(roundView)
|
||||
}
|
||||
|
||||
// MARK: animations
|
||||
|
||||
internal func fillAnimation(_ duration: Double, startAngle: Float, completion: @escaping () -> Void) {
|
||||
guard circle != nil else {
|
||||
return
|
||||
|
||||
internal func createRoundView(_ rect: CGRect, color: UIColor?) {
|
||||
let roundView = Init(UIView(frame: rect)) {
|
||||
$0.backgroundColor = UIColor.black
|
||||
$0.layer.cornerRadius = rect.size.width / 2.0
|
||||
$0.backgroundColor = color
|
||||
}
|
||||
addSubview(roundView)
|
||||
}
|
||||
|
||||
let rotateTransform = CATransform3DMakeRotation(CGFloat(startAngle.degrees), 0, 0, 1)
|
||||
layer.transform = rotateTransform
|
||||
|
||||
CATransaction.begin()
|
||||
CATransaction.setCompletionBlock(completion)
|
||||
let animation = Init(CABasicAnimation(keyPath: "strokeEnd")) {
|
||||
$0.duration = CFTimeInterval(duration)
|
||||
$0.fromValue = (0)
|
||||
$0.toValue = (1)
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
|
||||
// MARK: animations
|
||||
|
||||
internal func fillAnimation(_ duration: Double, startAngle: Float, completion: @escaping () -> Void) {
|
||||
guard circle != nil else {
|
||||
return
|
||||
}
|
||||
|
||||
let rotateTransform = CATransform3DMakeRotation(CGFloat(startAngle.degrees), 0, 0, 1)
|
||||
layer.transform = rotateTransform
|
||||
|
||||
CATransaction.begin()
|
||||
CATransaction.setCompletionBlock(completion)
|
||||
let animation = Init(CABasicAnimation(keyPath: "strokeEnd")) {
|
||||
$0.duration = CFTimeInterval(duration)
|
||||
$0.fromValue = 0
|
||||
$0.toValue = 1
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
}
|
||||
circle?.add(animation, forKey: nil)
|
||||
CATransaction.commit()
|
||||
}
|
||||
circle?.add(animation, forKey: nil)
|
||||
CATransaction.commit()
|
||||
}
|
||||
|
||||
internal func hideAnimation(_ duration: CGFloat, delay: Double, completion: @escaping () -> Void) {
|
||||
|
||||
let scale = Init(CABasicAnimation(keyPath: "transform.scale")) {
|
||||
$0.toValue = 1.2
|
||||
$0.duration = CFTimeInterval(duration)
|
||||
$0.fillMode = kCAFillModeForwards
|
||||
$0.isRemovedOnCompletion = false
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
|
||||
$0.beginTime = CACurrentMediaTime() + delay
|
||||
|
||||
internal func hideAnimation(_ duration: CGFloat, delay: Double, completion: @escaping () -> Void) {
|
||||
|
||||
let scale = Init(CABasicAnimation(keyPath: "transform.scale")) {
|
||||
$0.toValue = 1.2
|
||||
$0.duration = CFTimeInterval(duration)
|
||||
$0.fillMode = kCAFillModeForwards
|
||||
$0.isRemovedOnCompletion = false
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
|
||||
$0.beginTime = CACurrentMediaTime() + delay
|
||||
}
|
||||
layer.add(scale, forKey: nil)
|
||||
|
||||
UIView.animate(
|
||||
withDuration: CFTimeInterval(duration),
|
||||
delay: delay,
|
||||
options: UIViewAnimationOptions.curveEaseIn,
|
||||
animations: { () -> Void in
|
||||
self.alpha = 0
|
||||
},
|
||||
completion: { (_) -> Void in
|
||||
self.removeFromSuperview()
|
||||
completion()
|
||||
})
|
||||
}
|
||||
layer.add(scale, forKey: nil)
|
||||
|
||||
UIView.animate(
|
||||
withDuration: CFTimeInterval(duration),
|
||||
delay: delay,
|
||||
options: UIViewAnimationOptions.curveEaseIn,
|
||||
animations: { () -> Void in
|
||||
self.alpha = 0
|
||||
},
|
||||
completion: { (success) -> Void in
|
||||
self.removeFromSuperview()
|
||||
completion()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,25 +6,23 @@
|
||||
// Copyright © 2016 Alex K. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import CircleMenu
|
||||
import XCTest
|
||||
|
||||
class CircleMenuTests: XCTestCase {
|
||||
|
||||
let buttonsCount = 4
|
||||
|
||||
let circleMenu = CircleMenu(
|
||||
frame: CGRect(x: 200, y: 200, width: 50, height: 50),
|
||||
normalIcon:"icon_menu",
|
||||
selectedIcon:"icon_close",
|
||||
buttonsCount: 4,
|
||||
duration: 4,
|
||||
distance: 120)
|
||||
frame: CGRect(x: 200, y: 200, width: 50, height: 50),
|
||||
normalIcon: "icon_menu",
|
||||
selectedIcon: "icon_close",
|
||||
buttonsCount: 4,
|
||||
duration: 4,
|
||||
distance: 120)
|
||||
|
||||
let view = UIView()
|
||||
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
circleMenu.buttonsCount = buttonsCount
|
||||
@@ -33,7 +31,6 @@ class CircleMenuTests: XCTestCase {
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
|
||||
}
|
||||
|
||||
func testCircleMenuShowButtons() {
|
||||
@@ -47,7 +44,6 @@ class CircleMenuTests: XCTestCase {
|
||||
XCTAssertEqual(circleMenu.buttons?.count, buttonsCount, "button is created")
|
||||
}
|
||||
|
||||
|
||||
func testCircleMenuHideButtons() {
|
||||
// given
|
||||
circleMenu.onTap()
|
||||
@@ -59,7 +55,6 @@ class CircleMenuTests: XCTestCase {
|
||||
XCTAssertNil(circleMenu.buttons, "button is removed")
|
||||
}
|
||||
|
||||
|
||||
func testCircleMenuHideButtonsAfterAnimation() {
|
||||
// given
|
||||
circleMenu.onTap()
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
[](https://business.ramotion.com?utm_source=gthb&utm_medium=special&utm_campaign=circle-menu-logo)
|
||||

|
||||

|
||||
|
||||
# CircleMenu
|
||||
[](http://twitter.com/Ramotion)
|
||||
[](https://cocoapods.org/pods/CircleMenu)
|
||||
@@ -8,19 +10,29 @@
|
||||
[](https://codebeat.co/projects/github-com-ramotion-circle-menu)
|
||||
[](https://travis-ci.org/Ramotion/circle-menu)
|
||||
|
||||
## About
|
||||
This project is maintained by Ramotion, Inc.<br>
|
||||
We specialize in the designing and coding of custom UI for Mobile Apps and Websites.<br><br>**Looking for developers for your project?** [[▶︎CONTACT OUR TEAM◀︎](http://business.ramotion.com?utm_source=gthb&utm_medium=special&utm_campaign=circle-menu-contact-us/#Get_in_Touch)]
|
||||
# Check this library on other platforms:
|
||||
<a href="https://github.com/Ramotion/circle-menu-android">
|
||||
<img src="https://github.com/ramotion/navigation-stack/raw/master/Android_Java@2x.png" width="178" height="81"></a>
|
||||
<a href="https://github.com/Ramotion/react-native-circle-menu">
|
||||
<img src="https://github.com/ramotion/navigation-stack/raw/master/React Native@2x.png" width="178" height="81"></a>
|
||||
|
||||
[](https://dribbble.com/shots/2534780-Circle-Menu-Swift-Open-Source)
|
||||
**Looking for developers for your project?**<br>
|
||||
This project is maintained by Ramotion, Inc. We specialize in the designing and coding of custom UI for Mobile Apps and Websites.
|
||||
|
||||
<a href="https://ramotion.com/?utm_source=gthb&utm_medium=special&utm_campaign=circle-menu-contact-us/#Get_in_Touch">
|
||||
<img src="https://github.com/ramotion/gliding-collection/raw/master/contact_our_team@2x.png" width="187" height="34"></a> <br>
|
||||
|
||||
|
||||
The [iPhone mockup](https://store.ramotion.com/product/iphone-6-mockups?utm_source=gthb&utm_medium=special&utm_campaign=circle-menu) available [here](https://store.ramotion.com/product/iphone-6-mockups?utm_source=gthb&utm_medium=special&utm_campaign=circle-menu).
|
||||
The [iPhone mockup](https://store.ramotion.com?utm_source=gthb&utm_medium=special&utm_campaign=circle-menu) available [here](https://store.ramotion.com?utm_source=gthb&utm_medium=special&utm_campaign=circle-menu).
|
||||
|
||||
## Try this UI control in action
|
||||
|
||||
<a href="https://itunes.apple.com/app/apple-store/id1182360240?pt=550053&ct=gthb-circle-menu&mt=8" > <img src="https://github.com/Ramotion/navigation-stack/raw/master/Download_on_the_App_Store_Badge_US-UK_135x40.png" width="170" height="58"></a>
|
||||
|
||||
## Requirements
|
||||
|
||||
- iOS 8.0+
|
||||
- Xcode 7.3
|
||||
- iOS 9.0+
|
||||
- Xcode 9.0.1
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -29,8 +41,7 @@ Just add CircleMenuLib folder to your project.
|
||||
or use [CocoaPods](https://cocoapods.org) with Podfile:
|
||||
|
||||
```ruby
|
||||
pod 'CircleMenu', '~> 2.0.1' swift 3
|
||||
pod 'CircleMenu', '~> 1.0.7' swift 2
|
||||
pod 'CircleMenu'
|
||||
```
|
||||
or [Carthage](https://github.com/Carthage/Carthage) users can simply add to their `Cartfile`:
|
||||
```
|
||||
@@ -48,7 +59,7 @@ github "Ramotion/circle-menu"
|
||||
3) Use delegate method to configure buttons
|
||||
|
||||
```swift
|
||||
func circleMenu(circleMenu: CircleMenu, willDisplay button: CircleMenuButton, atIndex: Int)
|
||||
func circleMenu(circleMenu: CircleMenu, willDisplay button: UIButton, atIndex: Int)
|
||||
```
|
||||
|
||||
4) Use properties to confiure CircleMenu
|
||||
@@ -78,13 +89,13 @@ view.addSubview(button)
|
||||
|
||||
```swift
|
||||
// configure buttons
|
||||
optional func circleMenu(circleMenu: CircleMenu, willDisplay button: CircleMenuButton, atIndex: Int)
|
||||
optional func circleMenu(circleMenu: CircleMenu, willDisplay button: UIButton, atIndex: Int)
|
||||
|
||||
// call before animation
|
||||
optional func circleMenu(circleMenu: CircleMenu, buttonWillSelected button: CircleMenuButton, atIndex: Int)
|
||||
optional func circleMenu(circleMenu: CircleMenu, buttonWillSelected button: UIButton, atIndex: Int)
|
||||
|
||||
// call after animation
|
||||
optional func circleMenu(circleMenu: CircleMenu, buttonDidSelected button: CircleMenuButton, atIndex: Int)
|
||||
optional func circleMenu(circleMenu: CircleMenu, buttonDidSelected button: UIButton, atIndex: Int)
|
||||
|
||||
// call upon cancel of the menu
|
||||
optional func menuCollapsed(circleMenu: CircleMenu)
|
||||
@@ -94,8 +105,18 @@ optional func menuCollapsed(circleMenu: CircleMenu)
|
||||
|
||||
Circle menu is released under the MIT license.
|
||||
See [LICENSE](./LICENSE) for details.
|
||||
<br>
|
||||
|
||||
## Folow us
|
||||
# Get the Showroom App for iOS and Android to give it a try
|
||||
Try this UI component and more like this in our mobile app. Contact us if interested.
|
||||
|
||||
<a href="https://itunes.apple.com/app/apple-store/id1182360240?pt=550053&ct=circle-menu&mt=8" >
|
||||
<img src="https://github.com/ramotion/gliding-collection/raw/master/app_store@2x.png" width="117" height="34"></a>
|
||||
<a href="https://ramotion.com/?utm_source=gthb&utm_medium=special&utm_campaign=circle-menu-contact-us/#Get_in_Touch">
|
||||
<img src="https://github.com/ramotion/gliding-collection/raw/master/contact_our_team@2x.png" width="187" height="34"></a>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
Follow us for the latest updates<br>
|
||||
[](https://twitter.com/intent/tweet?text=https://github.com/ramotion/circle-menu)
|
||||
[](https://twitter.com/ramotion)
|
||||
|
||||
Reference in New Issue
Block a user