2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Example/Tests/SwiftyMenuDelegateSpy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuDelegateSpy.swift
3 | // SwiftyMenu_Example
4 | //
5 | // Created by Karim Ebrahem on 3/25/20.
6 | // Copyright © 2020 CocoaPods. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | @testable import SwiftyMenu
11 |
12 | class SwiftyMenuDelegateSpy: SwiftyMenuDelegate {
13 | var willExpandCalled = false
14 | var didExpandCalled = false
15 | var willCollapseCalled = false
16 | var didCollapseCalled = false
17 |
18 | func swiftyMenu(_ swiftyMenu: SwiftyMenu, didSelectItem item: SwiftyMenuDisplayable, atIndex index: Int) {
19 | }
20 |
21 | func swiftyMenu(_ swiftyMenu: SwiftyMenu, didDeselectItem item: SwiftyMenuDisplayable, atIndex index: Int) {
22 | }
23 |
24 | func swiftyMenu(willExpand swiftyMenu: SwiftyMenu) {
25 | willExpandCalled = true
26 | }
27 |
28 | func swiftyMenu(didExpand swiftyMenu: SwiftyMenu) {
29 | didExpandCalled = true
30 | }
31 |
32 | func swiftyMenu(willCollapse swiftyMenu: SwiftyMenu) {
33 | willCollapseCalled = true
34 | }
35 |
36 | func swiftyMenu(didCollapse swiftyMenu: SwiftyMenu) {
37 | didCollapseCalled = true
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Example/SwiftyMenu/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 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/SwiftyMenu.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod lib lint SwiftyMenu.podspec' to ensure this is a
3 | # valid spec before submitting.
4 | #
5 | # Any lines starting with a # are optional, but their use is encouraged
6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
7 | #
8 |
9 | Pod::Spec.new do |s|
10 | s.name = 'SwiftyMenu'
11 | s.version = '1.1.0'
12 | s.summary = 'SwiftyMenu is Simple Drop Down Menu for iOS.'
13 |
14 | s.description = <<-DESC
15 | This drop down is to overcome the loss of usability and user experience due to the UIPickerView.
16 | DESC
17 |
18 | s.homepage = 'https://github.com/KarimEbrahemAbdelaziz/SwiftyMenu'
19 | s.screenshots = 'https://github.com/KarimEbrahemAbdelaziz/SwiftyMenu/blob/master/Screenshots/3.png?raw=true'
20 | s.license = { :type => 'MIT', :file => 'LICENSE' }
21 | s.author = { 'KarimEbrahemAbdelaziz' => 'karimabdelazizmansour@gmail.com' }
22 | s.source = { :git => 'https://github.com/KarimEbrahemAbdelaziz/SwiftyMenu.git', :tag => s.version.to_s }
23 | s.social_media_url = 'https://www.linkedin.com/in/karimebrahem'
24 |
25 | s.ios.deployment_target = '10.0'
26 | s.swift_version = '5.0'
27 |
28 | s.source_files = 'SwiftyMenu/Classes/**/*'
29 | s.resources = 'SwiftyMenu/Assets/*'
30 |
31 | s.dependency 'SnapKit', '~> 5.0.1'
32 | end
33 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/SwiftMenuDisplayable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftMenuDisplayable.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | /// `SwiftyMenuDisplayable` is the markable interface to allow custom types to be used with SwiftyMenu.
28 | public protocol SwiftyMenuDisplayable {
29 | /// The value that will be displayed in the menu item
30 | var displayableValue: String { get }
31 |
32 | /// The value that will be returned when select menu item
33 | var retrievableValue: Any { get }
34 | }
35 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/UIViewExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewExtensions.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import UIKit
26 |
27 | extension UIView {
28 | /// Returns the `ParentViewController` for any view.
29 | var parentViewController: UIViewController? {
30 | var responder: UIResponder? = self
31 | while !(responder is UIViewController) {
32 | responder = responder?.next
33 | if responder == nil {
34 | break
35 | }
36 | }
37 | return (responder as? UIViewController)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/SwiftyMenuState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuState.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | /// Type describing the current state of `SwiftyMenu`.
28 | public enum SwiftyMenuState {
29 | /// `SwiftyMenu` is expanded.
30 | case shown
31 |
32 | /// `SwiftyMenu` is collapsed.
33 | case hidden
34 |
35 | /// Change the current state of SwiftyMenu
36 | mutating func toggle() {
37 | switch self {
38 | case .shown:
39 | self = .hidden
40 | case .hidden:
41 | self = .shown
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/MenuAttributes/SwiftyMenuAttributes+Scroll.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuAttributes+Scroll.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | public extension SwiftyMenuAttributes {
28 |
29 | /** Describes the event of scroll user interaction */
30 | enum Scroll {
31 |
32 | /** The scroll ability is totally disabled */
33 | case disabled
34 |
35 | /** The scroll abiliby is enabled */
36 | case enabled
37 |
38 | var isEnabled: Bool {
39 | switch self {
40 | case .disabled:
41 | return false
42 | default:
43 | return true
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/MenuAttributes/SwiftyMenuAttributes+Accessory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuAttributes+Accessory.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | public extension SwiftyMenuAttributes {
28 |
29 | /** Describes the event of scroll user interaction */
30 | enum Accessory {
31 |
32 | /** The scroll ability is totally disabled */
33 | case disabled
34 |
35 | /** The scroll abiliby is enabled */
36 | case enabled
37 |
38 | var isEnabled: Bool {
39 | switch self {
40 | case .disabled:
41 | return false
42 | default:
43 | return true
44 | }
45 | }
46 | }
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/MenuAttributes/SwiftyMenuAttributes+PlaceHolderStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuAttributes+MenuPlaceHolderStyle.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 | import UIKit
27 |
28 | public extension SwiftyMenuAttributes {
29 |
30 | /** Describes the style for row of the menu */
31 | enum PlaceHolderStyle {
32 |
33 | /** Placeholder with text and color */
34 | case value(text: String, textColor: UIColor)
35 |
36 | var placeHolderValues: (text: String, textColor: UIColor) {
37 | switch self {
38 | case let .value(text, textColor):
39 | return (text: text, textColor: textColor)
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/MenuAttributes/SwiftyMenuAttributes+AnimationTiming.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuAttributes+AnimationTiming.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | public extension SwiftyMenuAttributes {
28 |
29 | /** Describes how long the menu animates */
30 | enum AnimationTiming {
31 |
32 | case `default`
33 |
34 | case value(duration: Double, delay: Double)
35 |
36 | var animationTimingValues: (duration: Double, delay: Double) {
37 | switch self {
38 | case let .value(duration, delay):
39 | return (duration: duration, delay: delay)
40 | case .default:
41 | return (duration: 0.5, delay: 0.0)
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/MenuAttributes/SwiftyMenuAttributes+ErrorInformation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuAttributes+ErrorInformation.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 | import UIKit
27 |
28 | public extension SwiftyMenuAttributes{
29 | enum ErrorInformation {
30 | case `default`
31 | case custom(placeholderTextColor: UIColor = .red, iconTintColor: UIColor? = .red)
32 |
33 | public var errorInfoValues: (placeholderTextColor: UIColor, iconTintColor: UIColor?) {
34 | switch self {
35 | case .default:
36 | return (placeholderTextColor: .red, iconTintColor: .red)
37 | case let .custom(placeholderTextColor, iconTintColor):
38 | return (placeholderTextColor: placeholderTextColor, iconTintColor: iconTintColor)
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/SwiftyMenuAnimationStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuAnimationStyle.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | /// Type describing the Animation Styles used to create the animation of Expanding and Collapsing `SwiftyMenu`.
28 | public enum AnimationStyle {
29 | /// `SwiftyMenu` animation should be linear (Smooth Animation)
30 | case linear
31 |
32 | /// `SwiftyMenu` animation should be spring (Bouncy Animation)
33 | case spring(level: SpringPowerLevel)
34 |
35 | /// Defines how bouncy the animation should be
36 | ///
37 | /// - low: Bit of smooth and a bit of bounciness at the end
38 | /// - normal: Not too bouncy and not too smooth
39 | /// - high: Too bouncy
40 | public enum SpringPowerLevel: Double {
41 | case low = 0.75
42 | case normal = 1.0
43 | case high = 1.5
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/SwiftyMenuCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuCell.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | class SwiftyMenuCell: UITableViewCell {
28 | var rightMargin: CGFloat = 0.0
29 | var leftMargin: CGFloat = 0.0
30 | var isContentRightToLeft: Bool = false
31 |
32 |
33 | override func layoutSubviews() {
34 | super.layoutSubviews()
35 |
36 | if isContentRightToLeft {
37 | textLabel?.frame.origin.x = rightMargin
38 | textLabel?.frame.size.width = contentView.frame.width - rightMargin - leftMargin
39 | textLabel?.textAlignment = .right
40 | } else {
41 | textLabel?.frame.origin.x = leftMargin
42 | textLabel?.frame.size.width = contentView.frame.width - rightMargin - leftMargin
43 | textLabel?.textAlignment = .left
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/MenuAttributes/SwiftyMenuAttributes+SeparatorStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuAttributes+SeparatorStyle.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 | import UIKit
27 |
28 | public extension SwiftyMenuAttributes {
29 |
30 | /** Describes the style for separator of the menu */
31 | enum SeparatorStyle {
32 |
33 | case `default`
34 |
35 | case value(color: UIColor, isBlured: Bool, style: UITableViewCell.SeparatorStyle)
36 |
37 | var separatorStyleValues: (color: UIColor, isBlured: Bool, style: UITableViewCell.SeparatorStyle) {
38 | switch self {
39 | case let .value(color, isBlured, style):
40 | return (color: color, isBlured: isBlured, style: style)
41 | case .default:
42 | return (color: .gray, isBlured: false, style: UITableViewCell.SeparatorStyle.singleLine)
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/MenuAttributes/SwiftyMenuAttributes+MarginHorizontal.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuAttributes+MarginHorizontal.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 | import UIKit
27 |
28 | public extension SwiftyMenuAttributes {
29 | enum MarginHorizontal {
30 |
31 | case `default`
32 | case value(leading: CGFloat, trailing: CGFloat)
33 |
34 | var leadingValue: CGFloat {
35 | switch self {
36 | case let .value(leading, _):
37 | return leading
38 | case .default:
39 | return 16.0 // Default leading margin
40 | }
41 | }
42 |
43 | var trailingValue: CGFloat {
44 | switch self {
45 | case let .value(_, trailing):
46 | return trailing
47 | case .default:
48 | return 16.0 // Default trailing margin
49 | }
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/MenuAttributes/SwiftyMenuAttributes+RowStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuAttributes+RowStyle.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 | import UIKit
27 |
28 | public extension SwiftyMenuAttributes {
29 |
30 | /** Describes the style for row of the menu */
31 | enum RowStyle {
32 |
33 | /** Row with default color, background color and selected color */
34 | case `default`
35 |
36 | /** Row with color, background color and selected color */
37 | case value(height: Int, backgroundColor: UIColor, selectedColor: UIColor)
38 |
39 | var rowStyleValues: (height: Int, backgroundColor: UIColor, selectedColor: UIColor) {
40 | switch self {
41 | case let .value(height, backgroundColor, selectedColor):
42 | return (height: height, backgroundColor: backgroundColor, selectedColor: selectedColor)
43 | case .default:
44 | return (height: 35, backgroundColor: .white, selectedColor: .clear)
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/MenuAttributes/SwiftyMenuAttributes+TextStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuAttributes+TextStyle.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 | import UIKit
27 |
28 | public extension SwiftyMenuAttributes {
29 |
30 | /** Describes the style for separator of the menu */
31 | enum TextStyle {
32 |
33 | case `default`
34 |
35 | case value(color: UIColor, selectedColor: UIColor? = nil, separator: String, font: UIFont?, alignment: NSTextAlignment = .left)
36 |
37 | var textStyleValues: (color: UIColor, selectedColor: UIColor?, separator: String, font: UIFont?, alignment: NSTextAlignment) {
38 | switch self {
39 | case let .value(color, selectedColor, separator, font, alignment):
40 | return (color: color, selectedColor ?? color, separator: separator, font: font, alignment: alignment)
41 | case .default:
42 | return (color: .black, nil, separator: ", ", font: .systemFont(ofSize: 12), alignment: .left)
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/MenuAttributes/SwiftyMenuAttributes+Animation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuAttributes+Animation.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | extension SwiftyMenuAttributes {
28 | /// Type describing the Animation Styles used to create the animation of Expanding and Collapsing `SwiftyMenu`.
29 | public enum Animation {
30 | /// `SwiftyMenu` animation should be linear (Smooth Animation)
31 | case linear
32 |
33 | /// `SwiftyMenu` animation should be spring (Bouncy Animation)
34 | case spring(level: SpringPowerLevel)
35 |
36 | /// Defines how bouncy the animation should be
37 | ///
38 | /// - low: Bit of smooth and a bit of bounciness at the end
39 | /// - normal: Not too bouncy and not too smooth
40 | /// - high: Too bouncy
41 | public enum SpringPowerLevel: Double {
42 | case low = 0.75
43 | case normal = 1.0
44 | case high = 1.5
45 | }
46 |
47 | var springPower: Double? {
48 | switch self {
49 | case let .spring(level):
50 | return level.rawValue
51 | default:
52 | return nil
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Example/SwiftyMenu/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // SwiftyMenu
4 | //
5 | // Created by KEMansour on 04/17/2019.
6 | // Copyright (c) 2019 KEMansour. 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 |
20 | // For testing RTL support
21 | //UIView.appearance().semanticContentAttribute = .forceRightToLeft
22 |
23 | return true
24 | }
25 |
26 | func applicationWillResignActive(_ application: UIApplication) {
27 | // 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.
28 | // 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.
29 | }
30 |
31 | func applicationDidEnterBackground(_ application: UIApplication) {
32 | // 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.
33 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
34 | }
35 |
36 | func applicationWillEnterForeground(_ application: UIApplication) {
37 | // 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.
38 | }
39 |
40 | func applicationDidBecomeActive(_ application: UIApplication) {
41 | // 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.
42 | }
43 |
44 | func applicationWillTerminate(_ application: UIApplication) {
45 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
46 | }
47 |
48 |
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/MenuAttributes/SwiftyMenuAttributes+HeaderStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuAttributes+HeaderStyle.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 | import UIKit
27 |
28 | public extension SwiftyMenuAttributes {
29 |
30 | /** Describes the style for header of the menu */
31 | enum HeaderStyle {
32 |
33 | /** Row with background color */
34 | case value(backgroundColor: UIColor, contentHorizontalAlignment: UIControl.ContentHorizontalAlignment = Self.defaultContentHorizontalAlignment, height: Int)
35 |
36 | var headerStyleValues: (backgroundColor: UIColor, contentHorizontalAlignment: UIControl.ContentHorizontalAlignment, height: Int) {
37 | switch self {
38 | case let .value(backgroundColor, contentHorizontalAlignment, height):
39 | return (backgroundColor: backgroundColor, contentHorizontalAlignment: contentHorizontalAlignment, height: height)
40 | }
41 | }
42 |
43 | public static var defaultContentHorizontalAlignment: UIControl.ContentHorizontalAlignment {
44 | if #available(iOS 11.0, *) {
45 | return .leading
46 | } else {
47 | return .left
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/MenuAttributes/SwiftyMenuAttributes+SelectionBehavior.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuAttributes+SelectionBehavior.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | public extension SwiftyMenuAttributes {
28 |
29 | /** Describes the event of scroll user interaction */
30 | enum MultiSelection {
31 |
32 | /** The scroll ability is totally disabled */
33 | case disabled(allowSingleDeselection: Bool = false)
34 |
35 | /** The scroll abiliby is enabled */
36 | case enabled
37 |
38 | var isEnabled: Bool {
39 | switch self {
40 | case .disabled:
41 | return false
42 | default:
43 | return true
44 | }
45 | }
46 | }
47 |
48 | /** Describes the event of scroll user interaction */
49 | enum HideMenuWhenSelection {
50 |
51 | /** The scroll ability is totally disabled */
52 | case disabled
53 |
54 | /** The scroll abiliby is enabled */
55 | case enabled
56 |
57 | var isEnabled: Bool {
58 | switch self {
59 | case .disabled:
60 | return false
61 | default:
62 | return true
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/MenuAttributes/SwiftyMenuAttributes+ArrowStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuAttributes+ArrowStyle.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 | import UIKit
27 |
28 | public extension SwiftyMenuAttributes {
29 |
30 | /** Describes the style for row of the menu */
31 | enum ArrowStyle {
32 |
33 | case `default`
34 |
35 | case value(isEnabled: Bool, image: UIImage? = nil, tintColor: UIColor? = nil, spacingBetweenText: CGFloat = 0) // Added tintColor parameter with default nil
36 |
37 | var arrowStyleValues: (isEnabled: Bool, image: UIImage?, tintColor: UIColor?, spacingBetweenText: CGFloat) { // Added tintColor to the return type
38 | #if SWIFT_PACKAGE
39 | let frameworkBundle = Bundle.module
40 | #else
41 | let frameworkBundle = Bundle(for: SwiftyMenu.self)
42 | #endif
43 | let defaultImage = UIImage(named: "downArrow", in: frameworkBundle, compatibleWith: nil)!
44 |
45 | switch self {
46 | case let .value(isEnabled, image, tintColor, spacingBetweenText):
47 | if isEnabled && image != nil {
48 | return (isEnabled: isEnabled, image: image, tintColor: tintColor, spacingBetweenText: spacingBetweenText)
49 | } else if isEnabled {
50 | return (isEnabled: isEnabled, image: defaultImage, tintColor: tintColor, spacingBetweenText: spacingBetweenText)
51 | } else {
52 | return (isEnabled: isEnabled, image: nil, tintColor: tintColor, spacingBetweenText: spacingBetweenText)
53 | }
54 | case .default:
55 | return (isEnabled: true, image: defaultImage, tintColor: nil, spacingBetweenText: 0) // Default tintColor is nil
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## [1.0.0] - 2021-07-04
8 | ### Added
9 | - SwiftMenu different Attributes.
10 | - Support for SPM.
11 |
12 | ### Changed
13 | - Removed all `IBDesignable` from SwiftyMenu and changed the styling of the menu using `SwiftyMenuAttributes`.
14 |
15 | ### Fixed
16 | - Arrow direction when multi selection enabled.
17 |
18 |
19 | ## [0.6.5] - 2021-03-15
20 | ### Fixed
21 | - UI issues.
22 |
23 | ## [0.6.4] - 2020-07-13
24 | ### Changed
25 | - Update SnapKit version.
26 |
27 | ### Fixed
28 | - Support RTL in SwiftyMenu.
29 |
30 | ## [0.6.3] - 2020-04-06
31 | ### Added
32 | - Allow Changing separator color.
33 | - Allow Changing separator characters.
34 |
35 | ## [0.6.2] - 2020-04-03
36 | ### Fixed
37 | - Fix arrow rotation when open or close SwiftyMenu
38 |
39 | ## [0.6.1] - 2019-03-25
40 | ### Added
41 | - Add Unit Tests to SwiftMenu with Coverage of 57%.
42 |
43 | ## [0.6.0] - 2019-03-25
44 | ### Added
45 | - Restucture project code and add some Docs.
46 | - Allow to add SwiftyMenu programmatically.
47 | - Allow to toggle SwiftyMenu from Code.
48 |
49 | ## [0.5.9] - 2019-02-20
50 | ### Changed
51 | - SwiftyMenu is now can adapt on number of items it contain when the scrolling is enabled.
52 | - Remove support for Swift 4 and only focus on Swift 5 and above.
53 |
54 | ## [0.5.8] - 2019-09-03
55 | ### Fixed
56 | - Fix pre selected index in single selection didn't update label.
57 |
58 | ## [0.5.7] - 2019-09-03
59 | ### Fixed
60 | - Fix pre selected index in single selection didn't update label.
61 |
62 | ## [0.5.6] - 2019-09-02
63 | ### Fixed
64 | - Fix pre selected indices didn't update label.
65 |
66 | ## [0.5.5] - 2019-09-02
67 | ### Fixed
68 | - Fix pre selected indices didn't update label.
69 |
70 | ## [0.5.4] - 2019-09-02
71 | ### Fixed
72 | - Delete extra comma at the end when menu is multi selection.
73 | - Fix title label truncate.
74 |
75 | ## [0.5.3] - 2019-09-02
76 | ### Fixed
77 | - Delete extra comma at the end when menu is multi selection.
78 | - Fix title label truncate.
79 |
80 | ## [0.5.2] - 2019-06-17
81 | ### Fixed
82 | - Delete un expected animation while select option.
83 | - Fix arrow space in different sizes.
84 |
85 | ## [0.5.1] - 2019-06-17
86 | ### Fixed
87 | - Delete un expected animation while select option.
88 | - Fix arrow space in different sizes.
89 |
90 | ## [0.5.0] - 2019-06-01
91 | ### Added
92 | - Support to generic datasource.
93 |
94 | ## [0.4.9] - 2019-06-01
95 | ### Added
96 | - Support to generic datasource.
97 |
98 | ## [0.4.8] - 2019-05-06
99 | ### Added
100 | - Automate release new version to Cocoapods from Travis CI.
101 | - Add CHANGELOG file for the project.
102 |
103 | ## [0.4.7] - 2019-05-06
104 | ### Added
105 | - Automate release new version to Cocoapods from Travis CI.
106 | - Add CHANGELOG file for the project.
107 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/MenuAttributes/SwiftyMenuAttributes+FrameStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuAttributes+FrameStyle.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 | import CoreGraphics
27 | import UIKit
28 |
29 | public extension SwiftyMenuAttributes {
30 |
31 | enum ListHeight {
32 |
33 | case `default`
34 | case value(height: Int)
35 |
36 | var listHeightValue: Int {
37 | switch self {
38 | case let .value(height):
39 | return height
40 | case .default:
41 | return 0
42 | }
43 | }
44 |
45 | }
46 |
47 | /** Corner radius of the entry - Specifies the corners */
48 | enum RoundCorners {
49 |
50 | /** *None* of the corners will be round */
51 | case none
52 |
53 | /** *All* of the corners will be round */
54 | case all(radius: CGFloat)
55 |
56 | var hasRoundCorners: Bool {
57 | switch self {
58 | case .none:
59 | return false
60 | default:
61 | return true
62 | }
63 | }
64 |
65 | var cornerValue: CGFloat? {
66 | switch self {
67 | case let .all(radius):
68 | return radius
69 | case .none:
70 | return nil
71 | }
72 | }
73 | }
74 |
75 | /** The border around the entry */
76 | enum Border {
77 |
78 | /** No border */
79 | case none
80 |
81 | /** Border wirh color and width */
82 | case value(color: UIColor, width: CGFloat)
83 |
84 | var hasBorder: Bool {
85 | switch self {
86 | case .none:
87 | return false
88 | default:
89 | return true
90 | }
91 | }
92 |
93 | var borderValues: (color: UIColor, width: CGFloat)? {
94 | switch self {
95 | case .value(color: let color, width: let width):
96 | return(color: color, width: width)
97 | case .none:
98 | return nil
99 | }
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/SwiftyMenuDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuDelegate.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | /// `SwiftyMenuDelegate` is the delegate interface to delegate different actions of SwiftyMenu.
28 | public protocol SwiftyMenuDelegate: AnyObject {
29 | /// Called when select an item from `SwiftyMenu`.
30 | ///
31 | /// - Parameters:
32 | /// - swiftyMenu: The `SwiftyMenu` that was selected from it's items.
33 | /// - item: The `Item` that had been selected from `SwiftyMenu`.
34 | /// - index: The `Index` of the selected `Item`.
35 | func swiftyMenu(_ swiftyMenu: SwiftyMenu, didSelectItem item: SwiftyMenuDisplayable, atIndex index: Int)
36 |
37 | /// Called when select an item from `SwiftyMenu`.
38 | ///
39 | /// - Parameters:
40 | /// - swiftyMenu: The `SwiftyMenu` that was deselected from it's items.
41 | /// - item: The `Item` that had been deselected from `SwiftyMenu`.
42 | /// - index: The `Index` of the deselected `Item`.
43 | func swiftyMenu(_ swiftyMenu: SwiftyMenu, didDeselectItem item: SwiftyMenuDisplayable, atIndex index: Int)
44 |
45 | /// Called when `SwiftyMenu` will going to Expand.
46 | ///
47 | /// - Parameter swiftyMenu: The `SwiftyMenu` that is going to Expand.
48 | func swiftyMenu(willExpand swiftyMenu: SwiftyMenu)
49 |
50 | /// Called when `SwiftyMenu` Expanded.
51 | ///
52 | /// - Parameter swiftyMenu: The `SwiftyMenu` that Expanded.
53 | func swiftyMenu(didExpand swiftyMenu: SwiftyMenu)
54 |
55 | /// Called when `SwiftyMenu` will going to Collapse.
56 | ///
57 | /// - Parameter swiftyMenu: The `SwiftyMenu` that is going to Collapse.
58 | func swiftyMenu(willCollapse swiftyMenu: SwiftyMenu)
59 |
60 | /// Called when `SwiftyMenu` Collapsed.
61 | ///
62 | /// - Parameter swiftyMenu: The `SwiftyMenu` that Collapsed.
63 | func swiftyMenu(didCollapse swiftyMenu: SwiftyMenu)
64 | }
65 |
66 | /// `SwiftyMenuDelegate` Default implementation to workaround Optionality.
67 | public extension SwiftyMenuDelegate {
68 | func swiftyMenu(willExpand swiftyMenu: SwiftyMenu) { }
69 | func swiftyMenu(didExpand swiftyMenu: SwiftyMenu) { }
70 | func swiftyMenu(willCollapse swiftyMenu: SwiftyMenu) { }
71 | func swiftyMenu(didCollapse swiftyMenu: SwiftyMenu) { }
72 | }
73 |
--------------------------------------------------------------------------------
/Example/SwiftyMenu/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 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/MenuAttributes/SwiftyMenuAttributes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuAttributes.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 | import UIKit
27 |
28 | public struct SwiftyMenuAttributes {
29 |
30 | /**
31 | A settable **optional** name that matches the menu-attributes.
32 | */
33 | public var name: String?
34 |
35 | /**
36 | Describes the scrolling behaviour of the menu.
37 | */
38 | public var scroll = Scroll.enabled
39 |
40 | /**
41 | Describes the accessory behaviour of the menu.
42 | */
43 | public var accessory = Accessory.enabled
44 |
45 | /**
46 | Describes the selection behaviour of the menu.
47 | */
48 | public var multiSelect = MultiSelection.enabled
49 |
50 | /**
51 | Describes the after selection behaviour of the menu.
52 | */
53 | public var hideOptionsWhenSelect = HideMenuWhenSelection.enabled
54 |
55 | /**
56 | Describes the style for row of the menu.
57 | */
58 | public var rowStyle = RowStyle.default
59 |
60 | /**
61 | Describes the style for header of the menu.
62 | */
63 | public var headerStyle = HeaderStyle.value(backgroundColor: .clear, height: 40)
64 |
65 | /**
66 | Describes the style for arrow of the menu.
67 | */
68 | public var arrowStyle = ArrowStyle.default
69 |
70 | /**
71 | Describes the style for separator of the menu.
72 | */
73 | public var separatorStyle = SeparatorStyle.default
74 |
75 | /**
76 | Describes the style for text of the menu.
77 | */
78 | public var textStyle = TextStyle.default
79 |
80 | /**
81 | Describes the style for row of the menu.
82 | */
83 | public var placeHolderStyle = PlaceHolderStyle.value(text: "", textColor: .black)
84 |
85 | /** The corner attributes */
86 | public var roundCorners = RoundCorners.none
87 |
88 | /** The border around the menue */
89 | public var border = Border.none
90 |
91 | /** The height of the menue */
92 | public var height = ListHeight.default
93 |
94 | /** Describes how the menu animates while expanding */
95 | public var expandingAnimation = Animation.linear
96 |
97 | /** Describes how the menu animates while collapsing */
98 | public var collapsingAnimation = Animation.linear
99 |
100 | /** Describes how long the menu animates while expanding */
101 | public var expandingTiming = AnimationTiming.default
102 |
103 | /** Describes how long the menu animates while collapsing */
104 | public var collapsingTiming = AnimationTiming.default
105 |
106 | /** Describes errorInfo */
107 | public var errorInfo = ErrorInformation.default
108 |
109 | /** Describes title margin horizontal */
110 | public var titleMarginHorizontal = MarginHorizontal.default
111 |
112 | /** Describes item margin horizontal */
113 | public var itemMarginHorizontal = MarginHorizontal.default
114 |
115 | /** Describes arrow margin right, sometime the custom image is no loaded well, so it's better add more space */
116 | public var arrowMarginRight = CGFloat.zero
117 |
118 | /** Init with default attributes */
119 | public init() {}
120 | }
121 |
--------------------------------------------------------------------------------
/Example/SwiftyMenu.xcodeproj/xcshareddata/xcschemes/SwiftyMenu-Example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
45 |
46 |
52 |
53 |
54 |
55 |
57 |
63 |
64 |
65 |
67 |
73 |
74 |
75 |
76 |
77 |
87 |
89 |
95 |
96 |
97 |
98 |
104 |
106 |
112 |
113 |
114 |
115 |
117 |
118 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/Example/Tests/SwiftyMenuTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenuTests.swift
3 | // SwiftyMenu_Tests
4 | //
5 | // Created by Karim Ebrahem on 3/25/20.
6 | // Copyright © 2020 CocoaPods. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftyMenu
11 |
12 | class SwiftyMenuTests: XCTestCase {
13 |
14 | var sut: SwiftyMenu?
15 | var swiftyMenuDelegateSpy: SwiftyMenuDelegateSpy?
16 |
17 | override func setUp() {
18 | sut = SwiftyMenu()
19 | sut?.heightConstraint = NSLayoutConstraint(item: sut!, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 40)
20 | swiftyMenuDelegateSpy = SwiftyMenuDelegateSpy()
21 | sut?.delegate = swiftyMenuDelegateSpy
22 | sut?.configure(with: SwiftyMenuAttributes())
23 | super.setUp()
24 | }
25 |
26 | override func tearDown() {
27 | sut = nil
28 | swiftyMenuDelegateSpy = nil
29 | super.tearDown()
30 | }
31 |
32 | func testWillExpandDelegateSuccessCalled() {
33 | // When
34 | sut?.toggle()
35 |
36 | // Then
37 | XCTAssertTrue(swiftyMenuDelegateSpy?.willExpandCalled ?? false)
38 | }
39 |
40 | func testDidExpandDelegateSuccessCalled() {
41 | // Given
42 | let didExpandExpectation = expectation(description: "SwiftyMenu doing some animation and will call this callback when finish")
43 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
44 | // Then
45 | XCTAssertTrue(self.swiftyMenuDelegateSpy!.didExpandCalled)
46 | didExpandExpectation.fulfill()
47 | }
48 | // When
49 | sut?.toggle()
50 |
51 | // Wait
52 | waitForExpectations(timeout: 3) { error in
53 | if let error = error {
54 | XCTFail("waitForExpectationsWithTimeout errored: \(error)")
55 | }
56 | }
57 |
58 | }
59 |
60 | func testWillCollapseDelegateSuccessCalled() {
61 | // When
62 | sut?.toggle()
63 | sut?.toggle()
64 |
65 | // Then
66 | XCTAssertTrue(swiftyMenuDelegateSpy!.willCollapseCalled)
67 | }
68 |
69 | func testDidCollapseDelegateSuccessCalled() {
70 | // Given
71 | let didCollapseExpectation = expectation(description: "SwiftyMenu doing some animation and will call this callback when finish")
72 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
73 | // Then
74 | XCTAssertTrue(self.swiftyMenuDelegateSpy!.didCollapseCalled)
75 | didCollapseExpectation.fulfill()
76 | }
77 |
78 | // When
79 | sut?.toggle()
80 | sut?.toggle()
81 |
82 | // Wait
83 | waitForExpectations(timeout: 3) { error in
84 | if let error = error {
85 | XCTFail("waitForExpectationsWithTimeout errored: \(error)")
86 | }
87 | }
88 | }
89 |
90 | func testWillExpandCallbackSuccessCalled() {
91 | // Given
92 | var willExpandCallbackCalled = false
93 | sut?.willExpand = {
94 | willExpandCallbackCalled = true
95 | }
96 |
97 | // When
98 | sut?.toggle()
99 |
100 | // Then
101 | XCTAssertTrue(willExpandCallbackCalled)
102 | }
103 |
104 | func testDidExpandCallbackSuccessCalled() {
105 | // Given
106 | let didExpandExpectation = expectation(description: "SwiftyMenu doing some animation and will call this callback when finish")
107 | var didExpandCallbackCalled = false
108 | sut?.didExpand = {
109 | didExpandCallbackCalled = true
110 |
111 | // Then
112 | XCTAssertTrue(didExpandCallbackCalled)
113 | didExpandExpectation.fulfill()
114 | }
115 |
116 | // When
117 | sut?.toggle()
118 |
119 | // Wait
120 | waitForExpectations(timeout: 3) { error in
121 | if let error = error {
122 | XCTFail("waitForExpectationsWithTimeout errored: \(error)")
123 | }
124 | }
125 | }
126 |
127 | func testWillCollapseCallbackSuccessCalled() {
128 | // Given
129 | var willCollapseCallbackCalled = false
130 | sut?.willCollapse = {
131 | willCollapseCallbackCalled = true
132 | }
133 |
134 | // When
135 | sut?.toggle()
136 | sut?.toggle()
137 |
138 | // Then
139 | XCTAssertTrue(willCollapseCallbackCalled)
140 | }
141 |
142 | func testDidCollapseCallbackSuccessCalled() {
143 | // Given
144 | let didCollapsExpectation = expectation(description: "SwiftyMenu doing some animation and will call this callback when finish")
145 | var didCollapseCallbackCalled = false
146 | sut?.didCollapse = {
147 | didCollapseCallbackCalled = true
148 |
149 | // Then
150 | XCTAssertTrue(didCollapseCallbackCalled)
151 | didCollapsExpectation.fulfill()
152 | }
153 |
154 | // When
155 | sut?.toggle()
156 | sut?.toggle()
157 |
158 | // Wait
159 | waitForExpectations(timeout: 3) { error in
160 | if let error = error {
161 | XCTFail("waitForExpectationsWithTimeout errored: \(error)")
162 | }
163 | }
164 | }
165 |
166 | }
167 |
--------------------------------------------------------------------------------
/Example/SwiftyMenu/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // SwiftyMenu
4 | //
5 | // Created by KEMansour on 04/17/2019.
6 | // Copyright (c) 2019 KEMansour. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SwiftyMenu
11 | import SnapKit
12 |
13 | class ViewController: UIViewController {
14 |
15 | @IBOutlet weak var contentScrollView: UIView!
16 | @IBOutlet weak var stackView: UIStackView!
17 | @IBOutlet private weak var dropDown: SwiftyMenu!
18 | @IBOutlet private weak var dropDown2: SwiftyMenu!
19 | @IBOutlet private weak var dropDown3: SwiftyMenu!
20 | @IBOutlet private weak var otherView: UIView!
21 | @IBOutlet weak var isErrorEnableSwitch: UISwitch!
22 |
23 | let indexSelectedForError = 0
24 |
25 | /// Define menu data source
26 | /// The data source type should conform to `SwiftyMenuDisplayable`
27 | private let dropDownOptionsDataSource = [
28 | "Option 1", "Option 2", "Option 3",
29 | "Option 4", "Option 5", "Option 6",
30 | "Option 7", "Option 8", "Option 9"
31 | ]
32 |
33 | private let dropDownCodeOptionsDataSource = [
34 | MealSize(id: 1, name: "Small"),
35 | MealSize(id: 2, name: "Medium"),
36 | MealSize(id: 3, name: "Large"),
37 | MealSize(id: 4, name: "Combo Large"),
38 | ]
39 |
40 | /// Define menu attributes
41 | private var storyboardMenuAttributes: SwiftyMenuAttributes {
42 | var attributes = SwiftyMenuAttributes()
43 |
44 | // Custom Behavior
45 | attributes.multiSelect = .enabled
46 | attributes.scroll = .enabled
47 | attributes.hideOptionsWhenSelect = .disabled
48 |
49 | // Custom UI
50 | attributes.roundCorners = .all(radius: 8)
51 | attributes.rowStyle = .value(height: 35, backgroundColor: .white, selectedColor: UIColor.gray.withAlphaComponent(0.1))
52 | attributes.headerStyle = .value(backgroundColor: .white, height: 35)
53 | attributes.placeHolderStyle = .value(text: "Please Select Item", textColor: .lightGray)
54 | attributes.textStyle = .value(color: .gray, separator: " & ", font: .systemFont(ofSize: 12))
55 | attributes.separatorStyle = .value(color: .black, isBlured: false, style: .singleLine)
56 | attributes.arrowStyle = .value(isEnabled: true)
57 | attributes.height = .value(height: 300)
58 |
59 | // Custom Animations
60 | attributes.expandingAnimation = .spring(level: .low)
61 | attributes.expandingTiming = .value(duration: 0.5, delay: 0)
62 |
63 | attributes.collapsingAnimation = .linear
64 | attributes.collapsingTiming = .value(duration: 0.5, delay: 0)
65 |
66 | attributes.titleMarginHorizontal = .value(leading: 5, trailing: 5)
67 | attributes.itemMarginHorizontal = .value(leading: 5, trailing: 5)
68 |
69 | attributes.errorInfo = .default
70 |
71 | return attributes
72 | }
73 |
74 | private var codeMenuAttributes: SwiftyMenuAttributes {
75 | var attributes = SwiftyMenuAttributes()
76 |
77 | // Custom Behavior
78 | attributes.multiSelect = .disabled(allowSingleDeselection: true)
79 | attributes.scroll = .disabled
80 | attributes.hideOptionsWhenSelect = .enabled
81 |
82 | // Custom UI
83 | attributes.roundCorners = .all(radius: 8)
84 | attributes.rowStyle = .value(height: 39, backgroundColor: .white, selectedColor: .white)
85 | attributes.headerStyle = .value(backgroundColor: .white, height: 40)
86 | attributes.placeHolderStyle = .value(text: "Please Select Size", textColor: .lightGray)
87 | attributes.textStyle = .value(color: .gray, separator: " & ", font: .systemFont(ofSize: 12))
88 | attributes.separatorStyle = .value(color: .black, isBlured: false, style: .singleLine)
89 | attributes.arrowStyle = .value(isEnabled: false)
90 | attributes.accessory = .disabled
91 |
92 | // Custom Animations
93 | attributes.expandingAnimation = .linear
94 | attributes.expandingTiming = .value(duration: 0.5, delay: 0)
95 |
96 | attributes.collapsingAnimation = .linear
97 | attributes.collapsingTiming = .value(duration: 0.5, delay: 0)
98 |
99 | return attributes
100 | }
101 |
102 | override func viewDidLoad() {
103 | super.viewDidLoad()
104 |
105 | setupStoryboardMenu()
106 | setupMenu2()
107 | setupMenu3()
108 | setupCodeMenu()
109 | }
110 |
111 | /// Example of building SwiftyMenu from Storyboard
112 | private func setupStoryboardMenu() {
113 | /// Setup component
114 | dropDown.delegate = self
115 | dropDown.items = dropDownOptionsDataSource
116 |
117 | /// Configure SwiftyMenu with the attributes
118 | dropDown.configure(with: storyboardMenuAttributes)
119 | }
120 |
121 | //Example for swiftyMenu2 with custom margins
122 | private func setupMenu2() {
123 | /// Setup component
124 | dropDown2.delegate = self
125 | dropDown2.items = dropDownOptionsDataSource
126 |
127 | var attributes = storyboardMenuAttributes
128 | attributes.titleMarginHorizontal = .value(leading: 10, trailing: 20)
129 | attributes.itemMarginHorizontal = .value(leading: 10, trailing: 10)
130 | let image = UIImage(named: "arrow.down.custom")
131 | attributes.arrowStyle = .value(isEnabled: true, image: image, tintColor: .purple, spacingBetweenText: 10.0)
132 | /// Configure SwiftyMenu with the attributes
133 | dropDown2.configure(with: attributes)
134 | }
135 |
136 | private func setupMenu3() {
137 | /// Setup component
138 | dropDown3.delegate = self
139 | dropDown3.items = dropDownOptionsDataSource
140 |
141 | var attributes = storyboardMenuAttributes
142 | attributes.titleMarginHorizontal = .value(leading: 0, trailing: 0)
143 | attributes.itemMarginHorizontal = .value(leading: 40, trailing: 40)
144 |
145 | attributes.multiSelect = .disabled(allowSingleDeselection: false)
146 | attributes.arrowStyle = .value(isEnabled: false)
147 | attributes.hideOptionsWhenSelect = .enabled
148 |
149 | /// Configure SwiftyMenu with the attributes
150 | dropDown3.configure(with: attributes)
151 | }
152 |
153 | /// Example of building SwiftyMenu from Code
154 | private func setupCodeMenu() {
155 |
156 | /// Init SwiftyMenu from Code
157 | let dropDownCode = SwiftyMenu(frame: CGRect(x: 0, y: 0, width: 0, height: 40))
158 |
159 | /// Add it as subview
160 | view.addSubview(dropDownCode)
161 |
162 | /// Add constraints to SwiftyMenu
163 | /// You must take care of `hegiht` constraint, please.
164 | dropDownCode.translatesAutoresizingMaskIntoConstraints = false
165 | //let horizontalConstraint = NSLayoutConstraint(item: dropDownCode, attribute: NSLayoutConstraint.Attribute.centerX, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view, attribute: NSLayoutConstraint.Attribute.centerX, multiplier: 1, constant: 0)
166 | //let topConstraint = NSLayoutConstraint(item: dropDownCode, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: otherView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 64)
167 | //let widthConstraint = NSLayoutConstraint(item: dropDownCode, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 255)
168 | dropDownCode.heightConstraint = NSLayoutConstraint(item: dropDownCode, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 40)
169 |
170 | NSLayoutConstraint.activate(
171 | [
172 | //horizontalConstraint,
173 | //topConstraint,
174 | //widthConstraint,
175 | dropDownCode.heightConstraint,
176 | ]
177 | )
178 |
179 | stackView.addArrangedSubview(dropDownCode)
180 |
181 | /// Setup SwiftyMenu data source
182 | dropDownCode.items = dropDownCodeOptionsDataSource
183 |
184 | /// SwiftyMenu also supports `CallBacks`
185 | dropDownCode.willExpand = {
186 | print("SwiftyMenu Code Will Expand!")
187 | }
188 |
189 | dropDownCode.didExpand = {
190 | print("SwiftyMenu Code Expanded!")
191 | }
192 |
193 | dropDownCode.willCollapse = {
194 | print("SwiftyMenu Code Will Collapse!")
195 | }
196 |
197 | dropDownCode.didCollapse = {
198 | print("SwiftyMenu Code Collapsed!")
199 | }
200 |
201 | dropDownCode.didSelectItem = { _, item, index in
202 | print("Selected from Code \(item) at index: \(index)")
203 | }
204 |
205 | dropDownCode.didDeselectItem = { _, item, index in
206 | print("Deselected from Code \(item) at index: \(index)")
207 | }
208 |
209 | /// Configure SwiftyMenu with the attributes
210 | var attributes = codeMenuAttributes
211 | attributes.border = .value(color: .black, width: 1.0)
212 | /*//attributes.arrowStyle = .default
213 | //attributes.arrowStyle = .value(isEnabled: true, image: nil, tintColor: nil)
214 | attributes.separatorStyle = .value(color: .black, isBlured: false, style: .singleLine)
215 | attributes.arrowStyle = .value(isEnabled: false)
216 | //attributes.multiSelect = .disabled(allowSingleDeselection: false)
217 | attributes.multiSelect = .enabled
218 | attributes.hideOptionsWhenSelect = .disabled*/
219 |
220 | dropDownCode.configure(with: attributes)
221 |
222 | }
223 |
224 | @IBAction func toggleIsErrorEnable(_ sender: UISwitch) {
225 | if sender == isErrorEnableSwitch{
226 | if sender.isOn{
227 | if dropDown.selectedIndecis.contains(where: { $0.key == indexSelectedForError }), isErrorEnableSwitch.isOn{
228 | dropDown.setError(hasError: true)
229 | }else{
230 | dropDown.setError(hasError: false)
231 | }
232 | }else{
233 | dropDown.setError(hasError: false)
234 | }
235 | }
236 | }
237 |
238 | override func viewDidAppear(_ animated: Bool) {
239 | DispatchQueue.main.asyncAfter(deadline: .now()+1){
240 | self.dropDown2.setError(hasError: true)
241 | self.dropDown2.toggle()
242 | }
243 | }
244 | }
245 |
246 | // MARK: - SwiftMenuDelegate
247 |
248 | extension ViewController: SwiftyMenuDelegate {
249 | func swiftyMenu(_ swiftyMenu: SwiftyMenu, didSelectItem item: SwiftyMenuDisplayable, atIndex index: Int) {
250 | print("didSelectItem item: \(item), at index: \(index)")
251 |
252 | if swiftyMenu == dropDown{
253 | if index == indexSelectedForError{
254 | if isErrorEnableSwitch.isOn{
255 | dropDown.setError(hasError: true)
256 | }else{
257 | dropDown.setError(hasError: false)
258 | }
259 | }else{
260 | if dropDown.selectedIndecis.contains(where: { $0.key == indexSelectedForError }), isErrorEnableSwitch.isOn{
261 | dropDown.setError(hasError: true)
262 | }else{
263 | dropDown.setError(hasError: false)
264 | }
265 | }
266 | }
267 | }
268 |
269 | func swiftyMenu(_ swiftyMenu: SwiftyMenu, didDeselectItem item: SwiftyMenuDisplayable, atIndex index: Int) {
270 | print("didDeselectItem item: \(item), at index: \(index)")
271 |
272 | if swiftyMenu == dropDown{
273 | if index == indexSelectedForError{
274 | if isErrorEnableSwitch.isOn{
275 | dropDown.setError(hasError: false)
276 | }
277 | }
278 | }
279 | }
280 |
281 | func swiftyMenu(willExpand swiftyMenu: SwiftyMenu) {
282 | print("SwiftyMenu willExpand.")
283 | }
284 |
285 | func swiftyMenu(didExpand swiftyMenu: SwiftyMenu) {
286 | print("SwiftyMenu didExpand.")
287 | }
288 |
289 | func swiftyMenu(willCollapse swiftyMenu: SwiftyMenu) {
290 | print("SwiftyMenu willCollapse.")
291 | }
292 |
293 | func swiftyMenu(didCollapse swiftyMenu: SwiftyMenu) {
294 | print("SwiftyMenu didCollapse.")
295 | }
296 |
297 | }
298 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
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 | SwiftyMenu is simple yet powerfull drop down menu component for iOS. It allow you to have drop down menu that doesn't appear over your views, which give you awesome user experience.
28 |
29 | - [Screenshots](#screenshots)
30 | - [Requirements](#requirements)
31 | - [Installation](#installation)
32 | - [Cocoapods](#cocoapods)
33 | - [Swift Package Manager](#swift-package-manager)
34 | - [Usage](#usage)
35 | - [Initialization](#initialization)
36 | - [Storyboard](#storyboard)
37 | - [Code](#code)
38 | - [Configure DataSource](#configure-datasource)
39 | - [Setup Models](#setup-models)
40 | - [Assign DataSource](#assign-datasource)
41 | - [Capture Selection](#capture-selection)
42 | - [Using Delegate](#using-delegate)
43 | - [Using Closures](#using-closures)
44 | - [UI Customization](#ui-customization)
45 | - [Placeholder](#placeholder)
46 | - [Text Style](#text-style)
47 | - [Scroll](#scroll)
48 | - [Selection Behavior](#selection-behavior)
49 | - [Row Style](#row-style)
50 | - [Frame Style](#frame-style)
51 | - [Arrow Style](#arrow-style)
52 | - [Separator Style](#separator-style)
53 | - [Header Style](#header-style)
54 | - [Accessory](#accessory)
55 | - [Animation](#animation)
56 | - [Margin Horizontal](#margin-horizontal)
57 | - [Error Info](#error-info)
58 | - [Set Error](#set-error)
59 | - [New Functionalities](#new-functionalities)
60 | - [Example Project](#example-project)
61 | - [Todo](#todo)
62 | - [Android Version](#android)
63 | - [Author](#author)
64 | - [Credits](#credits)
65 | - [License](#license)
66 |
67 | ## Screenshots
68 |
69 |
70 |
71 |
72 |
73 | ## Requirements
74 |
75 | * Xcode 10.2+
76 | * Swift 5+
77 | * iOS 10+
78 |
79 | ## Installation
80 |
81 | ### CocoaPods
82 |
83 | [CocoaPods](https://cocoapods.org) is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate SwiftyMenu into your Xcode project using CocoaPods, specify it in your `Podfile`:
84 |
85 | ```ruby
86 | pod 'SwiftyMenu', '~> 1.1.0'
87 | ```
88 |
89 | ### Swift Package Manager
90 |
91 | 1. Automatically in Xcode:
92 |
93 | - Click **File > Swift Packages > Add Package Dependency...**
94 | - Use the package URL `https://github.com/KarimEbrahemAbdelaziz/SwiftyMenu` to add TimelaneCombine to your project.
95 |
96 | 2. Manually in your **Package.swift** file add:
97 |
98 | ```swift
99 | .package(url: "https://github.com/KarimEbrahemAbdelaziz/SwiftyMenu", from: "1.1.0")
100 | ```
101 |
102 | ## Usage
103 |
104 | `SwiftyMenu` supports initialization from `Storyboard` and `Code`.
105 |
106 | ### Initialization
107 |
108 | #### Storyboard
109 |
110 | Setup your view controller:
111 |
112 | ```swift
113 | // Connect view in storyboard with you outlet
114 | @IBOutlet private weak var dropDownMenu: SwiftyMenu!
115 | ```
116 | Then connect `IBOutlet` to `Storyboard`, and connect the `Height` Constraints of the menu as shown below.
117 |
118 |
119 |
120 | #### Code
121 |
122 | 1. Init `SwiftyMenu`
123 |
124 | ```swift
125 | /// Init SwiftyMenu from Code
126 | let dropDownCode = SwiftyMenu(frame: CGRect(x: 0, y: 0, width: 0, height: 40))
127 | ```
128 | 2. Add `SwiftyMenu` as `Subview`
129 |
130 | ```swift
131 | /// Add it as subview
132 | view.addSubview(dropDownCode)
133 | ```
134 | 3. Setup Constraints
135 |
136 | ```swift
137 | /// Add constraints to SwiftyMenu
138 | /// You must take care of `hegiht` constraint, please.
139 | dropDownCode.translatesAutoresizingMaskIntoConstraints = false
140 |
141 | let horizontalConstraint = NSLayoutConstraint(item: dropDownCode, attribute: NSLayoutConstraint.Attribute.centerX, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view, attribute: NSLayoutConstraint.Attribute.centerX, multiplier: 1, constant: 0)
142 |
143 | let topConstraint = NSLayoutConstraint(item: dropDownCode, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: otherView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 64)
144 |
145 | let widthConstraint = NSLayoutConstraint(item: dropDownCode, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 255)
146 |
147 | dropDownCode.heightConstraint = NSLayoutConstraint(item: dropDownCode, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 40)
148 |
149 | NSLayoutConstraint.activate(
150 | [
151 | horizontalConstraint,
152 | topConstraint,
153 | widthConstraint,
154 | dropDownCode.heightConstraint
155 | ]
156 | )
157 | ```
158 |
159 | ### Configure DataSource
160 |
161 | To configure `SwiftyMenu` DataSource, you'll need to prepare your `Model` to be able to presented and retrived from the menu. Then, create and assign the DataSource to `SwiftyMenu`.
162 |
163 | #### Setup Models
164 |
165 | We are supporting Generic Data Source, all you have to do is conforming to our Generic Protocol on which type you want to add to the menu.
166 |
167 | - Example of `String`
168 |
169 | ```swift
170 | extension String: SwiftyMenuDisplayable {
171 | public var displayableValue: String {
172 | return self
173 | }
174 |
175 | public var retrivableValue: Any {
176 | return self
177 | }
178 | }
179 | ```
180 | - Example of custom `Struct`
181 |
182 | ```swift
183 | struct MealSize {
184 | let id: Int
185 | let name: String
186 | }
187 |
188 | extension MealSize: SwiftyMenuDisplayable {
189 | public var displayableValue: String {
190 | return self.name
191 | }
192 |
193 | public var retrievableValue: Any {
194 | return self.id
195 | }
196 | }
197 | ```
198 |
199 | #### Assign DataSource
200 |
201 | 1. Create an `Array` of your models
202 |
203 | ```swift
204 | /// Define menu data source
205 | /// The data source type must conform to `SwiftyMenuDisplayable`
206 | private let dropDownOptionsDataSource = [
207 | MealSize(id: 1, name: "Small"),
208 | MealSize(id: 2, name: "Medium"),
209 | MealSize(id: 3, name: "Large"),
210 | MealSize(id: 4, name: "Combo Large")
211 | ]
212 | ```
213 | 2. Assign it to `SwiftyMenu` DataSource property
214 |
215 | ```swift
216 | dropDownCode.items = dropDownOptionsDataSource
217 | ```
218 | ### Capture Selection
219 |
220 | `SwiftyMenu` supports 2 ways to capture the selected items, `Delegate` and `Closure`s. You can use one or both of them at the same time.
221 |
222 | #### Using Delegate
223 |
224 | 1. Conform to `SwiftyMenu` Delegate protocol
225 |
226 | ```swift
227 | extension ViewController: SwiftyMenuDelegate {
228 | // Get selected option from SwiftyMenu
229 | func swiftyMenu(_ swiftyMenu: SwiftyMenu, didSelectItem item: SwiftyMenuDisplayable, atIndex index: Int) {
230 | print("Selected item: \(item), at index: \(index)")
231 | }
232 |
233 | // NEW - Get selected option from SwiftyMenu
234 | func swiftyMenu(_ swiftyMenu: SwiftyMenu, didDeselectItem item: SwiftyMenuDisplayable, atIndex index: Int){
235 | print("deselected item: \(item), at index: \(index)")
236 | }
237 |
238 | // SwiftyMenu drop down menu will expand
239 | func swiftyMenu(willExpand swiftyMenu: SwiftyMenu) {
240 | print("SwiftyMenu willExpand.")
241 | }
242 |
243 | // SwiftyMenu drop down menu did expand
244 | func swiftyMenu(didExpand swiftyMenu: SwiftyMenu) {
245 | print("SwiftyMenu didExpand.")
246 | }
247 |
248 | // SwiftyMenu drop down menu will collapse
249 | func swiftyMenu(willCollapse swiftyMenu: SwiftyMenu) {
250 | print("SwiftyMenu willCollapse.")
251 | }
252 |
253 | // SwiftyMenu drop down menu did collapse
254 | func swiftyMenu(didCollapse swiftyMenu: SwiftyMenu) {
255 | print("SwiftyMenu didCollapse.")
256 | }
257 | }
258 | ```
259 | 2. Assign `SwiftyMenu` delegate
260 |
261 | ```swift
262 | dropDownCode.delegate = self
263 | ```
264 |
265 | #### Using Closures
266 |
267 | You can use callbacks to know what happen:
268 |
269 | ```swift
270 | /// SwiftyMenu also supports `CallBacks`
271 | dropDownCode.didSelectItem = { menu, item, index in
272 | print("Selected \(item) at index: \(index)")
273 | }
274 |
275 | dropDownCode.willExpand = {
276 | print("SwiftyMenu Will Expand!")
277 | }
278 |
279 | dropDownCode.didExpand = {
280 | print("SwiftyMenu Expanded!")
281 | }
282 |
283 | dropDownCode.willCollapse = {
284 | print("SwiftyMenu Will Collapse!")
285 | }
286 |
287 | dropDownCode.didCollapse = {
288 | print("SwiftyMenu Collapsed!")
289 | }
290 | ```
291 |
292 | ### UI Customization
293 |
294 | Having an amazing drop down menu is essential. So, there're a lot of UI customization for `SwiftyMenu` (More to be added soon).
295 |
296 | To configure UI customization for `SwiftyMenu`:
297 |
298 | 1. Create `SwiftyMenuAttributes` property
299 |
300 | ```swift
301 | private var codeMenuAttributes = SwiftyMenuAttributes()
302 | ```
303 | 2. Assign it to `SwiftyMenu`
304 |
305 | ```swift
306 | /// Configure SwiftyMenu with the attributes
307 | dropDownCode.configure(with: codeMenuAttributes)
308 | ```
309 | Also before assigning it to `SwiftyMenu` you could customize the menu look using following attributes.
310 |
311 | #### Placeholder
312 |
313 | ```swift
314 | attributes.placeHolderStyle = .value(text: "Please Select Size", textColor: .lightGray)
315 | ```
316 |
317 | #### Text Style
318 |
319 | ```swift
320 | attributes.textStyle = .value(color: .gray, separator: " & ", font: .systemFont(ofSize: 12))
321 | ```
322 |
323 | #### Scroll
324 |
325 | ```swift
326 | attributes.scroll = .disabled
327 | ```
328 |
329 | #### Selection Behavior
330 |
331 | ```swift
332 | //NEW - Single selection disallowing deselection clicking the same item
333 | attributes.multiSelect = .disabled(allowSingleDeselection: false)
334 | //NEW - Single selection allowing deselection clicking the same item
335 | attributes.multiSelect = .disabled(allowSingleDeselection: true)
336 | //Multi selection enable
337 | attributes.multiSelect = .enabled
338 | attributes.hideOptionsWhenSelect = .enabled
339 | ```
340 |
341 | #### Row Style
342 |
343 | ```swift
344 | attributes.rowStyle = .value(height: 40, backgroundColor: .white, selectedColor: .white)
345 | ```
346 |
347 | #### Frame Style
348 |
349 | ```swift
350 | /// Rounded Corners
351 | attributes.roundCorners = .all(radius: 8)
352 |
353 | /// Menu Maximum Height
354 | attributes.height = .value(height: 300)
355 |
356 | /// Menu Border
357 | attributes.border = .value(color: .gray, width: 0.5)
358 | ```
359 |
360 | #### Arrow Style
361 |
362 | ```swift
363 | /// `SwiftyMenu` have default arrow
364 | attributes.arrowStyle = .value(isEnabled: true)
365 | /// NEW - Now able to add a custom image, tint color and space between icon and text
366 | attributes.arrowStyle = .value(isEnabled: true, image: image, tintColor: .purple, spacingBetweenText: 10.0)
367 | ```
368 |
369 | #### Separator Style
370 |
371 | ```swift
372 | attributes.separatorStyle = .value(color: .black, isBlured: false, style: .singleLine)
373 | ```
374 |
375 | #### Header Style
376 |
377 | ```swift
378 | attributes.headerStyle = .value(backgroundColor: .white, height: 40)
379 | ```
380 |
381 | #### Accessory
382 |
383 | ```swift
384 | attributes.accessory = .disabled
385 | ```
386 |
387 | #### Animation
388 |
389 | ```swift
390 | attributes.expandingAnimation = .linear
391 | attributes.expandingTiming = .value(duration: 0.5, delay: 0)
392 |
393 | attributes.collapsingAnimation = .linear
394 | attributes.collapsingTiming = .value(duration: 0.5, delay: 0)
395 | ```
396 |
397 | #### Margin Horizontal
398 |
399 | ```swift
400 | /// Margin leading and trailing for title / placeholder
401 | attributes.titleMarginHorizontal = .value(leading: 10, trailing: 20)
402 | /// Margin leading and trailing for item list
403 | attributes.itemMarginHorizontal = .value(leading: 10, trailing: 10)
404 | ```
405 |
406 | #### Error Info
407 |
408 | ```swift
409 | /// Default use the .red color
410 | attributes.errorInfo = .default
411 | /// Custom color for placeholder and icon if it's enabled
412 | attributes.errorInfo = .custom(placeholderTextColor: .red, iconTintColor: .red)
413 | ```
414 |
415 | #### Set Error
416 |
417 | ```swift
418 | /// Enabling this will show the config in errorInfo
419 | dropDown.setError(hasError: true)
420 | ```
421 |
422 | ### New Functionalities
423 | * Custom arrow and color and space between arrow and title
424 | * Fix multi select title: showing now sorted by index
425 | * Margin leading and trailing for title and items
426 | * Handling deselection for multi and single selection
427 | * Seting Error with custom color for title and arrow
428 |
429 | ### Example Project
430 |
431 | You could check the full `Example` project [Here](https://github.com/KarimEbrahemAbdelaziz/SwiftyMenu/tree/master/Example).
432 |
433 | You could check the full new `Example` project [Here New Example](https://github.com/nowjordanhappy/SwiftyMenu/tree/feature/customization-ui/Example).
434 |
435 | ## TODO
436 |
437 | - [x] Automate release new version to Cocoapods from Github Actions.
438 | - [x] Add CHANGELOG file for the project.
439 | - [x] Allow custom header and options cells.
440 | - [ ] Allow different interactions to dismiss SwiftyMenu.
441 | - [x] Allow to customize the default seperator.
442 | - [x] Support Generic DataSource.
443 | - [x] Support multi selection in SwiftMenu 🔥.
444 | - [x] Support multi SwiftyMenu in one screen.
445 | - [x] Support stack view and add example.
446 | - [x] Support call backs and delegation.
447 | - [x] Support different types of Animations.
448 | - [x] Add different customization to colors for default cells.
449 | - [x] Margin horizontal for title and item
450 | - [x] Custom arrow and tint color
451 |
452 |
453 |
454 | And much more ideas to make it solid drop down menu for iOS projects 😎💪🏻
455 |
456 | ## Android
457 |
458 | - [ExpandableSelectionView](https://github.com/ashrafDoubleO7/ExpandableSelectionView), Thanks to [Ahmed Ashraf](https://github.com/ashrafDoubleO7) ❤️💪🏻
459 |
460 | ## Author
461 |
462 | Karim Ebrahem, karimabdelazizmansour@gmail.com
463 |
464 | ## License
465 |
466 | SwiftyMenu is available under the MIT license. See the `LICENSE` file for more info.
467 |
468 | ## Credits
469 |
470 | You can find me on Twitter [@k_ebrahem_](https://twitter.com/k_ebrahem_).
471 |
472 | It will be updated when necessary and fixes will be done as soon as discovered to keep it up to date.
473 |
474 | Enjoy!
475 |
--------------------------------------------------------------------------------
/Example/SwiftyMenu/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 |
30 |
31 |
32 |
33 |
34 |
35 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
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 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
--------------------------------------------------------------------------------
/SwiftyMenu/Classes/SwiftyMenu.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyMenu.swift
3 | //
4 | // Copyright (c) 2019-2024 Karim Ebrahem (https://www.linkedin.com/in/karimebrahem)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 | import UIKit
27 | import SnapKit
28 |
29 | /// `SwiftyMenu` is the menu class that provides common state, delegate, and callbacks handling.
30 | final public class SwiftyMenu: UIView {
31 |
32 | // MARK: - IBOutlets
33 |
34 | @IBOutlet public var heightConstraint: NSLayoutConstraint!
35 |
36 | // MARK: - Public Properties
37 |
38 | /// `selectedIndex` is a property to get and set selected item in `SwiftyMenu` when it is a Single Selection.
39 | public var selectedIndex: Int? {
40 | didSet {
41 | if selectedIndex == nil {
42 | setPlaceholder()
43 | } else {
44 | setSingleSelectedOption()
45 | }
46 | }
47 | }
48 |
49 | /// `selectedIndecis` is a property to get and set selected item in `SwiftyMenu` when it is a Multi Selection.
50 | public var selectedIndecis: [Int: Int] = [:] {
51 | didSet {
52 | setMultiSelectedOptions()
53 | }
54 | }
55 |
56 | /// `items` is the `SwiftyMenu` DataSource.
57 | ///
58 | /// The items that will appear in the `SwiftyMenu`.
59 | public var items = [SwiftyMenuDisplayable]() {
60 | didSet {
61 | self.itemsTableView.reloadData()
62 | }
63 | }
64 |
65 | /// `delegate` is the `SwiftyMenu` delegate property.
66 | public weak var delegate: SwiftyMenuDelegate?
67 |
68 | /// `separatorCharacters` is a property to get and set separator characters in `SwiftyMenu` when it is a Multi Selection.
69 | public var separatorCharacters: String?
70 |
71 | // MARK: - Public Callbacks
72 |
73 | /// `willExpand` is a completion closure that will be executed when `SwiftyMenu` is going to expand.
74 | public var willExpand: (() -> Void) = { }
75 |
76 | /// `didExpand` is a completion closure that will be executed once `SwiftyMenu` expanded.
77 | public var didExpand: (() -> Void) = { }
78 |
79 | /// `willCollapse` is a completion closure that will be executed when `SwiftyMenu` is going to collapse.
80 | public var willCollapse: (() -> Void) = { }
81 |
82 | /// `didCollapse` is a completion closure that will be executed once `SwiftyMenu` collapsed.
83 | public var didCollapse: (() -> Void) = { }
84 |
85 | /// `didSelectItem` is a completion closure that will be executed when select an item from `SwiftyMenu`.
86 | /// - Parameters:
87 | /// - swiftyMenu: The `SwiftyMenu` that was selected from it's items.
88 | /// - item: The `Item` that had been selected from `SwiftyMenu`.
89 | /// - index: The `Index` of the selected `Item`.
90 | public var didSelectItem: ((_ swiftyMenu: SwiftyMenu,_ item: SwiftyMenuDisplayable,_ index: Int) -> Void) = { _, _, _ in }
91 |
92 | /// `didDselectItem` is a completion closure that will be executed when deselect an item from `SwiftyMenu`.
93 | /// - Parameters:
94 | /// - swiftyMenu: The `SwiftyMenu` that was deselected from it's items.
95 | /// - item: The `Item` that had been deselected from `SwiftyMenu`.
96 | /// - index: The `Index` of the deselected `Item`.
97 | public var didDeselectItem: ((_ swiftyMenu: SwiftyMenu,_ item: SwiftyMenuDisplayable,_ index: Int) -> Void) = { _, _, _ in }
98 |
99 | // MARK: - Private Properties
100 |
101 | private var selectButton: UIButton!
102 | private var itemsTableView: UITableView!
103 | private var state: SwiftyMenuState = .hidden
104 | private var width: CGFloat!
105 | private var height: CGFloat!
106 | private var setuped: Bool = false
107 | private var attributes: SwiftyMenuAttributes!
108 | private var hasError: Bool = false
109 |
110 | // MARK: - Init
111 |
112 | public override init(frame: CGRect) {
113 | super.init(frame: frame)
114 |
115 | selectButton = UIButton(frame: self.frame)
116 | itemsTableView = UITableView()
117 | }
118 |
119 | public required init(coder aDecoder: NSCoder) {
120 | super.init(coder: aDecoder)!
121 |
122 | selectButton = UIButton(frame: self.frame)
123 | itemsTableView = UITableView()
124 | }
125 |
126 | // MARK: - LifeCycle
127 |
128 | public override func layoutSubviews() {
129 | super.layoutSubviews()
130 |
131 | if !setuped {
132 | setupUI()
133 | setuped = true
134 | changeTintColorArrow(hasError: hasError)
135 | }
136 | }
137 |
138 | // MARK: - Public Funcitons
139 |
140 | /// Configure `SwiftyMenu` with attributes.
141 | public func configure(with attributes: SwiftyMenuAttributes) {
142 | self.attributes = attributes
143 | }
144 |
145 | /// Expand or Collapse `SwiftyMenu` from Code.
146 | public func toggle() {
147 | handleMenuState()
148 | }
149 |
150 | /// change style for Error from Code.
151 | public func setError(hasError: Bool){
152 | self.hasError = hasError
153 | if attributes.multiSelect.isEnabled {
154 | if selectedIndecis.isEmpty {
155 | selectButton.setTitleColor(hasError ? attributes.errorInfo.errorInfoValues.placeholderTextColor : attributes.placeHolderStyle.placeHolderValues.textColor, for: .normal)
156 | } else {
157 | selectButton.setTitleColor(hasError ? attributes.errorInfo.errorInfoValues.placeholderTextColor : attributes.textStyle.textStyleValues.color, for: .normal)
158 | }
159 | } else {
160 | if selectedIndex == nil {
161 | selectButton.setTitleColor(hasError ? attributes.errorInfo.errorInfoValues.placeholderTextColor : attributes.placeHolderStyle.placeHolderValues.textColor, for: .normal)
162 | } else {
163 | selectButton.setTitleColor(hasError ? attributes.errorInfo.errorInfoValues.placeholderTextColor : attributes.textStyle.textStyleValues.color, for: .normal)
164 | }
165 | }
166 |
167 | changeTintColorArrow(hasError: hasError)
168 | }
169 |
170 | private func changeTintColorArrow(hasError: Bool){
171 | if attributes.arrowStyle.arrowStyleValues.isEnabled {
172 | if let image = attributes.arrowStyle.arrowStyleValues.image{
173 | if let tintColor = attributes.arrowStyle.arrowStyleValues.tintColor{
174 | selectButton.setImage(image.withRenderingMode(.alwaysTemplate), for: .normal)
175 | selectButton.tintColor = (hasError ? (attributes.errorInfo.errorInfoValues.iconTintColor ?? tintColor) : tintColor)
176 | }else{
177 | if hasError, let errorColor = attributes.errorInfo.errorInfoValues.iconTintColor{
178 | selectButton.setImage(image.withRenderingMode(.alwaysTemplate), for: .normal)
179 | selectButton.tintColor = (errorColor)
180 | }else{
181 | selectButton.tintColor = nil
182 | selectButton.setImage(image.withRenderingMode(.alwaysOriginal), for: .normal)
183 | }
184 | }
185 | }
186 | selectButton.layoutIfNeeded()
187 | }
188 | }
189 | }
190 |
191 | // MARK: - UITableViewDataSource Functions
192 |
193 | extension SwiftyMenu: UITableViewDataSource {
194 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
195 | return items.count
196 | }
197 |
198 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
199 |
200 | let cell = tableView.dequeueReusableCell(withIdentifier: "OptionCell", for: indexPath) as! SwiftyMenuCell
201 | cell.textLabel?.text = items[indexPath.row].displayableValue
202 | cell.textLabel?.textColor = attributes.textStyle.textStyleValues.color
203 | cell.textLabel?.font = attributes.textStyle.textStyleValues.font
204 | cell.textLabel?.textAlignment = attributes.textStyle.textStyleValues.alignment
205 | cell.tintColor = attributes.textStyle.textStyleValues.color
206 | cell.backgroundColor = attributes.rowStyle.rowStyleValues.backgroundColor
207 | cell.selectionStyle = .none
208 | cell.leftMargin = attributes.itemMarginHorizontal.leadingValue
209 | cell.rightMargin = attributes.itemMarginHorizontal.trailingValue
210 | cell.isContentRightToLeft = self.isContentRightToLeft()
211 |
212 | if attributes.multiSelect.isEnabled {
213 | if selectedIndecis[indexPath.row] != nil {
214 | cell.textLabel?.textColor = attributes.textStyle.textStyleValues.selectedColor
215 | cell.accessoryType = attributes.accessory.isEnabled ? .checkmark : .none
216 | cell.backgroundColor = attributes.rowStyle.rowStyleValues.selectedColor
217 | } else {
218 | cell.accessoryType = .none
219 | }
220 | } else {
221 | if indexPath.row == selectedIndex {
222 | cell.textLabel?.textColor = attributes.textStyle.textStyleValues.selectedColor
223 | cell.accessoryType = attributes.accessory.isEnabled ? .checkmark : .none
224 | cell.backgroundColor = attributes.rowStyle.rowStyleValues.selectedColor
225 | } else {
226 | cell.accessoryType = .none
227 | }
228 | }
229 |
230 |
231 | return cell
232 | }
233 | }
234 |
235 | // MARK: - UITableViewDelegate Functions
236 |
237 | extension SwiftyMenu: UITableViewDelegate {
238 | public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
239 | return CGFloat(attributes.rowStyle.rowStyleValues.height)
240 | }
241 |
242 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
243 |
244 | if attributes.multiSelect.isEnabled {
245 | if selectedIndecis[indexPath.row] != nil {
246 | let deselectedText = self.items[selectedIndecis[indexPath.row]!]
247 | delegate?.swiftyMenu(self, didDeselectItem: deselectedText, atIndex: indexPath.row)
248 | self.didDeselectItem(self, deselectedText, indexPath.row)
249 | selectedIndecis[indexPath.row] = nil
250 | setSelectedOptionsAsTitle()
251 | tableView.reloadData()
252 | if attributes.hideOptionsWhenSelect.isEnabled {
253 | handleMenuState()
254 | }
255 | } else {
256 | //clean arrow color for error
257 | if hasError && selectedIndecis.isEmpty{
258 | hasError = false
259 | changeTintColorArrow(hasError: hasError)
260 | }
261 | selectedIndecis[indexPath.row] = indexPath.row
262 | setSelectedOptionsAsTitle()
263 | let selectedText = self.items[selectedIndecis[indexPath.row]!]
264 | delegate?.swiftyMenu(self, didSelectItem: selectedText, atIndex: indexPath.row)
265 | self.didSelectItem(self, selectedText, indexPath.row)
266 | tableView.reloadData()
267 | if attributes.hideOptionsWhenSelect.isEnabled {
268 | handleMenuState()
269 | }
270 | }
271 | } else {
272 | if selectedIndex == indexPath.row {
273 | switch attributes.multiSelect{
274 | case .disabled(let allowSingleDeselection):
275 | if allowSingleDeselection{
276 | let deselectedText = self.items[self.selectedIndex!]
277 | delegate?.swiftyMenu(self, didDeselectItem: deselectedText, atIndex: indexPath.row)
278 | self.didDeselectItem(self, deselectedText, indexPath.row)
279 | selectedIndex = nil
280 | setSelectedOptionsAsTitle()
281 | tableView.reloadData()
282 | }
283 | default:
284 | break
285 | }
286 |
287 | if attributes.hideOptionsWhenSelect.isEnabled {
288 | handleMenuState()
289 | }
290 | } else {
291 | //clean arrow color for error
292 | if hasError && selectedIndex == nil{
293 | hasError = false
294 | changeTintColorArrow(hasError: hasError)
295 | }
296 | selectedIndex = indexPath.row
297 | setSelectedOptionsAsTitle()
298 | let selectedText = self.items[self.selectedIndex!]
299 | delegate?.swiftyMenu(self, didSelectItem: selectedText, atIndex: indexPath.row)
300 | self.didSelectItem(self, selectedText, indexPath.row)
301 | tableView.reloadData()
302 | if attributes.hideOptionsWhenSelect.isEnabled {
303 | handleMenuState()
304 | }
305 | }
306 | }
307 | }
308 | }
309 |
310 | // MARK: - Setup SwiftyMenu Views Functions
311 |
312 | extension SwiftyMenu {
313 | private func setupUI () {
314 | setupView()
315 | getViewWidth()
316 | getViewHeight()
317 | setupSelectButton()
318 | setupDataTableView()
319 | setupSeparatorStyle()
320 | }
321 |
322 | public func setupSeparatorStyle() {
323 | itemsTableView.separatorStyle = attributes.separatorStyle.separatorStyleValues.style
324 | if attributes.separatorStyle.separatorStyleValues.isBlured {
325 | itemsTableView.separatorEffect = UIBlurEffect()
326 | }
327 | itemsTableView.separatorColor = attributes.separatorStyle.separatorStyleValues.color
328 | }
329 |
330 | private func isContentRightToLeft() -> Bool{
331 | return UIView.userInterfaceLayoutDirection(for: selectButton.semanticContentAttribute) == .rightToLeft
332 | }
333 |
334 | private func setupView() {
335 | clipsToBounds = true
336 | layer.cornerRadius = attributes.roundCorners.cornerValue ?? 0
337 | layer.borderWidth = attributes.border.borderValues?.width ?? 0
338 | layer.borderColor = attributes.border.borderValues?.color.cgColor
339 | }
340 |
341 | private func setupSelectButton() {
342 | self.addSubview(selectButton)
343 |
344 | selectButton.snp.makeConstraints { maker in
345 | maker.leading.trailing.top.equalTo(self)
346 | maker.height.equalTo(height)
347 | }
348 |
349 | let color = attributes.placeHolderStyle.placeHolderValues.textColor
350 | selectButton.setTitleColor(color, for: .normal)
351 | UIView.performWithoutAnimation {
352 | selectButton.setTitle(attributes.placeHolderStyle.placeHolderValues.text, for: .normal)
353 | selectButton.layoutIfNeeded()
354 | }
355 |
356 | let isContentRightToLeft = isContentRightToLeft()
357 |
358 | let lineBreakMode: NSLineBreakMode = isContentRightToLeft ? .byTruncatingHead : .byTruncatingTail
359 | let isArrowEnable: Bool = attributes.arrowStyle.arrowStyleValues.isEnabled
360 | let arrow = isArrowEnable ? attributes.arrowStyle.arrowStyleValues.image : nil
361 | let leftPadding = isContentRightToLeft ? attributes.titleMarginHorizontal.trailingValue : attributes.titleMarginHorizontal.leadingValue
362 | let rightPadding = isContentRightToLeft ? attributes.titleMarginHorizontal.leadingValue : attributes.titleMarginHorizontal.trailingValue
363 | let font = self.attributes.textStyle.textStyleValues.font
364 | let placeholderTextColor = attributes.placeHolderStyle.placeHolderValues.textColor
365 | let spacingBetweenText = isArrowEnable ? attributes.arrowStyle.arrowStyleValues.spacingBetweenText : 0.0
366 |
367 | selectButton.backgroundColor = attributes.headerStyle.headerStyleValues.backgroundColor
368 |
369 |
370 | selectButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
371 |
372 | if #available(iOS 15.0, *) {
373 | var btnConfig = UIButton.Configuration.plain()
374 | btnConfig.titleAlignment = .leading
375 | btnConfig.imagePlacement = .trailing
376 | btnConfig.image = arrow
377 | btnConfig.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: leftPadding, bottom: 0, trailing: rightPadding - 2)
378 | btnConfig.imagePadding = spacingBetweenText //spacing between image and icon
379 | btnConfig.image = arrow
380 | btnConfig.titleLineBreakMode = lineBreakMode
381 | btnConfig.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { incoming in
382 | var outgoing = incoming
383 | outgoing.font = font
384 | return outgoing
385 | }
386 | selectButton.configuration = btnConfig
387 | selectButton.translatesAutoresizingMaskIntoConstraints = false
388 | selectButton.contentMode = .scaleAspectFit
389 | selectButton.contentHorizontalAlignment = (isArrowEnable ? .fill : .leading)
390 | selectButton.clipsToBounds = true
391 | selectButton.setTitleColor(color, for: .normal)
392 | }else{
393 | if #available(iOS 11.0, *) {
394 | selectButton.contentHorizontalAlignment = .leading
395 | } else {
396 | selectButton.contentHorizontalAlignment = .left
397 | }
398 |
399 | selectButton.titleEdgeInsets = UIEdgeInsets(top: 0, left: leftPadding, bottom: 0, right: rightPadding)
400 | selectButton.titleLabel?.lineBreakMode = lineBreakMode
401 | selectButton.titleLabel?.font = font
402 | selectButton.setTitleColor(placeholderTextColor, for: .normal)
403 |
404 | self.selectButton?.imageView?.contentMode = .scaleAspectFit
405 |
406 | self.selectButton.setImage(arrow, for: .normal)
407 |
408 | let imageWidth: CGFloat = isArrowEnable ? (arrow?.size.width ?? .zero) : 0
409 |
410 |
411 | var adjustImageEdgeInsets: UIEdgeInsets = .zero
412 | var adjustTitleEdgeInsets: UIEdgeInsets = .zero
413 |
414 | let arrowRight = rightPadding + imageWidth
415 |
416 | if isContentRightToLeft {
417 | adjustImageEdgeInsets.left = leftPadding
418 | adjustImageEdgeInsets.right = width - arrowRight - spacingBetweenText
419 |
420 | adjustTitleEdgeInsets.left = arrowRight + spacingBetweenText
421 | adjustTitleEdgeInsets.right = -imageWidth + rightPadding
422 | }else{
423 | adjustImageEdgeInsets.left = width - arrowRight
424 | adjustImageEdgeInsets.right = rightPadding
425 |
426 | adjustTitleEdgeInsets.left = -imageWidth + leftPadding
427 | adjustTitleEdgeInsets.right = arrowRight + spacingBetweenText
428 | }
429 |
430 |
431 | self.selectButton.imageEdgeInsets = adjustImageEdgeInsets
432 | self.selectButton.titleEdgeInsets = adjustTitleEdgeInsets
433 |
434 | }
435 |
436 | selectButton.contentHorizontalAlignment = attributes.headerStyle.headerStyleValues.contentHorizontalAlignment
437 | selectButton.addTarget(self, action: #selector(handleMenuState), for: .touchUpInside)
438 | }
439 |
440 | private func setupDataTableView() {
441 | self.addSubview(itemsTableView)
442 |
443 | itemsTableView.snp.makeConstraints { maker in
444 | maker.leading.trailing.bottom.equalTo(self)
445 | maker.top.equalTo(selectButton.snp.bottom)
446 | }
447 |
448 | itemsTableView.delegate = self
449 | itemsTableView.dataSource = self
450 | itemsTableView.rowHeight = CGFloat(attributes.rowStyle.rowStyleValues.height)
451 | itemsTableView.separatorInset.left = 0
452 | itemsTableView.separatorInset.right = 0
453 | itemsTableView.backgroundColor = attributes.rowStyle.rowStyleValues.backgroundColor
454 | itemsTableView.isScrollEnabled = attributes.scroll.isEnabled
455 | itemsTableView.register(SwiftyMenuCell.self, forCellReuseIdentifier: "OptionCell")
456 | itemsTableView.showsVerticalScrollIndicator = false
457 | }
458 |
459 | @objc private func handleMenuState() {
460 | switch self.state {
461 | case .shown:
462 | collapseSwiftyMenu()
463 | case .hidden:
464 | expandSwiftyMenu()
465 | }
466 | state.toggle()
467 | }
468 | }
469 |
470 | // MARK: - Private Functions
471 |
472 | extension SwiftyMenu {
473 | private func getViewWidth() {
474 | width = self.frame.width
475 | }
476 |
477 | private func getViewHeight() {
478 | height = self.frame.height
479 | }
480 |
481 | private func setMultiSelectedOptions() {
482 | let sortedSelectedIndecis = selectedIndecis.sorted { $0.key < $1.key }
483 | let titles = sortedSelectedIndecis.map { (index, _) -> String in
484 | return items[index].displayableValue
485 | }
486 | var selectedTitle = ""
487 | selectedTitle = titles.joined(separator: separatorCharacters ?? ", ")
488 | UIView.performWithoutAnimation {
489 | selectButton.setTitle(selectedTitle, for: .normal)
490 | selectButton.layoutIfNeeded()
491 | }
492 | selectButton.setTitleColor(attributes.textStyle.textStyleValues.color, for: .normal)
493 | }
494 |
495 | private func setSingleSelectedOption() {
496 | UIView.performWithoutAnimation {
497 | selectButton.setTitle(items[selectedIndex!].displayableValue, for: .normal)
498 | selectButton.layoutIfNeeded()
499 | }
500 | selectButton.setTitleColor(attributes.textStyle.textStyleValues.color, for: .normal)
501 | }
502 |
503 | private func setPlaceholder() {
504 | UIView.performWithoutAnimation {
505 | selectButton.setTitle(attributes.placeHolderStyle.placeHolderValues.text, for: .normal)
506 | selectButton.layoutIfNeeded()
507 | }
508 | selectButton.setTitleColor(attributes.placeHolderStyle.placeHolderValues.textColor, for: .normal)
509 | }
510 |
511 | private func setSelectedOptionsAsTitle() {
512 | if attributes.multiSelect.isEnabled {
513 | if selectedIndecis.isEmpty {
514 | setPlaceholder()
515 | } else {
516 | setMultiSelectedOptions()
517 | }
518 | } else {
519 | if selectedIndex == nil {
520 | setPlaceholder()
521 | } else {
522 | setSingleSelectedOption()
523 | }
524 | }
525 | }
526 | }
527 |
528 | // MARK: - SwiftyMenu Expand and Collapse Functions
529 |
530 | extension SwiftyMenu {
531 | /// Called to Expand `SwiftyMenu`.
532 | private func expandSwiftyMenu() {
533 | delegate?.swiftyMenu(willExpand: self)
534 | self.willExpand()
535 | heightConstraint.constant = attributes.height.listHeightValue == 0 || !attributes.scroll.isEnabled || (CGFloat(Double(attributes.rowStyle.rowStyleValues.height) * Double(items.count + 1)) < CGFloat(attributes.height.listHeightValue)) ? CGFloat(Double(attributes.rowStyle.rowStyleValues.height) * Double(items.count + 1)) : CGFloat(attributes.height.listHeightValue)
536 |
537 | switch attributes.expandingAnimation {
538 | case .linear:
539 | UIView.animate(withDuration: attributes.expandingTiming.animationTimingValues.duration,
540 | delay: attributes.expandingTiming.animationTimingValues.delay,
541 | animations: animationBlock,
542 | completion: expandingAnimationCompletionBlock)
543 |
544 | case .spring(level: let powerLevel):
545 | let damping = CGFloat(0.5 / powerLevel.rawValue)
546 | let initialVelocity = CGFloat(0.5 * powerLevel.rawValue)
547 |
548 | UIView.animate(withDuration: attributes.expandingTiming.animationTimingValues.duration,
549 | delay: attributes.expandingTiming.animationTimingValues.delay,
550 | usingSpringWithDamping: damping,
551 | initialSpringVelocity: initialVelocity,
552 | options: [],
553 | animations: animationBlock,
554 | completion: expandingAnimationCompletionBlock)
555 | }
556 | }
557 |
558 | /// Called to Collapse `SwiftyMenu`.
559 | private func collapseSwiftyMenu() {
560 | delegate?.swiftyMenu(willCollapse: self)
561 | self.willCollapse()
562 | heightConstraint.constant = CGFloat(attributes.headerStyle.headerStyleValues.height)
563 |
564 | switch attributes.collapsingAnimation {
565 | case .linear:
566 | UIView.animate(withDuration: attributes.collapsingTiming.animationTimingValues.duration,
567 | delay: attributes.collapsingTiming.animationTimingValues.delay,
568 | animations: animationBlock,
569 | completion: collapsingAnimationCompletionBlock)
570 |
571 | case .spring(level: let powerLevel):
572 | let damping = CGFloat(1.0 * powerLevel.rawValue)
573 | let initialVelocity = CGFloat(10.0 * powerLevel.rawValue)
574 |
575 | UIView.animate(withDuration: attributes.collapsingTiming.animationTimingValues.duration,
576 | delay: attributes.collapsingTiming.animationTimingValues.delay,
577 | usingSpringWithDamping: damping,
578 | initialSpringVelocity: initialVelocity,
579 | options: .curveEaseIn,
580 | animations: animationBlock,
581 | completion: collapsingAnimationCompletionBlock)
582 | }
583 | }
584 | }
585 |
586 | // MARK: - SwiftyMenu Animation Functions
587 |
588 | extension SwiftyMenu {
589 | private func animationBlock() {
590 | if attributes.arrowStyle.arrowStyleValues.isEnabled {
591 | self.selectButton.imageView?.transform = self.selectButton.imageView!.transform.rotated(by: CGFloat.pi)
592 | }
593 | self.parentViewController?.view.layoutIfNeeded()
594 | }
595 |
596 | private func expandingAnimationCompletionBlock(didAppeared: Bool) {
597 | if didAppeared {
598 | self.delegate?.swiftyMenu(didExpand: self)
599 | self.didExpand()
600 | }
601 | }
602 |
603 | private func collapsingAnimationCompletionBlock(didAppeared: Bool) {
604 | if didAppeared {
605 | self.delegate?.swiftyMenu(didCollapse: self)
606 | self.didCollapse()
607 | }
608 | }
609 | }
610 |
--------------------------------------------------------------------------------
/Example/SwiftyMenu.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 4B8AD907BB006589A259B86C /* Pods_SwiftyMenu_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5554E47DC28F7043A9445538 /* Pods_SwiftyMenu_Tests.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 | B7E5D6153C7C62E389FF49F9 /* Pods_SwiftyMenu_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD0DD0B626C9414217DC8F17 /* Pods_SwiftyMenu_Example.framework */; };
17 | BC4F88DD22802E050056082A /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = BC4F88DC22802E050056082A /* CHANGELOG.md */; };
18 | BC7A3004242BF8BD00B76276 /* SwiftyMenuTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC7A3003242BF8BD00B76276 /* SwiftyMenuTests.swift */; };
19 | BC7A3009242BF9B900B76276 /* SwiftyMenuDelegateSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC7A3007242BF9B400B76276 /* SwiftyMenuDelegateSpy.swift */; };
20 | BC8E4C6A2690AD4700029145 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC8E4C692690AD4600029145 /* String+Extensions.swift */; };
21 | BC8E4C6C2690AD7C00029145 /* MealSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC8E4C6B2690AD7C00029145 /* MealSize.swift */; };
22 | /* End PBXBuildFile section */
23 |
24 | /* Begin PBXContainerItemProxy section */
25 | 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = {
26 | isa = PBXContainerItemProxy;
27 | containerPortal = 607FACC81AFB9204008FA782 /* Project object */;
28 | proxyType = 1;
29 | remoteGlobalIDString = 607FACCF1AFB9204008FA782;
30 | remoteInfo = SwiftyMenu;
31 | };
32 | /* End PBXContainerItemProxy section */
33 |
34 | /* Begin PBXFileReference section */
35 | 1C898DFC3115A1762BA35E9F /* Pods-SwiftyMenu_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftyMenu_Tests.debug.xcconfig"; path = "Target Support Files/Pods-SwiftyMenu_Tests/Pods-SwiftyMenu_Tests.debug.xcconfig"; sourceTree = ""; };
36 | 3E32D178976FCFD2A66D351A /* SwiftyMenu.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = SwiftyMenu.podspec; path = ../SwiftyMenu.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
37 | 4D040975F688722B5C186CF7 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; };
38 | 5554E47DC28F7043A9445538 /* Pods_SwiftyMenu_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftyMenu_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
39 | 607FACD01AFB9204008FA782 /* SwiftyMenu_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftyMenu_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
40 | 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
41 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
42 | 607FACD71AFB9204008FA782 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
43 | 607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
44 | 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
45 | 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; };
46 | 607FACE51AFB9204008FA782 /* SwiftyMenu_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftyMenu_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
47 | 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
48 | 62EA56F689738FB56657BC80 /* Pods-SwiftyMenu_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftyMenu_Example.debug.xcconfig"; path = "Target Support Files/Pods-SwiftyMenu_Example/Pods-SwiftyMenu_Example.debug.xcconfig"; sourceTree = ""; };
49 | 7A003F545CFB45340C03F42C /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; };
50 | 808EB17321FD09CF60EA98B2 /* Pods-SwiftyMenu_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftyMenu_Tests.release.xcconfig"; path = "Target Support Files/Pods-SwiftyMenu_Tests/Pods-SwiftyMenu_Tests.release.xcconfig"; sourceTree = ""; };
51 | 97D36CF7E7BAEC32980EFB93 /* Pods-SwiftyMenu_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftyMenu_Example.release.xcconfig"; path = "Target Support Files/Pods-SwiftyMenu_Example/Pods-SwiftyMenu_Example.release.xcconfig"; sourceTree = ""; };
52 | BC4F88DC22802E050056082A /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = CHANGELOG.md; path = ../CHANGELOG.md; sourceTree = ""; };
53 | BC7A3003242BF8BD00B76276 /* SwiftyMenuTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyMenuTests.swift; sourceTree = ""; };
54 | BC7A3007242BF9B400B76276 /* SwiftyMenuDelegateSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyMenuDelegateSpy.swift; sourceTree = ""; };
55 | BC8E4C692690AD4600029145 /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = ""; };
56 | BC8E4C6B2690AD7C00029145 /* MealSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MealSize.swift; sourceTree = ""; };
57 | DD0DD0B626C9414217DC8F17 /* Pods_SwiftyMenu_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftyMenu_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
58 | /* End PBXFileReference section */
59 |
60 | /* Begin PBXFrameworksBuildPhase section */
61 | 607FACCD1AFB9204008FA782 /* Frameworks */ = {
62 | isa = PBXFrameworksBuildPhase;
63 | buildActionMask = 2147483647;
64 | files = (
65 | B7E5D6153C7C62E389FF49F9 /* Pods_SwiftyMenu_Example.framework in Frameworks */,
66 | );
67 | runOnlyForDeploymentPostprocessing = 0;
68 | };
69 | 607FACE21AFB9204008FA782 /* Frameworks */ = {
70 | isa = PBXFrameworksBuildPhase;
71 | buildActionMask = 2147483647;
72 | files = (
73 | 4B8AD907BB006589A259B86C /* Pods_SwiftyMenu_Tests.framework in Frameworks */,
74 | );
75 | runOnlyForDeploymentPostprocessing = 0;
76 | };
77 | /* End PBXFrameworksBuildPhase section */
78 |
79 | /* Begin PBXGroup section */
80 | 3180438BB32DAAD8409A8F19 /* Frameworks */ = {
81 | isa = PBXGroup;
82 | children = (
83 | DD0DD0B626C9414217DC8F17 /* Pods_SwiftyMenu_Example.framework */,
84 | 5554E47DC28F7043A9445538 /* Pods_SwiftyMenu_Tests.framework */,
85 | );
86 | name = Frameworks;
87 | sourceTree = "";
88 | };
89 | 425F0E06115A3B7EB0798B09 /* Pods */ = {
90 | isa = PBXGroup;
91 | children = (
92 | 62EA56F689738FB56657BC80 /* Pods-SwiftyMenu_Example.debug.xcconfig */,
93 | 97D36CF7E7BAEC32980EFB93 /* Pods-SwiftyMenu_Example.release.xcconfig */,
94 | 1C898DFC3115A1762BA35E9F /* Pods-SwiftyMenu_Tests.debug.xcconfig */,
95 | 808EB17321FD09CF60EA98B2 /* Pods-SwiftyMenu_Tests.release.xcconfig */,
96 | );
97 | path = Pods;
98 | sourceTree = "";
99 | };
100 | 607FACC71AFB9204008FA782 = {
101 | isa = PBXGroup;
102 | children = (
103 | 607FACF51AFB993E008FA782 /* Podspec Metadata */,
104 | 607FACD21AFB9204008FA782 /* Example for SwiftyMenu */,
105 | 607FACE81AFB9204008FA782 /* Tests */,
106 | 607FACD11AFB9204008FA782 /* Products */,
107 | 425F0E06115A3B7EB0798B09 /* Pods */,
108 | 3180438BB32DAAD8409A8F19 /* Frameworks */,
109 | );
110 | sourceTree = "";
111 | };
112 | 607FACD11AFB9204008FA782 /* Products */ = {
113 | isa = PBXGroup;
114 | children = (
115 | 607FACD01AFB9204008FA782 /* SwiftyMenu_Example.app */,
116 | 607FACE51AFB9204008FA782 /* SwiftyMenu_Tests.xctest */,
117 | );
118 | name = Products;
119 | sourceTree = "";
120 | };
121 | 607FACD21AFB9204008FA782 /* Example for SwiftyMenu */ = {
122 | isa = PBXGroup;
123 | children = (
124 | BC8E4C682690AD3600029145 /* Extensions */,
125 | BC8E4C672690AD2800029145 /* Models */,
126 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */,
127 | 607FACD71AFB9204008FA782 /* ViewController.swift */,
128 | 607FACD91AFB9204008FA782 /* Main.storyboard */,
129 | 607FACDC1AFB9204008FA782 /* Images.xcassets */,
130 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */,
131 | 607FACD31AFB9204008FA782 /* Supporting Files */,
132 | );
133 | name = "Example for SwiftyMenu";
134 | path = SwiftyMenu;
135 | sourceTree = "";
136 | };
137 | 607FACD31AFB9204008FA782 /* Supporting Files */ = {
138 | isa = PBXGroup;
139 | children = (
140 | 607FACD41AFB9204008FA782 /* Info.plist */,
141 | );
142 | name = "Supporting Files";
143 | sourceTree = "";
144 | };
145 | 607FACE81AFB9204008FA782 /* Tests */ = {
146 | isa = PBXGroup;
147 | children = (
148 | BC7A3006242BF9AA00B76276 /* Spies */,
149 | 607FACE91AFB9204008FA782 /* Supporting Files */,
150 | BC7A3003242BF8BD00B76276 /* SwiftyMenuTests.swift */,
151 | );
152 | path = Tests;
153 | sourceTree = "";
154 | };
155 | 607FACE91AFB9204008FA782 /* Supporting Files */ = {
156 | isa = PBXGroup;
157 | children = (
158 | 607FACEA1AFB9204008FA782 /* Info.plist */,
159 | );
160 | name = "Supporting Files";
161 | sourceTree = "";
162 | };
163 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = {
164 | isa = PBXGroup;
165 | children = (
166 | BC4F88DC22802E050056082A /* CHANGELOG.md */,
167 | 3E32D178976FCFD2A66D351A /* SwiftyMenu.podspec */,
168 | 7A003F545CFB45340C03F42C /* README.md */,
169 | 4D040975F688722B5C186CF7 /* LICENSE */,
170 | );
171 | name = "Podspec Metadata";
172 | sourceTree = "";
173 | };
174 | BC7A3006242BF9AA00B76276 /* Spies */ = {
175 | isa = PBXGroup;
176 | children = (
177 | BC7A3007242BF9B400B76276 /* SwiftyMenuDelegateSpy.swift */,
178 | );
179 | name = Spies;
180 | sourceTree = "";
181 | };
182 | BC8E4C672690AD2800029145 /* Models */ = {
183 | isa = PBXGroup;
184 | children = (
185 | BC8E4C6B2690AD7C00029145 /* MealSize.swift */,
186 | );
187 | name = Models;
188 | sourceTree = "";
189 | };
190 | BC8E4C682690AD3600029145 /* Extensions */ = {
191 | isa = PBXGroup;
192 | children = (
193 | BC8E4C692690AD4600029145 /* String+Extensions.swift */,
194 | );
195 | name = Extensions;
196 | sourceTree = "";
197 | };
198 | /* End PBXGroup section */
199 |
200 | /* Begin PBXNativeTarget section */
201 | 607FACCF1AFB9204008FA782 /* SwiftyMenu_Example */ = {
202 | isa = PBXNativeTarget;
203 | buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SwiftyMenu_Example" */;
204 | buildPhases = (
205 | 04AD95354171211AE70D72D8 /* [CP] Check Pods Manifest.lock */,
206 | 607FACCC1AFB9204008FA782 /* Sources */,
207 | 607FACCD1AFB9204008FA782 /* Frameworks */,
208 | 607FACCE1AFB9204008FA782 /* Resources */,
209 | 3736782BD9248E9D776F5581 /* [CP] Embed Pods Frameworks */,
210 | );
211 | buildRules = (
212 | );
213 | dependencies = (
214 | );
215 | name = SwiftyMenu_Example;
216 | productName = SwiftyMenu;
217 | productReference = 607FACD01AFB9204008FA782 /* SwiftyMenu_Example.app */;
218 | productType = "com.apple.product-type.application";
219 | };
220 | 607FACE41AFB9204008FA782 /* SwiftyMenu_Tests */ = {
221 | isa = PBXNativeTarget;
222 | buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SwiftyMenu_Tests" */;
223 | buildPhases = (
224 | B0B8349DDFC68D696EEFDFEB /* [CP] Check Pods Manifest.lock */,
225 | 607FACE11AFB9204008FA782 /* Sources */,
226 | 607FACE21AFB9204008FA782 /* Frameworks */,
227 | 607FACE31AFB9204008FA782 /* Resources */,
228 | );
229 | buildRules = (
230 | );
231 | dependencies = (
232 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */,
233 | );
234 | name = SwiftyMenu_Tests;
235 | productName = Tests;
236 | productReference = 607FACE51AFB9204008FA782 /* SwiftyMenu_Tests.xctest */;
237 | productType = "com.apple.product-type.bundle.unit-test";
238 | };
239 | /* End PBXNativeTarget section */
240 |
241 | /* Begin PBXProject section */
242 | 607FACC81AFB9204008FA782 /* Project object */ = {
243 | isa = PBXProject;
244 | attributes = {
245 | DefaultBuildSystemTypeForWorkspace = Original;
246 | LastSwiftUpdateCheck = 1130;
247 | LastUpgradeCheck = 1320;
248 | ORGANIZATIONNAME = CocoaPods;
249 | TargetAttributes = {
250 | 607FACCF1AFB9204008FA782 = {
251 | CreatedOnToolsVersion = 6.3.1;
252 | DevelopmentTeam = 89T7S3AF6B;
253 | LastSwiftMigration = 1020;
254 | };
255 | 607FACE41AFB9204008FA782 = {
256 | CreatedOnToolsVersion = 6.3.1;
257 | DevelopmentTeam = 89T7S3AF6B;
258 | LastSwiftMigration = 1020;
259 | TestTargetID = 607FACCF1AFB9204008FA782;
260 | };
261 | };
262 | };
263 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "SwiftyMenu" */;
264 | compatibilityVersion = "Xcode 3.2";
265 | developmentRegion = en;
266 | hasScannedForEncodings = 0;
267 | knownRegions = (
268 | en,
269 | Base,
270 | );
271 | mainGroup = 607FACC71AFB9204008FA782;
272 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */;
273 | projectDirPath = "";
274 | projectRoot = "";
275 | targets = (
276 | 607FACCF1AFB9204008FA782 /* SwiftyMenu_Example */,
277 | 607FACE41AFB9204008FA782 /* SwiftyMenu_Tests */,
278 | );
279 | };
280 | /* End PBXProject section */
281 |
282 | /* Begin PBXResourcesBuildPhase section */
283 | 607FACCE1AFB9204008FA782 /* Resources */ = {
284 | isa = PBXResourcesBuildPhase;
285 | buildActionMask = 2147483647;
286 | files = (
287 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */,
288 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */,
289 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */,
290 | BC4F88DD22802E050056082A /* CHANGELOG.md in Resources */,
291 | );
292 | runOnlyForDeploymentPostprocessing = 0;
293 | };
294 | 607FACE31AFB9204008FA782 /* Resources */ = {
295 | isa = PBXResourcesBuildPhase;
296 | buildActionMask = 2147483647;
297 | files = (
298 | );
299 | runOnlyForDeploymentPostprocessing = 0;
300 | };
301 | /* End PBXResourcesBuildPhase section */
302 |
303 | /* Begin PBXShellScriptBuildPhase section */
304 | 04AD95354171211AE70D72D8 /* [CP] Check Pods Manifest.lock */ = {
305 | isa = PBXShellScriptBuildPhase;
306 | buildActionMask = 2147483647;
307 | files = (
308 | );
309 | inputFileListPaths = (
310 | );
311 | inputPaths = (
312 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
313 | "${PODS_ROOT}/Manifest.lock",
314 | );
315 | name = "[CP] Check Pods Manifest.lock";
316 | outputFileListPaths = (
317 | );
318 | outputPaths = (
319 | "$(DERIVED_FILE_DIR)/Pods-SwiftyMenu_Example-checkManifestLockResult.txt",
320 | );
321 | runOnlyForDeploymentPostprocessing = 0;
322 | shellPath = /bin/sh;
323 | 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";
324 | showEnvVarsInLog = 0;
325 | };
326 | 3736782BD9248E9D776F5581 /* [CP] Embed Pods Frameworks */ = {
327 | isa = PBXShellScriptBuildPhase;
328 | buildActionMask = 2147483647;
329 | files = (
330 | );
331 | inputPaths = (
332 | "${PODS_ROOT}/Target Support Files/Pods-SwiftyMenu_Example/Pods-SwiftyMenu_Example-frameworks.sh",
333 | "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework",
334 | "${BUILT_PRODUCTS_DIR}/SwiftyMenu/SwiftyMenu.framework",
335 | );
336 | name = "[CP] Embed Pods Frameworks";
337 | outputPaths = (
338 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework",
339 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyMenu.framework",
340 | );
341 | runOnlyForDeploymentPostprocessing = 0;
342 | shellPath = /bin/sh;
343 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SwiftyMenu_Example/Pods-SwiftyMenu_Example-frameworks.sh\"\n";
344 | showEnvVarsInLog = 0;
345 | };
346 | B0B8349DDFC68D696EEFDFEB /* [CP] Check Pods Manifest.lock */ = {
347 | isa = PBXShellScriptBuildPhase;
348 | buildActionMask = 2147483647;
349 | files = (
350 | );
351 | inputFileListPaths = (
352 | );
353 | inputPaths = (
354 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
355 | "${PODS_ROOT}/Manifest.lock",
356 | );
357 | name = "[CP] Check Pods Manifest.lock";
358 | outputFileListPaths = (
359 | );
360 | outputPaths = (
361 | "$(DERIVED_FILE_DIR)/Pods-SwiftyMenu_Tests-checkManifestLockResult.txt",
362 | );
363 | runOnlyForDeploymentPostprocessing = 0;
364 | shellPath = /bin/sh;
365 | 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";
366 | showEnvVarsInLog = 0;
367 | };
368 | /* End PBXShellScriptBuildPhase section */
369 |
370 | /* Begin PBXSourcesBuildPhase section */
371 | 607FACCC1AFB9204008FA782 /* Sources */ = {
372 | isa = PBXSourcesBuildPhase;
373 | buildActionMask = 2147483647;
374 | files = (
375 | 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */,
376 | BC8E4C6A2690AD4700029145 /* String+Extensions.swift in Sources */,
377 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */,
378 | BC8E4C6C2690AD7C00029145 /* MealSize.swift in Sources */,
379 | );
380 | runOnlyForDeploymentPostprocessing = 0;
381 | };
382 | 607FACE11AFB9204008FA782 /* Sources */ = {
383 | isa = PBXSourcesBuildPhase;
384 | buildActionMask = 2147483647;
385 | files = (
386 | BC7A3009242BF9B900B76276 /* SwiftyMenuDelegateSpy.swift in Sources */,
387 | BC7A3004242BF8BD00B76276 /* SwiftyMenuTests.swift in Sources */,
388 | );
389 | runOnlyForDeploymentPostprocessing = 0;
390 | };
391 | /* End PBXSourcesBuildPhase section */
392 |
393 | /* Begin PBXTargetDependency section */
394 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */ = {
395 | isa = PBXTargetDependency;
396 | target = 607FACCF1AFB9204008FA782 /* SwiftyMenu_Example */;
397 | targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */;
398 | };
399 | /* End PBXTargetDependency section */
400 |
401 | /* Begin PBXVariantGroup section */
402 | 607FACD91AFB9204008FA782 /* Main.storyboard */ = {
403 | isa = PBXVariantGroup;
404 | children = (
405 | 607FACDA1AFB9204008FA782 /* Base */,
406 | );
407 | name = Main.storyboard;
408 | sourceTree = "";
409 | };
410 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = {
411 | isa = PBXVariantGroup;
412 | children = (
413 | 607FACDF1AFB9204008FA782 /* Base */,
414 | );
415 | name = LaunchScreen.xib;
416 | sourceTree = "";
417 | };
418 | /* End PBXVariantGroup section */
419 |
420 | /* Begin XCBuildConfiguration section */
421 | 607FACED1AFB9204008FA782 /* Debug */ = {
422 | isa = XCBuildConfiguration;
423 | buildSettings = {
424 | ALWAYS_SEARCH_USER_PATHS = NO;
425 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
426 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
427 | CLANG_CXX_LIBRARY = "libc++";
428 | CLANG_ENABLE_MODULES = YES;
429 | CLANG_ENABLE_OBJC_ARC = YES;
430 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
431 | CLANG_WARN_BOOL_CONVERSION = YES;
432 | CLANG_WARN_COMMA = YES;
433 | CLANG_WARN_CONSTANT_CONVERSION = YES;
434 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
435 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
436 | CLANG_WARN_EMPTY_BODY = YES;
437 | CLANG_WARN_ENUM_CONVERSION = YES;
438 | CLANG_WARN_INFINITE_RECURSION = YES;
439 | CLANG_WARN_INT_CONVERSION = YES;
440 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
441 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
442 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
443 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
444 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
445 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
446 | CLANG_WARN_STRICT_PROTOTYPES = YES;
447 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
448 | CLANG_WARN_UNREACHABLE_CODE = YES;
449 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
450 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
451 | COPY_PHASE_STRIP = NO;
452 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
453 | ENABLE_STRICT_OBJC_MSGSEND = YES;
454 | ENABLE_TESTABILITY = YES;
455 | GCC_C_LANGUAGE_STANDARD = gnu99;
456 | GCC_DYNAMIC_NO_PIC = NO;
457 | GCC_NO_COMMON_BLOCKS = YES;
458 | GCC_OPTIMIZATION_LEVEL = 0;
459 | GCC_PREPROCESSOR_DEFINITIONS = (
460 | "DEBUG=1",
461 | "$(inherited)",
462 | );
463 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
464 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
465 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
466 | GCC_WARN_UNDECLARED_SELECTOR = YES;
467 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
468 | GCC_WARN_UNUSED_FUNCTION = YES;
469 | GCC_WARN_UNUSED_VARIABLE = YES;
470 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
471 | MTL_ENABLE_DEBUG_INFO = YES;
472 | ONLY_ACTIVE_ARCH = YES;
473 | SDKROOT = iphoneos;
474 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
475 | };
476 | name = Debug;
477 | };
478 | 607FACEE1AFB9204008FA782 /* Release */ = {
479 | isa = XCBuildConfiguration;
480 | buildSettings = {
481 | ALWAYS_SEARCH_USER_PATHS = NO;
482 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
483 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
484 | CLANG_CXX_LIBRARY = "libc++";
485 | CLANG_ENABLE_MODULES = YES;
486 | CLANG_ENABLE_OBJC_ARC = YES;
487 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
488 | CLANG_WARN_BOOL_CONVERSION = YES;
489 | CLANG_WARN_COMMA = YES;
490 | CLANG_WARN_CONSTANT_CONVERSION = YES;
491 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
492 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
493 | CLANG_WARN_EMPTY_BODY = YES;
494 | CLANG_WARN_ENUM_CONVERSION = YES;
495 | CLANG_WARN_INFINITE_RECURSION = YES;
496 | CLANG_WARN_INT_CONVERSION = YES;
497 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
498 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
499 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
500 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
501 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
502 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
503 | CLANG_WARN_STRICT_PROTOTYPES = YES;
504 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
505 | CLANG_WARN_UNREACHABLE_CODE = YES;
506 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
507 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
508 | COPY_PHASE_STRIP = NO;
509 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
510 | ENABLE_NS_ASSERTIONS = NO;
511 | ENABLE_STRICT_OBJC_MSGSEND = YES;
512 | GCC_C_LANGUAGE_STANDARD = gnu99;
513 | GCC_NO_COMMON_BLOCKS = YES;
514 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
515 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
516 | GCC_WARN_UNDECLARED_SELECTOR = YES;
517 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
518 | GCC_WARN_UNUSED_FUNCTION = YES;
519 | GCC_WARN_UNUSED_VARIABLE = YES;
520 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
521 | MTL_ENABLE_DEBUG_INFO = NO;
522 | SDKROOT = iphoneos;
523 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
524 | VALIDATE_PRODUCT = YES;
525 | };
526 | name = Release;
527 | };
528 | 607FACF01AFB9204008FA782 /* Debug */ = {
529 | isa = XCBuildConfiguration;
530 | baseConfigurationReference = 62EA56F689738FB56657BC80 /* Pods-SwiftyMenu_Example.debug.xcconfig */;
531 | buildSettings = {
532 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
533 | DEVELOPMENT_TEAM = 89T7S3AF6B;
534 | INFOPLIST_FILE = SwiftyMenu/Info.plist;
535 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
536 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
537 | MODULE_NAME = ExampleApp;
538 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
539 | "PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = "org.cocoapods.demo.SwiftyMenu-Example";
540 | PRODUCT_NAME = "$(TARGET_NAME)";
541 | SWIFT_VERSION = 5.0;
542 | };
543 | name = Debug;
544 | };
545 | 607FACF11AFB9204008FA782 /* Release */ = {
546 | isa = XCBuildConfiguration;
547 | baseConfigurationReference = 97D36CF7E7BAEC32980EFB93 /* Pods-SwiftyMenu_Example.release.xcconfig */;
548 | buildSettings = {
549 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
550 | DEVELOPMENT_TEAM = 89T7S3AF6B;
551 | INFOPLIST_FILE = SwiftyMenu/Info.plist;
552 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
553 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
554 | MODULE_NAME = ExampleApp;
555 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
556 | "PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = "org.cocoapods.demo.SwiftyMenu-Example";
557 | PRODUCT_NAME = "$(TARGET_NAME)";
558 | SWIFT_VERSION = 5.0;
559 | };
560 | name = Release;
561 | };
562 | 607FACF31AFB9204008FA782 /* Debug */ = {
563 | isa = XCBuildConfiguration;
564 | baseConfigurationReference = 1C898DFC3115A1762BA35E9F /* Pods-SwiftyMenu_Tests.debug.xcconfig */;
565 | buildSettings = {
566 | DEVELOPMENT_TEAM = 89T7S3AF6B;
567 | FRAMEWORK_SEARCH_PATHS = (
568 | "$(SDKROOT)/Developer/Library/Frameworks",
569 | "$(inherited)",
570 | );
571 | GCC_PREPROCESSOR_DEFINITIONS = (
572 | "DEBUG=1",
573 | "$(inherited)",
574 | );
575 | INFOPLIST_FILE = Tests/Info.plist;
576 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
577 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
578 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
579 | PRODUCT_NAME = "$(TARGET_NAME)";
580 | SWIFT_VERSION = 5.0;
581 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftyMenu_Example.app/SwiftyMenu_Example";
582 | };
583 | name = Debug;
584 | };
585 | 607FACF41AFB9204008FA782 /* Release */ = {
586 | isa = XCBuildConfiguration;
587 | baseConfigurationReference = 808EB17321FD09CF60EA98B2 /* Pods-SwiftyMenu_Tests.release.xcconfig */;
588 | buildSettings = {
589 | DEVELOPMENT_TEAM = 89T7S3AF6B;
590 | FRAMEWORK_SEARCH_PATHS = (
591 | "$(SDKROOT)/Developer/Library/Frameworks",
592 | "$(inherited)",
593 | );
594 | INFOPLIST_FILE = Tests/Info.plist;
595 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
596 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
597 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
598 | PRODUCT_NAME = "$(TARGET_NAME)";
599 | SWIFT_VERSION = 5.0;
600 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftyMenu_Example.app/SwiftyMenu_Example";
601 | };
602 | name = Release;
603 | };
604 | /* End XCBuildConfiguration section */
605 |
606 | /* Begin XCConfigurationList section */
607 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "SwiftyMenu" */ = {
608 | isa = XCConfigurationList;
609 | buildConfigurations = (
610 | 607FACED1AFB9204008FA782 /* Debug */,
611 | 607FACEE1AFB9204008FA782 /* Release */,
612 | );
613 | defaultConfigurationIsVisible = 0;
614 | defaultConfigurationName = Release;
615 | };
616 | 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SwiftyMenu_Example" */ = {
617 | isa = XCConfigurationList;
618 | buildConfigurations = (
619 | 607FACF01AFB9204008FA782 /* Debug */,
620 | 607FACF11AFB9204008FA782 /* Release */,
621 | );
622 | defaultConfigurationIsVisible = 0;
623 | defaultConfigurationName = Release;
624 | };
625 | 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SwiftyMenu_Tests" */ = {
626 | isa = XCConfigurationList;
627 | buildConfigurations = (
628 | 607FACF31AFB9204008FA782 /* Debug */,
629 | 607FACF41AFB9204008FA782 /* Release */,
630 | );
631 | defaultConfigurationIsVisible = 0;
632 | defaultConfigurationName = Release;
633 | };
634 | /* End XCConfigurationList section */
635 | };
636 | rootObject = 607FACC81AFB9204008FA782 /* Project object */;
637 | }
638 |
--------------------------------------------------------------------------------