├── .build └── workspace-state.json ├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── Drop Down TextField.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Drop Down TextField.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Drop Down TextField ├── AppDelegate.h ├── AppDelegate.m ├── Drop Down TextField-Info.plist ├── Drop Down TextField-Prefix.pch ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── LaunchScreen.storyboard ├── Main.storyboard ├── Montserrat-Regular.otf ├── ViewController.h ├── ViewController.m ├── en.lproj │ └── InfoPlist.strings └── main.m ├── DropDownTextFieldSwift ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── Info.plist ├── Main.storyboard ├── SceneDelegate.swift └── ViewController.swift ├── IQDropDownTextField.podspec.json ├── IQDropDownTextField ├── IQDropDownTextField+DateTime.h ├── IQDropDownTextField+DateTime.m ├── IQDropDownTextField+Internal.h ├── IQDropDownTextField+Internal.m ├── IQDropDownTextField.h ├── IQDropDownTextField.m ├── IQDropDownTextFieldConstants.h └── PrivacyInfo.xcprivacy ├── IQDropDownTextFieldSwift.podspec.json ├── IQDropDownTextFieldSwift ├── IQDropDownTextField+Date.swift ├── IQDropDownTextField+Menu.swift ├── IQDropDownTextField+Picker.swift ├── IQDropDownTextField.swift ├── IQDropDownTextFieldConstants.swift ├── IQDropDownTextFieldDataSource.swift ├── IQDropDownTextFieldDelegate.swift └── PrivacyInfo.xcprivacy ├── Images ├── date.png ├── date_time.png ├── large_text.png ├── multi_list.png ├── simple.png └── time.png ├── LICENSE ├── LICENSE.md ├── Package.swift ├── Podfile ├── Podfile.lock └── README.md /.build/workspace-state.json: -------------------------------------------------------------------------------- 1 | { 2 | "object" : { 3 | "artifacts" : [ 4 | 5 | ], 6 | "dependencies" : [ 7 | 8 | ] 9 | }, 10 | "version" : 6 11 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | *.xccheckout 19 | 20 | #CocoaPods 21 | Pods 22 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Drop Down TextField.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Drop Down TextField.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Drop Down TextField.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Drop Down TextField.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Drop Down TextField/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Drop Down TextField 4 | // 5 | // Created by hp on 10/11/13. 6 | // Copyright (c) 2013 Iftekhar. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class ViewController; 12 | 13 | @interface AppDelegate : UIResponder 14 | 15 | @property (strong, nonatomic) UIWindow *window; 16 | 17 | @property (strong, nonatomic) ViewController *viewController; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Drop Down TextField/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Drop Down TextField 4 | // 5 | // Created by hp on 10/11/13. 6 | // Copyright (c) 2013 Iftekhar. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import 11 | #import "ViewController.h" 12 | 13 | @implementation AppDelegate 14 | 15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 16 | { 17 | return YES; 18 | } 19 | 20 | - (void)applicationWillResignActive:(UIApplication *)application 21 | { 22 | // 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. 23 | // 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. 24 | } 25 | 26 | - (void)applicationDidEnterBackground:(UIApplication *)application 27 | { 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 | - (void)applicationWillEnterForeground:(UIApplication *)application 33 | { 34 | // 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. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application 38 | { 39 | // 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. 40 | } 41 | 42 | - (void)applicationWillTerminate:(UIApplication *)application 43 | { 44 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /Drop Down TextField/Drop Down TextField-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | DropDown TextField 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIcons 12 | 13 | CFBundleIcons~ipad 14 | 15 | CFBundleIdentifier 16 | $(PRODUCT_BUNDLE_IDENTIFIER) 17 | CFBundleInfoDictionaryVersion 18 | 6.0 19 | CFBundleName 20 | ${PRODUCT_NAME} 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | $(MARKETING_VERSION) 25 | CFBundleSignature 26 | ???? 27 | CFBundleVersion 28 | $(CURRENT_PROJECT_VERSION) 29 | LSRequiresIPhoneOS 30 | 31 | UIAppFonts 32 | 33 | Montserrat-Regular.otf 34 | 35 | UILaunchStoryboardName 36 | LaunchScreen 37 | UIMainStoryboardFile 38 | Main 39 | UIRequiredDeviceCapabilities 40 | 41 | armv7 42 | 43 | UISupportedInterfaceOrientations 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Drop Down TextField/Drop Down TextField-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'Drop Down TextField' target in the 'Drop Down TextField' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_4_0 8 | #warning "This project uses features only available in iOS SDK 4.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /Drop Down TextField/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /Drop Down TextField/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Drop Down TextField/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "11.0", 8 | "subtype" : "2436h", 9 | "scale" : "3x" 10 | }, 11 | { 12 | "orientation" : "portrait", 13 | "idiom" : "iphone", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "8.0", 16 | "subtype" : "736h", 17 | "scale" : "3x" 18 | }, 19 | { 20 | "orientation" : "portrait", 21 | "idiom" : "iphone", 22 | "extent" : "full-screen", 23 | "minimum-system-version" : "8.0", 24 | "subtype" : "667h", 25 | "scale" : "2x" 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | } 32 | } -------------------------------------------------------------------------------- /Drop Down TextField/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 | 29 | 30 | -------------------------------------------------------------------------------- /Drop Down TextField/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 | 33 | 40 | 41 | 42 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 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 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /Drop Down TextField/Montserrat-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQDropDownTextField/099faf11b3b2a18f4852cb76b0b908a334fae05d/Drop Down TextField/Montserrat-Regular.otf -------------------------------------------------------------------------------- /Drop Down TextField/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // Drop Down TextField 4 | // 5 | // Created by hp on 10/11/13. 6 | // Copyright (c) 2013 Iftekhar. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "IQDropDownTextField.h" 11 | 12 | @interface ViewController : UIViewController 13 | { 14 | IBOutlet IQDropDownTextField *textFieldTextPicker; 15 | IBOutlet IQDropDownTextField *textFieldOptionalTextPicker; 16 | IBOutlet IQDropDownTextField *textFieldDatePicker; 17 | IBOutlet IQDropDownTextField *textFieldTimePicker; 18 | IBOutlet IQDropDownTextField *textFieldDateTimePicker; 19 | 20 | } 21 | @end 22 | -------------------------------------------------------------------------------- /Drop Down TextField/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // Drop Down TextField 4 | // 5 | // Created by hp on 10/11/13. 6 | // Copyright (c) 2013 Iftekhar. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | @interface ViewController () 12 | 13 | //@property IQDropDownTextField *dropDown; 14 | 15 | @end 16 | 17 | @implementation ViewController 18 | 19 | - (void)viewDidLoad 20 | { 21 | [super viewDidLoad]; 22 | 23 | // self.dropDown = [[IQDropDownTextField alloc] init]; 24 | // [self.dropDown setItemList:@[@"London",@"Johannesburg",@"Moscow",@"Mumbai",@"Tokyo",@"Sydney",@"Paris",@"Bangkok",@"New York",@"Istanbul",@"Dubai",@"Singapore"]]; 25 | // self.dropDown.dropDownMode = IQDropDownModeTextField; 26 | // self.dropDown.isOptionalDropDown = YES; 27 | // [self.view addSubview:self.dropDown]; 28 | 29 | textFieldTextPicker.showDismissToolbar = YES; 30 | textFieldOptionalTextPicker.showDismissToolbar = YES; 31 | textFieldDatePicker.showDismissToolbar = YES; 32 | textFieldTimePicker.showDismissToolbar = YES; 33 | textFieldDateTimePicker.showDismissToolbar = YES; 34 | 35 | UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; 36 | [indicator startAnimating]; 37 | 38 | UISwitch *aSwitch = [[UISwitch alloc] init]; 39 | 40 | [textFieldTextPicker setItemList:@[@"London",@"Johannesburg",@"Moscow",@"Mumbai",@"Tokyo",@"Sydney",@"Paris",@"Bangkok",@"New York",@"Istanbul",@"Dubai",@"Singapore"]]; 41 | textFieldTextPicker.selectedRow = 2; 42 | [textFieldTextPicker setItemListView:@[[NSNull null],indicator,[NSNull null],aSwitch,[NSNull null],[NSNull null],[NSNull null],[NSNull null],[NSNull null],[NSNull null],[NSNull null],[NSNull null],[NSNull null]]]; 43 | 44 | /* 45 | Uncomment the following lines to set a custom font or text color for the items, as well as a custom text color for 46 | the optional item. 47 | */ 48 | // textFieldTextPicker.font = [UIFont fontWithName:@"Montserrat-Regular" size:16]; 49 | // textFieldTextPicker.textColor = [UIColor redColor]; 50 | // textFieldTextPicker.optionalItemTextColor = [UIColor brownColor]; 51 | 52 | [textFieldOptionalTextPicker setItemList:[NSArray arrayWithObjects:@"1",@"2",@"3",@"4",@"5",@"6", nil]]; 53 | textFieldOptionalTextPicker.selectedRow = 3; 54 | [textFieldOptionalTextPicker setItemListUI:[NSArray arrayWithObjects:@"1 Year Old",@"2 Years Old",@"3 Years Old",@"4 Years Old",@"5 Years Old",@"6 Years Old", nil]]; 55 | 56 | // NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 57 | // [formatter setDateFormat:@"EEE MMMM dd yyyy"]; 58 | // [textFieldDatePicker setDateFormatter:formatter]; 59 | [textFieldDatePicker setDropDownMode:IQDropDownModeDatePicker]; 60 | [textFieldTimePicker setDropDownMode:IQDropDownModeTimePicker]; 61 | [textFieldDateTimePicker setDropDownMode:IQDropDownModeDateTimePicker]; 62 | } 63 | 64 | -(void)textField:(nonnull IQDropDownTextField*)textField didSelectItem:(nullable NSString*)item 65 | { 66 | NSLog(@"%@: %@",NSStringFromSelector(_cmd),item); 67 | } 68 | 69 | -(void)textField:(IQDropDownTextField *)textField didSelectDate:(nullable NSDate *)date 70 | { 71 | NSLog(@"%@: %@",NSStringFromSelector(_cmd),date); 72 | } 73 | 74 | -(BOOL)textField:(nonnull IQDropDownTextField*)textField canSelectItem:(nullable NSString*)item 75 | { 76 | NSLog(@"%@: %@",NSStringFromSelector(_cmd),item); 77 | return YES; 78 | } 79 | 80 | -(IQProposedSelection)textField:(nonnull IQDropDownTextField*)textField proposedSelectionModeForItem:(nullable NSString*)item 81 | { 82 | NSLog(@"%@: %@",NSStringFromSelector(_cmd),item); 83 | return IQProposedSelectionBoth; 84 | } 85 | 86 | -(void)textFieldDidBeginEditing:(UITextField *)textField 87 | { 88 | NSLog(@"%@",NSStringFromSelector(_cmd)); 89 | } 90 | 91 | -(void)textFieldDidEndEditing:(UITextField *)textField 92 | { 93 | NSLog(@"%@",NSStringFromSelector(_cmd)); 94 | } 95 | 96 | -(void)doneClicked:(UIBarButtonItem*)button 97 | { 98 | [self.view endEditing:YES]; 99 | 100 | NSLog(@"textFieldTextPicker.selectedItem: %@", textFieldTextPicker.selectedItem); 101 | NSLog(@"textFieldOptionalTextPicker.selectedItem: %@", textFieldOptionalTextPicker.selectedItem); 102 | NSLog(@"textFieldDatePicker.selectedItem: %@", textFieldDatePicker.selectedItem); 103 | NSLog(@"textFieldTimePicker.selectedItem: %@", textFieldTimePicker.selectedItem); 104 | NSLog(@"textFieldDateTimePicker.selectedItem: %@", textFieldDateTimePicker.selectedItem); 105 | } 106 | 107 | - (IBAction)isOptionalToggle:(UIButton *)sender { 108 | textFieldOptionalTextPicker.isOptionalDropDown = !textFieldOptionalTextPicker.isOptionalDropDown; 109 | textFieldTextPicker.isOptionalDropDown = !textFieldTextPicker.isOptionalDropDown; 110 | textFieldDatePicker.isOptionalDropDown = !textFieldDatePicker.isOptionalDropDown; 111 | textFieldTimePicker.isOptionalDropDown = !textFieldTimePicker.isOptionalDropDown; 112 | textFieldDateTimePicker.isOptionalDropDown = !textFieldDateTimePicker.isOptionalDropDown; 113 | // self.dropDown.isOptionalDropDown = !self.dropDown.isOptionalDropDown; 114 | } 115 | 116 | - (IBAction)resetAction:(UIButton *)sender { 117 | textFieldTextPicker.selectedItem = nil; 118 | textFieldOptionalTextPicker.selectedItem = nil; 119 | textFieldDatePicker.selectedItem = nil; 120 | textFieldTimePicker.date = nil; 121 | textFieldDateTimePicker.selectedItem = nil; 122 | // self.dropDown.selectedItem = nil; 123 | } 124 | 125 | - (void)didReceiveMemoryWarning 126 | { 127 | [super didReceiveMemoryWarning]; 128 | // Dispose of any resources that can be recreated. 129 | } 130 | 131 | @end 132 | -------------------------------------------------------------------------------- /Drop Down TextField/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Drop Down TextField/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Drop Down TextField 4 | // 5 | // Created by hp on 10/11/13. 6 | // Copyright (c) 2013 Iftekhar. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DropDownTextFieldSwift/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DropDownTextFieldSwift 4 | // 5 | // Created by Iftekhar on 31/08/20. 6 | // Copyright © 2020 Iftekhar. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @main 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | func application(_ application: UIApplication, 15 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | @available(iOS 13.0, *) 23 | func application(_ application: UIApplication, 24 | configurationForConnecting connectingSceneSession: UISceneSession, 25 | options: UIScene.ConnectionOptions) -> UISceneConfiguration { 26 | // Called when a new scene session is being created. 27 | // Use this method to select a configuration to create the new scene with. 28 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 29 | } 30 | 31 | @available(iOS 13.0, *) 32 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 33 | // Called when the user discards a scene session. 34 | // If any sessions were discarded while the application was not running, 35 | // this will be called shortly after application:didFinishLaunchingWithOptions. 36 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /DropDownTextFieldSwift/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /DropDownTextFieldSwift/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DropDownTextFieldSwift/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 | -------------------------------------------------------------------------------- /DropDownTextFieldSwift/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | UISupportedInterfaceOrientations~ipad 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationPortraitUpsideDown 60 | UIInterfaceOrientationLandscapeLeft 61 | UIInterfaceOrientationLandscapeRight 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /DropDownTextFieldSwift/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 | 52 | 59 | 66 | 67 | 68 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 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 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /DropDownTextFieldSwift/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // DropDownTextFieldSwift 4 | // 5 | // Created by Iftekhar on 31/08/20. 6 | // Copyright © 2020 Iftekhar. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @available(iOS 13.0, *) 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, 17 | options connectionOptions: UIScene.ConnectionOptions) { 18 | } 19 | 20 | func sceneDidDisconnect(_ scene: UIScene) { 21 | } 22 | 23 | func sceneDidBecomeActive(_ scene: UIScene) { 24 | } 25 | 26 | func sceneWillResignActive(_ scene: UIScene) { 27 | } 28 | 29 | func sceneWillEnterForeground(_ scene: UIScene) { 30 | } 31 | 32 | func sceneDidEnterBackground(_ scene: UIScene) { 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /DropDownTextFieldSwift/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // DropDownTextFieldSwift 4 | // 5 | // Created by Iftekhar on 31/08/20. 6 | // Copyright © 2020 Iftekhar. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import IQDropDownTextFieldSwift 11 | 12 | class ViewController: UIViewController { 13 | 14 | // @IBOutlet var mainStackView: UIStackView! 15 | 16 | @IBOutlet var textFieldTextPicker: IQDropDownTextField! 17 | @IBOutlet var textFieldOptionalTextPicker: IQDropDownTextField! 18 | @IBOutlet var textFieldMultiListTextPicker: IQDropDownTextField! 19 | @IBOutlet var textFieldDatePicker: IQDropDownTextField! 20 | @IBOutlet var textFieldTimePicker: IQDropDownTextField! 21 | @IBOutlet var textFieldDateTimePicker: IQDropDownTextField! 22 | @IBOutlet var menuButton: UIButton! 23 | 24 | // private var dropDown: IQDropDownTextField = IQDropDownTextField() 25 | 26 | // swiftlint:disable function_body_length 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | 30 | // self.dropDown.itemList = ["London", 31 | // "Johannesburg", 32 | // "Moscow", 33 | // "Mumbai", 34 | // "Tokyo", 35 | // "Sydney", 36 | // "Paris", 37 | // "Bangkok", 38 | // "New York", 39 | // "Istanbul", 40 | // "Dubai", 41 | // "Singapore"] 42 | // self.dropDown.dropDownMode = .list 43 | // self.dropDown.selectedRow = 2 44 | // self.dropDown.isOptionalDropDown = true 45 | // self.mainStackView.addArrangedSubview(self.dropDown) 46 | 47 | textFieldTextPicker.showDismissToolbar = true 48 | textFieldOptionalTextPicker.showDismissToolbar = true 49 | textFieldMultiListTextPicker.showDismissToolbar = true 50 | textFieldDatePicker.showDismissToolbar = true 51 | textFieldTimePicker.showDismissToolbar = true 52 | textFieldDateTimePicker.showDismissToolbar = true 53 | 54 | let indicator: UIActivityIndicatorView! = { 55 | if #available(iOS 13.0, *) { 56 | return UIActivityIndicatorView(style: .medium) 57 | } else { 58 | return UIActivityIndicatorView(style: .gray) 59 | } 60 | }() 61 | indicator.startAnimating() 62 | 63 | let aSwitch: UISwitch = UISwitch() 64 | 65 | textFieldTextPicker.itemList = ["London", 66 | "Johannesburg", 67 | "Moscow", 68 | "Mumbai", 69 | "Tokyo", 70 | "Sydney", 71 | "Paris", 72 | "Bangkok", 73 | "New York", 74 | "Istanbul", 75 | "Dubai", 76 | "Singapore"] 77 | 78 | // textFieldTextPicker.itemList = ["Thomas Jefferson High School for Science and Technology", 79 | // "Gwinnett School of Mathematics, Science and Technology", 80 | // "California Academy of Mathematics and Science", 81 | // "Loveless Academic Magnet Program High School", 82 | // "Irma Lerma Rangel Young Women's Leadership School", 83 | // "Middlesex County Academy, Mathematics and Engineering Technologies", 84 | // "Queens High School for the Sciences at York College"] 85 | // textFieldTextPicker.adjustsFontSizeToFitWidth = false 86 | 87 | textFieldTextPicker.selectedRow = 2 88 | let viewList: [UIView?] = [nil, indicator, nil, aSwitch] 89 | textFieldTextPicker.itemListView = viewList 90 | 91 | /* 92 | Uncomment the following lines to set a custom font or text color for the items, 93 | as well as a custom text color for the optional item. 94 | */ 95 | // textFieldTextPicker.font = [UIFont fontWithName:@"Montserrat-Regular" size:16]; 96 | // textFieldTextPicker.textColor = [UIColor redColor]; 97 | // textFieldTextPicker.optionalItemTextColor = [UIColor brownColor]; 98 | 99 | textFieldOptionalTextPicker.itemList = ["1", "2", "3", "4", "5", "6"] 100 | textFieldOptionalTextPicker.selectedRow = 3 101 | 102 | textFieldOptionalTextPicker.selectionFormatHandler = { (selectedItem, _) in 103 | 104 | if let selectedItem = selectedItem { 105 | if selectedItem == "1" { 106 | return selectedItem + " Year Old" 107 | } else { 108 | return selectedItem + " Years Old" 109 | } 110 | } else { 111 | return "" 112 | } 113 | } 114 | textFieldMultiListTextPicker.dropDownMode = .multiList 115 | let heightFeet: [String] = ["Not Sure", "4", "5", "6", "7", "8"] 116 | let heightInches: [String] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"] 117 | 118 | // textFieldMultiListTextPicker.widthsForComponents = [100, 150] 119 | // textFieldMultiListTextPicker.heightsForComponents = [30, 100] 120 | textFieldMultiListTextPicker.isOptionalDropDowns = [true, false] 121 | textFieldMultiListTextPicker.optionalItemTexts = ["Select Feet", "Select Inches"] 122 | textFieldMultiListTextPicker.multiItemList = [heightFeet, heightInches] 123 | textFieldMultiListTextPicker.multiListSelectionFormatHandler = { (selectedItems, selectedIndexes) in 124 | 125 | if selectedIndexes.first == 0 { 126 | return "Not Sure" 127 | } else if let first = selectedItems.first, let first = first, 128 | let last = selectedItems.last, let last = last { 129 | return "\(first)' \(last)\"" 130 | } else { 131 | return "" 132 | } 133 | } 134 | 135 | // NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 136 | // [formatter setDateFormat:@"EEE MMMM dd yyyy"]; 137 | // [textFieldDatePicker setDateFormatter:formatter]; 138 | 139 | textFieldDatePicker.dropDownMode = .date 140 | textFieldTimePicker.dropDownMode = .time 141 | textFieldDateTimePicker.dropDownMode = .dateTime 142 | 143 | textFieldTextPicker.delegate = self 144 | textFieldTextPicker.dataSource = self 145 | 146 | textFieldOptionalTextPicker.delegate = self 147 | textFieldOptionalTextPicker.dataSource = self 148 | 149 | textFieldDatePicker.delegate = self 150 | textFieldDatePicker.dataSource = self 151 | 152 | textFieldTimePicker.delegate = self 153 | textFieldTimePicker.dataSource = self 154 | 155 | textFieldDateTimePicker.delegate = self 156 | textFieldDateTimePicker.dataSource = self 157 | 158 | if #available(iOS 15.0, *) { 159 | menuButton.isHidden = false 160 | } else { 161 | menuButton.isHidden = true 162 | } 163 | 164 | // dropDown.delegate = self 165 | // dropDown.dataSource = self 166 | } 167 | // swiftlint:enable function_body_length 168 | 169 | func textFieldDidBeginEditing(textField: UITextField) { 170 | print(#function) 171 | } 172 | 173 | func textFieldDidEndEditing(textField: UITextField) { 174 | print(#function) 175 | } 176 | 177 | func doneClicked(button: UIBarButtonItem!) { 178 | self.view.endEditing(true) 179 | 180 | // print("textFieldTextPicker.selectedItem: \(textFieldTextPicker.selectedItem)") 181 | // print("textFieldOptionalTextPicker.selectedItem: \(textFieldOptionalTextPicker.selectedItem)") 182 | // print("textFieldDatePicker.selectedItem: \(textFieldDatePicker.selectedItem)") 183 | // print("textFieldTimePicker.selectedItem: \(textFieldTimePicker.selectedItem)") 184 | // print("textFieldDateTimePicker.selectedItem: \(textFieldDateTimePicker.selectedItem)") 185 | } 186 | 187 | @IBAction func menuToggle(_ sender: UIButton) { 188 | if #available(iOS 15.0, *) { 189 | textFieldTextPicker.showMenuButton = !textFieldTextPicker.showMenuButton 190 | textFieldOptionalTextPicker.showMenuButton = !textFieldOptionalTextPicker.showMenuButton 191 | textFieldMultiListTextPicker.showMenuButton = !textFieldMultiListTextPicker.showMenuButton 192 | textFieldDatePicker.showMenuButton = !textFieldDatePicker.showMenuButton 193 | textFieldTimePicker.showMenuButton = !textFieldTimePicker.showMenuButton 194 | textFieldDateTimePicker.showMenuButton = !textFieldDateTimePicker.showMenuButton 195 | } 196 | } 197 | 198 | @IBAction func isOptionalToggle(_ sender: UIButton) { 199 | textFieldTextPicker.isOptionalDropDown = !textFieldTextPicker.isOptionalDropDown 200 | textFieldOptionalTextPicker.isOptionalDropDown = !textFieldOptionalTextPicker.isOptionalDropDown 201 | textFieldMultiListTextPicker.isOptionalDropDown = !textFieldMultiListTextPicker.isOptionalDropDown 202 | textFieldDatePicker.isOptionalDropDown = !textFieldDatePicker.isOptionalDropDown 203 | textFieldTimePicker.isOptionalDropDown = !textFieldTimePicker.isOptionalDropDown 204 | textFieldDateTimePicker.isOptionalDropDown = !textFieldDateTimePicker.isOptionalDropDown 205 | // self.dropDown.isOptionalDropDown = !self.dropDown.isOptionalDropDown 206 | } 207 | 208 | @IBAction func resetAction(_ sender: UIButton) { 209 | textFieldTextPicker.selectedItem = nil 210 | textFieldOptionalTextPicker.selectedItem = nil 211 | textFieldMultiListTextPicker.selectedItem = nil 212 | textFieldDatePicker.selectedItem = nil 213 | textFieldTimePicker.date = nil 214 | textFieldDateTimePicker.selectedItem = nil 215 | // self.dropDown.selectedItem = nil 216 | } 217 | } 218 | 219 | extension ViewController: IQDropDownTextFieldDelegate, IQDropDownTextFieldDataSource { 220 | 221 | func textField(textField: IQDropDownTextField, didSelectItem item: String?) { 222 | print(#function) 223 | // print(item) 224 | } 225 | 226 | func textField(textField: IQDropDownTextField, didSelectItems items: [String?]) { 227 | } 228 | 229 | func textField(textField: IQDropDownTextField, didSelectDate date: Date?) { 230 | print(#function) 231 | } 232 | 233 | func textField(textField: IQDropDownTextField, canSelectItem item: String) -> Bool { 234 | print(#function) 235 | // print(item) 236 | return true 237 | } 238 | 239 | func textField(textField: IQDropDownTextField, proposedSelectionModeForItem item: String) -> IQProposedSelection { 240 | print(#function) 241 | // print(item) 242 | return .both 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /IQDropDownTextField.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "IQDropDownTextField", 3 | "version": "4.0.4", 4 | "source": { 5 | "git": "https://github.com/hackiftekhar/IQDropDownTextField.git", 6 | "tag": "4.0.4" 7 | }, 8 | "summary": "TextField with DropDown support using UIPickerView", 9 | "homepage": "https://github.com/hackiftekhar/IQDropDownTextField", 10 | "license": "MIT", 11 | "authors": { 12 | "Iftekhar Qurashi": "hack.iftekhar@gmail.com" 13 | }, 14 | "platforms": { 15 | "ios": "11.0" 16 | }, 17 | "source_files": [ 18 | "IQDropDownTextField/*.{h,m}" 19 | ], 20 | "resource_bundles": {"IQDropDownTextField": "IQDropDownTextField/PrivacyInfo.xcprivacy"}, 21 | "public_header_files": [ 22 | "IQDropDownTextField/IQDropDownTextField.h", 23 | "IQDropDownTextField/IQDropDownTextField+DateTime.h", 24 | "IQDropDownTextField/IQDropDownTextFieldConstants.h" 25 | ], 26 | "frameworks": [ 27 | "UIKit", 28 | "Foundation", 29 | "CoreGraphics" 30 | ], 31 | "requires_arc": true 32 | } 33 | -------------------------------------------------------------------------------- /IQDropDownTextField/IQDropDownTextField+DateTime.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQDropDownTextField+DateTime.h 3 | // IQDropDownTextField 4 | // 5 | // Created by Iftekhar on 12/19/23. 6 | // 7 | 8 | #import "IQDropDownTextField.h" 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface IQDropDownTextField (DateTime) 13 | 14 | /** 15 | These are the picker object which internally used for showing list. Changing some properties might not work properly so do it at your own risk. 16 | */ 17 | @property (nonnull, nonatomic, readonly) UIDatePicker *datePicker; 18 | @property (nonnull, nonatomic, readonly) UIDatePicker *timePicker; 19 | @property (nonnull, nonatomic, readonly) UIDatePicker *dateTimePicker; 20 | 21 | 22 | ///-------------------------------------------------------- 23 | /// @name IQDropDownModeDatePicker/IQDropDownModeTimePicker 24 | ///*------------------------------------------------------- 25 | 26 | /** 27 | Selected date in UIDatePicker. 28 | */ 29 | @property(nullable, nonatomic, copy) NSDate *date; 30 | 31 | /** 32 | Select date in UIDatePicker. 33 | */ 34 | - (void)setDate:(nullable NSDate *)date animated:(BOOL)animated; 35 | 36 | /** 37 | DateComponents for date picker. 38 | */ 39 | @property (nullable, nonatomic, readonly, copy) NSDateComponents *dateComponents; 40 | 41 | /** 42 | year 43 | */ 44 | @property (nonatomic, readonly) NSInteger year; 45 | 46 | /** 47 | month 48 | */ 49 | @property (nonatomic, readonly) NSInteger month; 50 | 51 | /** 52 | day 53 | */ 54 | @property (nonatomic, readonly) NSInteger day; 55 | 56 | /** 57 | hour 58 | */ 59 | @property (nonatomic, readonly) NSInteger hour; 60 | 61 | /** 62 | minute 63 | */ 64 | @property (nonatomic, readonly) NSInteger minute; 65 | 66 | /** 67 | second 68 | */ 69 | @property (nonatomic, readonly) NSInteger second; 70 | 71 | 72 | ///------------------------------- 73 | /// @name IQDropDownModeDatePicker 74 | ///------------------------------- 75 | 76 | /** 77 | Select date in UIDatePicker. Default is UIDatePickerModeDate 78 | */ 79 | @property (nonatomic, assign) UIDatePickerMode datePickerMode; 80 | 81 | /** 82 | Date formatter to show date as text in textField. 83 | */ 84 | @property (nullable, nonatomic, retain) NSDateFormatter *dateFormatter UI_APPEARANCE_SELECTOR; 85 | 86 | 87 | ///------------------------------- 88 | /// @name IQDropDownModeTimePicker 89 | ///------------------------------- 90 | 91 | /** 92 | Time formatter to show time as text in textField. 93 | */ 94 | @property (nullable, nonatomic, retain) NSDateFormatter *timeFormatter UI_APPEARANCE_SELECTOR; 95 | 96 | @property (nullable, nonatomic, retain) NSDateFormatter *dateTimeFormatter UI_APPEARANCE_SELECTOR; 97 | 98 | @end 99 | 100 | NS_ASSUME_NONNULL_END 101 | -------------------------------------------------------------------------------- /IQDropDownTextField/IQDropDownTextField+DateTime.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQDropDownTextField+DateTime.m 3 | // IQDropDownTextField 4 | // 5 | // Created by Iftekhar on 12/19/23. 6 | // 7 | 8 | #import "IQDropDownTextField+DateTime.h" 9 | #import "IQDropDownTextField+Internal.h" 10 | #import 11 | 12 | @implementation IQDropDownTextField (DateTime) 13 | 14 | - (nonnull UIDatePicker *) timePicker 15 | { 16 | UIDatePicker *_timePicker = objc_getAssociatedObject(self, _cmd); 17 | if (!_timePicker) 18 | { 19 | _timePicker = [[UIDatePicker alloc] init]; 20 | [_timePicker setAutoresizingMask:(UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight)]; 21 | [_timePicker setDatePickerMode:UIDatePickerModeTime]; 22 | if (@available(iOS 13.4, *)) { 23 | [_timePicker setPreferredDatePickerStyle:UIDatePickerStyleWheels]; 24 | } 25 | [_timePicker addTarget:self action:@selector(timeChanged:) forControlEvents:UIControlEventValueChanged]; 26 | objc_setAssociatedObject(self, _cmd, _timePicker, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 27 | } 28 | 29 | return _timePicker; 30 | } 31 | 32 | - (nonnull UIDatePicker *) dateTimePicker 33 | { 34 | UIDatePicker *_dateTimePicker = objc_getAssociatedObject(self, _cmd); 35 | 36 | if (!_dateTimePicker) 37 | { 38 | _dateTimePicker = [[UIDatePicker alloc] init]; 39 | [_dateTimePicker setAutoresizingMask:(UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight)]; 40 | [_dateTimePicker setDatePickerMode:UIDatePickerModeDateAndTime]; 41 | if (@available(iOS 13.4, *)) { 42 | [_dateTimePicker setPreferredDatePickerStyle:UIDatePickerStyleWheels]; 43 | } 44 | [_dateTimePicker addTarget:self action:@selector(dateTimeChanged:) forControlEvents:UIControlEventValueChanged]; 45 | objc_setAssociatedObject(self, _cmd, _dateTimePicker, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 46 | } 47 | return _dateTimePicker; 48 | } 49 | 50 | - (nonnull UIDatePicker *) datePicker 51 | { 52 | UIDatePicker *_datePicker = objc_getAssociatedObject(self, _cmd); 53 | 54 | if (!_datePicker) 55 | { 56 | _datePicker = [[UIDatePicker alloc] init]; 57 | [_datePicker setAutoresizingMask:(UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight)]; 58 | [_datePicker setDatePickerMode:UIDatePickerModeDate]; 59 | if (@available(iOS 13.4, *)) { 60 | [_datePicker setPreferredDatePickerStyle:UIDatePickerStyleWheels]; 61 | } 62 | [_datePicker addTarget:self action:@selector(dateChanged:) forControlEvents:UIControlEventValueChanged]; 63 | objc_setAssociatedObject(self, _cmd, _datePicker, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 64 | } 65 | 66 | return _datePicker; 67 | } 68 | 69 | #pragma mark - UIDatePicker delegate 70 | 71 | - (void)dateChanged:(nonnull UIDatePicker *)datePicker 72 | { 73 | [self _setSelectedItem:[self.dateFormatter stringFromDate:datePicker.date] animated:NO shouldNotifyDelegate:YES]; 74 | } 75 | 76 | - (void)timeChanged:(nonnull UIDatePicker *)timePicker 77 | { 78 | [self _setSelectedItem:[self.timeFormatter stringFromDate:timePicker.date] animated:NO shouldNotifyDelegate:YES]; 79 | } 80 | 81 | - (void)dateTimeChanged:(nonnull UIDatePicker *)datePicker 82 | { 83 | [self _setSelectedItem:[self.dateTimeFormatter stringFromDate:datePicker.date] animated:NO shouldNotifyDelegate:YES]; 84 | } 85 | 86 | -(nullable NSDate *)date 87 | { 88 | switch (self.dropDownMode) 89 | { 90 | case IQDropDownModeDatePicker: 91 | { 92 | if (self.isOptionalDropDown) 93 | { 94 | return [super.text length] ? [self.datePicker.date copy] : nil; break; 95 | } 96 | else 97 | { 98 | return [self.datePicker.date copy]; 99 | } 100 | } 101 | case IQDropDownModeTimePicker: 102 | { 103 | if (self.isOptionalDropDown) 104 | { 105 | return [super.text length] ? [self.timePicker.date copy] : nil; break; 106 | } 107 | else 108 | { 109 | return [self.timePicker.date copy]; 110 | } 111 | } 112 | case IQDropDownModeDateTimePicker: 113 | { 114 | if (self.isOptionalDropDown) 115 | { 116 | return [super.text length] ? [self.dateTimePicker.date copy] : nil; break; 117 | } 118 | else 119 | { 120 | return [self.dateTimePicker.date copy]; 121 | } 122 | } 123 | default: return nil; break; 124 | } 125 | } 126 | 127 | -(void)setDate:(nullable NSDate *)date 128 | { 129 | [self setDate:date animated:NO]; 130 | } 131 | 132 | - (void)setDate:(nullable NSDate *)date animated:(BOOL)animated 133 | { 134 | switch (self.dropDownMode) 135 | { 136 | case IQDropDownModeDatePicker: 137 | [self _setSelectedItem:[self.dateFormatter stringFromDate:date] animated:animated shouldNotifyDelegate:NO]; 138 | break; 139 | case IQDropDownModeTimePicker: 140 | [self _setSelectedItem:[self.timeFormatter stringFromDate:date] animated:animated shouldNotifyDelegate:NO]; 141 | break; 142 | case IQDropDownModeDateTimePicker: 143 | [self _setSelectedItem:[self.dateTimeFormatter stringFromDate:date] animated:animated shouldNotifyDelegate:NO]; 144 | break; 145 | default: 146 | break; 147 | } 148 | } 149 | 150 | -(nullable NSDateComponents *)dateComponents 151 | { 152 | return [[NSCalendar currentCalendar] components: kCFCalendarUnitSecond | kCFCalendarUnitMinute | kCFCalendarUnitHour | kCFCalendarUnitDay | kCFCalendarUnitMonth | kCFCalendarUnitYear | kCFCalendarUnitEra fromDate:self.date]; 153 | } 154 | 155 | - (NSInteger)year { return [[self dateComponents] year]; } 156 | - (NSInteger)month { return [[self dateComponents] month]; } 157 | - (NSInteger)day { return [[self dateComponents] day]; } 158 | - (NSInteger)hour { return [[self dateComponents] hour]; } 159 | - (NSInteger)minute { return [[self dateComponents] minute]; } 160 | - (NSInteger)second { return [[self dateComponents] second]; } 161 | 162 | -(UIDatePickerMode)datePickerMode 163 | { 164 | NSNumber *datePickerMode = objc_getAssociatedObject(self, @selector(datePickerMode)); 165 | return [datePickerMode integerValue]; 166 | } 167 | 168 | - (void)setDatePickerMode:(UIDatePickerMode)datePickerMode 169 | { 170 | if (self.dropDownMode == IQDropDownModeDatePicker) 171 | { 172 | objc_setAssociatedObject(self, @selector(datePickerMode), @(datePickerMode), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 173 | 174 | [self.datePicker setDatePickerMode:datePickerMode]; 175 | 176 | switch (datePickerMode) { 177 | case UIDatePickerModeCountDownTimer: 178 | [self.dateFormatter setDateStyle:NSDateFormatterNoStyle]; 179 | [self.dateFormatter setTimeStyle:NSDateFormatterNoStyle]; 180 | break; 181 | case UIDatePickerModeDate: 182 | [self.dateFormatter setDateStyle:NSDateFormatterShortStyle]; 183 | [self.dateFormatter setTimeStyle:NSDateFormatterNoStyle]; 184 | break; 185 | case UIDatePickerModeTime: 186 | [self.timeFormatter setDateStyle:NSDateFormatterNoStyle]; 187 | [self.timeFormatter setTimeStyle:NSDateFormatterShortStyle]; 188 | break; 189 | case UIDatePickerModeDateAndTime: 190 | [self.dateTimeFormatter setDateStyle:NSDateFormatterShortStyle]; 191 | [self.dateTimeFormatter setTimeStyle:NSDateFormatterShortStyle]; 192 | break; 193 | } 194 | } 195 | } 196 | 197 | -(nullable NSDateFormatter *)dateFormatter 198 | { 199 | NSDateFormatter *dateFormatter = objc_getAssociatedObject(self, @selector(dateFormatter)); 200 | 201 | if (!dateFormatter) { 202 | 203 | if ([[IQDropDownTextField appearance] dateFormatter]) 204 | { 205 | dateFormatter = [[IQDropDownTextField appearance] dateFormatter]; 206 | } 207 | else 208 | { 209 | static NSDateFormatter *defaultDateFormatter = nil; 210 | 211 | static dispatch_once_t onceToken; 212 | dispatch_once(&onceToken, ^{ 213 | defaultDateFormatter = [[NSDateFormatter alloc] init]; 214 | [defaultDateFormatter setDateStyle:NSDateFormatterMediumStyle]; 215 | [defaultDateFormatter setTimeStyle:NSDateFormatterNoStyle]; 216 | }); 217 | 218 | dateFormatter = defaultDateFormatter; 219 | } 220 | } 221 | 222 | return dateFormatter; 223 | } 224 | 225 | -(void)setDateFormatter:(nullable NSDateFormatter *)dateFormatter 226 | { 227 | objc_setAssociatedObject(self, @selector(dateFormatter), dateFormatter, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 228 | [self.datePicker setLocale:dateFormatter.locale]; 229 | } 230 | 231 | -(nullable NSDateFormatter *)timeFormatter 232 | { 233 | NSDateFormatter *timeFormatter = objc_getAssociatedObject(self, @selector(timeFormatter)); 234 | 235 | if (!timeFormatter) 236 | { 237 | if ([[IQDropDownTextField appearance] timeFormatter]) 238 | { 239 | timeFormatter = [[IQDropDownTextField appearance] timeFormatter]; 240 | } 241 | else 242 | { 243 | static NSDateFormatter *defaultTimeFormatter = nil; 244 | 245 | static dispatch_once_t onceToken; 246 | dispatch_once(&onceToken, ^{ 247 | defaultTimeFormatter = [[NSDateFormatter alloc] init]; 248 | [defaultTimeFormatter setDateStyle:NSDateFormatterNoStyle]; 249 | [defaultTimeFormatter setTimeStyle:NSDateFormatterShortStyle]; 250 | }); 251 | 252 | timeFormatter = defaultTimeFormatter; 253 | } 254 | } 255 | 256 | return timeFormatter; 257 | } 258 | 259 | -(void)setTimeFormatter:(nullable NSDateFormatter *)timeFormatter 260 | { 261 | objc_setAssociatedObject(self, @selector(timeFormatter), timeFormatter, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 262 | [self.timePicker setLocale:timeFormatter.locale]; 263 | } 264 | 265 | -(nullable NSDateFormatter *)dateTimeFormatter 266 | { 267 | NSDateFormatter *dateTimeFormatter = objc_getAssociatedObject(self, @selector(dateTimeFormatter)); 268 | 269 | if (!dateTimeFormatter) { 270 | if ([[IQDropDownTextField appearance] dateTimeFormatter]) 271 | { 272 | dateTimeFormatter = [[IQDropDownTextField appearance] dateTimeFormatter]; 273 | } 274 | else 275 | { 276 | static NSDateFormatter *defaultDateTimeFormatter = nil; 277 | 278 | static dispatch_once_t onceToken; 279 | dispatch_once(&onceToken, ^{ 280 | defaultDateTimeFormatter = [[NSDateFormatter alloc] init]; 281 | [defaultDateTimeFormatter setDateStyle:NSDateFormatterMediumStyle]; 282 | [defaultDateTimeFormatter setTimeStyle:NSDateFormatterShortStyle]; 283 | }); 284 | 285 | dateTimeFormatter = defaultDateTimeFormatter; 286 | } 287 | } 288 | 289 | return dateTimeFormatter; 290 | } 291 | 292 | -(void)setDateTimeFormatter:(nullable NSDateFormatter *)dateTimeFormatter 293 | { 294 | objc_setAssociatedObject(self, @selector(dateTimeFormatter), dateTimeFormatter, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 295 | [self.dateTimePicker setLocale:dateTimeFormatter.locale]; 296 | } 297 | 298 | @end 299 | -------------------------------------------------------------------------------- /IQDropDownTextField/IQDropDownTextField+Internal.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQDropDownTextField+IQDropDownTextField_Internal.h 3 | // IQDropDownTextField 4 | // 5 | // Created by Iftekhar on 12/19/23. 6 | // 7 | 8 | #import "IQDropDownTextField.h" 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface IQDropDownTextField (Internal) 13 | 14 | -(void)_setSelectedItem:(nullable NSString *)selectedItem animated:(BOOL)animated shouldNotifyDelegate:(BOOL)shouldNotifyDelegate; 15 | 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | -------------------------------------------------------------------------------- /IQDropDownTextField/IQDropDownTextField+Internal.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQDropDownTextField+Internal.m 3 | // IQDropDownTextField 4 | // 5 | // Created by Iftekhar on 12/19/23. 6 | // 7 | 8 | #import "IQDropDownTextField+Internal.h" 9 | #import "IQDropDownTextField+DateTime.h" 10 | 11 | @implementation IQDropDownTextField (Internal) 12 | 13 | -(void)_setSelectedItem:(NSString *)selectedItem animated:(BOOL)animated shouldNotifyDelegate:(BOOL)shouldNotifyDelegate 14 | { 15 | switch (self.dropDownMode) 16 | { 17 | case IQDropDownModeTextPicker: 18 | { 19 | if ([self.itemList containsObject:selectedItem]) 20 | { 21 | NSInteger index = [self.itemList indexOfObject:selectedItem]; 22 | [self setSelectedRow:index animated:animated]; 23 | 24 | if (shouldNotifyDelegate && [self.delegate respondsToSelector:@selector(textField:didSelectItem:row:)]) 25 | [self.delegate textField:self didSelectItem:selectedItem row:index]; 26 | } 27 | else 28 | { 29 | NSInteger selectedIndex = self.isOptionalDropDown ? IQOptionalTextFieldIndex : 0; 30 | 31 | [self setSelectedRow:selectedIndex animated:animated]; 32 | 33 | if (shouldNotifyDelegate && [self.delegate respondsToSelector:@selector(textField:didSelectItem:row:)]) 34 | [self.delegate textField:self didSelectItem:nil row:selectedIndex]; 35 | } 36 | } 37 | break; 38 | case IQDropDownModeDatePicker: 39 | { 40 | NSDate *date = [self.dateFormatter dateFromString:selectedItem]; 41 | if (date) 42 | { 43 | super.text = selectedItem; 44 | [self.datePicker setDate:date animated:animated]; 45 | 46 | if (shouldNotifyDelegate && [self.delegate respondsToSelector:@selector(textField:didSelectDate:)]) 47 | [self.delegate textField:self didSelectDate:date]; 48 | } 49 | else if (self.isOptionalDropDown && [selectedItem length] == 0) 50 | { 51 | super.text = @""; 52 | [self.datePicker setDate:[NSDate date] animated:animated]; 53 | 54 | if (shouldNotifyDelegate && [self.delegate respondsToSelector:@selector(textField:didSelectDate:)]) 55 | [self.delegate textField:self didSelectDate:nil]; 56 | } 57 | break; 58 | } 59 | case IQDropDownModeTimePicker: 60 | { 61 | NSDate *time = [self.timeFormatter dateFromString:selectedItem]; 62 | 63 | if (time) 64 | { 65 | NSDate *day = [NSDate dateWithTimeIntervalSinceReferenceDate: 0]; 66 | NSDateComponents *componentsDay = [[NSCalendar currentCalendar] components: NSCalendarUnitEra | NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay fromDate: day]; 67 | NSDateComponents *componentsTime = [[NSCalendar currentCalendar] components: NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond fromDate: time]; 68 | componentsDay.hour = componentsTime.hour; 69 | componentsDay.minute = componentsTime.minute; 70 | componentsDay.second = componentsTime.second; 71 | 72 | NSDate *date = [[NSCalendar currentCalendar] dateFromComponents: componentsDay]; 73 | 74 | super.text = selectedItem; 75 | [self.timePicker setDate:date animated:animated]; 76 | 77 | if (shouldNotifyDelegate && [self.delegate respondsToSelector:@selector(textField:didSelectDate:)]) 78 | [self.delegate textField:self didSelectDate:date]; 79 | } 80 | else if (self.isOptionalDropDown && [selectedItem length] == 0) 81 | { 82 | super.text = @""; 83 | [self.timePicker setDate:[NSDate date] animated:animated]; 84 | 85 | if (shouldNotifyDelegate && [self.delegate respondsToSelector:@selector(textField:didSelectDate:)]) 86 | [self.delegate textField:self didSelectDate:nil]; 87 | } 88 | break; 89 | } 90 | case IQDropDownModeDateTimePicker: 91 | { 92 | NSDate *date = [self.dateTimeFormatter dateFromString:selectedItem]; 93 | if (date) 94 | { 95 | super.text = selectedItem; 96 | [self.dateTimePicker setDate:date animated:animated]; 97 | 98 | if (shouldNotifyDelegate && [self.delegate respondsToSelector:@selector(textField:didSelectDate:)]) 99 | [self.delegate textField:self didSelectDate:date]; 100 | } 101 | else if (self.isOptionalDropDown && [selectedItem length] == 0) 102 | { 103 | super.text = @""; 104 | [self.dateTimePicker setDate:[NSDate date] animated:animated]; 105 | 106 | if (shouldNotifyDelegate && [self.delegate respondsToSelector:@selector(textField:didSelectDate:)]) 107 | [self.delegate textField:self didSelectDate:nil]; 108 | } 109 | break; 110 | } 111 | case IQDropDownModeTextField:{ 112 | super.text = selectedItem; 113 | } 114 | break; 115 | } 116 | } 117 | 118 | @end 119 | -------------------------------------------------------------------------------- /IQDropDownTextField/IQDropDownTextField.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQDropDownTextField.h 3 | // https://github.com/hackiftekhar/IQDropDownTextField 4 | // Copyright (c) 2013-15 Iftekhar Qurashi. 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 26 | #import "IQDropDownTextFieldConstants.h" 27 | 28 | NS_ASSUME_NONNULL_BEGIN 29 | 30 | /** 31 | Integer constant to use with `selectedRow` property, this will select `Select` option in optional textField. 32 | */ 33 | extern NSInteger const IQOptionalTextFieldIndex; 34 | 35 | 36 | @class IQDropDownTextField; 37 | 38 | /** 39 | Drop down text field delegate. 40 | */ 41 | @protocol IQDropDownTextFieldDelegate 42 | 43 | @optional 44 | -(void)textField:(nonnull IQDropDownTextField*)textField didSelectItem:(nullable NSString*)item row:(NSInteger)row; //Called when textField changes it's selected item. Supported for IQDropDownModeTextPicker 45 | 46 | -(void)textField:(nonnull IQDropDownTextField*)textField didSelectDate:(nullable NSDate*)date; //Called when textField changes it's selected item. Supported for IQDropDownModeTimePicker, IQDropDownModeDatePicker, IQDropDownModeDateTimePicker 47 | 48 | @end 49 | 50 | 51 | /** 52 | Drop down text field data source. This is only valid for IQDropDownModeTextField mode 53 | */ 54 | @protocol IQDropDownTextFieldDataSource 55 | 56 | @optional 57 | -(BOOL)textField:(nonnull IQDropDownTextField*)textField canSelectItem:(nonnull NSString*)item row:(NSInteger)row; //Check if an item can be selected by dropdown texField. 58 | -(IQProposedSelection)textField:(nonnull IQDropDownTextField*)textField proposedSelectionModeForItem:(nonnull NSString*)item row:(NSInteger)row; //If canSelectItem return NO, then textField:proposedSelectionModeForItem: asked for proposed selection mode. 59 | 60 | @end 61 | 62 | 63 | /** 64 | Add a UIPickerView as inputView 65 | */ 66 | @interface IQDropDownTextField : UITextField 67 | 68 | /** 69 | This is picker object which internally used for showing list. Changing some properties might not work properly so do it at your own risk. 70 | */ 71 | @property (nonnull, nonatomic, readonly) UIPickerView *pickerView; 72 | 73 | @property(nullable, nonatomic,weak) IBOutlet id delegate; // default is nil. weak reference 74 | @property(nullable, nonatomic,weak) IBOutlet id dataSource; // default is nil. weak reference 75 | 76 | /** 77 | If YES then a toolbar will be added at the top to dismiss textfield, if NO then toolbar will be removed. Default to NO. 78 | */ 79 | @property (nonatomic, assign) BOOL showDismissToolbar; 80 | 81 | /** 82 | DropDownMode style to show in picker. Default is IQDropDownModeTextPicker. 83 | */ 84 | @property (nonatomic, assign) IQDropDownMode dropDownMode; 85 | 86 | /** 87 | Label for the optional item if isOptionalDropDown is YES. Default is Select. 88 | */ 89 | @property (nullable, nonatomic, copy) IBInspectable NSString *optionalItemText; 90 | 91 | /** 92 | If YES then it will add a optionalItemLabel item at top of dropDown list. If NO then first field will automatically be selected. Default is YES 93 | */ 94 | @property (nonatomic, assign) IBInspectable BOOL isOptionalDropDown; 95 | 96 | /** 97 | Use selectedItem property to get/set dropdown text. 98 | */ 99 | @property(nullable, nonatomic,copy) NSString *text NS_DEPRECATED_IOS(3_0, 5_0, "Please use selectedItem property to get/set dropdown selected text instead"); 100 | 101 | /** 102 | attributedText is unavailable in IQDropDownTextField. 103 | */ 104 | @property(nullable, nonatomic,copy) NSAttributedString *attributedText NS_UNAVAILABLE; 105 | 106 | /** 107 | Sets a custom font for the IQDropdownTextField items. Default is boldSystemFontOfSize:18.0. 108 | */ 109 | @property (nullable, strong, nonatomic) UIFont *dropDownFont; 110 | 111 | /** 112 | Sets a custom color for the IQDropdownTextField items. Default is blackColor. 113 | */ 114 | @property (nullable, strong, nonatomic) UIColor *dropDownTextColor; 115 | 116 | /** 117 | Sets a custom color for the optional item. Default is lightGrayColor. 118 | */ 119 | @property (nullable, strong, nonatomic) UIColor *optionalItemTextColor; 120 | 121 | 122 | ///---------------------- 123 | /// @name Title Selection 124 | ///---------------------- 125 | 126 | /** 127 | Selected item of pickerView. 128 | */ 129 | @property (nullable, nonatomic, copy) NSString *selectedItem; 130 | 131 | /** 132 | Set selected item of pickerView. 133 | */ 134 | - (void)setSelectedItem:(nullable NSString*)selectedItem animated:(BOOL)animated; 135 | 136 | 137 | ///------------------------------- 138 | /// @name IQDropDownModeTextPicker 139 | ///------------------------------- 140 | 141 | /** 142 | Items to show in pickerView. For example. @[ @"1", @"2", @"3" ]. This field must be set. 143 | */ 144 | @property (nullable, nonatomic, copy) NSArray *itemList; 145 | 146 | /** 147 | UIView items to show in pickerView. For example. @[ view1, view2, view3 ]. If itemList text needs to be shown then pass [NSNull null] instead of UIView object at appropriate index. This field is optional. 148 | */ 149 | @property (nullable, nonatomic, copy) NSArray <__kindof NSObject*> *itemListView; 150 | 151 | /** 152 | If this is set then we'll show textfield's text from this list instead from regular itemList. This is only for showing different messaging in textfield's text. This itemListUI array count must be equal to itemList array count. 153 | */ 154 | @property (nullable, nonatomic, copy) NSArray *itemListUI; 155 | 156 | /** 157 | Selected row index of selected item. 158 | */ 159 | @property (nonatomic, assign) NSInteger selectedRow; 160 | 161 | /** 162 | Defines Picker labels fontSizeAdjustment by width. Default is NO 163 | */ 164 | @property (nonatomic, assign) IBInspectable BOOL adjustPickerLabelFontSizeWidth; 165 | 166 | /** 167 | Select row index of selected item. 168 | */ 169 | - (void)setSelectedRow:(NSInteger)row animated:(BOOL)animated; 170 | 171 | @end 172 | 173 | NS_ASSUME_NONNULL_END 174 | 175 | // This import is forcefully written as bottom to automatically include the DateTime category when importing IQDropDownTextField 176 | #import "IQDropDownTextField+DateTime.h" 177 | -------------------------------------------------------------------------------- /IQDropDownTextField/IQDropDownTextField.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQDropDownTextField.m 3 | // https://github.com/hackiftekhar/IQDropDownTextField 4 | // Copyright (c) 2013-15 Iftekhar Qurashi. 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 "IQDropDownTextField.h" 26 | #import "IQDropDownTextField+Internal.h" 27 | 28 | NSInteger const IQOptionalTextFieldIndex = -1; 29 | 30 | @interface IQDropDownTextField () 31 | 32 | @property (nonnull, nonatomic, strong) UIToolbar *dismissToolbar; 33 | 34 | @property BOOL hasSetInitialIsOptional; 35 | @property NSInteger pickerSelectedRow; 36 | 37 | @end 38 | 39 | @implementation IQDropDownTextField 40 | 41 | @synthesize dropDownMode = _dropDownMode; 42 | @synthesize itemList = _itemList; 43 | @synthesize isOptionalDropDown = _isOptionalDropDown; 44 | @synthesize optionalItemText = _optionalItemText; 45 | @synthesize adjustPickerLabelFontSizeWidth = _adjustPickerLabelFontSizeWidth; 46 | 47 | @synthesize dropDownFont = _dropDownFont; 48 | @synthesize dropDownTextColor = _dropDownTextColor; 49 | @synthesize optionalItemTextColor = _optionalItemTextColor; 50 | 51 | @dynamic delegate; 52 | @dynamic text; 53 | @dynamic attributedText; 54 | 55 | @synthesize pickerView = _pickerView; 56 | 57 | #pragma mark - NSObject 58 | 59 | - (void)dealloc { 60 | [_pickerView setDelegate:nil]; 61 | [_pickerView setDataSource:nil]; 62 | _pickerView = nil; 63 | self.delegate = nil; 64 | _dataSource = nil; 65 | _optionalItemText = nil; 66 | _itemList = nil; 67 | _dropDownFont = nil; 68 | _dropDownTextColor = nil; 69 | _optionalItemTextColor = nil; 70 | } 71 | 72 | #pragma mark - Initialization 73 | 74 | - (void)initialize 75 | { 76 | [self setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter]; 77 | [self setContentHorizontalAlignment:UIControlContentHorizontalAlignmentCenter]; 78 | 79 | if (self.optionalItemText == nil) 80 | { 81 | self.optionalItemText = NSLocalizedString(@"Select", nil); 82 | } 83 | 84 | //These will update the UI and other components, all the validation added if awakeFromNib for textField is called after custom UIView awakeFromNib call 85 | { 86 | self.dropDownMode = self.dropDownMode; 87 | 88 | self.isOptionalDropDown = self.hasSetInitialIsOptional?self.isOptionalDropDown:YES; 89 | self.adjustPickerLabelFontSizeWidth = self.adjustPickerLabelFontSizeWidth; 90 | } 91 | _pickerSelectedRow = -1; 92 | } 93 | 94 | - (instancetype)initWithFrame:(CGRect)frame 95 | { 96 | self = [super initWithFrame:frame]; 97 | if (self) 98 | { 99 | [self initialize]; 100 | } 101 | return self; 102 | } 103 | 104 | -(void)awakeFromNib 105 | { 106 | [super awakeFromNib]; 107 | [self initialize]; 108 | } 109 | 110 | - (BOOL)becomeFirstResponder 111 | { 112 | BOOL result = [super becomeFirstResponder]; 113 | if (_pickerSelectedRow >= 0) { 114 | [_pickerView selectRow:_pickerSelectedRow inComponent:0 animated:NO]; 115 | } 116 | 117 | return result; 118 | } 119 | 120 | #pragma mark - UITextField overrides 121 | 122 | - (CGRect)caretRectForPosition:(nonnull UITextPosition *)position 123 | { 124 | if (self.dropDownMode == IQDropDownModeTextField) { 125 | return [super caretRectForPosition:position]; 126 | } else { 127 | return CGRectZero; 128 | } 129 | } 130 | 131 | #pragma mark - UIPickerView data source 132 | 133 | - (NSInteger)numberOfComponentsInPickerView:(nonnull UIPickerView *)pickerView 134 | { 135 | return 1; 136 | } 137 | 138 | - (NSInteger)pickerView:(nonnull UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component 139 | { 140 | return self.itemList.count + (self.isOptionalDropDown ? 1 : 0); 141 | } 142 | 143 | #pragma mark UIPickerView delegate 144 | 145 | - (nonnull UIView *)pickerView:(nonnull UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(nullable UIView *)view 146 | { 147 | row = row - (self.isOptionalDropDown ? 1 : 0); 148 | 149 | if (row == IQOptionalTextFieldIndex) { 150 | UILabel *labelText = (UILabel*)view; 151 | 152 | if (labelText == nil) 153 | { 154 | labelText = [[UILabel alloc] init]; 155 | [labelText setTextAlignment:NSTextAlignmentCenter]; 156 | [labelText setAdjustsFontSizeToFitWidth:YES]; 157 | labelText.backgroundColor = [UIColor clearColor]; 158 | labelText.backgroundColor = [UIColor clearColor]; 159 | } 160 | 161 | if (_dropDownFont) { 162 | if (_dropDownFont.pointSize < 30) { 163 | labelText.font = [_dropDownFont fontWithSize:30]; 164 | } else { 165 | labelText.font = _dropDownFont; 166 | } 167 | } else { 168 | labelText.font = [UIFont boldSystemFontOfSize:30.0]; 169 | } 170 | 171 | labelText.textColor = _optionalItemTextColor ? _optionalItemTextColor : [UIColor lightGrayColor]; 172 | labelText.text = self.optionalItemText; 173 | 174 | return labelText; 175 | 176 | } else { 177 | if (_itemListView.count > row && [_itemListView[row] isKindOfClass:[UIView class]]) 178 | { 179 | //Archiving and Unarchiving is necessary to copy UIView instance. 180 | NSData *viewData = [NSKeyedArchiver archivedDataWithRootObject:_itemListView[row]]; 181 | UIView *copyOfView = [NSKeyedUnarchiver unarchiveObjectWithData:viewData]; 182 | return copyOfView; 183 | } 184 | else 185 | { 186 | UILabel *labelText = (UILabel*)view; 187 | if (labelText == nil) 188 | { 189 | labelText = [[UILabel alloc] init]; 190 | [labelText setTextAlignment:NSTextAlignmentCenter]; 191 | [labelText setAdjustsFontSizeToFitWidth:YES]; 192 | labelText.backgroundColor = [UIColor clearColor]; 193 | labelText.backgroundColor = [UIColor clearColor]; 194 | } 195 | 196 | if (_dropDownFont) { 197 | labelText.font = _dropDownFont; 198 | } else { 199 | labelText.font = [UIFont boldSystemFontOfSize:18.0]; 200 | } 201 | 202 | NSString *text = [self.itemList objectAtIndex:row]; 203 | 204 | [labelText setText:text]; 205 | 206 | { 207 | BOOL canSelect = YES; 208 | 209 | if ([self.dataSource respondsToSelector:@selector(textField:canSelectItem:row:)]) 210 | { 211 | canSelect = [self.dataSource textField:self canSelectItem:text row:row]; 212 | } 213 | 214 | if (canSelect) 215 | { 216 | if (_dropDownTextColor) { 217 | labelText.textColor = _dropDownTextColor; 218 | } else { 219 | labelText.textColor = [UIColor blackColor]; 220 | } 221 | } 222 | else 223 | { 224 | labelText.textColor = [UIColor lightGrayColor]; 225 | } 226 | 227 | labelText.adjustsFontSizeToFitWidth = self.adjustPickerLabelFontSizeWidth; 228 | } 229 | return labelText; 230 | } 231 | } 232 | } 233 | 234 | - (void)pickerView:(nonnull UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component 235 | { 236 | _pickerSelectedRow = row; 237 | 238 | row = row - (self.isOptionalDropDown ? 1 : 0); 239 | 240 | if (row == IQOptionalTextFieldIndex) { 241 | [self _setSelectedItem:nil animated:NO shouldNotifyDelegate:YES]; 242 | } else if (row >= 0) { 243 | 244 | NSString *text = [self.itemList objectAtIndex:row]; 245 | 246 | BOOL canSelect = YES; 247 | 248 | if ([self.dataSource respondsToSelector:@selector(textField:canSelectItem:row:)]) 249 | { 250 | canSelect = [self.dataSource textField:self canSelectItem:text row:row]; 251 | } 252 | 253 | if (canSelect) 254 | { 255 | [self _setSelectedItem:text animated:NO shouldNotifyDelegate:YES]; 256 | } 257 | else 258 | { 259 | IQProposedSelection proposedSelection = IQProposedSelectionBoth; 260 | 261 | if ([self.dataSource respondsToSelector:@selector(textField:proposedSelectionModeForItem:row:)]) 262 | { 263 | proposedSelection = [self.dataSource textField:self proposedSelectionModeForItem:text row:row]; 264 | } 265 | 266 | NSInteger aboveIndex = row-1; 267 | NSInteger belowIndex = row+1; 268 | 269 | if (proposedSelection == IQProposedSelectionAbove) 270 | { 271 | belowIndex = self.itemList.count; 272 | } 273 | else if (proposedSelection == IQProposedSelectionBelow) 274 | { 275 | aboveIndex = -1; 276 | } 277 | 278 | while (aboveIndex >= 0 || belowIndex < self.itemList.count) 279 | { 280 | if (aboveIndex >= 0) 281 | { 282 | NSString *aboveText = [self.itemList objectAtIndex:aboveIndex]; 283 | 284 | if ([self.dataSource textField:self canSelectItem:aboveText row:aboveIndex]) 285 | { 286 | [self _setSelectedItem:aboveText animated:YES shouldNotifyDelegate:YES]; 287 | return; 288 | } 289 | 290 | aboveIndex--; 291 | } 292 | 293 | if (belowIndex < self.itemList.count) 294 | { 295 | NSString *belowText = [self.itemList objectAtIndex:aboveIndex]; 296 | 297 | if ([self.dataSource textField:self canSelectItem:belowText row:belowIndex]) 298 | { 299 | [self _setSelectedItem:belowText animated:YES shouldNotifyDelegate:YES]; 300 | return; 301 | } 302 | 303 | belowIndex++; 304 | } 305 | } 306 | 307 | return [self setSelectedRow:0 animated:YES]; 308 | } 309 | } 310 | } 311 | 312 | #pragma mark - Selected Row 313 | 314 | - (NSInteger)selectedRow 315 | { 316 | NSInteger pickerViewSelectedRow = _pickerSelectedRow; //It may return -1 317 | pickerViewSelectedRow = MAX(pickerViewSelectedRow, 0); 318 | 319 | return pickerViewSelectedRow - (self.isOptionalDropDown ? 1 : 0); 320 | } 321 | 322 | -(void)setSelectedRow:(NSInteger)selectedRow 323 | { 324 | [self setSelectedRow:selectedRow animated:NO]; 325 | } 326 | 327 | - (void)setSelectedRow:(NSInteger)row animated:(BOOL)animated 328 | { 329 | if (row == IQOptionalTextFieldIndex) { 330 | if (self.isOptionalDropDown) { 331 | super.text = @""; 332 | } else if (_itemList.count > 0) { 333 | super.text = [_itemListUI?:_itemList objectAtIndex:0]; 334 | } else { 335 | super.text = @""; 336 | } 337 | } else { 338 | super.text = [_itemListUI?:_itemList objectAtIndex:row]; 339 | } 340 | 341 | NSInteger pickerViewRow = row + (self.isOptionalDropDown ? 1 : 0); 342 | _pickerSelectedRow = pickerViewRow; 343 | [self.pickerView selectRow:pickerViewRow inComponent:0 animated:animated]; 344 | } 345 | 346 | #pragma mark - Toolbar 347 | 348 | -(void)setShowDismissToolbar:(BOOL)showDismissToolbar 349 | { 350 | self.inputAccessoryView = (showDismissToolbar ? self.dismissToolbar : nil); 351 | } 352 | 353 | -(BOOL)showDismissToolbar 354 | { 355 | return (self.inputAccessoryView == _dismissToolbar); 356 | } 357 | 358 | -(nonnull UIToolbar *)dismissToolbar 359 | { 360 | if (_dismissToolbar == nil) 361 | { 362 | _dismissToolbar = [[UIToolbar alloc] init]; 363 | _dismissToolbar.translucent = YES; 364 | [_dismissToolbar sizeToFit]; 365 | UIBarButtonItem *buttonFlexible = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; 366 | UIBarButtonItem *buttonDone = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(resignFirstResponder)]; 367 | [_dismissToolbar setItems:@[buttonFlexible, buttonDone]]; 368 | } 369 | 370 | return _dismissToolbar; 371 | } 372 | 373 | #pragma mark - Setters 374 | - (void)setDropDownMode:(IQDropDownMode)dropDownMode 375 | { 376 | _dropDownMode = dropDownMode; 377 | 378 | switch (_dropDownMode) 379 | { 380 | case IQDropDownModeTextPicker: 381 | { 382 | self.inputView = self.pickerView; 383 | [self setSelectedRow:self.selectedRow animated:YES]; 384 | } 385 | break; 386 | case IQDropDownModeDatePicker: 387 | { 388 | self.inputView = self.datePicker; 389 | 390 | if (self.isOptionalDropDown == NO) 391 | { 392 | [self setDate:self.datePicker.date]; 393 | } 394 | } 395 | break; 396 | case IQDropDownModeTimePicker: 397 | { 398 | self.inputView = self.timePicker; 399 | 400 | if (self.isOptionalDropDown == NO) 401 | { 402 | [self setDate:self.timePicker.date]; 403 | } 404 | } 405 | break; 406 | case IQDropDownModeDateTimePicker: 407 | { 408 | self.inputView = self.dateTimePicker; 409 | 410 | if (self.isOptionalDropDown == NO) 411 | { 412 | [self setDate:self.dateTimePicker.date]; 413 | } 414 | } 415 | break; 416 | case IQDropDownModeTextField: 417 | { 418 | self.inputView = nil; 419 | } 420 | break; 421 | default: 422 | break; 423 | } 424 | } 425 | 426 | - (void)setItemList:(nullable NSArray *)itemList 427 | { 428 | _itemList = itemList; 429 | 430 | //Refreshing pickerView 431 | [self setIsOptionalDropDown:_isOptionalDropDown]; 432 | 433 | [self setSelectedRow:self.selectedRow]; 434 | } 435 | 436 | - (nullable NSString*)selectedItem 437 | { 438 | switch (_dropDownMode) 439 | { 440 | case IQDropDownModeTextPicker: 441 | { 442 | NSInteger selectedRow = self.selectedRow; 443 | 444 | if (selectedRow >= 0) 445 | { 446 | return [_itemList objectAtIndex:selectedRow]; 447 | } 448 | else 449 | { 450 | return nil; 451 | } 452 | } 453 | break; 454 | case IQDropDownModeDatePicker: 455 | { 456 | return [super.text length] ? [self.dateFormatter stringFromDate:self.datePicker.date] : nil; break; 457 | } 458 | break; 459 | case IQDropDownModeTimePicker: 460 | { 461 | return [super.text length] ? [self.timeFormatter stringFromDate:self.timePicker.date] : nil; break; 462 | } 463 | break; 464 | case IQDropDownModeDateTimePicker: 465 | { 466 | return [super.text length] ? [self.dateTimeFormatter stringFromDate:self.dateTimePicker.date] : nil; break; 467 | } 468 | break; 469 | case IQDropDownModeTextField: 470 | { 471 | return super.text; 472 | } 473 | break; 474 | } 475 | } 476 | 477 | -(void)setSelectedItem:(nullable NSString *)selectedItem 478 | { 479 | [self _setSelectedItem:selectedItem animated:NO shouldNotifyDelegate:NO]; 480 | } 481 | 482 | -(void)setSelectedItem:(nullable NSString *)selectedItem animated:(BOOL)animated 483 | { 484 | [self _setSelectedItem:selectedItem animated:animated shouldNotifyDelegate:NO]; 485 | } 486 | 487 | -(nullable NSString *)optionalItemText 488 | { 489 | if (_optionalItemText.length) 490 | { 491 | return _optionalItemText; 492 | } 493 | else 494 | { 495 | return NSLocalizedString(@"Select", nil); 496 | } 497 | } 498 | 499 | -(void)setOptionalItemText:(nullable NSString *)optionalItemText 500 | { 501 | _optionalItemText = [optionalItemText copy]; 502 | 503 | [self _updateOptionsList]; 504 | } 505 | 506 | -(BOOL)adjustPickerLabelFontSizeWidth { 507 | return _adjustPickerLabelFontSizeWidth; 508 | } 509 | -(void)setAdjustPickerLabelFontSizeWidth:(BOOL)adjustPickerLabelFontSizeWidth { 510 | _adjustPickerLabelFontSizeWidth = adjustPickerLabelFontSizeWidth; 511 | 512 | [self _updateOptionsList]; 513 | } 514 | 515 | -(void)setIsOptionalDropDown:(BOOL)isOptionalDropDown 516 | { 517 | if (_hasSetInitialIsOptional == NO || _isOptionalDropDown != isOptionalDropDown) 518 | { 519 | NSInteger previousSelectedRow = self.selectedRow; 520 | 521 | _isOptionalDropDown = isOptionalDropDown; 522 | _hasSetInitialIsOptional = YES; 523 | 524 | if (_hasSetInitialIsOptional == YES && self.dropDownMode == IQDropDownModeTextPicker) 525 | { 526 | [self.pickerView reloadAllComponents]; 527 | 528 | [self setSelectedRow:previousSelectedRow]; 529 | } 530 | } 531 | } 532 | 533 | - (void) _updateOptionsList { 534 | 535 | switch (_dropDownMode) 536 | { 537 | case IQDropDownModeDatePicker: 538 | { 539 | if (self.isOptionalDropDown == NO) 540 | { 541 | [self setDate:self.datePicker.date]; 542 | } 543 | } 544 | break; 545 | case IQDropDownModeTimePicker: 546 | { 547 | if (self.isOptionalDropDown == NO) 548 | { 549 | [self setDate:self.timePicker.date]; 550 | } 551 | } 552 | break; 553 | case IQDropDownModeDateTimePicker: 554 | { 555 | if (self.isOptionalDropDown == NO) 556 | { 557 | [self setDate:self.dateTimePicker.date]; 558 | } 559 | } 560 | case IQDropDownModeTextPicker: 561 | { 562 | [self.pickerView reloadAllComponents]; 563 | } 564 | break; 565 | default: 566 | break; 567 | } 568 | } 569 | 570 | - (BOOL)canPerformAction:(SEL)action withSender:(id)sender 571 | { 572 | BOOL isRestrictedAction = (action == @selector(paste:) || action == @selector(cut:)); 573 | if (isRestrictedAction && self.dropDownMode != IQDropDownModeTextField) { 574 | return NO; 575 | } 576 | 577 | return [super canPerformAction:action withSender:sender]; 578 | } 579 | 580 | #pragma mark - Getter 581 | 582 | - (nonnull UIPickerView *) pickerView { 583 | if (!_pickerView) 584 | { 585 | _pickerView = [[UIPickerView alloc] init]; 586 | [_pickerView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight)]; 587 | [_pickerView setShowsSelectionIndicator:YES]; 588 | [_pickerView setDelegate:self]; 589 | [_pickerView setDataSource:self]; 590 | } 591 | return _pickerView; 592 | } 593 | 594 | @end 595 | -------------------------------------------------------------------------------- /IQDropDownTextField/IQDropDownTextFieldConstants.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQDropDownTextFieldConstants.h 3 | // IQDropDownTextField 4 | // 5 | // Created by Iftekhar on 12/19/23. 6 | // 7 | 8 | #ifndef IQDropDownTextFieldConstants_h 9 | #define IQDropDownTextFieldConstants_h 10 | 11 | #import 12 | 13 | /** 14 | Drop Down Mode settings. 15 | 16 | `IQDropDownModeTextPicker` 17 | Show pickerView with provided text data. 18 | 19 | `IQDropDownModeTimePicker` 20 | Show UIDatePicker to pick time. 21 | 22 | `IQDropDownModeDatePicker` 23 | Show UIDatePicker to pick date. 24 | */ 25 | typedef NS_ENUM(NSInteger, IQDropDownMode) { 26 | IQDropDownModeTextPicker, 27 | IQDropDownModeTimePicker, 28 | IQDropDownModeDatePicker, 29 | IQDropDownModeDateTimePicker, 30 | IQDropDownModeTextField 31 | }; 32 | 33 | /** 34 | `IQProposedSelectionAbove` 35 | pickerView find the nearest items above the deselected item that can be selected and then selecting that row. 36 | 37 | `IQProposedSelectionBelow` 38 | pickerView find the nearest items below the deselected item that can be selected and then selecting that row. 39 | 40 | `IQProposedSelectionBoth` 41 | pickerView find the nearest items that can be selected above or below the deselected item and then selecting that row. 42 | */ 43 | typedef NS_ENUM(NSInteger, IQProposedSelection) { 44 | IQProposedSelectionBoth, 45 | IQProposedSelectionAbove, 46 | IQProposedSelectionBelow 47 | }; 48 | 49 | 50 | #endif /* IQDropDownTextFieldConstants_h */ 51 | -------------------------------------------------------------------------------- /IQDropDownTextField/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyCollectedDataTypes 6 | 7 | NSPrivacyTracking 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /IQDropDownTextFieldSwift.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "IQDropDownTextFieldSwift", 3 | "version": "4.0.5", 4 | "source": { 5 | "git": "https://github.com/hackiftekhar/IQDropDownTextField.git", 6 | "tag": "4.0.5" 7 | }, 8 | "summary": "TextField with DropDown support using UIPickerView", 9 | "homepage": "https://github.com/hackiftekhar/IQDropDownTextField", 10 | "license": "MIT", 11 | "authors": { 12 | "Iftekhar Qurashi": "hack.iftekhar@gmail.com" 13 | }, 14 | "platforms": { 15 | "ios": "11.0" 16 | }, 17 | "source_files": [ 18 | "IQDropDownTextFieldSwift/*.{swift}" 19 | ], 20 | "resource_bundles": {"IQDropDownTextFieldSwift": "IQDropDownTextFieldSwift/PrivacyInfo.xcprivacy"}, 21 | "swift_versions": [ 22 | "5.5", 23 | "5.6", 24 | "5.7", 25 | "5.8", 26 | "5.9", 27 | "6.0" 28 | ], 29 | "frameworks": [ 30 | "UIKit", 31 | "Foundation", 32 | "CoreGraphics" 33 | ], 34 | "requires_arc": true 35 | } 36 | -------------------------------------------------------------------------------- /IQDropDownTextFieldSwift/IQDropDownTextField+Date.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQDropDownTextField+Date.swift 3 | // https://github.com/hackiftekhar/IQDropDownTextField 4 | // Copyright (c) 2020-21 Iftekhar Qurashi. 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 | import UIKit 25 | 26 | @MainActor 27 | extension IQDropDownTextField { 28 | 29 | // MARK: - UIDatePicker delegate 30 | 31 | @objc internal func dateChanged(_ picker: UIDatePicker) { 32 | let selectedItem: String = dateFormatter.string(from: picker.date) 33 | privateSetSelectedItems(selectedItems: [selectedItem], animated: false, shouldNotifyDelegate: true) 34 | } 35 | 36 | @objc internal func timeChanged(_ picker: UIDatePicker) { 37 | let selectedItem: String = timeFormatter.string(from: picker.date) 38 | privateSetSelectedItems(selectedItems: [selectedItem], animated: false, shouldNotifyDelegate: true) 39 | } 40 | 41 | @objc internal func dateTimeChanged(_ picker: UIDatePicker) { 42 | let selectedItem: String = dateTimeFormatter.string(from: picker.date) 43 | privateSetSelectedItems(selectedItems: [selectedItem], animated: false, shouldNotifyDelegate: true) 44 | } 45 | 46 | @objc open var datePickerMode: UIDatePicker.Mode { 47 | get { 48 | return datePicker.datePickerMode 49 | } 50 | set { 51 | if dropDownMode == .date { 52 | datePicker.datePickerMode = newValue 53 | switch newValue { 54 | case .countDownTimer: 55 | dateFormatter.dateStyle = .none 56 | dateFormatter.timeStyle = .none 57 | case .date: 58 | dateFormatter.dateStyle = .short 59 | dateFormatter.timeStyle = .none 60 | case .time: 61 | timeFormatter.dateStyle = .none 62 | timeFormatter.timeStyle = .short 63 | case .dateAndTime: 64 | dateTimeFormatter.dateStyle = .short 65 | dateTimeFormatter.timeStyle = .short 66 | @unknown default: 67 | break 68 | } 69 | } 70 | } 71 | } 72 | 73 | @objc open var date: Date? { 74 | get { 75 | switch dropDownMode { 76 | case .date: 77 | if isOptionalDropDown { 78 | return (super.text?.isEmpty ?? true) ? nil : datePicker.date 79 | } else { 80 | return datePicker.date 81 | } 82 | case .time: 83 | if isOptionalDropDown { 84 | return (super.text?.isEmpty ?? true) ? nil : timePicker.date 85 | } else { 86 | return timePicker.date 87 | } 88 | case .dateTime: 89 | if isOptionalDropDown { 90 | return (super.text?.isEmpty ?? true) ? nil : dateTimePicker.date 91 | } else { 92 | return dateTimePicker.date 93 | } 94 | case .list, .textField, .multiList: 95 | return nil 96 | } 97 | } 98 | set { 99 | setDate(date: newValue, animated: false) 100 | } 101 | } 102 | 103 | @objc open func setDate(date: Date?, animated: Bool) { 104 | switch dropDownMode { 105 | case .date: 106 | let selectedItem: String? 107 | if let date = date { 108 | selectedItem = dateFormatter.string(from: date) 109 | } else { 110 | selectedItem = nil 111 | } 112 | privateSetSelectedItems(selectedItems: [selectedItem], animated: animated, shouldNotifyDelegate: false) 113 | case .time: 114 | let selectedItem: String? 115 | if let date = date { 116 | selectedItem = timeFormatter.string(from: date) 117 | } else { 118 | selectedItem = nil 119 | } 120 | 121 | privateSetSelectedItems(selectedItems: [selectedItem], animated: animated, shouldNotifyDelegate: false) 122 | case .dateTime: 123 | let selectedItem: String? 124 | if let date = date { 125 | selectedItem = dateTimeFormatter.string(from: date) 126 | } else { 127 | selectedItem = nil 128 | } 129 | 130 | privateSetSelectedItems(selectedItems: [selectedItem], animated: animated, shouldNotifyDelegate: false) 131 | default: 132 | break 133 | } 134 | } 135 | 136 | public var dateComponents: DateComponents { 137 | let components: Set = [.second, .minute, .hour, .day, .month, .year, .era] 138 | return Calendar.current.dateComponents(components, from: date ?? Date()) 139 | } 140 | 141 | public var year: Int { return dateComponents.year ?? 0 } 142 | public var month: Int { return dateComponents.month ?? 0 } 143 | public var day: Int { return dateComponents.day ?? 0 } 144 | public var hour: Int { return dateComponents.hour ?? 0 } 145 | public var minute: Int { return dateComponents.minute ?? 0 } 146 | public var second: Int { return dateComponents.second ?? 0 } 147 | } 148 | -------------------------------------------------------------------------------- /IQDropDownTextFieldSwift/IQDropDownTextField+Menu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQDropDownTextField+Menu.swift 3 | // https://github.com/hackiftekhar/IQDropDownTextField 4 | // Copyright (c) 2020-21 Iftekhar Qurashi. 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 | import UIKit 25 | 26 | @available(iOS 15.0, *) 27 | @MainActor 28 | extension IQDropDownTextField { 29 | 30 | @objc open var menuButton: UIButton { 31 | privateMenuButton 32 | } 33 | 34 | @objc open var showMenuButton: Bool { 35 | get { 36 | privateMenuButton.superview != nil 37 | } 38 | set { 39 | if newValue { 40 | datePicker.preferredDatePickerStyle = .inline 41 | dateTimePicker.preferredDatePickerStyle = .inline 42 | 43 | self.addSubview(privateMenuButton) 44 | NSLayoutConstraint.activate([ 45 | privateMenuButton.leadingAnchor.constraint(equalTo: leadingAnchor), 46 | privateMenuButton.trailingAnchor.constraint(equalTo: trailingAnchor), 47 | privateMenuButton.topAnchor.constraint(equalTo: topAnchor), 48 | privateMenuButton.bottomAnchor.constraint(equalTo: bottomAnchor) 49 | ]) 50 | reconfigureMenu() 51 | } else { 52 | datePicker.preferredDatePickerStyle = .wheels 53 | dateTimePicker.preferredDatePickerStyle = .wheels 54 | 55 | privateMenuButton.removeFromSuperview() 56 | } 57 | } 58 | } 59 | 60 | internal func initializeMenu() { 61 | privateMenuButton.setImage(UIImage(systemName: "chevron.up.chevron.down"), for: .normal) 62 | privateMenuButton.contentHorizontalAlignment = .trailing 63 | privateMenuButton.contentEdgeInsets = .init(top: 0, left: 5, bottom: 0, right: 5) 64 | privateMenuButton.translatesAutoresizingMaskIntoConstraints = false 65 | privateMenuButton.addTarget(self, action: #selector(menuActionTriggered), for: .menuActionTriggered) 66 | privateMenuButton.showsMenuAsPrimaryAction = true 67 | } 68 | 69 | internal func reconfigureMenu() { 70 | 71 | switch dropDownMode { 72 | 73 | case .list, .multiList: 74 | let deferredMenuElement = UIDeferredMenuElement.uncached({ completion in 75 | 76 | var actions: [UIMenuElement] = [] 77 | if self.multiItemList.count <= 1 { 78 | let selectedItem = self.selectedItem 79 | actions = self.itemList.map { item in 80 | return UIAction(title: item, image: nil, 81 | state: item == selectedItem ? .on : .off) { (_) in 82 | self.privateSetSelectedItems(selectedItems: [item], animated: true, 83 | shouldNotifyDelegate: true) 84 | } 85 | } 86 | } else { 87 | var selectedItems = self.selectedItems 88 | for (index, itemList) in self.multiItemList.enumerated() { 89 | let selectedItem = selectedItems[index] 90 | let childrens: [UIMenuElement] = itemList.map { item in 91 | return UIAction(title: item, image: nil, 92 | state: item == selectedItem ? .on : .off) { (_) in 93 | selectedItems[index] = item 94 | self.privateSetSelectedItems(selectedItems: selectedItems, animated: true, 95 | shouldNotifyDelegate: true) 96 | } 97 | } 98 | 99 | let title: String 100 | if index < self.optionalItemTexts.count { 101 | title = self.optionalItemTexts[index] ?? self.optionalItemText ?? "" 102 | } else { 103 | title = self.optionalItemText ?? "" 104 | } 105 | let subMenu = UIMenu(title: title, children: childrens) 106 | 107 | actions.append(subMenu) 108 | } 109 | } 110 | completion(actions) 111 | }) 112 | let deferredMenus = UIMenu(title: self.placeholder ?? "", children: [deferredMenuElement]) 113 | privateMenuButton.menu = deferredMenus 114 | privateMenuButton.isHidden = false 115 | case .time, .date, .dateTime: 116 | privateMenuButton.isHidden = false 117 | privateMenuButton.addTarget(self, action: #selector(menuButtonTapped), for: .touchUpInside) 118 | privateMenuButton.menu = nil 119 | case .textField: 120 | privateMenuButton.isHidden = true 121 | privateMenuButton.menu = nil 122 | } 123 | } 124 | 125 | @objc private func menuActionTriggered() { 126 | containerViewController?.view.endEditing(true) 127 | } 128 | 129 | @objc private func menuButtonTapped() { 130 | guard let containerViewController = containerViewController else { 131 | return 132 | } 133 | let popoverViewController = UIViewController() 134 | popoverViewController.modalPresentationStyle = .popover 135 | popoverViewController.popoverPresentationController?.sourceView = privateMenuButton 136 | popoverViewController.popoverPresentationController?.sourceRect = privateMenuButton.bounds 137 | popoverViewController.popoverPresentationController?.delegate = self 138 | switch dropDownMode { 139 | case .list, .multiList, .textField: 140 | break 141 | case .time: 142 | popoverViewController.view = timePicker 143 | timePicker.sizeToFit() 144 | popoverViewController.preferredContentSize = timePicker.bounds.size 145 | containerViewController.present(popoverViewController, animated: true, completion: nil) 146 | case .date: 147 | popoverViewController.view = datePicker 148 | datePicker.sizeToFit() 149 | popoverViewController.preferredContentSize = datePicker.bounds.size 150 | containerViewController.present(popoverViewController, animated: true, completion: nil) 151 | case .dateTime: 152 | popoverViewController.view = dateTimePicker 153 | dateTimePicker.sizeToFit() 154 | popoverViewController.preferredContentSize = dateTimePicker.bounds.size 155 | containerViewController.present(popoverViewController, animated: true, completion: nil) 156 | } 157 | } 158 | } 159 | 160 | @MainActor 161 | extension IQDropDownTextField { 162 | private var containerViewController: UIViewController? { 163 | var next = self.next 164 | 165 | repeat { 166 | if let next = next as? UIViewController { 167 | return next 168 | } else { 169 | next = next?.next 170 | } 171 | } while next != nil 172 | return nil 173 | } 174 | } 175 | 176 | @MainActor 177 | extension IQDropDownTextField: UIPopoverPresentationControllerDelegate { 178 | public func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle { 179 | // Force popover style 180 | return .none 181 | } 182 | 183 | public func prepareForPopoverPresentation(_ popoverPresentationController: UIPopoverPresentationController) { 184 | containerViewController?.view.endEditing(true) 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /IQDropDownTextFieldSwift/IQDropDownTextField+Picker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQDropDownTextField+Picker.swift 3 | // https://github.com/hackiftekhar/IQDropDownTextField 4 | // Copyright (c) 2020-21 Iftekhar Qurashi. 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 | import UIKit 25 | 26 | // MARK: - UIPickerView data source 27 | @MainActor 28 | extension IQDropDownTextField: UIPickerViewDataSource { 29 | 30 | public func numberOfComponents(in pickerView: UIPickerView) -> Int { 31 | return multiItemList.count 32 | } 33 | 34 | public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { 35 | let isOptionalDropDown: Bool 36 | if component < isOptionalDropDowns.count { 37 | isOptionalDropDown = isOptionalDropDowns[component] 38 | } else if let last = isOptionalDropDowns.last { 39 | isOptionalDropDown = last 40 | } else { 41 | isOptionalDropDown = true 42 | } 43 | 44 | return multiItemList[component].count + (isOptionalDropDown ? 1 : 0) 45 | } 46 | } 47 | 48 | // MARK: UIPickerView delegate 49 | @MainActor 50 | extension IQDropDownTextField: UIPickerViewDelegate { 51 | 52 | public func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { 53 | if let heightsForComponents = heightsForComponents, 54 | component < heightsForComponents.count, 55 | 0 < heightsForComponents[component] { 56 | return heightsForComponents[component] 57 | } 58 | 59 | return 44 60 | } 61 | 62 | public func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { 63 | if let widthsForComponents = widthsForComponents, 64 | component < widthsForComponents.count, 65 | 0 < widthsForComponents[component] { 66 | return widthsForComponents[component] 67 | } 68 | 69 | // else calculating it's size. 70 | let availableWidth = (pickerView.bounds.width - 20) - 2 * CGFloat(multiItemList.count - 1) 71 | return availableWidth / CGFloat(multiItemList.count) 72 | } 73 | 74 | // swiftlint:disable function_body_length 75 | public func pickerView(_ pickerView: UIPickerView, 76 | viewForRow row: Int, forComponent component: Int, 77 | reusing view: UIView?) -> UIView { 78 | 79 | let isOptionalDropDown: Bool 80 | if component < isOptionalDropDowns.count { 81 | isOptionalDropDown = isOptionalDropDowns[component] 82 | } else if let last = isOptionalDropDowns.last { 83 | isOptionalDropDown = last 84 | } else { 85 | isOptionalDropDown = true 86 | } 87 | 88 | let row = row - (isOptionalDropDown ? 1 : 0) 89 | 90 | if row == Self.optionalItemIndex { 91 | 92 | let labelText: UILabel 93 | if let label = view as? UILabel { 94 | labelText = label 95 | } else { 96 | labelText = UILabel() 97 | labelText.textAlignment = .center 98 | labelText.adjustsFontSizeToFitWidth = adjustsFontSizeToFitWidth 99 | labelText.backgroundColor = UIColor.clear 100 | labelText.backgroundColor = UIColor.clear 101 | } 102 | 103 | labelText.font = dropDownFont ?? UIFont.systemFont(ofSize: 18) 104 | labelText.textColor = dropDownTextColor ?? UIColor.black 105 | 106 | labelText.isEnabled = false 107 | labelText.text = optionalItemText 108 | 109 | return labelText 110 | 111 | } else { 112 | 113 | let viewToReturn: UIView? 114 | 115 | if component < multiItemListView.count, 116 | row < multiItemListView[component].count, 117 | let view = multiItemListView[component][row] { 118 | 119 | // Archiving and Unarchiving is necessary to copy UIView instance. 120 | let viewData: Data = NSKeyedArchiver.archivedData(withRootObject: view) 121 | viewToReturn = NSKeyedUnarchiver.unarchiveObject(with: viewData) as? UIView 122 | } else { 123 | viewToReturn = nil 124 | } 125 | 126 | if let viewToReturn = viewToReturn { 127 | return viewToReturn 128 | } else { 129 | 130 | let labelText: UILabel 131 | if let label = view as? UILabel { 132 | labelText = label 133 | } else { 134 | labelText = UILabel() 135 | labelText.textAlignment = .center 136 | labelText.adjustsFontSizeToFitWidth = adjustsFontSizeToFitWidth 137 | labelText.backgroundColor = UIColor.clear 138 | labelText.backgroundColor = UIColor.clear 139 | } 140 | 141 | labelText.font = dropDownFont ?? UIFont.systemFont(ofSize: 18) 142 | labelText.textColor = dropDownTextColor ?? UIColor.black 143 | 144 | let itemList = multiItemList[component] 145 | let text = itemList[row] 146 | labelText.text = text 147 | labelText.adjustsFontSizeToFitWidth = adjustsFontSizeToFitWidth 148 | labelText.numberOfLines = adjustsFontSizeToFitWidth ? 1 : 0 149 | let canSelect: Bool 150 | if let result = dataSource?.textField(textField: self, canSelectItem: text) { 151 | canSelect = result 152 | } else { 153 | canSelect = true 154 | } 155 | labelText.isEnabled = canSelect 156 | 157 | return labelText 158 | } 159 | } 160 | } 161 | // swiftlint:enable function_body_length 162 | 163 | // swiftlint:disable cyclomatic_complexity 164 | // swiftlint:disable function_body_length 165 | public func pickerView(_ pickerView: UIPickerView, 166 | didSelectRow row: Int, inComponent component: Int) { 167 | 168 | privatePickerSelectedRows[component] = row 169 | 170 | let isOptionalDropDown: Bool 171 | if component < isOptionalDropDowns.count { 172 | isOptionalDropDown = isOptionalDropDowns[component] 173 | } else if let last = isOptionalDropDowns.last { 174 | isOptionalDropDown = last 175 | } else { 176 | isOptionalDropDown = true 177 | } 178 | 179 | let row = row - (isOptionalDropDown ? 1 : 0) 180 | 181 | if row == Self.optionalItemIndex { 182 | var selectedItems = selectedItems 183 | selectedItems[component] = nil 184 | privateSetSelectedItems(selectedItems: selectedItems, animated: false, shouldNotifyDelegate: true) 185 | } else if 0 <= row { 186 | 187 | let itemList = multiItemList[component] 188 | let text: String = itemList[row] 189 | 190 | let canSelect: Bool 191 | if let result = dataSource?.textField(textField: self, canSelectItem: text) { 192 | canSelect = result 193 | } else { 194 | canSelect = true 195 | } 196 | 197 | if canSelect { 198 | var selectedItems = selectedItems 199 | selectedItems[component] = text 200 | privateSetSelectedItems(selectedItems: selectedItems, animated: false, shouldNotifyDelegate: true) 201 | } else { 202 | 203 | let proposedSelection: IQProposedSelection 204 | if let result = dataSource?.textField(textField: self, proposedSelectionModeForItem: text) { 205 | proposedSelection = result 206 | } else { 207 | proposedSelection = .both 208 | } 209 | 210 | var aboveIndex: Int = row-1 211 | var belowIndex: Int = row+1 212 | 213 | if proposedSelection == .above { 214 | belowIndex = itemList.count 215 | } else if proposedSelection == .below { 216 | aboveIndex = -1 217 | } 218 | 219 | while 0 <= aboveIndex || belowIndex < itemList.count { 220 | if 0 <= aboveIndex { 221 | let aboveText: String = itemList[aboveIndex] 222 | 223 | if let result = dataSource?.textField(textField: self, canSelectItem: aboveText), result { 224 | var selectedItems = selectedItems 225 | selectedItems[component] = aboveText 226 | privateSetSelectedItems(selectedItems: selectedItems, 227 | animated: true, 228 | shouldNotifyDelegate: true) 229 | return 230 | } 231 | 232 | aboveIndex -= 1 233 | } 234 | 235 | if belowIndex < itemList.count { 236 | let belowText: String = itemList[belowIndex] 237 | 238 | if let result = dataSource?.textField(textField: self, canSelectItem: belowText), result { 239 | var selectedItems = selectedItems 240 | selectedItems[component] = belowText 241 | privateSetSelectedItems(selectedItems: selectedItems, 242 | animated: true, 243 | shouldNotifyDelegate: true) 244 | return 245 | } 246 | 247 | belowIndex += 1 248 | } 249 | } 250 | 251 | var selectedRows = selectedRows 252 | selectedRows[component] = 0 253 | setSelectedRows(rows: selectedRows, animated: true) 254 | } 255 | } 256 | } 257 | // swiftlint:enable cyclomatic_complexity 258 | // swiftlint:enable function_body_length 259 | } 260 | -------------------------------------------------------------------------------- /IQDropDownTextFieldSwift/IQDropDownTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQDropDownTextField.swift 3 | // https://github.com/hackiftekhar/IQDropDownTextField 4 | // Copyright (c) 2020-21 Iftekhar Qurashi. 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 | import UIKit 25 | 26 | // swiftlint:disable file_length 27 | @MainActor 28 | open class IQDropDownTextField: UITextField { 29 | 30 | nonisolated public static let optionalItemIndex: Int = -1 31 | 32 | open lazy var pickerView: UIPickerView = { 33 | let view = UIPickerView() 34 | view.autoresizingMask = [.flexibleWidth, .flexibleHeight] 35 | view.showsSelectionIndicator = true 36 | view.delegate = self 37 | view.dataSource = self 38 | return view 39 | }() 40 | 41 | open lazy var timePicker: UIDatePicker = { 42 | let view = UIDatePicker() 43 | view.autoresizingMask = [.flexibleWidth, .flexibleHeight] 44 | view.datePickerMode = .time 45 | if #available(iOS 14.0, *) { 46 | view.preferredDatePickerStyle = .wheels 47 | } 48 | view.addTarget(self, action: #selector(timeChanged(_:)), for: .valueChanged) 49 | return view 50 | }() 51 | 52 | open lazy var dateTimePicker: UIDatePicker = { 53 | let view = UIDatePicker() 54 | view.autoresizingMask = [.flexibleWidth, .flexibleHeight] 55 | view.datePickerMode = .dateAndTime 56 | if #available(iOS 13.4, *) { 57 | view.preferredDatePickerStyle = .wheels 58 | } 59 | view.addTarget(self, action: #selector(dateTimeChanged(_:)), for: .valueChanged) 60 | return view 61 | }() 62 | 63 | open lazy var datePicker: UIDatePicker = { 64 | let view = UIDatePicker() 65 | view.autoresizingMask = [.flexibleWidth, .flexibleHeight] 66 | view.datePickerMode = .date 67 | if #available(iOS 13.4, *) { 68 | view.preferredDatePickerStyle = .wheels 69 | } 70 | view.addTarget(self, action: #selector(dateChanged(_:)), for: .valueChanged) 71 | return view 72 | }() 73 | 74 | private lazy var dismissToolbar: UIToolbar = { 75 | let view = UIToolbar() 76 | view.isTranslucent = true 77 | view.sizeToFit() 78 | let buttonFlexible: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, 79 | target: nil, action: nil) 80 | let buttonDone: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, 81 | action: #selector(resignFirstResponder)) 82 | view.items = [buttonFlexible, buttonDone] 83 | return view 84 | }() 85 | 86 | internal let privateMenuButton: UIButton = UIButton() 87 | 88 | // Sets a custom font for the IQDropdownTextField items. Default is boldSystemFontOfSize:18.0. 89 | open var dropDownFont: UIFont? 90 | 91 | // Sets a custom color for the IQDropdownTextField items. Default is blackColor. 92 | open var dropDownTextColor: UIColor? 93 | 94 | // Width and height to adopt for each section. 95 | // If you don't want to specify a row width then use 0 to calculate row width automatically. 96 | open var widthsForComponents: [CGFloat]? 97 | open var heightsForComponents: [CGFloat]? 98 | 99 | open var dateFormatter: DateFormatter = DateFormatter() { 100 | didSet { 101 | datePicker.locale = dateFormatter.locale 102 | } 103 | } 104 | 105 | open var timeFormatter: DateFormatter = DateFormatter() { 106 | didSet { 107 | timePicker.locale = timeFormatter.locale 108 | } 109 | } 110 | 111 | open var dateTimeFormatter: DateFormatter = DateFormatter() { 112 | didSet { 113 | dateTimePicker.locale = dateTimeFormatter.locale 114 | } 115 | } 116 | 117 | weak open var dropDownDelegate: (any IQDropDownTextFieldDelegate)? 118 | weak open override var delegate: (any UITextFieldDelegate)? { 119 | didSet { 120 | dropDownDelegate = delegate as? (any IQDropDownTextFieldDelegate) 121 | } 122 | } 123 | 124 | open var dataSource: (any IQDropDownTextFieldDataSource)? 125 | 126 | open var dropDownMode: IQDropDownMode = .list { 127 | didSet { 128 | switch dropDownMode { 129 | case .list, .multiList: 130 | inputView = pickerView 131 | setSelectedRows(rows: selectedRows, animated: true) 132 | case .date: 133 | 134 | inputView = datePicker 135 | if !isOptionalDropDown { 136 | date = datePicker.date 137 | } 138 | case .time: 139 | 140 | inputView = timePicker 141 | if !isOptionalDropDown { 142 | date = timePicker.date 143 | } 144 | case .dateTime: 145 | 146 | inputView = dateTimePicker 147 | if !isOptionalDropDown { 148 | date = dateTimePicker.date 149 | } 150 | case .textField: 151 | inputView = nil 152 | } 153 | if #available(iOS 15.0, *) { 154 | reconfigureMenu() 155 | } 156 | } 157 | } 158 | 159 | private var privateOptionalItemText: String? 160 | private var privateOptionalItemTexts: [String?] = [] 161 | 162 | private var privateIsOptionalDropDowns: [Bool] = [] 163 | 164 | open var multiListSelectionFormatHandler: ((_ selectedItems: [String?], _ selectedIndexes: [Int]) -> String)? { 165 | didSet { 166 | if let handler = multiListSelectionFormatHandler { 167 | super.text = handler(selectedItems, selectedRows) 168 | } else { 169 | super.text = selectedItems.compactMap({ $0 }).joined(separator: ", ") 170 | } 171 | } 172 | } 173 | 174 | open var selectionFormatHandler: ((_ selectedItem: String?, _ selectedIndex: Int) -> String)? { 175 | didSet { 176 | if let handler = selectionFormatHandler { 177 | super.text = handler(selectedItem, selectedRow) 178 | } else { 179 | super.text = selectedItems.compactMap({ $0 }).joined(separator: ", ") 180 | } 181 | } 182 | } 183 | 184 | @available(*, deprecated, message: "use 'selectedItem' instead", renamed: "selectedItem") 185 | open override var text: String? { 186 | didSet { 187 | } 188 | } 189 | 190 | @available(*, deprecated, message: "use 'selectedItem' instead", renamed: "selectedItem") 191 | open override var attributedText: NSAttributedString? { 192 | didSet { 193 | } 194 | } 195 | 196 | open var multiItemList: [[String]] = [] { 197 | didSet { 198 | // Refreshing pickerView 199 | isOptionalDropDowns = privateIsOptionalDropDowns 200 | let selectedRows = selectedRows 201 | self.selectedRows = selectedRows 202 | } 203 | } 204 | 205 | open var multiItemListView: [[UIView?]] = [] { 206 | didSet { 207 | // Refreshing pickerView 208 | isOptionalDropDowns = privateIsOptionalDropDowns 209 | let selectedRows = selectedRows 210 | self.selectedRows = selectedRows 211 | } 212 | } 213 | 214 | open override var adjustsFontSizeToFitWidth: Bool { 215 | didSet { 216 | privateUpdateOptionsList() 217 | } 218 | } 219 | 220 | private var hasSetInitialIsOptional: Bool = false 221 | 222 | func dealloc() { 223 | pickerView.delegate = nil 224 | pickerView.dataSource = nil 225 | self.delegate = nil 226 | dataSource = nil 227 | privateOptionalItemText = nil 228 | } 229 | 230 | // MARK: - Initialization 231 | 232 | func initialize() { 233 | contentVerticalAlignment = .center 234 | contentHorizontalAlignment = .center 235 | 236 | // These will update the UI and other components, 237 | // all the validation added if awakeFromNib for textField is called after custom UIView awakeFromNib call 238 | do { 239 | let mode = dropDownMode 240 | dropDownMode = mode 241 | 242 | isOptionalDropDown = hasSetInitialIsOptional ? isOptionalDropDown : true 243 | } 244 | 245 | dateFormatter.dateStyle = .medium 246 | dateFormatter.timeStyle = .none 247 | 248 | timeFormatter.dateStyle = .none 249 | timeFormatter.timeStyle = .short 250 | 251 | dateTimeFormatter.dateStyle = .medium 252 | dateTimeFormatter.timeStyle = .short 253 | 254 | if #available(iOS 15.0, *) { 255 | initializeMenu() 256 | } 257 | } 258 | 259 | override init(frame: CGRect) { 260 | super.init(frame: frame) 261 | initialize() 262 | } 263 | 264 | required public init?(coder: NSCoder) { 265 | super.init(coder: coder) 266 | } 267 | 268 | open override func awakeFromNib() { 269 | super.awakeFromNib() 270 | initialize() 271 | } 272 | 273 | // MARK: - Overrides 274 | @discardableResult 275 | open override func becomeFirstResponder() -> Bool { 276 | let result = super.becomeFirstResponder() 277 | 278 | for (index, selectedRow) in privatePickerSelectedRows where 0 <= selectedRow { 279 | pickerView.selectRow(selectedRow, inComponent: index, animated: false) 280 | } 281 | return result 282 | } 283 | 284 | open override func caretRect(for position: UITextPosition) -> CGRect { 285 | if dropDownMode == .textField { 286 | return super.caretRect(for: position) 287 | } else { 288 | return .zero 289 | } 290 | } 291 | 292 | open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { 293 | if action == #selector(cut(_:)) || action == #selector(paste(_:)) { 294 | return false 295 | } else { 296 | return super.canPerformAction(action, withSender: sender) 297 | } 298 | } 299 | 300 | // MARK: - Selected Row 301 | 302 | // Key represents section index and value represents selection 303 | internal var privatePickerSelectedRows: [Int: Int] = [:] 304 | 305 | // MARK: - Setters 306 | open func privateUpdateOptionsList() { 307 | 308 | switch dropDownMode { 309 | case .date: 310 | if !isOptionalDropDown { 311 | date = datePicker.date 312 | } 313 | case .time: 314 | if !isOptionalDropDown { 315 | date = timePicker.date 316 | } 317 | case .dateTime: 318 | 319 | if !isOptionalDropDown { 320 | date = dateTimePicker.date 321 | } 322 | case .list, .multiList: 323 | pickerView.reloadAllComponents() 324 | case .textField: 325 | break 326 | } 327 | } 328 | } 329 | 330 | // MARK: - Toolbar 331 | 332 | @MainActor 333 | extension IQDropDownTextField { 334 | 335 | @objc open var showDismissToolbar: Bool { 336 | get { 337 | return (inputAccessoryView == dismissToolbar) 338 | } 339 | set { 340 | inputAccessoryView = (newValue ? dismissToolbar : nil) 341 | } 342 | } 343 | } 344 | 345 | // MARK: - Optional drop down 346 | 347 | @MainActor 348 | extension IQDropDownTextField { 349 | 350 | @IBInspectable 351 | open var isOptionalDropDown: Bool { 352 | get { return privateIsOptionalDropDowns.first ?? true } 353 | set { 354 | isOptionalDropDowns = [newValue] 355 | } 356 | } 357 | 358 | @objc open var isOptionalDropDowns: [Bool] { 359 | get { return privateIsOptionalDropDowns } 360 | set { 361 | if !hasSetInitialIsOptional || privateIsOptionalDropDowns != newValue { 362 | 363 | let previousSelectedRows: [Int] = selectedRows 364 | 365 | privateIsOptionalDropDowns = newValue 366 | hasSetInitialIsOptional = true 367 | 368 | if dropDownMode == .list || dropDownMode == .multiList { 369 | pickerView.reloadAllComponents() 370 | selectedRows = previousSelectedRows 371 | } 372 | } 373 | } 374 | } 375 | 376 | @IBInspectable 377 | open var optionalItemText: String? { 378 | get { 379 | if let privateOptionalItemText = privateOptionalItemText, !privateOptionalItemText.isEmpty { 380 | return privateOptionalItemText 381 | } else { 382 | return NSLocalizedString("Select", comment: "") 383 | } 384 | } 385 | set { 386 | privateOptionalItemText = newValue 387 | privateUpdateOptionsList() 388 | } 389 | } 390 | 391 | public var optionalItemTexts: [String?] { 392 | get { 393 | return privateOptionalItemTexts 394 | } 395 | set { 396 | privateOptionalItemTexts = newValue 397 | privateUpdateOptionsList() 398 | } 399 | } 400 | } 401 | 402 | // MARK: - Item List 403 | 404 | @MainActor 405 | extension IQDropDownTextField { 406 | 407 | @objc open var itemList: [String] { 408 | get { 409 | multiItemList.first ?? [] 410 | } 411 | set { 412 | multiItemList = [newValue] 413 | } 414 | } 415 | 416 | public var itemListView: [UIView?] { 417 | get { 418 | multiItemListView.first ?? [] 419 | } 420 | set { 421 | multiItemListView = [newValue] 422 | } 423 | } 424 | 425 | } 426 | 427 | // MARK: - Selected Row 428 | 429 | @MainActor 430 | extension IQDropDownTextField { 431 | 432 | @objc open var selectedRow: Int { 433 | get { 434 | var pickerViewSelectedRow: Int = selectedRows.first /*It may return -1*/ ?? 0 435 | pickerViewSelectedRow = max(pickerViewSelectedRow, 0) 436 | return pickerViewSelectedRow - (isOptionalDropDown ? 1 : 0) 437 | } 438 | set { 439 | selectedRows = [newValue] 440 | } 441 | } 442 | 443 | @objc open var selectedRows: [Int] { 444 | get { 445 | var selection: [Int] = [] 446 | for index in multiItemList.indices { 447 | 448 | let isOptionalDropDown: Bool 449 | if index < isOptionalDropDowns.count { 450 | isOptionalDropDown = isOptionalDropDowns[index] 451 | } else if let last = isOptionalDropDowns.last { 452 | isOptionalDropDown = last 453 | } else { 454 | isOptionalDropDown = true 455 | } 456 | 457 | var pickerViewSelectedRow: Int = privatePickerSelectedRows[index] ?? -1 /*It may return -1*/ 458 | pickerViewSelectedRow = max(pickerViewSelectedRow, 0) 459 | 460 | let finalSelection = pickerViewSelectedRow - (isOptionalDropDown ? 1 : 0) 461 | selection.append(finalSelection) 462 | } 463 | return selection 464 | } 465 | set { 466 | setSelectedRows(rows: newValue, animated: false) 467 | } 468 | } 469 | 470 | @objc open func selectedRow(inSection section: Int) -> Int { 471 | privatePickerSelectedRows[section] ?? Self.optionalItemIndex 472 | } 473 | 474 | @objc open func setSelectedRow(row: Int, animated: Bool) { 475 | setSelectedRows(rows: [row], animated: animated) 476 | } 477 | 478 | @objc open func setSelectedRow(row: Int, inSection section: Int, animated: Bool) { 479 | var selectedRows = selectedRows 480 | selectedRows[section] = row 481 | setSelectedRows(rows: selectedRows, animated: animated) 482 | } 483 | 484 | @objc open func setSelectedRows(rows: [Int], animated: Bool) { 485 | 486 | var finalResults: [String?] = [] 487 | for (index, row) in rows.enumerated() { 488 | 489 | let itemList: [String] 490 | 491 | if index < multiItemList.count { 492 | itemList = multiItemList[index] 493 | } else { 494 | itemList = [] 495 | } 496 | 497 | let isOptionalDropDown: Bool 498 | if index < isOptionalDropDowns.count { 499 | isOptionalDropDown = isOptionalDropDowns[index] 500 | } else if let last = isOptionalDropDowns.last { 501 | isOptionalDropDown = last 502 | } else { 503 | isOptionalDropDown = true 504 | } 505 | 506 | if row == Self.optionalItemIndex { 507 | 508 | if !isOptionalDropDown, !itemList.isEmpty { 509 | finalResults.append(itemList[0]) 510 | } else { 511 | finalResults.append(nil) 512 | } 513 | } else { 514 | if row < itemList.count { 515 | finalResults.append(itemList[row]) 516 | } else { 517 | finalResults.append(nil) 518 | } 519 | } 520 | 521 | let pickerViewRow: Int = row + (isOptionalDropDown ? 1 : 0) 522 | privatePickerSelectedRows[index] = pickerViewRow 523 | if index < pickerView.numberOfComponents { 524 | pickerView.selectRow(pickerViewRow, inComponent: index, animated: animated) 525 | } 526 | } 527 | 528 | if let multiListSelectionFormatHandler = multiListSelectionFormatHandler { 529 | super.text = multiListSelectionFormatHandler(finalResults, rows) 530 | } else if let selectionFormatHandler = selectionFormatHandler, 531 | let selectedItem = finalResults.first, 532 | let selectedRow = rows.first { 533 | super.text = selectionFormatHandler(selectedItem, selectedRow) 534 | } else { 535 | super.text = finalResults.compactMap({ $0 }).joined(separator: ", ") 536 | } 537 | } 538 | } 539 | 540 | // MARK: - Selected Items 541 | 542 | @MainActor 543 | extension IQDropDownTextField { 544 | 545 | @objc open var selectedItem: String? { 546 | get { 547 | return selectedItems.first ?? nil 548 | } 549 | set { 550 | switch dropDownMode { 551 | case .multiList: 552 | if let newValue = newValue { 553 | selectedItems = [newValue] 554 | } else { 555 | selectedItems = multiItemList.map({ _ in nil }) // Resetting every section 556 | } 557 | case .list, .date, .time, .dateTime, .textField: 558 | selectedItems = [newValue] 559 | } 560 | } 561 | } 562 | 563 | public var selectedItems: [String?] { 564 | get { 565 | switch dropDownMode { 566 | case .list, .multiList: 567 | var finalSelection: [String?] = [] 568 | for (index, selectedRow) in selectedRows.enumerated() { 569 | if 0 <= selectedRow, index < multiItemList.count { 570 | finalSelection.append(multiItemList[index][selectedRow]) 571 | } else { 572 | finalSelection.append(nil) 573 | } 574 | } 575 | return finalSelection 576 | case .date: 577 | return (super.text?.isEmpty ?? true) ? [nil] : [dateFormatter.string(from: datePicker.date)] 578 | case .time: 579 | return (super.text?.isEmpty ?? true) ? [nil] : [timeFormatter.string(from: timePicker.date)] 580 | case .dateTime: 581 | return (super.text?.isEmpty ?? true) ? [nil] : [dateTimeFormatter.string(from: dateTimePicker.date)] 582 | case .textField: 583 | return [super.text] 584 | } 585 | } 586 | 587 | set { 588 | privateSetSelectedItems(selectedItems: newValue, animated: false, shouldNotifyDelegate: false) 589 | } 590 | } 591 | 592 | @objc open func setSelectedItem(selectedItem: String?, animated: Bool) { 593 | privateSetSelectedItems(selectedItems: [selectedItem], animated: animated, shouldNotifyDelegate: false) 594 | } 595 | 596 | public func setSelectedItems(selectedItems: [String?], animated: Bool) { 597 | privateSetSelectedItems(selectedItems: selectedItems, animated: animated, shouldNotifyDelegate: false) 598 | } 599 | } 600 | 601 | @MainActor 602 | internal extension IQDropDownTextField { 603 | 604 | // swiftlint:disable cyclomatic_complexity 605 | // swiftlint:disable function_body_length 606 | func privateSetSelectedItems(selectedItems: [String?], 607 | animated: Bool, shouldNotifyDelegate: Bool) { 608 | switch dropDownMode { 609 | case .list, .multiList: 610 | 611 | var finalIndexes: [Int] = [] 612 | var finalSelection: [String?] = [] 613 | 614 | for (index, selectedItem) in selectedItems.enumerated() { 615 | 616 | if let selectedItem = selectedItem, 617 | index < multiItemList.count, 618 | let index = multiItemList[index].firstIndex(of: selectedItem) { 619 | finalIndexes.append(index) 620 | finalSelection.append(selectedItem) 621 | 622 | } else { 623 | 624 | let isOptionalDropDown: Bool 625 | if index < isOptionalDropDowns.count { 626 | isOptionalDropDown = isOptionalDropDowns[index] 627 | } else if let last = isOptionalDropDowns.last { 628 | isOptionalDropDown = last 629 | } else { 630 | isOptionalDropDown = true 631 | } 632 | 633 | let selectedIndex = isOptionalDropDown ? Self.optionalItemIndex : 0 634 | finalIndexes.append(selectedIndex) 635 | finalSelection.append(nil) 636 | } 637 | } 638 | 639 | setSelectedRows(rows: finalIndexes, animated: animated) 640 | 641 | if shouldNotifyDelegate { 642 | if dropDownMode == .multiList { 643 | dropDownDelegate?.textField(textField: self, didSelectItems: finalSelection) 644 | } else if let selectedItem = finalSelection.first { 645 | dropDownDelegate?.textField(textField: self, didSelectItem: selectedItem) 646 | } 647 | } 648 | 649 | case .date: 650 | 651 | if let selectedItem = selectedItems.first, 652 | let selectedItem = selectedItem, 653 | let date = dateFormatter.date(from: selectedItem) { 654 | super.text = selectedItem 655 | datePicker.setDate(date, animated: animated) 656 | 657 | if shouldNotifyDelegate { 658 | dropDownDelegate?.textField(textField: self, didSelectDate: date) 659 | } 660 | } else if isOptionalDropDown, 661 | let selectedItem = selectedItems.first, 662 | selectedItem == nil || selectedItem?.isEmpty == true { 663 | super.text = "" 664 | 665 | datePicker.setDate(Date(), animated: animated) 666 | 667 | if shouldNotifyDelegate { 668 | dropDownDelegate?.textField(textField: self, didSelectDate: nil) 669 | } 670 | } 671 | case .time: 672 | 673 | if let selectedItem = selectedItems.first, 674 | let selectedItem = selectedItem, 675 | let time = timeFormatter.date(from: selectedItem) { 676 | let day: Date = Date(timeIntervalSinceReferenceDate: 0) 677 | let componentsForDay: Set = [.era, .year, .month, .day] 678 | let componentsForTime: Set = [.hour, .minute, .second] 679 | var componentsDay: DateComponents = Calendar.current.dateComponents(componentsForDay, from: day) 680 | let componentsTime: DateComponents = Calendar.current.dateComponents(componentsForTime, from: time) 681 | componentsDay.hour = componentsTime.hour 682 | componentsDay.minute = componentsTime.minute 683 | componentsDay.second = componentsTime.second 684 | 685 | if let date = Calendar.current.date(from: componentsDay) { 686 | super.text = selectedItem 687 | timePicker.setDate(date, animated: animated) 688 | 689 | if shouldNotifyDelegate { 690 | dropDownDelegate?.textField(textField: self, didSelectDate: date) 691 | } 692 | } 693 | } else if isOptionalDropDown, 694 | let selectedItem = selectedItems.first, 695 | selectedItem == nil || selectedItem?.isEmpty == true { 696 | super.text = "" 697 | timePicker.setDate(Date(), animated: animated) 698 | 699 | if shouldNotifyDelegate { 700 | dropDownDelegate?.textField(textField: self, didSelectDate: nil) 701 | } 702 | } 703 | 704 | case .dateTime: 705 | 706 | if let selectedItem = selectedItems.first, 707 | let selectedItem = selectedItem, 708 | let date: Date = dateTimeFormatter.date(from: selectedItem) { 709 | super.text = selectedItem 710 | dateTimePicker.setDate(date, animated: animated) 711 | 712 | if shouldNotifyDelegate { 713 | dropDownDelegate?.textField(textField: self, didSelectDate: date) 714 | } 715 | } else if isOptionalDropDown, 716 | let selectedItem = selectedItems.first, 717 | selectedItem == nil || selectedItem?.isEmpty == true { 718 | 719 | super.text = "" 720 | dateTimePicker.setDate(Date(), animated: animated) 721 | 722 | if shouldNotifyDelegate { 723 | dropDownDelegate?.textField(textField: self, didSelectDate: nil) 724 | } 725 | } 726 | case .textField: 727 | super.text = selectedItems.compactMap({ $0 }).joined(separator: ", ") 728 | } 729 | } 730 | // swiftlint:enable cyclomatic_complexity 731 | // swiftlint:enable function_body_length 732 | } 733 | -------------------------------------------------------------------------------- /IQDropDownTextFieldSwift/IQDropDownTextFieldConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQDropDownTextFieldConstants.swift 3 | // https://github.com/hackiftekhar/IQDropDownTextField 4 | // Copyright (c) 2020-21 Iftekhar Qurashi. 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 | import Foundation 25 | 26 | public enum IQDropDownMode: Sendable { 27 | case list 28 | case multiList 29 | case time 30 | case date 31 | case dateTime 32 | case textField 33 | } 34 | 35 | public enum IQProposedSelection: Sendable { 36 | case both 37 | case above 38 | case below 39 | } 40 | -------------------------------------------------------------------------------- /IQDropDownTextFieldSwift/IQDropDownTextFieldDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQDropDownTextFieldDataSource.swift 3 | // https://github.com/hackiftekhar/IQDropDownTextField 4 | // Copyright (c) 2020-21 Iftekhar Qurashi. 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 | import UIKit 25 | 26 | @MainActor 27 | public protocol IQDropDownTextFieldDataSource: AnyObject { 28 | 29 | // Check if an item can be selected by dropdown texField. 30 | func textField(textField: IQDropDownTextField, canSelectItem item: String) -> Bool 31 | 32 | // If canSelectItem return NO, then textField:proposedSelectionModeForItem: asked for proposed selection mode. 33 | // .above: pickerView find the nearest items above the deselected item 34 | // that can be selected and then selecting that row. 35 | // .below: pickerView find the nearest items below the deselected item 36 | // that can be selected and then selecting that row. 37 | // both.: pickerView find the nearest items that can be selected 38 | // above or below the deselected item and then selecting that row. 39 | func textField(textField: IQDropDownTextField, proposedSelectionModeForItem item: String) -> IQProposedSelection 40 | } 41 | 42 | @MainActor 43 | extension IQDropDownTextFieldDataSource { 44 | 45 | func textField(textField: IQDropDownTextField, didSelectDate date: Date?) { } 46 | func textField(textField: IQDropDownTextField, canSelectItem item: String) -> Bool { return true } 47 | func textField(textField: IQDropDownTextField, proposedSelectionModeForItem item: String) -> IQProposedSelection { 48 | return .both 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /IQDropDownTextFieldSwift/IQDropDownTextFieldDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IQDropDownTextFieldDelegate.swift 3 | // https://github.com/hackiftekhar/IQDropDownTextField 4 | // Copyright (c) 2020-21 Iftekhar Qurashi. 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 | import UIKit 25 | 26 | @MainActor 27 | public protocol IQDropDownTextFieldDelegate: UITextFieldDelegate { 28 | 29 | // Called when textField changes it's selected item. Supported for list mode 30 | func textField(textField: IQDropDownTextField, didSelectItem item: String?) 31 | 32 | // Called when textField changes it's selected item. Supported for multiList mode 33 | func textField(textField: IQDropDownTextField, didSelectItems items: [String?]) 34 | 35 | // Called when textField changes it's selected item. Supported for time, date, dateTime mode 36 | func textField(textField: IQDropDownTextField, didSelectDate date: Date?) 37 | } 38 | 39 | @MainActor 40 | extension IQDropDownTextFieldDelegate { 41 | 42 | func textField(textField: IQDropDownTextField, didSelectItem item: String?) { } 43 | 44 | func textField(textField: IQDropDownTextField, didSelectItems items: [String?]) { } 45 | 46 | func textField(textField: IQDropDownTextField, didSelectDate date: Date?) { } 47 | } 48 | -------------------------------------------------------------------------------- /IQDropDownTextFieldSwift/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyCollectedDataTypes 6 | 7 | NSPrivacyTracking 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Images/date.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQDropDownTextField/099faf11b3b2a18f4852cb76b0b908a334fae05d/Images/date.png -------------------------------------------------------------------------------- /Images/date_time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQDropDownTextField/099faf11b3b2a18f4852cb76b0b908a334fae05d/Images/date_time.png -------------------------------------------------------------------------------- /Images/large_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQDropDownTextField/099faf11b3b2a18f4852cb76b0b908a334fae05d/Images/large_text.png -------------------------------------------------------------------------------- /Images/multi_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQDropDownTextField/099faf11b3b2a18f4852cb76b0b908a334fae05d/Images/multi_list.png -------------------------------------------------------------------------------- /Images/simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQDropDownTextField/099faf11b3b2a18f4852cb76b0b908a334fae05d/Images/simple.png -------------------------------------------------------------------------------- /Images/time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackiftekhar/IQDropDownTextField/099faf11b3b2a18f4852cb76b0b908a334fae05d/Images/time.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Mohd Iftekhar Qurashi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013-2017 Iftekhar Qurashi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "IQDropDownTextFieldSwift", 8 | platforms: [ 9 | .iOS(.v11) 10 | ], 11 | products: [ 12 | .library( 13 | name: "IQDropDownTextFieldSwift", 14 | targets: ["IQDropDownTextFieldSwift"] 15 | ) 16 | ], 17 | targets: [ 18 | .target( 19 | name: "IQDropDownTextFieldSwift", 20 | path: "IQDropDownTextFieldSwift", 21 | resources: [ 22 | .copy("PrivacyInfo.xcprivacy") 23 | ] 24 | ) 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | project 'Drop Down TextField.xcodeproj' 2 | 3 | platform :ios, '11.0' 4 | use_frameworks! 5 | 6 | target 'Drop Down TextField' do 7 | pod "IQDropDownTextField", :path => "." 8 | end 9 | 10 | target 'DropDownTextFieldSwift' do 11 | pod "IQDropDownTextFieldSwift", :path => "." 12 | pod "SwiftLint" 13 | end 14 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - IQDropDownTextField (4.0.2) 3 | - IQDropDownTextFieldSwift (4.0.2) 4 | - SwiftLint (0.53.0) 5 | 6 | DEPENDENCIES: 7 | - IQDropDownTextField (from `.`) 8 | - IQDropDownTextFieldSwift (from `.`) 9 | - SwiftLint 10 | 11 | SPEC REPOS: 12 | trunk: 13 | - SwiftLint 14 | 15 | EXTERNAL SOURCES: 16 | IQDropDownTextField: 17 | :path: "." 18 | IQDropDownTextFieldSwift: 19 | :path: "." 20 | 21 | SPEC CHECKSUMS: 22 | IQDropDownTextField: 763cdb0b0ebe15cf5f486a0d31fe281e07a0800d 23 | IQDropDownTextFieldSwift: f705be4c1955eb475b74335105f8b360a0001db8 24 | SwiftLint: 5ce4d6a8ff83f1b5fd5ad5dbf30965d35af65e44 25 | 26 | PODFILE CHECKSUM: c45ef6dbf4032cdb8f2212b9067a426ee589b296 27 | 28 | COCOAPODS: 1.13.0 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | IQDropDownTextField 2 | =================== 3 | 4 | TextField with DropDown support using UIPickerView 5 | 6 | [![Simple](https://raw.githubusercontent.com/hackiftekhar/IQDropDownTextField/master/Images/simple.png)] 7 | [![Large Text](https://raw.githubusercontent.com/hackiftekhar/IQDropDownTextField/master/Images/large_text.png)] 8 | [![Multi List](https://raw.githubusercontent.com/hackiftekhar/IQDropDownTextField/master/Images/multi_list.png)] 9 | [![Date](https://raw.githubusercontent.com/hackiftekhar/IQDropDownTextField/master/Images/date.png)] 10 | [![Time](https://raw.githubusercontent.com/hackiftekhar/IQDropDownTextField/master/Images/time.png)] 11 | [![Date Time](https://raw.githubusercontent.com/hackiftekhar/IQDropDownTextField/master/Images/date_time)] 12 | 13 | ## Installing 14 | 15 | Install using [cocoapods](http://cocoapods.org). Add in your `Podfile`: 16 | 17 | ``` 18 | pod 'IQDropDownTextField' 19 | ``` 20 | 21 | Or below for Swift version 22 | ``` 23 | pod 'IQDropDownTextFieldSwift' 24 | ``` 25 | 26 | ## How to Use 27 | 28 | In IB (_story boards or xibs_) you can add `UITextField`'s and set the class as `IQDropDownTextField` 29 | 30 | ### Objective-C 31 | 32 | Nothing more easy than it! 33 | 34 | ```objective-c 35 | 36 | @implementation ViewController 37 | 38 | - (void)viewDidLoad 39 | { 40 | [super viewDidLoad]; 41 | 42 | textFieldTextPicker.isOptionalDropDown = NO; 43 | [textFieldTextPicker setItemList:[NSArray arrayWithObjects:@"London",@"Johannesburg",@"Moscow",@"Mumbai",@"Tokyo",@"Sydney", nil]]; 44 | } 45 | @end 46 | 47 | ``` 48 | 49 | ### Swift 50 | 51 | It's very simple to setup your `IQDropDownTextField`. The sample below shows you how to: 52 | 53 | ```swift 54 | import IQDropDownTextFieldSwift 55 | 56 | class MyController : UIViewController { 57 | @IBOutlet var occupationTextField: IQDropDownTextField! 58 | 59 | override func viewDidLoad() { 60 | occupationTextField.isOptionalDropDown = false 61 | occupationTextField.itemList = ["programmer", "teacher", "engineer"] 62 | } 63 | } 64 | ``` 65 | 66 | And that's all you need! =) 67 | 68 | ## Contributions 69 | 70 | Any contribution is more than welcome! You can contribute through pull requests and issues on GitHub. 71 | 72 | ## Author 73 | 74 | If you wish to contact me, email at: hack.iftekhar@gmail.com 75 | 76 | ## LICENSE 77 | 78 | Copyright (c) 2010-2015 79 | 80 | Permission is hereby granted, free of charge, to any person obtaining a copy 81 | of this software and associated documentation files (the "Software"), to deal 82 | in the Software without restriction, including without limitation the rights 83 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 84 | copies of the Software, and to permit persons to whom the Software is 85 | furnished to do so, subject to the following conditions: 86 | 87 | The above copyright notice and this permission notice shall be included in 88 | all copies or substantial portions of the Software. 89 | 90 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 91 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 92 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 93 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 94 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 95 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 96 | THE SOFTWARE. 97 | --------------------------------------------------------------------------------