├── 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 |
2 |
3 | [](https://cocoapods.org/pods/SwiftyContextMenu)
4 | [](https://cocoapods.org/pods/SwiftyContextMenu)
5 | [](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 |
--------------------------------------------------------------------------------