├── .gitignore ├── Example ├── Example.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── rizwana.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── Example.xcscheme │ └── xcuserdata │ │ └── rizwana.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── Example │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Colors │ │ │ ├── Contents.json │ │ │ ├── black.colorset │ │ │ │ └── Contents.json │ │ │ └── yellow.colorset │ │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Common │ │ ├── LocalizedString.swift │ │ ├── ThemeCard.swift │ │ └── Utils.swift │ ├── ExampleApp.swift │ ├── Extension │ │ ├── Color+Extension.swift │ │ └── View+Extension.swift │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── View │ │ └── PickerExampleView.swift │ └── ViewModel │ │ └── PickerViewModel.swift └── en.lproj │ └── Localizable.strings ├── LICENSE ├── Package.swift ├── README.md ├── SSDateTimePicker.podspec └── Sources └── SSDateTimePicker ├── SSDateTimePicker.podspec ├── SSDateTimePicker.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist └── SSDateTimePicker ├── Assets.xcassets ├── Contents.json ├── darkGreen.colorset │ └── Contents.json ├── darkPink.colorset │ └── Contents.json ├── lightBlue.colorset │ └── Contents.json ├── lightGreen.colorset │ └── Contents.json ├── lightPink.colorset │ └── Contents.json └── peach.colorset │ └── Contents.json ├── Common ├── Date picker │ ├── SSDatePickerConfiguration.swift │ └── SSDatePickerManager.swift ├── SSCornerRadiusStyle.swift ├── SSImageConstant.swift ├── SSLocalizedString.swift ├── SSPickerConstants.swift ├── SSUtils.swift └── Time picker │ ├── SSCustomWheelPicker.swift │ ├── SSTimePickerConfiguration.swift │ └── SSTimePickerManager.swift ├── DateTimePicker.docc └── DateTimePicker.md ├── DateTimePicker.h ├── Extensions ├── SSCalendar+Extension.swift ├── SSColor+Extension.swift ├── SSDate+Extension.swift ├── SSDateComponents+Extension.swift ├── SSDateFormatter+Extension.swift ├── SSInt+Extension.swift ├── SSString+Extension.swift └── SSView+Extension.swift ├── Views ├── Date picker │ ├── SSDatePicker.swift │ ├── SSDateRangePicker.swift │ ├── SSDateView.swift │ ├── SSMonthSelectionView.swift │ ├── SSMultiDatePicker.swift │ ├── SSWeekDatesView.swift │ └── SSYearSelectionView.swift ├── SSThemeButton.swift └── Time Picker │ ├── SSClockPicker.swift │ ├── SSTimePicker.swift │ └── SSTimeTextField.swift └── en.lproj └── Localizable.strings /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 650720D32B194D2100AC1FB6 /* PickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720D22B194D2100AC1FB6 /* PickerViewModel.swift */; }; 11 | 6549D9AC2B2C4B4B0089EBB1 /* ThemeCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6549D9AB2B2C4B4B0089EBB1 /* ThemeCard.swift */; }; 12 | 6549D9B02B2C4DBA0089EBB1 /* View+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6549D9AF2B2C4DBA0089EBB1 /* View+Extension.swift */; }; 13 | 658A5CE12B3523B50044930D /* SSDateTimePicker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 658A5CDC2B3523490044930D /* SSDateTimePicker.framework */; }; 14 | 658A5CE22B3523B50044930D /* SSDateTimePicker.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 658A5CDC2B3523490044930D /* SSDateTimePicker.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 15 | 65AB6B0B2B283131009EA7EC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 65AB6B0D2B283131009EA7EC /* Localizable.strings */; }; 16 | 65AB6B102B2839F9009EA7EC /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AB6B0F2B2839F9009EA7EC /* Utils.swift */; }; 17 | 65AB6B122B283AED009EA7EC /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AB6B112B283AED009EA7EC /* LocalizedString.swift */; }; 18 | 65AB6B152B285309009EA7EC /* Color+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AB6B142B285309009EA7EC /* Color+Extension.swift */; }; 19 | 65E0584E2B0E3E850049A7BA /* ExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E0584D2B0E3E850049A7BA /* ExampleApp.swift */; }; 20 | 65E058502B0E3E850049A7BA /* PickerExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E0584F2B0E3E850049A7BA /* PickerExampleView.swift */; }; 21 | 65E058522B0E3E860049A7BA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 65E058512B0E3E860049A7BA /* Assets.xcassets */; }; 22 | 65E058552B0E3E860049A7BA /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 65E058542B0E3E860049A7BA /* Preview Assets.xcassets */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXContainerItemProxy section */ 26 | 658A5CDB2B3523490044930D /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = 658A5CD62B3523490044930D /* SSDateTimePicker.xcodeproj */; 29 | proxyType = 2; 30 | remoteGlobalIDString = 65E0580B2B0E2B260049A7BA; 31 | remoteInfo = SSDateTimePicker; 32 | }; 33 | 658A5CDD2B3523490044930D /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = 658A5CD62B3523490044930D /* SSDateTimePicker.xcodeproj */; 36 | proxyType = 2; 37 | remoteGlobalIDString = 65E058152B0E2B260049A7BA; 38 | remoteInfo = SSDateTimePickerTests; 39 | }; 40 | /* End PBXContainerItemProxy section */ 41 | 42 | /* Begin PBXCopyFilesBuildPhase section */ 43 | 6520E7C62B2D9BDF008D7329 /* Embed Frameworks */ = { 44 | isa = PBXCopyFilesBuildPhase; 45 | buildActionMask = 2147483647; 46 | dstPath = ""; 47 | dstSubfolderSpec = 10; 48 | files = ( 49 | 658A5CE22B3523B50044930D /* SSDateTimePicker.framework in Embed Frameworks */, 50 | ); 51 | name = "Embed Frameworks"; 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXCopyFilesBuildPhase section */ 55 | 56 | /* Begin PBXFileReference section */ 57 | 650720D22B194D2100AC1FB6 /* PickerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerViewModel.swift; sourceTree = ""; }; 58 | 6549D9AB2B2C4B4B0089EBB1 /* ThemeCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeCard.swift; sourceTree = ""; }; 59 | 6549D9AF2B2C4DBA0089EBB1 /* View+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extension.swift"; sourceTree = ""; }; 60 | 658A5CD62B3523490044930D /* SSDateTimePicker.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SSDateTimePicker.xcodeproj; path = ../Sources/SSDateTimePicker/SSDateTimePicker.xcodeproj; sourceTree = ""; }; 61 | 65AB6B0C2B283131009EA7EC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 62 | 65AB6B0F2B2839F9009EA7EC /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; 63 | 65AB6B112B283AED009EA7EC /* LocalizedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedString.swift; sourceTree = ""; }; 64 | 65AB6B142B285309009EA7EC /* Color+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extension.swift"; sourceTree = ""; }; 65 | 65E0584A2B0E3E850049A7BA /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 66 | 65E0584D2B0E3E850049A7BA /* ExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleApp.swift; sourceTree = ""; }; 67 | 65E0584F2B0E3E850049A7BA /* PickerExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerExampleView.swift; sourceTree = ""; }; 68 | 65E058512B0E3E860049A7BA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 69 | 65E058542B0E3E860049A7BA /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 70 | /* End PBXFileReference section */ 71 | 72 | /* Begin PBXFrameworksBuildPhase section */ 73 | 65E058472B0E3E850049A7BA /* Frameworks */ = { 74 | isa = PBXFrameworksBuildPhase; 75 | buildActionMask = 2147483647; 76 | files = ( 77 | 658A5CE12B3523B50044930D /* SSDateTimePicker.framework in Frameworks */, 78 | ); 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | /* End PBXFrameworksBuildPhase section */ 82 | 83 | /* Begin PBXGroup section */ 84 | 650720CF2B194C7900AC1FB6 /* ViewModel */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 650720D22B194D2100AC1FB6 /* PickerViewModel.swift */, 88 | ); 89 | path = ViewModel; 90 | sourceTree = ""; 91 | }; 92 | 658A5CD72B3523490044930D /* Products */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 658A5CDC2B3523490044930D /* SSDateTimePicker.framework */, 96 | 658A5CDE2B3523490044930D /* SSDateTimePickerTests.xctest */, 97 | ); 98 | name = Products; 99 | sourceTree = ""; 100 | }; 101 | 658A5CDF2B3523590044930D /* Frameworks */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | ); 105 | name = Frameworks; 106 | sourceTree = ""; 107 | }; 108 | 65AB6B0E2B28393B009EA7EC /* View */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 65E0584F2B0E3E850049A7BA /* PickerExampleView.swift */, 112 | ); 113 | path = View; 114 | sourceTree = ""; 115 | }; 116 | 65AB6B132B283AF2009EA7EC /* Common */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 65AB6B0F2B2839F9009EA7EC /* Utils.swift */, 120 | 65AB6B112B283AED009EA7EC /* LocalizedString.swift */, 121 | 6549D9AB2B2C4B4B0089EBB1 /* ThemeCard.swift */, 122 | ); 123 | path = Common; 124 | sourceTree = ""; 125 | }; 126 | 65AB6B162B285312009EA7EC /* Extension */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 65AB6B142B285309009EA7EC /* Color+Extension.swift */, 130 | 6549D9AF2B2C4DBA0089EBB1 /* View+Extension.swift */, 131 | ); 132 | path = Extension; 133 | sourceTree = ""; 134 | }; 135 | 65E058412B0E3E850049A7BA = { 136 | isa = PBXGroup; 137 | children = ( 138 | 658A5CD62B3523490044930D /* SSDateTimePicker.xcodeproj */, 139 | 65AB6B0D2B283131009EA7EC /* Localizable.strings */, 140 | 65E0584C2B0E3E850049A7BA /* Example */, 141 | 65E0584B2B0E3E850049A7BA /* Products */, 142 | 658A5CDF2B3523590044930D /* Frameworks */, 143 | ); 144 | sourceTree = ""; 145 | }; 146 | 65E0584B2B0E3E850049A7BA /* Products */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | 65E0584A2B0E3E850049A7BA /* Example.app */, 150 | ); 151 | name = Products; 152 | sourceTree = ""; 153 | }; 154 | 65E0584C2B0E3E850049A7BA /* Example */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | 65AB6B162B285312009EA7EC /* Extension */, 158 | 65AB6B132B283AF2009EA7EC /* Common */, 159 | 65AB6B0E2B28393B009EA7EC /* View */, 160 | 650720CF2B194C7900AC1FB6 /* ViewModel */, 161 | 65E0584D2B0E3E850049A7BA /* ExampleApp.swift */, 162 | 65E058512B0E3E860049A7BA /* Assets.xcassets */, 163 | 65E058532B0E3E860049A7BA /* Preview Content */, 164 | ); 165 | path = Example; 166 | sourceTree = ""; 167 | }; 168 | 65E058532B0E3E860049A7BA /* Preview Content */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | 65E058542B0E3E860049A7BA /* Preview Assets.xcassets */, 172 | ); 173 | path = "Preview Content"; 174 | sourceTree = ""; 175 | }; 176 | /* End PBXGroup section */ 177 | 178 | /* Begin PBXNativeTarget section */ 179 | 65E058492B0E3E850049A7BA /* Example */ = { 180 | isa = PBXNativeTarget; 181 | buildConfigurationList = 65E058582B0E3E860049A7BA /* Build configuration list for PBXNativeTarget "Example" */; 182 | buildPhases = ( 183 | 65E058462B0E3E850049A7BA /* Sources */, 184 | 65E058472B0E3E850049A7BA /* Frameworks */, 185 | 65E058482B0E3E850049A7BA /* Resources */, 186 | 6520E7C62B2D9BDF008D7329 /* Embed Frameworks */, 187 | ); 188 | buildRules = ( 189 | ); 190 | dependencies = ( 191 | ); 192 | name = Example; 193 | productName = Example; 194 | productReference = 65E0584A2B0E3E850049A7BA /* Example.app */; 195 | productType = "com.apple.product-type.application"; 196 | }; 197 | /* End PBXNativeTarget section */ 198 | 199 | /* Begin PBXProject section */ 200 | 65E058422B0E3E850049A7BA /* Project object */ = { 201 | isa = PBXProject; 202 | attributes = { 203 | BuildIndependentTargetsInParallel = 1; 204 | LastSwiftUpdateCheck = 1430; 205 | LastUpgradeCheck = 1430; 206 | TargetAttributes = { 207 | 65E058492B0E3E850049A7BA = { 208 | CreatedOnToolsVersion = 14.3.1; 209 | }; 210 | }; 211 | }; 212 | buildConfigurationList = 65E058452B0E3E850049A7BA /* Build configuration list for PBXProject "Example" */; 213 | compatibilityVersion = "Xcode 14.0"; 214 | developmentRegion = en; 215 | hasScannedForEncodings = 0; 216 | knownRegions = ( 217 | en, 218 | Base, 219 | ar, 220 | ); 221 | mainGroup = 65E058412B0E3E850049A7BA; 222 | productRefGroup = 65E0584B2B0E3E850049A7BA /* Products */; 223 | projectDirPath = ""; 224 | projectReferences = ( 225 | { 226 | ProductGroup = 658A5CD72B3523490044930D /* Products */; 227 | ProjectRef = 658A5CD62B3523490044930D /* SSDateTimePicker.xcodeproj */; 228 | }, 229 | ); 230 | projectRoot = ""; 231 | targets = ( 232 | 65E058492B0E3E850049A7BA /* Example */, 233 | ); 234 | }; 235 | /* End PBXProject section */ 236 | 237 | /* Begin PBXReferenceProxy section */ 238 | 658A5CDC2B3523490044930D /* SSDateTimePicker.framework */ = { 239 | isa = PBXReferenceProxy; 240 | fileType = wrapper.framework; 241 | path = SSDateTimePicker.framework; 242 | remoteRef = 658A5CDB2B3523490044930D /* PBXContainerItemProxy */; 243 | sourceTree = BUILT_PRODUCTS_DIR; 244 | }; 245 | 658A5CDE2B3523490044930D /* SSDateTimePickerTests.xctest */ = { 246 | isa = PBXReferenceProxy; 247 | fileType = wrapper.cfbundle; 248 | path = SSDateTimePickerTests.xctest; 249 | remoteRef = 658A5CDD2B3523490044930D /* PBXContainerItemProxy */; 250 | sourceTree = BUILT_PRODUCTS_DIR; 251 | }; 252 | /* End PBXReferenceProxy section */ 253 | 254 | /* Begin PBXResourcesBuildPhase section */ 255 | 65E058482B0E3E850049A7BA /* Resources */ = { 256 | isa = PBXResourcesBuildPhase; 257 | buildActionMask = 2147483647; 258 | files = ( 259 | 65E058552B0E3E860049A7BA /* Preview Assets.xcassets in Resources */, 260 | 65AB6B0B2B283131009EA7EC /* Localizable.strings in Resources */, 261 | 65E058522B0E3E860049A7BA /* Assets.xcassets in Resources */, 262 | ); 263 | runOnlyForDeploymentPostprocessing = 0; 264 | }; 265 | /* End PBXResourcesBuildPhase section */ 266 | 267 | /* Begin PBXSourcesBuildPhase section */ 268 | 65E058462B0E3E850049A7BA /* Sources */ = { 269 | isa = PBXSourcesBuildPhase; 270 | buildActionMask = 2147483647; 271 | files = ( 272 | 65AB6B122B283AED009EA7EC /* LocalizedString.swift in Sources */, 273 | 65AB6B152B285309009EA7EC /* Color+Extension.swift in Sources */, 274 | 6549D9B02B2C4DBA0089EBB1 /* View+Extension.swift in Sources */, 275 | 6549D9AC2B2C4B4B0089EBB1 /* ThemeCard.swift in Sources */, 276 | 650720D32B194D2100AC1FB6 /* PickerViewModel.swift in Sources */, 277 | 65E058502B0E3E850049A7BA /* PickerExampleView.swift in Sources */, 278 | 65E0584E2B0E3E850049A7BA /* ExampleApp.swift in Sources */, 279 | 65AB6B102B2839F9009EA7EC /* Utils.swift in Sources */, 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | }; 283 | /* End PBXSourcesBuildPhase section */ 284 | 285 | /* Begin PBXVariantGroup section */ 286 | 65AB6B0D2B283131009EA7EC /* Localizable.strings */ = { 287 | isa = PBXVariantGroup; 288 | children = ( 289 | 65AB6B0C2B283131009EA7EC /* en */, 290 | ); 291 | name = Localizable.strings; 292 | sourceTree = ""; 293 | }; 294 | /* End PBXVariantGroup section */ 295 | 296 | /* Begin XCBuildConfiguration section */ 297 | 65E058562B0E3E860049A7BA /* Debug */ = { 298 | isa = XCBuildConfiguration; 299 | buildSettings = { 300 | ALWAYS_SEARCH_USER_PATHS = NO; 301 | CLANG_ANALYZER_NONNULL = YES; 302 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 303 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 304 | CLANG_ENABLE_MODULES = YES; 305 | CLANG_ENABLE_OBJC_ARC = YES; 306 | CLANG_ENABLE_OBJC_WEAK = YES; 307 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 308 | CLANG_WARN_BOOL_CONVERSION = YES; 309 | CLANG_WARN_COMMA = YES; 310 | CLANG_WARN_CONSTANT_CONVERSION = YES; 311 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 312 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 313 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 314 | CLANG_WARN_EMPTY_BODY = YES; 315 | CLANG_WARN_ENUM_CONVERSION = YES; 316 | CLANG_WARN_INFINITE_RECURSION = YES; 317 | CLANG_WARN_INT_CONVERSION = YES; 318 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 319 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 320 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 321 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 322 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 323 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 324 | CLANG_WARN_STRICT_PROTOTYPES = YES; 325 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 326 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 327 | CLANG_WARN_UNREACHABLE_CODE = YES; 328 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 329 | COPY_PHASE_STRIP = NO; 330 | DEBUG_INFORMATION_FORMAT = dwarf; 331 | ENABLE_STRICT_OBJC_MSGSEND = YES; 332 | ENABLE_TESTABILITY = YES; 333 | GCC_C_LANGUAGE_STANDARD = gnu11; 334 | GCC_DYNAMIC_NO_PIC = NO; 335 | GCC_NO_COMMON_BLOCKS = YES; 336 | GCC_OPTIMIZATION_LEVEL = 0; 337 | GCC_PREPROCESSOR_DEFINITIONS = ( 338 | "DEBUG=1", 339 | "$(inherited)", 340 | ); 341 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 342 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 343 | GCC_WARN_UNDECLARED_SELECTOR = YES; 344 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 345 | GCC_WARN_UNUSED_FUNCTION = YES; 346 | GCC_WARN_UNUSED_VARIABLE = YES; 347 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 348 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 349 | MTL_FAST_MATH = YES; 350 | ONLY_ACTIVE_ARCH = YES; 351 | SDKROOT = iphoneos; 352 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 353 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 354 | }; 355 | name = Debug; 356 | }; 357 | 65E058572B0E3E860049A7BA /* Release */ = { 358 | isa = XCBuildConfiguration; 359 | buildSettings = { 360 | ALWAYS_SEARCH_USER_PATHS = NO; 361 | CLANG_ANALYZER_NONNULL = YES; 362 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 363 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 364 | CLANG_ENABLE_MODULES = YES; 365 | CLANG_ENABLE_OBJC_ARC = YES; 366 | CLANG_ENABLE_OBJC_WEAK = YES; 367 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 368 | CLANG_WARN_BOOL_CONVERSION = YES; 369 | CLANG_WARN_COMMA = YES; 370 | CLANG_WARN_CONSTANT_CONVERSION = YES; 371 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 372 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 373 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 374 | CLANG_WARN_EMPTY_BODY = YES; 375 | CLANG_WARN_ENUM_CONVERSION = YES; 376 | CLANG_WARN_INFINITE_RECURSION = YES; 377 | CLANG_WARN_INT_CONVERSION = YES; 378 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 379 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 380 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 381 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 382 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 383 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 384 | CLANG_WARN_STRICT_PROTOTYPES = YES; 385 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 386 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 387 | CLANG_WARN_UNREACHABLE_CODE = YES; 388 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 389 | COPY_PHASE_STRIP = NO; 390 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 391 | ENABLE_NS_ASSERTIONS = NO; 392 | ENABLE_STRICT_OBJC_MSGSEND = YES; 393 | GCC_C_LANGUAGE_STANDARD = gnu11; 394 | GCC_NO_COMMON_BLOCKS = YES; 395 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 396 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 397 | GCC_WARN_UNDECLARED_SELECTOR = YES; 398 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 399 | GCC_WARN_UNUSED_FUNCTION = YES; 400 | GCC_WARN_UNUSED_VARIABLE = YES; 401 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 402 | MTL_ENABLE_DEBUG_INFO = NO; 403 | MTL_FAST_MATH = YES; 404 | SDKROOT = iphoneos; 405 | SWIFT_COMPILATION_MODE = wholemodule; 406 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 407 | VALIDATE_PRODUCT = YES; 408 | }; 409 | name = Release; 410 | }; 411 | 65E058592B0E3E860049A7BA /* Debug */ = { 412 | isa = XCBuildConfiguration; 413 | buildSettings = { 414 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 415 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 416 | CODE_SIGN_STYLE = Automatic; 417 | CURRENT_PROJECT_VERSION = 1; 418 | DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\""; 419 | DEVELOPMENT_TEAM = K7XJG666ZW; 420 | ENABLE_PREVIEWS = YES; 421 | GENERATE_INFOPLIST_FILE = YES; 422 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 423 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 424 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 425 | INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; 426 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; 427 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 428 | LD_RUNPATH_SEARCH_PATHS = ( 429 | "$(inherited)", 430 | "@executable_path/Frameworks", 431 | ); 432 | MARKETING_VERSION = 1.0; 433 | PRODUCT_BUNDLE_IDENTIFIER = simform.Example; 434 | PRODUCT_NAME = "$(TARGET_NAME)"; 435 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 436 | SUPPORTS_MACCATALYST = NO; 437 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; 438 | SWIFT_EMIT_LOC_STRINGS = YES; 439 | SWIFT_VERSION = 5.0; 440 | TARGETED_DEVICE_FAMILY = 1; 441 | }; 442 | name = Debug; 443 | }; 444 | 65E0585A2B0E3E860049A7BA /* Release */ = { 445 | isa = XCBuildConfiguration; 446 | buildSettings = { 447 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 448 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 449 | CODE_SIGN_STYLE = Automatic; 450 | CURRENT_PROJECT_VERSION = 1; 451 | DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\""; 452 | DEVELOPMENT_TEAM = K7XJG666ZW; 453 | ENABLE_PREVIEWS = YES; 454 | GENERATE_INFOPLIST_FILE = YES; 455 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 456 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 457 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 458 | INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; 459 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; 460 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 461 | LD_RUNPATH_SEARCH_PATHS = ( 462 | "$(inherited)", 463 | "@executable_path/Frameworks", 464 | ); 465 | MARKETING_VERSION = 1.0; 466 | PRODUCT_BUNDLE_IDENTIFIER = simform.Example; 467 | PRODUCT_NAME = "$(TARGET_NAME)"; 468 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 469 | SUPPORTS_MACCATALYST = NO; 470 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; 471 | SWIFT_EMIT_LOC_STRINGS = YES; 472 | SWIFT_VERSION = 5.0; 473 | TARGETED_DEVICE_FAMILY = 1; 474 | }; 475 | name = Release; 476 | }; 477 | /* End XCBuildConfiguration section */ 478 | 479 | /* Begin XCConfigurationList section */ 480 | 65E058452B0E3E850049A7BA /* Build configuration list for PBXProject "Example" */ = { 481 | isa = XCConfigurationList; 482 | buildConfigurations = ( 483 | 65E058562B0E3E860049A7BA /* Debug */, 484 | 65E058572B0E3E860049A7BA /* Release */, 485 | ); 486 | defaultConfigurationIsVisible = 0; 487 | defaultConfigurationName = Release; 488 | }; 489 | 65E058582B0E3E860049A7BA /* Build configuration list for PBXNativeTarget "Example" */ = { 490 | isa = XCConfigurationList; 491 | buildConfigurations = ( 492 | 65E058592B0E3E860049A7BA /* Debug */, 493 | 65E0585A2B0E3E860049A7BA /* Release */, 494 | ); 495 | defaultConfigurationIsVisible = 0; 496 | defaultConfigurationName = Release; 497 | }; 498 | /* End XCConfigurationList section */ 499 | }; 500 | rootObject = 65E058422B0E3E850049A7BA /* Project object */; 501 | } 502 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/xcuserdata/rizwana.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSDateTimePicker/286fbbfd3b33e809f66dff57258cb6b3c507b2af/Example/Example.xcodeproj/project.xcworkspace/xcuserdata/rizwana.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 42 | 44 | 50 | 51 | 52 | 53 | 59 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/xcuserdata/rizwana.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/xcuserdata/rizwana.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Example.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 65E058492B0E3E850049A7BA 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/Colors/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/Colors/black.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "18", 9 | "green" : "18", 10 | "red" : "18" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/Colors/yellow.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "134", 9 | "green" : "204", 10 | "red" : "247" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Example/Common/LocalizedString.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalizedString.swift 3 | // Example 4 | // 5 | // Created by Rizwana Desai on 12/12/23. 6 | // 7 | 8 | import Foundation 9 | 10 | class LocalizedString { 11 | 12 | static let multipleDateSelectionExample = "Multiple date selection Example." 13 | static let singleDateSelectionExample = "Single date selection Example." 14 | static let dateRangeSelectionExample = "Date range selection Example." 15 | static let timePickerExample = "Time Picker Example" 16 | static let selectMultipleDate = "Select Multiple Date" 17 | static let selectSingleDate = "Select Single Date" 18 | static let selectDateRange = "Select Date Range" 19 | static let selectTime = "Select Time" 20 | static let customizedDatePicker = "Customized Date Picker Example" 21 | static let startDate = "Start Date:" 22 | static let endDate = "End Date:" 23 | static let selectedDate = "Selected Date:" 24 | static let selectedDates = "Selected Dates:" 25 | static let selectedTime = "Selected Time:" 26 | static let dateTimePickerExample = "SSDateTimePicker Example" 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Example/Example/Common/ThemeCard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThemeCard.swift 3 | // Example 4 | // 5 | // Created by Rizwana Desai on 15/12/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ThemeCard: ViewModifier { 11 | 12 | func body(content: Content) -> some View { 13 | content 14 | .padding(20) 15 | .background(Color.lightPink) 16 | .clipShape(RoundedRectangle(cornerRadius: 0.0)) 17 | .shadow(color: .peach, radius: 2) 18 | .padding(EdgeInsets(top: 10, leading: 20, bottom: 0, trailing: 20)) 19 | } 20 | 21 | } 22 | 23 | -------------------------------------------------------------------------------- /Example/Example/Common/Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThemeButton.swift 3 | // Example 4 | // 5 | // Created by Rizwana Desai on 12/12/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ThemeButton: ViewModifier { 11 | 12 | func body(content: Content) -> some View { 13 | content 14 | .frame(maxWidth: .infinity, minHeight: 44) 15 | .font(.system(size: 14, weight: .semibold)) 16 | .background(Color.darkPink) 17 | .foregroundColor(Color.white) 18 | .cornerRadius(10) 19 | .padding(.leading, 20) 20 | .padding(.trailing, 20) 21 | } 22 | 23 | } 24 | 25 | struct ClearBackgroundView: UIViewRepresentable { 26 | 27 | func makeUIView(context: Context) -> UIView { 28 | return InnerView() 29 | } 30 | 31 | func updateUIView(_ uiView: UIView, context: Context) { 32 | } 33 | 34 | private class InnerView: UIView { 35 | override func didMoveToWindow() { 36 | super.didMoveToWindow() 37 | superview?.superview?.backgroundColor = .clear 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Example/Example/ExampleApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleApp.swift 3 | // Example 4 | // 5 | // Created by Rizwana Desai on 22/11/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct ExampleApp: App { 12 | 13 | var body: some Scene { 14 | WindowGroup { 15 | PickerExampleView() 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Example/Example/Extension/Color+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color+Extension.swift 3 | // Example 4 | // 5 | // Created by Rizwana Desai on 12/12/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | extension Color { 12 | 13 | public static let themeBlack = Color("black") 14 | public static let themeYellow = Color("yellow") 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Example/Example/Extension/View+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // View+Extension.swift 3 | // Example 4 | // 5 | // Created by Rizwana Desai on 15/12/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | extension View { 11 | 12 | func themeButton() -> some View { 13 | self.modifier(ThemeButton()) 14 | } 15 | 16 | func themeCard() -> some View { 17 | self.modifier(ThemeCard()) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Example/Example/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Example/View/PickerExampleView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PickerExampleView.swift 3 | // Example 4 | // 5 | // Created by Rizwana Desai on 22/11/23. 6 | // 7 | 8 | import SwiftUI 9 | import SSDateTimePicker 10 | 11 | struct PickerExampleView: View { 12 | 13 | //MARK: - Properties 14 | 15 | @State var showDatePicker: Bool = false 16 | @State var showMultiDatePicker: Bool = false 17 | @State var showDateRangePicker: Bool = false 18 | @State var showTimePicker: Bool = false 19 | @State var displayCustomizedCalendar: Bool = false 20 | @State var selectedDate: Date = Date() 21 | @State var selectedDateCombine: Date? 22 | @ObservedObject var pickerViewModel = PickerViewModel() 23 | 24 | //MARK: - Body 25 | var body: some View { 26 | ZStack { 27 | dateTimePickerExampleView 28 | datePicker 29 | multiDatePicker 30 | dateRangePicker 31 | timePicker 32 | } 33 | 34 | } 35 | 36 | //MARK: - Sub views 37 | 38 | var headerView: some View { 39 | Text(LocalizedString.dateTimePickerExample) 40 | .font(.system(size: 22, weight: .bold)) 41 | .foregroundColor(.white) 42 | .frame(maxWidth: .infinity, maxHeight: 60) 43 | .background(Color.darkPink.ignoresSafeArea()) 44 | } 45 | 46 | var dateTimePickerExampleView: some View { 47 | VStack { 48 | headerView 49 | ScrollView { 50 | VStack(alignment: .center, spacing: 10) { 51 | singleDateSelectionView 52 | multipleDateSelectionView 53 | dateRangeSelectionView 54 | timePickerView 55 | } 56 | } 57 | } 58 | .background(Color.white) 59 | .foregroundStyle(.black) 60 | } 61 | 62 | var singleDateSelectionView: some View { 63 | VStack(spacing: 10) { 64 | Text("\(LocalizedString.singleDateSelectionExample)") 65 | .font(.callout) 66 | Text("\(LocalizedString.selectedDate) \(pickerViewModel.selectedDate?.monthDateYear ?? "-")") 67 | .font(.footnote) 68 | btnSelectSingleDate 69 | } 70 | .themeCard() 71 | } 72 | 73 | var multipleDateSelectionView: some View { 74 | VStack(spacing: 10) { 75 | Text("\(LocalizedString.multipleDateSelectionExample)") 76 | .font(.callout) 77 | Text("\(LocalizedString.selectedDates) \(pickerViewModel.getSelectedDates() ?? "-")") 78 | .font(.footnote) 79 | btnSelectMultipleDates 80 | } 81 | .themeCard() 82 | } 83 | 84 | var dateRangeSelectionView: some View { 85 | VStack(spacing: 10) { 86 | Text("\(LocalizedString.dateRangeSelectionExample)") 87 | .font(.callout) 88 | Text("\(LocalizedString.startDate) \(pickerViewModel.selectedDateRange?.startDate.monthDateYear ?? "-"), \(LocalizedString.endDate) \(pickerViewModel.selectedDateRange?.endDate.monthDateYear ?? "-")") 89 | .font(.footnote) 90 | btnSelectDateRange 91 | } 92 | .themeCard() 93 | } 94 | 95 | var btnSelectDateRange: some View { 96 | Button { 97 | withAnimation { 98 | showDateRangePicker.toggle() 99 | } 100 | } label: { 101 | Text(LocalizedString.selectDateRange) 102 | .themeButton() 103 | } 104 | } 105 | 106 | var timePickerView: some View { 107 | VStack(spacing: 10) { 108 | Text("\(LocalizedString.timePickerExample)") 109 | .font(.callout) 110 | Text("\(LocalizedString.selectedTime) \(pickerViewModel.selectedTime?.timeOnlyWithPadding ?? "-")") 111 | .font(.footnote) 112 | btnTimePicker 113 | } 114 | .themeCard() 115 | } 116 | 117 | var btnTimePicker: some View { 118 | Button { 119 | withAnimation { 120 | showTimePicker.toggle() 121 | } 122 | } label: { 123 | Text(LocalizedString.selectTime) 124 | .themeButton() 125 | } 126 | } 127 | 128 | var btnSelectSingleDate: some View { 129 | Button { 130 | withAnimation { 131 | showDatePicker.toggle() 132 | } 133 | } label: { 134 | Text(LocalizedString.selectSingleDate) 135 | .themeButton() 136 | } 137 | } 138 | 139 | var btnSelectMultipleDates: some View { 140 | Button { 141 | withAnimation { 142 | showMultiDatePicker.toggle() 143 | } 144 | } label: { 145 | Text(LocalizedString.selectMultipleDate) 146 | .themeButton() 147 | } 148 | } 149 | 150 | var datePicker: some View { 151 | SSDatePicker(showDatePicker: $showDatePicker) 152 | .selectedDate(self.pickerViewModel.selectedDate) 153 | .onDateSelection({ date in 154 | self.pickerViewModel.selectedDate = date 155 | }) 156 | } 157 | 158 | var dateRangePicker: some View { 159 | SSDatePicker(showDatePicker: $showDateRangePicker) 160 | .enableDateRangeSelection() 161 | .selectedDateRange(pickerViewModel.selectedDateRange) 162 | .onDateRangeSelection({ dateRange in 163 | pickerViewModel.selectedDateRange = dateRange 164 | }) 165 | } 166 | 167 | var multiDatePicker: some View { 168 | SSDatePicker(showDatePicker: $showMultiDatePicker) 169 | .enableMultipleDateSelection() 170 | .selectedDates(pickerViewModel.selectedDates) 171 | .onMultiDateSelection({ dates in 172 | pickerViewModel.selectedDates = dates 173 | }) 174 | } 175 | 176 | var timePicker: some View { 177 | SSTimePicker(showTimePicker: $showTimePicker) 178 | .selectedTime(pickerViewModel.selectedTime) 179 | .onTimeSelection { time in 180 | pickerViewModel.selectedTime = time 181 | } 182 | } 183 | 184 | } 185 | 186 | struct ContentView_Previews: PreviewProvider { 187 | static var previews: some View { 188 | PickerExampleView() 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /Example/Example/ViewModel/PickerViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PickerViewModel.swift 3 | // Example 4 | // 5 | // Created by Rizwana Desai on 01/12/23. 6 | // 7 | 8 | import Foundation 9 | import SSDateTimePicker 10 | import SwiftUI 11 | 12 | final class PickerViewModel: ObservableObject { 13 | 14 | // MARK: - Properties 15 | 16 | @Published var selectedDate: Date? 17 | @Published var selectedDates: [Date]? 18 | @Published var selectedDateRange: DateRange? 19 | @Published var selectedTime: Date? 20 | 21 | //MARK: - Initializer 22 | 23 | init() { 24 | 25 | } 26 | 27 | //MARK: - Methods 28 | 29 | func getSelectedDates() -> String? { 30 | let dates = self.selectedDates?.compactMap({ date in 31 | date.monthDateYear 32 | }).joined(separator: ",") 33 | return dates 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Example/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | Example 4 | 5 | Created by Rizwana Desai on 12/12/23. 6 | 7 | */ 8 | 9 | "Ok" = "Ok"; 10 | "cancel" = "Cancel"; 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Simform Solutions 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.9 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: "SSDateTimePicker", 8 | defaultLocalization: "en", platforms: [.iOS(.v15)], 9 | 10 | products: [ 11 | // Products define the executables and libraries a package produces, making them visible to other packages. 12 | .library( 13 | name: "SSDateTimePicker", 14 | targets: ["SSDateTimePicker"]), 15 | ], 16 | targets: [ 17 | // Targets are the basic building blocks of a package, defining a module or a test suite. 18 | // Targets can depend on other targets in this package and products from dependencies. 19 | .target( 20 | name: "SSDateTimePicker"), 21 | 22 | ] 23 | ) 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | SSDateTimePicker 4 | 5 | 6 | # SSDateTimePicker 7 | 8 | SSDateTimePicker is a SwiftUI library that simplifies date and time selection in your applications, providing a variety of features and a customizable UI for both date and time pickers. 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 27 | 30 | 33 | 34 | 35 |
Time PickerDate PickerMultiple Date PickerDate Range Picker
22 | 23 | 25 | 26 | 28 | 29 | 31 | 32 |
36 | 37 | 38 | ## Features! 39 | * Single Date Selection 40 | * Multiple Date Selection 41 | * Date Range Selection 42 | * Disable Past or Future Dates 43 | * Disable Specific Dates 44 | * Limit date selection to a predefined range 45 | * Time selection with a clock-style interface 46 | * Personalize fonts and colors for seamless integration with your app's design. 47 | 48 | ## Requirements 49 | 50 | * iOS 15.0+ 51 | * Xcode 12+ 52 | 53 | ## Installation 54 | 55 | ### [Swift Package Manager](https://swift.org/package-manager/) 56 | 57 | You can install `SSDateTimePicker` using Swift Package Manager by: 58 | 59 | 1. Go to `Xcode` -> `File` -> `Add Package Dependencies...` 60 | 2. Add package URL [https://github.com/SimformSolutionsPvtLtd/SSDateTimePicker][SSDateTimePicker] 61 | 62 | ### [CocoaPods](http://cocoapods.org) 63 | 64 | To install `SSDateTimePicker`, simply add the following line to your Podfile: 65 | 66 | ```swift 67 | pod 'SSDateTimePicker' 68 | ``` 69 | 70 | 71 | ## Usage 72 | 73 | ### SSDatePicker 74 | 75 | SSDatePicker offers versatile date selection options, including single date, multiple date, and date range selection. Follow this guide to set up the date picker according to your preferences: 76 | 77 | 1. import `SSDateTimePicker`. 78 | 2. Add a bool to control date picker presentation state. 79 | 3. Add a `SSDatePicker` in your view. 80 | 81 | 82 | ```swift 83 | import SSDateTimePicker 84 | 85 | struct PickerExample: View { 86 | 87 | @State var showDatePicker: Bool = false 88 | @State var selectedDate: Date? 89 | 90 | var body: some View { 91 | ZStack { 92 | Text("Your view") 93 | SSDatePicker(showDatePicker: $showDatePicker) 94 | } 95 | } 96 | } 97 | ``` 98 | 99 | ###### Single Date Selection Picker 100 | 101 | 1. Add `.selectedDate(selectedDate)` modifier to pre-select specific date. 102 | 2. Set the callback closure ` .onDateSelection` to get selected date. 103 | 104 | ```swift 105 | SSDatePicker(showDatePicker: $showDatePicker) 106 | .selectedDate(selectedDate) 107 | .onDateSelection({ date in 108 | selectedDate = date 109 | }) 110 | ``` 111 | 112 | ###### Multiple date selection picker 113 | 1. To enable multiple date selection add `.enableMultipleDateSelection()` modifier. 114 | 2. Add `.selectedDates(selectedDates)` modifier to pre-select specific dates. 115 | 4. Set `.onMultiDateSelection` callback to get selected multiple dates. 116 | 117 | ```swift 118 | SSDatePicker(showDatePicker: $showDatePicker) 119 | .enableMultipleDateSelection() 120 | .selectedDates(selectedDates) 121 | .onMultiDateSelection({ dates in 122 | selectedDates = dates 123 | }) 124 | ``` 125 | 126 | ###### Date Range Selection Picker 127 | 128 | 1. To enable date range selection add `.enableDateRangeSelection()` modifier. 129 | 2. Add `.selectedDateRange(selectedDateRange))` modifier to pre-select specific dates. 130 | 2. Sets `.onDateRangeSelection` callback to get selected date range. 131 | 132 | ```swift 133 | SSDatePicker(showDatePicker: $showDatePicker) 134 | .enableDateRangeSelection() 135 | .selectedDateRange(selectedDateRange) 136 | .onDateRangeSelection({ dateRange in 137 | selectedDateRange = dateRange 138 | }) 139 | ``` 140 | 141 | ### SSTimePicker 142 | 143 | 1. import `SSDateTimePicker` 144 | 2. Add a bool to control date-time picker presentation state 145 | 3. Add a `SSTimePicker` in your view 146 | 4. Add `.selectedTime(pickerViewModel.selectedTime)` modifier to pre-select specific dates. 147 | 2. Set `.onTimeSelection` callback to get selected date range. 148 | 149 | ```swift 150 | import SSDateTimePicker 151 | 152 | struct TimePickerExample: View { 153 | 154 | @State var showTimePicker: Bool = false 155 | @State var selectedTime: Time? 156 | 157 | var body: some View { 158 | ZStack { 159 | Text("Your view") 160 | SSTimePicker(showTimePicker: $showTimePicker) 161 | .selectedTime(selectedTime) 162 | .onTimeSelection { time in 163 | selectedTime = time 164 | } 165 | } 166 | } 167 | } 168 | ``` 169 | 170 | ## Configuration Guide 171 | 172 | Explore the following modifiers to effortlessly customize the UI and behaviour of SSDateTimePicker to suit your preferences. 173 | 174 | ### SSDatePicker 175 | 176 | ###### Behavioral Modifiers 177 | 178 | - `.minimumDate(_ date: Date)` - Set the minimum selectable date in the date picker. 179 | - `.maximumDate(_ date: Date)` - Set the maximum selectable date in the date picker. 180 | - `.disableDates(_ dates: [Date])` - Block the selection of specific dates. 181 | - `.disablePastDate()` - Prevent the selection of past dates. 182 | - `.disableFutureDate()` - Prevent the selection of future dates. 183 | - `.currentMonth(_ date: Date)` - Set the initial display month in the date picker, providing a specific starting point when the picker is opened. By default it will open current month claendar. 184 | - `.enableDateRangeSelection()` - Enable the selection of date range. 185 | - `.enableMultipleDateSelection()` - Enable the selection of multiple dates. 186 | - `.selectedDate(_ date: Date?)` - Pre-select a specific date in the date picker. 187 | - `.selectedDates(_ dates: [Date]?)` - Pre-select a specific dates in the date picker. 188 | - `.selectedDates(_ dateRange: DateRange?)` - Pre-select a specific date range in the date picker. 189 | - `.calendar(_ calendar: Calendar)` - Set the calendar used by the date picker. 190 | 191 | ###### UI Modifiers 192 | 193 | - `.themeColor(pickerBackgroundColor: Color, primaryColor: Color)` - Define the overall theme color, where the primary color sets the background of selected dates, buttons and selectected month, year foreground. 194 | - `.todayColor(backgroundColor: Color?, foregroundColor: Color?)` - Highlight today's date with specific foreground and background colors. 195 | - `.todayDateSelectionColor(backgroundColor: Color?, foregroundColor: Color?)` - Adjust the foreground and background colors for the today's date selection state. 196 | - `.headerTitleStyle(color: Color?, font: Font?)` - Customize the font and color of the header text. 197 | - `.headerDateStyle(color: Color?, font: Font?)` - Customize font and color for header date text. 198 | - `.weekdayStyle(color: Color?, font: Font?)` - Adjust the text color and font of weekdays. 199 | - `.dateStyle(color: Color?, font: Font?)` - Customize color and font for date text. 200 | - `.monthStyle(color: Color?, font: Font?)` - Modify the font and color for the selected month. 201 | - `.selectedMonthStyle(color: Color?, font: Font?)` - Customize font and color for selected month. 202 | - `.yearStyle(color: Color?, font: Font?) -> SSDatePicker ` - Customize the text color and font for the year text. 203 | - `.selectedYearStyle(color: Color?, font: Font?)` - Adjust the font and color for the selected year. 204 | - `.buttonStyle(color: Color?, font: Font?)` - Set the font and color for the buttons. 205 | - `.currentMonthYearLabelStyle(color: Color?, font: Font?)` - Customize the color and font for the label displaying the current month and year in the bottom navigation. 206 | - `.selectedDateColor(backgroundColor: Color?, foregroundColor: Color?) ` - Change the foreground and background color for selected dates. 207 | - `.pickerBackgroundColor(_ color: Color) ` - Define the background color of the entire picker view. 208 | - `.sepratorLineColor(_ color: Color)` - Change the color of the separator line within the picker. 209 | - `.popupOverlayColor(_ color: Color)` - Customize the color of the popup overlay, 210 | 211 | ### SSTimePicker 212 | 213 | - `themeColor(pickerBackgroundColor: Color, primaryColor: Color, timeLabelBackgroundColor: Color)` - Apply a custom color scheme to the time picker, primary color is designated for the clock hand and the foreground of the time label. 214 | - `selectedTime(_ time: Time?)` - Set the initially selected time for the time picker. 215 | - `headerTitleStyle(color: Color?, font: Font?)` - Customize the style of the header title. 216 | - `timeLabelStyle(color: Color?, font: Font?)` - Customize time label(HH:MM) font and foreground color. 217 | - `timeFormatStyle(color: Color?, font: Font?)` - Modify Time format(AM/PM) color and font. 218 | - `selectedTimeFormatStyle(color: Color?, font: Font?)` - Customize selected time format(AM/PM) style. 219 | - `clockNumberStyle(color: Color?, font: Font?)` - Customize the style of the clock numbers. 220 | - `buttonStyle(color: Color?, font: Font?)` - Customize buttons font and foreground color. 221 | 222 | ## Find this samples useful? :heart: 223 | 224 | Support it by joining [stargazers] :star: for this repository. 225 | 226 | ## How to Contribute :handshake: 227 | 228 | Whether you're helping us fix bugs, improve the docs, or a feature request, we'd love to have you! :muscle: \ 229 | Check out our __[Contributing Guide]__ for ideas on contributing. 230 | 231 | ## Bugs and Feedback 232 | 233 | For bugs, feature feature requests, and discussion use [GitHub Issues]. 234 | 235 | ## Other Mobile Libraries 236 | 237 | Check out our other libraries [Awesome-Mobile-Libraries]. 238 | 239 | ## License 240 | 241 | Distributed under the MIT license. See [LICENSE] for details. 242 | 243 | 244 | 245 | 246 | [SSDateTimePicker]: https://github.com/SimformSolutionsPvtLtd/SSDateTimePicker 247 | 248 | [Swift Package Manager]: https://www.swift.org/package-manager 249 | 250 | [stargazers]: https://github.com/SimformSolutionsPvtLtd/SSDateTimePicker/stargazers 251 | 252 | [Awesome-Mobile-Libraries]: https://github.com/SimformSolutionsPvtLtd/Awesome-Mobile-Libraries 253 | 254 | [license]: LICENSE 255 | 256 | [Github Issues]: https://github.com/SimformSolutionsPvtLtd/SSDateTimePicker/issues 257 | 258 | [Contributing Guide]: CONTRIBUTING.md 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /SSDateTimePicker.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |spec| 3 | 4 | spec.name = "SSDateTimePicker" 5 | spec.version = "1.0.0" 6 | spec.summary = "Date and time selection picker." 7 | spec.description = "SSDateTimePicker offers versatile date and time selection options, including single date, multiple date, and date range selection." 8 | spec.homepage = "https://github.com/SimformSolutionsPvtLtd/SSDateTimePicker.git" 9 | spec.license = { :type => 'MIT', :file => 'LICENSE' } 10 | 11 | spec.author = { "Rizwana" => "rizwana@simformsolutions.com" } 12 | 13 | spec.platform = :ios, "15.0" 14 | spec.source = { :git => "https://github.com/SimformSolutionsPvtLtd/SSDateTimePicker.git", :tag => spec.version.to_s } 15 | spec.source_files = "Sources", "Sources/SSDateTimePicker/SSDateTimePicker/**/*.{swift, xcassets}" 16 | spec.resources = "Sources/SSDateTimePicker/**/*.{xcassets,storyboard,xib,plist,json,strings,lproj}" 17 | spec.swift_versions = "5.9" 18 | 19 | end 20 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |spec| 3 | 4 | spec.name = "SSDateTimePicker" 5 | spec.version = "1.0.0" 6 | spec.summary = "Date and time selection picker." 7 | spec.description = "SSDateTimePicker offers versatile date and time selection options, including single date, multiple date, and date range selection." 8 | spec.homepage = "https://github.com/SimformSolutionsPvtLtd/SSDateTimePicker" 9 | spec.license = "MIT" 10 | 11 | spec.author = { "Rizwana" => "rizwana@simformsolutions.com" } 12 | 13 | spec.platform = :ios, "15.0" 14 | spec.source = { :git => "https://github.com/SimformSolutionsPvtLtd/SSDateTimePicker.git", :tag => spec.version.to_s } 15 | spec.source_files = "SSDateTimePicker/**/*.{swift}" 16 | spec.swift_versions = "5.9" 17 | 18 | end 19 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6507209C2B0F4D4500AC1FB6 /* SSDatePickerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6507209B2B0F4D4500AC1FB6 /* SSDatePickerConfiguration.swift */; }; 11 | 6507209E2B0F538400AC1FB6 /* SSPickerConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6507209D2B0F538400AC1FB6 /* SSPickerConstants.swift */; }; 12 | 650720A22B0F644000AC1FB6 /* SSWeekDatesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720A12B0F644000AC1FB6 /* SSWeekDatesView.swift */; }; 13 | 650720A62B0F6BE600AC1FB6 /* SSDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720A52B0F6BE600AC1FB6 /* SSDateView.swift */; }; 14 | 650720AA2B0F819F00AC1FB6 /* SSImageConstant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720A92B0F819F00AC1FB6 /* SSImageConstant.swift */; }; 15 | 650720B12B107F6C00AC1FB6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 650720B02B107F6C00AC1FB6 /* Assets.xcassets */; }; 16 | 650720B32B10880900AC1FB6 /* SSMonthSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720B22B10880900AC1FB6 /* SSMonthSelectionView.swift */; }; 17 | 650720B52B10881900AC1FB6 /* SSYearSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720B42B10881900AC1FB6 /* SSYearSelectionView.swift */; }; 18 | 650720B82B10CB0300AC1FB6 /* SSCalendar+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720B72B10CB0300AC1FB6 /* SSCalendar+Extension.swift */; }; 19 | 650720BA2B10CB2800AC1FB6 /* SSDateComponents+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720B92B10CB2800AC1FB6 /* SSDateComponents+Extension.swift */; }; 20 | 650720BC2B10CB5000AC1FB6 /* SSDate+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720BB2B10CB5000AC1FB6 /* SSDate+Extension.swift */; }; 21 | 650720BE2B10CB7300AC1FB6 /* SSDateFormatter+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720BD2B10CB7300AC1FB6 /* SSDateFormatter+Extension.swift */; }; 22 | 650720C22B10CBC500AC1FB6 /* SSColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720C12B10CBC500AC1FB6 /* SSColor+Extension.swift */; }; 23 | 650720C62B13BE4D00AC1FB6 /* SSDatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720C52B13BE4D00AC1FB6 /* SSDatePicker.swift */; }; 24 | 650720C82B146E9500AC1FB6 /* SSDatePickerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720C72B146E9500AC1FB6 /* SSDatePickerManager.swift */; }; 25 | 650720CB2B181E5400AC1FB6 /* SSView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720CA2B181E5400AC1FB6 /* SSView+Extension.swift */; }; 26 | 650720CD2B18257600AC1FB6 /* SSCornerRadiusStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720CC2B18257600AC1FB6 /* SSCornerRadiusStyle.swift */; }; 27 | 650720EF2B19FD7A00AC1FB6 /* SSThemeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720EE2B19FD7A00AC1FB6 /* SSThemeButton.swift */; }; 28 | 650720F52B1DAFFB00AC1FB6 /* SSTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720F42B1DAFFB00AC1FB6 /* SSTimePicker.swift */; }; 29 | 650720F72B1DDF3600AC1FB6 /* SSTimePickerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720F62B1DDF3600AC1FB6 /* SSTimePickerManager.swift */; }; 30 | 650720F92B1DE9FD00AC1FB6 /* SSTimeTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720F82B1DE9FD00AC1FB6 /* SSTimeTextField.swift */; }; 31 | 650720FB2B1DEF8300AC1FB6 /* SSTimePickerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650720FA2B1DEF8300AC1FB6 /* SSTimePickerConfiguration.swift */; }; 32 | 651668582B20842000AD02A1 /* SSInt+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 651668572B20842000AD02A1 /* SSInt+Extension.swift */; }; 33 | 654A7F952B1F04DF00EB9B33 /* SSClockPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 654A7F942B1F04DF00EB9B33 /* SSClockPicker.swift */; }; 34 | 6579912D2B3016D100BE8B25 /* SSDateRangePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6579912C2B3016D100BE8B25 /* SSDateRangePicker.swift */; }; 35 | 6579912F2B3016F900BE8B25 /* SSMultiDatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6579912E2B3016F900BE8B25 /* SSMultiDatePicker.swift */; }; 36 | 657991332B30834400BE8B25 /* SSUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 657991322B30834400BE8B25 /* SSUtils.swift */; }; 37 | 65AB6AF22B28205E009EA7EC /* SSLocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AB6AF12B28205E009EA7EC /* SSLocalizedString.swift */; }; 38 | 65AB6AF42B282280009EA7EC /* SSString+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AB6AF32B282280009EA7EC /* SSString+Extension.swift */; }; 39 | 65AB6B032B282A18009EA7EC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 65AB6B052B282A18009EA7EC /* Localizable.strings */; }; 40 | 65E058102B0E2B260049A7BA /* DateTimePicker.docc in Sources */ = {isa = PBXBuildFile; fileRef = 65E0580F2B0E2B260049A7BA /* DateTimePicker.docc */; }; 41 | 65E058162B0E2B260049A7BA /* SSDateTimePicker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65E0580B2B0E2B260049A7BA /* SSDateTimePicker.framework */; }; 42 | 65E0581C2B0E2B260049A7BA /* DateTimePicker.h in Headers */ = {isa = PBXBuildFile; fileRef = 65E0580E2B0E2B260049A7BA /* DateTimePicker.h */; settings = {ATTRIBUTES = (Public, ); }; }; 43 | /* End PBXBuildFile section */ 44 | 45 | /* Begin PBXContainerItemProxy section */ 46 | 65E058172B0E2B260049A7BA /* PBXContainerItemProxy */ = { 47 | isa = PBXContainerItemProxy; 48 | containerPortal = 65E058022B0E2B260049A7BA /* Project object */; 49 | proxyType = 1; 50 | remoteGlobalIDString = 65E0580A2B0E2B260049A7BA; 51 | remoteInfo = DateTimePicker; 52 | }; 53 | /* End PBXContainerItemProxy section */ 54 | 55 | /* Begin PBXFileReference section */ 56 | 6507209B2B0F4D4500AC1FB6 /* SSDatePickerConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSDatePickerConfiguration.swift; sourceTree = ""; }; 57 | 6507209D2B0F538400AC1FB6 /* SSPickerConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSPickerConstants.swift; sourceTree = ""; }; 58 | 650720A12B0F644000AC1FB6 /* SSWeekDatesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSWeekDatesView.swift; sourceTree = ""; }; 59 | 650720A52B0F6BE600AC1FB6 /* SSDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSDateView.swift; sourceTree = ""; }; 60 | 650720A92B0F819F00AC1FB6 /* SSImageConstant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSImageConstant.swift; sourceTree = ""; }; 61 | 650720B02B107F6C00AC1FB6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 62 | 650720B22B10880900AC1FB6 /* SSMonthSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSMonthSelectionView.swift; sourceTree = ""; }; 63 | 650720B42B10881900AC1FB6 /* SSYearSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSYearSelectionView.swift; sourceTree = ""; }; 64 | 650720B72B10CB0300AC1FB6 /* SSCalendar+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SSCalendar+Extension.swift"; sourceTree = ""; }; 65 | 650720B92B10CB2800AC1FB6 /* SSDateComponents+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SSDateComponents+Extension.swift"; sourceTree = ""; }; 66 | 650720BB2B10CB5000AC1FB6 /* SSDate+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SSDate+Extension.swift"; sourceTree = ""; }; 67 | 650720BD2B10CB7300AC1FB6 /* SSDateFormatter+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SSDateFormatter+Extension.swift"; sourceTree = ""; }; 68 | 650720C12B10CBC500AC1FB6 /* SSColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SSColor+Extension.swift"; sourceTree = ""; }; 69 | 650720C52B13BE4D00AC1FB6 /* SSDatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSDatePicker.swift; sourceTree = ""; }; 70 | 650720C72B146E9500AC1FB6 /* SSDatePickerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSDatePickerManager.swift; sourceTree = ""; }; 71 | 650720CA2B181E5400AC1FB6 /* SSView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SSView+Extension.swift"; sourceTree = ""; }; 72 | 650720CC2B18257600AC1FB6 /* SSCornerRadiusStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSCornerRadiusStyle.swift; sourceTree = ""; }; 73 | 650720EE2B19FD7A00AC1FB6 /* SSThemeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSThemeButton.swift; sourceTree = ""; }; 74 | 650720F42B1DAFFB00AC1FB6 /* SSTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSTimePicker.swift; sourceTree = ""; }; 75 | 650720F62B1DDF3600AC1FB6 /* SSTimePickerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSTimePickerManager.swift; sourceTree = ""; }; 76 | 650720F82B1DE9FD00AC1FB6 /* SSTimeTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSTimeTextField.swift; sourceTree = ""; }; 77 | 650720FA2B1DEF8300AC1FB6 /* SSTimePickerConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSTimePickerConfiguration.swift; sourceTree = ""; }; 78 | 651668572B20842000AD02A1 /* SSInt+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SSInt+Extension.swift"; sourceTree = ""; }; 79 | 654A7F942B1F04DF00EB9B33 /* SSClockPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSClockPicker.swift; sourceTree = ""; }; 80 | 6579912C2B3016D100BE8B25 /* SSDateRangePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSDateRangePicker.swift; sourceTree = ""; }; 81 | 6579912E2B3016F900BE8B25 /* SSMultiDatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSMultiDatePicker.swift; sourceTree = ""; }; 82 | 657991322B30834400BE8B25 /* SSUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSUtils.swift; sourceTree = ""; }; 83 | 65AB6AF12B28205E009EA7EC /* SSLocalizedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSLocalizedString.swift; sourceTree = ""; }; 84 | 65AB6AF32B282280009EA7EC /* SSString+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SSString+Extension.swift"; sourceTree = ""; }; 85 | 65AB6B042B282A18009EA7EC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 86 | 65E0580B2B0E2B260049A7BA /* SSDateTimePicker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SSDateTimePicker.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 87 | 65E0580E2B0E2B260049A7BA /* DateTimePicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DateTimePicker.h; sourceTree = ""; }; 88 | 65E0580F2B0E2B260049A7BA /* DateTimePicker.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = DateTimePicker.docc; sourceTree = ""; }; 89 | 65E058152B0E2B260049A7BA /* SSDateTimePickerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SSDateTimePickerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 90 | /* End PBXFileReference section */ 91 | 92 | /* Begin PBXFrameworksBuildPhase section */ 93 | 65E058082B0E2B260049A7BA /* Frameworks */ = { 94 | isa = PBXFrameworksBuildPhase; 95 | buildActionMask = 2147483647; 96 | files = ( 97 | ); 98 | runOnlyForDeploymentPostprocessing = 0; 99 | }; 100 | 65E058122B0E2B260049A7BA /* Frameworks */ = { 101 | isa = PBXFrameworksBuildPhase; 102 | buildActionMask = 2147483647; 103 | files = ( 104 | 65E058162B0E2B260049A7BA /* SSDateTimePicker.framework in Frameworks */, 105 | ); 106 | runOnlyForDeploymentPostprocessing = 0; 107 | }; 108 | /* End PBXFrameworksBuildPhase section */ 109 | 110 | /* Begin PBXGroup section */ 111 | 650720A32B0F645000AC1FB6 /* Views */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 650720EE2B19FD7A00AC1FB6 /* SSThemeButton.swift */, 115 | 6516685A2B21F5F500AD02A1 /* Time Picker */, 116 | 651668592B21F5ED00AD02A1 /* Date picker */, 117 | ); 118 | path = Views; 119 | sourceTree = ""; 120 | }; 121 | 650720A42B0F645600AC1FB6 /* Common */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 654A7F972B1F2CF400EB9B33 /* Time picker */, 125 | 654A7F962B1F2CEC00EB9B33 /* Date picker */, 126 | 6507209D2B0F538400AC1FB6 /* SSPickerConstants.swift */, 127 | 650720A92B0F819F00AC1FB6 /* SSImageConstant.swift */, 128 | 650720CC2B18257600AC1FB6 /* SSCornerRadiusStyle.swift */, 129 | 65AB6AF12B28205E009EA7EC /* SSLocalizedString.swift */, 130 | 657991322B30834400BE8B25 /* SSUtils.swift */, 131 | ); 132 | path = Common; 133 | sourceTree = ""; 134 | }; 135 | 650720B62B10CABF00AC1FB6 /* Extensions */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 650720B72B10CB0300AC1FB6 /* SSCalendar+Extension.swift */, 139 | 650720B92B10CB2800AC1FB6 /* SSDateComponents+Extension.swift */, 140 | 650720BB2B10CB5000AC1FB6 /* SSDate+Extension.swift */, 141 | 650720BD2B10CB7300AC1FB6 /* SSDateFormatter+Extension.swift */, 142 | 650720C12B10CBC500AC1FB6 /* SSColor+Extension.swift */, 143 | 650720CA2B181E5400AC1FB6 /* SSView+Extension.swift */, 144 | 651668572B20842000AD02A1 /* SSInt+Extension.swift */, 145 | 65AB6AF32B282280009EA7EC /* SSString+Extension.swift */, 146 | ); 147 | path = Extensions; 148 | sourceTree = ""; 149 | }; 150 | 651668592B21F5ED00AD02A1 /* Date picker */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 650720C52B13BE4D00AC1FB6 /* SSDatePicker.swift */, 154 | 650720B42B10881900AC1FB6 /* SSYearSelectionView.swift */, 155 | 650720B22B10880900AC1FB6 /* SSMonthSelectionView.swift */, 156 | 650720A52B0F6BE600AC1FB6 /* SSDateView.swift */, 157 | 650720A12B0F644000AC1FB6 /* SSWeekDatesView.swift */, 158 | 6579912C2B3016D100BE8B25 /* SSDateRangePicker.swift */, 159 | 6579912E2B3016F900BE8B25 /* SSMultiDatePicker.swift */, 160 | ); 161 | path = "Date picker"; 162 | sourceTree = ""; 163 | }; 164 | 6516685A2B21F5F500AD02A1 /* Time Picker */ = { 165 | isa = PBXGroup; 166 | children = ( 167 | 654A7F942B1F04DF00EB9B33 /* SSClockPicker.swift */, 168 | 650720F82B1DE9FD00AC1FB6 /* SSTimeTextField.swift */, 169 | 650720F42B1DAFFB00AC1FB6 /* SSTimePicker.swift */, 170 | ); 171 | path = "Time Picker"; 172 | sourceTree = ""; 173 | }; 174 | 654A7F962B1F2CEC00EB9B33 /* Date picker */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | 650720C72B146E9500AC1FB6 /* SSDatePickerManager.swift */, 178 | 6507209B2B0F4D4500AC1FB6 /* SSDatePickerConfiguration.swift */, 179 | ); 180 | path = "Date picker"; 181 | sourceTree = ""; 182 | }; 183 | 654A7F972B1F2CF400EB9B33 /* Time picker */ = { 184 | isa = PBXGroup; 185 | children = ( 186 | 650720FA2B1DEF8300AC1FB6 /* SSTimePickerConfiguration.swift */, 187 | 650720F62B1DDF3600AC1FB6 /* SSTimePickerManager.swift */, 188 | ); 189 | path = "Time picker"; 190 | sourceTree = ""; 191 | }; 192 | 65E058012B0E2B260049A7BA = { 193 | isa = PBXGroup; 194 | children = ( 195 | 65E0580D2B0E2B260049A7BA /* SSDateTimePicker */, 196 | 65E0580C2B0E2B260049A7BA /* Products */, 197 | ); 198 | sourceTree = ""; 199 | }; 200 | 65E0580C2B0E2B260049A7BA /* Products */ = { 201 | isa = PBXGroup; 202 | children = ( 203 | 65E0580B2B0E2B260049A7BA /* SSDateTimePicker.framework */, 204 | 65E058152B0E2B260049A7BA /* SSDateTimePickerTests.xctest */, 205 | ); 206 | name = Products; 207 | sourceTree = ""; 208 | }; 209 | 65E0580D2B0E2B260049A7BA /* SSDateTimePicker */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | 65AB6B052B282A18009EA7EC /* Localizable.strings */, 213 | 650720B02B107F6C00AC1FB6 /* Assets.xcassets */, 214 | 650720B62B10CABF00AC1FB6 /* Extensions */, 215 | 650720A42B0F645600AC1FB6 /* Common */, 216 | 650720A32B0F645000AC1FB6 /* Views */, 217 | 65E0580E2B0E2B260049A7BA /* DateTimePicker.h */, 218 | 65E0580F2B0E2B260049A7BA /* DateTimePicker.docc */, 219 | ); 220 | path = SSDateTimePicker; 221 | sourceTree = ""; 222 | }; 223 | /* End PBXGroup section */ 224 | 225 | /* Begin PBXHeadersBuildPhase section */ 226 | 65E058062B0E2B260049A7BA /* Headers */ = { 227 | isa = PBXHeadersBuildPhase; 228 | buildActionMask = 2147483647; 229 | files = ( 230 | 65E0581C2B0E2B260049A7BA /* DateTimePicker.h in Headers */, 231 | ); 232 | runOnlyForDeploymentPostprocessing = 0; 233 | }; 234 | /* End PBXHeadersBuildPhase section */ 235 | 236 | /* Begin PBXNativeTarget section */ 237 | 65E0580A2B0E2B260049A7BA /* SSDateTimePicker */ = { 238 | isa = PBXNativeTarget; 239 | buildConfigurationList = 65E0581F2B0E2B260049A7BA /* Build configuration list for PBXNativeTarget "SSDateTimePicker" */; 240 | buildPhases = ( 241 | 65E058062B0E2B260049A7BA /* Headers */, 242 | 65E058072B0E2B260049A7BA /* Sources */, 243 | 65E058082B0E2B260049A7BA /* Frameworks */, 244 | 65E058092B0E2B260049A7BA /* Resources */, 245 | ); 246 | buildRules = ( 247 | ); 248 | dependencies = ( 249 | ); 250 | name = SSDateTimePicker; 251 | productName = DateTimePicker; 252 | productReference = 65E0580B2B0E2B260049A7BA /* SSDateTimePicker.framework */; 253 | productType = "com.apple.product-type.framework"; 254 | }; 255 | 65E058142B0E2B260049A7BA /* SSDateTimePickerTests */ = { 256 | isa = PBXNativeTarget; 257 | buildConfigurationList = 65E058222B0E2B260049A7BA /* Build configuration list for PBXNativeTarget "SSDateTimePickerTests" */; 258 | buildPhases = ( 259 | 65E058112B0E2B260049A7BA /* Sources */, 260 | 65E058122B0E2B260049A7BA /* Frameworks */, 261 | 65E058132B0E2B260049A7BA /* Resources */, 262 | ); 263 | buildRules = ( 264 | ); 265 | dependencies = ( 266 | 65E058182B0E2B260049A7BA /* PBXTargetDependency */, 267 | ); 268 | name = SSDateTimePickerTests; 269 | productName = DateTimePickerTests; 270 | productReference = 65E058152B0E2B260049A7BA /* SSDateTimePickerTests.xctest */; 271 | productType = "com.apple.product-type.bundle.unit-test"; 272 | }; 273 | /* End PBXNativeTarget section */ 274 | 275 | /* Begin PBXProject section */ 276 | 65E058022B0E2B260049A7BA /* Project object */ = { 277 | isa = PBXProject; 278 | attributes = { 279 | BuildIndependentTargetsInParallel = 1; 280 | LastSwiftUpdateCheck = 1430; 281 | LastUpgradeCheck = 1430; 282 | TargetAttributes = { 283 | 65E0580A2B0E2B260049A7BA = { 284 | CreatedOnToolsVersion = 14.3.1; 285 | }; 286 | 65E058142B0E2B260049A7BA = { 287 | CreatedOnToolsVersion = 14.3.1; 288 | }; 289 | }; 290 | }; 291 | buildConfigurationList = 65E058052B0E2B260049A7BA /* Build configuration list for PBXProject "SSDateTimePicker" */; 292 | compatibilityVersion = "Xcode 14.0"; 293 | developmentRegion = en; 294 | hasScannedForEncodings = 0; 295 | knownRegions = ( 296 | en, 297 | Base, 298 | ); 299 | mainGroup = 65E058012B0E2B260049A7BA; 300 | productRefGroup = 65E0580C2B0E2B260049A7BA /* Products */; 301 | projectDirPath = ""; 302 | projectRoot = ""; 303 | targets = ( 304 | 65E0580A2B0E2B260049A7BA /* SSDateTimePicker */, 305 | 65E058142B0E2B260049A7BA /* SSDateTimePickerTests */, 306 | ); 307 | }; 308 | /* End PBXProject section */ 309 | 310 | /* Begin PBXResourcesBuildPhase section */ 311 | 65E058092B0E2B260049A7BA /* Resources */ = { 312 | isa = PBXResourcesBuildPhase; 313 | buildActionMask = 2147483647; 314 | files = ( 315 | 650720B12B107F6C00AC1FB6 /* Assets.xcassets in Resources */, 316 | 65AB6B032B282A18009EA7EC /* Localizable.strings in Resources */, 317 | ); 318 | runOnlyForDeploymentPostprocessing = 0; 319 | }; 320 | 65E058132B0E2B260049A7BA /* Resources */ = { 321 | isa = PBXResourcesBuildPhase; 322 | buildActionMask = 2147483647; 323 | files = ( 324 | ); 325 | runOnlyForDeploymentPostprocessing = 0; 326 | }; 327 | /* End PBXResourcesBuildPhase section */ 328 | 329 | /* Begin PBXSourcesBuildPhase section */ 330 | 65E058072B0E2B260049A7BA /* Sources */ = { 331 | isa = PBXSourcesBuildPhase; 332 | buildActionMask = 2147483647; 333 | files = ( 334 | 650720BA2B10CB2800AC1FB6 /* SSDateComponents+Extension.swift in Sources */, 335 | 650720A22B0F644000AC1FB6 /* SSWeekDatesView.swift in Sources */, 336 | 650720B82B10CB0300AC1FB6 /* SSCalendar+Extension.swift in Sources */, 337 | 650720CB2B181E5400AC1FB6 /* SSView+Extension.swift in Sources */, 338 | 650720A62B0F6BE600AC1FB6 /* SSDateView.swift in Sources */, 339 | 650720C62B13BE4D00AC1FB6 /* SSDatePicker.swift in Sources */, 340 | 650720B32B10880900AC1FB6 /* SSMonthSelectionView.swift in Sources */, 341 | 650720CD2B18257600AC1FB6 /* SSCornerRadiusStyle.swift in Sources */, 342 | 650720AA2B0F819F00AC1FB6 /* SSImageConstant.swift in Sources */, 343 | 651668582B20842000AD02A1 /* SSInt+Extension.swift in Sources */, 344 | 650720BE2B10CB7300AC1FB6 /* SSDateFormatter+Extension.swift in Sources */, 345 | 650720B52B10881900AC1FB6 /* SSYearSelectionView.swift in Sources */, 346 | 6579912D2B3016D100BE8B25 /* SSDateRangePicker.swift in Sources */, 347 | 654A7F952B1F04DF00EB9B33 /* SSClockPicker.swift in Sources */, 348 | 650720F72B1DDF3600AC1FB6 /* SSTimePickerManager.swift in Sources */, 349 | 650720C82B146E9500AC1FB6 /* SSDatePickerManager.swift in Sources */, 350 | 650720F92B1DE9FD00AC1FB6 /* SSTimeTextField.swift in Sources */, 351 | 6507209E2B0F538400AC1FB6 /* SSPickerConstants.swift in Sources */, 352 | 65AB6AF22B28205E009EA7EC /* SSLocalizedString.swift in Sources */, 353 | 650720FB2B1DEF8300AC1FB6 /* SSTimePickerConfiguration.swift in Sources */, 354 | 65E058102B0E2B260049A7BA /* DateTimePicker.docc in Sources */, 355 | 650720BC2B10CB5000AC1FB6 /* SSDate+Extension.swift in Sources */, 356 | 650720F52B1DAFFB00AC1FB6 /* SSTimePicker.swift in Sources */, 357 | 650720EF2B19FD7A00AC1FB6 /* SSThemeButton.swift in Sources */, 358 | 650720C22B10CBC500AC1FB6 /* SSColor+Extension.swift in Sources */, 359 | 657991332B30834400BE8B25 /* SSUtils.swift in Sources */, 360 | 65AB6AF42B282280009EA7EC /* SSString+Extension.swift in Sources */, 361 | 6507209C2B0F4D4500AC1FB6 /* SSDatePickerConfiguration.swift in Sources */, 362 | 6579912F2B3016F900BE8B25 /* SSMultiDatePicker.swift in Sources */, 363 | ); 364 | runOnlyForDeploymentPostprocessing = 0; 365 | }; 366 | 65E058112B0E2B260049A7BA /* Sources */ = { 367 | isa = PBXSourcesBuildPhase; 368 | buildActionMask = 2147483647; 369 | files = ( 370 | ); 371 | runOnlyForDeploymentPostprocessing = 0; 372 | }; 373 | /* End PBXSourcesBuildPhase section */ 374 | 375 | /* Begin PBXTargetDependency section */ 376 | 65E058182B0E2B260049A7BA /* PBXTargetDependency */ = { 377 | isa = PBXTargetDependency; 378 | target = 65E0580A2B0E2B260049A7BA /* SSDateTimePicker */; 379 | targetProxy = 65E058172B0E2B260049A7BA /* PBXContainerItemProxy */; 380 | }; 381 | /* End PBXTargetDependency section */ 382 | 383 | /* Begin PBXVariantGroup section */ 384 | 65AB6B052B282A18009EA7EC /* Localizable.strings */ = { 385 | isa = PBXVariantGroup; 386 | children = ( 387 | 65AB6B042B282A18009EA7EC /* en */, 388 | ); 389 | name = Localizable.strings; 390 | sourceTree = ""; 391 | }; 392 | /* End PBXVariantGroup section */ 393 | 394 | /* Begin XCBuildConfiguration section */ 395 | 65E0581D2B0E2B260049A7BA /* Debug */ = { 396 | isa = XCBuildConfiguration; 397 | buildSettings = { 398 | ALWAYS_SEARCH_USER_PATHS = NO; 399 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 400 | CLANG_ANALYZER_NONNULL = YES; 401 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 402 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 403 | CLANG_ENABLE_MODULES = YES; 404 | CLANG_ENABLE_OBJC_ARC = YES; 405 | CLANG_ENABLE_OBJC_WEAK = YES; 406 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 407 | CLANG_WARN_BOOL_CONVERSION = YES; 408 | CLANG_WARN_COMMA = YES; 409 | CLANG_WARN_CONSTANT_CONVERSION = YES; 410 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 411 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 412 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 413 | CLANG_WARN_EMPTY_BODY = YES; 414 | CLANG_WARN_ENUM_CONVERSION = YES; 415 | CLANG_WARN_INFINITE_RECURSION = YES; 416 | CLANG_WARN_INT_CONVERSION = YES; 417 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 418 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 419 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 420 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 421 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 422 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 423 | CLANG_WARN_STRICT_PROTOTYPES = YES; 424 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 425 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 426 | CLANG_WARN_UNREACHABLE_CODE = YES; 427 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 428 | COPY_PHASE_STRIP = NO; 429 | CURRENT_PROJECT_VERSION = 1; 430 | DEBUG_INFORMATION_FORMAT = dwarf; 431 | ENABLE_STRICT_OBJC_MSGSEND = YES; 432 | ENABLE_TESTABILITY = YES; 433 | GCC_C_LANGUAGE_STANDARD = gnu11; 434 | GCC_DYNAMIC_NO_PIC = NO; 435 | GCC_NO_COMMON_BLOCKS = YES; 436 | GCC_OPTIMIZATION_LEVEL = 0; 437 | GCC_PREPROCESSOR_DEFINITIONS = ( 438 | "DEBUG=1", 439 | "$(inherited)", 440 | ); 441 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 442 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 443 | GCC_WARN_UNDECLARED_SELECTOR = YES; 444 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 445 | GCC_WARN_UNUSED_FUNCTION = YES; 446 | GCC_WARN_UNUSED_VARIABLE = YES; 447 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 448 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 449 | MTL_FAST_MATH = YES; 450 | ONLY_ACTIVE_ARCH = YES; 451 | SDKROOT = iphoneos; 452 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 453 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 454 | VERSIONING_SYSTEM = "apple-generic"; 455 | VERSION_INFO_PREFIX = ""; 456 | }; 457 | name = Debug; 458 | }; 459 | 65E0581E2B0E2B260049A7BA /* Release */ = { 460 | isa = XCBuildConfiguration; 461 | buildSettings = { 462 | ALWAYS_SEARCH_USER_PATHS = NO; 463 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 464 | CLANG_ANALYZER_NONNULL = YES; 465 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 466 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 467 | CLANG_ENABLE_MODULES = YES; 468 | CLANG_ENABLE_OBJC_ARC = YES; 469 | CLANG_ENABLE_OBJC_WEAK = YES; 470 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 471 | CLANG_WARN_BOOL_CONVERSION = YES; 472 | CLANG_WARN_COMMA = YES; 473 | CLANG_WARN_CONSTANT_CONVERSION = YES; 474 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 475 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 476 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 477 | CLANG_WARN_EMPTY_BODY = YES; 478 | CLANG_WARN_ENUM_CONVERSION = YES; 479 | CLANG_WARN_INFINITE_RECURSION = YES; 480 | CLANG_WARN_INT_CONVERSION = YES; 481 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 482 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 483 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 484 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 485 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 486 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 487 | CLANG_WARN_STRICT_PROTOTYPES = YES; 488 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 489 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 490 | CLANG_WARN_UNREACHABLE_CODE = YES; 491 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 492 | COPY_PHASE_STRIP = NO; 493 | CURRENT_PROJECT_VERSION = 1; 494 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 495 | ENABLE_NS_ASSERTIONS = NO; 496 | ENABLE_STRICT_OBJC_MSGSEND = YES; 497 | GCC_C_LANGUAGE_STANDARD = gnu11; 498 | GCC_NO_COMMON_BLOCKS = YES; 499 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 500 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 501 | GCC_WARN_UNDECLARED_SELECTOR = YES; 502 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 503 | GCC_WARN_UNUSED_FUNCTION = YES; 504 | GCC_WARN_UNUSED_VARIABLE = YES; 505 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 506 | MTL_ENABLE_DEBUG_INFO = NO; 507 | MTL_FAST_MATH = YES; 508 | SDKROOT = iphoneos; 509 | SWIFT_COMPILATION_MODE = wholemodule; 510 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 511 | VALIDATE_PRODUCT = YES; 512 | VERSIONING_SYSTEM = "apple-generic"; 513 | VERSION_INFO_PREFIX = ""; 514 | }; 515 | name = Release; 516 | }; 517 | 65E058202B0E2B260049A7BA /* Debug */ = { 518 | isa = XCBuildConfiguration; 519 | buildSettings = { 520 | CODE_SIGN_STYLE = Automatic; 521 | CURRENT_PROJECT_VERSION = 1; 522 | DEFINES_MODULE = YES; 523 | DYLIB_COMPATIBILITY_VERSION = 1; 524 | DYLIB_CURRENT_VERSION = 1; 525 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 526 | ENABLE_MODULE_VERIFIER = YES; 527 | GENERATE_INFOPLIST_FILE = YES; 528 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 529 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 530 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 531 | LD_RUNPATH_SEARCH_PATHS = ( 532 | "$(inherited)", 533 | "@executable_path/Frameworks", 534 | "@loader_path/Frameworks", 535 | ); 536 | MARKETING_VERSION = 1.0; 537 | MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; 538 | MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; 539 | PRODUCT_BUNDLE_IDENTIFIER = simform.DateTimePicker; 540 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 541 | SKIP_INSTALL = YES; 542 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 543 | SUPPORTS_MACCATALYST = NO; 544 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; 545 | SWIFT_EMIT_LOC_STRINGS = YES; 546 | SWIFT_VERSION = 5.0; 547 | TARGETED_DEVICE_FAMILY = "1,2"; 548 | }; 549 | name = Debug; 550 | }; 551 | 65E058212B0E2B260049A7BA /* Release */ = { 552 | isa = XCBuildConfiguration; 553 | buildSettings = { 554 | CODE_SIGN_STYLE = Automatic; 555 | CURRENT_PROJECT_VERSION = 1; 556 | DEFINES_MODULE = YES; 557 | DYLIB_COMPATIBILITY_VERSION = 1; 558 | DYLIB_CURRENT_VERSION = 1; 559 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 560 | ENABLE_MODULE_VERIFIER = YES; 561 | GENERATE_INFOPLIST_FILE = YES; 562 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 563 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 564 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 565 | LD_RUNPATH_SEARCH_PATHS = ( 566 | "$(inherited)", 567 | "@executable_path/Frameworks", 568 | "@loader_path/Frameworks", 569 | ); 570 | MARKETING_VERSION = 1.0; 571 | MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; 572 | MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; 573 | PRODUCT_BUNDLE_IDENTIFIER = simform.DateTimePicker; 574 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 575 | SKIP_INSTALL = YES; 576 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 577 | SUPPORTS_MACCATALYST = NO; 578 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; 579 | SWIFT_EMIT_LOC_STRINGS = YES; 580 | SWIFT_VERSION = 5.0; 581 | TARGETED_DEVICE_FAMILY = "1,2"; 582 | }; 583 | name = Release; 584 | }; 585 | 65E058232B0E2B260049A7BA /* Debug */ = { 586 | isa = XCBuildConfiguration; 587 | buildSettings = { 588 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 589 | CODE_SIGN_STYLE = Automatic; 590 | CURRENT_PROJECT_VERSION = 1; 591 | GENERATE_INFOPLIST_FILE = YES; 592 | MARKETING_VERSION = 1.0; 593 | PRODUCT_BUNDLE_IDENTIFIER = simform.DateTimePickerTests; 594 | PRODUCT_NAME = "$(TARGET_NAME)"; 595 | SWIFT_EMIT_LOC_STRINGS = NO; 596 | SWIFT_VERSION = 5.0; 597 | TARGETED_DEVICE_FAMILY = "1,2"; 598 | }; 599 | name = Debug; 600 | }; 601 | 65E058242B0E2B260049A7BA /* Release */ = { 602 | isa = XCBuildConfiguration; 603 | buildSettings = { 604 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 605 | CODE_SIGN_STYLE = Automatic; 606 | CURRENT_PROJECT_VERSION = 1; 607 | GENERATE_INFOPLIST_FILE = YES; 608 | MARKETING_VERSION = 1.0; 609 | PRODUCT_BUNDLE_IDENTIFIER = simform.DateTimePickerTests; 610 | PRODUCT_NAME = "$(TARGET_NAME)"; 611 | SWIFT_EMIT_LOC_STRINGS = NO; 612 | SWIFT_VERSION = 5.0; 613 | TARGETED_DEVICE_FAMILY = "1,2"; 614 | }; 615 | name = Release; 616 | }; 617 | /* End XCBuildConfiguration section */ 618 | 619 | /* Begin XCConfigurationList section */ 620 | 65E058052B0E2B260049A7BA /* Build configuration list for PBXProject "SSDateTimePicker" */ = { 621 | isa = XCConfigurationList; 622 | buildConfigurations = ( 623 | 65E0581D2B0E2B260049A7BA /* Debug */, 624 | 65E0581E2B0E2B260049A7BA /* Release */, 625 | ); 626 | defaultConfigurationIsVisible = 0; 627 | defaultConfigurationName = Release; 628 | }; 629 | 65E0581F2B0E2B260049A7BA /* Build configuration list for PBXNativeTarget "SSDateTimePicker" */ = { 630 | isa = XCConfigurationList; 631 | buildConfigurations = ( 632 | 65E058202B0E2B260049A7BA /* Debug */, 633 | 65E058212B0E2B260049A7BA /* Release */, 634 | ); 635 | defaultConfigurationIsVisible = 0; 636 | defaultConfigurationName = Release; 637 | }; 638 | 65E058222B0E2B260049A7BA /* Build configuration list for PBXNativeTarget "SSDateTimePickerTests" */ = { 639 | isa = XCConfigurationList; 640 | buildConfigurations = ( 641 | 65E058232B0E2B260049A7BA /* Debug */, 642 | 65E058242B0E2B260049A7BA /* Release */, 643 | ); 644 | defaultConfigurationIsVisible = 0; 645 | defaultConfigurationName = Release; 646 | }; 647 | /* End XCConfigurationList section */ 648 | }; 649 | rootObject = 65E058022B0E2B260049A7BA /* Project object */; 650 | } 651 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Assets.xcassets/darkGreen.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "43", 9 | "green" : "105", 10 | "red" : "68" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Assets.xcassets/darkPink.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "105", 9 | "green" : "94", 10 | "red" : "221" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Assets.xcassets/lightBlue.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "252", 9 | "green" : "244", 10 | "red" : "242" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Assets.xcassets/lightGreen.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "221", 9 | "green" : "234", 10 | "red" : "230" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Assets.xcassets/lightPink.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "250", 9 | "green" : "250", 10 | "red" : "253" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Assets.xcassets/peach.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "213", 9 | "green" : "209", 10 | "red" : "244" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Common/Date picker/SSDatePickerConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SSCalendarConfiguration.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 23/11/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | /// A configuration structure for customizing the appearance and behavior of the SSDatePicker. 12 | struct SSDatePickerConfiguration { 13 | 14 | // MARK: - Colors and fonts 15 | 16 | /// Color and font for the header title ("Select date"). 17 | var headerTitleStyle: SSStyle = .init(font: .headerTitle, color: .gray) 18 | 19 | /// Color and font for the header date text. 20 | var headerDateStyle: SSStyle = .init(font: .headerDate, color: .black) 21 | 22 | /// Text color and font for weekdays in the picker. 23 | var weekdayStyle: SSStyle = .init(font: .weekday, color: .gray) 24 | 25 | /// Text color and font for date in the picker. 26 | var dateStyle: SSStyle = .init(font: .date, color: .black) 27 | 28 | /// Text color and font for month text. 29 | var monthStyle: SSStyle = .init(font: .month, color: .black) 30 | 31 | /// Text color and font for selected month text. 32 | var selectedMonthStyle: SSStyle = .init(font: .selectedMonth, color: .darkPink) 33 | 34 | /// Text color and font for year text. 35 | var yearStyle: SSStyle = .init(font: .year, color: .black) 36 | 37 | /// Text color and font for selected year text. 38 | var selectedYearStyle: SSStyle = .init(font: .selectedYear, color: .darkPink) 39 | 40 | /// Font and foreground color for buttons. 41 | var buttonStyle: SSStyle = .init(font: .buttonText, color: .darkPink) 42 | 43 | /// Font and color for the current month and year label at the bottom of the picker. 44 | var currentMonthYearLabelStyle: SSStyle = .init(font: .currentMonthYear, color: .black) 45 | 46 | /// Selected date foreground and background color. 47 | var selectedDateColor: SSColor = .init(foregroundColor: .white, backgroundColor: .darkPink) 48 | 49 | /// Foreground and background color for today's date text. 50 | var todayDateColor: SSColor = .init(foregroundColor: nil, backgroundColor: nil) 51 | 52 | /// Background and foreground color for today's selected date. 53 | var todayDateSelectionColor: SSColor = .init(foregroundColor: nil, backgroundColor: nil) 54 | 55 | /// Background color for the picker. 56 | var pickerBackgroundColor: Color 57 | 58 | /// Color for the separator lines. 59 | var sepratorLineColor: Color = Color(uiColor: UIColor.opaqueSeparator) 60 | 61 | /// Color for the overlay background of the popup. 62 | var popupOverlayColor: Color = Color.black.opacity(0.5) 63 | 64 | // MARK: - Date Selection Behavior Configuration Properties 65 | 66 | /// Allow selecting multiple dates. 67 | var allowMultipleSelection: Bool = false 68 | 69 | /// Allow selecting a range of dates. 70 | var allowRangeSelection: Bool = false 71 | 72 | /// The minimum selectable date. Dates before this minimum date will be disabled for selection. 73 | var minimumDate: Date? 74 | 75 | /// The maximum selectable date. Dates after this maximum date will be disabled for selection. 76 | var maximumDate: Date? 77 | 78 | /// Calendar to use for date calculations. 79 | var calendar: Calendar = Calendar.current 80 | 81 | // MARK: - Additional Properties 82 | 83 | /// Radius for the corner of the picker view. 84 | var pickerViewRadius: CGFloat = 15 85 | 86 | /// Date format for the header. 87 | var headerDateFormat: String = DateFormat.monthDateYear 88 | 89 | // MARK: - Initializer 90 | 91 | /// Creates a custom-themed date picker configuration. 92 | /// 93 | /// - Parameters: 94 | /// - pickerBackgroundColor: The background color of the picker view. Default is light pink. 95 | /// - primaryColor: The color for both the selected date's background and the buttons. Deafult is dark pink. 96 | /// 97 | /// Use this instance to set up the appearance and behavior of the SSDatePicker according to your preferences. 98 | init(pickerBackgroundColor: Color = Color.lightPink, primaryColor: Color = Color.darkPink) { 99 | self.selectedDateColor.backgroundColor = primaryColor 100 | self.buttonStyle.color = primaryColor 101 | self.pickerBackgroundColor = pickerBackgroundColor 102 | } 103 | } 104 | 105 | 106 | protocol DatePickerConfigurationDirectAccess { 107 | 108 | var configuration: SSDatePickerConfiguration { get } 109 | 110 | } 111 | 112 | extension DatePickerConfigurationDirectAccess { 113 | 114 | var calendar: Calendar { 115 | configuration.calendar 116 | } 117 | 118 | var headerTitleColor: Color { 119 | configuration.headerTitleStyle.color 120 | } 121 | 122 | var headerDateColor: Color { 123 | configuration.headerDateStyle.color 124 | } 125 | 126 | var weekdayTextColor: Color { 127 | configuration.weekdayStyle.color 128 | } 129 | 130 | var dateMonthYearTextColor: Color { 131 | configuration.dateStyle.color 132 | } 133 | 134 | var selectedDateTextColor: Color { 135 | configuration.selectedDateColor.foregroundColor ?? dateMonthYearTextColor 136 | } 137 | 138 | var selectionBackgroundColor: Color { 139 | configuration.selectedDateColor.backgroundColor ?? .darkPink 140 | } 141 | 142 | var todayColor: Color { 143 | configuration.todayDateColor.foregroundColor ?? dateMonthYearTextColor 144 | } 145 | 146 | var todaySelectionBgColor: Color { 147 | configuration.todayDateSelectionColor.backgroundColor ?? selectionBackgroundColor 148 | } 149 | 150 | var todaySelectionFontColor: Color { 151 | configuration.todayDateSelectionColor.foregroundColor ?? dateMonthYearTextColor 152 | } 153 | 154 | var buttonsForegroundColor: Color { 155 | configuration.buttonStyle.color 156 | } 157 | 158 | var pickerBackgroundColor: Color { 159 | configuration.pickerBackgroundColor 160 | } 161 | 162 | // date format 163 | var headerDateFormat: String { 164 | configuration.headerDateFormat 165 | } 166 | 167 | var pickerViewRadius: CGFloat { 168 | configuration.pickerViewRadius 169 | } 170 | 171 | var nextPrevButtonColor: Color { 172 | configuration.buttonStyle.color 173 | } 174 | 175 | var monthYearNavigationLabelColor: Color { 176 | configuration.currentMonthYearLabelStyle.color 177 | } 178 | 179 | var allowMultipleSelection: Bool { 180 | configuration.allowMultipleSelection 181 | } 182 | 183 | var allowRangeSelection: Bool { 184 | configuration.allowRangeSelection 185 | } 186 | 187 | var minimumDate: Date? { 188 | configuration.minimumDate 189 | } 190 | 191 | var maximumDate: Date? { 192 | configuration.maximumDate 193 | } 194 | 195 | var popupOverlayColor: Color { 196 | configuration.popupOverlayColor 197 | } 198 | 199 | var headerTitleFont: Font { 200 | configuration.headerTitleStyle.font 201 | } 202 | 203 | var headerDateFont: Font { 204 | configuration.headerDateStyle.font 205 | } 206 | 207 | var weekdayTextFont: Font { 208 | configuration.weekdayStyle.font 209 | } 210 | 211 | var dateTextFont: Font { 212 | configuration.dateStyle.font 213 | } 214 | 215 | var monthTextFont: Font { 216 | configuration.monthStyle.font 217 | } 218 | 219 | var selectedMonthTextFont: Font { 220 | configuration.selectedMonthStyle.font 221 | } 222 | 223 | var yearTextFont: Font { 224 | configuration.yearStyle.font 225 | } 226 | 227 | var buttonsFont: Font { 228 | configuration.buttonStyle.font 229 | } 230 | 231 | var currentMonthYearBottomLabelFont: Font { 232 | configuration.currentMonthYearLabelStyle.font 233 | } 234 | 235 | var selectedYearTextFont: Font { 236 | configuration.selectedYearStyle.font 237 | } 238 | 239 | var sepratorLineColor: Color { 240 | configuration.sepratorLineColor 241 | } 242 | 243 | } 244 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Common/Date picker/SSDatePickerManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SSCalendarManager.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 27/11/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import Combine 11 | 12 | /// A manager class for handling the state and behavior of the SSDatePicker. 13 | final class SSDatePickerManager: ObservableObject, DatePickerConfigurationDirectAccess { 14 | 15 | // MARK: - Properties 16 | 17 | /// The current month displayed in the date picker. 18 | /// Setting this property will determine the month whose calendar dates will be displayed when the picker is opened. 19 | @Published var currentMonth: Date 20 | 21 | /// The selected date in the date picker. Set this property to pre-select a specific date. 22 | @Published var selectedDate: Date? = nil 23 | 24 | /// The selected dates in the date picker when multiple selection is allowed. Set this property to pre-select a specific dates. 25 | @Published var selectedDates: [Date]? = nil 26 | 27 | /// The start date for range selection in the date picker. 28 | @Published var startDate: Date? = nil 29 | 30 | /// The end date for range selection in the date picker. 31 | @Published var endDate: Date? = nil 32 | 33 | /// List of dates that needs to be disabled 34 | var disableDates: [Date]? 35 | 36 | /// The configuration for the SSDatePicker. 37 | public var configuration: SSDatePickerConfiguration 38 | 39 | /// The year range displayed in the year selection view. 40 | @Published private(set) var yearRange = [Int]() 41 | 42 | /// The last known current month for canceling selection. 43 | private var lastCurrentMonth: Date 44 | 45 | /// The last known selected date for canceling selection. 46 | private var lastSelectedDate: Date? 47 | 48 | var dateRangeSelectionCallback: (DateRange) -> () = {_ in} 49 | var multiDateSelectionCallback: ([Date]) -> () = {_ in} 50 | var dateSelectionCallback: (Date) -> () = {_ in} 51 | 52 | // MARK: - Initializer 53 | 54 | /// Initializes the SSDatePickerManager with the provided configuration. 55 | /// 56 | /// - Parameters: 57 | /// - currentMonth: The initial current month to be displayed in the date picker. 58 | /// - selectedDate: The initially selected date to be displayed in the date picker. 59 | /// - configuration: The style configuration for the SSDatePicker. 60 | init(currentMonth: Date = Date(), selectedDate: Date? = nil, configuration: SSDatePickerConfiguration = SSDatePickerConfiguration()) { 61 | self.currentMonth = currentMonth 62 | self.selectedDate = selectedDate 63 | self.configuration = configuration 64 | self.lastCurrentMonth = currentMonth 65 | self.lastSelectedDate = selectedDate 66 | } 67 | 68 | // MARK: - Methods 69 | 70 | /// Advances to the next month, year based on the current view (date, month, or year). 71 | func actionNext(for view: SelectionView) { 72 | switch view { 73 | case .date: 74 | currentMonth = currentMonth.getNextMonth(calendar) ?? currentMonth 75 | case .month: 76 | currentMonth = currentMonth.getNextYear(calendar) ?? currentMonth 77 | updateYearSelection(date: currentMonth) 78 | case .year: 79 | guard let year = self.yearRange.last else { return } 80 | self.updateYearRange(year: year+12) 81 | } 82 | } 83 | 84 | /// Moves back to the previous month, year based on the current view (date, month, or year). 85 | func actionPrev(for view: SelectionView) { 86 | switch view { 87 | case .date: 88 | currentMonth = currentMonth.getPreviousMonth(calendar) ?? currentMonth 89 | case .month: 90 | currentMonth = currentMonth.getPreviousYear(calendar) ?? currentMonth 91 | updateYearSelection(date: currentMonth) 92 | case .year: 93 | guard let year = self.yearRange.first else { return } 94 | self.updateYearRange(year: year-1) 95 | } 96 | } 97 | 98 | /// Updates the year selection based on the chosen year. 99 | func updateYearSelection(date: Date) { 100 | updateYearSelection(year: date.year(calendar)) 101 | } 102 | 103 | /// Checks if a given month is currently selected in the date picker. 104 | func isSelected(_ month: String) -> Bool { 105 | month.lowercased() == (selectedDate ?? currentMonth).fullMonth.lowercased() 106 | } 107 | 108 | /// Checks if a given year is currently selected in the date picker. 109 | func isSelected(_ year: Int) -> Bool { 110 | year == (selectedDate ?? currentMonth).year(calendar) 111 | } 112 | 113 | /// Updates the date selection based on the chosen date. 114 | func updateDateSelection(date: Date) { 115 | if configuration.allowMultipleSelection { 116 | if selectedDates == nil { 117 | self.selectedDates = [] 118 | } 119 | updateMultipleSelection(date: date) 120 | } else if configuration.allowRangeSelection { 121 | self.updateRangeSelection(date: date) 122 | } else { 123 | self.selectedDate = date 124 | } 125 | } 126 | 127 | func updateMultipleSelection(date: Date) { 128 | // Find the index of the selected date in the array, if it exists 129 | if let existingIndex = selectedDates?.firstIndex(where: { selectedDate in 130 | calendar.isDate(date, equalTo: selectedDate, toGranularities: [.day, .month, .year]) 131 | }) { 132 | // If the date is already selected, remove it to support deselection 133 | selectedDates?.remove(at: existingIndex) 134 | } else { 135 | // If the date is not selected, add it to the selected dates array 136 | selectedDates?.append(date) 137 | } 138 | } 139 | 140 | func handleMultiDateDeselection(date: Date) { 141 | 142 | } 143 | 144 | /// Updates the range selection based on the chosen date. 145 | func updateRangeSelection(date: Date) { 146 | if let startDate = startDate , date >= configuration.calendar.startOfDay(for: startDate) { 147 | endDate = date 148 | } else { 149 | startDate = date 150 | } 151 | } 152 | 153 | /// Updates the month selection based on the chosen month. 154 | func updateMonthSelection(month: Int) { 155 | guard let selectedDate else { 156 | var component = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: self.currentMonth) 157 | component.month = month 158 | self.currentMonth = calendar.date(from: component) ?? currentMonth 159 | return 160 | } 161 | var component = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: selectedDate) 162 | component.month = month 163 | self.selectedDate = calendar.date(from: component) 164 | self.currentMonth = self.selectedDate ?? currentMonth 165 | } 166 | 167 | /// Updates the year selection based on the chosen year. 168 | func updateYearSelection(year: Int) { 169 | guard let selectedDate else { 170 | var component = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: self.currentMonth) 171 | component.year = year 172 | self.currentMonth = calendar.date(from: component) ?? currentMonth 173 | return 174 | } 175 | var component = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: selectedDate) 176 | component.year = year 177 | self.selectedDate = calendar.date(from: component) 178 | self.currentMonth = self.selectedDate ?? currentMonth 179 | } 180 | 181 | /// Updates the displayed year range in the year selection view. 182 | func updateYearRange(year: Int) { 183 | let lowerBound = year - 11 184 | let upperBound = year 185 | self.yearRange = Array(lowerBound...upperBound) 186 | } 187 | 188 | /// Reverts the selection to the last known values before the user's interaction. 189 | /// 190 | /// - Parameter view: The view (date, month, or year) for which the selection is being canceled. 191 | func selectionCanceled(for view: SelectionView) { 192 | switch view { 193 | case .date: 194 | self.selectedDate = lastSelectedDate 195 | case .month, .year: 196 | self.currentMonth = lastCurrentMonth 197 | } 198 | } 199 | 200 | /// Confirms the selection and notifies the delegate. 201 | /// 202 | /// - Parameter view: The view (date, month, or year) for which the selection is being confirmed. 203 | func selectionConfirmed(for view: SelectionView) { 204 | switch view { 205 | case .date: 206 | lastSelectedDate = self.selectedDate 207 | handleCallback() 208 | case .month, .year: 209 | lastCurrentMonth = self.currentMonth 210 | } 211 | } 212 | 213 | /// Notifies the delegate about the selected dates based on the configuration. 214 | func handleCallback() { 215 | if configuration.allowMultipleSelection { 216 | guard let selectedDates else { return } 217 | multiDateSelectionCallback(selectedDates) 218 | } else if configuration.allowRangeSelection { 219 | guard let startDate, let endDate else { return } 220 | dateRangeSelectionCallback(DateRange(startDate, endDate)) 221 | } else { 222 | guard let selectedDate else { return } 223 | dateSelectionCallback(selectedDate) 224 | } 225 | } 226 | 227 | /// Determines if a given date can be selected based on a list of disabled dates. 228 | /// - Parameters: 229 | /// - date: The date to be checked for selectability. 230 | /// - Returns: `true` if the date is selectable, `false` if it is disabled. 231 | func canSelectDate(_ date: Date) -> Bool { 232 | guard let disableDates = disableDates else { 233 | return true // all dates are selectable if disableDates is nil 234 | } 235 | 236 | return !disableDates.contains { disableDate in 237 | calendar.isDate(date, equalTo: disableDate, toGranularities: [.day, .month, .year]) 238 | } 239 | } 240 | 241 | } 242 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Common/SSCornerRadiusStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CornerRadiusStyle.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 30/11/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct SSCornerRadiusStyle: ViewModifier { 12 | 13 | // MARK: - Properties 14 | 15 | var radius: CGFloat 16 | var corners: UIRectCorner 17 | 18 | //MARK: - Body 19 | 20 | func body(content: Content) -> some View { 21 | content 22 | .clipShape(SSCornerRadiusShape(radius: radius, corners: corners)) 23 | } 24 | 25 | } 26 | 27 | struct SSCornerRadiusShape: Shape { 28 | 29 | //MARK: - Property 30 | 31 | var radius = CGFloat.infinity 32 | var corners = UIRectCorner.allCorners 33 | 34 | //MARK: - Methods 35 | 36 | func path(in rect: CGRect) -> Path { 37 | let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) 38 | return Path(path.cgPath) 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Common/SSImageConstant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageConstant.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 23/11/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SSImageConstant { 11 | 12 | static let chevronRight = "chevron.right" 13 | static let chevronLeft = "chevron.left" 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Common/SSLocalizedString.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalizedString.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 12/12/23. 6 | // 7 | 8 | import Foundation 9 | 10 | class SSLocalizedString { 11 | 12 | static let ok = "Ok".localized() 13 | static let cancel = "Cancel".localized() 14 | static let selectDate = "Select Date".localized() 15 | static let selectTime = "Select Time".localized() 16 | static let am = "AM".localized() 17 | static let pm = "PM".localized() 18 | static let hour = "HH".localized() 19 | static let minute = "MM".localized() 20 | static let done = "Done".localized() 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Common/SSPickerConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SSCalendarConstant.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 23/11/23. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | import SwiftUI 11 | 12 | let screen = UIScreen.main.bounds 13 | 14 | // MARK: - Picker Constants 15 | 16 | struct SSPickerConstants { 17 | 18 | static let pickerViewWidth: CGFloat = screen.width - (pickerLeadingTrailing*2) 19 | static let pickerViewInnerPadding: CGFloat = 8 20 | static let pickerLeadingTrailing: CGFloat = 40 21 | static let bottomButtonHSpacing: CGFloat = 5 22 | static let widthForDaysOfWeek: CGFloat = (pickerViewWidth - (pickerViewInnerPadding*2)) / 7 23 | static let monthYearGridSpacing: CGFloat = 15 24 | static let horizontalSpacingDates: CGFloat = 0 25 | static let verticleSpacingDates: CGFloat = 0 26 | static let selectionCirclePadding: CGFloat = 5 27 | static let dateRangeSelectionCornerRadius: CGFloat = 11 28 | static let monthYearViewTopSpace: CGFloat = 20 29 | static let monthYearViewBottomSpace: CGFloat = 20 30 | static let verticleSpacingTen: CGFloat = 10 31 | static let paddingTen: CGFloat = 10 32 | static let paddingFive: CGFloat = 5 33 | static let monthYearGridRows: Int = 3 34 | static let rangeSelectionPadding: CGFloat = 2 35 | static let deviderBottomPadding: CGFloat = 8 36 | 37 | // Time picker constant 38 | 39 | static let timeFieldPadding: CGFloat = 8 40 | static let timeFieldCornerRadius: CGFloat = 8 41 | static let circleSize: CGFloat = 40 42 | static let clockPadding: CGFloat = 50 43 | static let clockHeight: CGFloat = 300 44 | static let clockNumberRotationDegree: CGFloat = 30 // To create clock on 360 degree angle, use 30 degree (12*30 = 360) 45 | static let minuteRotationDegree: CGFloat = 6 // 12*5*6 = 360 46 | 47 | } 48 | 49 | // MARK: - Font Size 50 | 51 | struct Size { 52 | static let headerTitle: CGFloat = 12 53 | static let headerDate: CGFloat = 20 54 | static let month: CGFloat = 14 55 | static let year: CGFloat = 14 56 | static let buttonsText: CGFloat = 15 57 | static let currentMonthYear: CGFloat = 14 58 | static let clockNumber: CGFloat = 15 59 | static let timeLabel: CGFloat = 20 60 | static let timeFormat: CGFloat = 12 61 | static let selectedTimeFormat: CGFloat = 15 62 | } 63 | 64 | // MARK: - DateFormat 65 | 66 | /// A struct containing commonly used date format strings for formatting and parsing dates. 67 | public struct DateFormat { 68 | 69 | /// Abbreviated month format (e.g., "Dec"). 70 | public static let abbreviatedMonth = "MMM" 71 | 72 | /// Day of the week with month and day format (e.g., "Friday December 3"). 73 | public static let dayOfWeekWithMonthAndDay = "EEEE MMMM d" 74 | 75 | /// Full month format (e.g., "December"). 76 | public static let fullMonth = "MMMM" 77 | 78 | /// Month and year format (e.g., "December, 2023"). 79 | public static let monthYear = "MMMM, yyyy" 80 | 81 | /// Month, day, and year format (e.g., "Jun 3, 2023"). 82 | public static let monthDateYear = "MMM d, yyyy" 83 | 84 | /// Day, month, and year format (e.g., "3 Jun, 2023"). 85 | public static let dateMonthYear = "d MMM, yyyy" 86 | 87 | /// Full date with day, month, and year format (e.g., "03 December, 2023"). 88 | public static let dateMonthYearFull = "dd MMMM, yyyy" 89 | 90 | /// Month, day, and year full format (e.g., "December 03, 2023"). 91 | public static let monthDateYearFull = "MMMM dd, yyyy" 92 | 93 | /// Year, month, and day format (e.g., "2023-06-03"). 94 | public static let yearMonthDate = "yyyy-MM-dd" 95 | 96 | /// Short month and year format (e.g., "Jun 2023"). 97 | public static let shortMonthYear = "MMM yyyy" 98 | 99 | /// Day of the week, day, month, and year format (e.g., "Monday, 3 Jun, 2023"). 100 | public static let dayMonthYear = "EEEE, d MMM, yyyy" 101 | 102 | /// Full date format (e.g., "Monday, Jun 03, 2023"). 103 | public static let fullDate = "EEEE, MMMM d, yyyy" 104 | 105 | /// Time-only format with padding (e.g., "12:45 PM"). 106 | public static let timeOnlyWithPadding = "hh:mm a" 107 | 108 | /// Twenty-four-hour time format (e.g., "12:45"). 109 | public static let twentyFourHourFormat = "HH:mm" 110 | 111 | } 112 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Common/SSUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SSUtils.swift 3 | // SSDateTimePicker 4 | // 5 | // Created by Rizwana Desai on 18/12/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | // MARK: - typealias 12 | 13 | public typealias DateRange = (startDate: Date, endDate: Date) 14 | public typealias Time = Date 15 | 16 | // MARK: - SelectionView enum 17 | 18 | /// Enum representing different views available in a datetime picker. 19 | public enum SelectionView { 20 | 21 | /// Date view, allowing selection of day, month, and year. 22 | case date 23 | 24 | /// Month view, allowing selection of month and year. 25 | case month 26 | 27 | /// Year view, allowing selection of the year. 28 | case year 29 | 30 | } 31 | 32 | // MARK: - TimeFormat enum 33 | 34 | /// Enum representing different time formats for a time picker. 35 | enum TimeFormat { 36 | case am 37 | case pm 38 | } 39 | 40 | // MARK: - View style 41 | 42 | public struct SSStyle { 43 | public var font: Font 44 | public var color: Color 45 | 46 | init(font: Font, color: Color) { 47 | self.font = font 48 | self.color = color 49 | } 50 | } 51 | 52 | public struct SSColor { 53 | public var foregroundColor: Color? 54 | public var backgroundColor: Color? 55 | 56 | init(foregroundColor: Color? = nil, backgroundColor: Color? = nil) { 57 | self.foregroundColor = foregroundColor 58 | self.backgroundColor = backgroundColor 59 | } 60 | } 61 | 62 | // MARK: - Font used in SSDateTimePicker 63 | 64 | extension Font { 65 | static let headerTitle: Font = .system(size: Size.headerTitle, weight: .bold) 66 | static let headerDate: Font = .system(size: Size.headerDate, weight: .semibold) 67 | static let weekday: Font = .caption 68 | static let date: Font = .footnote 69 | static let month: Font = .system(size: Size.month, weight: .regular) 70 | static let selectedMonth: Font = .system(size: Size.month, weight: .bold) 71 | static let year: Font = .system(size: Size.year, weight: .regular) 72 | static let selectedYear: Font = .system(size: Size.year, weight: .bold) 73 | static let buttonText: Font = .system(size: Size.buttonsText, weight: .semibold) 74 | static let currentMonthYear: Font = .system(size: Size.currentMonthYear, weight: .medium) 75 | static let clockNumber: Font = .system(size: Size.clockNumber) 76 | static let timeLabel: Font = .system(size: Size.timeLabel, weight: .semibold) 77 | static let timeFormat: Font = .system(size: Size.timeFormat, weight: .bold) 78 | static let selectedTimeFormat: Font = .system(size: Size.selectedTimeFormat, weight: .bold) 79 | } 80 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Common/Time picker/SSCustomWheelPicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SSCustomWheelPicker.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 04/12/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SSCustomWheelPicker: View { 11 | 12 | var body: some View { 13 | wheelPicker 14 | .frame(height: 200) 15 | } 16 | 17 | var wheelPicker: some View { 18 | ScrollView() { 19 | VStack { 20 | ForEach(0..<60, id: \.self) { time in 21 | Text("\(time)") 22 | } 23 | } 24 | } 25 | .frame(height: 100) 26 | } 27 | 28 | } 29 | 30 | struct SSCustomWheelPicker_Previews: PreviewProvider { 31 | static var previews: some View { 32 | SSCustomWheelPicker() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Common/Time picker/SSTimePickerConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SSTimePickerConfiguration.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 04/12/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | /// A configuration structure for customizing the appearance and behavior of the SSTimePicker. 12 | struct SSTimePickerConfiguration { 13 | 14 | // MARK: - Colors and fonts 15 | 16 | /// Color for the header title "Select Time". 17 | var headerTitleStyle: SSStyle = .init(font: Font.headerTitle, color: .gray) 18 | 19 | /// Text color for the selected and unselected number on the clock. 20 | var clockNumberStyle: SSStyle = .init(font: Font.clockNumber, color: .black) 21 | 22 | /// Font for Hour (HH) and Minute (MM) labels. 23 | var timeLabelStyle: SSStyle = .init(font: Font.timeLabel, color: .darkPink) 24 | 25 | /// Font for the AM/PM time format when unselected. 26 | var timeFormatStyle: SSStyle = .init(font: Font.timeFormat, color: .gray) 27 | 28 | /// Font for the AM/PM time format when selected. 29 | var selectedTimeFormatStyle: SSStyle = .init(font: Font.selectedTimeFormat, color: .darkPink) 30 | 31 | var buttonStyle: SSStyle = .init(font: Font.selectedTimeFormat, color: .darkPink) 32 | 33 | // MARK: - Colors 34 | 35 | /// Color for the clock hand and circular selection circle. 36 | var clockHandColor: Color 37 | 38 | /// Background and foreground color for Hour (HH) and Minute (MM) labels. 39 | var timeLabelBackgroundColor: Color = .peach 40 | 41 | /// Color for the overlay background of the popup. 42 | var popupOverlayColor: Color = Color.black.opacity(0.5) 43 | 44 | /// Background color for the picker. 45 | var pickerBackgroundColor: Color 46 | 47 | // MARK: - Additional Properties 48 | 49 | /// Radius for the corner of the picker view. 50 | var pickerViewRadius: CGFloat = 15 51 | 52 | // MARK: - Initializer 53 | 54 | /// Creates a custom-themed time picker configuration. 55 | /// 56 | /// - Parameters: 57 | /// - pickerBackgroundColor: The background color of the picker view. Default is light pink. 58 | /// - primaryColor: The color for the selected time format (AM/PM), buttons text color, time text (HH:MM), and clock hand color. 59 | /// - timeLabelBackgroundColor: The background color for the time label (HH:MM). Default is light pink. 60 | /// 61 | /// Use this instance to set up the appearance and behavior of the SSTimePicker according to your preferences. 62 | init(pickerBackgroundColor: Color = Color.lightPink, primaryColor: Color = Color.darkPink, timeLabelBackgroundColor: Color = Color.peach) { 63 | self.timeLabelStyle.color = primaryColor 64 | self.selectedTimeFormatStyle.color = primaryColor 65 | self.buttonStyle.color = primaryColor 66 | self.clockHandColor = primaryColor 67 | self.pickerBackgroundColor = pickerBackgroundColor 68 | self.timeLabelBackgroundColor = timeLabelBackgroundColor 69 | } 70 | 71 | } 72 | 73 | /// A protocol providing direct access to specific properties of an `SSTimePickerConfiguration`. 74 | protocol TimePickerConfigurationDirectAccess { 75 | 76 | var configuration: SSTimePickerConfiguration { get } 77 | 78 | } 79 | 80 | extension TimePickerConfigurationDirectAccess { 81 | 82 | var timeLabelBackgroundColor: Color { 83 | configuration.timeLabelBackgroundColor 84 | } 85 | 86 | var timeLabelForegroundColor: Color { 87 | configuration.timeLabelStyle.color 88 | } 89 | 90 | var timeFormatSelectionColor: Color { 91 | configuration.selectedTimeFormatStyle.color 92 | } 93 | 94 | var timeFormatColor: Color { 95 | configuration.timeFormatStyle.color 96 | } 97 | 98 | var headerTitleColor: Color { 99 | configuration.headerTitleStyle.color 100 | } 101 | 102 | var buttonsForegroundColor: Color { 103 | configuration.buttonStyle.color 104 | } 105 | 106 | var pickerBackgroundColor: Color { 107 | configuration.pickerBackgroundColor 108 | } 109 | 110 | var popupOverlayColor: Color { 111 | configuration.popupOverlayColor 112 | } 113 | 114 | var pickerViewRadius: CGFloat { 115 | configuration.pickerViewRadius 116 | } 117 | 118 | var clockHandColor: Color { 119 | configuration.clockHandColor 120 | } 121 | 122 | var clockNumberTextColor: Color { 123 | configuration.clockNumberStyle.color 124 | } 125 | 126 | var headerTitleFont: Font { 127 | configuration.headerTitleStyle.font 128 | } 129 | 130 | var timeLabelFont: Font { 131 | configuration.timeLabelStyle.font 132 | } 133 | 134 | var timeFormatFont: Font { 135 | configuration.timeFormatStyle.font 136 | } 137 | 138 | var selectedTimeFormatFont: Font { 139 | configuration.selectedTimeFormatStyle.font 140 | } 141 | 142 | var clockNumberFont: Font { 143 | configuration.clockNumberStyle.font 144 | } 145 | 146 | var buttonFont: Font { 147 | configuration.buttonStyle.font 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Common/Time picker/SSTimePickerManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SSTimePickerManager.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 04/12/23. 6 | // 7 | 8 | import Foundation 9 | 10 | /// A manager class for handling the state and behavior of the SSTimePicker. 11 | final class SSTimePickerManager: ObservableObject { 12 | 13 | // MARK: - Properties 14 | 15 | /// The configuration for the SSTimePicker. 16 | var configuration: SSTimePickerConfiguration 17 | 18 | /// The selected time. Set this property to pre-select a specific time. 19 | @Published var selectedTime: Date? 20 | 21 | /// The selected time format (AM or PM). 22 | @Published var selectedTimeFromat: TimeFormat = .am 23 | 24 | /// The selected hour as a string. 25 | @Published var hourSelected: String = "12" 26 | 27 | /// The selected minutes as a string. 28 | @Published var minutesSelected: String = "00" 29 | 30 | /// The angle representing the current position of the clock hand. 31 | @Published var angle: Double = 0 32 | 33 | /// A flag indicating whether the minute clock view is currently active. 34 | @Published var isMinuteClock: Bool = false 35 | 36 | var timeSelectionCallback: (Date) -> () = {_ in} 37 | 38 | // MARK: - Initializer 39 | 40 | /// Initializes the SSTimePickerManager with the provided configuration. 41 | /// 42 | /// - Parameter configuration: The style configuration for the SSTimePicker. 43 | public init(configuration: SSTimePickerConfiguration = SSTimePickerConfiguration()) { 44 | self.configuration = configuration 45 | } 46 | 47 | //MARK: - Methods 48 | 49 | /// Switches the time picker to show the hour clock view and updates the current hour angle. 50 | func actionShowHourClock() { 51 | updateCurrentHourAngle() 52 | isMinuteClock = false 53 | } 54 | 55 | /// Switches the time picker to show the minute clock view and updates the current minute angle. 56 | func actionShowMinuteClock() { 57 | updateCurrentMinuteAngle() 58 | isMinuteClock = true 59 | } 60 | 61 | /// Updates the angle based on the current selected hour. 62 | func updateCurrentHourAngle() { 63 | let hour = updateWithDefaultHourIfEmpty() 64 | angle = Double(hour * 30) 65 | } 66 | 67 | /// Updates the angle based on the current selected minute. 68 | func updateCurrentMinuteAngle() { 69 | let minute = updateWithDefaultMinuteIfEmpty() 70 | angle = Double(minute * 6) 71 | } 72 | 73 | /// Updates the selected time based on the current hour, minute, and time format. 74 | func updateSelectedTime() { 75 | _ = updateWithDefaultMinuteIfEmpty() 76 | _ = updateWithDefaultHourIfEmpty() 77 | let format = DateFormatter.configure(with: DateFormat.twentyFourHourFormat) 78 | // get hours for 24-hrs format 79 | let hour = (Int(hourSelected) ?? 12) 80 | let updatedHour = selectedTimeFromat == .am ? (hour == 12 ? 00.formattedTime : hour.formattedTime) : "\(hour < 12 ? (hour + 12).formattedTime : hour.formattedTime)" 81 | let date = format.date(from: "\(updatedHour):\(minutesSelected)") 82 | selectedTime = date 83 | handleCallback() 84 | isMinuteClock = false 85 | } 86 | 87 | // In case if textfield is empty set default minute 88 | func updateWithDefaultMinuteIfEmpty() -> Int { 89 | let minute = Int(minutesSelected) ?? 00 90 | if minutesSelected == "" { 91 | minutesSelected = "00" 92 | } 93 | return minute 94 | } 95 | 96 | // In case if textfield is empty set default hour 97 | func updateWithDefaultHourIfEmpty() -> Int { 98 | let hour = Int(hourSelected) ?? 12 99 | if hourSelected == "" { 100 | hourSelected = "12" 101 | } 102 | return hour 103 | } 104 | 105 | /// Sets up the initial time, angle, and clock view based on the selected time. 106 | func setUpTimeAndAngle() { 107 | let calender = Calendar.current 108 | guard let selectedTime else { return } 109 | // 24 Hrs 110 | var hourTemp = calender.component(.hour, from: selectedTime) 111 | selectedTimeFromat = hourTemp <= 12 ? .am : .pm 112 | hourTemp = hourTemp == 0 ? 12 : hourTemp 113 | hourTemp = hourTemp <= 12 ? hourTemp: hourTemp - 12 114 | let minutesTemp = calender.component(.minute, from: selectedTime) 115 | hourSelected = hourTemp.formattedTime 116 | minutesSelected = minutesTemp.formattedTime 117 | updateCurrentHourAngle() 118 | } 119 | 120 | /// Notifies the delegate when a time is selected. 121 | func handleCallback() { 122 | if let selectedTime { 123 | timeSelectionCallback(selectedTime) 124 | } 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/DateTimePicker.docc/DateTimePicker.md: -------------------------------------------------------------------------------- 1 | # ``DateTimePicker`` 2 | 3 | Summary 4 | 5 | ## Overview 6 | 7 | Text 8 | 9 | ## Topics 10 | 11 | ### Group 12 | 13 | - ``Symbol`` -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/DateTimePicker.h: -------------------------------------------------------------------------------- 1 | // 2 | // DateTimePicker.h 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 22/11/23. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for DateTimePicker. 11 | FOUNDATION_EXPORT double DateTimePickerVersionNumber; 12 | 13 | //! Project version string for DateTimePicker. 14 | FOUNDATION_EXPORT const unsigned char DateTimePickerVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | 18 | 19 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Extensions/SSCalendar+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Calendar+Extension.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 24/11/23. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Calendar { 11 | 12 | // MARK: - Generate Dates Inside Interval 13 | 14 | /// Generates an array of dates within the specified date interval that match the given components. 15 | /// 16 | /// - Parameters: 17 | /// - interval: The date interval within which to generate dates. 18 | /// - components: The date components to match for each generated date. 19 | /// 20 | /// Example: 21 | /// ```swift 22 | /// let startDate = Date() 23 | /// let endDate = Calendar.current.date(byAdding: .month, value: 1, to: startDate)! 24 | /// let dateComponents = DateComponents(hour: 12, minute: 0) 25 | /// let generatedDates = Calendar.current.generateDates(inside: DateInterval(start: startDate, end: endDate), matching: dateComponents) 26 | /// ``` 27 | /// 28 | /// - Returns: An array of dates within the specified interval that match the provided components. 29 | func generateDates(inside interval: DateInterval, 30 | matching components: DateComponents) -> [Date] { 31 | var dates: [Date] = [] 32 | dates.append(interval.start) 33 | 34 | enumerateDates( 35 | startingAfter: interval.start, 36 | matching: components, 37 | matchingPolicy: .nextTime) { date, _, stop in 38 | if let date = date { 39 | if date < interval.end { 40 | dates.append(date) 41 | } else { 42 | stop = true 43 | } 44 | } 45 | } 46 | return dates 47 | } 48 | 49 | // MARK: - First Day of Every Week Date Components 50 | 51 | /// Represents a `DateComponents` instance set to midnight on the first day of the week. 52 | /// 53 | /// Example: 54 | /// ```swift 55 | /// let firstDayOfTheWeek = Calendar.current.firstDayOfEveryWeek 56 | /// // Use `firstDayOfTheWeek` to determine the starting date for a weekly calendar view. 57 | /// ``` 58 | /// 59 | /// - Note: The actual first day of the week is determined by the `firstWeekday` property of the calendar. 60 | /// 61 | /// - Returns: A `DateComponents` instance with hour, minute, and second set to zero and the weekday set to the first day of the week. 62 | var firstDayOfEveryWeek: DateComponents { 63 | DateComponents(hour: 0, minute: 0, second: 0, weekday: firstWeekday) 64 | } 65 | 66 | // MARK: - Compare Dates to Granularities 67 | 68 | /// Compares two dates based on specified granularities and determines if they are equal. 69 | /// 70 | /// - Parameters: 71 | /// - date1: The first date to compare. 72 | /// - date2: The second date to compare. 73 | /// - components: The set of granularities to consider in the comparison. 74 | /// 75 | /// Example: 76 | /// ```swift 77 | /// let startDate = Date() 78 | /// let endDate = Calendar.current.date(byAdding: .day, value: 1, to: startDate)! 79 | /// let granularities: Set = [.year, .month, .day] 80 | /// let result = Calendar.current.isDate(startDate, equalTo: endDate, toGranularities: granularities) 81 | /// print(result) // true if the dates are equal up to the specified granularities. 82 | /// ``` 83 | /// 84 | /// - Returns: `true` if the dates are equal up to the specified granularities; otherwise, `false`. 85 | func isDate(_ date1: Date, equalTo date2: Date, toGranularities components: Set) -> Bool { 86 | components.reduce(into: true) { isEqual, component in 87 | isEqual = isEqual && isDate(date1, equalTo: date2, toGranularity: component) 88 | } 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Extensions/SSColor+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color+Extension.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 24/11/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | public extension Color { 12 | 13 | static let lightGreen: Color = Color(red: 230, green: 234, blue: 221) 14 | static let darkGreen: Color = Color(red: 68, green: 105, blue: 43) 15 | static let darkPink: Color = Color(red: 221, green: 94, blue: 105) 16 | static let lightPink: Color = Color(red: 253, green: 250, blue: 250) 17 | static let lightBlue: Color = Color(red: 242, green: 244, blue: 252) 18 | static let peach: Color = Color(red: 244, green: 209, blue: 213) 19 | 20 | } 21 | 22 | fileprivate extension Color { 23 | 24 | init(red: Int, green: Int, blue: Int) { 25 | self.init(red: Double(red)/255, green: Double(green)/255, blue: Double(blue)/255) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Extensions/SSDate+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date+Extension.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 24/11/23. 6 | // 7 | 8 | import Foundation 9 | 10 | 11 | extension Date { 12 | 13 | // MARK: - Formatted Date Properties 14 | 15 | /// Abbreviated month representation of the date (e.g., "Dec"). 16 | public var abbreviatedMonth: String { 17 | DateFormatter.configure(with: DateFormat.abbreviatedMonth).string(from: self) 18 | } 19 | 20 | /// Full date with day of the week, month, and day representation (e.g., "Friday December 3"). 21 | public var dayOfWeekWithMonthAndDay: String { 22 | DateFormatter.configure(with: DateFormat.dayOfWeekWithMonthAndDay).string(from: self) 23 | } 24 | 25 | /// Full month representation of the date (e.g., "December"). 26 | public var fullMonth: String { 27 | DateFormatter.configure(with: DateFormat.fullMonth).string(from: self) 28 | } 29 | 30 | /// Time-only representation with padding (e.g., "12:45 PM"). 31 | public var timeOnlyWithPadding: String { 32 | DateFormatter.configure(with: DateFormat.timeOnlyWithPadding).string(from: self) 33 | } 34 | 35 | /// Month, day, and year representation (e.g., "Jun 3, 2023"). 36 | public var monthDateYear: String { 37 | DateFormatter.configure(with: DateFormat.monthDateYear).string(from: self) 38 | } 39 | 40 | /// Month and year representation (e.g., "December, 2023"). 41 | public var monthYear: String { 42 | DateFormatter.configure(with: DateFormat.monthYear).string(from: self) 43 | } 44 | 45 | // MARK: - Custom Formatted Date 46 | 47 | /// Returns a custom-formatted string representation of the date. 48 | /// 49 | /// - Parameter format: The format string for the desired date format. 50 | /// - Returns: A string representation of the date in the specified format. 51 | public func formatedString(_ format: String) -> String { 52 | DateFormatter.configure(with: format).string(from: self) 53 | } 54 | 55 | // MARK: - Date Calculations 56 | 57 | /// Returns the date of the next month. 58 | func getNextMonth(_ calender: Calendar) -> Date? { 59 | return calender.date(byAdding: .month, value: 1, to: self) 60 | } 61 | 62 | /// Returns the date of the previous month. 63 | func getPreviousMonth(_ calender: Calendar) -> Date? { 64 | return calender.date(byAdding: .month, value: -1, to: self) 65 | } 66 | 67 | /// Returns the date of the next year. 68 | func getNextYear(_ calender: Calendar) -> Date? { 69 | return calender.date(byAdding: .year, value: 1, to: self) 70 | } 71 | 72 | /// Returns the date of the previous year. 73 | func getPreviousYear(_ calender: Calendar) -> Date? { 74 | return calender.date(byAdding: .year, value: -1, to: self) 75 | } 76 | 77 | /// Returns the year of the date. 78 | /// 79 | /// - Parameter calender: The calendar to use for the date components. 80 | /// - Returns: The year of the date. 81 | func year(_ calender: Calendar) -> Int { 82 | let components = calender.dateComponents([.year], from: self) 83 | return components.year ?? 2023 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Extensions/SSDateComponents+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateComponents+Extension.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 24/11/23. 6 | // 7 | 8 | import Foundation 9 | 10 | extension DateComponents { 11 | 12 | // MARK: - Every Day Date Components 13 | 14 | /// Represents a `DateComponents` instance with zeroed hours, minutes, and seconds, 15 | /// effectively representing the concept of "every day." 16 | /// 17 | /// Example: 18 | /// ```swift 19 | /// let dailyResetTime = DateComponents.everyDay 20 | /// let nextResetDate = Calendar.current.date(byAdding: dailyResetTime, to: Date()) 21 | /// ``` 22 | /// 23 | /// - Returns: A `DateComponents` instance representing midnight, the start of every day. 24 | static var everyDay: DateComponents { 25 | DateComponents(hour: 0, minute: 0, second: 0) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Extensions/SSDateFormatter+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateFormatter+Extension.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 24/11/23. 6 | // 7 | 8 | import Foundation 9 | 10 | extension DateFormatter { 11 | 12 | // MARK: - Months List 13 | 14 | /// Provides an array of localized month names using the current locale. 15 | /// 16 | /// Example: 17 | /// ``` 18 | /// let months = DateFormatter.monthsList 19 | /// print(months) // ["January", "February", ..., "December"] 20 | /// ``` 21 | static var monthsList: [String] { 22 | let formatter = DateFormatter() 23 | let months = formatter.monthSymbols 24 | return months ?? [] 25 | } 26 | 27 | // MARK: - Date Formatter with Format String 28 | 29 | /// Returns a date formatter instance configured with the specified format string. 30 | /// 31 | /// - Parameter formate: The format string for the desired date format. 32 | /// 33 | /// Example: 34 | /// ```swift 35 | /// let customDateFormatter = DateFormatter.configure(with: "yyyy-MM-dd HH:mm:ss") 36 | /// let formattedDate = customDateFormatter.string(from: Date()) 37 | /// print(formattedDate) // Formatted date string using the specified format. 38 | /// ``` 39 | /// 40 | /// - Returns: A `DateFormatter` instance with the specified date format. 41 | static func configure(with formate: String) -> DateFormatter { 42 | let formatter = DateFormatter() 43 | formatter.dateFormat = formate 44 | return formatter 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Extensions/SSInt+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Int+Extension.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 06/12/23. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Int { 11 | 12 | // MARK: - Formatted Time String 13 | 14 | /// Converts an integer representing time (e.g., hours, minutes, seconds) into a two-digit formatted string. 15 | /// 16 | /// This extension is useful for ensuring a consistent two-digit representation, adding leading zeros if necessary. 17 | /// 18 | /// Example: 19 | /// ``` 20 | /// let hour = 8 21 | /// let formattedHour = hour.formattedTime // "08" 22 | /// ``` 23 | var formattedTime: String { 24 | String(format: "%02d", self) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Extensions/SSString+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Extension.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 12/12/23. 6 | // 7 | 8 | import Foundation 9 | 10 | extension String { 11 | 12 | // MARK: - Localized String with Comment 13 | 14 | /// Returns a localized version of the string, using the NSLocalizedString function, with an optional comment. 15 | /// 16 | /// - Parameters: 17 | /// - comment: An optional comment that can be used to provide context or additional information for translators. 18 | /// 19 | /// Example: 20 | /// ``` 21 | /// let greeting = "Hello".localized(withComment: "User greeting") 22 | /// print(greeting) // Localized version of "Hello" with the specified comment. 23 | /// ``` 24 | /// 25 | /// - Returns: A localized version of the string. 26 | func localized(withComment comment: String = "") -> String { 27 | return NSLocalizedString(self, comment: comment) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Extensions/SSView+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // View+Extension.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 30/11/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | extension View { 12 | 13 | /// Applies the given transform if the given condition evaluates to `true`. 14 | /// - Parameters: 15 | /// - condition: The condition to evaluate. 16 | /// - transform: The transform to apply to the source `View`. 17 | /// - Returns: Either the original `View` or the modified `View` if the condition is `true`. 18 | @ViewBuilder func `if`(_ condition: Bool, transform: (Self) -> Content) -> some View { 19 | if condition { 20 | transform(self) 21 | } else { 22 | self 23 | } 24 | } 25 | 26 | // MARK: - Corner Radius Modifier 27 | 28 | /// Applies a corner radius to specific corners of a view using a custom modifier. 29 | /// 30 | /// - Parameters: 31 | /// - radius: The radius of the corner. 32 | /// - corners: The corners to which the radius should be applied. 33 | /// 34 | /// Example: 35 | /// ``` 36 | /// someView.cornerRadius(10, corners: [.topLeft, .bottomRight]) 37 | /// ``` 38 | /// 39 | /// - Returns: A modified version of the view with the specified corner radius applied. 40 | func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { 41 | ModifiedContent(content: self, modifier: SSCornerRadiusStyle(radius: radius, corners: corners)) 42 | } 43 | 44 | /// Applies a theme-specific button style to the view using a custom modifier. 45 | /// 46 | /// - Parameters: 47 | /// - txtColor: The color of the text within the button. 48 | /// - font: The font style for the text within the button. 49 | /// 50 | /// Example: 51 | /// ``` 52 | /// someView.themeButton(.blue, .headline) 53 | /// ``` 54 | /// 55 | /// - Returns: A modified version of the view with the specified text color and font for a themed button. 56 | func themeButton(_ txtColor: Color, _ font: Font) -> some View { 57 | self.modifier(SSThemeButtonModifier(textColor: txtColor, font: font)) 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Views/Date picker/SSDatePicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SSDatePicker.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 24/11/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct SSDatePicker: View, DatePickerConfigurationDirectAccess { 11 | 12 | //MARK: - Properties 13 | 14 | @Binding var showDatePicker: Bool 15 | @State var currentView: SelectionView = .date 16 | @ObservedObject var datePickerManager: SSDatePickerManager = SSDatePickerManager() 17 | 18 | var configuration: SSDatePickerConfiguration { 19 | get { 20 | datePickerManager.configuration 21 | } 22 | set { 23 | datePickerManager.configuration = newValue 24 | } 25 | } 26 | 27 | private var weeks: [Date] { 28 | guard let monthInterval = calendar.dateInterval(of: .month, for: datePickerManager.currentMonth) else { 29 | return [] 30 | } 31 | return calendar.generateDates( 32 | inside: monthInterval, 33 | matching: calendar.firstDayOfEveryWeek) 34 | } 35 | 36 | //MARK: - Initializer 37 | 38 | public init(showDatePicker: Binding) { 39 | self._showDatePicker = showDatePicker 40 | } 41 | 42 | //MARK: - Sub views 43 | 44 | public var body: some View { 45 | ZStack(alignment: .center) { 46 | if showDatePicker { 47 | popupOverlayColor 48 | .ignoresSafeArea() 49 | .onTapGesture { 50 | actionCancel() 51 | } 52 | calenderContainerView 53 | .background(pickerBackgroundColor) 54 | .cornerRadius(pickerViewRadius) 55 | .padding(.leading, SSPickerConstants.pickerLeadingTrailing) 56 | .padding(.trailing, SSPickerConstants.pickerLeadingTrailing) 57 | .compositingGroup() 58 | } 59 | } 60 | .environmentObject(datePickerManager) 61 | } 62 | 63 | private var pickerContainerView: some View { 64 | ZStack(alignment: .center) { 65 | switch currentView { 66 | case .date: 67 | dateSectionView 68 | case .month: 69 | SSMonthSelectionView() 70 | case .year: 71 | SSYearSelectionView(currentView: $currentView) 72 | } 73 | } 74 | } 75 | 76 | private var calenderContainerView: some View { 77 | VStack(alignment: .leading, spacing: SSPickerConstants.verticleSpacingTen) { 78 | datePickerHeader 79 | pickerContainerView 80 | calenderFooterView 81 | bottomButtons 82 | } 83 | .padding(SSPickerConstants.pickerViewInnerPadding) 84 | } 85 | 86 | private var datePickerHeader: some View { 87 | VStack(alignment: .leading, spacing: SSPickerConstants.paddingFive) { 88 | lblSelectedDate 89 | Divider() 90 | .background(sepratorLineColor) 91 | .padding(.bottom, SSPickerConstants.deviderBottomPadding) 92 | } 93 | } 94 | 95 | private var dateSectionView: some View { 96 | VStack(alignment: .leading, spacing: SSPickerConstants.verticleSpacingTen) { 97 | daysOfWeekView 98 | datesView 99 | } 100 | } 101 | 102 | private var lblSelectedDate: some View { 103 | VStack(alignment: .leading, spacing: SSPickerConstants.verticleSpacingTen) { 104 | Text(SSLocalizedString.selectDate) 105 | .font(headerTitleFont) 106 | .foregroundColor(headerTitleColor) 107 | Text(datePickerManager.selectedDate?.formatedString(headerDateFormat) ?? datePickerManager.currentMonth.monthYear) 108 | .font(headerDateFont) 109 | .foregroundColor(headerDateColor) 110 | } 111 | .padding(SSPickerConstants.paddingTen) 112 | } 113 | 114 | private var daysOfWeekView: some View { 115 | HStack(spacing: SSPickerConstants.horizontalSpacingDates) { 116 | ForEach(calendar.shortWeekdaySymbols, id: \.self) { dayOfWeek in 117 | Text(dayOfWeek.prefix(1)) 118 | .font(weekdayTextFont) 119 | .frame(width: SSPickerConstants.widthForDaysOfWeek) 120 | .foregroundColor(weekdayTextColor) 121 | } 122 | } 123 | } 124 | 125 | private var datesView: some View { 126 | VStack(spacing: SSPickerConstants.verticleSpacingDates) { 127 | ForEach(weeks, id: \.self) { week in 128 | SSWeekDatesView(week: week) 129 | } 130 | } 131 | } 132 | 133 | private var calenderFooterView: some View { 134 | HStack { 135 | btnPrevious 136 | Spacer() 137 | lblMonthYear 138 | Spacer() 139 | btnNext 140 | } 141 | .frame(maxWidth: .infinity) 142 | .padding(SSPickerConstants.paddingFive) 143 | } 144 | 145 | private var lblMonthYear: some View { 146 | Button { 147 | withAnimation { 148 | updateView() 149 | } 150 | } label: { 151 | Text(currentMonthYear) 152 | .font(currentMonthYearBottomLabelFont) 153 | .foregroundColor(monthYearNavigationLabelColor) 154 | } 155 | } 156 | 157 | private var currentMonthYear: String { 158 | switch currentView { 159 | case .date: 160 | return datePickerManager.currentMonth.monthYear 161 | case .month: 162 | return String(datePickerManager.currentMonth.year(calendar)) 163 | case .year: 164 | guard let startingYear = datePickerManager.yearRange.first, let endYear = datePickerManager.yearRange.last else { 165 | return String(datePickerManager.currentMonth.year(calendar)) 166 | } 167 | return "\(startingYear) - \(endYear)" 168 | } 169 | } 170 | 171 | private var btnPrevious: some View { 172 | Button { 173 | self.datePickerManager.actionPrev(for: currentView) 174 | } label: { 175 | self.imageNextPrev(SSImageConstant.chevronLeft) 176 | } 177 | } 178 | 179 | private var btnNext: some View { 180 | Button { 181 | self.datePickerManager.actionNext(for: currentView) 182 | } label: { 183 | self.imageNextPrev(SSImageConstant.chevronRight) 184 | } 185 | } 186 | 187 | private func imageNextPrev(_ name: String) -> some View { 188 | Image(systemName: name) 189 | .foregroundColor(buttonsForegroundColor) 190 | .padding(SSPickerConstants.paddingFive) 191 | } 192 | 193 | private var bottomButtons: some View { 194 | HStack(spacing: SSPickerConstants.bottomButtonHSpacing) { 195 | Spacer() 196 | btnCancel 197 | btnOk 198 | } 199 | } 200 | 201 | private var btnCancel: some View { 202 | Button { 203 | withAnimation { 204 | self.actionCancel() 205 | } 206 | } label: { 207 | Text(SSLocalizedString.cancel) 208 | .themeButton(buttonsForegroundColor, buttonsFont) 209 | } 210 | } 211 | 212 | private var btnOk: some View { 213 | Button { 214 | withAnimation { 215 | self.actionOk() 216 | } 217 | } label: { 218 | Text(SSLocalizedString.ok) 219 | .themeButton(buttonsForegroundColor, buttonsFont) 220 | } 221 | } 222 | 223 | private func updateView() { 224 | switch currentView { 225 | case .date: 226 | currentView = .month 227 | case .month: 228 | currentView = .year 229 | case .year: 230 | currentView = .date 231 | } 232 | } 233 | 234 | } 235 | 236 | extension SSDatePicker { 237 | 238 | //MARK: - Action methods 239 | 240 | private func actionCancel() { 241 | datePickerManager.selectionCanceled(for: currentView) 242 | switch currentView { 243 | case .date: 244 | showDatePicker = false 245 | currentView = .date 246 | case .month: 247 | currentView = .date 248 | case .year: 249 | currentView = .month 250 | } 251 | } 252 | 253 | private func actionOk() { 254 | self.datePickerManager.selectionConfirmed(for: currentView) 255 | switch currentView { 256 | case .year, .month: 257 | currentView = .date 258 | case .date: 259 | showDatePicker = false 260 | currentView = .date 261 | } 262 | } 263 | 264 | } 265 | 266 | // MARK: - Modifiers 267 | 268 | extension SSDatePicker { 269 | 270 | /// Creates a custom-themed date picker. 271 | /// - Parameters: 272 | /// - pickerBackgroundColor: The background color of the picker view. Default is light pink. 273 | /// - primaryColor: The color for both the selected date's background and the buttons. Deafult is dark pink. 274 | /// - Returns: An updated SSDatePicker instance. 275 | public func themeColor(pickerBackgroundColor: Color = Color.lightPink, primaryColor: Color = Color.darkPink) -> SSDatePicker { 276 | var picker = self 277 | picker.configuration.selectedDateColor.backgroundColor = primaryColor 278 | picker.configuration.buttonStyle.color = primaryColor 279 | picker.configuration.pickerBackgroundColor = pickerBackgroundColor 280 | return picker 281 | } 282 | 283 | /// Sets the style for the header title in the date picker. 'Select Date' 284 | /// - Parameters: 285 | /// - color: The color of the header title. 286 | /// - font: The font of the header title. 287 | /// - Returns: An updated SSDatePicker instance. 288 | public func headerTitleStyle(color: Color? = nil, font: Font? = nil) -> SSDatePicker { 289 | var picker = self 290 | color.map { picker.configuration.headerTitleStyle.color = $0 } 291 | font.map { picker.configuration.headerTitleStyle.font = $0 } 292 | return picker 293 | } 294 | 295 | /// Sets the style for the header date in the date picker. 296 | /// - Parameters: 297 | /// - color: The color of the header date. 298 | /// - font: The font of the header date. 299 | /// - Returns: An updated SSDatePicker instance. 300 | public func headerDateStyle(color: Color? = nil, font: Font? = nil) -> SSDatePicker { 301 | var picker = self 302 | color.map { picker.configuration.headerDateStyle.color = $0 } 303 | font.map { picker.configuration.headerDateStyle.font = $0 } 304 | return picker 305 | } 306 | 307 | /// Sets the style for the weekday labels in the date picker. 308 | /// - Parameters: 309 | /// - color: The color of the weekday labels. 310 | /// - font: The font of the weekday labels. 311 | /// - Returns: An updated SSDatePicker instance. 312 | public func weekdayStyle(color: Color? = nil, font: Font? = nil) -> SSDatePicker { 313 | var picker = self 314 | color.map { picker.configuration.weekdayStyle.color = $0 } 315 | font.map { picker.configuration.weekdayStyle.font = $0 } 316 | return picker 317 | } 318 | 319 | /// Sets the style for the date labels in the date picker. 320 | /// - Parameters: 321 | /// - color: The color of the date labels. 322 | /// - font: The font of the date labels. 323 | /// - Returns: An updated SSDatePicker instance. 324 | public func dateStyle(color: Color? = nil, font: Font? = nil) -> SSDatePicker { 325 | var picker = self 326 | color.map { picker.configuration.dateStyle.color = $0 } 327 | font.map { picker.configuration.dateStyle.font = $0 } 328 | return picker 329 | } 330 | 331 | /// Sets the style for the month labels in the date picker. 332 | /// - Parameters: 333 | /// - color: The color of the month labels. 334 | /// - font: The font of the month labels. 335 | /// - Returns: An updated SSDatePicker instance. 336 | public func monthStyle(color: Color? = nil, font: Font? = nil) -> SSDatePicker { 337 | var picker = self 338 | color.map { picker.configuration.monthStyle.color = $0 } 339 | font.map { picker.configuration.monthStyle.font = $0 } 340 | return picker 341 | } 342 | 343 | /// Sets the style for the selected month in the date picker. 344 | /// - Parameters: 345 | /// - color: The color of the selected month. 346 | /// - font: The font of the selected month. 347 | /// - Returns: An updated SSDatePicker instance. 348 | public func selectedMonthStyle(color: Color? = nil, font: Font? = nil) -> SSDatePicker { 349 | var picker = self 350 | color.map { picker.configuration.selectedMonthStyle.color = $0 } 351 | font.map { picker.configuration.selectedMonthStyle.font = $0 } 352 | return picker 353 | } 354 | 355 | /// Sets the style for the year labels in the date picker. 356 | /// - Parameters: 357 | /// - color: The color of the year labels. 358 | /// - font: The font of the year labels. 359 | /// - Returns: An updated SSDatePicker instance. 360 | public func yearStyle(color: Color? = nil, font: Font? = nil) -> SSDatePicker { 361 | var picker = self 362 | color.map { picker.configuration.yearStyle.color = $0 } 363 | font.map { picker.configuration.yearStyle.font = $0 } 364 | return picker 365 | } 366 | 367 | /// Sets the style for the selected year in the date picker. 368 | /// - Parameters: 369 | /// - color: The color of the selected year. 370 | /// - font: The font of the selected year. 371 | /// - Returns: An updated SSDatePicker instance. 372 | public func selectedYearStyle(color: Color? = nil, font: Font? = nil) -> SSDatePicker { 373 | var picker = self 374 | color.map { picker.configuration.selectedYearStyle.color = $0 } 375 | font.map { picker.configuration.selectedYearStyle.font = $0 } 376 | return picker 377 | } 378 | 379 | /// Sets the style for the button in the date picker. 380 | /// - Parameters: 381 | /// - color: The color of the button. 382 | /// - font: The font of the button. 383 | /// - Returns: An updated SSDatePicker instance. 384 | public func buttonStyle(color: Color? = nil, font: Font? = nil) -> SSDatePicker { 385 | var picker = self 386 | color.map { picker.configuration.buttonStyle.color = $0 } 387 | font.map { picker.configuration.buttonStyle.font = $0 } 388 | return picker 389 | } 390 | 391 | /// Sets the style for the current month and year label in the date picker. 392 | /// - Parameters: 393 | /// - color: The color of the current month and year label. 394 | /// - font: The font of the current month and year label. 395 | /// - Returns: An updated SSDatePicker instance. 396 | public func currentMonthYearLabelStyle(color: Color? = nil, font: Font? = nil) -> SSDatePicker { 397 | var picker = self 398 | color.map { picker.configuration.currentMonthYearLabelStyle.color = $0 } 399 | font.map { picker.configuration.currentMonthYearLabelStyle.font = $0 } 400 | return picker 401 | } 402 | 403 | /// Sets the color for the selected date in the date picker. 404 | /// - Parameters: 405 | /// - backgroundColor: The background color of the selected date. 406 | /// - foregroundColor: The foreground color of the selected date. 407 | /// - Returns: An updated SSDatePicker instance. 408 | public func selectedDateColor(backgroundColor: Color? = nil, foregroundColor: Color? = nil) -> SSDatePicker { 409 | var picker = self 410 | backgroundColor.map { picker.configuration.selectedDateColor.backgroundColor = $0 } 411 | foregroundColor.map { picker.configuration.selectedDateColor.foregroundColor = $0 } 412 | return picker 413 | } 414 | 415 | /// Sets the color for today's date in the date picker. 416 | /// - Parameters: 417 | /// - backgroundColor: The background color of today's date. 418 | /// - foregroundColor: The foreground color of today's date. 419 | /// - Returns: An updated SSDatePicker instance. 420 | public func todayColor(backgroundColor: Color? = nil, foregroundColor: Color? = nil) -> SSDatePicker { 421 | var picker = self 422 | picker.configuration.todayDateColor.backgroundColor = backgroundColor 423 | picker.configuration.todayDateColor.foregroundColor = foregroundColor 424 | return picker 425 | } 426 | 427 | /// Sets the color for today's date when it is selected in the date picker. 428 | /// - Parameters: 429 | /// - backgroundColor: The background color of the selected today's date. 430 | /// - foregroundColor: The foreground color of the selected today's date. 431 | /// - Returns: An updated SSDatePicker instance. 432 | public func todayDateSelectionColor(backgroundColor: Color? = nil, foregroundColor: Color? = nil) -> SSDatePicker { 433 | var picker = self 434 | picker.configuration.todayDateSelectionColor.backgroundColor = backgroundColor 435 | picker.configuration.todayDateSelectionColor.foregroundColor = foregroundColor 436 | return picker 437 | } 438 | 439 | /// Sets the background color for the date picker. 440 | /// - Parameter color: The background color of the date picker. 441 | /// - Returns: An updated SSDatePicker instance. 442 | public func pickerBackgroundColor(_ color: Color) -> SSDatePicker { 443 | var picker = self 444 | picker.configuration.pickerBackgroundColor = color 445 | return picker 446 | } 447 | 448 | /// Sets the color for the separator line between date components in the date picker. 449 | /// - Parameter color: The color of the separator line. 450 | /// - Returns: An updated SSDatePicker instance. 451 | public func sepratorLineColor(_ color: Color) -> SSDatePicker { 452 | var picker = self 453 | picker.configuration.sepratorLineColor = color 454 | return picker 455 | } 456 | 457 | /// Sets the overlay color for the background of the entire date picker. 458 | /// - Parameter color: The color of the overlay. 459 | /// - Returns: An updated SSDatePicker instance. 460 | public func popupOverlayColor(_ color: Color) -> SSDatePicker { 461 | var picker = self 462 | picker.configuration.popupOverlayColor = color 463 | return picker 464 | } 465 | 466 | /// Sets the minimum selectable date in the date picker. 467 | /// - Parameter date: The minimum selectable date. 468 | /// - Returns: An updated SSDatePicker instance. 469 | public func minimumDate(_ date: Date) -> SSDatePicker { 470 | var picker = self 471 | picker.configuration.minimumDate = date 472 | return picker 473 | } 474 | 475 | /// Sets the maximum selectable date in the date picker. 476 | /// - Parameter date: The maximum selectable date. 477 | /// - Returns: An updated SSDatePicker instance. 478 | public func maximumDate(_ date: Date) -> SSDatePicker { 479 | var picker = self 480 | picker.configuration.maximumDate = date 481 | return picker 482 | } 483 | 484 | /// Disables selection of past dates in the date picker. 485 | /// - Returns: An updated SSDatePicker instance. 486 | public func disablePastDate() -> SSDatePicker { 487 | var picker = self 488 | picker.configuration.minimumDate = Date() 489 | return picker 490 | } 491 | 492 | /// Disables selection of future dates in the date picker. 493 | /// - Returns: An updated SSDatePicker instance. 494 | public func disableFutureDate() -> SSDatePicker { 495 | var picker = self 496 | picker.configuration.maximumDate = Date() 497 | return picker 498 | } 499 | 500 | /// Sets the calendar used by the date picker. 501 | /// - Parameter calendar: The calendar to be used. 502 | /// - Returns: An updated SSDatePicker instance. 503 | public func calendar(_ calendar: Calendar) -> SSDatePicker { 504 | var picker = self 505 | picker.configuration.calendar = calendar 506 | return picker 507 | } 508 | 509 | /// Sets the callback closure to be executed when a date is selected in the date picker. 510 | /// - Parameter completion: The closure to be executed. 511 | /// - Returns: An updated SSDatePicker instance. 512 | public func onDateSelection(_ completion: @escaping (Date) -> ()) -> SSDatePicker { 513 | let picker = self 514 | picker.datePickerManager.dateSelectionCallback = completion 515 | return picker 516 | } 517 | 518 | /// The current month displayed in the date picker. 519 | /// Setting this property will determine the month whose calendar dates will be displayed when the picker is opened. 520 | public func currentMonth(_ date: Date) -> SSDatePicker { 521 | let picker = self 522 | picker.datePickerManager.currentMonth = date 523 | return picker 524 | } 525 | 526 | /// The selected date in the date picker. Set this property to pre-select a specific date. 527 | public func selectedDate(_ date: Date?) -> SSDatePicker { 528 | let picker = self 529 | picker.datePickerManager.selectedDate = date 530 | return picker 531 | } 532 | 533 | /// Sets the disable dates in the date picker. 534 | /// - Parameter dates: All dates that should be disable. 535 | /// - Returns: An updated SSDatePicker instance. 536 | public func disableDates(_ dates: [Date]) -> SSDatePicker { 537 | let picker = self 538 | picker.datePickerManager.disableDates = dates 539 | return picker 540 | } 541 | 542 | /// Enables date range selection in the date picker. 543 | /// - Returns: An SSDateRangePicker instance for configuring date range selection. 544 | public func enableDateRangeSelection() -> SSDateRangePicker { 545 | var picker = self 546 | picker.configuration.allowRangeSelection = true 547 | return SSDateRangePicker(picker) 548 | } 549 | 550 | /// Enables multiple date selection in the date picker. 551 | /// - Returns: An SSMultiDatePicker instance for configuring multiple date selection. 552 | public func enableMultipleDateSelection() -> SSMultiDatePicker { 553 | var picker = self 554 | picker.configuration.allowMultipleSelection = true 555 | return SSMultiDatePicker(picker) 556 | } 557 | 558 | } 559 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Views/Date picker/SSDateRangePicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SSDateRangePicker.swift 3 | // SSDateTimePicker 4 | // 5 | // Created by Rizwana Desai on 18/12/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct SSDateRangePicker: View { 11 | 12 | // MARK: - Properties 13 | 14 | var datePicker: SSDatePicker 15 | 16 | // MARK: - Initializer 17 | 18 | init(_ datePicker: SSDatePicker) { 19 | self.datePicker = datePicker 20 | } 21 | 22 | //MARK: - Body 23 | 24 | public var body: some View { 25 | datePicker 26 | } 27 | 28 | } 29 | 30 | // MARK: - Modifiers 31 | 32 | extension SSDateRangePicker { 33 | 34 | /// Sets a callback closure to be executed when a date range is selected. 35 | /// 36 | /// - Parameter completion: A closure to be called when a date range is selected. The closure takes a `DateRange` parameter. 37 | /// - Returns: The modified `SSDateRangePicker` instance. 38 | public func onDateRangeSelection(_ completion: @escaping (DateRange) -> ()) -> SSDateRangePicker { 39 | let picker = self 40 | picker.datePicker.datePickerManager.dateRangeSelectionCallback = completion 41 | return picker 42 | } 43 | 44 | /// Sets the selected dates for the date range picker. 45 | /// 46 | /// - Parameter dateRange: An optional `DateRange` representing the selected date range. Pass `nil` to clear the selection. 47 | /// - Returns: The modified `SSDateRangePicker` instance. 48 | public func selectedDateRange(_ dateRange: DateRange?) -> SSDateRangePicker { 49 | let picker = self 50 | picker.datePicker.datePickerManager.startDate = dateRange?.startDate 51 | picker.datePicker.datePickerManager.endDate = dateRange?.endDate 52 | return picker 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Views/Date picker/SSDateView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateView.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 23/11/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SSDateView: View, DatePickerConfigurationDirectAccess { 11 | 12 | // MARK: - Properties 13 | 14 | @EnvironmentObject var calendarManager: SSDatePickerManager 15 | var date: Date 16 | var weekDateInSelectedMonth: Date 17 | 18 | var configuration: SSDatePickerConfiguration { 19 | calendarManager.configuration 20 | } 21 | 22 | private var isDayToday: Bool { 23 | calendar.isDateInToday(date) 24 | } 25 | 26 | private var isDaySelectableAndInRange: Bool { 27 | isDayWithinDateRange && isDayWithinWeekMonthAndYear && canSelectDay 28 | } 29 | 30 | private var isDayWithinDateRange: Bool { 31 | guard let minimumDate, let maximumDate else { return true } 32 | return date >= calendar.startOfDay(for: minimumDate) && date <= maximumDate 33 | } 34 | 35 | private var isDayWithinWeekMonthAndYear: Bool { 36 | calendar.isDate(weekDateInSelectedMonth, equalTo: date, toGranularities: [.month, .year]) 37 | } 38 | 39 | private var canSelectDay: Bool { 40 | calendarManager.canSelectDate(date) 41 | } 42 | 43 | private var isSelected: Bool { 44 | if allowMultipleSelection { 45 | return isSelectedDatesContainSameDate 46 | } else { 47 | return isSelectedDateSame 48 | } 49 | } 50 | 51 | private var numericDay: String { 52 | String(calendar.component(.day, from: date)) 53 | } 54 | 55 | private var foregroundColor: Color { 56 | if isDayToday { 57 | return isSelected && isDaySelectableAndInRange ? todaySelectionFontColor : todayColor 58 | } else if isSelected && isDaySelectableAndInRange { 59 | return selectedDateTextColor 60 | } else { 61 | return dateMonthYearTextColor 62 | } 63 | } 64 | 65 | private var backgroundColor: Color { 66 | if isDayToday && isSelected { 67 | return todaySelectionBgColor 68 | } else if isSelected || isStartDate || isEndDate { 69 | return selectionBackgroundColor 70 | } else if isDayWithinSelectedDateRange { 71 | return selectionBackgroundColor.opacity(0.3) 72 | } else { 73 | return Color.clear 74 | } 75 | } 76 | 77 | private var opacity: Double { 78 | return isDaySelectableAndInRange ? 1 : 0.15 79 | } 80 | 81 | private var isSelectedDateSame: Bool { 82 | guard let selectedDate = calendarManager.selectedDate else { return false } 83 | return calendar.isDate(selectedDate, equalTo: date, toGranularities: [.day, .month, .year]) 84 | } 85 | 86 | private var isSelectedDatesContainSameDate: Bool { 87 | guard let selectedDates = calendarManager.selectedDates else { return false } 88 | for selectedDate in selectedDates { 89 | if calendar.isDate(selectedDate, equalTo: date, toGranularities: [.day, .month, .year]) { 90 | return true 91 | } 92 | } 93 | return false 94 | } 95 | 96 | private var isDayWithinSelectedDateRange: Bool { 97 | guard let startDate = calendarManager.startDate, let endDate = calendarManager.endDate else { return false } 98 | return date >= calendar.startOfDay(for: startDate) && date <= endDate 99 | } 100 | 101 | private var isStartDate: Bool { 102 | guard let startDate = calendarManager.startDate else { return false } 103 | return calendar.isDate(startDate, equalTo: date, toGranularities: [.day, .month, .year]) 104 | } 105 | 106 | private var isEndDate: Bool { 107 | guard let endDate = calendarManager.endDate else { return false } 108 | return calendar.isDate(endDate, equalTo: date, toGranularities: [.day, .month, .year]) 109 | } 110 | 111 | private var corners: UIRectCorner { 112 | isStartDate ? [.topLeft,.bottomLeft] : [.topRight,.bottomRight] 113 | } 114 | 115 | //MARK: - Initializer 116 | 117 | init(date: Date, weekDateInSelectedMonth: Date) { 118 | self.date = date 119 | self.weekDateInSelectedMonth = weekDateInSelectedMonth 120 | } 121 | 122 | //MARK: - Body 123 | 124 | var body: some View { 125 | lblDate 126 | } 127 | 128 | //MARK: - Sub views 129 | 130 | private var lblDate: some View { 131 | Text(numericDay) 132 | .font(dateTextFont) 133 | .foregroundColor(foregroundColor) 134 | .frame(width: SSPickerConstants.widthForDaysOfWeek, height: SSPickerConstants.widthForDaysOfWeek) 135 | .if(!allowRangeSelection && isDaySelectableAndInRange, transform: { view in 136 | view 137 | .background(Circle() 138 | .padding(.leading, SSPickerConstants.selectionCirclePadding) 139 | .padding(.trailing, SSPickerConstants.selectionCirclePadding) 140 | .foregroundColor(backgroundColor)) 141 | }) 142 | .if(allowRangeSelection, transform: { view in 143 | view 144 | .background(backgroundColor) 145 | .cornerRadius((isStartDate || isEndDate) ? SSPickerConstants.dateRangeSelectionCornerRadius : 0, corners: corners) 146 | .padding(.top, SSPickerConstants.rangeSelectionPadding) 147 | .padding(.bottom, SSPickerConstants.rangeSelectionPadding) 148 | }) 149 | .opacity(opacity) 150 | .onTapGesture(perform: updateSelection) 151 | } 152 | 153 | //MARK: - Methods 154 | 155 | private func updateSelection() { 156 | guard isDaySelectableAndInRange else { return } 157 | self.calendarManager.updateDateSelection(date: date) 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Views/Date picker/SSMonthSelectionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MonthSelectionView.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 24/11/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SSMonthSelectionView: View, DatePickerConfigurationDirectAccess { 11 | 12 | // MARK: - Properties 13 | 14 | @EnvironmentObject var calendarManager: SSDatePickerManager 15 | @State var monthList: [String] = DateFormatter.monthsList 16 | private var gridItem: [GridItem] = Array(repeating: .init(.flexible()), count: SSPickerConstants.monthYearGridRows) 17 | 18 | var configuration: SSDatePickerConfiguration { 19 | calendarManager.configuration 20 | } 21 | 22 | //MARK: - Body 23 | 24 | var body: some View { 25 | monthsGridView 26 | .padding(.top, SSPickerConstants.monthYearViewTopSpace) 27 | .padding(.bottom, SSPickerConstants.monthYearViewBottomSpace) 28 | } 29 | 30 | //MARK: - Sub views 31 | 32 | private var monthsGridView: some View { 33 | HStack { 34 | LazyVGrid(columns: gridItem, spacing: SSPickerConstants.monthYearGridSpacing) { 35 | ForEach(monthList, id: \.self) { month in 36 | btnMonth(for: month) 37 | } 38 | } 39 | } 40 | } 41 | 42 | @ViewBuilder 43 | private func btnMonth(for month: String) -> some View { 44 | let monthName = month 45 | let isSelectedMonth = calendarManager.isSelected(monthName) 46 | Button { 47 | updateMonth(month: month) 48 | } label: { 49 | Text(monthName) 50 | .font(isSelectedMonth ? selectedMonthTextFont : monthTextFont) 51 | .foregroundColor(isSelectedMonth ? selectionBackgroundColor : dateMonthYearTextColor) 52 | } 53 | } 54 | 55 | //MARK: - Methods 56 | 57 | private func updateMonth(month: String) { 58 | guard let month = monthList.firstIndex(where: { $0 == month}) else { return } 59 | calendarManager.updateMonthSelection(month: month+1) 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Views/Date picker/SSMultiDatePicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SSMultiDatePicker.swift 3 | // SSDateTimePicker 4 | // 5 | // Created by Rizwana Desai on 18/12/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct SSMultiDatePicker: View { 11 | 12 | // MARK: - Properties 13 | 14 | var datePicker: SSDatePicker 15 | 16 | //MARK: - Initializer 17 | 18 | init(_ datePicker: SSDatePicker) { 19 | self.datePicker = datePicker 20 | } 21 | 22 | //MARK: - Body 23 | 24 | public var body: some View { 25 | datePicker 26 | } 27 | 28 | } 29 | 30 | // MARK: - Modifiers 31 | 32 | extension SSMultiDatePicker { 33 | 34 | /// Sets a callback closure to be executed when multiple dates are selected. 35 | /// 36 | /// - Parameter completion: A closure to be called when multiple dates are selected. The closure takes an array of `Date` as a parameter. 37 | /// - Returns: The modified `SSMultiDatePicker` instance. 38 | public func onMultiDateSelection(_ completion: @escaping ([Date]) -> ()) -> SSMultiDatePicker { 39 | let picker = self 40 | picker.datePicker.datePickerManager.multiDateSelectionCallback = completion 41 | return picker 42 | } 43 | 44 | /// Sets the selected dates for the multi-date picker. 45 | /// 46 | /// - Parameter dates: An optional array of `Date` representing the selected dates. Pass `nil` to clear the selection. 47 | /// - Returns: The modified `SSMultiDatePicker` instance. 48 | public func selectedDates(_ dates: [Date]?) -> SSMultiDatePicker { 49 | let picker = self 50 | picker.datePicker.datePickerManager.selectedDates = dates 51 | return picker 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Views/Date picker/SSWeekDatesView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DatesView.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 23/11/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SSWeekDatesView: View, DatePickerConfigurationDirectAccess { 11 | 12 | // MARK: - Properties 13 | 14 | @EnvironmentObject var calendarManager: SSDatePickerManager 15 | var week: Date 16 | 17 | var configuration: SSDatePickerConfiguration { 18 | calendarManager.configuration 19 | } 20 | 21 | private var dates: [Date] { 22 | guard let weekInterval = calendar.dateInterval(of: .weekOfYear, for: week) else { 23 | return [] 24 | } 25 | return calendar.generateDates( 26 | inside: weekInterval, 27 | matching: .everyDay) 28 | } 29 | 30 | //MARK: - Initializer 31 | init(week: Date) { 32 | self.week = week 33 | } 34 | 35 | //MARK: - Body 36 | 37 | var body: some View { 38 | datesForWeek 39 | } 40 | 41 | //MARK: - Sub views 42 | 43 | private var datesForWeek: some View { 44 | HStack(spacing: SSPickerConstants.horizontalSpacingDates) { 45 | ForEach(dates, id: \.self) { date in 46 | SSDateView(date: date, weekDateInSelectedMonth: week) 47 | } 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Views/Date picker/SSYearSelectionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YearSelectionView.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 24/11/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct SSYearSelectionView: View, DatePickerConfigurationDirectAccess { 11 | 12 | // MARK: - Properties 13 | 14 | @Binding var currentView: SelectionView 15 | @EnvironmentObject var calendarManager: SSDatePickerManager 16 | private var gridItem: [GridItem] = Array(repeating: .init(.flexible()), count: SSPickerConstants.monthYearGridRows) 17 | 18 | var configuration: SSDatePickerConfiguration { 19 | calendarManager.configuration 20 | } 21 | 22 | //MARK: - Initializer 23 | 24 | public init(currentView: Binding) { 25 | self._currentView = currentView 26 | } 27 | 28 | //MARK: - Body 29 | 30 | public var body: some View { 31 | yearsGridView 32 | .padding(.top, SSPickerConstants.monthYearViewTopSpace) 33 | .padding(.bottom, SSPickerConstants.monthYearViewBottomSpace) 34 | .onAppear { 35 | calendarManager.updateYearRange(year: (calendarManager.selectedDate ?? calendarManager.currentMonth).year(calendar)) 36 | } 37 | } 38 | 39 | //MARK: - Sub views 40 | 41 | private var yearsGridView: some View { 42 | HStack { 43 | LazyVGrid(columns: gridItem, spacing: SSPickerConstants.monthYearGridSpacing) { 44 | ForEach(calendarManager.yearRange, id: \.self) { year in 45 | btnYear(for: year) 46 | } 47 | } 48 | } 49 | } 50 | 51 | @ViewBuilder 52 | private func btnYear(for year: Int) -> some View { 53 | let isSelectedYear = calendarManager.isSelected(year) 54 | Button { 55 | updateYearSelection(year: year) 56 | } label: { 57 | Text(String(year)) 58 | .font(isSelectedYear ? selectedYearTextFont : yearTextFont) 59 | .foregroundColor(isSelectedYear ? selectionBackgroundColor : dateMonthYearTextColor) 60 | } 61 | } 62 | 63 | //MARK: - Methods 64 | 65 | private func updateYearSelection(year: Int) { 66 | calendarManager.updateYearSelection(year: year) 67 | currentView = .month 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Views/SSThemeButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThemeButton.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 01/12/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SSThemeButtonModifier: ViewModifier { 11 | 12 | var textColor: Color 13 | var font: Font 14 | 15 | func body(content: Content) -> some View { 16 | content 17 | .font(font) 18 | .padding(SSPickerConstants.paddingTen) 19 | .foregroundColor(textColor) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Views/Time Picker/SSClockPicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SSClockPicker.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 04/12/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SSClockPicker: View, TimePickerConfigurationDirectAccess { 11 | 12 | // MARK: - Properties 13 | 14 | @ObservedObject var timePickerManager: SSTimePickerManager 15 | private var threeSixtyDegree: CGFloat = 360 16 | private var oneEightyDegree: CGFloat = 180 17 | var configuration: SSTimePickerConfiguration { 18 | timePickerManager.configuration 19 | } 20 | 21 | //MARK: - Initializer 22 | 23 | init(timePickerManager: SSTimePickerManager) { 24 | self.timePickerManager = timePickerManager 25 | } 26 | 27 | //MARK: - Body 28 | 29 | var body: some View { 30 | GeometryReader { reader in 31 | ZStack { 32 | let widthGeo = reader.frame(in: .global).width/2 33 | selectionCircle(widthGeo) 34 | clockHand(widthGeo) 35 | clockFace(widthGeo) 36 | } 37 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) 38 | } 39 | .frame(height: SSPickerConstants.clockHeight) 40 | } 41 | 42 | //MARK: - Sub views 43 | 44 | private func selectionCircle(_ widthGeo: CGFloat) -> some View { 45 | Circle() 46 | .fill(clockHandColor) 47 | .frame(width: SSPickerConstants.circleSize, height: SSPickerConstants.circleSize) 48 | .offset(x: widthGeo - SSPickerConstants.clockPadding) 49 | .rotationEffect(.init(degrees: timePickerManager.angle)) 50 | .simultaneousGesture(DragGesture().onChanged(onChanged(value:)).onEnded(onEnd(value:))) 51 | .rotationEffect(.init(degrees: -90)) 52 | } 53 | 54 | private func clockFace(_ widthGeo: CGFloat) -> some View { 55 | ForEach(1...12, id: \.self) { index in 56 | VStack { 57 | let time = timePickerManager.isMinuteClock ? index*5 : index 58 | Text("\(time)") 59 | .font(clockNumberFont) 60 | .fontWeight(.semibold) 61 | .foregroundColor(clockNumberTextColor) 62 | .rotationEffect(.init(degrees: Double(-index)*SSPickerConstants.clockNumberRotationDegree)) 63 | .highPriorityGesture(TapGesture().onEnded { 64 | actionClockNumberSelection(number: index) 65 | }).allowsHitTesting(isClockNumberTapable("\(time)")) 66 | } 67 | .offset(y: -widthGeo+SSPickerConstants.clockPadding) 68 | .rotationEffect(.init(degrees: Double(index)*SSPickerConstants.clockNumberRotationDegree)) // rotating view : 12*30 = 360 69 | } 70 | } 71 | 72 | private func clockHand(_ widthGeo: CGFloat) -> some View { 73 | Circle() 74 | .fill(clockHandColor) 75 | .frame(width: SSPickerConstants.paddingTen, height: SSPickerConstants.paddingTen) 76 | .overlay ( 77 | Rectangle() 78 | .fill(clockHandColor) 79 | .frame(width: 2, height: clockHandHeight(widthGeo)) 80 | , alignment: .bottom 81 | ) 82 | .rotationEffect(.init(degrees: timePickerManager.angle)) 83 | } 84 | 85 | //MARK: - Methods 86 | 87 | private func clockHandHeight(_ width: CGFloat) -> CGFloat { 88 | width-SSPickerConstants.clockPadding 89 | } 90 | 91 | // To disable clock number tap gesture when it is already selected to priotize grag gesture 92 | private func isClockNumberTapable(_ time: String) -> Bool { 93 | if timePickerManager.isMinuteClock { 94 | let minute = time == "60" ? "00" : time 95 | return timePickerManager.minutesSelected != minute 96 | } else { 97 | return timePickerManager.hourSelected != time 98 | } 99 | } 100 | 101 | // To update selected hour/miute when user tap on any of the number on clock 102 | private func actionClockNumberSelection(number: Int) { 103 | if timePickerManager.isMinuteClock { 104 | let minute = number * 5 105 | timePickerManager.minutesSelected = (minute == 60 ? 00 : minute).formattedTime 106 | timePickerManager.updateCurrentMinuteAngle() 107 | } else { 108 | timePickerManager.hourSelected = number.formattedTime 109 | timePickerManager.updateCurrentHourAngle() 110 | } 111 | } 112 | 113 | } 114 | 115 | //MARK: - Drag gesture methods 116 | 117 | extension SSClockPicker { 118 | 119 | private func onChanged(value: DragGesture.Value) { 120 | // getting angle 121 | let vector = CGVector(dx: value.location.x 122 | , dy: value.location.y) 123 | let radians = atan2(vector.dy, vector.dx) 124 | var angle = radians * oneEightyDegree / .pi 125 | if angle < 0 { 126 | angle = threeSixtyDegree + angle 127 | } 128 | timePickerManager.angle = angle 129 | if !timePickerManager.isMinuteClock { 130 | // updating angle for hour if the angle is in between hours 131 | let roundValue = Int(SSPickerConstants.clockNumberRotationDegree) * Int(round(angle/SSPickerConstants.clockNumberRotationDegree)) 132 | timePickerManager.angle = Double(roundValue) 133 | updateHour() 134 | } else { 135 | // updating minutes 136 | let progress = angle / threeSixtyDegree 137 | timePickerManager.minutesSelected = Int(progress*60).formattedTime 138 | } 139 | } 140 | 141 | private func onEnd(value: DragGesture.Value) { 142 | if !timePickerManager.isMinuteClock { 143 | updateHour() 144 | // updating picker to minutes 145 | withAnimation { 146 | timePickerManager.angle = Double((Int(timePickerManager.minutesSelected) ?? 00) * Int(SSPickerConstants.minuteRotationDegree)) 147 | timePickerManager.isMinuteClock = true 148 | } 149 | } 150 | } 151 | 152 | // updating hour value 153 | private func updateHour() { 154 | let hour = Int(timePickerManager.angle / SSPickerConstants.clockNumberRotationDegree) 155 | timePickerManager.hourSelected = (hour == 0 ? 12 : hour).formattedTime 156 | } 157 | 158 | } 159 | 160 | 161 | struct SSClockPicker_Previews: PreviewProvider { 162 | static var previews: some View { 163 | SSClockPicker(timePickerManager: SSTimePickerManager(configuration: SSTimePickerConfiguration())) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Views/Time Picker/SSTimePicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SSTimePicker.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 04/12/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct SSTimePicker: View, TimePickerConfigurationDirectAccess { 11 | 12 | // MARK: - Properties 13 | 14 | @ObservedObject var timePickerManager: SSTimePickerManager = SSTimePickerManager() 15 | @Binding var showTimePicker: Bool 16 | @State var isInEditMode: Bool = false 17 | 18 | var configuration: SSTimePickerConfiguration { 19 | get { 20 | timePickerManager.configuration 21 | } set { 22 | timePickerManager.configuration = newValue 23 | } 24 | } 25 | 26 | //MARK: - Initializer 27 | 28 | public init(showTimePicker: Binding) { 29 | self._showTimePicker = showTimePicker 30 | } 31 | 32 | //MARK: - Body 33 | 34 | public var body: some View { 35 | ZStack(alignment: .center) { 36 | if showTimePicker { 37 | popupOverlayColor 38 | .ignoresSafeArea() 39 | .onTapGesture { 40 | actionCancel() 41 | } 42 | timePickerSubview 43 | .background(pickerBackgroundColor) 44 | .cornerRadius(pickerViewRadius) 45 | .padding(.leading, SSPickerConstants.pickerLeadingTrailing) 46 | .padding(.trailing, SSPickerConstants.pickerLeadingTrailing) 47 | .compositingGroup() 48 | } 49 | } 50 | .onChange(of: showTimePicker) { newValue in 51 | if showTimePicker { 52 | timePickerManager.setUpTimeAndAngle() 53 | } 54 | } 55 | } 56 | 57 | //MARK: - Sub views 58 | 59 | private var timePickerSubview: some View { 60 | VStack(alignment: .leading, spacing: SSPickerConstants.verticleSpacingTen) { 61 | timePickerHeader 62 | SSClockPicker(timePickerManager: timePickerManager) 63 | bottomButtons 64 | } 65 | .padding(SSPickerConstants.pickerViewInnerPadding) 66 | } 67 | 68 | private var timePickerHeader: some View { 69 | VStack(alignment: .leading, spacing: SSPickerConstants.verticleSpacingTen) { 70 | lblSelectedDate 71 | Divider() 72 | } 73 | } 74 | 75 | private var lblSelectedDate: some View { 76 | VStack(alignment: .leading, spacing: SSPickerConstants.verticleSpacingTen) { 77 | Text(SSLocalizedString.selectTime) 78 | .font(headerTitleFont) 79 | .foregroundColor(headerTitleColor) 80 | textFieldHourMinutes 81 | } 82 | .padding(SSPickerConstants.paddingTen) 83 | } 84 | 85 | private var textFieldHourMinutes: some View { 86 | HStack(spacing: 4) { 87 | SSTimeTextField(time: $timePickerManager.hourSelected, configuration: configuration, isHourField: true, isInEditMode: $isInEditMode) 88 | .simultaneousGesture(TapGesture().onEnded { 89 | timePickerManager.actionShowHourClock() 90 | }) 91 | Text(":") 92 | .font(timeLabelFont) 93 | .foregroundColor(timeLabelForegroundColor) 94 | SSTimeTextField(time: $timePickerManager.minutesSelected, configuration: configuration, isHourField: false, isInEditMode: $isInEditMode) 95 | .simultaneousGesture(TapGesture().onEnded { 96 | timePickerManager.actionShowMinuteClock() 97 | }) 98 | amPMView 99 | .padding(2) 100 | Spacer() 101 | } 102 | .toolbar { 103 | ToolbarItem(placement: .keyboard) { 104 | HStack { 105 | Spacer() 106 | Button(SSLocalizedString.done) { 107 | actionDone() 108 | } 109 | } 110 | } 111 | } 112 | } 113 | 114 | private var amPMView: some View { 115 | VStack { 116 | btnAM 117 | btnPM 118 | } 119 | } 120 | 121 | private var btnAM: some View { 122 | Button { 123 | withAnimation { 124 | timePickerManager.selectedTimeFromat = .am 125 | } 126 | } label: { 127 | labelTimeFormat(SSLocalizedString.am, isSelected: timePickerManager.selectedTimeFromat == .am) 128 | } 129 | } 130 | 131 | private var btnPM: some View { 132 | Button { 133 | withAnimation { 134 | timePickerManager.selectedTimeFromat = .pm 135 | } 136 | } label: { 137 | labelTimeFormat(SSLocalizedString.pm, isSelected: timePickerManager.selectedTimeFromat == .pm) 138 | } 139 | } 140 | 141 | private var bottomButtons: some View { 142 | HStack(spacing: SSPickerConstants.bottomButtonHSpacing) { 143 | Spacer() 144 | btnCancel 145 | btnOk 146 | } 147 | } 148 | 149 | private var btnCancel: some View { 150 | Button { 151 | withAnimation { 152 | self.actionCancel() 153 | } 154 | } label: { 155 | Text(SSLocalizedString.cancel) 156 | .themeButton(buttonsForegroundColor, buttonFont) 157 | } 158 | } 159 | 160 | private var btnOk: some View { 161 | Button { 162 | withAnimation { 163 | self.actionOk() 164 | } 165 | } label: { 166 | Text(SSLocalizedString.ok) 167 | .themeButton(buttonsForegroundColor, buttonFont) 168 | } 169 | } 170 | 171 | private func labelTimeFormat(_ format: String, isSelected: Bool) -> some View { 172 | Text(format) 173 | .font(isSelected ? selectedTimeFormatFont : timeFormatFont) 174 | .foregroundColor(isSelected ? timeFormatSelectionColor : timeFormatColor) 175 | .transition(.scale) 176 | .animation(.easeIn, value: isSelected) 177 | } 178 | 179 | } 180 | 181 | extension SSTimePicker { 182 | 183 | //MARK: - Methods 184 | 185 | func actionCancel() { 186 | if timePickerManager.isMinuteClock { 187 | timePickerManager.isMinuteClock = false 188 | } else { 189 | showTimePicker.toggle() 190 | } 191 | } 192 | 193 | func actionOk() { 194 | timePickerManager.updateSelectedTime() 195 | showTimePicker.toggle() 196 | } 197 | 198 | func actionDone() { 199 | isInEditMode = false 200 | if timePickerManager.isMinuteClock { 201 | timePickerManager.updateCurrentMinuteAngle() 202 | } else { 203 | timePickerManager.updateCurrentHourAngle() 204 | } 205 | } 206 | 207 | } 208 | 209 | // MARK: - Modifiers 210 | 211 | extension SSTimePicker { 212 | 213 | /// Set the selected time for the time picker. 214 | /// - Parameter time: The selected time. 215 | /// - Returns: The modified SSTimePicker instance. 216 | public func selectedTime(_ time: Time?) -> SSTimePicker { 217 | let picker = self 218 | picker.timePickerManager.selectedTime = time 219 | return picker 220 | } 221 | 222 | /// Apply a custom theme to the time picker. 223 | /// - Parameters: 224 | /// - pickerBackgroundColor: Background color of the time picker. 225 | /// - primaryColor: Primary color used for various elements. 226 | /// - timeLabelBackgroundColor: Background color for the time label. 227 | /// - Returns: The modified SSTimePicker instance. 228 | public func themeColor(pickerBackgroundColor: Color, primaryColor: Color, timeLabelBackgroundColor: Color) -> SSTimePicker { 229 | var picker = self 230 | picker.configuration.timeLabelStyle.color = primaryColor 231 | picker.configuration.selectedTimeFormatStyle.color = primaryColor 232 | picker.configuration.buttonStyle.color = primaryColor 233 | picker.configuration.clockHandColor = primaryColor 234 | picker.configuration.pickerBackgroundColor = pickerBackgroundColor 235 | picker.configuration.timeLabelBackgroundColor = timeLabelBackgroundColor 236 | return picker 237 | } 238 | 239 | /// Customize the style of the header title. 240 | /// - Parameters: 241 | /// - color: Text color of the header title. 242 | /// - font: Font of the header title. 243 | /// - Returns: The modified SSTimePicker instance. 244 | public func headerTitleStyle(color: Color? = nil, font: Font? = nil) -> SSTimePicker { 245 | var picker = self 246 | color.map { picker.configuration.headerTitleStyle.color = $0 } 247 | font.map { picker.configuration.headerTitleStyle.font = $0 } 248 | return picker 249 | } 250 | 251 | /// Customize the style of the time label. 252 | /// - Parameters: 253 | /// - color: Text color of the time label. 254 | /// - font: Font of the time label. 255 | /// - Returns: The modified SSTimePicker instance. 256 | public func timeLabelStyle(color: Color? = nil, font: Font? = nil) -> SSTimePicker { 257 | var picker = self 258 | color.map { picker.configuration.timeLabelStyle.color = $0 } 259 | font.map { picker.configuration.timeLabelStyle.font = $0 } 260 | return picker 261 | } 262 | 263 | /// Customize the style of the time format(AM/PM). 264 | /// - Parameters: 265 | /// - color: Text color of the time format. 266 | /// - font: Font of the time format. 267 | /// - Returns: The modified SSTimePicker instance. 268 | public func timeFormatStyle(color: Color? = nil, font: Font? = nil) -> SSTimePicker { 269 | var picker = self 270 | color.map { picker.configuration.timeFormatStyle.color = $0 } 271 | font.map { picker.configuration.timeFormatStyle.font = $0 } 272 | return picker 273 | } 274 | 275 | /// Customize the style of the clock numbers. 276 | /// - Parameters: 277 | /// - color: Text color of the clock numbers. 278 | /// - font: Font of the clock numbers. 279 | /// - Returns: The modified SSTimePicker instance. 280 | public func clockNumberStyle(color: Color? = nil, font: Font? = nil) -> SSTimePicker { 281 | var picker = self 282 | color.map { picker.configuration.clockNumberStyle.color = $0 } 283 | font.map { picker.configuration.clockNumberStyle.font = $0 } 284 | return picker 285 | } 286 | 287 | /// Customize the style of the buttons. 288 | /// - Parameters: 289 | /// - color: Text color of the buttons. 290 | /// - font: Font of the buttons. 291 | /// - Returns: The modified SSTimePicker instance. 292 | public func buttonStyle(color: Color? = nil, font: Font? = nil) -> SSTimePicker { 293 | var picker = self 294 | color.map { picker.configuration.buttonStyle.color = $0 } 295 | font.map { picker.configuration.buttonStyle.font = $0 } 296 | return picker 297 | } 298 | 299 | /// Customize the style of the selected time format. 300 | /// - Parameters: 301 | /// - color: Text color of the selected time format. 302 | /// - font: Font of the selected time format. 303 | /// - Returns: The modified SSTimePicker instance. 304 | public func selectedTimeFormatStyle(color: Color? = nil, font: Font? = nil) -> SSTimePicker { 305 | var picker = self 306 | color.map { picker.configuration.selectedTimeFormatStyle.color = $0 } 307 | font.map { picker.configuration.selectedTimeFormatStyle.font = $0 } 308 | return picker 309 | } 310 | 311 | /// Set a callback closure to be executed when a time is selected. 312 | /// - Parameter completion: A closure to be called with the selected time. 313 | /// - Returns: The modified SSTimePicker instance. 314 | public func onTimeSelection(_ completion: @escaping (Date) -> ()) -> SSTimePicker { 315 | let picker = self 316 | picker.timePickerManager.timeSelectionCallback = completion 317 | return picker 318 | } 319 | 320 | } 321 | 322 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/Views/Time Picker/SSTimeTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SSTimeTextField.swift 3 | // DateTimePicker 4 | // 5 | // Created by Rizwana Desai on 04/12/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SSTimeTextField: View, TimePickerConfigurationDirectAccess { 11 | 12 | // MARK: - Properties 13 | 14 | @Binding var time: String 15 | var configuration: SSTimePickerConfiguration 16 | @State var isHourField: Bool 17 | @Binding var isInEditMode: Bool 18 | private let charLimit = 2 19 | @FocusState private var isFocused: Bool 20 | 21 | //MARK: - Body 22 | 23 | var body: some View { 24 | Group { 25 | if isInEditMode { 26 | txtField 27 | } else { 28 | timeLabel 29 | .gesture(TapGesture(count: 2).onEnded { 30 | isInEditMode = true 31 | }) 32 | } 33 | } 34 | .onChange(of: isInEditMode) { newValue in 35 | isFocused = newValue 36 | } 37 | } 38 | 39 | //MARK: - Sub views 40 | 41 | private var timeLabel: some View { 42 | Text(time.count == 1 ? "0\(time)" : time) 43 | .multilineTextAlignment(.center) 44 | .font(timeLabelFont) 45 | .padding(SSPickerConstants.timeFieldPadding) 46 | .foregroundColor(timeLabelForegroundColor) 47 | .background(timeLabelBackgroundColor) 48 | .fixedSize() 49 | .cornerRadius(SSPickerConstants.timeFieldCornerRadius) 50 | } 51 | 52 | private var txtField: some View { 53 | TextField(isHourField ? SSLocalizedString.hour : SSLocalizedString.minute, text: $time) 54 | .focused($isFocused) 55 | .keyboardType(.numberPad) 56 | .multilineTextAlignment(.center) 57 | .font(timeLabelFont) 58 | .padding(SSPickerConstants.timeFieldPadding) 59 | .foregroundColor(timeLabelForegroundColor) 60 | .background(timeLabelBackgroundColor) 61 | .fixedSize() 62 | .cornerRadius(SSPickerConstants.timeFieldCornerRadius) 63 | .onChange(of: time) { [time] newTime in 64 | if Int(newTime) ?? 00 > (isHourField ? 12 : 59) { 65 | self.time = time 66 | } 67 | if self.time.count > charLimit { 68 | self.time = String(time.prefix(charLimit)) 69 | self.time = String(time.prefix(charLimit)) 70 | } 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /Sources/SSDateTimePicker/SSDateTimePicker/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | DateTimePicker 4 | 5 | Created by Rizwana Desai on 12/12/23. 6 | 7 | */ 8 | 9 | 10 | "Ok" = "Ok"; 11 | "Cancel" = "Cancel"; 12 | "Select Date" = "Select Date"; 13 | "Select Time" = "Select Time"; 14 | "AM" = "AM"; 15 | "PM" = "PM"; 16 | --------------------------------------------------------------------------------