├── Resources ├── demo.gif ├── logo.png ├── demo_dark.gif └── demo_userStyleDidChange.gif ├── Example ├── Podfile ├── ContextMenu │ ├── Images.xcassets │ │ ├── Contents.json │ │ ├── heart.fill.imageset │ │ │ ├── heart.fill.png │ │ │ └── Contents.json │ │ ├── trash.fill.imageset │ │ │ ├── trash.fill.png │ │ │ └── Contents.json │ │ ├── square.and.arrow.up.fill.imageset │ │ │ ├── square.and.arrow.up.fill.png │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── AppDelegate.swift │ ├── ViewController.swift │ └── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard ├── ContextMenu.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── ContextMenu-Example.xcscheme │ └── project.pbxproj └── ContextMenu.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── SwiftyContextMenu ├── Extensions │ ├── UIColor+.swift │ ├── UIStackView+.swift │ ├── UIViewController+.swift │ ├── UIView+.swift │ └── UIView+ContextMenu.swift ├── ContextMenuDelegate.swift ├── Views │ ├── SeparatorView.swift │ └── IntensityVisualEffectView.swift ├── ContextMenuBackgroundBlurView.swift ├── ContextMenuWindow.swift ├── ContextMenuBlurView.swift ├── ContextMenuTableView.swift ├── ContextMenuContentBlurView.swift ├── ContextMenuTitleLabel.swift ├── ContextMenuSeparatorView.swift ├── ContextMenuActionTableViewCell.swift ├── ContextMenu.swift └── ContextMenuViewController.swift ├── SwiftyContextMenu.podspec ├── LICENSE ├── .gitignore └── README.md /Resources/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarioIannotta/SwiftyContextMenu/HEAD/Resources/demo.gif -------------------------------------------------------------------------------- /Resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarioIannotta/SwiftyContextMenu/HEAD/Resources/logo.png -------------------------------------------------------------------------------- /Resources/demo_dark.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarioIannotta/SwiftyContextMenu/HEAD/Resources/demo_dark.gif -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | target 'ContextMenu_Example' do 4 | pod 'SwiftyContextMenu', :path => '../' 5 | end 6 | -------------------------------------------------------------------------------- /Example/ContextMenu/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Resources/demo_userStyleDidChange.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarioIannotta/SwiftyContextMenu/HEAD/Resources/demo_userStyleDidChange.gif -------------------------------------------------------------------------------- /Example/ContextMenu/Images.xcassets/heart.fill.imageset/heart.fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarioIannotta/SwiftyContextMenu/HEAD/Example/ContextMenu/Images.xcassets/heart.fill.imageset/heart.fill.png -------------------------------------------------------------------------------- /Example/ContextMenu/Images.xcassets/trash.fill.imageset/trash.fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarioIannotta/SwiftyContextMenu/HEAD/Example/ContextMenu/Images.xcassets/trash.fill.imageset/trash.fill.png -------------------------------------------------------------------------------- /Example/ContextMenu.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/ContextMenu/Images.xcassets/square.and.arrow.up.fill.imageset/square.and.arrow.up.fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarioIannotta/SwiftyContextMenu/HEAD/Example/ContextMenu/Images.xcassets/square.and.arrow.up.fill.imageset/square.and.arrow.up.fill.png -------------------------------------------------------------------------------- /Example/ContextMenu.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/ContextMenu.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftyContextMenu/Extensions/UIColor+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContextMenuTitleLabel.swift 3 | // SwiftyContextMenu 4 | // 5 | // Created by Paul Bancarel on 28/11/2020. 6 | // 7 | 8 | extension UIColor { 9 | 10 | static var defaultLabelMenuActionColor: UIColor { 11 | if #available(iOS 13.0, *) { 12 | return .label 13 | } else { 14 | return .black 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Example/ContextMenu/Images.xcassets/heart.fill.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "heart.fill.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Example/ContextMenu/Images.xcassets/trash.fill.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "trash.fill.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftyContextMenu/Extensions/UIStackView+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContextMenuTitleLabel.swift 3 | // SwiftyContextMenu 4 | // 5 | // Created by Paul Bancarel on 28/11/2020. 6 | // 7 | 8 | extension UIStackView { 9 | 10 | convenience init(arrangedSubviews: [UIView], axis: NSLayoutConstraint.Axis, spacing: CGFloat = 0) { 11 | self.init(arrangedSubviews: arrangedSubviews) 12 | self.spacing = spacing 13 | self.axis = axis 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Example/ContextMenu/Images.xcassets/square.and.arrow.up.fill.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "square.and.arrow.up.fill.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftyContextMenu/ContextMenuDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContextMenuDelegate.swift 3 | // ContextMenu 4 | // 5 | // Created by Mario Iannotta on 09/07/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol ContextMenuDelegate: class { 11 | 12 | func contextMenuWillAppear(_ contextMenu: ContextMenu) 13 | func contextMenuDidAppear(_ contextMenu: ContextMenu) 14 | } 15 | 16 | public extension ContextMenuDelegate { 17 | 18 | func contextMenuWillAppear(_ contextMenu: ContextMenu) { } 19 | func contextMenuDidAppear(_ contextMenu: ContextMenu) { } 20 | } 21 | -------------------------------------------------------------------------------- /SwiftyContextMenu/Views/SeparatorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContextMenuTitleLabel.swift 3 | // SwiftyContextMenu 4 | // 5 | // Created by Paul Bancarel on 28/11/2020. 6 | // 7 | 8 | class SeparatorView: UIView { 9 | 10 | override class var layerClass: AnyClass { return CAShapeLayer.self } 11 | 12 | override func layoutSubviews() { 13 | super.layoutSubviews() 14 | updateLayer() 15 | } 16 | 17 | private func updateLayer() { 18 | let layer = self.layer as! CAShapeLayer 19 | layer.path = UIBezierPath(rect: bounds).cgPath 20 | layer.fillColor = tintColor.cgColor 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SwiftyContextMenu/Extensions/UIViewController+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+.swift 3 | // ContextMenu 4 | // 5 | // Created by Mario Iannotta on 14/06/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIViewController { 11 | 12 | var topAnchor: NSLayoutYAxisAnchor { 13 | if #available(iOS 11.0, *) { 14 | return view.safeAreaLayoutGuide.topAnchor 15 | } else { 16 | return topLayoutGuide.topAnchor 17 | } 18 | } 19 | 20 | var bottomAnchor: NSLayoutYAxisAnchor { 21 | if #available(iOS 11.0, *) { 22 | return view.safeAreaLayoutGuide.bottomAnchor 23 | } else { 24 | return bottomLayoutGuide.topAnchor 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SwiftyContextMenu/ContextMenuBackgroundBlurView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContextMenuBackgroundBlurView.swift 3 | // SwiftyContextMenu 4 | // 5 | // Created by Paul Bancarel on 28/11/2020. 6 | // 7 | 8 | class ContextMenuBackgroundBlurView: ContextMenuBlurView { 9 | 10 | override class var intensity: CGFloat { 0.25 } 11 | 12 | override class func blurEffect(_ style: ContextMenuUserInterfaceStyle) -> UIVisualEffect { 13 | switch style { 14 | case .automatic: 15 | return UIBlurEffect(style: .light) 16 | case .light: 17 | if #available(iOS 13.0, *) { 18 | return UIBlurEffect(style: .light) 19 | } else { 20 | return UIBlurEffect(style: .extraLight) 21 | } 22 | case .dark: 23 | return UIBlurEffect(style: .dark) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SwiftyContextMenu.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'SwiftyContextMenu' 3 | s.version = '0.1.5' 4 | s.summary = 'UIContextMenu backporting with Swifter API.' 5 | s.description = <<-DESC 6 | SwiftyContextMenu is a backporting of UIContextMenu available on iOS 10+ that is easier to integrate. 7 | DESC 8 | 9 | s.homepage = 'https://github.com/MarioIannotta/SwiftyContextMenu' 10 | s.license = { :type => 'MIT', :file => 'LICENSE' } 11 | s.author = { 'MarioIannotta' => 'info@marioiannotta.com' } 12 | s.source = { :git => 'https://github.com/MarioIannotta/SwiftyContextMenu.git', :tag => s.version.to_s } 13 | s.social_media_url = 'https://twitter.com/MarioIannotta' 14 | 15 | s.ios.deployment_target = '10.0' 16 | s.swift_version = '5.2' 17 | s.source_files = 'SwiftyContextMenu/**/*' 18 | 19 | end 20 | -------------------------------------------------------------------------------- /SwiftyContextMenu/ContextMenuWindow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContextMenuWindow.swift 3 | // ContextMenu 4 | // 5 | // Created by Mario Iannotta on 14/06/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | class ContextMenuWindow: UIWindow, ContextMenuViewControllerDelegate { 11 | 12 | private var onDismiss: (() -> Void)? 13 | 14 | init(contextMenu: ContextMenu, onDismiss: @escaping (() -> Void)) { 15 | super.init(frame: UIScreen.main.bounds) 16 | self.onDismiss = onDismiss 17 | rootViewController = ContextMenuViewController(contextMenu: contextMenu, delegate: self) 18 | windowLevel = UIWindow.Level.statusBar 19 | } 20 | 21 | required init?(coder: NSCoder) { 22 | fatalError("init(coder:) has not been implemented") 23 | } 24 | 25 | override func resignKey() { 26 | super.resignKey() 27 | onDismiss?() 28 | } 29 | 30 | func contextMenuViewControllerDidDismiss(_ contextMenuViewController: ContextMenuViewController) { 31 | resignKey() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /SwiftyContextMenu/ContextMenuBlurView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContextMenuBlurView.swift 3 | // SwiftyContextMenu 4 | // 5 | // Created by Paul Bancarel on 28/11/2020. 6 | // 7 | 8 | class ContextMenuBlurView: IntensityVisualEffectView { 9 | 10 | class var intensity: CGFloat { 1.0 } 11 | internal let style: ContextMenuUserInterfaceStyle 12 | 13 | init(_ style: ContextMenuUserInterfaceStyle) { 14 | self.style = style 15 | super.init( 16 | effect: type(of: self).blurEffect(style), 17 | intensity: type(of: self).intensity 18 | ) 19 | } 20 | 21 | required init?(coder aDecoder: NSCoder) { 22 | fatalError("init(coder:) has not been implemented") 23 | } 24 | 25 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { 26 | super.traitCollectionDidChange(previousTraitCollection) 27 | effect = type(of: self).blurEffect(style) 28 | } 29 | 30 | internal class func blurEffect(_ style: ContextMenuUserInterfaceStyle) -> UIVisualEffect { 31 | UIVisualEffect() 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Example/ContextMenu/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /SwiftyContextMenu/ContextMenuTableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContextMenuTableView.swift 3 | // ContextMenu 4 | // 5 | // Created by Mario Iannotta on 14/06/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | class ContextMenuTableView: UITableView { 11 | 12 | init() { 13 | super.init(frame: .zero, style: .plain) 14 | separatorInset = .zero 15 | separatorStyle = .none 16 | backgroundColor = .clear 17 | } 18 | 19 | required init?(coder: NSCoder) { 20 | fatalError("init(coder:) has not been implemented") 21 | } 22 | 23 | override var contentSize: CGSize { 24 | didSet { 25 | guard 26 | contentSize != oldValue 27 | else { return } 28 | invalidateIntrinsicContentSize() 29 | setNeedsLayout() 30 | } 31 | } 32 | 33 | override var intrinsicContentSize: CGSize { 34 | CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height) 35 | } 36 | 37 | override func layoutSubviews() { 38 | super.layoutSubviews() 39 | isScrollEnabled = contentSize.height > bounds.height 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mario Iannotta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SwiftyContextMenu/ContextMenuContentBlurView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContextMenuContentBlurView.swift 3 | // SwiftyContextMenu 4 | // 5 | // Created by Paul Bancarel on 29/11/2020. 6 | // 7 | 8 | class ContextMenuContentBlurView: ContextMenuBlurView { 9 | 10 | override class var intensity: CGFloat { 1.0 } 11 | 12 | override class func blurEffect(_ style: ContextMenuUserInterfaceStyle) -> UIVisualEffect { 13 | switch style { 14 | case .automatic: 15 | if #available(iOS 13.0, *) { 16 | return UIBlurEffect(style: .systemMaterial) 17 | } else { 18 | print("Cannot have an automatic blur effect below iOS 13.") 19 | return UIBlurEffect(style: .light) 20 | } 21 | case .light: 22 | if #available(iOS 13.0, *) { 23 | return UIBlurEffect(style: .systemMaterialLight) 24 | } else { 25 | return UIBlurEffect(style: .extraLight) 26 | } 27 | case .dark: 28 | if #available(iOS 13.0, *) { 29 | return UIBlurEffect(style: .systemMaterialDark) 30 | } else { 31 | return UIBlurEffect(style: .dark) 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /SwiftyContextMenu/Views/IntensityVisualEffectView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContextMenuTitleLabel.swift 3 | // SwiftyContextMenu 4 | // 5 | // Created by Paul Bancarel on 28/11/2020. 6 | // 7 | 8 | class IntensityVisualEffectView: UIVisualEffectView { 9 | 10 | private var animator: UIViewPropertyAnimator! 11 | private let intensity: CGFloat 12 | private let _effect: UIVisualEffect 13 | 14 | init(effect: UIVisualEffect, intensity: CGFloat) { 15 | self.intensity = intensity 16 | self._effect = effect 17 | super.init(effect: nil) 18 | apply(intensity: intensity) 19 | 20 | NotificationCenter.default.addObserver(self, selector: #selector(applicationWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil) 21 | } 22 | 23 | required init?(coder aDecoder: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | 27 | deinit { 28 | NotificationCenter.default.removeObserver(self) 29 | } 30 | 31 | @objc private func applicationWillEnterForeground() { 32 | effect = nil 33 | apply(intensity: intensity) 34 | } 35 | 36 | private func apply(intensity: CGFloat) { 37 | animator = UIViewPropertyAnimator(duration: 1, curve: .linear) { [unowned self] in self.effect = _effect } 38 | animator.fractionComplete = intensity 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Example/ContextMenu/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | NSAppTransportSecurity 30 | 31 | NSAllowsLocalNetworking 32 | 33 | 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /SwiftyContextMenu/ContextMenuTitleLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContextMenuTitleLabel.swift 3 | // SwiftyContextMenu 4 | // 5 | // Created by Paul Bancarel on 28/11/2020. 6 | // 7 | 8 | class ContextMenuTitleLabel: UILabel { 9 | 10 | private let style: ContextMenuUserInterfaceStyle 11 | private let darkTextColor = UIColor(red: 0.922, green: 0.922, blue: 0.961, alpha: 0.6) 12 | private let lightTextColor = UIColor(red: 0.235, green: 0.235, blue: 0.263, alpha: 0.6) 13 | 14 | init(frame: CGRect, style: ContextMenuUserInterfaceStyle) { 15 | self.style = style 16 | super.init(frame: frame) 17 | font = UIFont.preferredFont(forTextStyle: .footnote) 18 | adjustsFontForContentSizeCategory = true 19 | textAlignment = .center 20 | updateTextColor() 21 | } 22 | 23 | required init?(coder: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | 27 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { 28 | super.traitCollectionDidChange(previousTraitCollection) 29 | updateTextColor() 30 | } 31 | 32 | private func updateTextColor() { 33 | switch style { 34 | case .automatic: 35 | textColor = isDarkMode ? darkTextColor : lightTextColor 36 | case .light: 37 | textColor = lightTextColor 38 | case .dark: 39 | textColor = darkTextColor 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /SwiftyContextMenu/Extensions/UIView+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+.swift 3 | // ContextMenu 4 | // 5 | // Created by Mario Iannotta on 14/06/2020. 6 | // 7 | 8 | import Foundation 9 | 10 | extension UIView { 11 | 12 | var absoluteFrame: CGRect { 13 | UIApplication.shared.delegate?.window.flatMap { convert(bounds, to: $0) } ?? .zero 14 | } 15 | 16 | func snapshot() -> UIImage? { 17 | UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.main.scale) 18 | drawHierarchy(in: bounds, afterScreenUpdates: true) 19 | let image = UIGraphicsGetImageFromCurrentImageContext() 20 | UIGraphicsEndImageContext() 21 | return image 22 | } 23 | 24 | func fill(with subview: UIView, insets: UIEdgeInsets = .zero) { 25 | subview.translatesAutoresizingMaskIntoConstraints = false 26 | addSubview(subview) 27 | NSLayoutConstraint.activate([ 28 | subview.leadingAnchor.constraint(equalTo: leadingAnchor, constant: insets.left), 29 | trailingAnchor.constraint(equalTo: subview.trailingAnchor, constant: insets.right), 30 | subview.topAnchor.constraint(equalTo: topAnchor, constant: insets.top), 31 | bottomAnchor.constraint(equalTo: subview.bottomAnchor, constant: insets.bottom)]) 32 | } 33 | 34 | var isDarkMode: Bool { 35 | if #available(iOS 13.0, *) { 36 | return traitCollection.userInterfaceStyle == .dark 37 | } else { 38 | return false 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /SwiftyContextMenu/ContextMenuSeparatorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContextMenuSeparatorView.swift 3 | // SwiftyContextMenu 4 | // 5 | // Created by Paul Bancarel on 28/11/2020. 6 | // 7 | 8 | class ContextMenuSeparatorView: UIStackView { 9 | var style: ContextMenuUserInterfaceStyle { 10 | didSet { 11 | updateLightDarkVisibility() 12 | } 13 | } 14 | private let lightView: UIView 15 | private let darkView: UIVisualEffectView 16 | 17 | init(frame: CGRect, style: ContextMenuUserInterfaceStyle) { 18 | self.style = style 19 | 20 | let separatorView = SeparatorView(frame: frame) 21 | darkView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: UIBlurEffect(style: .dark))) 22 | darkView.contentView.fill(with: separatorView) 23 | 24 | lightView = SeparatorView(frame: frame) 25 | lightView.tintColor = UIColor(red: 0.235, green: 0.235, blue: 0.263, alpha: 0.6) 26 | 27 | super.init(frame: frame) 28 | backgroundColor = .clear 29 | axis = .vertical 30 | 31 | addArrangedSubview(lightView) 32 | addArrangedSubview(darkView) 33 | updateLightDarkVisibility() 34 | } 35 | 36 | required init(coder: NSCoder) { 37 | fatalError("init(coder:) has not been implemented") 38 | } 39 | 40 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { 41 | super.traitCollectionDidChange(previousTraitCollection) 42 | updateLightDarkVisibility() 43 | } 44 | 45 | private func updateLightDarkVisibility() { 46 | switch style { 47 | case .automatic: 48 | lightView.isHidden = isDarkMode ? true : false 49 | darkView.isHidden = isDarkMode ? false : true 50 | case .light: 51 | lightView.isHidden = false 52 | darkView.isHidden = true 53 | case .dark: 54 | lightView.isHidden = true 55 | darkView.isHidden = false 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Example/ContextMenu/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ContextMenu 4 | // 5 | // Created by Mario Iannotta on 06/14/2020. 6 | // Copyright (c) 2020 Mario Iannotta. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | .DS_Store 92 | 93 | Example/Pods/ 94 | 95 | Example/Podfile.lock 96 | -------------------------------------------------------------------------------- /Example/ContextMenu/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // ContextMenu 4 | // 5 | // Created by Mario Iannotta on 06/14/2020. 6 | // Copyright (c) 2020 Mario Iannotta. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftyContextMenu 11 | 12 | class ViewController: UIViewController { 13 | 14 | @IBOutlet private var uiContextMenuButtons: [UIButton]! 15 | @IBOutlet private var contextMenuButtons: [UIButton]! 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | (contextMenuButtons + uiContextMenuButtons).forEach { 21 | $0.layer.cornerRadius = 10 22 | } 23 | 24 | if #available(iOS 13.0, *) { 25 | uiContextMenuButtons.forEach { 26 | $0.addInteraction(UIContextMenuInteraction(delegate: self)) 27 | } 28 | } 29 | 30 | let favoriteAction = ContextMenuAction(title: "Looooooooooooong title", 31 | image: UIImage(named: "heart.fill"), 32 | action: { _ in print("favorite") }) 33 | let shareAction = ContextMenuAction(title: "Share", 34 | image: UIImage(named: "square.and.arrow.up.fill"), 35 | action: { _ in print("share") }) 36 | let deleteAction = ContextMenuAction(title: "Delete", 37 | image: UIImage(named: "trash.fill"), 38 | tintColor: UIColor.red, 39 | action: { _ in print("delete") }) 40 | let actions = [favoriteAction, shareAction, deleteAction] 41 | let contextMenu = ContextMenu( 42 | title: "Actions", 43 | actions: actions, 44 | delegate: self) 45 | contextMenuButtons 46 | .forEach { 47 | $0.addContextMenu(contextMenu, for: .tap(numberOfTaps: 1), .longPress(duration: 0.3)) 48 | } 49 | } 50 | } 51 | 52 | extension ViewController: UIContextMenuInteractionDelegate { 53 | 54 | @available(iOS 13.0, *) 55 | func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { 56 | 57 | let favorite = UIAction(title: "Looooooooooooong title", image: UIImage(systemName: "heart.fill"), handler: { _ in }) 58 | let share = UIAction(title: "Share", image: UIImage(systemName: "square.and.arrow.up.fill"), handler: { _ in }) 59 | let delete = UIAction(title: "Delete", image: UIImage(systemName: "trash.fill"), attributes: [.destructive], handler: { _ in }) 60 | 61 | let actions = [favorite, share, delete] 62 | 63 | return UIContextMenuConfiguration(identifier: nil, 64 | previewProvider: nil) { _ in UIMenu(title: "Actions", children: actions) } 65 | } 66 | 67 | } 68 | 69 | extension ViewController: ContextMenuDelegate { 70 | 71 | func contextMenuWillAppear(_ contextMenu: ContextMenu) { 72 | print("context menu will appear") 73 | } 74 | 75 | func contextMenuDidAppear(_ contextMenu: ContextMenu) { 76 | print("context menu did appear") 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SwiftyContextMenu: UIContextMenu backporting with Swifter API 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/SwiftyContextMenu.svg?style=flat)](https://cocoapods.org/pods/SwiftyContextMenu) 4 | [![License](https://img.shields.io/cocoapods/l/SwiftyContextMenu.svg?style=flat)](https://cocoapods.org/pods/SwiftyContextMenu) 5 | [![Platform](https://img.shields.io/cocoapods/p/SwiftyContextMenu.svg?style=flat)](https://cocoapods.org/pods/SwiftyContextMenu) 6 | 7 | | Light mode | Dark mode | Runtime change | 8 | | --- | --- | --- | 9 | | | | | 10 | 11 | ## Example 12 | 13 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 14 | 15 | ## Requirements 16 | 17 | * iOS 10+ 18 | 19 | ## Installation 20 | 21 | ContextMenu is available through [CocoaPods](https://cocoapods.org). To install 22 | it, simply add the following line to your Podfile: 23 | 24 | ```ruby 25 | pod 'SwiftyContextMenu' 26 | ``` 27 | 28 | ## Usage 29 | 30 | ```swift 31 | let favoriteAction = ContextMenuAction(title: "Looooooooooooong title", 32 | image: UIImage(named: "heart.fill"), 33 | action: { _ in print("favorite") }) 34 | let shareAction = ContextMenuAction(title: "Share", 35 | image: UIImage(named: "square.and.arrow.up.fill"), 36 | action: { _ in print("square") }) 37 | let deleteAction = ContextMenuAction(title: "Delete", 38 | image: UIImage(named: "trash.fill"), 39 | tintColor: UIColor.red, 40 | action: { _ in print("delete") }) 41 | let actions = [favoriteAction, shareAction, deleteAction] 42 | let contextMenu = ContextMenu(title: "Actions", actions: actions) 43 | button.addContextMenu(contextMenu, for: .tap(numberOfTaps: 1), .longPress(duration: 0.3)) 44 | ``` 45 | 46 | ### Dark mode 47 | By default the `ContextMenu` adapts his style automatically with the system (> iOS 13) but you can force an appearance if you want. 48 | 49 | ```swift 50 | let favoriteAction = ContextMenuAction( 51 | title: "Like", 52 | image: UIImage(named: "heart.fill"), 53 | tintColor: UIColor.black, 54 | tintColorDark: UIColor.white, 55 | action: { _ in } 56 | ) 57 | 58 | let contextMenu = ContextMenu( 59 | mode: .light, //.automatic, .light or .dark 60 | title: "Actions", 61 | actions: actions 62 | ) 63 | 64 | ``` 65 | If you set mode to `.automatic` please be sure to provide a `tintColor` to the `ContextMenuAction` that contains light and dark appearance. (When set to `.automatic` only `tintColor` is applied.) 66 | 67 | ## Author 68 | 69 | Mario Iannotta, info@marioiannotta.com. 70 | 71 | If you like this git you can follow me here or on Twitter [@MarioIannotta](http://www.twitter.com/marioiannotta); sometimes I post interesting stuff. 72 | 73 | ## License 74 | 75 | ContextMenu is available under the MIT license. See the LICENSE file for more info. 76 | 77 | ## TODOs: 78 | 79 | * [ ] Document all the public stuff 80 | * [x] Support dark mode 81 | * [ ] Improve the Readme Usage section 82 | * [x] Support dynamic type 83 | -------------------------------------------------------------------------------- /Example/ContextMenu/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /SwiftyContextMenu/ContextMenuActionTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContextMenuActionTableViewCell.swift 3 | // ContextMenu 4 | // 5 | // Created by Mario Iannotta on 14/06/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | class ContextMenuActionTableViewCell: UITableViewCell { 11 | 12 | private let rightImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 25, height: 25)) 13 | private let lightSelectedBackgroundView: UIVisualEffectView 14 | private let darkSelectedBackgroundView: UIView 15 | 16 | private var separatorView: ContextMenuSeparatorView? 17 | private var style: ContextMenuUserInterfaceStyle = .light { 18 | didSet { 19 | updateSelectedBackgroundView() 20 | separatorView?.style = style 21 | } 22 | } 23 | 24 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 25 | let separatorView = SeparatorView(frame: .zero) 26 | separatorView.alpha = 0.7 27 | lightSelectedBackgroundView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: UIBlurEffect(style: .light))) 28 | lightSelectedBackgroundView.contentView.fill(with: separatorView) 29 | 30 | darkSelectedBackgroundView = UIView(frame: .zero) 31 | darkSelectedBackgroundView.backgroundColor = UIColor.white.withAlphaComponent(0.1) 32 | 33 | super.init(style: style, reuseIdentifier: reuseIdentifier) 34 | backgroundColor = .clear 35 | textLabel?.numberOfLines = 0 36 | rightImageView.contentMode = .scaleAspectFit 37 | accessoryView = rightImageView 38 | addSeparatorView() 39 | } 40 | 41 | required init?(coder: NSCoder) { 42 | fatalError("init(coder:) has not been implemented") 43 | } 44 | 45 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { 46 | super.traitCollectionDidChange(previousTraitCollection) 47 | updateSelectedBackgroundView() 48 | } 49 | 50 | func configure(action: ContextMenuAction, with style: ContextMenuUserInterfaceStyle) { 51 | textLabel?.text = action.title 52 | rightImageView.image = action.image?.withRenderingMode(.alwaysTemplate) 53 | 54 | self.style = style 55 | switch style { 56 | case .automatic: 57 | textLabel?.textColor = action.tintColor 58 | rightImageView.tintColor = action.tintColor 59 | case .light: 60 | textLabel?.textColor = action.tintColor 61 | rightImageView.tintColor = action.tintColor 62 | case .dark: 63 | textLabel?.textColor = action.tintColorDark 64 | rightImageView.tintColor = action.tintColorDark 65 | } 66 | } 67 | 68 | private func addSeparatorView() { 69 | let separatorView = ContextMenuSeparatorView(frame: .zero, style: self.style) 70 | self.separatorView = separatorView 71 | separatorView.translatesAutoresizingMaskIntoConstraints = false 72 | contentView.addSubview(separatorView) 73 | DispatchQueue.main.async { 74 | NSLayoutConstraint.activate([ 75 | separatorView.topAnchor.constraint(equalTo: self.contentView.topAnchor), 76 | separatorView.heightAnchor.constraint(equalToConstant: 0.33), 77 | separatorView.widthAnchor.constraint(equalTo: self.widthAnchor), 78 | separatorView.centerXAnchor.constraint(equalTo: self.centerXAnchor) 79 | ]) 80 | 81 | } 82 | } 83 | 84 | private func updateSelectedBackgroundView() { 85 | darkSelectedBackgroundView.bounds = bounds 86 | lightSelectedBackgroundView.bounds = bounds 87 | lightSelectedBackgroundView.contentView.subviews.first?.frame = bounds 88 | 89 | switch style { 90 | case .automatic: 91 | selectedBackgroundView = isDarkMode ? darkSelectedBackgroundView : lightSelectedBackgroundView 92 | case .light: 93 | selectedBackgroundView = lightSelectedBackgroundView 94 | case .dark: 95 | selectedBackgroundView = darkSelectedBackgroundView 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Example/ContextMenu.xcodeproj/xcshareddata/xcschemes/ContextMenu-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 76 | 78 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /SwiftyContextMenu/ContextMenu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContextMenu.swift 3 | // ContextMenu 4 | // 5 | // Created by Mario Iannotta on 06/14/2020. 6 | // Copyright (c) 2020 Mario Iannotta. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public struct ContextMenu { 12 | let style: ContextMenuUserInterfaceStyle 13 | let title: String? 14 | let actions: [ContextMenuAction] 15 | let layout: ContextMenuLayout 16 | let animation: ContextMenuAnimation 17 | weak var delegate: ContextMenuDelegate? 18 | 19 | var sourceViewInfo: ContextMenuSourceViewInfo? 20 | 21 | public init(title: String?, 22 | actions: [ContextMenuAction], 23 | style: ContextMenuUserInterfaceStyle? = nil, 24 | layout: ContextMenuLayout = ContextMenuLayout(), 25 | animation: ContextMenuAnimation = ContextMenuAnimation(), 26 | delegate: ContextMenuDelegate? = nil) { 27 | 28 | if #available(iOS 13, *) { 29 | self.style = style ?? .automatic 30 | } else { 31 | self.style = style ?? .light 32 | } 33 | self.title = title 34 | self.actions = actions 35 | self.layout = layout 36 | self.animation = animation 37 | self.delegate = delegate 38 | } 39 | } 40 | 41 | public struct ContextMenuAction { 42 | let title: String 43 | let image: UIImage? 44 | let tintColor: UIColor 45 | let tintColorDark: UIColor 46 | let action: ((ContextMenuAction) -> Void)? 47 | 48 | public init(title: String, 49 | image: UIImage? = nil, 50 | tintColor: UIColor? = nil, 51 | tintColorDark: UIColor? = nil, 52 | action: ((ContextMenuAction) -> Void)?) { 53 | self.title = title 54 | self.image = image 55 | self.tintColor = tintColor ?? .defaultLabelMenuActionColor 56 | self.tintColorDark = tintColorDark ?? .defaultLabelMenuActionColor 57 | self.action = action 58 | } 59 | } 60 | 61 | public enum ContextMenuEvent { 62 | case longPress(duration: TimeInterval) 63 | case tap(numberOfTaps: Int) 64 | } 65 | 66 | public enum ContextMenuUserInterfaceStyle { 67 | @available(iOS 13, *) case automatic 68 | case light, dark 69 | } 70 | 71 | public struct ContextMenuAnimation { 72 | let sourceViewBounceRange: ClosedRange 73 | let optionsViewBounceRange: ClosedRange 74 | 75 | public init(sourceViewBounceRange: ClosedRange = 0.9...1.15, 76 | optionsViewBounceRange: ClosedRange = 0.05...1) { 77 | self.sourceViewBounceRange = sourceViewBounceRange 78 | self.optionsViewBounceRange = optionsViewBounceRange 79 | } 80 | } 81 | 82 | public struct ContextMenuLayout { 83 | let width: CGFloat 84 | let spacing: CGFloat 85 | let padding: CGFloat 86 | let sourceViewCornerRadius: CGFloat 87 | 88 | public init(width: CGFloat = 250, 89 | spacing: CGFloat = 20, 90 | padding: CGFloat = 30, 91 | sourceViewCornerRadius: CGFloat = 0) { 92 | self.width = width 93 | self.spacing = spacing 94 | self.padding = padding 95 | self.sourceViewCornerRadius = sourceViewCornerRadius 96 | } 97 | } 98 | 99 | struct ContextMenuSourceViewInfo { 100 | let alpha: CGFloat 101 | let snapshot: UIImage? 102 | let originalFrame: CGRect 103 | let targetFrame: CGRect 104 | } 105 | 106 | public protocol ContextMenuSourceView: UIView { } 107 | 108 | extension UIView: ContextMenuSourceView { 109 | 110 | public func addContextMenu(_ contextMenu: ContextMenu, for events: ContextMenuEvent...) { 111 | self.contextMenu = contextMenu 112 | self.contextMenuGestureRecognizers = [] 113 | events.forEach(addGestureRecognizer) 114 | } 115 | 116 | public func removeContextMenu() { 117 | contextMenu = nil 118 | contextMenuGestureRecognizers?.forEach(removeGestureRecognizer) 119 | contextMenuGestureRecognizers = [] 120 | } 121 | 122 | public func showContextMenu(completion: (() -> Void)?) { 123 | snapshotSourceView() 124 | guard 125 | let contextMenu = contextMenu 126 | else { return } 127 | alpha = 0 128 | contextMenuWindow = ContextMenuWindow( 129 | contextMenu: contextMenu, 130 | onDismiss: { [weak self] in 131 | self?.contextMenuWindow = nil 132 | self?.alpha = 1 133 | }) 134 | contextMenuWindow?.makeKeyAndVisible() 135 | } 136 | 137 | public func dismissContextMenu(completion: (() -> Void)?) { 138 | contextMenuWindow?.resignKey() 139 | } 140 | 141 | func snapshotSourceView() { 142 | let padding = contextMenu?.layout.padding ?? 0 143 | let originalFrame = absoluteFrame 144 | let originalFrameWithPadding = CGRect(x: originalFrame.origin.x - padding, 145 | y: originalFrame.origin.y - padding, 146 | width: originalFrame.width + padding * 2, 147 | height: originalFrame.height + padding * 2) 148 | let targetFrame: CGRect 149 | if UIScreen.main.bounds.contains(originalFrameWithPadding) { 150 | targetFrame = originalFrame 151 | } else { 152 | let x: CGFloat 153 | let y: CGFloat 154 | if originalFrame.minX < padding { 155 | x = padding 156 | } else if originalFrame.maxX > UIScreen.main.bounds.width - padding { 157 | x = UIScreen.main.bounds.width - originalFrame.width - padding 158 | } else { 159 | x = originalFrame.origin.x 160 | } 161 | if originalFrame.minY < padding { 162 | y = padding 163 | } else if originalFrame.maxY > UIScreen.main.bounds.height - padding { 164 | y = UIScreen.main.bounds.height - originalFrame.height - padding 165 | } else { 166 | y = originalFrame.origin.y 167 | } 168 | targetFrame = CGRect(origin: CGPoint(x: x, y: y), size: originalFrame.size) 169 | } 170 | contextMenu?.sourceViewInfo = ContextMenuSourceViewInfo(alpha: alpha, 171 | snapshot: snapshot(), 172 | originalFrame: originalFrame, 173 | targetFrame: targetFrame) 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /SwiftyContextMenu/Extensions/UIView+ContextMenu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+ContextMenu.swift 3 | // ContextMenu 4 | // 5 | // Created by Mario Iannotta on 14/06/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | private struct ContextMenuAssociatedObjectKey { 11 | static var contextMenu = "ContextMenu.ContextMenu" 12 | static var contextMenuWindow = "ContextMenu.ContextMenuWindow" 13 | static var contextMenuGestureRecognizers = "ContextMenu.GestureRecognizers" 14 | } 15 | 16 | extension UIView { 17 | 18 | var contextMenu: ContextMenu? { 19 | get { objc_getAssociatedObject(self, &ContextMenuAssociatedObjectKey.contextMenu) as? ContextMenu } 20 | set { objc_setAssociatedObject(self, &ContextMenuAssociatedObjectKey.contextMenu, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } 21 | } 22 | 23 | var contextMenuWindow: ContextMenuWindow? { 24 | get { objc_getAssociatedObject(self, &ContextMenuAssociatedObjectKey.contextMenuWindow) as? ContextMenuWindow } 25 | set { objc_setAssociatedObject(self, &ContextMenuAssociatedObjectKey.contextMenuWindow, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } 26 | } 27 | 28 | var contextMenuGestureRecognizers: [UIGestureRecognizer]? { 29 | get { objc_getAssociatedObject(self, &ContextMenuAssociatedObjectKey.contextMenuGestureRecognizers) as? [UIGestureRecognizer] } 30 | set { objc_setAssociatedObject(self, &ContextMenuAssociatedObjectKey.contextMenuGestureRecognizers, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } 31 | } 32 | 33 | func addGestureRecognizer(event: ContextMenuEvent) { 34 | switch event { 35 | case .longPress(let duration): 36 | let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressGestureRecognizer(_:))) 37 | longPressGestureRecognizer.minimumPressDuration = duration 38 | addGestureRecognizer(longPressGestureRecognizer) 39 | contextMenuGestureRecognizers?.append(longPressGestureRecognizer) 40 | case .tap(let numberOfTaps): 41 | let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGestureRecognizer(_:))) 42 | tapGestureRecognizer.numberOfTapsRequired = numberOfTaps 43 | addGestureRecognizer(tapGestureRecognizer) 44 | contextMenuGestureRecognizers?.append(tapGestureRecognizer) 45 | } 46 | } 47 | 48 | @objc private func handleLongPressGestureRecognizer(_ longPressGestureRecognizer: UILongPressGestureRecognizer) { 49 | guard 50 | longPressGestureRecognizer.state == .began 51 | else { return } 52 | showContextMenu(completion: nil) 53 | } 54 | 55 | @objc private func handleTapGestureRecognizer(_ tapGestureRecognizer: UITapGestureRecognizer) { 56 | guard 57 | tapGestureRecognizer.state == .ended 58 | else { return } 59 | showContextMenu(completion: nil) 60 | } 61 | } 62 | 63 | extension ContextMenu { 64 | 65 | private var sourceViewTranslationSize: CGSize { 66 | guard 67 | let targetFrame = sourceViewInfo?.targetFrame, 68 | let originalFrame = sourceViewInfo?.originalFrame, 69 | (originalFrame.origin.x < 0 70 | || originalFrame.origin.y < 0 71 | || originalFrame.maxX > UIScreen.main.bounds.width 72 | || originalFrame.maxY > UIScreen.main.bounds.height) 73 | else { 74 | return .zero 75 | } 76 | let translationX = targetFrame.midX - originalFrame.midX 77 | let translationY = targetFrame.midY - originalFrame.midY 78 | return CGSize(width: translationX, height: translationY) 79 | } 80 | 81 | private var sourceViewTranslationTransform: CGAffineTransform { 82 | return CGAffineTransform(translationX: sourceViewTranslationSize.width, 83 | y: sourceViewTranslationSize.height) 84 | } 85 | 86 | var sourceViewFirstStepTransform: CGAffineTransform { 87 | CGAffineTransform(scaleX: animation.sourceViewBounceRange.lowerBound, 88 | y: animation.sourceViewBounceRange.lowerBound) 89 | .concatenating(sourceViewTranslationTransform) 90 | } 91 | 92 | var sourceViewSecondTransform: CGAffineTransform { 93 | let middlePoint = (animation.sourceViewBounceRange.upperBound - animation.sourceViewBounceRange.lowerBound) / 2 94 | let scale = animation.sourceViewBounceRange.lowerBound + middlePoint 95 | return CGAffineTransform(scaleX: scale, y: scale) 96 | .concatenating(sourceViewTranslationTransform) 97 | } 98 | 99 | func sourceViewThirdTransform(isContextMenuUp: Bool, isContextMenuRight: Bool) -> CGAffineTransform { 100 | guard 101 | let targetFrame = sourceViewInfo?.targetFrame 102 | else { 103 | return sourceViewTranslationTransform 104 | } 105 | let scaleTransform = CGAffineTransform(scaleX: animation.sourceViewBounceRange.upperBound, 106 | y: animation.sourceViewBounceRange.upperBound) 107 | let finalSnapshotSize = targetFrame.applying(scaleTransform) 108 | var translationX = (targetFrame.size.width - finalSnapshotSize.size.width) / 2 109 | var translationY = (targetFrame.size.height - finalSnapshotSize.size.height) / 2 110 | if !isContextMenuUp { translationY *= -1 } 111 | if !isContextMenuRight { translationX *= -1 } 112 | return scaleTransform.translatedBy(x: sourceViewTranslationSize.width + translationX, 113 | y: sourceViewTranslationSize.height + translationY) 114 | 115 | } 116 | 117 | func optionsViewFirstTransform(isContextMenuUp: Bool) -> CGAffineTransform { 118 | sourceViewTranslationTransform 119 | .concatenating( 120 | CGAffineTransform( 121 | scaleX: animation.optionsViewBounceRange.lowerBound, 122 | y: animation.optionsViewBounceRange.lowerBound 123 | ) 124 | ) 125 | .concatenating( 126 | CGAffineTransform( 127 | translationX: 0, 128 | y: (!isContextMenuUp ? -1 : 1) * 120 129 | ) 130 | ) 131 | } 132 | 133 | var optionsViewSecondTransform: CGAffineTransform { 134 | CGAffineTransform(scaleX: animation.optionsViewBounceRange.upperBound, 135 | y: animation.optionsViewBounceRange.upperBound) 136 | .concatenating(sourceViewTranslationTransform) 137 | } 138 | 139 | var optionsViewThirdTransform: CGAffineTransform { 140 | sourceViewTranslationTransform 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /SwiftyContextMenu/ContextMenuViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContextMenuViewController.swift 3 | // ContextMenu 4 | // 5 | // Created by Mario Iannotta on 14/06/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol ContextMenuViewControllerDelegate: class { 11 | 12 | func contextMenuViewControllerDidDismiss(_ contextMenuViewController: ContextMenuViewController) 13 | } 14 | 15 | class ContextMenuViewController: UIViewController { 16 | 17 | private let contextMenu: ContextMenu 18 | private weak var delegate: ContextMenuViewControllerDelegate? 19 | 20 | private let blurView: ContextMenuBackgroundBlurView 21 | private let overlayView = UIView(frame: .zero) 22 | private let snapshotImageView = UIImageView(frame: .zero) 23 | private let contextMenuTableView = ContextMenuTableView() 24 | private let contextMenuView = UIView() 25 | 26 | private let cellIdentifier = "ContextMenuCell" 27 | 28 | private var isContextMenuUp: Bool { (contextMenu.sourceViewInfo?.targetFrame.midY ?? 0) > UIScreen.main.bounds.height / 2 } 29 | private var isContextMenuRight: Bool { (contextMenu.sourceViewInfo?.targetFrame.midX ?? 0) > UIScreen.main.bounds.width / 2 } 30 | 31 | init(contextMenu: ContextMenu, delegate: ContextMenuViewControllerDelegate?) { 32 | self.delegate = delegate 33 | self.contextMenu = contextMenu 34 | self.blurView = ContextMenuBackgroundBlurView(contextMenu.style) 35 | super.init(nibName: nil, bundle: nil) 36 | } 37 | 38 | required init?(coder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | 42 | // MARK: - Lifecycle 43 | 44 | override func viewDidLoad() { 45 | super.viewDidLoad() 46 | setupUI() 47 | addBlurView() 48 | addBlackOverlay() 49 | addSnapshotView() 50 | addContextMenuTableView() 51 | } 52 | 53 | override func viewDidAppear(_ animated: Bool) { 54 | super.viewDidAppear(animated) 55 | fadeIn() 56 | } 57 | 58 | // MARK: - Setup 59 | 60 | private func setupUI() { 61 | view.backgroundColor = .clear 62 | } 63 | 64 | private func addBlurView() { 65 | blurView.alpha = 0 66 | view.fill(with: blurView) 67 | } 68 | 69 | private func addBlackOverlay() { 70 | overlayView.alpha = 0 71 | overlayView.backgroundColor = UIColor.black.withAlphaComponent(0.25) 72 | view.fill(with: overlayView) 73 | let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleDismissGestureRecognizer)) 74 | overlayView.addGestureRecognizer(tapGesture) 75 | } 76 | 77 | private func addSnapshotView() { 78 | snapshotImageView.image = contextMenu.sourceViewInfo?.snapshot 79 | snapshotImageView.frame = contextMenu.sourceViewInfo?.originalFrame ?? .zero 80 | snapshotImageView.clipsToBounds = true 81 | snapshotImageView.layer.cornerRadius = contextMenu.layout.sourceViewCornerRadius 82 | view.addSubview(snapshotImageView) 83 | } 84 | 85 | private func addContextMenuTableView() { 86 | contextMenuTableView.delegate = self 87 | contextMenuTableView.dataSource = self 88 | contextMenuTableView.rowHeight = UITableView.automaticDimension 89 | contextMenuTableView.estimatedRowHeight = 44 90 | contextMenuTableView.register(ContextMenuActionTableViewCell.self, forCellReuseIdentifier: cellIdentifier) 91 | 92 | let arrangedSubviews = [makeTitleView(), contextMenuTableView].compactMap { $0 } 93 | let stackView = UIStackView(arrangedSubviews: arrangedSubviews, axis: .vertical) 94 | 95 | let blurView = ContextMenuContentBlurView(contextMenu.style) 96 | blurView.layer.cornerRadius = 14 97 | blurView.clipsToBounds = true 98 | blurView.contentView.fill(with: stackView) 99 | 100 | contextMenuView.alpha = 0 101 | contextMenuView.layer.cornerRadius = 14 102 | contextMenuView.layer.shadowColor = UIColor.black.cgColor 103 | contextMenuView.layer.shadowOffset = CGSize(width: 2, height: 5) 104 | contextMenuView.layer.shadowRadius = 6 105 | contextMenuView.layer.shadowOpacity = 0.08 106 | contextMenuView.translatesAutoresizingMaskIntoConstraints = false 107 | contextMenuView.fill(with: blurView) 108 | view.addSubview(contextMenuView) 109 | 110 | let edgeConstraint: NSLayoutConstraint 111 | let verticalConstraint: NSLayoutConstraint 112 | let horizontalConstraint: NSLayoutConstraint 113 | if isContextMenuUp { 114 | edgeConstraint = contextMenuView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, 115 | constant: contextMenu.layout.padding) 116 | verticalConstraint = snapshotImageView.topAnchor.constraint(equalTo: contextMenuView.bottomAnchor, 117 | constant: contextMenu.layout.spacing) 118 | } else { 119 | edgeConstraint = bottomAnchor.constraint(greaterThanOrEqualTo: contextMenuView.bottomAnchor, 120 | constant: contextMenu.layout.padding) 121 | verticalConstraint = contextMenuView.topAnchor.constraint(equalTo: snapshotImageView.bottomAnchor, 122 | constant: contextMenu.layout.spacing) 123 | } 124 | if isContextMenuRight { 125 | horizontalConstraint = snapshotImageView.trailingAnchor.constraint(equalTo: contextMenuView.trailingAnchor) 126 | } else { 127 | horizontalConstraint = contextMenuView.leadingAnchor.constraint(equalTo: snapshotImageView.leadingAnchor) 128 | } 129 | NSLayoutConstraint.activate([ 130 | contextMenuView.widthAnchor.constraint(equalToConstant: 250), 131 | horizontalConstraint, 132 | verticalConstraint, 133 | edgeConstraint 134 | ]) 135 | } 136 | 137 | private func makeTitleView() -> UIView? { 138 | guard 139 | let title = contextMenu.title 140 | else { 141 | return nil 142 | } 143 | let titleLabelContainterView = UIView(frame: .zero) 144 | titleLabelContainterView.backgroundColor = .clear 145 | let titleLabel = ContextMenuTitleLabel(frame: .zero, style: contextMenu.style) 146 | titleLabel.text = title 147 | titleLabel.sizeToFit() 148 | titleLabelContainterView.fill(with: titleLabel, insets: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)) 149 | 150 | return titleLabelContainterView 151 | } 152 | 153 | private func fadeIn() { 154 | contextMenu.delegate?.contextMenuWillAppear(contextMenu) 155 | contextMenuView.transform = contextMenu.optionsViewFirstTransform(isContextMenuUp: isContextMenuUp) 156 | showSourceView { 157 | self.contextMenu.delegate?.contextMenuDidAppear(self.contextMenu) 158 | self.showContextMenu() 159 | } 160 | } 161 | 162 | private func showSourceView(completion: @escaping () -> Void) { 163 | UIView.animate( 164 | withDuration: 0.2, 165 | animations: { 166 | self.overlayView.alpha = 1 167 | self.blurView.alpha = 1 168 | self.snapshotImageView.transform = self.contextMenu.sourceViewFirstStepTransform 169 | }, 170 | completion: { _ in 171 | UIView.animate( 172 | withDuration: 0.2, 173 | animations: { 174 | self.snapshotImageView.transform = self.contextMenu.sourceViewSecondTransform 175 | }, 176 | completion: { _ in completion() }) 177 | }) 178 | } 179 | 180 | private func showContextMenu() { 181 | UIView.animate( 182 | withDuration: 0.5, 183 | delay: 0.0, 184 | usingSpringWithDamping: 0.7, 185 | initialSpringVelocity: 5, 186 | options: .curveLinear, 187 | animations: { 188 | self.contextMenuView.alpha = 1 189 | self.contextMenuView.transform = self.contextMenu.optionsViewSecondTransform 190 | 191 | let transform = self.contextMenu.sourceViewThirdTransform( 192 | isContextMenuUp: self.isContextMenuUp, 193 | isContextMenuRight: self.isContextMenuRight 194 | ) 195 | 196 | self.snapshotImageView.transform = transform 197 | }, 198 | completion: { _ in 199 | UIView.animate( 200 | withDuration: 0.2, 201 | animations: { 202 | self.contextMenuView.transform = self.contextMenu.optionsViewThirdTransform 203 | }) 204 | }) 205 | } 206 | 207 | private func fadeOutAndClose() { 208 | UIView.animate( 209 | withDuration: 0.3, 210 | animations: { 211 | self.blurView.alpha = 0 212 | self.contextMenuView.alpha = 0 213 | self.snapshotImageView.transform = .identity 214 | self.contextMenuView.transform = self.contextMenu.optionsViewFirstTransform(isContextMenuUp: self.isContextMenuUp) 215 | }, 216 | completion: { _ in 217 | self.delegate?.contextMenuViewControllerDidDismiss(self) 218 | }) 219 | } 220 | 221 | @objc private func handleDismissGestureRecognizer() { 222 | fadeOutAndClose() 223 | } 224 | } 225 | 226 | extension ContextMenuViewController: UITableViewDataSource, UITableViewDelegate { 227 | 228 | // MARK: - UITableViewDataSource 229 | 230 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 231 | contextMenu.actions.count 232 | } 233 | 234 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 235 | guard 236 | let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? ContextMenuActionTableViewCell 237 | else { 238 | return UITableViewCell() 239 | } 240 | cell.configure(action: contextMenu.actions[indexPath.row], with: contextMenu.style) 241 | return cell 242 | } 243 | 244 | // MARK: - UITableViewDelegate 245 | 246 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 247 | let action = contextMenu.actions[indexPath.row] 248 | action.action?(action) 249 | fadeOutAndClose() 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /Example/ContextMenu/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 37 | 45 | 46 | 47 | 48 | 49 | 50 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 75 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /Example/ContextMenu.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5B730B272CD4BACB2159A18E /* Pods_ContextMenu_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D0F68EE83B4EDDE4831E70E /* Pods_ContextMenu_Example.framework */; }; 11 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; 12 | 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; }; 13 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; }; 14 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; 15 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 193D9B05DFD0C9E5696C4A7D /* Pods-ContextMenu_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ContextMenu_Example.release.xcconfig"; path = "Target Support Files/Pods-ContextMenu_Example/Pods-ContextMenu_Example.release.xcconfig"; sourceTree = ""; }; 20 | 4D0F68EE83B4EDDE4831E70E /* Pods_ContextMenu_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ContextMenu_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 607FACD01AFB9204008FA782 /* ContextMenu_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ContextMenu_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 23 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 24 | 607FACD71AFB9204008FA782 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 25 | 607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 26 | 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 27 | 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 28 | 644516059E4119D1BD7912E8 /* SwiftyContextMenu.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = SwiftyContextMenu.podspec; path = ../SwiftyContextMenu.podspec; sourceTree = ""; }; 29 | 6517BF40E3027BC8EA590303 /* Pods-ContextMenu_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ContextMenu_Example.debug.xcconfig"; path = "Target Support Files/Pods-ContextMenu_Example/Pods-ContextMenu_Example.debug.xcconfig"; sourceTree = ""; }; 30 | AF42EF197D7CCC1D24C0ABDC /* Pods-ContextMenu_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ContextMenu_Tests.debug.xcconfig"; path = "Target Support Files/Pods-ContextMenu_Tests/Pods-ContextMenu_Tests.debug.xcconfig"; sourceTree = ""; }; 31 | C5D6163252E047CEE3A87AB2 /* Pods-ContextMenu_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ContextMenu_Tests.release.xcconfig"; path = "Target Support Files/Pods-ContextMenu_Tests/Pods-ContextMenu_Tests.release.xcconfig"; sourceTree = ""; }; 32 | E362271670D02473229F7754 /* Pods_ContextMenu_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ContextMenu_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | E943DD55312BFC6CD63F7376 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 34 | EE47ADE878A3327460D73046 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | 607FACCD1AFB9204008FA782 /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | 5B730B272CD4BACB2159A18E /* Pods_ContextMenu_Example.framework in Frameworks */, 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | /* End PBXFrameworksBuildPhase section */ 47 | 48 | /* Begin PBXGroup section */ 49 | 607FACC71AFB9204008FA782 = { 50 | isa = PBXGroup; 51 | children = ( 52 | 607FACF51AFB993E008FA782 /* Podspec Metadata */, 53 | 607FACD21AFB9204008FA782 /* Example for ContextMenu */, 54 | 607FACD11AFB9204008FA782 /* Products */, 55 | C298931A83D65826DA3CFDCF /* Pods */, 56 | 66559C47A36EF5A3565C08CA /* Frameworks */, 57 | ); 58 | sourceTree = ""; 59 | }; 60 | 607FACD11AFB9204008FA782 /* Products */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | 607FACD01AFB9204008FA782 /* ContextMenu_Example.app */, 64 | ); 65 | name = Products; 66 | sourceTree = ""; 67 | }; 68 | 607FACD21AFB9204008FA782 /* Example for ContextMenu */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */, 72 | 607FACD71AFB9204008FA782 /* ViewController.swift */, 73 | 607FACD91AFB9204008FA782 /* Main.storyboard */, 74 | 607FACDC1AFB9204008FA782 /* Images.xcassets */, 75 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */, 76 | 607FACD31AFB9204008FA782 /* Supporting Files */, 77 | ); 78 | name = "Example for ContextMenu"; 79 | path = ContextMenu; 80 | sourceTree = ""; 81 | }; 82 | 607FACD31AFB9204008FA782 /* Supporting Files */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 607FACD41AFB9204008FA782 /* Info.plist */, 86 | ); 87 | name = "Supporting Files"; 88 | sourceTree = ""; 89 | }; 90 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 644516059E4119D1BD7912E8 /* SwiftyContextMenu.podspec */, 94 | E943DD55312BFC6CD63F7376 /* README.md */, 95 | EE47ADE878A3327460D73046 /* LICENSE */, 96 | ); 97 | name = "Podspec Metadata"; 98 | sourceTree = ""; 99 | }; 100 | 66559C47A36EF5A3565C08CA /* Frameworks */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 4D0F68EE83B4EDDE4831E70E /* Pods_ContextMenu_Example.framework */, 104 | E362271670D02473229F7754 /* Pods_ContextMenu_Tests.framework */, 105 | ); 106 | name = Frameworks; 107 | sourceTree = ""; 108 | }; 109 | C298931A83D65826DA3CFDCF /* Pods */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 6517BF40E3027BC8EA590303 /* Pods-ContextMenu_Example.debug.xcconfig */, 113 | 193D9B05DFD0C9E5696C4A7D /* Pods-ContextMenu_Example.release.xcconfig */, 114 | AF42EF197D7CCC1D24C0ABDC /* Pods-ContextMenu_Tests.debug.xcconfig */, 115 | C5D6163252E047CEE3A87AB2 /* Pods-ContextMenu_Tests.release.xcconfig */, 116 | ); 117 | path = Pods; 118 | sourceTree = ""; 119 | }; 120 | /* End PBXGroup section */ 121 | 122 | /* Begin PBXNativeTarget section */ 123 | 607FACCF1AFB9204008FA782 /* ContextMenu_Example */ = { 124 | isa = PBXNativeTarget; 125 | buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "ContextMenu_Example" */; 126 | buildPhases = ( 127 | 9BCDDCBAB18F472166D22F17 /* [CP] Check Pods Manifest.lock */, 128 | 607FACCC1AFB9204008FA782 /* Sources */, 129 | 607FACCD1AFB9204008FA782 /* Frameworks */, 130 | 607FACCE1AFB9204008FA782 /* Resources */, 131 | B5CA4D390561F30C3A2882A2 /* [CP] Embed Pods Frameworks */, 132 | ); 133 | buildRules = ( 134 | ); 135 | dependencies = ( 136 | ); 137 | name = ContextMenu_Example; 138 | productName = ContextMenu; 139 | productReference = 607FACD01AFB9204008FA782 /* ContextMenu_Example.app */; 140 | productType = "com.apple.product-type.application"; 141 | }; 142 | /* End PBXNativeTarget section */ 143 | 144 | /* Begin PBXProject section */ 145 | 607FACC81AFB9204008FA782 /* Project object */ = { 146 | isa = PBXProject; 147 | attributes = { 148 | LastSwiftUpdateCheck = 0830; 149 | LastUpgradeCheck = 1220; 150 | ORGANIZATIONNAME = CocoaPods; 151 | TargetAttributes = { 152 | 607FACCF1AFB9204008FA782 = { 153 | CreatedOnToolsVersion = 6.3.1; 154 | LastSwiftMigration = 1150; 155 | }; 156 | }; 157 | }; 158 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "ContextMenu" */; 159 | compatibilityVersion = "Xcode 3.2"; 160 | developmentRegion = en; 161 | hasScannedForEncodings = 0; 162 | knownRegions = ( 163 | en, 164 | Base, 165 | ); 166 | mainGroup = 607FACC71AFB9204008FA782; 167 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */; 168 | projectDirPath = ""; 169 | projectRoot = ""; 170 | targets = ( 171 | 607FACCF1AFB9204008FA782 /* ContextMenu_Example */, 172 | ); 173 | }; 174 | /* End PBXProject section */ 175 | 176 | /* Begin PBXResourcesBuildPhase section */ 177 | 607FACCE1AFB9204008FA782 /* Resources */ = { 178 | isa = PBXResourcesBuildPhase; 179 | buildActionMask = 2147483647; 180 | files = ( 181 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */, 182 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */, 183 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, 184 | ); 185 | runOnlyForDeploymentPostprocessing = 0; 186 | }; 187 | /* End PBXResourcesBuildPhase section */ 188 | 189 | /* Begin PBXShellScriptBuildPhase section */ 190 | 9BCDDCBAB18F472166D22F17 /* [CP] Check Pods Manifest.lock */ = { 191 | isa = PBXShellScriptBuildPhase; 192 | buildActionMask = 2147483647; 193 | files = ( 194 | ); 195 | inputFileListPaths = ( 196 | ); 197 | inputPaths = ( 198 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 199 | "${PODS_ROOT}/Manifest.lock", 200 | ); 201 | name = "[CP] Check Pods Manifest.lock"; 202 | outputFileListPaths = ( 203 | ); 204 | outputPaths = ( 205 | "$(DERIVED_FILE_DIR)/Pods-ContextMenu_Example-checkManifestLockResult.txt", 206 | ); 207 | runOnlyForDeploymentPostprocessing = 0; 208 | shellPath = /bin/sh; 209 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 210 | showEnvVarsInLog = 0; 211 | }; 212 | B5CA4D390561F30C3A2882A2 /* [CP] Embed Pods Frameworks */ = { 213 | isa = PBXShellScriptBuildPhase; 214 | buildActionMask = 2147483647; 215 | files = ( 216 | ); 217 | inputPaths = ( 218 | "${PODS_ROOT}/Target Support Files/Pods-ContextMenu_Example/Pods-ContextMenu_Example-frameworks.sh", 219 | "${BUILT_PRODUCTS_DIR}/SwiftyContextMenu/SwiftyContextMenu.framework", 220 | ); 221 | name = "[CP] Embed Pods Frameworks"; 222 | outputPaths = ( 223 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyContextMenu.framework", 224 | ); 225 | runOnlyForDeploymentPostprocessing = 0; 226 | shellPath = /bin/sh; 227 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ContextMenu_Example/Pods-ContextMenu_Example-frameworks.sh\"\n"; 228 | showEnvVarsInLog = 0; 229 | }; 230 | /* End PBXShellScriptBuildPhase section */ 231 | 232 | /* Begin PBXSourcesBuildPhase section */ 233 | 607FACCC1AFB9204008FA782 /* Sources */ = { 234 | isa = PBXSourcesBuildPhase; 235 | buildActionMask = 2147483647; 236 | files = ( 237 | 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */, 238 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, 239 | ); 240 | runOnlyForDeploymentPostprocessing = 0; 241 | }; 242 | /* End PBXSourcesBuildPhase section */ 243 | 244 | /* Begin PBXVariantGroup section */ 245 | 607FACD91AFB9204008FA782 /* Main.storyboard */ = { 246 | isa = PBXVariantGroup; 247 | children = ( 248 | 607FACDA1AFB9204008FA782 /* Base */, 249 | ); 250 | name = Main.storyboard; 251 | sourceTree = ""; 252 | }; 253 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = { 254 | isa = PBXVariantGroup; 255 | children = ( 256 | 607FACDF1AFB9204008FA782 /* Base */, 257 | ); 258 | name = LaunchScreen.xib; 259 | sourceTree = ""; 260 | }; 261 | /* End PBXVariantGroup section */ 262 | 263 | /* Begin XCBuildConfiguration section */ 264 | 607FACED1AFB9204008FA782 /* Debug */ = { 265 | isa = XCBuildConfiguration; 266 | buildSettings = { 267 | ALWAYS_SEARCH_USER_PATHS = NO; 268 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 269 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 270 | CLANG_CXX_LIBRARY = "libc++"; 271 | CLANG_ENABLE_MODULES = YES; 272 | CLANG_ENABLE_OBJC_ARC = YES; 273 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 274 | CLANG_WARN_BOOL_CONVERSION = YES; 275 | CLANG_WARN_COMMA = YES; 276 | CLANG_WARN_CONSTANT_CONVERSION = YES; 277 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 278 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 279 | CLANG_WARN_EMPTY_BODY = YES; 280 | CLANG_WARN_ENUM_CONVERSION = YES; 281 | CLANG_WARN_INFINITE_RECURSION = YES; 282 | CLANG_WARN_INT_CONVERSION = YES; 283 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 284 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 285 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 286 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 287 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 288 | CLANG_WARN_STRICT_PROTOTYPES = YES; 289 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 290 | CLANG_WARN_UNREACHABLE_CODE = YES; 291 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 292 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 293 | COPY_PHASE_STRIP = NO; 294 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 295 | ENABLE_STRICT_OBJC_MSGSEND = YES; 296 | ENABLE_TESTABILITY = YES; 297 | GCC_C_LANGUAGE_STANDARD = gnu99; 298 | GCC_DYNAMIC_NO_PIC = NO; 299 | GCC_NO_COMMON_BLOCKS = YES; 300 | GCC_OPTIMIZATION_LEVEL = 0; 301 | GCC_PREPROCESSOR_DEFINITIONS = ( 302 | "DEBUG=1", 303 | "$(inherited)", 304 | ); 305 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 306 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 307 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 308 | GCC_WARN_UNDECLARED_SELECTOR = YES; 309 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 310 | GCC_WARN_UNUSED_FUNCTION = YES; 311 | GCC_WARN_UNUSED_VARIABLE = YES; 312 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 313 | MTL_ENABLE_DEBUG_INFO = YES; 314 | ONLY_ACTIVE_ARCH = YES; 315 | SDKROOT = iphoneos; 316 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 317 | }; 318 | name = Debug; 319 | }; 320 | 607FACEE1AFB9204008FA782 /* Release */ = { 321 | isa = XCBuildConfiguration; 322 | buildSettings = { 323 | ALWAYS_SEARCH_USER_PATHS = NO; 324 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 325 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 326 | CLANG_CXX_LIBRARY = "libc++"; 327 | CLANG_ENABLE_MODULES = YES; 328 | CLANG_ENABLE_OBJC_ARC = YES; 329 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 330 | CLANG_WARN_BOOL_CONVERSION = YES; 331 | CLANG_WARN_COMMA = YES; 332 | CLANG_WARN_CONSTANT_CONVERSION = YES; 333 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 334 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 335 | CLANG_WARN_EMPTY_BODY = YES; 336 | CLANG_WARN_ENUM_CONVERSION = YES; 337 | CLANG_WARN_INFINITE_RECURSION = YES; 338 | CLANG_WARN_INT_CONVERSION = YES; 339 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 340 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 341 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 342 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 343 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 344 | CLANG_WARN_STRICT_PROTOTYPES = YES; 345 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 346 | CLANG_WARN_UNREACHABLE_CODE = YES; 347 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 348 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 349 | COPY_PHASE_STRIP = NO; 350 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 351 | ENABLE_NS_ASSERTIONS = NO; 352 | ENABLE_STRICT_OBJC_MSGSEND = YES; 353 | GCC_C_LANGUAGE_STANDARD = gnu99; 354 | GCC_NO_COMMON_BLOCKS = YES; 355 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 356 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 357 | GCC_WARN_UNDECLARED_SELECTOR = YES; 358 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 359 | GCC_WARN_UNUSED_FUNCTION = YES; 360 | GCC_WARN_UNUSED_VARIABLE = YES; 361 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 362 | MTL_ENABLE_DEBUG_INFO = NO; 363 | SDKROOT = iphoneos; 364 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 365 | VALIDATE_PRODUCT = YES; 366 | }; 367 | name = Release; 368 | }; 369 | 607FACF01AFB9204008FA782 /* Debug */ = { 370 | isa = XCBuildConfiguration; 371 | baseConfigurationReference = 6517BF40E3027BC8EA590303 /* Pods-ContextMenu_Example.debug.xcconfig */; 372 | buildSettings = { 373 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 374 | INFOPLIST_FILE = ContextMenu/Info.plist; 375 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 376 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 377 | MODULE_NAME = ExampleApp; 378 | PRODUCT_BUNDLE_IDENTIFIER = com.marioiannotta.contextMenuExample; 379 | PRODUCT_NAME = "$(TARGET_NAME)"; 380 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 381 | SWIFT_VERSION = 5.0; 382 | }; 383 | name = Debug; 384 | }; 385 | 607FACF11AFB9204008FA782 /* Release */ = { 386 | isa = XCBuildConfiguration; 387 | baseConfigurationReference = 193D9B05DFD0C9E5696C4A7D /* Pods-ContextMenu_Example.release.xcconfig */; 388 | buildSettings = { 389 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 390 | INFOPLIST_FILE = ContextMenu/Info.plist; 391 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 392 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 393 | MODULE_NAME = ExampleApp; 394 | PRODUCT_BUNDLE_IDENTIFIER = com.marioiannotta.contextMenuExample; 395 | PRODUCT_NAME = "$(TARGET_NAME)"; 396 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 397 | SWIFT_VERSION = 5.0; 398 | }; 399 | name = Release; 400 | }; 401 | /* End XCBuildConfiguration section */ 402 | 403 | /* Begin XCConfigurationList section */ 404 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "ContextMenu" */ = { 405 | isa = XCConfigurationList; 406 | buildConfigurations = ( 407 | 607FACED1AFB9204008FA782 /* Debug */, 408 | 607FACEE1AFB9204008FA782 /* Release */, 409 | ); 410 | defaultConfigurationIsVisible = 0; 411 | defaultConfigurationName = Release; 412 | }; 413 | 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "ContextMenu_Example" */ = { 414 | isa = XCConfigurationList; 415 | buildConfigurations = ( 416 | 607FACF01AFB9204008FA782 /* Debug */, 417 | 607FACF11AFB9204008FA782 /* Release */, 418 | ); 419 | defaultConfigurationIsVisible = 0; 420 | defaultConfigurationName = Release; 421 | }; 422 | /* End XCConfigurationList section */ 423 | }; 424 | rootObject = 607FACC81AFB9204008FA782 /* Project object */; 425 | } 426 | --------------------------------------------------------------------------------