├── .gitignore ├── Demo.gif ├── DragMenuPicker.podspec ├── DragMenuPicker.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── Cem.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── DragMenuPicker.xcscheme │ └── xcschememanagement.plist ├── DragMenuPicker ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── DragMenuPicker.swift ├── Info.plist └── ViewController.swift ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/swift,xcode 3 | 4 | ### Swift ### 5 | # Xcode 6 | # 7 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 8 | 9 | ## Build generated 10 | build/ 11 | DerivedData/ 12 | 13 | ## Various settings 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata/ 23 | 24 | ## Other 25 | *.moved-aside 26 | *.xcuserstate 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | *.ipa 31 | *.dSYM.zip 32 | *.dSYM 33 | 34 | ## Playgrounds 35 | timeline.xctimeline 36 | playground.xcworkspace 37 | 38 | # Swift Package Manager 39 | # 40 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 41 | # Packages/ 42 | .build/ 43 | 44 | # CocoaPods 45 | # 46 | # We recommend against adding the Pods directory to your .gitignore. However 47 | # you should judge for yourself, the pros and cons are mentioned at: 48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 49 | # 50 | Pods/ 51 | 52 | # Carthage 53 | # 54 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 55 | Carthage/Checkouts 56 | 57 | Carthage/Build 58 | 59 | # fastlane 60 | # 61 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 62 | # screenshots whenever they are needed. 63 | # For more information about the recommended setup visit: 64 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 65 | 66 | fastlane/report.xml 67 | fastlane/Preview.html 68 | fastlane/screenshots 69 | fastlane/test_output 70 | 71 | 72 | ### Xcode ### 73 | # Xcode 74 | # 75 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 76 | 77 | ## Build generated 78 | 79 | ## Various settings 80 | 81 | ## Other 82 | *.xccheckout 83 | *.xcscmblueprint 84 | 85 | # Docs 86 | docs/ 87 | 88 | # End of https://www.gitignore.io/api/swift,xcode 89 | -------------------------------------------------------------------------------- /Demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cemolcay/DragMenuPicker/7d4ef7648784494252aa7027fb1a616b685d7154/Demo.gif -------------------------------------------------------------------------------- /DragMenuPicker.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint DragSwitchControl.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | 11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 12 | # 13 | # These will help people to find your library, and whilst it 14 | # can feel like a chore to fill in it's definitely to your advantage. The 15 | # summary should be tweet-length, and the description more in depth. 16 | # 17 | 18 | s.name = "DragMenuPicker" 19 | s.version = "0.0.1" 20 | s.summary = "A custom picker lets you pick an option from its auto scrolling menu without lifting your finger up." 21 | 22 | # This description is used to generate tags and improve search results. 23 | # * Think: What does it do? Why did you write it? What is the focus? 24 | # * Try to keep it short, snappy and to the point. 25 | # * Write the description between the DESC delimiters below. 26 | # * Finally, don't worry about the indent, CocoaPods strips it! 27 | s.description = <<-DESC 28 | DragMenuPicker 29 | === 30 | 31 | A custom picker lets you pick an option from its auto scrolling menu without lifting your finger up. 32 | 33 | You can either use the `@IBDesignable` picker button `DragMenuPicker` or create your own with `DragMenuView` which implements all picker logic. 34 | 35 | Demo 36 | ---- 37 | ![alt tag](https://github.com/cemolcay/DragMenuPicker/raw/master/Demo.gif) 38 | 39 | Requirements 40 | ---- 41 | 42 | - iOS 9.0+ 43 | - Swift 3.0+ 44 | 45 | Install 46 | ---- 47 | 48 | ``` 49 | pod 'DragMenuPicker' 50 | ``` 51 | 52 | Usage 53 | ---- 54 | 55 | Create a `DragMenuPicker` from either storyboard or programmatically. 56 | Set its `title` and `items` property to shown in menu. 57 | Set its `didSelectItem` property or implement `dragMenuView(_ dragMenuView: DragMenuView, didSelect item: String, at index: Int)` delegate method to set your action after picking. 58 | You can also set its `direction`, either horizontal or vertical with `margins` to screen edges. 59 | 60 | 61 | ``` swift 62 | horizontalDragPicker?.title = "Horizontal Picker" 63 | horizontalDragPicker?.items = ["First", "Second", "Third", "Fourth", "Other", "Another", "Item 2", "Item 3"] 64 | horizontalDragPicker?.direction = .horizontal 65 | horizontalDragPicker?.margins = 20 66 | horizontalDragPicker?.menuDelegate = self 67 | horizontalDragPicker?.didSelectItem = { item, index in 68 | print("\(item) selected at index \(index)") 69 | } 70 | ``` 71 | 72 | 73 | `DragMenuPicker` shows `DragMenuView` with `DragMenuItemView`s inside when you touch down the picker. It disappears after you pick something from menu or cancel picking by lifting your finger up outside of the menu. 74 | 75 | They are heavily customisable. You can set `applyStyle` property which callbacks you prototype menu and item that you can style and it applies it to menu. 76 | 77 | Also there are `@IBInspectable` properties on `DragMenuPicker` that you can style basic properties inside storyboard. 78 | DESC 79 | 80 | s.homepage = "https://github.com/cemolcay/DragMenuPicker" 81 | # s.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif" 82 | 83 | 84 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 85 | # 86 | # Licensing your code is important. See http://choosealicense.com for more info. 87 | # CocoaPods will detect a license file if there is a named LICENSE* 88 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'. 89 | # 90 | 91 | s.license = "MIT" 92 | # s.license = { :type => "MIT", :file => "FILE_LICENSE" } 93 | 94 | 95 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 96 | # 97 | # Specify the authors of the library, with email addresses. Email addresses 98 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also 99 | # accepts just a name if you'd rather not provide an email address. 100 | # 101 | # Specify a social_media_url where others can refer to, for example a twitter 102 | # profile URL. 103 | # 104 | 105 | s.author = { "cemolcay" => "ccemolcay@gmail.com" } 106 | # Or just: s.author = "cemolcay" 107 | # s.authors = { "cemolcay" => "ccemolcay@gmail.com" } 108 | s.social_media_url = "http://twitter.com/cemolcay" 109 | 110 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 111 | # 112 | # If this Pod runs only on iOS or OS X, then specify the platform and 113 | # the deployment target. You can optionally include the target after the platform. 114 | # 115 | 116 | # s.platform = :ios 117 | s.platform = :ios, "9.0" 118 | 119 | # When using multiple platforms 120 | # s.ios.deployment_target = "5.0" 121 | # s.osx.deployment_target = "10.7" 122 | # s.watchos.deployment_target = "2.0" 123 | # s.tvos.deployment_target = "9.0" 124 | 125 | 126 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 127 | # 128 | # Specify the location from where the source should be retrieved. 129 | # Supports git, hg, bzr, svn and HTTP. 130 | # 131 | 132 | s.source = { :git => "https://github.com/cemolcay/DragMenuPicker.git", :tag => "#{s.version}" } 133 | 134 | 135 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 136 | # 137 | # CocoaPods is smart about how it includes source code. For source files 138 | # giving a folder will include any swift, h, m, mm, c & cpp files. 139 | # For header files it will include any header in the folder. 140 | # Not including the public_header_files will make all headers public. 141 | # 142 | 143 | s.source_files = "DragMenuPicker/DragMenuPicker.swift" 144 | # s.exclude_files = "Classes/Exclude" 145 | 146 | # s.public_header_files = "Classes/**/*.h" 147 | 148 | 149 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 150 | # 151 | # A list of resources included with the Pod. These are copied into the 152 | # target bundle with a build phase script. Anything else will be cleaned. 153 | # You can preserve files from being cleaned, please don't preserve 154 | # non-essential files like tests, examples and documentation. 155 | # 156 | 157 | # s.resource = "icon.png" 158 | # s.resources = "Resources/*.png" 159 | 160 | # s.preserve_paths = "FilesToSave", "MoreFilesToSave" 161 | 162 | 163 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 164 | # 165 | # Link your library with frameworks, or libraries. Libraries do not include 166 | # the lib prefix of their name. 167 | # 168 | 169 | # s.framework = "SomeFramework" 170 | # s.frameworks = "SomeFramework", "AnotherFramework" 171 | 172 | # s.library = "iconv" 173 | # s.libraries = "iconv", "xml2" 174 | 175 | 176 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 177 | # 178 | # If your library depends on compiler flags you can set them in the xcconfig hash 179 | # where they will only apply to your library. If you depend on other Podspecs 180 | # you can include multiple dependencies to ensure it works. 181 | 182 | s.requires_arc = true 183 | 184 | # s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" } 185 | # s.dependency "JSONKit", "~> 1.4" 186 | 187 | end 188 | -------------------------------------------------------------------------------- /DragMenuPicker.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B25675111F62FB7F0080D8ED /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25675081F62FB7F0080D8ED /* AppDelegate.swift */; }; 11 | B25675121F62FB7F0080D8ED /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B25675091F62FB7F0080D8ED /* Assets.xcassets */; }; 12 | B25675131F62FB7F0080D8ED /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B256750A1F62FB7F0080D8ED /* LaunchScreen.storyboard */; }; 13 | B25675141F62FB7F0080D8ED /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B256750C1F62FB7F0080D8ED /* Main.storyboard */; }; 14 | B25675151F62FB7F0080D8ED /* DragMenuPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B256750E1F62FB7F0080D8ED /* DragMenuPicker.swift */; }; 15 | B25675161F62FB7F0080D8ED /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B256750F1F62FB7F0080D8ED /* Info.plist */; }; 16 | B25675171F62FB7F0080D8ED /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25675101F62FB7F0080D8ED /* ViewController.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | B25674F01F601A2B0080D8ED /* DragMenuPicker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DragMenuPicker.app; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | B25675081F62FB7F0080D8ED /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 22 | B25675091F62FB7F0080D8ED /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 23 | B256750B1F62FB7F0080D8ED /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 24 | B256750D1F62FB7F0080D8ED /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 25 | B256750E1F62FB7F0080D8ED /* DragMenuPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragMenuPicker.swift; sourceTree = ""; }; 26 | B256750F1F62FB7F0080D8ED /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 27 | B25675101F62FB7F0080D8ED /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 28 | /* End PBXFileReference section */ 29 | 30 | /* Begin PBXFrameworksBuildPhase section */ 31 | B25674ED1F601A2B0080D8ED /* Frameworks */ = { 32 | isa = PBXFrameworksBuildPhase; 33 | buildActionMask = 2147483647; 34 | files = ( 35 | ); 36 | runOnlyForDeploymentPostprocessing = 0; 37 | }; 38 | /* End PBXFrameworksBuildPhase section */ 39 | 40 | /* Begin PBXGroup section */ 41 | B25674E71F601A2B0080D8ED = { 42 | isa = PBXGroup; 43 | children = ( 44 | B25675071F62FB7F0080D8ED /* DragMenuPicker */, 45 | B25674F11F601A2B0080D8ED /* Products */, 46 | ); 47 | sourceTree = ""; 48 | }; 49 | B25674F11F601A2B0080D8ED /* Products */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | B25674F01F601A2B0080D8ED /* DragMenuPicker.app */, 53 | ); 54 | name = Products; 55 | sourceTree = ""; 56 | }; 57 | B25675071F62FB7F0080D8ED /* DragMenuPicker */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | B25675081F62FB7F0080D8ED /* AppDelegate.swift */, 61 | B25675101F62FB7F0080D8ED /* ViewController.swift */, 62 | B256750E1F62FB7F0080D8ED /* DragMenuPicker.swift */, 63 | B25675091F62FB7F0080D8ED /* Assets.xcassets */, 64 | B256750A1F62FB7F0080D8ED /* LaunchScreen.storyboard */, 65 | B256750C1F62FB7F0080D8ED /* Main.storyboard */, 66 | B256750F1F62FB7F0080D8ED /* Info.plist */, 67 | ); 68 | path = DragMenuPicker; 69 | sourceTree = ""; 70 | }; 71 | /* End PBXGroup section */ 72 | 73 | /* Begin PBXNativeTarget section */ 74 | B25674EF1F601A2B0080D8ED /* DragMenuPicker */ = { 75 | isa = PBXNativeTarget; 76 | buildConfigurationList = B25675021F601A2B0080D8ED /* Build configuration list for PBXNativeTarget "DragMenuPicker" */; 77 | buildPhases = ( 78 | B25674EC1F601A2B0080D8ED /* Sources */, 79 | B25674ED1F601A2B0080D8ED /* Frameworks */, 80 | B25674EE1F601A2B0080D8ED /* Resources */, 81 | ); 82 | buildRules = ( 83 | ); 84 | dependencies = ( 85 | ); 86 | name = DragMenuPicker; 87 | productName = DragSwitchControl; 88 | productReference = B25674F01F601A2B0080D8ED /* DragMenuPicker.app */; 89 | productType = "com.apple.product-type.application"; 90 | }; 91 | /* End PBXNativeTarget section */ 92 | 93 | /* Begin PBXProject section */ 94 | B25674E81F601A2B0080D8ED /* Project object */ = { 95 | isa = PBXProject; 96 | attributes = { 97 | LastSwiftUpdateCheck = 0830; 98 | LastUpgradeCheck = 0830; 99 | ORGANIZATIONNAME = cemolcay; 100 | TargetAttributes = { 101 | B25674EF1F601A2B0080D8ED = { 102 | CreatedOnToolsVersion = 8.3.3; 103 | DevelopmentTeam = 77Y3N48SNF; 104 | ProvisioningStyle = Automatic; 105 | }; 106 | }; 107 | }; 108 | buildConfigurationList = B25674EB1F601A2B0080D8ED /* Build configuration list for PBXProject "DragMenuPicker" */; 109 | compatibilityVersion = "Xcode 3.2"; 110 | developmentRegion = English; 111 | hasScannedForEncodings = 0; 112 | knownRegions = ( 113 | en, 114 | Base, 115 | ); 116 | mainGroup = B25674E71F601A2B0080D8ED; 117 | productRefGroup = B25674F11F601A2B0080D8ED /* Products */; 118 | projectDirPath = ""; 119 | projectRoot = ""; 120 | targets = ( 121 | B25674EF1F601A2B0080D8ED /* DragMenuPicker */, 122 | ); 123 | }; 124 | /* End PBXProject section */ 125 | 126 | /* Begin PBXResourcesBuildPhase section */ 127 | B25674EE1F601A2B0080D8ED /* Resources */ = { 128 | isa = PBXResourcesBuildPhase; 129 | buildActionMask = 2147483647; 130 | files = ( 131 | B25675161F62FB7F0080D8ED /* Info.plist in Resources */, 132 | B25675141F62FB7F0080D8ED /* Main.storyboard in Resources */, 133 | B25675121F62FB7F0080D8ED /* Assets.xcassets in Resources */, 134 | B25675131F62FB7F0080D8ED /* LaunchScreen.storyboard in Resources */, 135 | ); 136 | runOnlyForDeploymentPostprocessing = 0; 137 | }; 138 | /* End PBXResourcesBuildPhase section */ 139 | 140 | /* Begin PBXSourcesBuildPhase section */ 141 | B25674EC1F601A2B0080D8ED /* Sources */ = { 142 | isa = PBXSourcesBuildPhase; 143 | buildActionMask = 2147483647; 144 | files = ( 145 | B25675171F62FB7F0080D8ED /* ViewController.swift in Sources */, 146 | B25675111F62FB7F0080D8ED /* AppDelegate.swift in Sources */, 147 | B25675151F62FB7F0080D8ED /* DragMenuPicker.swift in Sources */, 148 | ); 149 | runOnlyForDeploymentPostprocessing = 0; 150 | }; 151 | /* End PBXSourcesBuildPhase section */ 152 | 153 | /* Begin PBXVariantGroup section */ 154 | B256750A1F62FB7F0080D8ED /* LaunchScreen.storyboard */ = { 155 | isa = PBXVariantGroup; 156 | children = ( 157 | B256750B1F62FB7F0080D8ED /* Base */, 158 | ); 159 | name = LaunchScreen.storyboard; 160 | sourceTree = ""; 161 | }; 162 | B256750C1F62FB7F0080D8ED /* Main.storyboard */ = { 163 | isa = PBXVariantGroup; 164 | children = ( 165 | B256750D1F62FB7F0080D8ED /* Base */, 166 | ); 167 | name = Main.storyboard; 168 | sourceTree = ""; 169 | }; 170 | /* End PBXVariantGroup section */ 171 | 172 | /* Begin XCBuildConfiguration section */ 173 | B25675001F601A2B0080D8ED /* Debug */ = { 174 | isa = XCBuildConfiguration; 175 | buildSettings = { 176 | ALWAYS_SEARCH_USER_PATHS = NO; 177 | CLANG_ANALYZER_NONNULL = YES; 178 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 179 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 180 | CLANG_CXX_LIBRARY = "libc++"; 181 | CLANG_ENABLE_MODULES = YES; 182 | CLANG_ENABLE_OBJC_ARC = YES; 183 | CLANG_WARN_BOOL_CONVERSION = YES; 184 | CLANG_WARN_CONSTANT_CONVERSION = YES; 185 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 186 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 187 | CLANG_WARN_EMPTY_BODY = YES; 188 | CLANG_WARN_ENUM_CONVERSION = YES; 189 | CLANG_WARN_INFINITE_RECURSION = YES; 190 | CLANG_WARN_INT_CONVERSION = YES; 191 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 192 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 193 | CLANG_WARN_UNREACHABLE_CODE = YES; 194 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 195 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 196 | COPY_PHASE_STRIP = NO; 197 | DEBUG_INFORMATION_FORMAT = dwarf; 198 | ENABLE_STRICT_OBJC_MSGSEND = YES; 199 | ENABLE_TESTABILITY = YES; 200 | GCC_C_LANGUAGE_STANDARD = gnu99; 201 | GCC_DYNAMIC_NO_PIC = NO; 202 | GCC_NO_COMMON_BLOCKS = YES; 203 | GCC_OPTIMIZATION_LEVEL = 0; 204 | GCC_PREPROCESSOR_DEFINITIONS = ( 205 | "DEBUG=1", 206 | "$(inherited)", 207 | ); 208 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 209 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 210 | GCC_WARN_UNDECLARED_SELECTOR = YES; 211 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 212 | GCC_WARN_UNUSED_FUNCTION = YES; 213 | GCC_WARN_UNUSED_VARIABLE = YES; 214 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 215 | MTL_ENABLE_DEBUG_INFO = YES; 216 | ONLY_ACTIVE_ARCH = YES; 217 | SDKROOT = iphoneos; 218 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 219 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 220 | TARGETED_DEVICE_FAMILY = "1,2"; 221 | }; 222 | name = Debug; 223 | }; 224 | B25675011F601A2B0080D8ED /* Release */ = { 225 | isa = XCBuildConfiguration; 226 | buildSettings = { 227 | ALWAYS_SEARCH_USER_PATHS = NO; 228 | CLANG_ANALYZER_NONNULL = YES; 229 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 230 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 231 | CLANG_CXX_LIBRARY = "libc++"; 232 | CLANG_ENABLE_MODULES = YES; 233 | CLANG_ENABLE_OBJC_ARC = YES; 234 | CLANG_WARN_BOOL_CONVERSION = YES; 235 | CLANG_WARN_CONSTANT_CONVERSION = YES; 236 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 237 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 238 | CLANG_WARN_EMPTY_BODY = YES; 239 | CLANG_WARN_ENUM_CONVERSION = YES; 240 | CLANG_WARN_INFINITE_RECURSION = YES; 241 | CLANG_WARN_INT_CONVERSION = YES; 242 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 243 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 244 | CLANG_WARN_UNREACHABLE_CODE = YES; 245 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 246 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 247 | COPY_PHASE_STRIP = NO; 248 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 249 | ENABLE_NS_ASSERTIONS = NO; 250 | ENABLE_STRICT_OBJC_MSGSEND = YES; 251 | GCC_C_LANGUAGE_STANDARD = gnu99; 252 | GCC_NO_COMMON_BLOCKS = YES; 253 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 254 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 255 | GCC_WARN_UNDECLARED_SELECTOR = YES; 256 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 257 | GCC_WARN_UNUSED_FUNCTION = YES; 258 | GCC_WARN_UNUSED_VARIABLE = YES; 259 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 260 | MTL_ENABLE_DEBUG_INFO = NO; 261 | SDKROOT = iphoneos; 262 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 263 | TARGETED_DEVICE_FAMILY = "1,2"; 264 | VALIDATE_PRODUCT = YES; 265 | }; 266 | name = Release; 267 | }; 268 | B25675031F601A2B0080D8ED /* Debug */ = { 269 | isa = XCBuildConfiguration; 270 | buildSettings = { 271 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 272 | DEVELOPMENT_TEAM = 77Y3N48SNF; 273 | INFOPLIST_FILE = "$(SRCROOT)/DragMenuPicker/Info.plist"; 274 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 275 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 276 | PRODUCT_BUNDLE_IDENTIFIER = com.cemolcay.DragMenuPicker; 277 | PRODUCT_NAME = "$(TARGET_NAME)"; 278 | SWIFT_VERSION = 3.0; 279 | }; 280 | name = Debug; 281 | }; 282 | B25675041F601A2B0080D8ED /* Release */ = { 283 | isa = XCBuildConfiguration; 284 | buildSettings = { 285 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 286 | DEVELOPMENT_TEAM = 77Y3N48SNF; 287 | INFOPLIST_FILE = "$(SRCROOT)/DragMenuPicker/Info.plist"; 288 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 289 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 290 | PRODUCT_BUNDLE_IDENTIFIER = com.cemolcay.DragMenuPicker; 291 | PRODUCT_NAME = "$(TARGET_NAME)"; 292 | SWIFT_VERSION = 3.0; 293 | }; 294 | name = Release; 295 | }; 296 | /* End XCBuildConfiguration section */ 297 | 298 | /* Begin XCConfigurationList section */ 299 | B25674EB1F601A2B0080D8ED /* Build configuration list for PBXProject "DragMenuPicker" */ = { 300 | isa = XCConfigurationList; 301 | buildConfigurations = ( 302 | B25675001F601A2B0080D8ED /* Debug */, 303 | B25675011F601A2B0080D8ED /* Release */, 304 | ); 305 | defaultConfigurationIsVisible = 0; 306 | defaultConfigurationName = Release; 307 | }; 308 | B25675021F601A2B0080D8ED /* Build configuration list for PBXNativeTarget "DragMenuPicker" */ = { 309 | isa = XCConfigurationList; 310 | buildConfigurations = ( 311 | B25675031F601A2B0080D8ED /* Debug */, 312 | B25675041F601A2B0080D8ED /* Release */, 313 | ); 314 | defaultConfigurationIsVisible = 0; 315 | defaultConfigurationName = Release; 316 | }; 317 | /* End XCConfigurationList section */ 318 | }; 319 | rootObject = B25674E81F601A2B0080D8ED /* Project object */; 320 | } 321 | -------------------------------------------------------------------------------- /DragMenuPicker.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DragMenuPicker.xcodeproj/xcuserdata/Cem.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /DragMenuPicker.xcodeproj/xcuserdata/Cem.xcuserdatad/xcschemes/DragMenuPicker.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /DragMenuPicker.xcodeproj/xcuserdata/Cem.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | DragMenuPicker.xcscheme 8 | 9 | orderHint 10 | 1 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | B25674EF1F601A2B0080D8ED 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /DragMenuPicker/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DragMenuPicker 4 | // 5 | // Created by Cem Olcay on 06/09/2017. 6 | // Copyright © 2017 cemolcay. 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: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /DragMenuPicker/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /DragMenuPicker/Base.lproj/LaunchScreen.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 | -------------------------------------------------------------------------------- /DragMenuPicker/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 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /DragMenuPicker/DragMenuPicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DragMenuPicker.swift 3 | // DragMenuPicker 4 | // 5 | // Created by Cem Olcay on 06/09/2017. 6 | // Copyright © 2017 cemolcay. All rights reserved. 7 | // 8 | // https://github.com/cemolcay/DragMenuPicker 9 | // 10 | 11 | import UIKit 12 | 13 | /// Action handler on drag menu item selection. 14 | public typealias DragMenuSelectItemAction = (_ item: String, _ index: Int) -> Void 15 | 16 | /// Applies view or layer style to menu and its every item in a block. 17 | public typealias DragMenuApplyStyleAction = (_ menu: DragMenuView, _ item: DragMenuItemView) -> Void 18 | 19 | /// Direction of drag menu. 20 | public enum DragMenuDirection { 21 | /// Left to right, horizontal direction. 22 | case horizontal 23 | /// Top to bottom, vertical direction. 24 | case vertical 25 | } 26 | 27 | /// An item view in drag menu. 28 | public class DragMenuItemView: UILabel { 29 | public var highlightedBackgroundColor: UIColor = .clear 30 | public var defaultTextColor: UIColor = .black 31 | 32 | public override var isHighlighted: Bool { 33 | didSet { 34 | backgroundColor = isHighlighted ? highlightedBackgroundColor : .clear 35 | textColor = isHighlighted ? highlightedTextColor : defaultTextColor 36 | } 37 | } 38 | } 39 | 40 | /// `DragMenuView` delegate that informs about menu state. 41 | @objc public protocol DragMenuViewDelegate { 42 | /// Informs about menu is going to be displayed. 43 | /// 44 | /// - Parameter dragMenuView: Displaying menu. 45 | @objc optional func dragMenuViewWillDisplayMenu(_ dragMenuView: DragMenuView) 46 | 47 | /// Informs about menu is displayed. 48 | /// 49 | /// - Parameter dragMenuView: Displayed menu. 50 | @objc optional func dragMenuViewDidDisplayMenu(_ dragMenuView: DragMenuView) 51 | 52 | /// Informs about menu going to be dismissed. 53 | /// 54 | /// - Parameter dragMenuView: Dismissing menu. 55 | @objc optional func dragMenuViewWillDismissMenu(_ dragMenuView: DragMenuView) 56 | 57 | /// Informs about menu did dismissed, with or without a selection 58 | /// 59 | /// - Parameter dragMenuView: Dismissed menu. 60 | @objc optional func dragMenuViewDidDismissMenu(_ dragMenuView: DragMenuView) 61 | 62 | /// Informs about menu item selection. 63 | /// 64 | /// - Parameters: 65 | /// - dragMenuView: Menu that select an item. 66 | /// - item: Selected item. 67 | /// - index: Selected item index. 68 | @objc optional func dragMenuView(_ dragMenuView: DragMenuView, didSelect item: String, at index: Int) 69 | } 70 | 71 | /// Drag menu view with items and display options. 72 | public class DragMenuView: UIView { 73 | /// Selection items in drag menu. 74 | public var items = [DragMenuItemView]() 75 | /// Triggers the menu scroll with a distance to edges. Defaults 40. 76 | public var scrollingThreshold = CGFloat(40) 77 | /// Maximum speed of scrolling. If scroll speed is not increased than it's always scrolls on that speed. Defaults 10. 78 | public var maximumScrollingSpeed = CGFloat(10) 79 | /// An option for controlling scroll speed over time. Scroll speed increases to `maximumScrollingSpeed` in two seconds. 80 | public var isScrollSpeedIncreases = true 81 | /// A reference the direction of drag menu. 82 | private var direction: DragMenuDirection 83 | /// Returns true if menu is scrolling any direction. 84 | public private(set) var isScrolling = false 85 | /// A timer object to update scrolling animation of menu. 86 | private var scrollTimer: Timer? 87 | /// Actual menu view, masked into parent to create scrolling effect. 88 | public private(set) var menuView = UIView() 89 | /// Delegate that informs display status. 90 | public weak var delegate: DragMenuViewDelegate? 91 | 92 | /// Helper enum to scroll menu in a direction. 93 | private enum ScrollDirection: Int { 94 | case left = 0, right, up, down 95 | } 96 | 97 | /// Initilizes the drag menu with items, direction and item options. 98 | /// 99 | /// - Parameters: 100 | /// - items: Selection items of drag menu. 101 | /// - initalSelection: Initially selected item index. 102 | /// - estimatedItemSize: Estimated item width if menu is horizontal, estimated item height if menu is vertical. 103 | /// - controlBounds: Reference `DragSwitchControl`'s bounds relative to window to fit menu properly in screen real estate. 104 | /// - direction: Direction of menu. Either horizontal or vertical. 105 | /// - margins: Margins from screen edges. Left and right if menu is horizontal, top and bottom if menu is vertical. 106 | /// - backgroundColor: Backgronud color of drag menu. 107 | /// - highlightedColor: Highlighted item background color in drag menu. 108 | /// - textColor: Text color of selection items in drag menu. 109 | /// - highlightedTextColor: Hihglighted text color of selected item in drag menu. 110 | /// - font: Font of selection items in drag menu. 111 | /// - applyStyle: Style drag menu and its every item with this optional function. 112 | public init(items: [String], initalSelection: Int, estimatedItemSize: CGFloat, controlBounds: CGRect, direction: DragMenuDirection, margins: CGFloat, backgroundColor: UIColor, highlightedColor: UIColor, textColor: UIColor, highlightedTextColor: UIColor, font: UIFont, applyStyle: DragMenuApplyStyleAction? = nil) { 113 | self.direction = direction 114 | super.init(frame: CGRect( 115 | x: direction == .horizontal ? -controlBounds.minX + margins : 0, 116 | y: direction == .horizontal ? 0 : -controlBounds.minY + margins, 117 | width: direction == .horizontal ? UIScreen.main.bounds.width - (margins * 2) : controlBounds.width, 118 | height: direction == .horizontal ? controlBounds.height : UIScreen.main.bounds.height - (margins * 2))) 119 | 120 | clipsToBounds = true 121 | addSubview(menuView) 122 | 123 | addGestureRecognizer(UITapGestureRecognizer(target: nil, action: nil)) // add dummy gesture recognizer 124 | menuView.backgroundColor = backgroundColor 125 | menuView.frame = CGRect( 126 | x: direction == .horizontal ? -(CGFloat(initalSelection) * estimatedItemSize) + controlBounds.minX : 0, 127 | y: direction == .horizontal ? 0 : -(CGFloat(initalSelection) * estimatedItemSize) + controlBounds.minY - margins, 128 | width: direction == .horizontal ? CGFloat(items.count) * estimatedItemSize : controlBounds.width, 129 | height: direction == .horizontal ? controlBounds.height : CGFloat(items.count) * estimatedItemSize) 130 | 131 | for (index, item) in items.enumerated() { 132 | let itemView = DragMenuItemView(frame: CGRect( 133 | x: direction == .horizontal ? CGFloat(index) * estimatedItemSize : 0, 134 | y: direction == .horizontal ? 0 : CGFloat(index) * estimatedItemSize, 135 | width: direction == .horizontal ? estimatedItemSize : controlBounds.width, 136 | height: direction == .horizontal ? controlBounds.height : estimatedItemSize)) 137 | itemView.highlightedBackgroundColor = highlightedColor 138 | itemView.tag = index 139 | itemView.text = item 140 | itemView.textAlignment = .center 141 | itemView.textColor = textColor 142 | itemView.highlightedTextColor = highlightedTextColor 143 | itemView.font = font 144 | self.items.append(itemView) 145 | menuView.addSubview(itemView) 146 | applyStyle?(self, itemView) 147 | } 148 | } 149 | 150 | public func didPan(gesture: UIPanGestureRecognizer) { 151 | return 152 | } 153 | 154 | public required init?(coder aDecoder: NSCoder) { 155 | direction = .horizontal 156 | super.init(coder: aDecoder) 157 | } 158 | 159 | // MARK: Update Menu 160 | 161 | /// Updates the selected item and menu scroll for given touch position. 162 | /// 163 | /// - Parameter location: Current position of touch on drag menu. 164 | public func updateMenu(for position: CGPoint) { 165 | // Check if menu is scrollable 166 | let scrollable = (menuView.frame.minX < 0 || menuView.frame.maxX > frame.size.width) || // check horizontal scrollable bounds 167 | (menuView.frame.minY < 0 || menuView.frame.maxY > frame.size.height) // check vertical scrollable bounds 168 | if scrollable { 169 | // Update menu position 170 | switch direction { 171 | case .horizontal: 172 | if position.x > bounds.minX && position.x < bounds.minX + scrollingThreshold { // Scroll left 173 | scroll(to: .left) 174 | } else if position.x < bounds.maxX && position.x > bounds.maxX - scrollingThreshold { // Scroll right 175 | scroll(to: .right) 176 | } else { 177 | stopScrolling() 178 | } 179 | case .vertical: 180 | if position.y > bounds.minY && position.y < bounds.minY + scrollingThreshold { // Scroll up 181 | scroll(to: .up) 182 | } else if position.y < bounds.maxY && position.y > bounds.maxY - scrollingThreshold { // Scroll down 183 | scroll(to: .down) 184 | } else { 185 | stopScrolling() 186 | } 187 | } 188 | } 189 | 190 | // Update highlighted item 191 | items.forEach({ $0.isHighlighted = $0.frame.contains(convert(position, to: menuView)) }) 192 | } 193 | 194 | private func scroll(to: ScrollDirection) { 195 | if isScrolling { 196 | return 197 | } 198 | 199 | isScrolling = true 200 | scrollTimer = Timer.scheduledTimer( 201 | timeInterval: 0.016, 202 | target: self, 203 | selector: #selector(timerTick(timer:)), 204 | userInfo: to.rawValue, 205 | repeats: true) 206 | } 207 | 208 | @objc private func timerTick(timer: Timer) { 209 | guard let directionValue = (timer.userInfo as? Int), 210 | let scrollDirection = ScrollDirection(rawValue: directionValue) 211 | else { return } 212 | switch scrollDirection { 213 | case .left: 214 | var newPosition = menuView.frame.origin.x + maximumScrollingSpeed 215 | if newPosition > 0 { 216 | newPosition = 0 217 | } 218 | menuView.layer.frame.origin.x = newPosition 219 | case .right: 220 | var newPosition = menuView.frame.origin.x - maximumScrollingSpeed 221 | if newPosition <= frame.size.width - menuView.frame.size.width { 222 | newPosition = frame.size.width - menuView.frame.size.width 223 | } 224 | menuView.layer.frame.origin.x = newPosition 225 | case .up: 226 | var newPosition = menuView.frame.origin.y + maximumScrollingSpeed 227 | if newPosition > 0 { 228 | newPosition = 0 229 | } 230 | menuView.frame.origin.y = newPosition 231 | case .down: 232 | var newPosition = menuView.frame.origin.y - maximumScrollingSpeed 233 | if newPosition <= frame.size.height - menuView.frame.size.height { 234 | newPosition = frame.size.height - menuView.frame.size.height 235 | } 236 | menuView.frame.origin.y = newPosition 237 | } 238 | } 239 | 240 | private func stopScrolling() { 241 | scrollTimer?.invalidate() 242 | scrollTimer = nil 243 | isScrolling = false 244 | } 245 | } 246 | 247 | /// A custom button with ability to select an option from its items menu with drag gesture. 248 | @IBDesignable public class DragMenuPicker: UIView, DragMenuViewDelegate { 249 | /// The title of the button. 250 | @IBInspectable public var title = "" { didSet { setNeedsLayout() }} 251 | /// Items of drag menu. 252 | public var items = [String]() 253 | /// Index of currently selected item in drag menu. Defaults 0. 254 | @IBInspectable public var selectedItemIndex = 0 { didSet { setNeedsLayout() }} 255 | /// Action on item selection from draw menu. 256 | public var didSelectItem: DragMenuSelectItemAction = { _, _ in return } 257 | /// Estimated minimum size for each item. Width size for horizontal, height size for vertical drag memu. Defaults 60. 258 | @IBInspectable public var estimatedItemSize = CGFloat(60) 259 | /// Direction of drag menu. Either horizontal or vertical. 260 | public var direction = DragMenuDirection.horizontal 261 | /// Margins from edges of screen. Left and right margins for horizontal, top and bottom margins for vertical drag menu. Defaults 0 262 | @IBInspectable public var margins = CGFloat(0) 263 | /// Apply custom view or layer styles for `DragMenuView` and its every `DragMenuItemView` with this function. 264 | public var applyMenuStyle: DragMenuApplyStyleAction? 265 | /// Informs about drag menu status via `DragMenuViewDelegate`. 266 | public weak var menuDelegate: DragMenuViewDelegate? 267 | 268 | /// Read-only property to get info about drag menu is shown or not. 269 | public dynamic var isOpen: Bool { return dragMenu != nil } 270 | /// Title label of button. 271 | public private(set) var titleLabel = UILabel() 272 | /// Selected item label of button. 273 | public private(set) var itemLabel = UILabel() 274 | /// Stack view of button with stack of labels. 275 | public private(set) var buttonStack = UIStackView() 276 | /// Drag menu with selection of items. Nil if not shown. 277 | public private(set) var dragMenu: DragMenuView? 278 | 279 | /// Text color of title label. Defaults black. 280 | @IBInspectable public var titleTextColor = UIColor.black { didSet { setNeedsLayout() }} 281 | /// Text color of selected item label. Defaults black. 282 | @IBInspectable public var itemTextColor = UIColor.black { didSet { setNeedsLayout() }} 283 | /// Highlighted text color of selected item label. Defaults black. 284 | @IBInspectable public var highlightedTextColor = UIColor.black { didSet { setNeedsLayout() }} 285 | /// Font of title label. Defaults 13. 286 | @IBInspectable public var titleFont = UIFont.systemFont(ofSize: 13) { didSet { setNeedsLayout() }} 287 | /// Font of selected item label. Defaults 15. 288 | @IBInspectable public var itemFont = UIFont.systemFont(ofSize: 15) { didSet { setNeedsLayout() }} 289 | /// Background color of drag menu. Defaults gray. 290 | @IBInspectable public var dragMenuBackgroundColor = UIColor.gray 291 | /// Highlighted item background color in drag menu. Defaults yellow. 292 | @IBInspectable public var dragMenuHightlightedItemColor = UIColor.yellow 293 | 294 | // MARK: Init 295 | 296 | /// Init the control with title, items and action. 297 | /// 298 | /// - Parameters: 299 | /// - frame: Frame of control. 300 | /// - title: Title label text. 301 | /// - items: Items of selection in drag menu. 302 | /// - initialSelectionIndex: Initially selected item index. 303 | /// - didSelectItem: Action on item selection. 304 | /// - applyMenuStyle: Apply custom style to `DragMenuView` and its every `DragMenuItemView` with this optional function. 305 | public init(frame: CGRect, title: String, items: [String], initialSelectionIndex: Int = 0, didSelectItem: @escaping DragMenuSelectItemAction, applyMenuStyle: DragMenuApplyStyleAction? = nil) { 306 | super.init(frame: frame) 307 | self.title = title 308 | self.items = items 309 | self.selectedItemIndex = initialSelectionIndex 310 | self.didSelectItem = didSelectItem 311 | self.applyMenuStyle = applyMenuStyle 312 | addGestureRecognizer(UITapGestureRecognizer(target: nil, action: nil)) 313 | commonInit() 314 | } 315 | 316 | public override init(frame: CGRect) { 317 | super.init(frame: frame) 318 | commonInit() 319 | } 320 | 321 | public required init?(coder aDecoder: NSCoder) { 322 | super.init(coder: aDecoder) 323 | commonInit() 324 | } 325 | 326 | private func commonInit() { 327 | buttonStack.axis = .vertical 328 | buttonStack.addArrangedSubview(titleLabel) 329 | buttonStack.addArrangedSubview(itemLabel) 330 | 331 | itemLabel.setContentHuggingPriority(UILayoutPriorityDefaultLow, for: .vertical) 332 | titleLabel.setContentHuggingPriority(UILayoutPriorityDefaultHigh, for: .vertical) 333 | 334 | addSubview(buttonStack) 335 | } 336 | 337 | // MARK: Lifecycle 338 | 339 | public override func layoutSubviews() { 340 | super.layoutSubviews() 341 | 342 | buttonStack.frame = bounds 343 | titleLabel.font = titleFont 344 | itemLabel.font = itemFont 345 | titleLabel.textColor = titleTextColor 346 | itemLabel.textColor = itemTextColor 347 | titleLabel.text = title 348 | itemLabel.text = items[selectedItemIndex] 349 | } 350 | 351 | // MARK: Drag menu 352 | 353 | private func createDragMenu() -> DragMenuView { 354 | return DragMenuView( 355 | items: items, 356 | initalSelection: selectedItemIndex, 357 | estimatedItemSize: estimatedItemSize, 358 | controlBounds: convert(bounds, to: nil), 359 | direction: direction, 360 | margins: margins, 361 | backgroundColor: dragMenuBackgroundColor, 362 | highlightedColor: dragMenuHightlightedItemColor, 363 | textColor: itemTextColor, 364 | highlightedTextColor: highlightedTextColor, 365 | font: itemFont, 366 | applyStyle: applyMenuStyle) 367 | } 368 | 369 | // MARK: Handle touches 370 | 371 | public override func touchesBegan(_ touches: Set, with event: UIEvent?) { 372 | guard dragMenu == nil else { return } 373 | dragMenu = createDragMenu() 374 | guard let dragMenu = self.dragMenu else { return } 375 | menuDelegate?.dragMenuViewWillDisplayMenu?(dragMenu) 376 | addSubview(dragMenu) 377 | menuDelegate?.dragMenuViewDidDisplayMenu?(dragMenu) 378 | } 379 | 380 | public override func touchesMoved(_ touches: Set, with event: UIEvent?) { 381 | guard isOpen, 382 | let dragMenu = dragMenu, 383 | let touchLocation = touches.first?.location(in: dragMenu) 384 | else { return } 385 | dragMenu.updateMenu(for: touchLocation) 386 | } 387 | 388 | public override func touchesEnded(_ touches: Set, with event: UIEvent?) { 389 | guard let dragMenu = self.dragMenu else { return } 390 | menuDelegate?.dragMenuViewWillDismissMenu?(dragMenu) 391 | 392 | if let selectedItem = dragMenu.items.filter({ $0.isHighlighted }).first { 393 | self.selectedItemIndex = selectedItem.tag 394 | self.didSelectItem(items[selectedItemIndex], selectedItemIndex) 395 | menuDelegate?.dragMenuView?(dragMenu, didSelect: items[selectedItemIndex], at: selectedItemIndex) 396 | } 397 | 398 | dragMenu.removeFromSuperview() 399 | menuDelegate?.dragMenuViewDidDismissMenu?(dragMenu) 400 | self.dragMenu = nil 401 | } 402 | 403 | // MARK: DragMenuViewDelegate 404 | 405 | public func dragMenuViewWillDisplayMenu(_ dragMenuView: DragMenuView) { 406 | guard let dragMenu = self.dragMenu else { return } 407 | menuDelegate?.dragMenuViewWillDisplayMenu?(dragMenu) 408 | } 409 | 410 | public func dragMenuViewDidDisplayMenu(_ dragMenuView: DragMenuView) { 411 | guard let dragMenu = self.dragMenu else { return } 412 | menuDelegate?.dragMenuViewDidDisplayMenu?(dragMenu) 413 | } 414 | 415 | public func dragMenuViewWillDismissMenu(_ dragMenuView: DragMenuView) { 416 | guard let dragMenu = self.dragMenu else { return } 417 | menuDelegate?.dragMenuViewWillDismissMenu?(dragMenu) 418 | } 419 | 420 | public func dragMenuViewDidDismissMenu(_ dragMenuView: DragMenuView) { 421 | guard let dragMenu = self.dragMenu else { return } 422 | menuDelegate?.dragMenuViewDidDismissMenu?(dragMenu) 423 | } 424 | 425 | public func dragMenuView(_ dragMenuView: DragMenuView, didSelect item: String, at index: Int) { 426 | guard let dragMenu = self.dragMenu else { return } 427 | menuDelegate?.dragMenuView?(dragMenu, didSelect: items[selectedItemIndex], at: selectedItemIndex) 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /DragMenuPicker/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | DragMenuPicker Demo 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 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 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /DragMenuPicker/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // DragMenuPicker 4 | // 5 | // Created by Cem Olcay on 06/09/2017. 6 | // Copyright © 2017 cemolcay. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController, DragMenuViewDelegate { 12 | @IBOutlet weak var scrollView: UIScrollView? 13 | @IBOutlet weak var horizontalDragPicker: DragMenuPicker? 14 | @IBOutlet weak var verticalDragPicker: DragMenuPicker? 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | scrollView?.contentSize.height = 2000 20 | let items = ["First", "Second", "Third", "Fourth", "Other", "Another", "Item 2", "Item 3"] 21 | 22 | // Horizontal DragSwitchPicker 23 | horizontalDragPicker?.title = "Horizontal Picker" 24 | horizontalDragPicker?.items = items 25 | horizontalDragPicker?.direction = .horizontal 26 | horizontalDragPicker?.margins = 20 27 | horizontalDragPicker?.menuDelegate = self 28 | horizontalDragPicker?.didSelectItem = { item, index in 29 | print("\(item) selected at index \(index)") 30 | } 31 | 32 | // VerticalDragPicker 33 | verticalDragPicker?.title = "Vertical Picker" 34 | verticalDragPicker?.items = items 35 | verticalDragPicker?.direction = .vertical 36 | verticalDragPicker?.margins = 40 37 | verticalDragPicker?.menuDelegate = self 38 | verticalDragPicker?.didSelectItem = { item, index in 39 | print("\(item) selected at index \(index)") 40 | } 41 | } 42 | 43 | // MARK: DragMenuViewDelegate 44 | 45 | func dragMenuViewWillDisplayMenu(_ dragMenuView: DragMenuView) { 46 | scrollView?.panGestureRecognizer.isEnabled = false 47 | } 48 | 49 | func dragMenuViewDidDisplayMenu(_ dragMenuView: DragMenuView) { 50 | scrollView?.panGestureRecognizer.isEnabled = true 51 | } 52 | 53 | func dragMenuView(_ dragMenuView: DragMenuView, didSelect item: String, at index: Int) { 54 | print("(from delegate) \(item) selected at index \(index)") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2017, Cem Olcay 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is 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 16 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DragMenuPicker 2 | === 3 | 4 | A custom picker lets you pick an option from its auto scrolling menu without lifting your finger up. 5 | 6 | You can either use the `@IBDesignable` picker button `DragMenuPicker` or create your own with `DragMenuView` which implements all picker logic. 7 | 8 | Demo 9 | ---- 10 | ![alt tag](https://github.com/cemolcay/DragMenuPicker/raw/master/Demo.gif) 11 | 12 | Requirements 13 | ---- 14 | 15 | - iOS 9.0+ 16 | - Swift 3.0+ 17 | 18 | Install 19 | ---- 20 | 21 | ``` 22 | pod 'DragMenuPicker' 23 | ``` 24 | 25 | Usage 26 | ---- 27 | 28 | Create a `DragMenuPicker` from either storyboard or programmatically. 29 | Set its `title` and `items` property to shown in menu. 30 | Set its `didSelectItem` property or implement `dragMenuView(_ dragMenuView: DragMenuView, didSelect item: String, at index: Int)` delegate method to set your action after picking. 31 | You can also set its `direction`, either horizontal or vertical with `margins` to screen edges. 32 | 33 | 34 | ``` swift 35 | horizontalDragPicker?.title = "Horizontal Picker" 36 | horizontalDragPicker?.items = ["First", "Second", "Third", "Fourth", "Other", "Another", "Item 2", "Item 3"] 37 | horizontalDragPicker?.direction = .horizontal 38 | horizontalDragPicker?.margins = 20 39 | horizontalDragPicker?.menuDelegate = self 40 | horizontalDragPicker?.didSelectItem = { item, index in 41 | print("\(item) selected at index \(index)") 42 | } 43 | ``` 44 | 45 | 46 | `DragMenuPicker` shows `DragMenuView` with `DragMenuItemView`s inside when you touch down the picker. It disappears after you pick something from menu or cancel picking by lifting your finger up outside of the menu. 47 | 48 | They are heavily customisable. You can set `applyStyle` property which callbacks you prototype menu and item that you can style and it applies it to menu. 49 | 50 | Also there are `@IBInspectable` properties on `DragMenuPicker` that you can style basic properties inside storyboard. --------------------------------------------------------------------------------