Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3186f54db5 | |||
| 06297df85f | |||
| cc36499919 | |||
| 0dadf7c8d7 | |||
| d30c57a5a8 | |||
| e7cf153f3c | |||
| 95489cd2bf | |||
| 7bd9f9c408 | |||
| 34f5b61af7 | |||
| b5b8531fa3 | |||
| 59b52e5217 | |||
| 710cb18b83 | |||
| 046d08e2c5 | |||
| 97fc51eb1f | |||
| 82fa81b1ab |
+1
-1
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = 'ContextMenu'
|
||||
spec.version = '0.2.0'
|
||||
spec.version = '0.3.0'
|
||||
spec.license = { :type => 'MIT' }
|
||||
spec.homepage = 'https://github.com/GitHawkApp/ContextMenu'
|
||||
spec.authors = { 'Ryan Nystrom' => 'rnystrom@whoisryannystrom.com' }
|
||||
|
||||
@@ -23,9 +23,23 @@
|
||||
2971CE97205453D900342296 /* ContextMenuPresenting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2971CE89205453D900342296 /* ContextMenuPresenting.swift */; };
|
||||
2971CE98205453D900342296 /* CGRect+Area.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2971CE8A205453D900342296 /* CGRect+Area.swift */; };
|
||||
298D3AEE205B398500EDFB66 /* ContextMenuDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298D3AED205B398500EDFB66 /* ContextMenuDelegate.swift */; };
|
||||
2991418720BC757100B63A3B /* CGRect_AreaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2991418620BC757100B63A3B /* CGRect_AreaTests.swift */; };
|
||||
2991418920BC757100B63A3B /* ContextMenu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2971CE722054539900342296 /* ContextMenu.framework */; };
|
||||
2991419020BC77FA00B63A3B /* CGRect+DominantCorner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2991418F20BC77FA00B63A3B /* CGRect+DominantCorner.swift */; };
|
||||
2991419220BC789D00B63A3B /* CGRect_DominantCornerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2991419120BC789D00B63A3B /* CGRect_DominantCornerTests.swift */; };
|
||||
DE5D838B2055D72A0069A81D /* UIViewController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE5D838A2055D72A0069A81D /* UIViewController+Extensions.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
2991418A20BC757100B63A3B /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 2971CE692054539900342296 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 2971CE712054539900342296;
|
||||
remoteInfo = ContextMenu;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
2971CE722054539900342296 /* ContextMenu.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ContextMenu.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
2971CE752054539900342296 /* ContextMenu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ContextMenu.h; sourceTree = "<group>"; };
|
||||
@@ -45,6 +59,11 @@
|
||||
2971CE89205453D900342296 /* ContextMenuPresenting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuPresenting.swift; sourceTree = "<group>"; };
|
||||
2971CE8A205453D900342296 /* CGRect+Area.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGRect+Area.swift"; sourceTree = "<group>"; };
|
||||
298D3AED205B398500EDFB66 /* ContextMenuDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuDelegate.swift; sourceTree = "<group>"; };
|
||||
2991418420BC757000B63A3B /* ContextMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ContextMenuTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
2991418620BC757100B63A3B /* CGRect_AreaTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGRect_AreaTests.swift; sourceTree = "<group>"; };
|
||||
2991418820BC757100B63A3B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
2991418F20BC77FA00B63A3B /* CGRect+DominantCorner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGRect+DominantCorner.swift"; sourceTree = "<group>"; };
|
||||
2991419120BC789D00B63A3B /* CGRect_DominantCornerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGRect_DominantCornerTests.swift; sourceTree = "<group>"; };
|
||||
DE5D838A2055D72A0069A81D /* UIViewController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extensions.swift"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@@ -56,6 +75,14 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
2991418120BC757000B63A3B /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
2991418920BC757100B63A3B /* ContextMenu.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
@@ -63,6 +90,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2971CE742054539900342296 /* ContextMenu */,
|
||||
2991418520BC757100B63A3B /* ContextMenuTests */,
|
||||
2971CE732054539900342296 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
@@ -71,6 +99,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2971CE722054539900342296 /* ContextMenu.framework */,
|
||||
2991418420BC757000B63A3B /* ContextMenuTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -79,6 +108,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2971CE8A205453D900342296 /* CGRect+Area.swift */,
|
||||
2991418F20BC77FA00B63A3B /* CGRect+DominantCorner.swift */,
|
||||
2971CE87205453D900342296 /* ClippedContainerViewController.swift */,
|
||||
2971CE752054539900342296 /* ContextMenu.h */,
|
||||
2971CE84205453D900342296 /* ContextMenu.swift */,
|
||||
@@ -100,6 +130,16 @@
|
||||
path = ContextMenu;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2991418520BC757100B63A3B /* ContextMenuTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2991418620BC757100B63A3B /* CGRect_AreaTests.swift */,
|
||||
2991419120BC789D00B63A3B /* CGRect_DominantCornerTests.swift */,
|
||||
2991418820BC757100B63A3B /* Info.plist */,
|
||||
);
|
||||
path = ContextMenuTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
@@ -132,12 +172,31 @@
|
||||
productReference = 2971CE722054539900342296 /* ContextMenu.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
2991418320BC757000B63A3B /* ContextMenuTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 2991418E20BC757100B63A3B /* Build configuration list for PBXNativeTarget "ContextMenuTests" */;
|
||||
buildPhases = (
|
||||
2991418020BC757000B63A3B /* Sources */,
|
||||
2991418120BC757000B63A3B /* Frameworks */,
|
||||
2991418220BC757000B63A3B /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
2991418B20BC757100B63A3B /* PBXTargetDependency */,
|
||||
);
|
||||
name = ContextMenuTests;
|
||||
productName = ContextMenuTests;
|
||||
productReference = 2991418420BC757000B63A3B /* ContextMenuTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
2971CE692054539900342296 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0930;
|
||||
LastUpgradeCheck = 0920;
|
||||
ORGANIZATIONNAME = "Ryan Nystrom";
|
||||
TargetAttributes = {
|
||||
@@ -146,6 +205,10 @@
|
||||
LastSwiftMigration = 0920;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
2991418320BC757000B63A3B = {
|
||||
CreatedOnToolsVersion = 9.3;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 2971CE6C2054539900342296 /* Build configuration list for PBXProject "ContextMenu" */;
|
||||
@@ -161,6 +224,7 @@
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
2971CE712054539900342296 /* ContextMenu */,
|
||||
2991418320BC757000B63A3B /* ContextMenuTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -173,6 +237,13 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
2991418220BC757000B63A3B /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -194,13 +265,31 @@
|
||||
2971CE8F205453D900342296 /* ContextMenu+Item.swift in Sources */,
|
||||
2971CE8B205453D900342296 /* ContextMenuPresentationController.swift in Sources */,
|
||||
2971CE8C205453D900342296 /* ContextMenu+Options.swift in Sources */,
|
||||
2991419020BC77FA00B63A3B /* CGRect+DominantCorner.swift in Sources */,
|
||||
2971CE92205453D900342296 /* ContextMenu.swift in Sources */,
|
||||
2971CE8D205453D900342296 /* ContextMenu+UIViewControllerTransitioningDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
2991418020BC757000B63A3B /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
2991419220BC789D00B63A3B /* CGRect_DominantCornerTests.swift in Sources */,
|
||||
2991418720BC757100B63A3B /* CGRect_AreaTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
2991418B20BC757100B63A3B /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 2971CE712054539900342296 /* ContextMenu */;
|
||||
targetProxy = 2991418A20BC757100B63A3B /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
2971CE782054539900342296 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
@@ -360,6 +449,42 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
2991418C20BC757100B63A3B /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 523C4DWBTH;
|
||||
INFOPLIST_FILE = ContextMenuTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.3;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.whoisryannystrom.ContextMenuTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
2991418D20BC757100B63A3B /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = 523C4DWBTH;
|
||||
INFOPLIST_FILE = ContextMenuTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.3;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.whoisryannystrom.ContextMenuTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
@@ -381,6 +506,15 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
2991418E20BC757100B63A3B /* Build configuration list for PBXNativeTarget "ContextMenuTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
2991418C20BC757100B63A3B /* Debug */,
|
||||
2991418D20BC757100B63A3B /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 2971CE692054539900342296 /* Project object */;
|
||||
|
||||
@@ -26,10 +26,28 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2991418320BC757000B63A3B"
|
||||
BuildableName = "ContextMenuTests.xctest"
|
||||
BlueprintName = "ContextMenuTests"
|
||||
ReferencedContainer = "container:ContextMenu.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2971CE712054539900342296"
|
||||
BuildableName = "ContextMenu.framework"
|
||||
BlueprintName = "ContextMenu"
|
||||
ReferencedContainer = "container:ContextMenu.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
@@ -37,7 +55,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// CGRect+DominantCorner.swift
|
||||
// ContextMenu
|
||||
//
|
||||
// Created by Ryan Nystrom on 5/28/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension CGRect {
|
||||
|
||||
func dominantCorner(in rect: CGRect) -> SourceViewCorner? {
|
||||
let corners: [SourceViewCorner] = [
|
||||
SourceViewCorner(point: CGPoint(x: rect.minX, y: rect.minY), position: .topLeft),
|
||||
SourceViewCorner(point: CGPoint(x: rect.maxX, y: rect.minY), position: .topRight),
|
||||
SourceViewCorner(point: CGPoint(x: rect.minX, y: rect.maxY), position: .bottomLeft),
|
||||
SourceViewCorner(point: CGPoint(x: rect.maxX, y: rect.maxY), position: .bottomRight),
|
||||
]
|
||||
|
||||
var maxArea: CGFloat = 0
|
||||
var maxCorner: SourceViewCorner? = nil
|
||||
for corner in corners {
|
||||
let area = self.area(corner: corner)
|
||||
if area > maxArea {
|
||||
maxArea = area
|
||||
maxCorner = corner
|
||||
}
|
||||
}
|
||||
return maxCorner
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,6 +32,7 @@ class ClippedContainerViewController: UIViewController {
|
||||
view.layer.cornerRadius = options.containerStyle.cornerRadius
|
||||
view.layer.shadowRadius = options.containerStyle.shadowRadius
|
||||
view.layer.shadowOpacity = options.containerStyle.shadowOpacity
|
||||
view.layer.shadowOffset = options.containerStyle.shadowOffset
|
||||
view.layer.shadowColor = UIColor.black.cgColor
|
||||
view.backgroundColor = options.containerStyle.backgroundColor
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@ extension ContextMenu {
|
||||
/// The shadow opacity of the menu container. The shadow color is `UIColor.black`.
|
||||
public let shadowOpacity: Float
|
||||
|
||||
/// The shadow offset of the menu container.
|
||||
public let shadowOffset: CGSize
|
||||
|
||||
/// The padding from the source-view corner to apply to the x-axis. Positive is further away.
|
||||
public let xPadding: CGFloat
|
||||
|
||||
@@ -43,8 +46,9 @@ extension ContextMenu {
|
||||
|
||||
public init(
|
||||
cornerRadius: CGFloat = 8,
|
||||
shadowRadius: CGFloat = 15,
|
||||
shadowOpacity: Float = 0.1,
|
||||
shadowRadius: CGFloat = 10,
|
||||
shadowOpacity: Float = 0.4,
|
||||
shadowOffset: CGSize = CGSize(width: 0, height: 2),
|
||||
xPadding: CGFloat = 8,
|
||||
yPadding: CGFloat = 8,
|
||||
edgePadding: CGFloat = 15,
|
||||
@@ -55,6 +59,7 @@ extension ContextMenu {
|
||||
self.cornerRadius = cornerRadius
|
||||
self.shadowRadius = shadowRadius
|
||||
self.shadowOpacity = shadowOpacity
|
||||
self.shadowOffset = shadowOffset
|
||||
self.xPadding = xPadding
|
||||
self.yPadding = yPadding
|
||||
self.edgePadding = edgePadding
|
||||
|
||||
@@ -23,18 +23,18 @@ extension ContextMenu {
|
||||
let menuStyle: MenuStyle
|
||||
|
||||
/// Trigger haptic feedback when the menu is shown.
|
||||
let haptics: Bool
|
||||
let hapticsStyle: UIImpactFeedbackStyle?
|
||||
|
||||
public init(
|
||||
durations: AnimationDurations = AnimationDurations(),
|
||||
containerStyle: ContainerStyle = ContainerStyle(),
|
||||
menuStyle: MenuStyle = .default,
|
||||
haptics: Bool = true
|
||||
hapticsStyle: UIImpactFeedbackStyle? = nil
|
||||
) {
|
||||
self.durations = durations
|
||||
self.containerStyle = containerStyle
|
||||
self.menuStyle = menuStyle
|
||||
self.haptics = haptics
|
||||
self.hapticsStyle = hapticsStyle
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ public class ContextMenu: NSObject {
|
||||
public static let shared = ContextMenu()
|
||||
|
||||
var item: Item?
|
||||
let haptics = UIImpactFeedbackGenerator(style: .medium)
|
||||
|
||||
private override init() {}
|
||||
|
||||
/// Show a context menu from a view controller with given options.
|
||||
///
|
||||
@@ -37,7 +37,8 @@ public class ContextMenu: NSObject {
|
||||
previous.viewController.dismiss(animated: false)
|
||||
}
|
||||
|
||||
if options.haptics {
|
||||
if let style = options.hapticsStyle {
|
||||
let haptics = UIImpactFeedbackGenerator(style: style)
|
||||
haptics.impactOccurred()
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,29 @@ class ContextMenuPresentationController: UIPresentationController {
|
||||
|
||||
weak var contextDelegate: ContextMenuPresentationControllerDelegate?
|
||||
let item: ContextMenu.Item
|
||||
var keyboardSpace: CGFloat = 0
|
||||
|
||||
init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, item: ContextMenu.Item) {
|
||||
self.item = item
|
||||
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(onKeyboard(notification:)),
|
||||
name: .UIKeyboardWillShow,
|
||||
object: nil
|
||||
)
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(onKeyboard(notification:)),
|
||||
name: .UIKeyboardWillHide,
|
||||
object: nil
|
||||
)
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(onKeyboard(notification:)),
|
||||
name: .UIKeyboardWillChangeFrame,
|
||||
object: nil
|
||||
)
|
||||
}
|
||||
|
||||
lazy var overlayView: UIView = {
|
||||
@@ -34,23 +53,7 @@ class ContextMenuPresentationController: UIPresentationController {
|
||||
let frame = item.sourceView?.superview?.convert(sourceViewFrame, to: containerView)
|
||||
else { return nil}
|
||||
|
||||
let corners: [SourceViewCorner] = [
|
||||
SourceViewCorner(point: CGPoint(x: frame.minX, y: frame.minY), position: .topLeft),
|
||||
SourceViewCorner(point: CGPoint(x: frame.maxX, y: frame.minY), position: .topRight),
|
||||
SourceViewCorner(point: CGPoint(x: frame.minX, y: frame.maxY), position: .bottomLeft),
|
||||
SourceViewCorner(point: CGPoint(x: frame.maxX, y: frame.maxY), position: .bottomRight),
|
||||
]
|
||||
|
||||
var maxArea: CGFloat = 0
|
||||
var maxCorner: SourceViewCorner? = nil
|
||||
for corner in corners {
|
||||
let area = containerView.bounds.area(corner: corner)
|
||||
if area > maxArea {
|
||||
maxArea = area
|
||||
maxCorner = corner
|
||||
}
|
||||
}
|
||||
return maxCorner
|
||||
return containerView.bounds.dominantCorner(in: frame)
|
||||
}
|
||||
|
||||
override var frameOfPresentedViewInContainerView: CGRect {
|
||||
@@ -73,7 +76,7 @@ class ContextMenuPresentationController: UIPresentationController {
|
||||
} else {
|
||||
return CGRect(
|
||||
x: (containerBounds.width - size.width)/2,
|
||||
y: (containerBounds.height - size.height)/2,
|
||||
y: (containerBounds.height - keyboardSpace - size.height)/2,
|
||||
width: size.width,
|
||||
height: size.height
|
||||
)
|
||||
@@ -158,4 +161,16 @@ class ContextMenuPresentationController: UIPresentationController {
|
||||
}
|
||||
}
|
||||
|
||||
@objc func onKeyboard(notification: Notification) {
|
||||
guard let frame = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? CGRect,
|
||||
let duration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? TimeInterval,
|
||||
let containerView = self.containerView
|
||||
else { return }
|
||||
keyboardSpace = containerView.bounds.height - frame.minY
|
||||
UIView.animate(withDuration: duration) {
|
||||
containerView.setNeedsLayout()
|
||||
containerView.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import UIKit
|
||||
|
||||
struct SourceViewCorner {
|
||||
|
||||
enum Position {
|
||||
enum Position: Int {
|
||||
case topLeft
|
||||
case topRight
|
||||
case bottomLeft
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// ContextMenuTests.swift
|
||||
// ContextMenuTests
|
||||
//
|
||||
// Created by Ryan Nystrom on 5/28/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import ContextMenu
|
||||
|
||||
class CGRect_AreaTests: XCTestCase {
|
||||
|
||||
let rect = CGRect(x: 0, y: 0, width: 100, height: 100)
|
||||
|
||||
func test_whenCornersHavePadding() {
|
||||
XCTAssertEqual(rect.area(corner: SourceViewCorner(point: CGPoint(x: 10, y: 10), position: .topLeft)), 100)
|
||||
XCTAssertEqual(rect.area(corner: SourceViewCorner(point: CGPoint(x: 90, y: 10), position: .topRight)), 100)
|
||||
XCTAssertEqual(rect.area(corner: SourceViewCorner(point: CGPoint(x: 90, y: 90), position: .bottomRight)), 100)
|
||||
XCTAssertEqual(rect.area(corner: SourceViewCorner(point: CGPoint(x: 10, y: 90), position: .bottomLeft)), 100)
|
||||
}
|
||||
|
||||
func test_whenCornersHaveNoPadding() {
|
||||
XCTAssertEqual(rect.area(corner: SourceViewCorner(point: CGPoint(x: 0, y: 0), position: .topLeft)), 0)
|
||||
XCTAssertEqual(rect.area(corner: SourceViewCorner(point: CGPoint(x: 100, y: 0), position: .topRight)), 0)
|
||||
XCTAssertEqual(rect.area(corner: SourceViewCorner(point: CGPoint(x: 100, y: 100), position: .bottomRight)), 0)
|
||||
XCTAssertEqual(rect.area(corner: SourceViewCorner(point: CGPoint(x: 00, y: 100), position: .bottomLeft)), 0)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// CGRect_DominantCornerTests.swift
|
||||
// ContextMenuTests
|
||||
//
|
||||
// Created by Ryan Nystrom on 5/28/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import ContextMenu
|
||||
|
||||
class CGRect_DominantCornerTests: XCTestCase {
|
||||
|
||||
let rect = CGRect(x: 0, y: 0, width: 100, height: 100)
|
||||
|
||||
func test_whenCornerExists_withTopLeft() {
|
||||
XCTAssertEqual(rect.dominantCorner(in: CGRect(x: 11, y: 11, width: 80, height: 80))?.position, .topLeft)
|
||||
}
|
||||
|
||||
func test_whenCornerExists_withTopRight() {
|
||||
XCTAssertEqual(rect.dominantCorner(in: CGRect(x: 9, y: 11, width: 80, height: 80))?.position, .topRight)
|
||||
}
|
||||
|
||||
func test_whenCornerExists_withBottomLeft() {
|
||||
XCTAssertEqual(rect.dominantCorner(in: CGRect(x: 11, y: 9, width: 80, height: 80))?.position, .bottomLeft)
|
||||
}
|
||||
|
||||
func test_whenCornerExists_withBottomRight() {
|
||||
XCTAssertEqual(rect.dominantCorner(in: CGRect(x: 9, y: 9, width: 80, height: 80))?.position, .bottomRight)
|
||||
}
|
||||
|
||||
func test_whenCornersEqual_thatReturnsNil() {
|
||||
XCTAssertNil(rect.dominantCorner(in: rect))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -13,6 +13,7 @@
|
||||
2971CEAD205454BD00342296 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2971CEAC205454BD00342296 /* Assets.xcassets */; };
|
||||
2971CEB0205454BD00342296 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2971CEAE205454BD00342296 /* LaunchScreen.storyboard */; };
|
||||
2971CEB82054553400342296 /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2971CEB72054553400342296 /* MenuViewController.swift */; };
|
||||
29DE277C211654E800556C44 /* KeyboardMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29DE277B211654E800556C44 /* KeyboardMenuViewController.swift */; };
|
||||
5D9BB1BB05FE1C79E1E43B74 /* Pods_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 67EA137013E06182CC4BE858 /* Pods_Example.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@@ -25,6 +26,7 @@
|
||||
2971CEAF205454BD00342296 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
2971CEB1205454BD00342296 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
2971CEB72054553400342296 /* MenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuViewController.swift; sourceTree = "<group>"; };
|
||||
29DE277B211654E800556C44 /* KeyboardMenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardMenuViewController.swift; sourceTree = "<group>"; };
|
||||
445EDEAD1EC427ACF9B0E27F /* Pods-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
67EA137013E06182CC4BE858 /* Pods_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A8435E3320DB10C18B762D4A /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig"; sourceTree = "<group>"; };
|
||||
@@ -66,6 +68,7 @@
|
||||
2971CEA5205454BD00342296 /* AppDelegate.swift */,
|
||||
2971CEAC205454BD00342296 /* Assets.xcassets */,
|
||||
2971CEB1205454BD00342296 /* Info.plist */,
|
||||
29DE277B211654E800556C44 /* KeyboardMenuViewController.swift */,
|
||||
2971CEAE205454BD00342296 /* LaunchScreen.storyboard */,
|
||||
2971CEA9205454BD00342296 /* Main.storyboard */,
|
||||
2971CEB72054553400342296 /* MenuViewController.swift */,
|
||||
@@ -220,6 +223,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
29DE277C211654E800556C44 /* KeyboardMenuViewController.swift in Sources */,
|
||||
2971CEA8205454BD00342296 /* ViewController.swift in Sources */,
|
||||
2971CEB82054553400342296 /* MenuViewController.swift in Sources */,
|
||||
2971CEA6205454BD00342296 /* AppDelegate.swift in Sources */,
|
||||
|
||||
@@ -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,11 +1,11 @@
|
||||
<?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" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="e4f-v2-AZw">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="e4f-v2-AZw">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
@@ -29,25 +29,38 @@
|
||||
<!--View Controller-->
|
||||
<scene sceneID="rxc-Qs-SX5">
|
||||
<objects>
|
||||
<viewController id="EDa-Kx-2Bx" customClass="ViewController" customModule="ThingsUI" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<viewController id="EDa-Kx-2Bx" customClass="ViewController" customModule="Example" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="XV0-SI-Dlz">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="detailDisclosure" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bMp-Tt-bsj">
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="detailDisclosure" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bMp-Tt-bsj">
|
||||
<rect key="frame" x="176" y="322" width="22" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<connections>
|
||||
<action selector="onButton:" destination="EDa-Kx-2Bx" eventType="touchUpInside" id="1kc-D5-Xxx"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="7wb-6n-2xQ">
|
||||
<rect key="frame" x="154" y="266" width="66" height="30"/>
|
||||
<state key="normal" title="Keyboard"/>
|
||||
<connections>
|
||||
<action selector="onKeyboardButton:" destination="EDa-Kx-2Bx" eventType="touchUpInside" id="cq5-s1-ege"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="bMp-Tt-bsj" firstAttribute="top" secondItem="7wb-6n-2xQ" secondAttribute="bottom" constant="26" id="KiU-xP-eNi"/>
|
||||
<constraint firstItem="bMp-Tt-bsj" firstAttribute="centerY" secondItem="XV0-SI-Dlz" secondAttribute="centerY" id="iz2-Cf-TYK"/>
|
||||
<constraint firstItem="7wb-6n-2xQ" firstAttribute="centerX" secondItem="XV0-SI-Dlz" secondAttribute="centerX" id="vZG-CE-9Aq"/>
|
||||
<constraint firstItem="bMp-Tt-bsj" firstAttribute="centerX" secondItem="XV0-SI-Dlz" secondAttribute="centerX" id="zsN-8S-KnR"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="0wB-Wa-A20"/>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="FIU-fZ-kBQ"/>
|
||||
<connections>
|
||||
<outlet property="button" destination="bMp-Tt-bsj" id="MPB-L4-weP"/>
|
||||
<outlet property="keyboardButton" destination="7wb-6n-2xQ" id="EI3-VG-aa5"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Hcb-0m-21I" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// KeyboardMenuViewController.swift
|
||||
// Example
|
||||
//
|
||||
// Created by Ryan Nystrom on 8/4/18.
|
||||
// Copyright © 2018 Ryan Nystrom. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class KeyboardMenuViewController: UIViewController {
|
||||
|
||||
let textView = UITextView()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
title = "Keyboard"
|
||||
preferredContentSize = CGSize(width: 300, height: 200)
|
||||
view.addSubview(textView)
|
||||
textView.textContainerInset = UIEdgeInsets(top: 8, left: 15, bottom: 8, right: 15)
|
||||
textView.font = UIFont.systemFont(ofSize: 18)
|
||||
textView.text = "Lorem ipsum"
|
||||
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||
barButtonSystemItem: .done,
|
||||
target: self,
|
||||
action: #selector(onDone)
|
||||
)
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(
|
||||
title: "Dismiss",
|
||||
style: .plain,
|
||||
target: self,
|
||||
action: #selector(onDismiss)
|
||||
)
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
textView.frame = view.bounds
|
||||
}
|
||||
|
||||
@objc func onDone() {
|
||||
dismiss(animated: true)
|
||||
}
|
||||
|
||||
@objc func onDismiss() {
|
||||
textView.resignFirstResponder()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,7 +12,8 @@ import ContextMenu
|
||||
class ViewController: UIViewController, ContextMenuDelegate {
|
||||
|
||||
@IBOutlet weak var button: UIButton!
|
||||
|
||||
@IBOutlet weak var keyboardButton: UIButton!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
button.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(onPan(gesture:))))
|
||||
@@ -27,12 +28,20 @@ class ViewController: UIViewController, ContextMenuDelegate {
|
||||
ContextMenu.shared.show(
|
||||
sourceViewController: self,
|
||||
viewController: MenuViewController(),
|
||||
options: ContextMenu.Options(containerStyle: ContextMenu.ContainerStyle(backgroundColor: UIColor(red: 41/255.0, green: 45/255.0, blue: 53/255.0, alpha: 1)), menuStyle: .minimal),
|
||||
options: ContextMenu.Options(containerStyle: ContextMenu.ContainerStyle(backgroundColor: UIColor(red: 41/255.0, green: 45/255.0, blue: 53/255.0, alpha: 1)), menuStyle: .default, hapticsStyle: .medium),
|
||||
sourceView: button,
|
||||
delegate: self
|
||||
)
|
||||
}
|
||||
|
||||
@IBAction func onKeyboardButton(_ sender: Any) {
|
||||
ContextMenu.shared.show(
|
||||
sourceViewController: self,
|
||||
viewController: KeyboardMenuViewController(),
|
||||
options: ContextMenu.Options(menuStyle: .default, hapticsStyle: .medium)
|
||||
)
|
||||
}
|
||||
|
||||
//MARK: ContextMenuDelegate
|
||||
|
||||
func contextMenuWillDismiss(viewController: UIViewController, animated: Bool) {
|
||||
|
||||
Reference in New Issue
Block a user