├── .gitignore ├── Assets └── demo.gif ├── README.md ├── TimerApp.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ ├── hexalitics.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ │ └── sadanand.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ ├── hexalitics.xcuserdatad │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ │ └── xcschememanagement.plist │ └── sadanand.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist └── TimerApp ├── Assets.xcassets ├── AccentColor.colorset │ └── Contents.json ├── AppIcon.appiconset │ ├── Contents.json │ └── logo.png └── Contents.json ├── ContentView.swift ├── Extension+Double.swift ├── Preview Content └── Preview Assets.xcassets │ └── Contents.json ├── ProgressBarView.swift ├── TimerAppApp.swift └── TimerViewModel.swift /.gitignore: -------------------------------------------------------------------------------- 1 | ## Build generated 2 | build/ 3 | DerivedData/ 4 | 5 | ## Various settings 6 | *.pbxuser 7 | !default.pbxuser 8 | !default.perspectivev3 9 | xcuserdata/ 10 | 11 | ## Other 12 | *.moved-aside 13 | *.xccheckout 14 | *.xcscmblueprint 15 | 16 | ## Obj-C/Swift specific 17 | *.hmap 18 | *.ipa 19 | *.dSYM.zip 20 | *.dSYM 21 | *.DS_Store 22 | 23 | # fastlane 24 | # 25 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 26 | # screenshots whenever they are needed. 27 | # For more information about the recommended setup visit: 28 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 29 | 30 | fastlane/report.xml 31 | fastlane/Preview.html 32 | fastlane/screenshots 33 | fastlane/test_output 34 | 35 | # Code Injection 36 | # 37 | # After new code Injection tools there's a generated folder /iOSInjectionProject 38 | # https://github.com/johnno1962/injectionforxcode 39 | 40 | iOSInjectionProject/ 41 | 42 | ### Objective-C Patch ### 43 | 44 | ### Objective-C.Carthage Stack ### 45 | # Carthage 46 | # 47 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 48 | # Carthage/Checkouts 49 | 50 | Carthage/Build 51 | 52 | ### Objective-C.CocoaPods Stack ### 53 | ## CocoaPods GitIgnore Template 54 | 55 | # CocoaPods - Only use to conserve bandwidth / Save time on Pushing 56 | # - Also handy if you have a lage number of dependant pods 57 | # 58 | - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGONRE THE LOCK 59 | Pods/ 60 | 61 | ### Swift ### 62 | # Xcode 63 | # 64 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 65 | 66 | ## Build generated 67 | 68 | ## Various settings 69 | 70 | ## Other 71 | 72 | ## Obj-C/Swift specific 73 | 74 | ## Playgrounds 75 | timeline.xctimeline 76 | playground.xcworkspace 77 | 78 | # Swift Package Manager 79 | # 80 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 81 | # Packages/ 82 | # Package.pins 83 | .build/ 84 | 85 | # CocoaPods - Refactored to standalone file 86 | 87 | # Carthage - Refactored to standalone file 88 | 89 | # fastlane 90 | # 91 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 92 | # screenshots whenever they are needed. 93 | # For more information about the recommended setup visit: 94 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 95 | 96 | 97 | ### Swift.Carthage Stack ### 98 | # Carthage 99 | # 100 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 101 | # Carthage/Checkouts 102 | 103 | 104 | ### Swift.CocoaPods Stack ### 105 | ## CocoaPods GitIgnore Template 106 | 107 | # CocoaPods - Only use to conserve bandwidth / Save time on Pushing 108 | # - Also handy if you have a lage number of dependant pods 109 | # - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGONRE THE LOCK FILE 110 | 111 | # End of https://www.gitignore.io/api/swift,objective-c 112 | -------------------------------------------------------------------------------- /Assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadanand-lowanshi/SwiftUI-TimerApp/051fc9cadb1f875386c43ef20a43fc3037c9c308/Assets/demo.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ⏱️ Timer SwiftUI Example 2 | [![Timer](https://img.shields.io/badge/Timer-4BC51D.svg?style=flat)](https://github.com/luckyhexalitics/TimerApp) 3 | [![Swift](https://img.shields.io/badge/SwiftUI-4_5-orange?style=flat)](https://img.shields.io/badge/SwiftUI-4_5-Orange?style=flat) 4 | [![Platforms](https://img.shields.io/badge/Platforms-macOS_iOS_tvOS_iPad_visionOS-yellowgreen?style=flat)](https://img.shields.io/badge/Platforms-macOS_iOS_tvOS_iPad_visionOS-yellowgreen?style=flat) 5 | 6 | ## 📎 About 7 | 8 | A simple timer app made in `SwiftUI`. Include Layout, UI and Animations. 9 | See projects files in `TimerApp` folders. Open an issue, if you need any improvements or face any issues. 10 | Don't forget to `put star ★` and follow me on GitHub if you like it: 11 | 12 | ## Requirements 13 | - iOS 15.0+ 14 | - Xcode 13.0+ 15 | - SwiftUI 4+ 16 | 17 | ## App Version 18 | - Version:- 1.0.0 (1) 19 | 20 | 21 | ## Preview 22 | ![ezgif com-resize](https://github.com/sadanand-lowanshi/SwiftUI-TimerApp/blob/main/Assets/demo.gif) 23 | 24 | 25 | ### 👨🏻‍💻 Authors 26 | [Sadanand Lowanshi](https://github.com/sadanand-lowanshi) 27 | 28 | ## Other Projects 29 | 30 | I love being helpful, here I have provided a repositorie that I keep up to date [ Apple Map Sample](https://github.com/hexalitics/AppleMapsSample) 31 | 32 | 33 | ## ✉️ Contacts 34 | 35 | email: sadanandlowanshi@gmail.com 36 | 37 | ## Help 38 | As always, if you have any questions about Timer App, we are available 24/7 to help. 39 | -------------------------------------------------------------------------------- /TimerApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | BF086CDF2AEA45B500EFBE1B /* Extension+Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF086CDE2AEA45B500EFBE1B /* Extension+Double.swift */; }; 11 | BF086CE12AEA45F500EFBE1B /* ProgressBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF086CE02AEA45F500EFBE1B /* ProgressBarView.swift */; }; 12 | BFCF4C002AE94AD700BA8235 /* TimerAppApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFCF4BFF2AE94AD700BA8235 /* TimerAppApp.swift */; }; 13 | BFCF4C022AE94AD700BA8235 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFCF4C012AE94AD700BA8235 /* ContentView.swift */; }; 14 | BFCF4C042AE94ADA00BA8235 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BFCF4C032AE94ADA00BA8235 /* Assets.xcassets */; }; 15 | BFCF4C072AE94ADA00BA8235 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BFCF4C062AE94ADA00BA8235 /* Preview Assets.xcassets */; }; 16 | BFCF4C0E2AE9562F00BA8235 /* TimerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFCF4C0D2AE9562F00BA8235 /* TimerViewModel.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | BF086CDE2AEA45B500EFBE1B /* Extension+Double.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+Double.swift"; sourceTree = ""; }; 21 | BF086CE02AEA45F500EFBE1B /* ProgressBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBarView.swift; sourceTree = ""; }; 22 | BFCF4BFC2AE94AD700BA8235 /* TimerApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TimerApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | BFCF4BFF2AE94AD700BA8235 /* TimerAppApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerAppApp.swift; sourceTree = ""; }; 24 | BFCF4C012AE94AD700BA8235 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 25 | BFCF4C032AE94ADA00BA8235 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26 | BFCF4C062AE94ADA00BA8235 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 27 | BFCF4C0D2AE9562F00BA8235 /* TimerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerViewModel.swift; sourceTree = ""; }; 28 | /* End PBXFileReference section */ 29 | 30 | /* Begin PBXFrameworksBuildPhase section */ 31 | BFCF4BF92AE94AD700BA8235 /* Frameworks */ = { 32 | isa = PBXFrameworksBuildPhase; 33 | buildActionMask = 2147483647; 34 | files = ( 35 | ); 36 | runOnlyForDeploymentPostprocessing = 0; 37 | }; 38 | /* End PBXFrameworksBuildPhase section */ 39 | 40 | /* Begin PBXGroup section */ 41 | BFCF4BF32AE94AD700BA8235 = { 42 | isa = PBXGroup; 43 | children = ( 44 | BFCF4BFE2AE94AD700BA8235 /* TimerApp */, 45 | BFCF4BFD2AE94AD700BA8235 /* Products */, 46 | ); 47 | sourceTree = ""; 48 | }; 49 | BFCF4BFD2AE94AD700BA8235 /* Products */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | BFCF4BFC2AE94AD700BA8235 /* TimerApp.app */, 53 | ); 54 | name = Products; 55 | sourceTree = ""; 56 | }; 57 | BFCF4BFE2AE94AD700BA8235 /* TimerApp */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | BFCF4BFF2AE94AD700BA8235 /* TimerAppApp.swift */, 61 | BFCF4C012AE94AD700BA8235 /* ContentView.swift */, 62 | BFCF4C0D2AE9562F00BA8235 /* TimerViewModel.swift */, 63 | BF086CE02AEA45F500EFBE1B /* ProgressBarView.swift */, 64 | BF086CDE2AEA45B500EFBE1B /* Extension+Double.swift */, 65 | BFCF4C032AE94ADA00BA8235 /* Assets.xcassets */, 66 | BFCF4C052AE94ADA00BA8235 /* Preview Content */, 67 | ); 68 | path = TimerApp; 69 | sourceTree = ""; 70 | }; 71 | BFCF4C052AE94ADA00BA8235 /* Preview Content */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | BFCF4C062AE94ADA00BA8235 /* Preview Assets.xcassets */, 75 | ); 76 | path = "Preview Content"; 77 | sourceTree = ""; 78 | }; 79 | /* End PBXGroup section */ 80 | 81 | /* Begin PBXNativeTarget section */ 82 | BFCF4BFB2AE94AD700BA8235 /* TimerApp */ = { 83 | isa = PBXNativeTarget; 84 | buildConfigurationList = BFCF4C0A2AE94ADA00BA8235 /* Build configuration list for PBXNativeTarget "TimerApp" */; 85 | buildPhases = ( 86 | BFCF4BF82AE94AD700BA8235 /* Sources */, 87 | BFCF4BF92AE94AD700BA8235 /* Frameworks */, 88 | BFCF4BFA2AE94AD700BA8235 /* Resources */, 89 | ); 90 | buildRules = ( 91 | ); 92 | dependencies = ( 93 | ); 94 | name = TimerApp; 95 | productName = TimerApp; 96 | productReference = BFCF4BFC2AE94AD700BA8235 /* TimerApp.app */; 97 | productType = "com.apple.product-type.application"; 98 | }; 99 | /* End PBXNativeTarget section */ 100 | 101 | /* Begin PBXProject section */ 102 | BFCF4BF42AE94AD700BA8235 /* Project object */ = { 103 | isa = PBXProject; 104 | attributes = { 105 | BuildIndependentTargetsInParallel = 1; 106 | LastSwiftUpdateCheck = 1410; 107 | LastUpgradeCheck = 1410; 108 | TargetAttributes = { 109 | BFCF4BFB2AE94AD700BA8235 = { 110 | CreatedOnToolsVersion = 14.1; 111 | }; 112 | }; 113 | }; 114 | buildConfigurationList = BFCF4BF72AE94AD700BA8235 /* Build configuration list for PBXProject "TimerApp" */; 115 | compatibilityVersion = "Xcode 14.0"; 116 | developmentRegion = en; 117 | hasScannedForEncodings = 0; 118 | knownRegions = ( 119 | en, 120 | Base, 121 | ); 122 | mainGroup = BFCF4BF32AE94AD700BA8235; 123 | productRefGroup = BFCF4BFD2AE94AD700BA8235 /* Products */; 124 | projectDirPath = ""; 125 | projectRoot = ""; 126 | targets = ( 127 | BFCF4BFB2AE94AD700BA8235 /* TimerApp */, 128 | ); 129 | }; 130 | /* End PBXProject section */ 131 | 132 | /* Begin PBXResourcesBuildPhase section */ 133 | BFCF4BFA2AE94AD700BA8235 /* Resources */ = { 134 | isa = PBXResourcesBuildPhase; 135 | buildActionMask = 2147483647; 136 | files = ( 137 | BFCF4C072AE94ADA00BA8235 /* Preview Assets.xcassets in Resources */, 138 | BFCF4C042AE94ADA00BA8235 /* Assets.xcassets in Resources */, 139 | ); 140 | runOnlyForDeploymentPostprocessing = 0; 141 | }; 142 | /* End PBXResourcesBuildPhase section */ 143 | 144 | /* Begin PBXSourcesBuildPhase section */ 145 | BFCF4BF82AE94AD700BA8235 /* Sources */ = { 146 | isa = PBXSourcesBuildPhase; 147 | buildActionMask = 2147483647; 148 | files = ( 149 | BF086CDF2AEA45B500EFBE1B /* Extension+Double.swift in Sources */, 150 | BFCF4C022AE94AD700BA8235 /* ContentView.swift in Sources */, 151 | BFCF4C0E2AE9562F00BA8235 /* TimerViewModel.swift in Sources */, 152 | BFCF4C002AE94AD700BA8235 /* TimerAppApp.swift in Sources */, 153 | BF086CE12AEA45F500EFBE1B /* ProgressBarView.swift in Sources */, 154 | ); 155 | runOnlyForDeploymentPostprocessing = 0; 156 | }; 157 | /* End PBXSourcesBuildPhase section */ 158 | 159 | /* Begin XCBuildConfiguration section */ 160 | BFCF4C082AE94ADA00BA8235 /* Debug */ = { 161 | isa = XCBuildConfiguration; 162 | buildSettings = { 163 | ALWAYS_SEARCH_USER_PATHS = NO; 164 | CLANG_ANALYZER_NONNULL = YES; 165 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 166 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 167 | CLANG_ENABLE_MODULES = YES; 168 | CLANG_ENABLE_OBJC_ARC = YES; 169 | CLANG_ENABLE_OBJC_WEAK = YES; 170 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 171 | CLANG_WARN_BOOL_CONVERSION = YES; 172 | CLANG_WARN_COMMA = YES; 173 | CLANG_WARN_CONSTANT_CONVERSION = YES; 174 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 175 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 176 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 177 | CLANG_WARN_EMPTY_BODY = YES; 178 | CLANG_WARN_ENUM_CONVERSION = YES; 179 | CLANG_WARN_INFINITE_RECURSION = YES; 180 | CLANG_WARN_INT_CONVERSION = YES; 181 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 182 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 183 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 184 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 185 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 186 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 187 | CLANG_WARN_STRICT_PROTOTYPES = YES; 188 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 189 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 190 | CLANG_WARN_UNREACHABLE_CODE = YES; 191 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 192 | COPY_PHASE_STRIP = NO; 193 | DEBUG_INFORMATION_FORMAT = dwarf; 194 | ENABLE_STRICT_OBJC_MSGSEND = YES; 195 | ENABLE_TESTABILITY = YES; 196 | GCC_C_LANGUAGE_STANDARD = gnu11; 197 | GCC_DYNAMIC_NO_PIC = NO; 198 | GCC_NO_COMMON_BLOCKS = YES; 199 | GCC_OPTIMIZATION_LEVEL = 0; 200 | GCC_PREPROCESSOR_DEFINITIONS = ( 201 | "DEBUG=1", 202 | "$(inherited)", 203 | ); 204 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 205 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 206 | GCC_WARN_UNDECLARED_SELECTOR = YES; 207 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 208 | GCC_WARN_UNUSED_FUNCTION = YES; 209 | GCC_WARN_UNUSED_VARIABLE = YES; 210 | IPHONEOS_DEPLOYMENT_TARGET = 16.1; 211 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 212 | MTL_FAST_MATH = YES; 213 | ONLY_ACTIVE_ARCH = YES; 214 | SDKROOT = iphoneos; 215 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 216 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 217 | }; 218 | name = Debug; 219 | }; 220 | BFCF4C092AE94ADA00BA8235 /* Release */ = { 221 | isa = XCBuildConfiguration; 222 | buildSettings = { 223 | ALWAYS_SEARCH_USER_PATHS = NO; 224 | CLANG_ANALYZER_NONNULL = YES; 225 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 226 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 227 | CLANG_ENABLE_MODULES = YES; 228 | CLANG_ENABLE_OBJC_ARC = YES; 229 | CLANG_ENABLE_OBJC_WEAK = YES; 230 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 231 | CLANG_WARN_BOOL_CONVERSION = YES; 232 | CLANG_WARN_COMMA = YES; 233 | CLANG_WARN_CONSTANT_CONVERSION = YES; 234 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 235 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 236 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 237 | CLANG_WARN_EMPTY_BODY = YES; 238 | CLANG_WARN_ENUM_CONVERSION = YES; 239 | CLANG_WARN_INFINITE_RECURSION = YES; 240 | CLANG_WARN_INT_CONVERSION = YES; 241 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 242 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 243 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 244 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 245 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 246 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 247 | CLANG_WARN_STRICT_PROTOTYPES = YES; 248 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 249 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 250 | CLANG_WARN_UNREACHABLE_CODE = YES; 251 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 252 | COPY_PHASE_STRIP = NO; 253 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 254 | ENABLE_NS_ASSERTIONS = NO; 255 | ENABLE_STRICT_OBJC_MSGSEND = YES; 256 | GCC_C_LANGUAGE_STANDARD = gnu11; 257 | GCC_NO_COMMON_BLOCKS = YES; 258 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 259 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 260 | GCC_WARN_UNDECLARED_SELECTOR = YES; 261 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 262 | GCC_WARN_UNUSED_FUNCTION = YES; 263 | GCC_WARN_UNUSED_VARIABLE = YES; 264 | IPHONEOS_DEPLOYMENT_TARGET = 16.1; 265 | MTL_ENABLE_DEBUG_INFO = NO; 266 | MTL_FAST_MATH = YES; 267 | SDKROOT = iphoneos; 268 | SWIFT_COMPILATION_MODE = wholemodule; 269 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 270 | VALIDATE_PRODUCT = YES; 271 | }; 272 | name = Release; 273 | }; 274 | BFCF4C0B2AE94ADA00BA8235 /* Debug */ = { 275 | isa = XCBuildConfiguration; 276 | buildSettings = { 277 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 278 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 279 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 280 | CODE_SIGN_STYLE = Manual; 281 | CURRENT_PROJECT_VERSION = 1; 282 | DEVELOPMENT_ASSET_PATHS = "\"TimerApp/Preview Content\""; 283 | DEVELOPMENT_TEAM = ""; 284 | "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8SS5HN6D42; 285 | ENABLE_PREVIEWS = YES; 286 | GENERATE_INFOPLIST_FILE = YES; 287 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 288 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 289 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 290 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 291 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 292 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 293 | LD_RUNPATH_SEARCH_PATHS = ( 294 | "$(inherited)", 295 | "@executable_path/Frameworks", 296 | ); 297 | MARKETING_VERSION = 1.0.0; 298 | PRODUCT_BUNDLE_IDENTIFIER = com.timerapp.swiftUI; 299 | PRODUCT_NAME = "$(TARGET_NAME)"; 300 | PROVISIONING_PROFILE_SPECIFIER = ""; 301 | "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = Hexalitics_wild_card; 302 | SWIFT_EMIT_LOC_STRINGS = YES; 303 | SWIFT_VERSION = 5.0; 304 | TARGETED_DEVICE_FAMILY = "1,2"; 305 | }; 306 | name = Debug; 307 | }; 308 | BFCF4C0C2AE94ADA00BA8235 /* Release */ = { 309 | isa = XCBuildConfiguration; 310 | buildSettings = { 311 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 312 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 313 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 314 | CODE_SIGN_STYLE = Manual; 315 | CURRENT_PROJECT_VERSION = 1; 316 | DEVELOPMENT_ASSET_PATHS = "\"TimerApp/Preview Content\""; 317 | DEVELOPMENT_TEAM = ""; 318 | "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8SS5HN6D42; 319 | ENABLE_PREVIEWS = YES; 320 | GENERATE_INFOPLIST_FILE = YES; 321 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 322 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 323 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 324 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 325 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 326 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 327 | LD_RUNPATH_SEARCH_PATHS = ( 328 | "$(inherited)", 329 | "@executable_path/Frameworks", 330 | ); 331 | MARKETING_VERSION = 1.0.0; 332 | PRODUCT_BUNDLE_IDENTIFIER = com.timerapp.swiftUI; 333 | PRODUCT_NAME = "$(TARGET_NAME)"; 334 | PROVISIONING_PROFILE_SPECIFIER = ""; 335 | "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = Hexalitics_wild_card; 336 | SWIFT_EMIT_LOC_STRINGS = YES; 337 | SWIFT_VERSION = 5.0; 338 | TARGETED_DEVICE_FAMILY = "1,2"; 339 | }; 340 | name = Release; 341 | }; 342 | /* End XCBuildConfiguration section */ 343 | 344 | /* Begin XCConfigurationList section */ 345 | BFCF4BF72AE94AD700BA8235 /* Build configuration list for PBXProject "TimerApp" */ = { 346 | isa = XCConfigurationList; 347 | buildConfigurations = ( 348 | BFCF4C082AE94ADA00BA8235 /* Debug */, 349 | BFCF4C092AE94ADA00BA8235 /* Release */, 350 | ); 351 | defaultConfigurationIsVisible = 0; 352 | defaultConfigurationName = Release; 353 | }; 354 | BFCF4C0A2AE94ADA00BA8235 /* Build configuration list for PBXNativeTarget "TimerApp" */ = { 355 | isa = XCConfigurationList; 356 | buildConfigurations = ( 357 | BFCF4C0B2AE94ADA00BA8235 /* Debug */, 358 | BFCF4C0C2AE94ADA00BA8235 /* Release */, 359 | ); 360 | defaultConfigurationIsVisible = 0; 361 | defaultConfigurationName = Release; 362 | }; 363 | /* End XCConfigurationList section */ 364 | }; 365 | rootObject = BFCF4BF42AE94AD700BA8235 /* Project object */; 366 | } 367 | -------------------------------------------------------------------------------- /TimerApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TimerApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TimerApp.xcodeproj/project.xcworkspace/xcuserdata/hexalitics.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadanand-lowanshi/SwiftUI-TimerApp/051fc9cadb1f875386c43ef20a43fc3037c9c308/TimerApp.xcodeproj/project.xcworkspace/xcuserdata/hexalitics.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /TimerApp.xcodeproj/project.xcworkspace/xcuserdata/sadanand.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadanand-lowanshi/SwiftUI-TimerApp/051fc9cadb1f875386c43ef20a43fc3037c9c308/TimerApp.xcodeproj/project.xcworkspace/xcuserdata/sadanand.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /TimerApp.xcodeproj/xcuserdata/hexalitics.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /TimerApp.xcodeproj/xcuserdata/hexalitics.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TimerApp.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /TimerApp.xcodeproj/xcuserdata/sadanand.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TimerApp.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /TimerApp/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 | -------------------------------------------------------------------------------- /TimerApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "logo.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /TimerApp/Assets.xcassets/AppIcon.appiconset/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sadanand-lowanshi/SwiftUI-TimerApp/051fc9cadb1f875386c43ef20a43fc3037c9c308/TimerApp/Assets.xcassets/AppIcon.appiconset/logo.png -------------------------------------------------------------------------------- /TimerApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TimerApp/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // TimerApp 4 | // 5 | // Created by Sadanand on 25/10/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | 12 | @ObservedObject var timerVM: TimerViewModel // Manage timer model 13 | @State var isPaused = false // Manage pause or start 14 | @State private var rotation = 0 15 | 16 | init(seconds: TimeInterval = 0) { 17 | timerVM = TimerViewModel( 18 | seconds: seconds, 19 | goalTime: 20 20 | ) 21 | } 22 | 23 | var body: some View { 24 | ZStack { 25 | // MARK: - Background color 26 | Color( 27 | red: 63/255, green: 68/255, blue: 3/255 28 | ) 29 | .ignoresSafeArea() // End background color 30 | 31 | // MARK: - Progress Bar ring 32 | ProgressBarView( 33 | progress: $timerVM.seconds, 34 | goal: $timerVM.goalTime 35 | ) // End Progress Bar ring 36 | 37 | // MARK: - Center title 38 | VStack { 39 | // Center title 40 | Text(timerVM.progress >= 1 ? "Done" : timerVM.displayTime) 41 | .font(.system(size: 50, weight: .bold)) 42 | .foregroundColor(.white) 43 | // Center sub title 44 | Text("\(timerVM.goalTime.asString(style: .short))") 45 | .foregroundColor(.white.opacity(0.6)) 46 | } // End Center title 47 | 48 | // MARK: - Bottom Buttons 49 | VStack { 50 | Text("Timer App Demo") 51 | .font(.title) 52 | .foregroundColor( 53 | Color( 54 | red: 180/255, green: 187/255, blue: 62/255 55 | ) 56 | ) 57 | Spacer() 58 | ButtonView() 59 | } // End Bottom Buttons 60 | .onAppear { 61 | timerVM.startSession() 62 | timerVM.viewDidLoad() 63 | } 64 | } 65 | } 66 | } 67 | 68 | extension ContentView { 69 | 70 | private func ButtonView() -> some View { 71 | 72 | HStack { 73 | // Reset Button 74 | Button { 75 | reset() 76 | } label: { 77 | HStack(spacing: 8) { 78 | Image(systemName: "arrow.clockwise") 79 | .rotationEffect(.degrees(Double(rotation))) 80 | Text("Reset") 81 | } .padding() 82 | .tint(.black) 83 | .frame(maxWidth: .infinity) 84 | .font(.system(size: 18, weight: .bold)) 85 | } 86 | .background( 87 | Color( 88 | red: 236/255, green: 230/255, blue: 0/255) 89 | ) 90 | .cornerRadius(15) // End 91 | 92 | // Start & pause Button 93 | Button { 94 | if timerVM.progress < 1 { 95 | isPaused.toggle() 96 | isPaused ? timerVM.pauseSession() : timerVM.startSession() 97 | } 98 | } label: { 99 | HStack(spacing: 8) { 100 | Image(systemName: isPaused ? "play.fill" : "pause.fill") 101 | Text(isPaused ? "Start" : "Pause") 102 | } .padding() 103 | .tint(.black) 104 | .frame(maxWidth: .infinity) 105 | .font(.system( 106 | size: 18, 107 | weight: .bold) 108 | ) 109 | } 110 | .background( 111 | Color( 112 | red: 236/255, green: 230/255, blue: 0/255) 113 | ) 114 | .cornerRadius(15) 115 | }.padding(.bottom, 40) 116 | .padding([.leading, .trailing], 20) // End Start & pause Button 117 | } 118 | 119 | private func reset() { 120 | withAnimation(.easeInOut(duration: 0.4)) { 121 | rotation += 360 122 | } 123 | if timerVM.progress >= 1 { 124 | timerVM.reset() 125 | timerVM.startSession() 126 | } else { 127 | timerVM.reset() 128 | timerVM.displayTime = "00:00" 129 | } 130 | } 131 | } 132 | 133 | struct ContentView_Previews: PreviewProvider { 134 | static var previews: some View { 135 | ContentView() 136 | } 137 | } 138 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /TimerApp/Extension+Double.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extension+Double.swift 3 | // TimerApp 4 | // 5 | // Created by Sadanand on 26/10/23. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Double { 11 | 12 | func asString(style: DateComponentsFormatter.UnitsStyle) -> String { 13 | let formatter = DateComponentsFormatter() 14 | formatter.allowedUnits = [.hour, .minute, .second, .nanosecond] 15 | formatter.unitsStyle = style 16 | return formatter.string(from: self) ?? "" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TimerApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TimerApp/ProgressBarView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProgressBarView.swift 3 | // TimerApp 4 | // 5 | // Created by Sadanand on 26/10/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ProgressBarView: View { 11 | 12 | @Binding var progress: TimeInterval // Current progress of timer 13 | @Binding var goal: Double // Total goal time 14 | 15 | var body: some View { 16 | 17 | ZStack { 18 | // Default circle 19 | Circle() 20 | .stroke( 21 | style: StrokeStyle( 22 | lineWidth: 20, 23 | lineCap: .butt, 24 | dash: [2, 6]) 25 | ) 26 | .fill(Color.gray) 27 | .rotationEffect(Angle(degrees: -90)) 28 | .frame( 29 | width: 300, 30 | height: 300 31 | ) 32 | 33 | // overlap circle 34 | Circle() 35 | .trim( 36 | from: 0, 37 | to: CGFloat(progress) / CGFloat(goal) 38 | ) 39 | .stroke( 40 | style: StrokeStyle( 41 | lineWidth: 20, 42 | lineCap: .butt, 43 | dash: [2, 6]) 44 | ) 45 | .fill( 46 | Color( 47 | red: 236/255, green: 230/255, blue: 0/255 48 | ) 49 | ) 50 | .animation( 51 | .spring(), 52 | value: progress 53 | ) 54 | .rotationEffect(Angle(degrees: -90)) 55 | .frame( 56 | width: 300, 57 | height: 300 58 | ) 59 | } 60 | } 61 | } 62 | 63 | struct ProgressBarView_Previews: PreviewProvider { 64 | 65 | static var previews: some View { 66 | ProgressBarView( 67 | progress: .constant(0), 68 | goal: .constant(0) 69 | ) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /TimerApp/TimerAppApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimerAppApp.swift 3 | // TimerApp 4 | // 5 | // Created by Sadanand on 25/10/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct TimerAppApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TimerApp/TimerViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimerViewModel.swift 3 | // TimerApp 4 | // 5 | // Created by Sadanand on 25/10/23. 6 | // 7 | 8 | import SwiftUI 9 | import AudioToolbox 10 | import AVFoundation 11 | 12 | class TimerViewModel: NSObject, ObservableObject { 13 | 14 | @Published var progress: Double 15 | @Published var seconds: TimeInterval 16 | @Published var displayTime: String = "" 17 | @Published var goalTime: Double = 0 // Default 0 18 | 19 | private var timer: Timer = Timer() 20 | private var SoundID: SystemSoundID = 1407 // A system sound object, identified with a sound file you want to play. 21 | private let feedback = UIImpactFeedbackGenerator(style: .soft) // A concrete feedback generator subclass that creates haptics to simulate physical impacts. 22 | 23 | init(seconds: TimeInterval, goalTime: Double) { 24 | self.seconds = seconds 25 | self.goalTime = goalTime 26 | self.progress = seconds / Double(goalTime) 27 | } 28 | 29 | @objc func fireTimer() { 30 | seconds += 0.2 31 | progress = Double(seconds) / Double(goalTime) 32 | self.displayTime = calculateDisplayTime() 33 | print(progress) 34 | 35 | // Timer is completed 36 | if progress >= 1 { 37 | stopSession() 38 | makeSoundAndVibration() // Play sound when time is completed 39 | } 40 | } 41 | 42 | func startSession() { 43 | print("Timer started") 44 | timer = Timer.scheduledTimer( 45 | timeInterval: 0.2, 46 | target: self, 47 | selector: #selector(self.fireTimer), 48 | userInfo: nil, 49 | repeats: true 50 | ) 51 | } 52 | 53 | func stopSession() { 54 | print("Timer Stoped") 55 | timer.invalidate() 56 | } 57 | 58 | func pauseSession() { 59 | timer.invalidate() 60 | } 61 | 62 | func reset() { 63 | seconds = 0 64 | progress = 0 65 | } 66 | } 67 | 68 | // MARK: - Public Methods 69 | extension TimerViewModel { 70 | 71 | func viewDidLoad() { 72 | self.progress = seconds / Double(goalTime) 73 | self.displayTime = calculateDisplayTime() 74 | } 75 | } 76 | 77 | extension TimerViewModel { 78 | 79 | private func calculateDisplayTime() -> String { 80 | var time = "" 81 | let minutes = Int(seconds) / 60 82 | let seconds = Int(seconds) % 60 83 | 84 | if minutes > 9 { 85 | time.append(minutes.description) 86 | } else { 87 | time.append("0" + minutes.description) 88 | } 89 | 90 | time.append(":") 91 | 92 | if seconds > 9 { 93 | time.append(seconds.description) 94 | } else { 95 | time.append("0" + seconds.description) 96 | } 97 | 98 | return time 99 | } 100 | } 101 | 102 | extension TimerViewModel { 103 | 104 | private func makeSoundAndVibration() { 105 | AudioServicesPlayAlertSoundWithCompletion(SoundID, nil) 106 | AudioServicesPlayAlertSoundWithCompletion( 107 | SystemSoundID(kSystemSoundID_Vibrate) 108 | ) { 109 | } 110 | } 111 | } 112 | --------------------------------------------------------------------------------