Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4534c0dcd4 | |||
| b24752e27a | |||
| 22ef289da4 | |||
| 1428534fcc | |||
| 3ee0a8a606 | |||
| 0f35c65a48 | |||
| c578516270 | |||
| 1d00b258e9 | |||
| 37940a69eb | |||
| 739c698c02 | |||
| 3289f9c315 | |||
| 82f01134d2 | |||
| f6d7e82c98 | |||
| 5c8a416f5b | |||
| 0122f09e6b | |||
| 98a4fa1db9 | |||
| a766ceddbd | |||
| 6d961f5923 | |||
| cf2619bbd8 | |||
| d378620f8b | |||
| e02c17db4f | |||
| cb88f53fc6 | |||
| 5826edf31f | |||
| ec9a6fc6a0 | |||
| 6d45896997 | |||
| 0c2b2c49ee | |||
| bdb3ccb994 | |||
| 51627a3f3d | |||
| 68881fb3cc | |||
| 83d32f754b | |||
| 628a4bab8a | |||
| 2c9a57acdc | |||
| db7ca734e9 | |||
| f4f645548d | |||
| c520680100 | |||
| 04e27502df | |||
| 3968b56d02 | |||
| fc589564b4 | |||
| 41df3a1714 | |||
| 5782a0fafa | |||
| 0652edf64d | |||
| 1fffcfb25e | |||
| 3f3a676a6d | |||
| 7a6175e8fb | |||
| 6cf24b1ee1 | |||
| c37e32ce3f | |||
| 38926b111a | |||
| dce71006c9 | |||
| 77ae3b58c9 | |||
| 2d23e4f7f3 | |||
| 7e95b925a5 |
@@ -1 +1 @@
|
||||
4.0
|
||||
4.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'CircleMenu'
|
||||
s.version = '3.0.2'
|
||||
s.version = '4.1.0'
|
||||
s.summary = 'Amazing animation with buttons'
|
||||
s.homepage = 'https://github.com/Ramotion/circle-menu'
|
||||
s.license = 'MIT'
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objectVersion = 51;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -17,7 +17,6 @@
|
||||
841EC5911C58E898008872D5 /* CircleMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841EC58F1C58E898008872D5 /* CircleMenuButton.swift */; };
|
||||
841EC5941C58F3E2008872D5 /* CircleMenuLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841EC5931C58F3E2008872D5 /* CircleMenuLoader.swift */; };
|
||||
8497460C1C6A1C5D001E7184 /* CircleMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841EC58D1C58E898008872D5 /* CircleMenu.swift */; };
|
||||
8497460D1C6A1C6D001E7184 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84F248BD1C58E65F008F12C1 /* Assets.xcassets */; };
|
||||
8497460E1C6A1C9D001E7184 /* CircleMenuLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841EC5931C58F3E2008872D5 /* CircleMenuLoader.swift */; };
|
||||
8497460F1C6A1CA0001E7184 /* CircleMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841EC58F1C58E898008872D5 /* CircleMenuButton.swift */; };
|
||||
84F248B71C58E65F008F12C1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F248B61C58E65F008F12C1 /* AppDelegate.swift */; };
|
||||
@@ -72,7 +71,6 @@
|
||||
84F248BB1C58E65F008F12C1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
84F248BD1C58E65F008F12C1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
84F248C01C58E65F008F12C1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
84F248C21C58E65F008F12C1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
84F248C71C58E65F008F12C1 /* CircleMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CircleMenuTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
84F248CB1C58E65F008F12C1 /* CircleMenuTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleMenuTests.swift; sourceTree = "<group>"; };
|
||||
84F248CD1C58E65F008F12C1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@@ -168,7 +166,6 @@
|
||||
84F248BA1C58E65F008F12C1 /* Main.storyboard */,
|
||||
84F248BD1C58E65F008F12C1 /* Assets.xcassets */,
|
||||
84F248BF1C58E65F008F12C1 /* LaunchScreen.storyboard */,
|
||||
84F248C21C58E65F008F12C1 /* Info.plist */,
|
||||
);
|
||||
path = CircleMenu;
|
||||
sourceTree = "<group>";
|
||||
@@ -221,7 +218,6 @@
|
||||
84F248AF1C58E65F008F12C1 /* Sources */,
|
||||
84F248B01C58E65F008F12C1 /* Frameworks */,
|
||||
84F248B11C58E65F008F12C1 /* Resources */,
|
||||
846980FB1C59F398002D77BE /* swift lint */,
|
||||
8403F5851CFF2C2E007D0BD1 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
@@ -259,27 +255,28 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0720;
|
||||
LastUpgradeCheck = 0920;
|
||||
LastUpgradeCheck = 1020;
|
||||
ORGANIZATIONNAME = "Alex K.";
|
||||
TargetAttributes = {
|
||||
8403F5781CFF2C2E007D0BD1 = {
|
||||
CreatedOnToolsVersion = 7.3.1;
|
||||
LastSwiftMigration = 0900;
|
||||
LastSwiftMigration = 1020;
|
||||
};
|
||||
84F248B21C58E65F008F12C1 = {
|
||||
CreatedOnToolsVersion = 7.2;
|
||||
DevelopmentTeam = 34MUF9YXTA;
|
||||
LastSwiftMigration = 0900;
|
||||
LastSwiftMigration = 1020;
|
||||
};
|
||||
84F248C61C58E65F008F12C1 = {
|
||||
CreatedOnToolsVersion = 7.2;
|
||||
LastSwiftMigration = 1020;
|
||||
TestTargetID = 84F248B21C58E65F008F12C1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 84F248AE1C58E65F008F12C1 /* Build configuration list for PBXProject "CircleMenu" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
compatibilityVersion = "Xcode 10.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
@@ -319,29 +316,11 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8497460D1C6A1C6D001E7184 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
846980FB1C59F398002D77BE /* swift lint */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "swift lint";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"SwiftLint does not exist, download from https://github.com/realm/SwiftLint\"\nfi";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
8403F5741CFF2C2E007D0BD1 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
@@ -424,12 +403,15 @@
|
||||
INFOPLIST_FILE = CircleMenu/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ramotion.CircleMenu;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
@@ -449,12 +431,15 @@
|
||||
INFOPLIST_FILE = CircleMenu/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ramotion.CircleMenu;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
@@ -465,6 +450,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -473,12 +459,14 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = 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_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
@@ -510,7 +498,7 @@
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -518,6 +506,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -526,12 +515,14 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = 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_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
@@ -555,8 +546,9 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 4.2;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
@@ -567,14 +559,17 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
INFOPLIST_FILE = CircleMenu/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ramotion.dev;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -584,14 +579,17 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
INFOPLIST_FILE = CircleMenu/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ramotion.dev;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -600,9 +598,14 @@
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
INFOPLIST_FILE = CircleMenuTests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ramotion.CircleMenuTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CircleMenuDemo.app/CircleMenuDemo";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -612,9 +615,14 @@
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
INFOPLIST_FILE = CircleMenuTests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ramotion.CircleMenuTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CircleMenuDemo.app/CircleMenuDemo";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0920"
|
||||
LastUpgradeVersion = "1020"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -26,7 +26,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
@@ -37,7 +36,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
@@ -15,7 +15,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
func application(
|
||||
_: UIApplication,
|
||||
didFinishLaunchingWithOptions _: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
||||
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +1,119 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon-40.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon-60.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon-58.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon-87.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon-80.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon-120.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon-120.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon-180.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon-20.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon-40.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon-29.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon-58.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon-40.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon-80.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon-76.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon-152.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon-167.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Ramotion1024.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"properties" : {
|
||||
"pre-rendered" : true
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 464 B |
|
After Width: | Height: | Size: 618 B |
|
After Width: | Height: | Size: 834 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
@@ -26,7 +26,7 @@ class ViewController: UIViewController, CircleMenuDelegate {
|
||||
("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)),
|
||||
("nearby-btn", UIColor(red: 1, green: 0.39, blue: 0, alpha: 1))
|
||||
]
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
@@ -26,7 +26,7 @@ import UIKit
|
||||
|
||||
// MARK: helpers
|
||||
|
||||
func Init<Type>(_ value: Type, block: (_ object: Type) -> Void) -> Type {
|
||||
func customize<Type>(_ value: Type, block: (_ object: Type) -> Void) -> Type {
|
||||
block(value)
|
||||
return value
|
||||
}
|
||||
@@ -66,11 +66,18 @@ func Init<Type>(_ value: Type, block: (_ object: Type) -> Void) -> Type {
|
||||
@objc optional func circleMenu(_ circleMenu: CircleMenu, buttonDidSelected button: UIButton, atIndex: Int)
|
||||
|
||||
/**
|
||||
Tells the delegate that the menu was collapsed - the cancel action.
|
||||
Tells the delegate that the menu was collapsed - the cancel action. Fires immediately on button press
|
||||
|
||||
- parameter circleMenu: A circle menu object informing the delegate about the new index selection.
|
||||
*/
|
||||
@objc optional func menuCollapsed(_ circleMenu: CircleMenu)
|
||||
|
||||
/**
|
||||
Tells the delegate that the menu was opened. Fires immediately on button press
|
||||
|
||||
- parameter circleMenu: A circle menu object informing the delegate about the new index selection.
|
||||
*/
|
||||
@objc optional func menuOpened(_ circleMenu: CircleMenu)
|
||||
}
|
||||
|
||||
// MARK: CircleMenu
|
||||
@@ -88,18 +95,29 @@ open class CircleMenu: UIButton {
|
||||
@IBInspectable open var distance: Float = 100
|
||||
/// Delay between show buttons
|
||||
@IBInspectable open var showDelay: Double = 0
|
||||
|
||||
/// Start angle of the circle
|
||||
@IBInspectable open var startAngle: Float = 0
|
||||
/// End angle of the circle
|
||||
@IBInspectable open var endAngle: Float = 360
|
||||
|
||||
// Pop buttons radius, if nil use center button size
|
||||
open var subButtonsRadius: CGFloat?
|
||||
|
||||
// Show buttons event
|
||||
open var showButtonsEvent: UIControl.Event = UIControl.Event.touchUpInside {
|
||||
didSet {
|
||||
addActions(newEvent: showButtonsEvent, oldEvent: oldValue)
|
||||
}
|
||||
}
|
||||
|
||||
/// The object that acts as the delegate of the circle menu.
|
||||
@IBOutlet open var delegate: AnyObject? // CircleMenuDelegate?
|
||||
@IBOutlet open weak var delegate: AnyObject? // CircleMenuDelegate?
|
||||
|
||||
var buttons: [UIButton]?
|
||||
weak var platform: UIView?
|
||||
|
||||
fileprivate var customNormalIconView: UIImageView?
|
||||
fileprivate var customSelectedIconView: UIImageView?
|
||||
public var customNormalIconView: UIImageView?
|
||||
public var customSelectedIconView: UIImageView?
|
||||
|
||||
/**
|
||||
Initializes and returns a circle menu object.
|
||||
@@ -122,7 +140,7 @@ open class CircleMenu: UIButton {
|
||||
super.init(frame: frame)
|
||||
|
||||
if let icon = normalIcon {
|
||||
setImage(UIImage(named: icon), for: UIControlState())
|
||||
setImage(UIImage(named: icon), for: .normal)
|
||||
}
|
||||
|
||||
if let icon = selectedIcon {
|
||||
@@ -143,15 +161,14 @@ open class CircleMenu: UIButton {
|
||||
}
|
||||
|
||||
fileprivate func commonInit() {
|
||||
addActions()
|
||||
addActions(newEvent: showButtonsEvent)
|
||||
|
||||
customNormalIconView = addCustomImageView(state: UIControlState())
|
||||
customNormalIconView = addCustomImageView(state: .normal)
|
||||
|
||||
customSelectedIconView = addCustomImageView(state: .selected)
|
||||
if customSelectedIconView != nil {
|
||||
customSelectedIconView?.alpha = 0
|
||||
}
|
||||
setImage(UIImage(), for: UIControlState())
|
||||
customSelectedIconView?.alpha = 0
|
||||
|
||||
setImage(UIImage(), for: .normal)
|
||||
setImage(UIImage(), for: .selected)
|
||||
}
|
||||
|
||||
@@ -170,7 +187,7 @@ open class CircleMenu: UIButton {
|
||||
|
||||
buttonsAnimationIsShow(isShow: false, duration: duration, hideDelay: hideDelay)
|
||||
|
||||
tapBounceAnimation()
|
||||
tapBounceAnimation(duration: 0.5)
|
||||
tapRotatedAnimation(0.3, isSelected: false)
|
||||
}
|
||||
|
||||
@@ -188,22 +205,23 @@ open class CircleMenu: UIButton {
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
open override func removeFromSuperview() {
|
||||
if self.platform?.superview != nil { self.platform?.removeFromSuperview() }
|
||||
super.removeFromSuperview()
|
||||
}
|
||||
|
||||
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)
|
||||
let step = getArcStep()
|
||||
for index in 0 ..< buttonsCount {
|
||||
|
||||
let angle: Float = Float(index) * step
|
||||
let angle: Float = startAngle + Float(index) * step
|
||||
let distance = Float(bounds.size.height / 2.0)
|
||||
let buttonSize: CGSize
|
||||
if let subButtonsRadius = self.subButtonsRadius {
|
||||
@@ -211,9 +229,9 @@ open class CircleMenu: UIButton {
|
||||
} else {
|
||||
buttonSize = bounds.size
|
||||
}
|
||||
let button = Init(CircleMenuButton(size: buttonSize, platform: platform, distance: distance, angle: angle)) {
|
||||
let button = customize(CircleMenuButton(size: buttonSize, platform: platform, distance: distance, angle: angle)) {
|
||||
$0.tag = index
|
||||
$0.addTarget(self, action: #selector(CircleMenu.buttonHandler(_:)), for: UIControlEvents.touchUpInside)
|
||||
$0.addTarget(self, action: #selector(CircleMenu.buttonHandler(_:)), for: UIControl.Event.touchUpInside)
|
||||
$0.alpha = 0
|
||||
}
|
||||
buttons.append(button)
|
||||
@@ -221,12 +239,12 @@ open class CircleMenu: UIButton {
|
||||
return buttons
|
||||
}
|
||||
|
||||
fileprivate func addCustomImageView(state: UIControlState) -> UIImageView? {
|
||||
fileprivate func addCustomImageView(state: UIControl.State) -> UIImageView? {
|
||||
guard let image = image(for: state) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let iconView = Init(UIImageView(image: image)) {
|
||||
let iconView = customize(UIImageView(image: image)) {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.contentMode = .center
|
||||
$0.isUserInteractionEnabled = false
|
||||
@@ -250,14 +268,14 @@ open class CircleMenu: UIButton {
|
||||
}
|
||||
|
||||
fileprivate func createPlatform() -> UIView {
|
||||
let platform = Init(UIView(frame: .zero)) {
|
||||
let platform = customize(UIView(frame: .zero)) {
|
||||
$0.backgroundColor = .clear
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
superview?.insertSubview(platform, belowSubview: self)
|
||||
|
||||
// constraints
|
||||
let sizeConstraints = [NSLayoutAttribute.width, .height].map {
|
||||
let sizeConstraints = [NSLayoutConstraint.Attribute.width, .height].map {
|
||||
NSLayoutConstraint(item: platform,
|
||||
attribute: $0,
|
||||
relatedBy: .equal,
|
||||
@@ -268,7 +286,7 @@ open class CircleMenu: UIButton {
|
||||
}
|
||||
platform.addConstraints(sizeConstraints)
|
||||
|
||||
let centerConstraints = [NSLayoutAttribute.centerX, .centerY].map {
|
||||
let centerConstraints = [NSLayoutConstraint.Attribute.centerX, .centerY].map {
|
||||
NSLayoutConstraint(item: self,
|
||||
attribute: $0,
|
||||
relatedBy: .equal,
|
||||
@@ -284,23 +302,50 @@ open class CircleMenu: UIButton {
|
||||
|
||||
// MARK: configure
|
||||
|
||||
fileprivate func addActions() {
|
||||
addTarget(self, action: #selector(CircleMenu.onTap), for: UIControlEvents.touchUpInside)
|
||||
fileprivate func addActions(newEvent: UIControl.Event, oldEvent: UIControl.Event? = nil) {
|
||||
if let oldEvent = oldEvent { removeTarget(self, action: #selector(CircleMenu.onTap), for: oldEvent) }
|
||||
addTarget(self, action: #selector(CircleMenu.onTap), for: newEvent)
|
||||
}
|
||||
|
||||
/**
|
||||
Retrieves the incremental lengths between buttons. If the arc length is 360 degrees or more, the increments
|
||||
will evenly space out in a full circle. If the arc length is less than 360 degrees, the last button will be
|
||||
placed on the endAngle.
|
||||
*/
|
||||
fileprivate func getArcStep() -> Float {
|
||||
var arcLength = endAngle - startAngle
|
||||
var stepCount = buttonsCount
|
||||
|
||||
if arcLength < 360 {
|
||||
stepCount -= 1
|
||||
} else if arcLength > 360 {
|
||||
arcLength = 360
|
||||
}
|
||||
|
||||
return arcLength / Float(stepCount)
|
||||
}
|
||||
|
||||
// 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
|
||||
DispatchQueue.main.asyncAfter(deadline: .now()) {
|
||||
self.delegate?.menuOpened?(self)
|
||||
}
|
||||
}
|
||||
let isShow = !buttonsIsShown()
|
||||
let duration = isShow ? 0.5 : 0.2
|
||||
buttonsAnimationIsShow(isShow: isShow, duration: duration)
|
||||
|
||||
tapBounceAnimation()
|
||||
tapBounceAnimation(duration: 0.5) { [weak self] _ in self?.isBounceAnimating = false }
|
||||
tapRotatedAnimation(0.3, isSelected: isShow)
|
||||
}
|
||||
|
||||
@@ -311,7 +356,7 @@ open class CircleMenu: UIButton {
|
||||
|
||||
let strokeWidth: CGFloat
|
||||
if let radius = self.subButtonsRadius {
|
||||
strokeWidth = radius * 2
|
||||
strokeWidth = radius * 2
|
||||
} else {
|
||||
strokeWidth = bounds.size.height
|
||||
}
|
||||
@@ -323,28 +368,24 @@ open class CircleMenu: UIButton {
|
||||
|
||||
if let container = sender.container { // rotation animation
|
||||
sender.rotationAnimation(container.angleZ + 360, duration: duration)
|
||||
container.superview?.bringSubview(toFront: container)
|
||||
container.superview?.bringSubviewToFront(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)
|
||||
})
|
||||
}
|
||||
let step = getArcStep()
|
||||
circle.fillAnimation(duration, startAngle: -90 + startAngle + step * 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)
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + duration, execute: {
|
||||
self.delegate?.circleMenu?(self, buttonDidSelected: sender, atIndex: sender.tag)
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: animations
|
||||
|
||||
@@ -353,13 +394,12 @@ open class CircleMenu: UIButton {
|
||||
return
|
||||
}
|
||||
|
||||
let step: Float = 360.0 / Float(buttonsCount)
|
||||
let step = getArcStep()
|
||||
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)
|
||||
|
||||
let angle: Float = startAngle + Float(index) * step
|
||||
button.rotatedZ(angle: angle, animated: false, delay: Double(index) * showDelay)
|
||||
button.showAnimation(distance: distance, duration: duration, delay: Double(index) * showDelay)
|
||||
} else {
|
||||
@@ -375,14 +415,14 @@ open class CircleMenu: UIButton {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func tapBounceAnimation() {
|
||||
fileprivate func tapBounceAnimation(duration: TimeInterval, completion: ((Bool)->())? = nil) {
|
||||
transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
|
||||
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 5,
|
||||
options: UIViewAnimationOptions.curveLinear,
|
||||
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 5,
|
||||
options: UIView.AnimationOptions.curveLinear,
|
||||
animations: { () -> Void in
|
||||
self.transform = CGAffineTransform(scaleX: 1, y: 1)
|
||||
},
|
||||
completion: nil)
|
||||
self.transform = CGAffineTransform(scaleX: 1, y: 1)
|
||||
},
|
||||
completion: completion)
|
||||
}
|
||||
|
||||
fileprivate func tapRotatedAnimation(_ duration: Float, isSelected: Bool) {
|
||||
@@ -403,25 +443,25 @@ open class CircleMenu: UIButton {
|
||||
toOpacity = 1
|
||||
}
|
||||
|
||||
let rotation = Init(CABasicAnimation(keyPath: "transform.rotation")) {
|
||||
let rotation = customize(CABasicAnimation(keyPath: "transform.rotation")) {
|
||||
$0.duration = TimeInterval(duration)
|
||||
$0.toValue = (toAngle.degrees)
|
||||
$0.fromValue = (fromAngle.degrees)
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
$0.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
||||
}
|
||||
let fade = Init(CABasicAnimation(keyPath: "opacity")) {
|
||||
let fade = customize(CABasicAnimation(keyPath: "opacity")) {
|
||||
$0.duration = TimeInterval(duration)
|
||||
$0.fromValue = fromOpacity
|
||||
$0.toValue = toOpacity
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
$0.fillMode = kCAFillModeForwards
|
||||
$0.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
||||
$0.fillMode = CAMediaTimingFillMode.forwards
|
||||
$0.isRemovedOnCompletion = false
|
||||
}
|
||||
let scale = Init(CABasicAnimation(keyPath: "transform.scale")) {
|
||||
let scale = customize(CABasicAnimation(keyPath: "transform.scale")) {
|
||||
$0.duration = TimeInterval(duration)
|
||||
$0.toValue = toScale
|
||||
$0.fromValue = fromScale
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
$0.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
||||
}
|
||||
|
||||
view.layer.add(rotation, forKey: nil)
|
||||
@@ -442,22 +482,22 @@ open class CircleMenu: UIButton {
|
||||
|
||||
fileprivate func hideCenterButton(duration: Double, delay: Double = 0) {
|
||||
UIView.animate(withDuration: TimeInterval(duration), delay: TimeInterval(delay),
|
||||
options: UIViewAnimationOptions.curveEaseOut,
|
||||
options: UIView.AnimationOptions.curveEaseOut,
|
||||
animations: { () -> Void in
|
||||
self.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
|
||||
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,
|
||||
initialSpringVelocity: 0, options: UIView.AnimationOptions.curveLinear,
|
||||
animations: { () -> Void in
|
||||
self.transform = CGAffineTransform(scaleX: 1, y: 1)
|
||||
self.alpha = 1
|
||||
},
|
||||
self.transform = CGAffineTransform(scaleX: 1, y: 1)
|
||||
self.alpha = 1
|
||||
},
|
||||
completion: nil)
|
||||
|
||||
let rotation = Init(CASpringAnimation(keyPath: "transform.rotation")) {
|
||||
let rotation = customize(CASpringAnimation(keyPath: "transform.rotation")) {
|
||||
$0.duration = TimeInterval(1.5)
|
||||
$0.toValue = 0
|
||||
$0.fromValue = (Float(-180).degrees)
|
||||
@@ -466,19 +506,19 @@ open class CircleMenu: UIButton {
|
||||
$0.beginTime = CACurrentMediaTime() + delay
|
||||
}
|
||||
|
||||
let fade = Init(CABasicAnimation(keyPath: "opacity")) {
|
||||
let fade = customize(CABasicAnimation(keyPath: "opacity")) {
|
||||
$0.duration = TimeInterval(0.01)
|
||||
$0.toValue = 0
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
$0.fillMode = kCAFillModeForwards
|
||||
$0.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
||||
$0.fillMode = CAMediaTimingFillMode.forwards
|
||||
$0.isRemovedOnCompletion = false
|
||||
$0.beginTime = CACurrentMediaTime() + delay
|
||||
}
|
||||
let show = Init(CABasicAnimation(keyPath: "opacity")) {
|
||||
let show = customize(CABasicAnimation(keyPath: "opacity")) {
|
||||
$0.duration = TimeInterval(duration)
|
||||
$0.toValue = 1
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
$0.fillMode = kCAFillModeForwards
|
||||
$0.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
||||
$0.fillMode = CAMediaTimingFillMode.forwards
|
||||
$0.isRemovedOnCompletion = false
|
||||
$0.beginTime = CACurrentMediaTime() + delay
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ internal class CircleMenuButton: UIButton {
|
||||
// MARK: configure
|
||||
|
||||
fileprivate func createContainer(_ size: CGSize, platform: UIView) -> UIView {
|
||||
let container = Init(UIView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: size))) {
|
||||
let container = customize(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)
|
||||
@@ -117,7 +117,7 @@ internal class CircleMenuButton: UIButton {
|
||||
UIView.animate(
|
||||
withDuration: duration,
|
||||
delay: delay,
|
||||
options: UIViewAnimationOptions(),
|
||||
options: UIView.AnimationOptions(),
|
||||
animations: { () -> Void in
|
||||
container.layer.transform = rotateTransform
|
||||
},
|
||||
@@ -132,7 +132,7 @@ internal class CircleMenuButton: UIButton {
|
||||
|
||||
internal extension CircleMenuButton {
|
||||
|
||||
internal func showAnimation(distance: Float, duration: Double, delay: Double = 0) {
|
||||
func showAnimation(distance: Float, duration: Double, delay: Double = 0) {
|
||||
guard let heightConstraint = (self.container?.constraints.filter { $0.identifier == "height" })?.first else {
|
||||
fatalError()
|
||||
}
|
||||
@@ -148,7 +148,7 @@ internal extension CircleMenuButton {
|
||||
delay: delay,
|
||||
usingSpringWithDamping: 0.7,
|
||||
initialSpringVelocity: 0,
|
||||
options: UIViewAnimationOptions.curveLinear,
|
||||
options: UIView.AnimationOptions.curveLinear,
|
||||
animations: { () -> Void in
|
||||
self.container?.superview?.layoutIfNeeded()
|
||||
self.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
|
||||
@@ -157,7 +157,7 @@ internal extension CircleMenuButton {
|
||||
})
|
||||
}
|
||||
|
||||
internal func hideAnimation(distance: Float, duration: Double, delay: Double = 0) {
|
||||
func hideAnimation(distance: Float, duration: Double, delay: Double = 0) {
|
||||
guard let heightConstraint = (self.container?.constraints.filter { $0.identifier == "height" })?.first else {
|
||||
return
|
||||
}
|
||||
@@ -166,7 +166,7 @@ internal extension CircleMenuButton {
|
||||
UIView.animate(
|
||||
withDuration: duration,
|
||||
delay: delay,
|
||||
options: UIViewAnimationOptions.curveEaseIn,
|
||||
options: UIView.AnimationOptions.curveEaseIn,
|
||||
animations: { () -> Void in
|
||||
self.container?.superview?.layoutIfNeeded()
|
||||
self.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
|
||||
@@ -179,7 +179,7 @@ internal extension CircleMenuButton {
|
||||
})
|
||||
}
|
||||
|
||||
internal func changeDistance(_ distance: CGFloat, animated _: Bool, duration: Double = 0, delay: Double = 0) {
|
||||
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()
|
||||
@@ -190,7 +190,7 @@ internal extension CircleMenuButton {
|
||||
UIView.animate(
|
||||
withDuration: duration,
|
||||
delay: delay,
|
||||
options: UIViewAnimationOptions.curveEaseIn,
|
||||
options: UIView.AnimationOptions.curveEaseIn,
|
||||
animations: { () -> Void in
|
||||
self.container?.superview?.layoutIfNeeded()
|
||||
},
|
||||
@@ -199,11 +199,11 @@ internal extension CircleMenuButton {
|
||||
|
||||
// MARK: layer animation
|
||||
|
||||
internal func rotationAnimation(_ angle: Float, duration: Double) {
|
||||
let rotation = Init(CABasicAnimation(keyPath: "transform.rotation")) {
|
||||
func rotationAnimation(_ angle: Float, duration: Double) {
|
||||
let rotation = customize(CABasicAnimation(keyPath: "transform.rotation")) {
|
||||
$0.duration = TimeInterval(duration)
|
||||
$0.toValue = (angle.degrees)
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
$0.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
||||
}
|
||||
container?.layer.add(rotation, forKey: "rotation")
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ internal class CircleMenuLoader: UIView {
|
||||
endAngle: CGFloat.pi * 2,
|
||||
clockwise: true)
|
||||
|
||||
let circle = Init(CAShapeLayer()) {
|
||||
let circle = customize(CAShapeLayer()) {
|
||||
$0.path = circlePath.cgPath
|
||||
$0.fillColor = UIColor.clear.cgColor
|
||||
$0.strokeColor = color?.cgColor
|
||||
@@ -81,7 +81,7 @@ internal class CircleMenuLoader: UIView {
|
||||
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
// added constraints
|
||||
let sizeConstraints = [NSLayoutAttribute.width, .height].map {
|
||||
let sizeConstraints = [NSLayoutConstraint.Attribute.width, .height].map {
|
||||
NSLayoutConstraint(item: self,
|
||||
attribute: $0,
|
||||
relatedBy: .equal,
|
||||
@@ -92,7 +92,7 @@ internal class CircleMenuLoader: UIView {
|
||||
}
|
||||
addConstraints(sizeConstraints)
|
||||
|
||||
let centerConstaraints = [NSLayoutAttribute.centerY, .centerX].map {
|
||||
let centerConstaraints = [NSLayoutConstraint.Attribute.centerY, .centerX].map {
|
||||
NSLayoutConstraint(item: platform,
|
||||
attribute: $0,
|
||||
relatedBy: .equal,
|
||||
@@ -105,7 +105,7 @@ internal class CircleMenuLoader: UIView {
|
||||
}
|
||||
|
||||
internal func createRoundView(_ rect: CGRect, color: UIColor?) {
|
||||
let roundView = Init(UIView(frame: rect)) {
|
||||
let roundView = customize(UIView(frame: rect)) {
|
||||
$0.backgroundColor = UIColor.black
|
||||
$0.layer.cornerRadius = rect.size.width / 2.0
|
||||
$0.backgroundColor = color
|
||||
@@ -125,11 +125,11 @@ internal class CircleMenuLoader: UIView {
|
||||
|
||||
CATransaction.begin()
|
||||
CATransaction.setCompletionBlock(completion)
|
||||
let animation = Init(CABasicAnimation(keyPath: "strokeEnd")) {
|
||||
let animation = customize(CABasicAnimation(keyPath: "strokeEnd")) {
|
||||
$0.duration = CFTimeInterval(duration)
|
||||
$0.fromValue = 0
|
||||
$0.toValue = 1
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
$0.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
||||
}
|
||||
circle?.add(animation, forKey: nil)
|
||||
CATransaction.commit()
|
||||
@@ -137,12 +137,12 @@ internal class CircleMenuLoader: UIView {
|
||||
|
||||
internal func hideAnimation(_ duration: CGFloat, delay: Double, completion: @escaping () -> Void) {
|
||||
|
||||
let scale = Init(CABasicAnimation(keyPath: "transform.scale")) {
|
||||
let scale = customize(CABasicAnimation(keyPath: "transform.scale")) {
|
||||
$0.toValue = 1.2
|
||||
$0.duration = CFTimeInterval(duration)
|
||||
$0.fillMode = kCAFillModeForwards
|
||||
$0.fillMode = CAMediaTimingFillMode.forwards
|
||||
$0.isRemovedOnCompletion = false
|
||||
$0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
|
||||
$0.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
|
||||
$0.beginTime = CACurrentMediaTime() + delay
|
||||
}
|
||||
layer.add(scale, forKey: nil)
|
||||
@@ -150,7 +150,7 @@ internal class CircleMenuLoader: UIView {
|
||||
UIView.animate(
|
||||
withDuration: CFTimeInterval(duration),
|
||||
delay: delay,
|
||||
options: UIViewAnimationOptions.curveEaseIn,
|
||||
options: UIView.AnimationOptions.curveEaseIn,
|
||||
animations: { () -> Void in
|
||||
self.alpha = 0
|
||||
},
|
||||
|
||||
@@ -60,7 +60,7 @@ class CircleMenuTests: XCTestCase {
|
||||
circleMenu.onTap()
|
||||
|
||||
// when
|
||||
circleMenu.buttonHandler((circleMenu.buttons?.first)!)
|
||||
circleMenu.buttonHandler((circleMenu.buttons?.first)! as! CircleMenuButton)
|
||||
|
||||
// then
|
||||
XCTAssertNil(circleMenu.buttons, "button is removed")
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// swift-tools-version:5.1
|
||||
//
|
||||
// Package.swift
|
||||
//
|
||||
// Copyright (c) Ramotion (https://www.ramotion.com/)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "CircleMenu",
|
||||
platforms: [
|
||||
.iOS(.v9)
|
||||
],
|
||||
products: [
|
||||
.library(name: "CircleMenu",
|
||||
targets: ["CircleMenu"])
|
||||
],
|
||||
targets: [
|
||||
.target(name: "CircleMenu",
|
||||
path: "CircleMenuLib")
|
||||
],
|
||||
swiftLanguageVersions: [.v5]
|
||||
)
|
||||
@@ -1,7 +1,27 @@
|
||||

|
||||

|
||||
<a href="https://www.ramotion.com/agency/app-development/?utm_source=gthb&utm_medium=repo&utm_campaign=circle-menu"><img src="https://github.com/Ramotion/circle-menu/blob/master/header.png"></a>
|
||||
|
||||
<a href="https://github.com/Ramotion/circle-menu">
|
||||
<img align="left" src="https://github.com/Ramotion/circle-menu/blob/master/circle-menu.gif" width="480" height="360" /></a>
|
||||
|
||||
<p><h1 align="left">CIRCLE MENU</h1></p>
|
||||
|
||||
<h4>Simple, elegant UI menu with a circular layout and material design animations</h4>
|
||||
|
||||
|
||||
___
|
||||
|
||||
|
||||
|
||||
<p><h6>We specialize in the designing and coding of custom UI for Mobile Apps and Websites.</h6>
|
||||
<a href="https://www.ramotion.com/agency/app-development/?utm_source=gthb&utm_medium=repo&utm_campaign=circle-menu">
|
||||
<img src="https://github.com/ramotion/gliding-collection/raw/master/contact_our_team@2x.png" width="187" height="34"></a>
|
||||
</p>
|
||||
<p><h6>Stay tuned for the latest updates:</h6>
|
||||
<a href="https://goo.gl/rPFpid" >
|
||||
<img src="https://i.imgur.com/ziSqeSo.png/" width="156" height="28"></a></p>
|
||||
|
||||
</br>
|
||||
|
||||
# CircleMenu
|
||||
[](http://twitter.com/Ramotion)
|
||||
[](https://cocoapods.org/pods/CircleMenu)
|
||||
[](http://cocoapods.org/pods/CircleMenu)
|
||||
@@ -9,25 +29,8 @@
|
||||
[](https://github.com/Ramotion/circle-menu)
|
||||
[](https://codebeat.co/projects/github-com-ramotion-circle-menu)
|
||||
[](https://travis-ci.org/Ramotion/circle-menu)
|
||||
[](https://paypal.me/Ramotion)
|
||||
|
||||
# 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>
|
||||
|
||||
**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?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
|
||||
|
||||
@@ -62,7 +65,7 @@ github "Ramotion/circle-menu"
|
||||
func circleMenu(circleMenu: CircleMenu, willDisplay button: UIButton, atIndex: Int)
|
||||
```
|
||||
|
||||
4) Use properties to confiure CircleMenu
|
||||
4) Use properties to configure CircleMenu
|
||||
|
||||
```swift
|
||||
@IBInspectable var buttonsCount: Int = 3
|
||||
@@ -97,26 +100,36 @@ optional func circleMenu(circleMenu: CircleMenu, buttonWillSelected button: UIBu
|
||||
// call after animation
|
||||
optional func circleMenu(circleMenu: CircleMenu, buttonDidSelected button: UIButton, atIndex: Int)
|
||||
|
||||
// call upon cancel of the menu
|
||||
// call upon cancel of the menu - fires immediately on button press
|
||||
optional func menuCollapsed(circleMenu: CircleMenu)
|
||||
|
||||
// call upon opening of the menu - fires immediately on button press
|
||||
optional func menuOpened(circleMenu: CircleMenu)
|
||||
```
|
||||
|
||||
## Licence
|
||||
## 🗂 Check this library on other language:
|
||||
<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>
|
||||
|
||||
Circle menu is released under the MIT license.
|
||||
|
||||
## 📄 License
|
||||
|
||||
Circle Menu is released under the MIT license.
|
||||
See [LICENSE](./LICENSE) for details.
|
||||
<br>
|
||||
|
||||
# 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.
|
||||
This library is a part of a <a href="https://github.com/Ramotion/swift-ui-animation-components-and-libraries"><b>selection of our best UI open-source projects.</b></a>
|
||||
|
||||
<a href="https://itunes.apple.com/app/apple-store/id1182360240?pt=550053&ct=circle-menu&mt=8" >
|
||||
If you use the open-source library in your project, please make sure to credit and backlink to www.ramotion.com
|
||||
|
||||
## 📱 Get the Showroom App for iOS to give it a try
|
||||
Try this UI component and more like this in our iOS app. Contact us if interested.
|
||||
|
||||
<a href="https://itunes.apple.com/app/apple-store/id1182360240?pt=550053&ct=folding-cell&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">
|
||||
|
||||
<a href="https://www.ramotion.com/agency/app-development/?utm_source=gthb&utm_medium=repo&utm_campaign=circle-menu">
|
||||
<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)
|
||||
|
||||
|
After Width: | Height: | Size: 3.2 MiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |