├── .gitattributes ├── .github └── workflows │ └── action.yml ├── .gitignore ├── AlarmClock ├── AlarmClock.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── AlarmClock │ ├── AlarmClockView.swift │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Bedtime.imageset │ │ ├── Bedtime.png │ │ └── Contents.json │ ├── Contents.json │ ├── Wake.imageset │ │ ├── Contents.json │ │ └── Wake.png │ ├── clock.imageset │ │ ├── Contents.json │ │ └── clock.png │ ├── ring.imageset │ │ ├── Contents.json │ │ └── ring.png │ └── sleep.imageset │ │ ├── Contents.json │ │ └── sleep.png │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── RotaionGestureRecognizer.swift │ ├── UIView+Extension.swift │ └── ViewController.swift ├── LICENSE ├── README.md └── Screencast ├── preview.gif └── preview01.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/action.yml: -------------------------------------------------------------------------------- 1 | name: "Close stale issues" 2 | on: 3 | schedule: 4 | - cron: "30 1 * * *" 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v3 11 | with: 12 | repo-token: ${{ secrets.GITHUB_TOKEN }} 13 | stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days' 14 | days-before-stale: 30 15 | days-before-close: 5 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | # 51 | # Add this line if you want to avoid checking in source code from the Xcode workspace 52 | # *.xcworkspace 53 | 54 | # Carthage 55 | # 56 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 57 | # Carthage/Checkouts 58 | 59 | Carthage/Build 60 | 61 | # fastlane 62 | # 63 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 64 | # screenshots whenever they are needed. 65 | # For more information about the recommended setup visit: 66 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 67 | 68 | fastlane/report.xml 69 | fastlane/Preview.html 70 | fastlane/screenshots/**/*.png 71 | fastlane/test_output 72 | 73 | # Code Injection 74 | # 75 | # After new code Injection tools there's a generated folder /iOSInjectionProject 76 | # https://github.com/johnno1962/injectionforxcode 77 | 78 | iOSInjectionProject/ 79 | -------------------------------------------------------------------------------- /AlarmClock/AlarmClock.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6BD5563921D46F29005095EA /* RotaionGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5563821D46F29005095EA /* RotaionGestureRecognizer.swift */; }; 11 | 6BD5563B21D472F8005095EA /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5563A21D472F8005095EA /* UIView+Extension.swift */; }; 12 | 6BF69DA221D26A68005D6995 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BF69DA121D26A68005D6995 /* AppDelegate.swift */; }; 13 | 6BF69DA421D26A68005D6995 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BF69DA321D26A68005D6995 /* ViewController.swift */; }; 14 | 6BF69DA721D26A68005D6995 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6BF69DA521D26A68005D6995 /* Main.storyboard */; }; 15 | 6BF69DA921D26A69005D6995 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6BF69DA821D26A69005D6995 /* Assets.xcassets */; }; 16 | 6BF69DAC21D26A69005D6995 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6BF69DAA21D26A69005D6995 /* LaunchScreen.storyboard */; }; 17 | 6BF69DB421D26AE0005D6995 /* AlarmClockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BF69DB321D26AE0005D6995 /* AlarmClockView.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 6BD5563821D46F29005095EA /* RotaionGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotaionGestureRecognizer.swift; sourceTree = ""; }; 22 | 6BD5563A21D472F8005095EA /* UIView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extension.swift"; sourceTree = ""; }; 23 | 6BF69D9E21D26A68005D6995 /* AlarmClock.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AlarmClock.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | 6BF69DA121D26A68005D6995 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 25 | 6BF69DA321D26A68005D6995 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 26 | 6BF69DA621D26A68005D6995 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 27 | 6BF69DA821D26A69005D6995 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 28 | 6BF69DAB21D26A69005D6995 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 29 | 6BF69DAD21D26A69005D6995 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 30 | 6BF69DB321D26AE0005D6995 /* AlarmClockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmClockView.swift; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | 6BF69D9B21D26A68005D6995 /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | ); 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXFrameworksBuildPhase section */ 42 | 43 | /* Begin PBXGroup section */ 44 | 6BF69D9521D26A68005D6995 = { 45 | isa = PBXGroup; 46 | children = ( 47 | 6BF69DA021D26A68005D6995 /* AlarmClock */, 48 | 6BF69D9F21D26A68005D6995 /* Products */, 49 | ); 50 | sourceTree = ""; 51 | }; 52 | 6BF69D9F21D26A68005D6995 /* Products */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | 6BF69D9E21D26A68005D6995 /* AlarmClock.app */, 56 | ); 57 | name = Products; 58 | sourceTree = ""; 59 | }; 60 | 6BF69DA021D26A68005D6995 /* AlarmClock */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | 6BF69DA121D26A68005D6995 /* AppDelegate.swift */, 64 | 6BF69DA321D26A68005D6995 /* ViewController.swift */, 65 | 6BF69DB321D26AE0005D6995 /* AlarmClockView.swift */, 66 | 6BD5563821D46F29005095EA /* RotaionGestureRecognizer.swift */, 67 | 6BD5563A21D472F8005095EA /* UIView+Extension.swift */, 68 | 6BF69DA521D26A68005D6995 /* Main.storyboard */, 69 | 6BF69DA821D26A69005D6995 /* Assets.xcassets */, 70 | 6BF69DAA21D26A69005D6995 /* LaunchScreen.storyboard */, 71 | 6BF69DAD21D26A69005D6995 /* Info.plist */, 72 | ); 73 | path = AlarmClock; 74 | sourceTree = ""; 75 | }; 76 | /* End PBXGroup section */ 77 | 78 | /* Begin PBXNativeTarget section */ 79 | 6BF69D9D21D26A68005D6995 /* AlarmClock */ = { 80 | isa = PBXNativeTarget; 81 | buildConfigurationList = 6BF69DB021D26A69005D6995 /* Build configuration list for PBXNativeTarget "AlarmClock" */; 82 | buildPhases = ( 83 | 6BF69D9A21D26A68005D6995 /* Sources */, 84 | 6BF69D9B21D26A68005D6995 /* Frameworks */, 85 | 6BF69D9C21D26A68005D6995 /* Resources */, 86 | ); 87 | buildRules = ( 88 | ); 89 | dependencies = ( 90 | ); 91 | name = AlarmClock; 92 | productName = AlarmClock; 93 | productReference = 6BF69D9E21D26A68005D6995 /* AlarmClock.app */; 94 | productType = "com.apple.product-type.application"; 95 | }; 96 | /* End PBXNativeTarget section */ 97 | 98 | /* Begin PBXProject section */ 99 | 6BF69D9621D26A68005D6995 /* Project object */ = { 100 | isa = PBXProject; 101 | attributes = { 102 | LastSwiftUpdateCheck = 1000; 103 | LastUpgradeCheck = 1000; 104 | ORGANIZATIONNAME = LC; 105 | TargetAttributes = { 106 | 6BF69D9D21D26A68005D6995 = { 107 | CreatedOnToolsVersion = 10.0; 108 | LastSwiftMigration = 1120; 109 | }; 110 | }; 111 | }; 112 | buildConfigurationList = 6BF69D9921D26A68005D6995 /* Build configuration list for PBXProject "AlarmClock" */; 113 | compatibilityVersion = "Xcode 9.3"; 114 | developmentRegion = en; 115 | hasScannedForEncodings = 0; 116 | knownRegions = ( 117 | en, 118 | Base, 119 | ); 120 | mainGroup = 6BF69D9521D26A68005D6995; 121 | productRefGroup = 6BF69D9F21D26A68005D6995 /* Products */; 122 | projectDirPath = ""; 123 | projectRoot = ""; 124 | targets = ( 125 | 6BF69D9D21D26A68005D6995 /* AlarmClock */, 126 | ); 127 | }; 128 | /* End PBXProject section */ 129 | 130 | /* Begin PBXResourcesBuildPhase section */ 131 | 6BF69D9C21D26A68005D6995 /* Resources */ = { 132 | isa = PBXResourcesBuildPhase; 133 | buildActionMask = 2147483647; 134 | files = ( 135 | 6BF69DAC21D26A69005D6995 /* LaunchScreen.storyboard in Resources */, 136 | 6BF69DA921D26A69005D6995 /* Assets.xcassets in Resources */, 137 | 6BF69DA721D26A68005D6995 /* Main.storyboard in Resources */, 138 | ); 139 | runOnlyForDeploymentPostprocessing = 0; 140 | }; 141 | /* End PBXResourcesBuildPhase section */ 142 | 143 | /* Begin PBXSourcesBuildPhase section */ 144 | 6BF69D9A21D26A68005D6995 /* Sources */ = { 145 | isa = PBXSourcesBuildPhase; 146 | buildActionMask = 2147483647; 147 | files = ( 148 | 6BD5563921D46F29005095EA /* RotaionGestureRecognizer.swift in Sources */, 149 | 6BF69DA421D26A68005D6995 /* ViewController.swift in Sources */, 150 | 6BF69DA221D26A68005D6995 /* AppDelegate.swift in Sources */, 151 | 6BD5563B21D472F8005095EA /* UIView+Extension.swift in Sources */, 152 | 6BF69DB421D26AE0005D6995 /* AlarmClockView.swift in Sources */, 153 | ); 154 | runOnlyForDeploymentPostprocessing = 0; 155 | }; 156 | /* End PBXSourcesBuildPhase section */ 157 | 158 | /* Begin PBXVariantGroup section */ 159 | 6BF69DA521D26A68005D6995 /* Main.storyboard */ = { 160 | isa = PBXVariantGroup; 161 | children = ( 162 | 6BF69DA621D26A68005D6995 /* Base */, 163 | ); 164 | name = Main.storyboard; 165 | sourceTree = ""; 166 | }; 167 | 6BF69DAA21D26A69005D6995 /* LaunchScreen.storyboard */ = { 168 | isa = PBXVariantGroup; 169 | children = ( 170 | 6BF69DAB21D26A69005D6995 /* Base */, 171 | ); 172 | name = LaunchScreen.storyboard; 173 | sourceTree = ""; 174 | }; 175 | /* End PBXVariantGroup section */ 176 | 177 | /* Begin XCBuildConfiguration section */ 178 | 6BF69DAE21D26A69005D6995 /* Debug */ = { 179 | isa = XCBuildConfiguration; 180 | buildSettings = { 181 | ALWAYS_SEARCH_USER_PATHS = NO; 182 | CLANG_ANALYZER_NONNULL = YES; 183 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 184 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 185 | CLANG_CXX_LIBRARY = "libc++"; 186 | CLANG_ENABLE_MODULES = YES; 187 | CLANG_ENABLE_OBJC_ARC = YES; 188 | CLANG_ENABLE_OBJC_WEAK = YES; 189 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 190 | CLANG_WARN_BOOL_CONVERSION = YES; 191 | CLANG_WARN_COMMA = YES; 192 | CLANG_WARN_CONSTANT_CONVERSION = YES; 193 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 194 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 195 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 196 | CLANG_WARN_EMPTY_BODY = YES; 197 | CLANG_WARN_ENUM_CONVERSION = YES; 198 | CLANG_WARN_INFINITE_RECURSION = YES; 199 | CLANG_WARN_INT_CONVERSION = YES; 200 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 201 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 202 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 203 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 204 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 205 | CLANG_WARN_STRICT_PROTOTYPES = YES; 206 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 207 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 208 | CLANG_WARN_UNREACHABLE_CODE = YES; 209 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 210 | CODE_SIGN_IDENTITY = "iPhone Developer"; 211 | COPY_PHASE_STRIP = NO; 212 | DEBUG_INFORMATION_FORMAT = dwarf; 213 | ENABLE_STRICT_OBJC_MSGSEND = YES; 214 | ENABLE_TESTABILITY = YES; 215 | GCC_C_LANGUAGE_STANDARD = gnu11; 216 | GCC_DYNAMIC_NO_PIC = NO; 217 | GCC_NO_COMMON_BLOCKS = YES; 218 | GCC_OPTIMIZATION_LEVEL = 0; 219 | GCC_PREPROCESSOR_DEFINITIONS = ( 220 | "DEBUG=1", 221 | "$(inherited)", 222 | ); 223 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 224 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 225 | GCC_WARN_UNDECLARED_SELECTOR = YES; 226 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 227 | GCC_WARN_UNUSED_FUNCTION = YES; 228 | GCC_WARN_UNUSED_VARIABLE = YES; 229 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 230 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 231 | MTL_FAST_MATH = YES; 232 | ONLY_ACTIVE_ARCH = YES; 233 | SDKROOT = iphoneos; 234 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 235 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 236 | SWIFT_VERSION = 5.0; 237 | }; 238 | name = Debug; 239 | }; 240 | 6BF69DAF21D26A69005D6995 /* Release */ = { 241 | isa = XCBuildConfiguration; 242 | buildSettings = { 243 | ALWAYS_SEARCH_USER_PATHS = NO; 244 | CLANG_ANALYZER_NONNULL = YES; 245 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 246 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 247 | CLANG_CXX_LIBRARY = "libc++"; 248 | CLANG_ENABLE_MODULES = YES; 249 | CLANG_ENABLE_OBJC_ARC = YES; 250 | CLANG_ENABLE_OBJC_WEAK = YES; 251 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 252 | CLANG_WARN_BOOL_CONVERSION = YES; 253 | CLANG_WARN_COMMA = YES; 254 | CLANG_WARN_CONSTANT_CONVERSION = YES; 255 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 256 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 257 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 258 | CLANG_WARN_EMPTY_BODY = YES; 259 | CLANG_WARN_ENUM_CONVERSION = YES; 260 | CLANG_WARN_INFINITE_RECURSION = YES; 261 | CLANG_WARN_INT_CONVERSION = YES; 262 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 263 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 264 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 265 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 266 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 267 | CLANG_WARN_STRICT_PROTOTYPES = YES; 268 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 269 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 270 | CLANG_WARN_UNREACHABLE_CODE = YES; 271 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 272 | CODE_SIGN_IDENTITY = "iPhone Developer"; 273 | COPY_PHASE_STRIP = NO; 274 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 275 | ENABLE_NS_ASSERTIONS = NO; 276 | ENABLE_STRICT_OBJC_MSGSEND = YES; 277 | GCC_C_LANGUAGE_STANDARD = gnu11; 278 | GCC_NO_COMMON_BLOCKS = YES; 279 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 280 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 281 | GCC_WARN_UNDECLARED_SELECTOR = YES; 282 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 283 | GCC_WARN_UNUSED_FUNCTION = YES; 284 | GCC_WARN_UNUSED_VARIABLE = YES; 285 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 286 | MTL_ENABLE_DEBUG_INFO = NO; 287 | MTL_FAST_MATH = YES; 288 | SDKROOT = iphoneos; 289 | SWIFT_COMPILATION_MODE = wholemodule; 290 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 291 | SWIFT_VERSION = 5.0; 292 | VALIDATE_PRODUCT = YES; 293 | }; 294 | name = Release; 295 | }; 296 | 6BF69DB121D26A69005D6995 /* Debug */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 300 | CODE_SIGN_STYLE = Automatic; 301 | INFOPLIST_FILE = AlarmClock/Info.plist; 302 | LD_RUNPATH_SEARCH_PATHS = ( 303 | "$(inherited)", 304 | "@executable_path/Frameworks", 305 | ); 306 | PRODUCT_BUNDLE_IDENTIFIER = com.LC.AlarmClock; 307 | PRODUCT_NAME = "$(TARGET_NAME)"; 308 | SWIFT_VERSION = 5.0; 309 | TARGETED_DEVICE_FAMILY = "1,2"; 310 | }; 311 | name = Debug; 312 | }; 313 | 6BF69DB221D26A69005D6995 /* Release */ = { 314 | isa = XCBuildConfiguration; 315 | buildSettings = { 316 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 317 | CODE_SIGN_STYLE = Automatic; 318 | INFOPLIST_FILE = AlarmClock/Info.plist; 319 | LD_RUNPATH_SEARCH_PATHS = ( 320 | "$(inherited)", 321 | "@executable_path/Frameworks", 322 | ); 323 | PRODUCT_BUNDLE_IDENTIFIER = com.LC.AlarmClock; 324 | PRODUCT_NAME = "$(TARGET_NAME)"; 325 | SWIFT_VERSION = 5.0; 326 | TARGETED_DEVICE_FAMILY = "1,2"; 327 | }; 328 | name = Release; 329 | }; 330 | /* End XCBuildConfiguration section */ 331 | 332 | /* Begin XCConfigurationList section */ 333 | 6BF69D9921D26A68005D6995 /* Build configuration list for PBXProject "AlarmClock" */ = { 334 | isa = XCConfigurationList; 335 | buildConfigurations = ( 336 | 6BF69DAE21D26A69005D6995 /* Debug */, 337 | 6BF69DAF21D26A69005D6995 /* Release */, 338 | ); 339 | defaultConfigurationIsVisible = 0; 340 | defaultConfigurationName = Release; 341 | }; 342 | 6BF69DB021D26A69005D6995 /* Build configuration list for PBXNativeTarget "AlarmClock" */ = { 343 | isa = XCConfigurationList; 344 | buildConfigurations = ( 345 | 6BF69DB121D26A69005D6995 /* Debug */, 346 | 6BF69DB221D26A69005D6995 /* Release */, 347 | ); 348 | defaultConfigurationIsVisible = 0; 349 | defaultConfigurationName = Release; 350 | }; 351 | /* End XCConfigurationList section */ 352 | }; 353 | rootObject = 6BF69D9621D26A68005D6995 /* Project object */; 354 | } 355 | -------------------------------------------------------------------------------- /AlarmClock/AlarmClock.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AlarmClock/AlarmClock.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AlarmClock/AlarmClock/AlarmClockView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlarmClockView.swift 3 | // AlarmClock 4 | // 5 | // Created by Liu Chuan on 2018/12/25. 6 | // Copyright © 2018 LC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | /// 闹钟视图代理协议 13 | protocol AlarmViewDelegate: NSObjectProtocol { 14 | 15 | /// 闹钟视图已更改 16 | /// 17 | /// - Parameters: 18 | /// - bedTime: 就寝时间 19 | /// - wakeTime: 起床时间 20 | func alarmViewIsChanged(withBedTime bedTime: String?, wakeTime: String?) 21 | 22 | } 23 | 24 | 25 | /// 旋转类型 26 | /// 27 | /// - StartAngle: 起始角度 28 | /// - EndAngle: 结束角度 29 | /// - CircularingLocation: 圆环位置 30 | /// - None: 无旋转 31 | enum RotationType { 32 | case StartAngle 33 | case EndAngle 34 | case CircularingLocation 35 | case None 36 | } 37 | 38 | 39 | /// 图标视图宽度 40 | private let IconViewWidth: CGFloat = 40 41 | 42 | /// 当前开始角度 43 | private var currentStartAngle: Float = 0 44 | 45 | /// 当前结束角度 46 | private var currentEndAngle: Float = 0 47 | 48 | /// 扇形图层颜色 49 | private let fanColor = UIColor(hue:0.12, saturation:0.81, brightness:1.00, alpha:1.00) 50 | 51 | 52 | 53 | /// 将角度转为弧度 54 | /// 55 | /// - Parameter x: x值 56 | /// - Returns: CGFloat 57 | func DgreesToRadoans(x: CGFloat) -> CGFloat { 58 | return .pi * (x) / 180.0 59 | } 60 | 61 | 62 | 63 | @objcMembers class AlarmClockView: UIView { 64 | 65 | // MARK: - Lazy Loading View 66 | 67 | /// 旋转类型 68 | private lazy var rotationType: RotationType = .StartAngle 69 | 70 | /// 就寝时间 71 | var bedTime = "" 72 | 73 | /// 起床时间 74 | var wakeTime = "" 75 | 76 | /// 开始角度 Default: 0 77 | var startAngle: CGFloat = 0.0 78 | 79 | /// 结束角度 Default: 0 80 | var endAngle: CGFloat = 0.0 81 | 82 | /// 代理 83 | weak var delegate: AlarmViewDelegate? 84 | 85 | /// 闹钟视图 86 | private lazy var alarmView: UIView = { 87 | let view = UIView(frame: bounds) 88 | return view 89 | }() 90 | 91 | /// 圆环父视图 92 | private lazy var ringSuperView: UIView = { 93 | let view = UIView(frame: bounds) 94 | return view 95 | }() 96 | 97 | /// 就寝父视图 98 | private lazy var sleepSuperView: UIView = { 99 | let view = UIView(frame: bounds) 100 | return view 101 | }() 102 | 103 | /// 按铃图片视图 104 | private lazy var ringView: UIImageView = { 105 | let ring = UIImageView(frame: CGRect(x: 0, y: 0, width: IconViewWidth, height: IconViewWidth)) 106 | ring.center = CGPoint(x: AlarmViewRadius, y: IconViewWidth / 2) 107 | ring.image = UIImage(named: "ring") 108 | return ring 109 | }() 110 | 111 | /// 就寝图片视图 112 | private lazy var sleepView: UIImageView = { 113 | let sleep = UIImageView(frame: CGRect(x: 0, y: 0, width: IconViewWidth, height: IconViewWidth)) 114 | sleep.center = CGPoint(x: AlarmViewRadius, y: IconViewWidth / 2) 115 | sleep.image = UIImage(named: "sleep") 116 | return sleep 117 | }() 118 | 119 | /// 时间刻度视图 120 | private lazy var timeScaleView: UIImageView = { 121 | let timeV = UIImageView(frame: CGRect(x: 0, y: 0, width: (AlarmViewRadius - IconViewWidth) * 2, height: (AlarmViewRadius - IconViewWidth) * 2)) 122 | timeV.center = CGPoint(x: AlarmViewRadius, y: AlarmViewRadius) 123 | timeV.image = UIImage(named: "clock") 124 | return timeV 125 | }() 126 | 127 | /// 最终时间标签 128 | private lazy var finallyTimeLabel: UILabel = { 129 | let timeLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 40)) 130 | timeLabel.center = CGPoint(x: AlarmViewRadius, y: AlarmViewRadius) 131 | timeLabel.text = "0小时0分" 132 | timeLabel.font = UIFont.boldSystemFont(ofSize: 20) 133 | timeLabel.numberOfLines = 1 134 | timeLabel.textColor = .white 135 | timeLabel.textAlignment = .center 136 | return timeLabel 137 | }() 138 | 139 | /// 最底部图层(在坐标系内绘制贝塞尔曲线) 140 | private lazy var lowestLayer: CAShapeLayer = { 141 | let lowestLayer = CAShapeLayer() 142 | lowestLayer.bounds = CGRect(x: 0, y: 0, width: bounds.size.width, height: bounds.size.height) 143 | /// 要呈现的形状的路径 144 | lowestLayer.path = drawAlarmPath(withStartAngle: 0, endAngle: 360)?.cgPath 145 | /// 填充路径的颜色 146 | lowestLayer.fillColor = UIColor(red: 0.07, green: 0.07, blue: 0.07, alpha: 1).cgColor 147 | return lowestLayer 148 | }() 149 | 150 | 151 | /// 渐变图层 152 | private lazy var gradientLayer: CAGradientLayer = { 153 | let gradient = CAGradientLayer() 154 | gradient.frame = fanLayer.bounds 155 | gradient.colors = [cgColorFor(red: 254, green: 149, blue: 40), 156 | cgColorFor(red: 254, green: 193, blue: 47), 157 | cgColorFor(red: 254, green: 200, blue: 49)] 158 | 159 | 160 | // gradient.colors = [cgColorFor(red: 254, green: 149, blue: 40), cgColorFor(red: 255, green: 255, blue: 0)] 161 | // gradient.locations = [0.0, 0.5, 1.0] 162 | gradient.startPoint = CGPoint(x: 0.5, y: 1) 163 | gradient.endPoint = CGPoint(x: 0.5, y: 0) 164 | return gradient 165 | }() 166 | 167 | /// 扇形遮罩图层(绘制贝塞尔曲线) 168 | private lazy var fanLayer: CAShapeLayer = { 169 | let fan = CAShapeLayer() 170 | fan.bounds = CGRect(x: 0, y: 0, width: bounds.size.width, height: bounds.size.height) 171 | // 要呈现的形状的路径 172 | fan.path = drawAlarmPath(withStartAngle: startAngle, endAngle: endAngle)?.cgPath 173 | // 要呈现的颜色 174 | fan.fillColor = fanViewColor 175 | // 设置描边色 176 | fan.strokeColor = UIColor.clear.cgColor 177 | return fan 178 | }() 179 | 180 | /// 扇形视图颜色 181 | public var fanViewColor: CGColor = UIColor.orange.cgColor { 182 | didSet{ // 监听数值 `fanViewColor` 的改变, 从而修改 `fanLayer` 的背景色 183 | if fanViewColor != oldValue { 184 | fanLayer.fillColor = fanViewColor 185 | } 186 | } 187 | } 188 | 189 | /// 旋转手势 190 | private lazy var gestureRecognizer: RotaionGestureRecognizer = { 191 | let ges = RotaionGestureRecognizer(center: alarmView.center, target: self, action: #selector(rotationAction(_:))) 192 | ges.rotaionGestureRecognizerDelegate = self 193 | return ges 194 | }() 195 | 196 | 197 | // MARK: - Initialization method 198 | 199 | /// 根据视图的尺寸位置\起始角度\结束角度创建视图 200 | /// 201 | /// - Parameters: 202 | /// - frame: 视图尺寸位置 203 | /// - startAngle: 起始角度 204 | /// - endAngle: 结束角度 205 | init(frame: CGRect, startAngle: CGFloat, endAngle: CGFloat) { 206 | super.init(frame: frame) 207 | self.startAngle = startAngle 208 | self.endAngle = endAngle 209 | currentStartAngle = Float(startAngle) 210 | currentEndAngle = Float(endAngle) 211 | 212 | configUI() 213 | configGesture() 214 | } 215 | 216 | required init?(coder aDecoder: NSCoder) { 217 | fatalError("init(coder:) has not been implemented") 218 | } 219 | 220 | 221 | override func draw(_ rect: CGRect) { 222 | // Drawing code 223 | } 224 | 225 | /// 旋转手势相应事件 226 | @objc private func rotationAction(_ gestureRec: RotaionGestureRecognizer) { 227 | print("旋转角度 == \(-gestureRec.rotation * 180 / .pi)") 228 | beginRotation(withAngle: (-gestureRec.rotation * 180 / .pi), beiginPiont: gestureRec.beginPoint) 229 | } 230 | 231 | /// 开始旋转 232 | /// 233 | /// - Parameters: 234 | /// - angle: 角度 235 | /// - point: 点 236 | func beginRotation(withAngle angle: CGFloat, beiginPiont point: CGPoint) { 237 | switch rotationType { 238 | case RotationType.StartAngle: 239 | changeStartAngle(angle) 240 | case RotationType.EndAngle: 241 | changeEndAngle(angle) 242 | case RotationType.CircularingLocation: 243 | changeCircularingLocation(angle) 244 | default: 245 | break 246 | } 247 | } 248 | 249 | 250 | /// 改变起始时间 251 | /// 252 | /// - Parameter startAngle: 开始角度 253 | func changeStartAngle(_ startAngle: CGFloat) { 254 | 255 | var start = startAngle 256 | 257 | print("角度差 = \(abs(endAngle - self.startAngle - startAngle))") 258 | 259 | if abs(endAngle - self.startAngle - startAngle) > 360 { 260 | //修复BUG 261 | // if startAngle > 0 { 262 | // startAngle = startAngle - 360 263 | // } else { 264 | // startAngle = startAngle + 360 265 | // } 266 | 267 | 268 | if startAngle > 0 { 269 | start = startAngle - 360 270 | }else{ 271 | start = startAngle + 360 272 | } 273 | } 274 | print("角度差2 = \(abs(endAngle - self.startAngle - start))") 275 | sleepSuperView.transform = CGAffineTransform(rotationAngle: DgreesToRadoans(x: self.startAngle + start)) //公转 276 | sleepView.transform = CGAffineTransform(rotationAngle: -DgreesToRadoans(x: self.startAngle + start)) //自转 277 | self.fanLayer.path = drawAlarmPath(withStartAngle: start + self.startAngle, endAngle: endAngle)?.cgPath 278 | 279 | } 280 | 281 | /// 改变结束时间 282 | /// 283 | /// - Parameter endAngle: 结束角度 284 | func changeEndAngle(_ endAngle: CGFloat) { 285 | 286 | var end = endAngle 287 | 288 | if abs(Float(startAngle - self.endAngle - endAngle)) > 360 { 289 | 290 | // if endAngle > 0 { 291 | // endAngle = endAngle - 360 292 | // } else { 293 | // endAngle = endAngle + 360 294 | // } 295 | 296 | 297 | if endAngle > 0 { 298 | end = endAngle - 360 299 | }else{ 300 | end = endAngle + 360 301 | } 302 | } 303 | print("角度差 = \(abs(Float(self.endAngle - startAngle - endAngle)))") 304 | ringSuperView.transform = CGAffineTransform(rotationAngle: DgreesToRadoans(x: self.endAngle + end)) 305 | ringView.transform = CGAffineTransform(rotationAngle: -DgreesToRadoans(x: self.endAngle + end)) 306 | fanLayer.path = drawAlarmPath(withStartAngle: startAngle, endAngle: self.endAngle + end)?.cgPath 307 | } 308 | 309 | /// 改变圆环位置 310 | /// 311 | /// - Parameter angle: 角度 312 | func changeCircularingLocation(_ angle: CGFloat) { 313 | sleepSuperView.transform = CGAffineTransform(rotationAngle: DgreesToRadoans(x: startAngle + angle)) //公转 314 | sleepView.transform = CGAffineTransform(rotationAngle: -DgreesToRadoans(x: startAngle + angle)) //自转 315 | ringSuperView.transform = CGAffineTransform(rotationAngle: DgreesToRadoans(x: endAngle + angle)) 316 | ringView.transform = CGAffineTransform(rotationAngle: -DgreesToRadoans(x: endAngle + angle)) 317 | fanLayer.path = drawAlarmPath(withStartAngle: startAngle + angle, endAngle: endAngle + angle)?.cgPath 318 | } 319 | 320 | } 321 | 322 | 323 | // MARK: - Custom Method 324 | extension AlarmClockView { 325 | 326 | 327 | /// 配置UI 328 | private func configUI() { 329 | 330 | self.layer.addSublayer(lowestLayer) 331 | //self.alarmView.layer.addSublayer(fanLayer) 332 | 333 | // 添加渐变层 334 | self.alarmView.layer.addSublayer(gradientLayer) 335 | // 设置遮罩 336 | gradientLayer.mask = fanLayer 337 | 338 | alarmView.transform = CGAffineTransform(rotationAngle: -.pi / 2) 339 | 340 | addSubview(alarmView) 341 | addSubview(ringSuperView) 342 | addSubview(sleepSuperView) 343 | addSubview(timeScaleView) 344 | addSubview(finallyTimeLabel) 345 | 346 | ringSuperView.addSubview(ringView) 347 | sleepSuperView.addSubview(sleepView) 348 | 349 | } 350 | /// 配置手势 351 | private func configGesture() { 352 | addGestureRecognizer(gestureRecognizer) 353 | changeStartAngle(0) 354 | changeEndAngle(0) 355 | } 356 | 357 | /// 绘制BezierPath 358 | /// 359 | /// - Parameters: 360 | /// - startAngle: 起始角度 361 | /// - endAngle: 结束角度 362 | /// - Returns: UIBezierPath 363 | func drawAlarmPath(withStartAngle startAngle: CGFloat, endAngle: CGFloat) -> UIBezierPath? { 364 | 365 | let circleRect = CGRect(x: AlarmViewRadius, y: AlarmViewRadius, width: bounds.size.width, height: bounds.size.height) 366 | let circlePath = UIBezierPath() 367 | 368 | /* 369 | 可以画出一段弧线。 370 | 看下各个参数的意义: 371 | center:圆心的坐标 372 | radius:半径 373 | startAngle:起始的弧度 374 | endAngle:圆弧结束的弧度 375 | clockwise:true为顺时针,false为逆时针 376 | */ 377 | 378 | circlePath.addArc(withCenter: CGPoint(x: circleRect.midX, y: circleRect.midY), radius: circleRect.size.width / 2, startAngle: DgreesToRadoans(x: startAngle), endAngle: DgreesToRadoans(x: endAngle), clockwise: true) 379 | circlePath.addLine(to: CGPoint(x: circleRect.midX, y: circleRect.midY)) 380 | circlePath.close() 381 | currentStartAngle = fmodf(Float(startAngle), 360) 382 | currentEndAngle = fmodf(Float(endAngle), 360) 383 | finallyTimeLabel.attributedText = timeBlock(withAngle: Float(currentEndAngle - currentStartAngle)) 384 | bedTime = bedTime(withAngle: CGFloat(currentStartAngle)) ?? "" 385 | wakeTime = wakeTime(withAngle: CGFloat(currentEndAngle)) ?? "" 386 | self.delegate?.alarmViewIsChanged(withBedTime: bedTime, wakeTime: wakeTime) 387 | return circlePath 388 | } 389 | 390 | /// 旋转类型 391 | /// 392 | /// - Parameter piont: 点 393 | /// - Returns: RotationType 394 | func rotationType(withPiont piont: CGPoint) -> RotationType { 395 | /// 时钟视图中心点 396 | let alarmViewCenter = CGPoint(x: AlarmViewRadius, y: AlarmViewRadius) 397 | /// 起始中心点 398 | let startCenter = CGPoint(x: cos( CGFloat((currentStartAngle - 90) / 180) * .pi) * (AlarmViewRadius - IconViewWidth / 2) + AlarmViewRadius, y: sin( CGFloat((currentStartAngle - 90) / 180) * .pi) * (AlarmViewRadius - IconViewWidth / 2) + AlarmViewRadius) 399 | /// 结束中心点 400 | let endCenter = CGPoint(x: cos( CGFloat((currentEndAngle - 90) / 180) * .pi) * (AlarmViewRadius - IconViewWidth / 2) + AlarmViewRadius, y: sin( CGFloat((currentEndAngle - 90) / 180) * .pi) * (AlarmViewRadius - IconViewWidth / 2) + AlarmViewRadius) 401 | 402 | if UIView.distanceBetweenPointA(alarmViewCenter, andPiontB: piont) >= AlarmViewRadius - IconViewWidth && UIView.distanceBetweenPointA(alarmViewCenter, andPiontB: piont) <= AlarmViewRadius { 403 | if UIView.distanceBetweenPointA(startCenter, andPiontB: piont) < IconViewWidth / 2 { 404 | return RotationType.StartAngle 405 | }else if UIView.distanceBetweenPointA(endCenter, andPiontB: piont) < IconViewWidth / 2 { 406 | return RotationType.EndAngle 407 | }else { 408 | return RotationType.None 409 | } 410 | } 411 | return RotationType.None 412 | } 413 | 414 | 415 | /// 根据弧度(角度)转换成字符串 416 | /// 417 | /// - Parameter angle: 角度 418 | /// - Returns: 字符串 419 | func timeBlock(withAngle angle: Float) -> NSAttributedString? { 420 | var angle = angle 421 | angle = angle < 0 ? fmodf(720 + angle, 360) : fmodf(angle, 360) 422 | var hour: Int 423 | var minute: Int 424 | if abs(angle) < 30 { 425 | hour = 0 426 | minute = Int(angle / 30 * 60) 427 | } else { 428 | hour = Int(angle / 30) 429 | minute = Int(fmodf(angle, 30) / 30 * 60) 430 | } 431 | hour = hour % 12 < 0 ? (48 + hour) % 12 : hour % 12 432 | minute = minute % 60 < 0 ? (120 + minute) % 60 : minute % 60 433 | print("\(hour)小时\(minute)分钟") 434 | print("\(hour)HR\(minute)MIN") 435 | return attributeString(withHour: hour, minute: minute) 436 | } 437 | 438 | 439 | /// 根据角度(弧度)转换就寝时间 440 | /// 441 | /// - Parameter angle: 角度 442 | /// - Returns: 字符串 443 | func bedTime(withAngle angle: CGFloat) -> String? { 444 | 445 | var angle = angle 446 | // fmodf: 获得浮点数除法操作的余数 447 | angle = angle < 0 ? CGFloat(fmodf(Float(720 + angle), 360)) : CGFloat(fmodf(Float(angle), 360)) 448 | /// 小时 449 | var hour: Int 450 | /// 分钟 451 | var minute: Int 452 | 453 | ///返回给定数字的绝对值。 454 | /// 455 | /// - 参数x:带符号的数字。 456 | /// - 返回:`x`的绝对值。 457 | 458 | if abs(Float(angle)) < 30 { 459 | 460 | hour = 0 461 | minute = Int(angle / 30 * 60) 462 | } else { 463 | hour = Int(angle / 30) 464 | minute = Int(fmodf(Float(angle), 30) / 30 * 60) 465 | } 466 | hour = hour % 12 < 0 ? (48 + hour) % 12 : hour % 12 467 | minute = minute % 60 < 0 ? (120 + minute) % 60 : minute % 60 468 | return String(format: "%02d:%02d", hour, minute) 469 | } 470 | 471 | /// 根据角度(弧度)转换起床时间 472 | /// 473 | /// - Parameter angle: 角度 474 | /// - Returns: 字符串 475 | func wakeTime(withAngle angle: CGFloat) -> String? { 476 | 477 | var angle = angle 478 | angle = angle < 0 ? CGFloat(fmodf(Float(720 + angle), 360)) : CGFloat(fmodf(Float(angle), 360)) 479 | var hour: Int 480 | var minute: Int 481 | 482 | if abs(Float(angle)) < 30 { 483 | hour = 0 484 | minute = Int(angle / 30 * 60) 485 | } else { 486 | hour = Int(angle / 30) 487 | minute = Int(fmodf(Float(angle), 30) / 30 * 60) 488 | } 489 | hour = hour % 12 < 0 ? (48 + hour) % 12 : hour % 12 490 | minute = minute % 60 < 0 ? (120 + minute) % 60 : minute % 60 491 | return String(format: "%02d:%02d", hour, minute) 492 | } 493 | 494 | 495 | /// 根据时间(小时\分钟)转换成字符串 496 | /// 497 | /// - Parameters: 498 | /// - hour: 小时 499 | /// - minute: 分钟 500 | /// - Returns: 字符串 501 | func attributeString(withHour hour: Int, minute: Int) -> NSAttributedString { 502 | 503 | /// 初始化NSMutableAttributedString 504 | let attributedString = NSMutableAttributedString() 505 | // 设置字体格式和大小 506 | let dictAttr0 = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14), NSAttributedString.Key.foregroundColor: UIColor.white] 507 | let dictAttr1 = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 25), NSAttributedString.Key.foregroundColor: UIColor.white] 508 | let attr0 = NSAttributedString(string: String(format: " %2d", hour), attributes: dictAttr1) 509 | // let attr1 = NSAttributedString(string: " 小时", attributes: dictAttr0) 510 | let attr1 = NSAttributedString(string: " HR", attributes: dictAttr0) 511 | let attr2 = NSAttributedString(string: String(format: " %2d", minute), attributes: dictAttr1) 512 | // let attr3 = NSAttributedString(string: " 分钟", attributes: dictAttr0) 513 | let attr3 = NSAttributedString(string: " MIN", attributes: dictAttr0) 514 | 515 | attributedString.append(attr0) 516 | attributedString.append(attr1) 517 | attributedString.append(attr2) 518 | attributedString.append(attr3) 519 | return attributedString 520 | } 521 | 522 | // 扩展CGColor 523 | func cgColorFor(red: CGFloat, green: CGFloat, blue: CGFloat) -> CGColor { 524 | return UIColor(red: red/255.0, green: green/255.0, blue: blue/255.0, alpha: 1).cgColor 525 | } 526 | 527 | } 528 | 529 | 530 | // MARK: - RotaionGestureRecognizerDelegate 531 | extension AlarmClockView : RotaionGestureRecognizerDelegate { 532 | 533 | 534 | func touchesBeg(_ touches: Set, with event: UIEvent) { 535 | 536 | let startPiont: CGPoint? = touches.first?.location(in: self) 537 | 538 | print("\(startPiont?.x ?? 0.0)--\(startPiont?.y ?? 0.0)") 539 | 540 | rotationType = rotationType(withPiont: startPiont!) 541 | } 542 | 543 | func touchesMov(_ touches: Set, with event: UIEvent) { 544 | 545 | } 546 | 547 | func touchesCancell(_ touches: Set, with event: UIEvent) { 548 | rotationType = .None 549 | startAngle = CGFloat(currentStartAngle) 550 | endAngle = CGFloat(currentEndAngle) 551 | } 552 | 553 | 554 | func touchesEnd(_ touches: Set, with event: UIEvent) { 555 | rotationType = .None 556 | startAngle = CGFloat(currentStartAngle) 557 | endAngle = CGFloat(currentEndAngle) 558 | } 559 | 560 | } 561 | -------------------------------------------------------------------------------- /AlarmClock/AlarmClock/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AlarmClock 4 | // 5 | // Created by Liu Chuan on 2018/12/25. 6 | // Copyright © 2018 LC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /AlarmClock/AlarmClock/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /AlarmClock/AlarmClock/Assets.xcassets/Bedtime.imageset/Bedtime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevLiuSir/AlarmClock/9895c5c06342e693d3e67b2da7455b43a743d87b/AlarmClock/AlarmClock/Assets.xcassets/Bedtime.imageset/Bedtime.png -------------------------------------------------------------------------------- /AlarmClock/AlarmClock/Assets.xcassets/Bedtime.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Bedtime.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /AlarmClock/AlarmClock/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /AlarmClock/AlarmClock/Assets.xcassets/Wake.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Wake.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /AlarmClock/AlarmClock/Assets.xcassets/Wake.imageset/Wake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevLiuSir/AlarmClock/9895c5c06342e693d3e67b2da7455b43a743d87b/AlarmClock/AlarmClock/Assets.xcassets/Wake.imageset/Wake.png -------------------------------------------------------------------------------- /AlarmClock/AlarmClock/Assets.xcassets/clock.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "clock.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /AlarmClock/AlarmClock/Assets.xcassets/clock.imageset/clock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevLiuSir/AlarmClock/9895c5c06342e693d3e67b2da7455b43a743d87b/AlarmClock/AlarmClock/Assets.xcassets/clock.imageset/clock.png -------------------------------------------------------------------------------- /AlarmClock/AlarmClock/Assets.xcassets/ring.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ring.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /AlarmClock/AlarmClock/Assets.xcassets/ring.imageset/ring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevLiuSir/AlarmClock/9895c5c06342e693d3e67b2da7455b43a743d87b/AlarmClock/AlarmClock/Assets.xcassets/ring.imageset/ring.png -------------------------------------------------------------------------------- /AlarmClock/AlarmClock/Assets.xcassets/sleep.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "sleep.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /AlarmClock/AlarmClock/Assets.xcassets/sleep.imageset/sleep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevLiuSir/AlarmClock/9895c5c06342e693d3e67b2da7455b43a743d87b/AlarmClock/AlarmClock/Assets.xcassets/sleep.imageset/sleep.png -------------------------------------------------------------------------------- /AlarmClock/AlarmClock/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /AlarmClock/AlarmClock/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /AlarmClock/AlarmClock/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIViewControllerBasedStatusBarAppearance 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarStyle 34 | UIStatusBarStyleLightContent 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /AlarmClock/AlarmClock/RotaionGestureRecognizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RotaionGestureRecognizer.swift 3 | // AlarmClock 4 | // 5 | // Created by Liu Chuan on 2018/12/27. 6 | // Copyright © 2018 LC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | /// 旋转手势代理 13 | @objc protocol RotaionGestureRecognizerDelegate: NSObjectProtocol { 14 | 15 | /// 手指按下事件 16 | /// 17 | /// - Parameters: 18 | /// - touches: 触摸 19 | /// - event: 事件 20 | @objc optional func touchesBeg(_ touches: Set, with event: UIEvent) 21 | 22 | /// 手指移动事件 23 | /// 24 | /// - Parameters: 25 | /// - touches: 触摸 26 | /// - event: 事件 27 | @objc optional func touchesMov(_ touches: Set, with event: UIEvent) 28 | 29 | /// 手指抬起事件 30 | /// 31 | /// - Parameters: 32 | /// - touches: 触摸 33 | /// - event: 事件 34 | @objc optional func touchesEnd(_ touches: Set, with event: UIEvent) 35 | 36 | /// 意外中断事件(如打电话打扰) 37 | /// 38 | /// - Parameters: 39 | /// - touches: 触摸 40 | /// - event: 事件 41 | @objc optional func touchesCancell(_ touches: Set, with event: UIEvent) 42 | 43 | } 44 | 45 | 46 | /// 旋转手势类 47 | class RotaionGestureRecognizer: UIGestureRecognizer { 48 | 49 | /// 当前旋转角度 50 | var currentRotation: CGFloat = 0.0 51 | /// 下一个旋转角度 52 | var previousRotation: CGFloat = 0.0 53 | /// 起始点 54 | var startingPoint = CGPoint.zero 55 | /// 结束点 56 | var endPoint = CGPoint.zero 57 | /// 中心点 58 | var center = CGPoint.zero 59 | 60 | /// 旋转偏移值 61 | var rotation: CGFloat { 62 | get { 63 | return currentRotation + previousRotation 64 | } 65 | } 66 | 67 | /// 起始点 68 | var beginPoint: CGPoint { 69 | get { 70 | return startingPoint 71 | } 72 | } 73 | 74 | /// 旋转手势代理 75 | weak var rotaionGestureRecognizerDelegate: RotaionGestureRecognizerDelegate? 76 | 77 | /// 初始化 center:旋转中心点 78 | /// 79 | /// - Parameters: 80 | /// - center: 中心点 81 | /// - target: 目标 82 | /// - action: 动作 83 | init(center: CGPoint, target: Any?, action: Selector?) { 84 | super.init(target: target, action: action) 85 | self.center = center 86 | } 87 | 88 | // func resetRotation() { 89 | // currentRotation = 0 90 | // previousRotation = 0 91 | // } 92 | 93 | /**** 复位动作 **** (拖动结束后自动重置) */ 94 | //你需要在touchesEnded 之后 touchesBegan 之前调用 reset() 。这可以让手势识别器清除它的状态然后重新开始。 95 | override func reset() { 96 | super.reset() 97 | // _previousRotation = [self rotation]; 98 | previousRotation = 0 99 | currentRotation = 0 100 | } 101 | } 102 | 103 | 104 | 105 | // MARK: - Event Response Method 106 | extension RotaionGestureRecognizer { 107 | 108 | override func touchesBegan(_ touches: Set, with event: UIEvent) { 109 | super.touchesBegan(touches, with: event) 110 | 111 | startingPoint = (touches.first?.location(in: view))! 112 | state = UIGestureRecognizer.State.began 113 | rotaionGestureRecognizerDelegate?.touchesBeg!(touches, with: event) 114 | } 115 | 116 | override func touchesMoved(_ touches: Set, with event: UIEvent) { 117 | super.touchesMoved(touches, with: event) 118 | 119 | let point: CGPoint = (touches.first?.location(in: view))! 120 | currentRotation = UIView.angleBetweenPoint1(startingPoint, point2: point, andCenter: center) 121 | state = UIGestureRecognizer.State.changed 122 | rotaionGestureRecognizerDelegate?.touchesMov!(touches, with: event) 123 | } 124 | 125 | override func touchesEnded(_ touches: Set, with event: UIEvent) { 126 | super.touchesEnded(touches, with: event) 127 | 128 | endPoint = (touches.first?.location(in: view))! 129 | currentRotation = UIView.angleBetweenPoint1(startingPoint, point2: endPoint, andCenter: center) 130 | state = UIGestureRecognizer.State.ended 131 | rotaionGestureRecognizerDelegate?.touchesEnd!(touches, with: event) 132 | } 133 | 134 | 135 | override func touchesCancelled(_ touches: Set, with event: UIEvent) { 136 | super.touchesCancelled(touches, with: event) 137 | 138 | state = UIGestureRecognizer.State.cancelled 139 | rotaionGestureRecognizerDelegate?.touchesCancell!(touches, with: event) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /AlarmClock/AlarmClock/UIView+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Extension.swift 3 | // AlarmClock 4 | // 5 | // Created by Liu Chuan on 2018/12/27. 6 | // Copyright © 2018 LC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIView { 12 | 13 | /// 时钟视图的半径 14 | var AlarmViewRadius: CGFloat { 15 | get { 16 | return bounds.width / 2 17 | } 18 | } 19 | 20 | /// 时钟视图的高度 21 | var AlarmViewWH: CGFloat { 22 | get { 23 | return bounds.width 24 | } 25 | } 26 | 27 | /// 两个坐标点的角度 28 | /// 29 | /// - Parameters: 30 | /// - first: 第一个点 31 | /// - second: 第二个点 32 | /// - center: 中心点 33 | /// - Returns: CGFloat 34 | class func angleBetweenPoint1(_ first: CGPoint, point2 second: CGPoint, andCenter center: CGPoint) -> CGFloat { 35 | 36 | let centeredPoint1 = CGPoint(x: first.x - center.x, y: first.y - center.y) 37 | let centeredPoint2 = CGPoint(x: second.x - center.x, y: second.y - center.y) 38 | let firstAngle: CGFloat = angleBetweenOriginAndPointA(centeredPoint1) 39 | let secondAngle: CGFloat = angleBetweenOriginAndPointA(centeredPoint2) 40 | let rads: CGFloat = secondAngle - firstAngle 41 | return rads 42 | } 43 | 44 | /// 两点的距离 45 | /// 46 | /// - Parameters: 47 | /// - pointA: 第一个点A 48 | /// - pointB: 第二个点B 49 | /// - Returns: CGFloat 50 | class func distanceBetweenPointA(_ pointA: CGPoint, andPiontB pointB: CGPoint) -> CGFloat { 51 | 52 | let a: CGFloat = pow(pointB.x - pointA.x, 2) 53 | let b: CGFloat = pow(pointB.y - pointA.y, 2) 54 | return sqrt(a + b) 55 | } 56 | 57 | //* 某点和原点间的角度 58 | class func angleBetweenOriginAndPointA(_ p: CGPoint) -> CGFloat { 59 | 60 | if p.x == 0 { 61 | return CGFloat(signA(num: p.y)) * .pi 62 | } 63 | 64 | // '-' because negative ordinates are positive in UIKit 65 | var angle = atan(-p.y / p.x) 66 | 67 | // atan() is defined in [-pi/2, pi/2], but we want a value in [0, 2*pi] 68 | // so we deal with these special cases accordingly 69 | switch quadrantForPointA(p: p) { 70 | case 1, 2: 71 | angle += .pi 72 | case 3: 73 | angle += 2 * .pi 74 | default: 75 | break 76 | } 77 | return angle 78 | } 79 | 80 | //* 点的象限 81 | class func quadrantForPointA(p: CGPoint) -> Int { 82 | if p.x > 0 && p.y < 0 { 83 | return 0 84 | } else if p.x < 0 && p.y < 0 { 85 | return 1 86 | } else if p.x < 0 && p.y > 0 { 87 | return 2 88 | } else if p.x > 0 && p.y > 0 { 89 | return 3 90 | } 91 | return 0 92 | } 93 | 94 | class func signA(num: CGFloat) -> Int { 95 | if num == 0 { 96 | return 0 97 | } else if num > 0 { 98 | return 1 99 | } else { 100 | return -1 101 | } 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /AlarmClock/AlarmClock/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // AlarmClock 4 | // 5 | // Created by Liu Chuan on 2018/12/25. 6 | // Copyright © 2018 LC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | /// 屏幕的宽度 13 | private let screenW: CGFloat = UIScreen.main.bounds.width 14 | /// 屏幕的高度 15 | private let screenH: CGFloat = UIScreen.main.bounds.height 16 | /// 屏幕宽度的一半 17 | private let screenWidthHalf: CGFloat = screenW / 2 18 | /// 屏幕高度的一半 19 | private let screenHeightHalf: CGFloat = screenH / 2 20 | /// 标签的宽度 21 | private let labelWidth: CGFloat = 120 22 | /// 标签的高度 23 | private let labelHeight: CGFloat = 30 24 | /// 闹钟视图的高度 25 | private let alarmClockViewHeight: CGFloat = 350 26 | /// 闹钟视图的X值 27 | let alarmViewX: CGFloat = screenW - alarmClockViewHeight / 2 28 | /// 闹钟视图的Y值 29 | let alarmViewY: CGFloat = screenH - alarmClockViewHeight / 2 30 | 31 | 32 | class ViewController: UIViewController { 33 | 34 | 35 | // MARK: - Lazy Loading 36 | /// 就寝标签 37 | // private lazy var bedLabel: UILabel = { 38 | // let label = UILabel(frame: CGRect(x: (screenWidthHalf - labelWidth)/2, y: 140, width: labelWidth, height: labelHeight)) 39 | // label.text = "Bedtime" 40 | // label.textColor = .white 41 | // label.font = UIFont.boldSystemFont(ofSize: 22) 42 | // label.textAlignment = .center 43 | // label.numberOfLines = 1 44 | // return label 45 | // }() 46 | 47 | /// 就寝图片 48 | private lazy var bedImage: UIImageView = { 49 | let imag = UIImageView(frame: CGRect(x: (screenWidthHalf - labelWidth)/2, y: 140, width: labelWidth, height: 30)) 50 | imag.image = UIImage(named: "Bedtime") 51 | imag.contentMode = .scaleAspectFill 52 | imag.clipsToBounds = true // 图像是否裁切 53 | return imag 54 | }() 55 | 56 | /// 就寝时间标签 57 | private lazy var bedTimeLabel: UILabel = { 58 | let label = UILabel(frame: CGRect(x: (screenWidthHalf - labelWidth)/2, y: 180, width: labelWidth, height: labelHeight)) 59 | label.text = alarmClockView.bedTime 60 | label.textColor = .white 61 | label.font = UIFont.systemFont(ofSize: 35) 62 | label.textAlignment = .center 63 | label.numberOfLines = 1 64 | return label 65 | }() 66 | 67 | // /// 起床标签 68 | // private lazy var wakeLabel: UILabel = { 69 | // let label = UILabel(frame: CGRect(x: screenWidthHalf + (screenWidthHalf - labelWidth)/2, y: 140, width: labelWidth, height: labelHeight)) 70 | // label.text = "Wake" 71 | // label.textColor = .white 72 | // label.font = UIFont.boldSystemFont(ofSize: 22) 73 | // label.textAlignment = .center 74 | // label.numberOfLines = 1 75 | // return label 76 | // }() 77 | 78 | 79 | /// 起床图片 80 | private lazy var wakeImage: UIImageView = { 81 | let imag = UIImageView(frame: CGRect(x: screenWidthHalf + (screenWidthHalf - labelWidth)/2, y: 140, width: labelWidth, height: 30)) 82 | imag.image = UIImage(named: "Wake") 83 | imag.contentMode = .scaleAspectFill 84 | imag.clipsToBounds = true // 图像是否裁切 85 | return imag 86 | }() 87 | 88 | /// 起床时间标签 89 | private lazy var wakeTimeLabel: UILabel = { 90 | let label = UILabel(frame: CGRect(x: screenWidthHalf + (screenWidthHalf - labelWidth)/2, y: 180, width: labelWidth, height: labelHeight)) 91 | label.text = alarmClockView.wakeTime 92 | label.textColor = .white 93 | label.font = UIFont.systemFont(ofSize: 35) 94 | label.textAlignment = .center 95 | label.numberOfLines = 1 96 | return label 97 | }() 98 | 99 | /// 闹钟视图 100 | private lazy var alarmClockView: AlarmClockView = { [unowned self] in 101 | let alarmClockView = AlarmClockView(frame: CGRect(x: alarmViewX, y: alarmViewY, width: alarmClockViewHeight, height: alarmClockViewHeight), startAngle: 45, endAngle: 90) 102 | alarmClockView.center = view.center 103 | alarmClockView.alpha = 1 104 | alarmClockView.backgroundColor = .clear 105 | alarmClockView.delegate = self 106 | return alarmClockView 107 | }() 108 | 109 | 110 | // MARK: - Life Cycle 111 | override func viewDidLoad() { 112 | super.viewDidLoad() 113 | 114 | view.backgroundColor = UIColor.black 115 | 116 | view.addSubview(alarmClockView) 117 | // view.addSubview(bedLabel) 118 | // view.addSubview(wakeLabel) 119 | 120 | 121 | view.addSubview(bedImage) 122 | view.addSubview(wakeImage) 123 | view.addSubview(bedTimeLabel) 124 | view.addSubview(wakeTimeLabel) 125 | 126 | 127 | } 128 | } 129 | 130 | 131 | // MARK: - AlarmViewDelegate 132 | extension ViewController : AlarmViewDelegate { 133 | 134 | func alarmViewIsChanged(withBedTime bedTime: String?, wakeTime: String?) { 135 | bedTimeLabel.text = bedTime 136 | wakeTimeLabel.text = wakeTime 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 ChinaHackers 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | 6 |

AlarmClock is a case that mimics the iOS bedtime.

7 | 8 | --- 9 | ![language](https://img.shields.io/badge/language-swift-orange.svg) 10 | [![Swift version](https://img.shields.io/badge/swift-5.1+-blue.svg?style=flat)](https://developer.apple.com/swift/) 11 | ![xcode version](https://img.shields.io/badge/xcode-11.2+-red.svg) 12 | ![build](https://img.shields.io/appveyor/ci/gruntjs/grunt.svg) 13 | ![platform](https://img.shields.io/badge/platform-ios-lightgrey.svg) 14 | ![https://github.com/ChinaHackers/AlarmClock/blob/master/LICENSE](https://img.shields.io/github/license/ChinaHackers/AlarmClock.svg) 15 | ![Github Star](https://img.shields.io/github/stars/ChinaHackers/AlarmClock.svg?style=social&label=Star) 16 | [![Twitter Follow](https://img.shields.io/twitter/follow/LiuChuan_.svg?style=social)](https://twitter.com/LiuChuan_) 17 | 18 | ### Screencast from our Demo 19 | 20 | ![](https://github.com/ChinaHackers/AlarmClock/raw/master/Screencast/preview.gif) 21 | 22 | 23 | ### Author 24 | | [](https://github.com/ChinaHackers) | [Mr Liu](https://github.com/ChinaHackers)

Software Engineer
[![Twitter][1.1]][1] [![Github][2.1]][2] [![LinkedIn][3.1]][3] | 25 | | :------------: | :------------: | 26 | 27 | [1.1]: http://i.imgur.com/wWzX9uB.png (twitter icon without padding) 28 | [2.1]: http://i.imgur.com/9I6NRUm.png (github icon without padding) 29 | [3.1]: https://www.kingsfund.org.uk/themes/custom/kingsfund/dist/img/svg/sprite-icon-linkedin.svg (linkedin icon) 30 | 31 | [1]: https://twitter.com/LiuChuan_ 32 | [2]: https://github.com/ChinaHackers 33 | [3]: https://www.linkedin.com/in/chuan-liu-00359115a/ 34 | -------------------------------------------------------------------------------- /Screencast/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevLiuSir/AlarmClock/9895c5c06342e693d3e67b2da7455b43a743d87b/Screencast/preview.gif -------------------------------------------------------------------------------- /Screencast/preview01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevLiuSir/AlarmClock/9895c5c06342e693d3e67b2da7455b43a743d87b/Screencast/preview01.png --------------------------------------------------------------------------------