├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── AMClockView.podspec
├── AMClockView.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ └── AMClockView.xcscheme
├── AMClockView
├── AMClockView.h
└── Info.plist
├── LICENSE
├── Package.swift
├── README.md
├── SampleAMClock
├── SampleAMClock.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── SampleAMClock
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Classes
│ │ ├── AppDelegate.swift
│ │ └── ViewController.swift
│ ├── Info.plist
│ └── image
│ │ ├── clock2@2x.png
│ │ ├── clock@2x.png
│ │ ├── h@2x.png
│ │ ├── hourhand2@2x.png
│ │ ├── hourhand@2x.png
│ │ ├── m@2x.png
│ │ ├── minutehand2@2x.png
│ │ └── minutehand@2x.png
└── SampleAMClockTests
│ ├── Info.plist
│ └── SampleAMClockTests.swift
└── Source
└── AMClockView.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode (from gitignore.io)
2 | build/
3 | *.pbxuser
4 | !default.pbxuser
5 | *.mode1v3
6 | !default.mode1v3
7 | *.mode2v3
8 | !default.mode2v3
9 | *.perspectivev3
10 | !default.perspectivev3
11 | xcuserdata
12 | *.xccheckout
13 | *.moved-aside
14 | DerivedData
15 | *.hmap
16 | *.ipa
17 | *.xcuserstate
18 |
19 | # CocoaPod
20 | Pods/*
21 | Podfile.lock
22 |
23 | # Carthage
24 | Carthage/Build
25 |
26 | # others
27 | *.swp
28 | !.gitkeep
29 | .DS_Store
30 | UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/AMClockView.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "AMClockView"
3 | s.version = "2.1.0"
4 | s.summary = "AMClockView is a view can select time."
5 | s.license = { :type => 'MIT', :file => 'LICENSE' }
6 | s.homepage = "https://github.com/adventam10/AMClockView"
7 | s.author = { "am10" => "adventam10@gmail.com" }
8 | s.source = { :git => "https://github.com/adventam10/AMClockView.git", :tag => "#{s.version}" }
9 | s.platform = :ios, "9.0"
10 | s.requires_arc = true
11 | s.source_files = 'Source/*.{swift}'
12 | s.swift_version = "5.0"
13 | end
14 |
--------------------------------------------------------------------------------
/AMClockView.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 48;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 0E47526C201E12F3002EA382 /* AMClockView.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E47526A201E12F2002EA382 /* AMClockView.h */; settings = {ATTRIBUTES = (Public, ); }; };
11 | 0EB234D82354933300442A85 /* AMClockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB234D72354933300442A85 /* AMClockView.swift */; };
12 | /* End PBXBuildFile section */
13 |
14 | /* Begin PBXFileReference section */
15 | 0E475267201E12F2002EA382 /* AMClockView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AMClockView.framework; sourceTree = BUILT_PRODUCTS_DIR; };
16 | 0E47526A201E12F2002EA382 /* AMClockView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AMClockView.h; sourceTree = ""; };
17 | 0E47526B201E12F3002EA382 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
18 | 0EB234D72354933300442A85 /* AMClockView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AMClockView.swift; sourceTree = ""; };
19 | /* End PBXFileReference section */
20 |
21 | /* Begin PBXFrameworksBuildPhase section */
22 | 0E475263201E12F2002EA382 /* Frameworks */ = {
23 | isa = PBXFrameworksBuildPhase;
24 | buildActionMask = 2147483647;
25 | files = (
26 | );
27 | runOnlyForDeploymentPostprocessing = 0;
28 | };
29 | /* End PBXFrameworksBuildPhase section */
30 |
31 | /* Begin PBXGroup section */
32 | 0E47525D201E12F2002EA382 = {
33 | isa = PBXGroup;
34 | children = (
35 | 0EB234D62354933300442A85 /* Source */,
36 | 0E475269201E12F2002EA382 /* AMClockView */,
37 | 0E475268201E12F2002EA382 /* Products */,
38 | );
39 | sourceTree = "";
40 | };
41 | 0E475268201E12F2002EA382 /* Products */ = {
42 | isa = PBXGroup;
43 | children = (
44 | 0E475267201E12F2002EA382 /* AMClockView.framework */,
45 | );
46 | name = Products;
47 | sourceTree = "";
48 | };
49 | 0E475269201E12F2002EA382 /* AMClockView */ = {
50 | isa = PBXGroup;
51 | children = (
52 | 0E47526A201E12F2002EA382 /* AMClockView.h */,
53 | 0E47526B201E12F3002EA382 /* Info.plist */,
54 | );
55 | path = AMClockView;
56 | sourceTree = "";
57 | };
58 | 0EB234D62354933300442A85 /* Source */ = {
59 | isa = PBXGroup;
60 | children = (
61 | 0EB234D72354933300442A85 /* AMClockView.swift */,
62 | );
63 | path = Source;
64 | sourceTree = "";
65 | };
66 | /* End PBXGroup section */
67 |
68 | /* Begin PBXHeadersBuildPhase section */
69 | 0E475264201E12F2002EA382 /* Headers */ = {
70 | isa = PBXHeadersBuildPhase;
71 | buildActionMask = 2147483647;
72 | files = (
73 | 0E47526C201E12F3002EA382 /* AMClockView.h in Headers */,
74 | );
75 | runOnlyForDeploymentPostprocessing = 0;
76 | };
77 | /* End PBXHeadersBuildPhase section */
78 |
79 | /* Begin PBXNativeTarget section */
80 | 0E475266201E12F2002EA382 /* AMClockView */ = {
81 | isa = PBXNativeTarget;
82 | buildConfigurationList = 0E47526F201E12F3002EA382 /* Build configuration list for PBXNativeTarget "AMClockView" */;
83 | buildPhases = (
84 | 0E475262201E12F2002EA382 /* Sources */,
85 | 0E475263201E12F2002EA382 /* Frameworks */,
86 | 0E475264201E12F2002EA382 /* Headers */,
87 | 0E475265201E12F2002EA382 /* Resources */,
88 | );
89 | buildRules = (
90 | );
91 | dependencies = (
92 | );
93 | name = AMClockView;
94 | productName = AMClockView;
95 | productReference = 0E475267201E12F2002EA382 /* AMClockView.framework */;
96 | productType = "com.apple.product-type.framework";
97 | };
98 | /* End PBXNativeTarget section */
99 |
100 | /* Begin PBXProject section */
101 | 0E47525E201E12F2002EA382 /* Project object */ = {
102 | isa = PBXProject;
103 | attributes = {
104 | LastUpgradeCheck = 1020;
105 | ORGANIZATIONNAME = am10;
106 | TargetAttributes = {
107 | 0E475266201E12F2002EA382 = {
108 | CreatedOnToolsVersion = 9.2;
109 | LastSwiftMigration = 1020;
110 | ProvisioningStyle = Automatic;
111 | };
112 | };
113 | };
114 | buildConfigurationList = 0E475261201E12F2002EA382 /* Build configuration list for PBXProject "AMClockView" */;
115 | compatibilityVersion = "Xcode 8.0";
116 | developmentRegion = en;
117 | hasScannedForEncodings = 0;
118 | knownRegions = (
119 | en,
120 | Base,
121 | );
122 | mainGroup = 0E47525D201E12F2002EA382;
123 | productRefGroup = 0E475268201E12F2002EA382 /* Products */;
124 | projectDirPath = "";
125 | projectRoot = "";
126 | targets = (
127 | 0E475266201E12F2002EA382 /* AMClockView */,
128 | );
129 | };
130 | /* End PBXProject section */
131 |
132 | /* Begin PBXResourcesBuildPhase section */
133 | 0E475265201E12F2002EA382 /* Resources */ = {
134 | isa = PBXResourcesBuildPhase;
135 | buildActionMask = 2147483647;
136 | files = (
137 | );
138 | runOnlyForDeploymentPostprocessing = 0;
139 | };
140 | /* End PBXResourcesBuildPhase section */
141 |
142 | /* Begin PBXSourcesBuildPhase section */
143 | 0E475262201E12F2002EA382 /* Sources */ = {
144 | isa = PBXSourcesBuildPhase;
145 | buildActionMask = 2147483647;
146 | files = (
147 | 0EB234D82354933300442A85 /* AMClockView.swift in Sources */,
148 | );
149 | runOnlyForDeploymentPostprocessing = 0;
150 | };
151 | /* End PBXSourcesBuildPhase section */
152 |
153 | /* Begin XCBuildConfiguration section */
154 | 0E47526D201E12F3002EA382 /* Debug */ = {
155 | isa = XCBuildConfiguration;
156 | buildSettings = {
157 | ALWAYS_SEARCH_USER_PATHS = NO;
158 | CLANG_ANALYZER_NONNULL = YES;
159 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
160 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
161 | CLANG_CXX_LIBRARY = "libc++";
162 | CLANG_ENABLE_MODULES = YES;
163 | CLANG_ENABLE_OBJC_ARC = YES;
164 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
165 | CLANG_WARN_BOOL_CONVERSION = YES;
166 | CLANG_WARN_COMMA = YES;
167 | CLANG_WARN_CONSTANT_CONVERSION = YES;
168 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
169 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
170 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
171 | CLANG_WARN_EMPTY_BODY = YES;
172 | CLANG_WARN_ENUM_CONVERSION = YES;
173 | CLANG_WARN_INFINITE_RECURSION = YES;
174 | CLANG_WARN_INT_CONVERSION = YES;
175 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
176 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
177 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
178 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
179 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
180 | CLANG_WARN_STRICT_PROTOTYPES = YES;
181 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
182 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
183 | CLANG_WARN_UNREACHABLE_CODE = YES;
184 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
185 | CODE_SIGN_IDENTITY = "iPhone Developer";
186 | COPY_PHASE_STRIP = NO;
187 | CURRENT_PROJECT_VERSION = 1;
188 | DEBUG_INFORMATION_FORMAT = dwarf;
189 | ENABLE_STRICT_OBJC_MSGSEND = YES;
190 | ENABLE_TESTABILITY = YES;
191 | GCC_C_LANGUAGE_STANDARD = gnu11;
192 | GCC_DYNAMIC_NO_PIC = NO;
193 | GCC_NO_COMMON_BLOCKS = YES;
194 | GCC_OPTIMIZATION_LEVEL = 0;
195 | GCC_PREPROCESSOR_DEFINITIONS = (
196 | "DEBUG=1",
197 | "$(inherited)",
198 | );
199 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
200 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
201 | GCC_WARN_UNDECLARED_SELECTOR = YES;
202 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
203 | GCC_WARN_UNUSED_FUNCTION = YES;
204 | GCC_WARN_UNUSED_VARIABLE = YES;
205 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
206 | MTL_ENABLE_DEBUG_INFO = YES;
207 | ONLY_ACTIVE_ARCH = YES;
208 | SDKROOT = iphoneos;
209 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
210 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
211 | VERSIONING_SYSTEM = "apple-generic";
212 | VERSION_INFO_PREFIX = "";
213 | };
214 | name = Debug;
215 | };
216 | 0E47526E201E12F3002EA382 /* Release */ = {
217 | isa = XCBuildConfiguration;
218 | buildSettings = {
219 | ALWAYS_SEARCH_USER_PATHS = NO;
220 | CLANG_ANALYZER_NONNULL = YES;
221 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
222 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
223 | CLANG_CXX_LIBRARY = "libc++";
224 | CLANG_ENABLE_MODULES = YES;
225 | CLANG_ENABLE_OBJC_ARC = YES;
226 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
227 | CLANG_WARN_BOOL_CONVERSION = YES;
228 | CLANG_WARN_COMMA = YES;
229 | CLANG_WARN_CONSTANT_CONVERSION = YES;
230 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
231 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
232 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
233 | CLANG_WARN_EMPTY_BODY = YES;
234 | CLANG_WARN_ENUM_CONVERSION = YES;
235 | CLANG_WARN_INFINITE_RECURSION = YES;
236 | CLANG_WARN_INT_CONVERSION = YES;
237 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
238 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
239 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
240 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
241 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
242 | CLANG_WARN_STRICT_PROTOTYPES = YES;
243 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
244 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
245 | CLANG_WARN_UNREACHABLE_CODE = YES;
246 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
247 | CODE_SIGN_IDENTITY = "iPhone Developer";
248 | COPY_PHASE_STRIP = NO;
249 | CURRENT_PROJECT_VERSION = 1;
250 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
251 | ENABLE_NS_ASSERTIONS = NO;
252 | ENABLE_STRICT_OBJC_MSGSEND = YES;
253 | GCC_C_LANGUAGE_STANDARD = gnu11;
254 | GCC_NO_COMMON_BLOCKS = YES;
255 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
256 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
257 | GCC_WARN_UNDECLARED_SELECTOR = YES;
258 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
259 | GCC_WARN_UNUSED_FUNCTION = YES;
260 | GCC_WARN_UNUSED_VARIABLE = YES;
261 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
262 | MTL_ENABLE_DEBUG_INFO = NO;
263 | SDKROOT = iphoneos;
264 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
265 | VALIDATE_PRODUCT = YES;
266 | VERSIONING_SYSTEM = "apple-generic";
267 | VERSION_INFO_PREFIX = "";
268 | };
269 | name = Release;
270 | };
271 | 0E475270201E12F3002EA382 /* Debug */ = {
272 | isa = XCBuildConfiguration;
273 | buildSettings = {
274 | CODE_SIGN_IDENTITY = "";
275 | CODE_SIGN_STYLE = Automatic;
276 | DEFINES_MODULE = YES;
277 | DEVELOPMENT_TEAM = "";
278 | DYLIB_COMPATIBILITY_VERSION = 1;
279 | DYLIB_CURRENT_VERSION = 1;
280 | DYLIB_INSTALL_NAME_BASE = "@rpath";
281 | INFOPLIST_FILE = AMClockView/Info.plist;
282 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
283 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
284 | PRODUCT_BUNDLE_IDENTIFIER = am10.AMClockView;
285 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
286 | SKIP_INSTALL = YES;
287 | SWIFT_VERSION = 5.0;
288 | TARGETED_DEVICE_FAMILY = "1,2";
289 | };
290 | name = Debug;
291 | };
292 | 0E475271201E12F3002EA382 /* Release */ = {
293 | isa = XCBuildConfiguration;
294 | buildSettings = {
295 | CODE_SIGN_IDENTITY = "";
296 | CODE_SIGN_STYLE = Automatic;
297 | DEFINES_MODULE = YES;
298 | DEVELOPMENT_TEAM = "";
299 | DYLIB_COMPATIBILITY_VERSION = 1;
300 | DYLIB_CURRENT_VERSION = 1;
301 | DYLIB_INSTALL_NAME_BASE = "@rpath";
302 | INFOPLIST_FILE = AMClockView/Info.plist;
303 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
304 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
305 | PRODUCT_BUNDLE_IDENTIFIER = am10.AMClockView;
306 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
307 | SKIP_INSTALL = YES;
308 | SWIFT_VERSION = 5.0;
309 | TARGETED_DEVICE_FAMILY = "1,2";
310 | };
311 | name = Release;
312 | };
313 | /* End XCBuildConfiguration section */
314 |
315 | /* Begin XCConfigurationList section */
316 | 0E475261201E12F2002EA382 /* Build configuration list for PBXProject "AMClockView" */ = {
317 | isa = XCConfigurationList;
318 | buildConfigurations = (
319 | 0E47526D201E12F3002EA382 /* Debug */,
320 | 0E47526E201E12F3002EA382 /* Release */,
321 | );
322 | defaultConfigurationIsVisible = 0;
323 | defaultConfigurationName = Release;
324 | };
325 | 0E47526F201E12F3002EA382 /* Build configuration list for PBXNativeTarget "AMClockView" */ = {
326 | isa = XCConfigurationList;
327 | buildConfigurations = (
328 | 0E475270201E12F3002EA382 /* Debug */,
329 | 0E475271201E12F3002EA382 /* Release */,
330 | );
331 | defaultConfigurationIsVisible = 0;
332 | defaultConfigurationName = Release;
333 | };
334 | /* End XCConfigurationList section */
335 | };
336 | rootObject = 0E47525E201E12F2002EA382 /* Project object */;
337 | }
338 |
--------------------------------------------------------------------------------
/AMClockView.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/AMClockView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/AMClockView.xcodeproj/xcshareddata/xcschemes/AMClockView.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/AMClockView/AMClockView.h:
--------------------------------------------------------------------------------
1 | //
2 | // AMClockView.h
3 | // AMClockView
4 | //
5 | // Created by am10 on 2018/01/28.
6 | // Copyright © 2018年 am10. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for AMClockView.
12 | FOUNDATION_EXPORT double AMClockViewVersionNumber;
13 |
14 | //! Project version string for AMClockView.
15 | FOUNDATION_EXPORT const unsigned char AMClockViewVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/AMClockView/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 adventam10
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 | //
4 | // AMClockView, https://github.com/adventam10/AMClockView
5 | //
6 | // Created by am10 on 2019/10/14.
7 | // Copyright © 2019年 am10. All rights reserved.
8 | //
9 |
10 | import PackageDescription
11 |
12 | let package = Package(name: "AMClockView",
13 | platforms: [.iOS(.v9)],
14 | products: [.library(name: "AMClockView",
15 | targets: ["AMClockView"])],
16 | targets: [.target(name: "AMClockView",
17 | path: "Source")],
18 | swiftLanguageVersions: [.v5])
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AMClockView
2 |
3 | 
4 | 
5 | [](http://cocoapods.org/pods/AMClockView)
6 | [](https://github.com/Carthage/Carthage)
7 | [](https://github.com/apple/swift-package-manager)
8 |
9 | `AMClockView` is a view can select time.
10 |
11 | ## Demo
12 |
13 | 
14 |
15 | ## Usage
16 |
17 | Create clockView.
18 |
19 | ```swift
20 | let clockView = AMClockView(frame: view.bounds)
21 |
22 | // customize here
23 |
24 | clockView.delegate = self
25 | view.addSubview(clockView)
26 | ```
27 |
28 | Conform to the protocol in the class implementation.
29 |
30 | ```swift
31 | func clockView(_ clockView: AMClockView, didChangeDate date: Date) {
32 | // use selected date here
33 | }
34 | ```
35 |
36 | The hour hand moves when you dragged inside of central circle.
37 |
38 | The minute hand moves when you draged outside of central circle.
39 |
40 | ### Customization
41 | `AMClockView` can be customized via the following properties.
42 |
43 | ```swift
44 | @IBInspectable public var clockBorderLineWidth: CGFloat = 5.0
45 | @IBInspectable public var smallClockIndexWidth: CGFloat = 1.0
46 | @IBInspectable public var clockIndexWidth: CGFloat = 2.0
47 | @IBInspectable public var hourHandWidth: CGFloat = 5.0
48 | @IBInspectable public var minuteHandWidth: CGFloat = 3.0
49 | @IBInspectable public var clockBorderLineColor: UIColor = .black
50 | @IBInspectable public var centerCircleLineColor: UIColor = .darkGray
51 | @IBInspectable public var hourHandColor: UIColor = .black
52 | @IBInspectable public var minuteHandColor: UIColor = .black
53 | @IBInspectable public var selectedTimeLabelTextColor: UIColor = .black
54 | @IBInspectable public var timeLabelTextColor: UIColor = .black
55 | @IBInspectable public var smallClockIndexColor: UIColor = .black
56 | @IBInspectable public var clockIndexColor: UIColor = .black
57 | @IBInspectable public var clockColor: UIColor = .clear
58 | @IBInspectable public var clockImage: UIImage?
59 | @IBInspectable public var minuteHandImage: UIImage?
60 | @IBInspectable public var hourHandImage: UIImage?
61 | @IBInspectable public var isShowSelectedTime: Bool = false
62 | public var clockType = AMCVClockType.arabic
63 | public var timeZone: TimeZone? // default is TimeZone.current
64 | public var selectedDate: Date?
65 | ```
66 |
67 | 
68 |
69 | ## Installation
70 |
71 | ### CocoaPods
72 |
73 | Add this to your Podfile.
74 |
75 | ```ogdl
76 | pod 'AMClockView'
77 | ```
78 |
79 | ### Carthage
80 |
81 | Add this to your Cartfile.
82 |
83 | ```ogdl
84 | github "adventam10/AMClockView"
85 | ```
86 |
87 | ## License
88 |
89 | MIT
90 |
91 |
--------------------------------------------------------------------------------
/SampleAMClock/SampleAMClock.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 48;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 0E47524C201DDCB4002EA382 /* clock2@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0E475244201DDCB4002EA382 /* clock2@2x.png */; };
11 | 0E47524D201DDCB4002EA382 /* clock@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0E475245201DDCB4002EA382 /* clock@2x.png */; };
12 | 0E47524E201DDCB4002EA382 /* h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0E475246201DDCB4002EA382 /* h@2x.png */; };
13 | 0E47524F201DDCB4002EA382 /* hourhand2@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0E475247201DDCB4002EA382 /* hourhand2@2x.png */; };
14 | 0E475250201DDCB4002EA382 /* hourhand@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0E475248201DDCB4002EA382 /* hourhand@2x.png */; };
15 | 0E475251201DDCB4002EA382 /* m@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0E475249201DDCB4002EA382 /* m@2x.png */; };
16 | 0E475252201DDCB4002EA382 /* minutehand2@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0E47524A201DDCB4002EA382 /* minutehand2@2x.png */; };
17 | 0E475253201DDCB4002EA382 /* minutehand@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0E47524B201DDCB4002EA382 /* minutehand@2x.png */; };
18 | 0E475258201E1211002EA382 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E475256201E1211002EA382 /* AppDelegate.swift */; };
19 | 0E475259201E1211002EA382 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E475257201E1211002EA382 /* ViewController.swift */; };
20 | 0EB234DB2354935200442A85 /* AMClockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB234DA2354935200442A85 /* AMClockView.swift */; };
21 | 0EB3D6382351CE450065073C /* SampleAMClockTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB3D6372351CE450065073C /* SampleAMClockTests.swift */; };
22 | 0EB809962000A2420090810F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0EB809942000A2420090810F /* Main.storyboard */; };
23 | 0EB809982000A2420090810F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0EB809972000A2420090810F /* Assets.xcassets */; };
24 | 0EB8099B2000A2420090810F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0EB809992000A2420090810F /* LaunchScreen.storyboard */; };
25 | /* End PBXBuildFile section */
26 |
27 | /* Begin PBXContainerItemProxy section */
28 | 0EB3D63A2351CE450065073C /* PBXContainerItemProxy */ = {
29 | isa = PBXContainerItemProxy;
30 | containerPortal = 0EB809852000A2420090810F /* Project object */;
31 | proxyType = 1;
32 | remoteGlobalIDString = 0EB8098C2000A2420090810F;
33 | remoteInfo = SampleAMClock;
34 | };
35 | /* End PBXContainerItemProxy section */
36 |
37 | /* Begin PBXFileReference section */
38 | 0E475244201DDCB4002EA382 /* clock2@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "clock2@2x.png"; sourceTree = ""; };
39 | 0E475245201DDCB4002EA382 /* clock@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "clock@2x.png"; sourceTree = ""; };
40 | 0E475246201DDCB4002EA382 /* h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "h@2x.png"; sourceTree = ""; };
41 | 0E475247201DDCB4002EA382 /* hourhand2@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "hourhand2@2x.png"; sourceTree = ""; };
42 | 0E475248201DDCB4002EA382 /* hourhand@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "hourhand@2x.png"; sourceTree = ""; };
43 | 0E475249201DDCB4002EA382 /* m@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "m@2x.png"; sourceTree = ""; };
44 | 0E47524A201DDCB4002EA382 /* minutehand2@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "minutehand2@2x.png"; sourceTree = ""; };
45 | 0E47524B201DDCB4002EA382 /* minutehand@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "minutehand@2x.png"; sourceTree = ""; };
46 | 0E475256201E1211002EA382 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
47 | 0E475257201E1211002EA382 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
48 | 0EB234DA2354935200442A85 /* AMClockView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AMClockView.swift; sourceTree = ""; };
49 | 0EB3D6352351CE450065073C /* SampleAMClockTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SampleAMClockTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
50 | 0EB3D6372351CE450065073C /* SampleAMClockTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleAMClockTests.swift; sourceTree = ""; };
51 | 0EB3D6392351CE450065073C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
52 | 0EB8098D2000A2420090810F /* SampleAMClock.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleAMClock.app; sourceTree = BUILT_PRODUCTS_DIR; };
53 | 0EB809952000A2420090810F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
54 | 0EB809972000A2420090810F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
55 | 0EB8099A2000A2420090810F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
56 | 0EB8099C2000A2420090810F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
57 | /* End PBXFileReference section */
58 |
59 | /* Begin PBXFrameworksBuildPhase section */
60 | 0EB3D6322351CE450065073C /* Frameworks */ = {
61 | isa = PBXFrameworksBuildPhase;
62 | buildActionMask = 2147483647;
63 | files = (
64 | );
65 | runOnlyForDeploymentPostprocessing = 0;
66 | };
67 | 0EB8098A2000A2420090810F /* Frameworks */ = {
68 | isa = PBXFrameworksBuildPhase;
69 | buildActionMask = 2147483647;
70 | files = (
71 | );
72 | runOnlyForDeploymentPostprocessing = 0;
73 | };
74 | /* End PBXFrameworksBuildPhase section */
75 |
76 | /* Begin PBXGroup section */
77 | 0E475243201DDCB4002EA382 /* image */ = {
78 | isa = PBXGroup;
79 | children = (
80 | 0E475244201DDCB4002EA382 /* clock2@2x.png */,
81 | 0E475245201DDCB4002EA382 /* clock@2x.png */,
82 | 0E475246201DDCB4002EA382 /* h@2x.png */,
83 | 0E475247201DDCB4002EA382 /* hourhand2@2x.png */,
84 | 0E475248201DDCB4002EA382 /* hourhand@2x.png */,
85 | 0E475249201DDCB4002EA382 /* m@2x.png */,
86 | 0E47524A201DDCB4002EA382 /* minutehand2@2x.png */,
87 | 0E47524B201DDCB4002EA382 /* minutehand@2x.png */,
88 | );
89 | path = image;
90 | sourceTree = "";
91 | };
92 | 0E475255201E1211002EA382 /* Classes */ = {
93 | isa = PBXGroup;
94 | children = (
95 | 0EB234D92354935200442A85 /* Source */,
96 | 0E475256201E1211002EA382 /* AppDelegate.swift */,
97 | 0E475257201E1211002EA382 /* ViewController.swift */,
98 | );
99 | path = Classes;
100 | sourceTree = "";
101 | };
102 | 0EB234D92354935200442A85 /* Source */ = {
103 | isa = PBXGroup;
104 | children = (
105 | 0EB234DA2354935200442A85 /* AMClockView.swift */,
106 | );
107 | name = Source;
108 | path = ../../../Source;
109 | sourceTree = "";
110 | };
111 | 0EB3D6362351CE450065073C /* SampleAMClockTests */ = {
112 | isa = PBXGroup;
113 | children = (
114 | 0EB3D6372351CE450065073C /* SampleAMClockTests.swift */,
115 | 0EB3D6392351CE450065073C /* Info.plist */,
116 | );
117 | path = SampleAMClockTests;
118 | sourceTree = "";
119 | };
120 | 0EB809842000A2420090810F = {
121 | isa = PBXGroup;
122 | children = (
123 | 0EB8098F2000A2420090810F /* SampleAMClock */,
124 | 0EB3D6362351CE450065073C /* SampleAMClockTests */,
125 | 0EB8098E2000A2420090810F /* Products */,
126 | );
127 | sourceTree = "";
128 | };
129 | 0EB8098E2000A2420090810F /* Products */ = {
130 | isa = PBXGroup;
131 | children = (
132 | 0EB8098D2000A2420090810F /* SampleAMClock.app */,
133 | 0EB3D6352351CE450065073C /* SampleAMClockTests.xctest */,
134 | );
135 | name = Products;
136 | sourceTree = "";
137 | };
138 | 0EB8098F2000A2420090810F /* SampleAMClock */ = {
139 | isa = PBXGroup;
140 | children = (
141 | 0E475255201E1211002EA382 /* Classes */,
142 | 0EB809942000A2420090810F /* Main.storyboard */,
143 | 0EB809972000A2420090810F /* Assets.xcassets */,
144 | 0EB809992000A2420090810F /* LaunchScreen.storyboard */,
145 | 0EB8099C2000A2420090810F /* Info.plist */,
146 | 0E475243201DDCB4002EA382 /* image */,
147 | );
148 | path = SampleAMClock;
149 | sourceTree = "";
150 | };
151 | /* End PBXGroup section */
152 |
153 | /* Begin PBXNativeTarget section */
154 | 0EB3D6342351CE450065073C /* SampleAMClockTests */ = {
155 | isa = PBXNativeTarget;
156 | buildConfigurationList = 0EB3D63E2351CE450065073C /* Build configuration list for PBXNativeTarget "SampleAMClockTests" */;
157 | buildPhases = (
158 | 0EB3D6312351CE450065073C /* Sources */,
159 | 0EB3D6322351CE450065073C /* Frameworks */,
160 | 0EB3D6332351CE450065073C /* Resources */,
161 | );
162 | buildRules = (
163 | );
164 | dependencies = (
165 | 0EB3D63B2351CE450065073C /* PBXTargetDependency */,
166 | );
167 | name = SampleAMClockTests;
168 | productName = SampleAMClockTests;
169 | productReference = 0EB3D6352351CE450065073C /* SampleAMClockTests.xctest */;
170 | productType = "com.apple.product-type.bundle.unit-test";
171 | };
172 | 0EB8098C2000A2420090810F /* SampleAMClock */ = {
173 | isa = PBXNativeTarget;
174 | buildConfigurationList = 0EB8099F2000A2420090810F /* Build configuration list for PBXNativeTarget "SampleAMClock" */;
175 | buildPhases = (
176 | 0EB809892000A2420090810F /* Sources */,
177 | 0EB8098A2000A2420090810F /* Frameworks */,
178 | 0EB8098B2000A2420090810F /* Resources */,
179 | );
180 | buildRules = (
181 | );
182 | dependencies = (
183 | );
184 | name = SampleAMClock;
185 | productName = SampleAMClock;
186 | productReference = 0EB8098D2000A2420090810F /* SampleAMClock.app */;
187 | productType = "com.apple.product-type.application";
188 | };
189 | /* End PBXNativeTarget section */
190 |
191 | /* Begin PBXProject section */
192 | 0EB809852000A2420090810F /* Project object */ = {
193 | isa = PBXProject;
194 | attributes = {
195 | LastSwiftUpdateCheck = 1100;
196 | LastUpgradeCheck = 1020;
197 | ORGANIZATIONNAME = am10;
198 | TargetAttributes = {
199 | 0EB3D6342351CE450065073C = {
200 | CreatedOnToolsVersion = 11.0;
201 | ProvisioningStyle = Automatic;
202 | TestTargetID = 0EB8098C2000A2420090810F;
203 | };
204 | 0EB8098C2000A2420090810F = {
205 | CreatedOnToolsVersion = 9.2;
206 | LastSwiftMigration = 1020;
207 | ProvisioningStyle = Automatic;
208 | };
209 | };
210 | };
211 | buildConfigurationList = 0EB809882000A2420090810F /* Build configuration list for PBXProject "SampleAMClock" */;
212 | compatibilityVersion = "Xcode 8.0";
213 | developmentRegion = en;
214 | hasScannedForEncodings = 0;
215 | knownRegions = (
216 | en,
217 | Base,
218 | );
219 | mainGroup = 0EB809842000A2420090810F;
220 | productRefGroup = 0EB8098E2000A2420090810F /* Products */;
221 | projectDirPath = "";
222 | projectRoot = "";
223 | targets = (
224 | 0EB8098C2000A2420090810F /* SampleAMClock */,
225 | 0EB3D6342351CE450065073C /* SampleAMClockTests */,
226 | );
227 | };
228 | /* End PBXProject section */
229 |
230 | /* Begin PBXResourcesBuildPhase section */
231 | 0EB3D6332351CE450065073C /* Resources */ = {
232 | isa = PBXResourcesBuildPhase;
233 | buildActionMask = 2147483647;
234 | files = (
235 | );
236 | runOnlyForDeploymentPostprocessing = 0;
237 | };
238 | 0EB8098B2000A2420090810F /* Resources */ = {
239 | isa = PBXResourcesBuildPhase;
240 | buildActionMask = 2147483647;
241 | files = (
242 | 0E47524D201DDCB4002EA382 /* clock@2x.png in Resources */,
243 | 0E47524F201DDCB4002EA382 /* hourhand2@2x.png in Resources */,
244 | 0E475251201DDCB4002EA382 /* m@2x.png in Resources */,
245 | 0E47524C201DDCB4002EA382 /* clock2@2x.png in Resources */,
246 | 0EB8099B2000A2420090810F /* LaunchScreen.storyboard in Resources */,
247 | 0EB809982000A2420090810F /* Assets.xcassets in Resources */,
248 | 0E475252201DDCB4002EA382 /* minutehand2@2x.png in Resources */,
249 | 0E475253201DDCB4002EA382 /* minutehand@2x.png in Resources */,
250 | 0E475250201DDCB4002EA382 /* hourhand@2x.png in Resources */,
251 | 0EB809962000A2420090810F /* Main.storyboard in Resources */,
252 | 0E47524E201DDCB4002EA382 /* h@2x.png in Resources */,
253 | );
254 | runOnlyForDeploymentPostprocessing = 0;
255 | };
256 | /* End PBXResourcesBuildPhase section */
257 |
258 | /* Begin PBXSourcesBuildPhase section */
259 | 0EB3D6312351CE450065073C /* Sources */ = {
260 | isa = PBXSourcesBuildPhase;
261 | buildActionMask = 2147483647;
262 | files = (
263 | 0EB3D6382351CE450065073C /* SampleAMClockTests.swift in Sources */,
264 | );
265 | runOnlyForDeploymentPostprocessing = 0;
266 | };
267 | 0EB809892000A2420090810F /* Sources */ = {
268 | isa = PBXSourcesBuildPhase;
269 | buildActionMask = 2147483647;
270 | files = (
271 | 0EB234DB2354935200442A85 /* AMClockView.swift in Sources */,
272 | 0E475259201E1211002EA382 /* ViewController.swift in Sources */,
273 | 0E475258201E1211002EA382 /* AppDelegate.swift in Sources */,
274 | );
275 | runOnlyForDeploymentPostprocessing = 0;
276 | };
277 | /* End PBXSourcesBuildPhase section */
278 |
279 | /* Begin PBXTargetDependency section */
280 | 0EB3D63B2351CE450065073C /* PBXTargetDependency */ = {
281 | isa = PBXTargetDependency;
282 | target = 0EB8098C2000A2420090810F /* SampleAMClock */;
283 | targetProxy = 0EB3D63A2351CE450065073C /* PBXContainerItemProxy */;
284 | };
285 | /* End PBXTargetDependency section */
286 |
287 | /* Begin PBXVariantGroup section */
288 | 0EB809942000A2420090810F /* Main.storyboard */ = {
289 | isa = PBXVariantGroup;
290 | children = (
291 | 0EB809952000A2420090810F /* Base */,
292 | );
293 | name = Main.storyboard;
294 | sourceTree = "";
295 | };
296 | 0EB809992000A2420090810F /* LaunchScreen.storyboard */ = {
297 | isa = PBXVariantGroup;
298 | children = (
299 | 0EB8099A2000A2420090810F /* Base */,
300 | );
301 | name = LaunchScreen.storyboard;
302 | sourceTree = "";
303 | };
304 | /* End PBXVariantGroup section */
305 |
306 | /* Begin XCBuildConfiguration section */
307 | 0EB3D63C2351CE450065073C /* Debug */ = {
308 | isa = XCBuildConfiguration;
309 | buildSettings = {
310 | BUNDLE_LOADER = "$(TEST_HOST)";
311 | CLANG_ENABLE_OBJC_WEAK = YES;
312 | CODE_SIGN_STYLE = Automatic;
313 | DEVELOPMENT_TEAM = H5QW7TLM9U;
314 | INFOPLIST_FILE = SampleAMClockTests/Info.plist;
315 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
316 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
317 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
318 | MTL_FAST_MATH = YES;
319 | PRODUCT_BUNDLE_IDENTIFIER = am10.SampleAMClockTests;
320 | PRODUCT_NAME = "$(TARGET_NAME)";
321 | SWIFT_VERSION = 5.0;
322 | TARGETED_DEVICE_FAMILY = "1,2";
323 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SampleAMClock.app/SampleAMClock";
324 | };
325 | name = Debug;
326 | };
327 | 0EB3D63D2351CE450065073C /* Release */ = {
328 | isa = XCBuildConfiguration;
329 | buildSettings = {
330 | BUNDLE_LOADER = "$(TEST_HOST)";
331 | CLANG_ENABLE_OBJC_WEAK = YES;
332 | CODE_SIGN_STYLE = Automatic;
333 | DEVELOPMENT_TEAM = H5QW7TLM9U;
334 | INFOPLIST_FILE = SampleAMClockTests/Info.plist;
335 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
336 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
337 | MTL_FAST_MATH = YES;
338 | PRODUCT_BUNDLE_IDENTIFIER = am10.SampleAMClockTests;
339 | PRODUCT_NAME = "$(TARGET_NAME)";
340 | SWIFT_VERSION = 5.0;
341 | TARGETED_DEVICE_FAMILY = "1,2";
342 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SampleAMClock.app/SampleAMClock";
343 | };
344 | name = Release;
345 | };
346 | 0EB8099D2000A2420090810F /* Debug */ = {
347 | isa = XCBuildConfiguration;
348 | buildSettings = {
349 | ALWAYS_SEARCH_USER_PATHS = NO;
350 | CLANG_ANALYZER_NONNULL = YES;
351 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
352 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
353 | CLANG_CXX_LIBRARY = "libc++";
354 | CLANG_ENABLE_MODULES = YES;
355 | CLANG_ENABLE_OBJC_ARC = YES;
356 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
357 | CLANG_WARN_BOOL_CONVERSION = YES;
358 | CLANG_WARN_COMMA = YES;
359 | CLANG_WARN_CONSTANT_CONVERSION = YES;
360 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
361 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
362 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
363 | CLANG_WARN_EMPTY_BODY = YES;
364 | CLANG_WARN_ENUM_CONVERSION = YES;
365 | CLANG_WARN_INFINITE_RECURSION = YES;
366 | CLANG_WARN_INT_CONVERSION = YES;
367 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
368 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
369 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
370 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
371 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
372 | CLANG_WARN_STRICT_PROTOTYPES = YES;
373 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
374 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
375 | CLANG_WARN_UNREACHABLE_CODE = YES;
376 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
377 | CODE_SIGN_IDENTITY = "iPhone Developer";
378 | COPY_PHASE_STRIP = NO;
379 | DEBUG_INFORMATION_FORMAT = dwarf;
380 | ENABLE_STRICT_OBJC_MSGSEND = YES;
381 | ENABLE_TESTABILITY = YES;
382 | GCC_C_LANGUAGE_STANDARD = gnu11;
383 | GCC_DYNAMIC_NO_PIC = NO;
384 | GCC_NO_COMMON_BLOCKS = YES;
385 | GCC_OPTIMIZATION_LEVEL = 0;
386 | GCC_PREPROCESSOR_DEFINITIONS = (
387 | "DEBUG=1",
388 | "$(inherited)",
389 | );
390 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
391 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
392 | GCC_WARN_UNDECLARED_SELECTOR = YES;
393 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
394 | GCC_WARN_UNUSED_FUNCTION = YES;
395 | GCC_WARN_UNUSED_VARIABLE = YES;
396 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
397 | MTL_ENABLE_DEBUG_INFO = YES;
398 | ONLY_ACTIVE_ARCH = YES;
399 | SDKROOT = iphoneos;
400 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
401 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
402 | };
403 | name = Debug;
404 | };
405 | 0EB8099E2000A2420090810F /* Release */ = {
406 | isa = XCBuildConfiguration;
407 | buildSettings = {
408 | ALWAYS_SEARCH_USER_PATHS = NO;
409 | CLANG_ANALYZER_NONNULL = YES;
410 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
411 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
412 | CLANG_CXX_LIBRARY = "libc++";
413 | CLANG_ENABLE_MODULES = YES;
414 | CLANG_ENABLE_OBJC_ARC = YES;
415 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
416 | CLANG_WARN_BOOL_CONVERSION = YES;
417 | CLANG_WARN_COMMA = YES;
418 | CLANG_WARN_CONSTANT_CONVERSION = YES;
419 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
420 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
421 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
422 | CLANG_WARN_EMPTY_BODY = YES;
423 | CLANG_WARN_ENUM_CONVERSION = YES;
424 | CLANG_WARN_INFINITE_RECURSION = YES;
425 | CLANG_WARN_INT_CONVERSION = YES;
426 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
427 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
428 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
429 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
430 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
431 | CLANG_WARN_STRICT_PROTOTYPES = YES;
432 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
433 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
434 | CLANG_WARN_UNREACHABLE_CODE = YES;
435 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
436 | CODE_SIGN_IDENTITY = "iPhone Developer";
437 | COPY_PHASE_STRIP = NO;
438 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
439 | ENABLE_NS_ASSERTIONS = NO;
440 | ENABLE_STRICT_OBJC_MSGSEND = YES;
441 | GCC_C_LANGUAGE_STANDARD = gnu11;
442 | GCC_NO_COMMON_BLOCKS = YES;
443 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
444 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
445 | GCC_WARN_UNDECLARED_SELECTOR = YES;
446 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
447 | GCC_WARN_UNUSED_FUNCTION = YES;
448 | GCC_WARN_UNUSED_VARIABLE = YES;
449 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
450 | MTL_ENABLE_DEBUG_INFO = NO;
451 | SDKROOT = iphoneos;
452 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
453 | VALIDATE_PRODUCT = YES;
454 | };
455 | name = Release;
456 | };
457 | 0EB809A02000A2420090810F /* Debug */ = {
458 | isa = XCBuildConfiguration;
459 | buildSettings = {
460 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
461 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
462 | CODE_SIGN_STYLE = Automatic;
463 | DEVELOPMENT_TEAM = "";
464 | INFOPLIST_FILE = SampleAMClock/Info.plist;
465 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
466 | PRODUCT_BUNDLE_IDENTIFIER = am10.SampleAMClock;
467 | PRODUCT_NAME = "$(TARGET_NAME)";
468 | PROVISIONING_PROFILE_SPECIFIER = "";
469 | SWIFT_VERSION = 5.0;
470 | TARGETED_DEVICE_FAMILY = "1,2";
471 | };
472 | name = Debug;
473 | };
474 | 0EB809A12000A2420090810F /* Release */ = {
475 | isa = XCBuildConfiguration;
476 | buildSettings = {
477 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
478 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
479 | CODE_SIGN_STYLE = Automatic;
480 | DEVELOPMENT_TEAM = "";
481 | INFOPLIST_FILE = SampleAMClock/Info.plist;
482 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
483 | PRODUCT_BUNDLE_IDENTIFIER = am10.SampleAMClock;
484 | PRODUCT_NAME = "$(TARGET_NAME)";
485 | PROVISIONING_PROFILE_SPECIFIER = "";
486 | SWIFT_VERSION = 5.0;
487 | TARGETED_DEVICE_FAMILY = "1,2";
488 | };
489 | name = Release;
490 | };
491 | /* End XCBuildConfiguration section */
492 |
493 | /* Begin XCConfigurationList section */
494 | 0EB3D63E2351CE450065073C /* Build configuration list for PBXNativeTarget "SampleAMClockTests" */ = {
495 | isa = XCConfigurationList;
496 | buildConfigurations = (
497 | 0EB3D63C2351CE450065073C /* Debug */,
498 | 0EB3D63D2351CE450065073C /* Release */,
499 | );
500 | defaultConfigurationIsVisible = 0;
501 | defaultConfigurationName = Release;
502 | };
503 | 0EB809882000A2420090810F /* Build configuration list for PBXProject "SampleAMClock" */ = {
504 | isa = XCConfigurationList;
505 | buildConfigurations = (
506 | 0EB8099D2000A2420090810F /* Debug */,
507 | 0EB8099E2000A2420090810F /* Release */,
508 | );
509 | defaultConfigurationIsVisible = 0;
510 | defaultConfigurationName = Release;
511 | };
512 | 0EB8099F2000A2420090810F /* Build configuration list for PBXNativeTarget "SampleAMClock" */ = {
513 | isa = XCConfigurationList;
514 | buildConfigurations = (
515 | 0EB809A02000A2420090810F /* Debug */,
516 | 0EB809A12000A2420090810F /* Release */,
517 | );
518 | defaultConfigurationIsVisible = 0;
519 | defaultConfigurationName = Release;
520 | };
521 | /* End XCConfigurationList section */
522 | };
523 | rootObject = 0EB809852000A2420090810F /* Project object */;
524 | }
525 |
--------------------------------------------------------------------------------
/SampleAMClock/SampleAMClock.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SampleAMClock/SampleAMClock.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SampleAMClock/SampleAMClock/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 | }
--------------------------------------------------------------------------------
/SampleAMClock/SampleAMClock/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 |
--------------------------------------------------------------------------------
/SampleAMClock/SampleAMClock/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 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
--------------------------------------------------------------------------------
/SampleAMClock/SampleAMClock/Classes/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // SampleAMClock
4 | //
5 | // Created by am10 on 2018/01/06.
6 | // Copyright © 2018年 am10. 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 |
--------------------------------------------------------------------------------
/SampleAMClock/SampleAMClock/Classes/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // SampleAMClock
4 | //
5 | // Created by am10 on 2018/01/06.
6 | // Copyright © 2018年 am10. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | @IBOutlet weak var cView1: AMClockView!
14 | @IBOutlet weak var cView2: AMClockView!
15 | @IBOutlet weak var cView3: AMClockView!
16 | @IBOutlet weak var cView4: AMClockView!
17 |
18 | @IBOutlet weak var cView5: AMClockView!
19 | @IBOutlet weak var cView6: AMClockView!
20 |
21 | @IBOutlet weak var timeLabel: UILabel!
22 | let dateFormatter = DateFormatter()
23 |
24 | override func viewDidLoad() {
25 | super.viewDidLoad()
26 | // Do any additional setup after loading the view, typically from a nib.
27 | cView1.delegate = self
28 | cView2.delegate = self
29 | cView3.delegate = self
30 | cView4.delegate = self
31 | cView5.delegate = self
32 | cView6.delegate = self
33 | cView1.timeZone = TimeZone(identifier: "America/Toronto")
34 | cView2.timeZone = TimeZone(identifier: "Europe/Moscow")
35 | cView3.timeZone = TimeZone(identifier: "Asia/Tokyo")
36 | cView4.timeZone = TimeZone(identifier: "GMT")
37 | cView5.timeZone = TimeZone(identifier: "Africa/Cairo")
38 | cView6.timeZone = TimeZone(identifier: "Australia/Sydney")
39 |
40 | dateFormatter.timeZone = TimeZone(identifier: "Asia/Tokyo")
41 | dateFormatter.dateFormat = "yyyy/MM/dd HH:mm"
42 | cView3.selectedDate = dateFormatter.date(from: "2018/01/01 10:10")
43 | }
44 |
45 | override func didReceiveMemoryWarning() {
46 | super.didReceiveMemoryWarning()
47 | // Dispose of any resources that can be recreated.
48 | }
49 | }
50 |
51 | extension ViewController: AMClockViewDelegate {
52 | func clockView(_ clockView: AMClockView, didChangeDate date: Date) {
53 | if let timeZone = clockView.timeZone {
54 | dateFormatter.timeZone = timeZone
55 | }
56 | timeLabel.text = "selected time: " + dateFormatter.string(from: date);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/SampleAMClock/SampleAMClock/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/SampleAMClock/SampleAMClock/image/clock2@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adventam10/AMClockView/2b48ca4dcc2eb5f346e25a7cabb0cce68853e6cd/SampleAMClock/SampleAMClock/image/clock2@2x.png
--------------------------------------------------------------------------------
/SampleAMClock/SampleAMClock/image/clock@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adventam10/AMClockView/2b48ca4dcc2eb5f346e25a7cabb0cce68853e6cd/SampleAMClock/SampleAMClock/image/clock@2x.png
--------------------------------------------------------------------------------
/SampleAMClock/SampleAMClock/image/h@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adventam10/AMClockView/2b48ca4dcc2eb5f346e25a7cabb0cce68853e6cd/SampleAMClock/SampleAMClock/image/h@2x.png
--------------------------------------------------------------------------------
/SampleAMClock/SampleAMClock/image/hourhand2@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adventam10/AMClockView/2b48ca4dcc2eb5f346e25a7cabb0cce68853e6cd/SampleAMClock/SampleAMClock/image/hourhand2@2x.png
--------------------------------------------------------------------------------
/SampleAMClock/SampleAMClock/image/hourhand@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adventam10/AMClockView/2b48ca4dcc2eb5f346e25a7cabb0cce68853e6cd/SampleAMClock/SampleAMClock/image/hourhand@2x.png
--------------------------------------------------------------------------------
/SampleAMClock/SampleAMClock/image/m@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adventam10/AMClockView/2b48ca4dcc2eb5f346e25a7cabb0cce68853e6cd/SampleAMClock/SampleAMClock/image/m@2x.png
--------------------------------------------------------------------------------
/SampleAMClock/SampleAMClock/image/minutehand2@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adventam10/AMClockView/2b48ca4dcc2eb5f346e25a7cabb0cce68853e6cd/SampleAMClock/SampleAMClock/image/minutehand2@2x.png
--------------------------------------------------------------------------------
/SampleAMClock/SampleAMClock/image/minutehand@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adventam10/AMClockView/2b48ca4dcc2eb5f346e25a7cabb0cce68853e6cd/SampleAMClock/SampleAMClock/image/minutehand@2x.png
--------------------------------------------------------------------------------
/SampleAMClock/SampleAMClockTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/SampleAMClock/SampleAMClockTests/SampleAMClockTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SampleAMClockTests.swift
3 | // SampleAMClockTests
4 | //
5 | // Created by am10 on 2019/10/12.
6 | // Copyright © 2019 am10. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SampleAMClock
11 |
12 | class SampleAMClockTests: XCTestCase {
13 |
14 | private let dateFormatter: DateFormatter = {
15 | var df = DateFormatter()
16 | df.locale = .init(identifier: "ja_JP")
17 | df.dateFormat = "yyyyMMddHHmmss"
18 | return df
19 | }()
20 | private let accuracy: Float = 0.00001
21 | private let anglePerMinute = Float((2 * Double.pi) / 60)
22 | private let anglePerHour = Float((2 * Double.pi) / 12)
23 | private let angle270 = Float(Double.pi/2 + Double.pi)
24 | private let radius: CGFloat = 1.0
25 | override func setUp() {
26 | // Put setup code here. This method is called before the invocation of each test method in the class.
27 | }
28 |
29 | override func tearDown() {
30 | // Put teardown code here. This method is called after the invocation of each test method in the class.
31 | }
32 |
33 | func testCurrentMinuteAngle() {
34 | let model = AMClockModel()
35 | model.currentDate = dateFormatter.date(from: "20191210120000")!
36 | XCTAssertEqual(cosf(model.currentMinuteAngle), 0.0, accuracy: accuracy)
37 | model.currentDate = dateFormatter.date(from: "20191210121500")!
38 | XCTAssertEqual(cosf(model.currentMinuteAngle), 1.0, accuracy: accuracy)
39 | model.currentDate = dateFormatter.date(from: "20191210123000")!
40 | XCTAssertEqual(cosf(model.currentMinuteAngle), 0.0, accuracy: accuracy)
41 | model.currentDate = dateFormatter.date(from: "20191210124500")!
42 | XCTAssertEqual(cosf(model.currentMinuteAngle), -1.0, accuracy: accuracy)
43 |
44 | model.currentDate = dateFormatter.date(from: "20191215121300")!
45 | XCTAssertEqual(cosf(model.currentMinuteAngle), cosf(angle270 + anglePerMinute*13), accuracy: accuracy)
46 | model.currentDate = dateFormatter.date(from: "20191215122200")!
47 | XCTAssertEqual(cosf(model.currentMinuteAngle), cosf(angle270 + anglePerMinute*22), accuracy: accuracy)
48 | model.currentDate = dateFormatter.date(from: "20191215123600")!
49 | XCTAssertEqual(cosf(model.currentMinuteAngle), cosf(angle270 + anglePerMinute*36), accuracy: accuracy)
50 | model.currentDate = dateFormatter.date(from: "20191215125200")!
51 | XCTAssertEqual(cosf(model.currentMinuteAngle), cosf(angle270 + anglePerMinute*52), accuracy: accuracy)
52 | }
53 |
54 | func testCurrentHourAngle() {
55 | let model = AMClockModel()
56 | model.currentDate = dateFormatter.date(from: "20191210120000")!
57 | XCTAssertEqual(cosf(model.currentHourAngle), 0.0, accuracy: accuracy)
58 | model.currentDate = dateFormatter.date(from: "20191210030000")!
59 | XCTAssertEqual(cosf(model.currentHourAngle), 1.0, accuracy: accuracy)
60 | model.currentDate = dateFormatter.date(from: "20191210060000")!
61 | XCTAssertEqual(cosf(model.currentHourAngle), 0.0, accuracy: accuracy)
62 | model.currentDate = dateFormatter.date(from: "20191210090000")!
63 | XCTAssertEqual(cosf(model.currentHourAngle), -1.0, accuracy: accuracy)
64 |
65 | model.currentDate = dateFormatter.date(from: "20191215010000")!
66 | XCTAssertEqual(cosf(model.currentHourAngle), cosf(angle270 + anglePerHour*1), accuracy: accuracy)
67 | model.currentDate = dateFormatter.date(from: "20191215040000")!
68 | XCTAssertEqual(cosf(model.currentHourAngle), cosf(angle270 + anglePerHour*4), accuracy: accuracy)
69 | model.currentDate = dateFormatter.date(from: "20191215070000")!
70 | XCTAssertEqual(cosf(model.currentHourAngle), cosf(angle270 + anglePerHour*7), accuracy: accuracy)
71 | model.currentDate = dateFormatter.date(from: "20191215110000")!
72 | XCTAssertEqual(cosf(model.currentHourAngle), cosf(angle270 + anglePerHour*11), accuracy: accuracy)
73 |
74 | model.currentDate = dateFormatter.date(from: "20191210124040")!
75 | XCTAssertEqual(cosf(model.currentHourAngle), cosf(reviseAngle(Float(Double.pi/2 + Double.pi), byMinute: 40)), accuracy: accuracy)
76 | model.currentDate = dateFormatter.date(from: "20191210031540")!
77 | XCTAssertEqual(cosf(model.currentHourAngle), cosf(reviseAngle(0, byMinute: 15)), accuracy: accuracy)
78 | model.currentDate = dateFormatter.date(from: "20191210063040")!
79 | XCTAssertEqual(cosf(model.currentHourAngle), cosf(reviseAngle(Float(Double.pi/2), byMinute: 30)), accuracy: accuracy)
80 | model.currentDate = dateFormatter.date(from: "20191210094540")!
81 | XCTAssertEqual(cosf(model.currentHourAngle), cosf(reviseAngle(Float(Double.pi), byMinute: 45)), accuracy: accuracy)
82 |
83 | model.currentDate = dateFormatter.date(from: "20191215021325")!
84 | XCTAssertEqual(cosf(model.currentHourAngle), cosf(reviseAngle(angle270 + anglePerHour*2, byMinute: 13)), accuracy: accuracy)
85 | model.currentDate = dateFormatter.date(from: "20191215052230")!
86 | XCTAssertEqual(cosf(model.currentHourAngle), cosf(reviseAngle(angle270 + anglePerHour*5, byMinute: 22)), accuracy: accuracy)
87 | model.currentDate = dateFormatter.date(from: "20191215073655")!
88 | XCTAssertEqual(cosf(model.currentHourAngle), cosf(reviseAngle(angle270 + anglePerHour*7, byMinute: 36)), accuracy: accuracy)
89 | model.currentDate = dateFormatter.date(from: "20191215115244")!
90 | XCTAssertEqual(cosf(model.currentHourAngle), cosf(reviseAngle(angle270 + anglePerHour*11, byMinute: 52)), accuracy: accuracy)
91 | }
92 |
93 | func testFormattedTime() {
94 | let model = AMClockModel()
95 | model.currentDate = dateFormatter.date(from: "20191210123322")!
96 | XCTAssertEqual(model.formattedTime, "12:33")
97 | }
98 |
99 | func testCurrentMinute() {
100 | let model = AMClockModel()
101 | model.currentDate = dateFormatter.date(from: "20191210120022")!
102 | XCTAssertEqual(model.currentMinute, 0)
103 | model.currentDate = dateFormatter.date(from: "20191210121522")!
104 | XCTAssertEqual(model.currentMinute, 15)
105 | model.currentDate = dateFormatter.date(from: "20191210123022")!
106 | XCTAssertEqual(model.currentMinute, 30)
107 | model.currentDate = dateFormatter.date(from: "20191210124522")!
108 | XCTAssertEqual(model.currentMinute, 45)
109 |
110 | model.currentDate = dateFormatter.date(from: "20191210121222")!
111 | XCTAssertEqual(model.currentMinute, 12)
112 | model.currentDate = dateFormatter.date(from: "20191210122222")!
113 | XCTAssertEqual(model.currentMinute, 22)
114 | model.currentDate = dateFormatter.date(from: "20191210123722")!
115 | XCTAssertEqual(model.currentMinute, 37)
116 | model.currentDate = dateFormatter.date(from: "20191210125322")!
117 | XCTAssertEqual(model.currentMinute, 53)
118 | }
119 |
120 | func testUpdateCurrentDateWithMinuteMethod() {
121 | let model = AMClockModel()
122 | model.currentDate = dateFormatter.date(from: "20191210120022")!
123 | model.updateCurrentDate(minute: 30)
124 | XCTAssertEqual(model.currentDate, dateFormatter.date(from: "20191210123022"))
125 | model.currentDate = dateFormatter.date(from: "20191210120022")!
126 | model.updateCurrentDate(minute: 65)
127 | XCTAssertEqual(model.currentDate, dateFormatter.date(from: "20191210130522"))
128 | }
129 |
130 | func testAppendHourMethod() {
131 | let model = AMClockModel()
132 | model.currentDate = dateFormatter.date(from: "20191210120022")!
133 | model.appendHour(1)
134 | XCTAssertEqual(model.currentDate, dateFormatter.date(from: "20191210130022"))
135 | model.currentDate = dateFormatter.date(from: "20191210120022")!
136 | model.appendHour(-1)
137 | XCTAssertEqual(model.currentDate, dateFormatter.date(from: "20191210110022"))
138 | }
139 |
140 | func testCalculateHourAngleWithPointMethod() {
141 | let model = AMClockModel()
142 | model.currentDate = dateFormatter.date(from: "20191210120000")!
143 | var angle = model.calculateHourAngle(point: .init(x: radius, y: 0.0), radius: radius)
144 | XCTAssertEqual(cosf(angle), cosf(angle270), accuracy: accuracy)
145 | angle = model.calculateHourAngle(point: .init(x: radius*2, y: radius), radius: radius)
146 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 3), accuracy: accuracy)
147 | angle = model.calculateHourAngle(point: .init(x: radius, y: radius*2), radius: radius)
148 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 6), accuracy: accuracy)
149 | angle = model.calculateHourAngle(point: .init(x: 0.0, y: radius), radius: radius)
150 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 9), accuracy: accuracy)
151 |
152 | angle = model.calculateHourAngle(point: point(minute: 1), radius: radius)
153 | XCTAssertEqual(cosf(angle), cosf(angle270), accuracy: accuracy)
154 | angle = model.calculateHourAngle(point: point(minute: 2), radius: radius)
155 | XCTAssertEqual(cosf(angle), cosf(angle270), accuracy: accuracy)
156 | angle = model.calculateHourAngle(point: point(minute: 3), radius: radius)
157 | XCTAssertEqual(cosf(angle), cosf(angle270), accuracy: accuracy)
158 | angle = model.calculateHourAngle(point: point(minute: 4), radius: radius)
159 | XCTAssertEqual(cosf(angle), cosf(angle270), accuracy: accuracy)
160 |
161 | angle = model.calculateHourAngle(point: point(minute: 6), radius: radius)
162 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 1), accuracy: accuracy)
163 | angle = model.calculateHourAngle(point: point(minute: 7), radius: radius)
164 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 1), accuracy: accuracy)
165 | angle = model.calculateHourAngle(point: point(minute: 8), radius: radius)
166 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 1), accuracy: accuracy)
167 | angle = model.calculateHourAngle(point: point(minute: 9), radius: radius)
168 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 1), accuracy: accuracy)
169 |
170 | angle = model.calculateHourAngle(point: point(minute: 11), radius: radius)
171 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 2), accuracy: accuracy)
172 | angle = model.calculateHourAngle(point: point(minute: 12), radius: radius)
173 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 2), accuracy: accuracy)
174 | angle = model.calculateHourAngle(point: point(minute: 13), radius: radius)
175 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 2), accuracy: accuracy)
176 | angle = model.calculateHourAngle(point: point(minute: 14), radius: radius)
177 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 2), accuracy: accuracy)
178 |
179 | angle = model.calculateHourAngle(point: point(minute: 16), radius: radius)
180 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 3), accuracy: accuracy)
181 | angle = model.calculateHourAngle(point: point(minute: 17), radius: radius)
182 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 3), accuracy: accuracy)
183 | angle = model.calculateHourAngle(point: point(minute: 18), radius: radius)
184 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 3), accuracy: accuracy)
185 | angle = model.calculateHourAngle(point: point(minute: 19), radius: radius)
186 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 3), accuracy: accuracy)
187 |
188 | angle = model.calculateHourAngle(point: point(minute: 21), radius: radius)
189 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 4), accuracy: accuracy)
190 | angle = model.calculateHourAngle(point: point(minute: 22), radius: radius)
191 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 4), accuracy: accuracy)
192 | angle = model.calculateHourAngle(point: point(minute: 23), radius: radius)
193 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 4), accuracy: accuracy)
194 | angle = model.calculateHourAngle(point: point(minute: 24), radius: radius)
195 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 4), accuracy: accuracy)
196 |
197 | angle = model.calculateHourAngle(point: point(minute: 26), radius: radius)
198 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 5), accuracy: accuracy)
199 | angle = model.calculateHourAngle(point: point(minute: 27), radius: radius)
200 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 5), accuracy: accuracy)
201 | angle = model.calculateHourAngle(point: point(minute: 28), radius: radius)
202 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 5), accuracy: accuracy)
203 | angle = model.calculateHourAngle(point: point(minute: 29), radius: radius)
204 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 5), accuracy: accuracy)
205 |
206 | angle = model.calculateHourAngle(point: point(minute: 31), radius: radius)
207 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 6), accuracy: accuracy)
208 | angle = model.calculateHourAngle(point: point(minute: 32), radius: radius)
209 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 6), accuracy: accuracy)
210 | angle = model.calculateHourAngle(point: point(minute: 33), radius: radius)
211 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 6), accuracy: accuracy)
212 | angle = model.calculateHourAngle(point: point(minute: 34), radius: radius)
213 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 6), accuracy: accuracy)
214 |
215 | angle = model.calculateHourAngle(point: point(minute: 36), radius: radius)
216 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 7), accuracy: accuracy)
217 | angle = model.calculateHourAngle(point: point(minute: 37), radius: radius)
218 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 7), accuracy: accuracy)
219 | angle = model.calculateHourAngle(point: point(minute: 38), radius: radius)
220 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 7), accuracy: accuracy)
221 | angle = model.calculateHourAngle(point: point(minute: 39), radius: radius)
222 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 7), accuracy: accuracy)
223 |
224 | angle = model.calculateHourAngle(point: point(minute: 41), radius: radius)
225 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 8), accuracy: accuracy)
226 | angle = model.calculateHourAngle(point: point(minute: 42), radius: radius)
227 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 8), accuracy: accuracy)
228 | angle = model.calculateHourAngle(point: point(minute: 43), radius: radius)
229 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 8), accuracy: accuracy)
230 | angle = model.calculateHourAngle(point: point(minute: 44), radius: radius)
231 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 8), accuracy: accuracy)
232 |
233 | angle = model.calculateHourAngle(point: point(minute: 46), radius: radius)
234 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 9), accuracy: accuracy)
235 | angle = model.calculateHourAngle(point: point(minute: 47), radius: radius)
236 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 9), accuracy: accuracy)
237 | angle = model.calculateHourAngle(point: point(minute: 48), radius: radius)
238 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 9), accuracy: accuracy)
239 | angle = model.calculateHourAngle(point: point(minute: 49), radius: radius)
240 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 9), accuracy: accuracy)
241 |
242 | angle = model.calculateHourAngle(point: point(minute: 51), radius: radius)
243 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 10), accuracy: accuracy)
244 | angle = model.calculateHourAngle(point: point(minute: 52), radius: radius)
245 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 10), accuracy: accuracy)
246 | angle = model.calculateHourAngle(point: point(minute: 53), radius: radius)
247 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 10), accuracy: accuracy)
248 | angle = model.calculateHourAngle(point: point(minute: 54), radius: radius)
249 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 10), accuracy: accuracy)
250 |
251 | angle = model.calculateHourAngle(point: point(minute: 56), radius: radius)
252 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 11), accuracy: accuracy)
253 | angle = model.calculateHourAngle(point: point(minute: 57), radius: radius)
254 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 11), accuracy: accuracy)
255 | angle = model.calculateHourAngle(point: point(minute: 58), radius: radius)
256 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 11), accuracy: accuracy)
257 | angle = model.calculateHourAngle(point: point(minute: 59), radius: radius)
258 | XCTAssertEqual(cosf(angle), cosf(angle270 + anglePerHour * 11), accuracy: accuracy)
259 | }
260 |
261 | func testCalculateMinuteWithPointMethod() {
262 | let model = AMClockModel()
263 | XCTAssertEqual(model.calculateMinute(point: .init(x: radius, y: 0.0), radius: radius), 0)
264 | XCTAssertEqual(model.calculateMinute(point: .init(x: radius*2, y: radius), radius: radius), 15)
265 | XCTAssertEqual(model.calculateMinute(point: .init(x: radius, y: radius*2), radius: radius), 30)
266 | XCTAssertEqual(model.calculateMinute(point: .init(x: 0.0, y: radius), radius: radius), 45)
267 |
268 | XCTAssertEqual(model.calculateMinute(point: point(minute: 1), radius: radius), 1)
269 | XCTAssertEqual(model.calculateMinute(point: point(minute: 2), radius: radius), 2)
270 | XCTAssertEqual(model.calculateMinute(point: point(minute: 3), radius: radius), 3)
271 | XCTAssertEqual(model.calculateMinute(point: point(minute: 4), radius: radius), 4)
272 | XCTAssertEqual(model.calculateMinute(point: point(minute: 5), radius: radius), 5)
273 | XCTAssertEqual(model.calculateMinute(point: point(minute: 6), radius: radius), 6)
274 | XCTAssertEqual(model.calculateMinute(point: point(minute: 7), radius: radius), 7)
275 | XCTAssertEqual(model.calculateMinute(point: point(minute: 8), radius: radius), 8)
276 | XCTAssertEqual(model.calculateMinute(point: point(minute: 9), radius: radius), 9)
277 | XCTAssertEqual(model.calculateMinute(point: point(minute: 10), radius: radius), 10)
278 | XCTAssertEqual(model.calculateMinute(point: point(minute: 11), radius: radius), 11)
279 | XCTAssertEqual(model.calculateMinute(point: point(minute: 12), radius: radius), 12)
280 | XCTAssertEqual(model.calculateMinute(point: point(minute: 13), radius: radius), 13)
281 | XCTAssertEqual(model.calculateMinute(point: point(minute: 14), radius: radius), 14)
282 |
283 | XCTAssertEqual(model.calculateMinute(point: point(minute: 16), radius: radius), 16)
284 | XCTAssertEqual(model.calculateMinute(point: point(minute: 17), radius: radius), 17)
285 | XCTAssertEqual(model.calculateMinute(point: point(minute: 18), radius: radius), 18)
286 | XCTAssertEqual(model.calculateMinute(point: point(minute: 19), radius: radius), 19)
287 | XCTAssertEqual(model.calculateMinute(point: point(minute: 20), radius: radius), 20)
288 | XCTAssertEqual(model.calculateMinute(point: point(minute: 21), radius: radius), 21)
289 | XCTAssertEqual(model.calculateMinute(point: point(minute: 22), radius: radius), 22)
290 | XCTAssertEqual(model.calculateMinute(point: point(minute: 23), radius: radius), 23)
291 | XCTAssertEqual(model.calculateMinute(point: point(minute: 24), radius: radius), 24)
292 | XCTAssertEqual(model.calculateMinute(point: point(minute: 25), radius: radius), 25)
293 | XCTAssertEqual(model.calculateMinute(point: point(minute: 26), radius: radius), 26)
294 | XCTAssertEqual(model.calculateMinute(point: point(minute: 27), radius: radius), 27)
295 | XCTAssertEqual(model.calculateMinute(point: point(minute: 28), radius: radius), 28)
296 | XCTAssertEqual(model.calculateMinute(point: point(minute: 29), radius: radius), 29)
297 |
298 | XCTAssertEqual(model.calculateMinute(point: point(minute: 31), radius: radius), 31)
299 | XCTAssertEqual(model.calculateMinute(point: point(minute: 32), radius: radius), 32)
300 | XCTAssertEqual(model.calculateMinute(point: point(minute: 33), radius: radius), 33)
301 | XCTAssertEqual(model.calculateMinute(point: point(minute: 34), radius: radius), 34)
302 | XCTAssertEqual(model.calculateMinute(point: point(minute: 35), radius: radius), 35)
303 | XCTAssertEqual(model.calculateMinute(point: point(minute: 36), radius: radius), 36)
304 | XCTAssertEqual(model.calculateMinute(point: point(minute: 37), radius: radius), 37)
305 | XCTAssertEqual(model.calculateMinute(point: point(minute: 38), radius: radius), 38)
306 | XCTAssertEqual(model.calculateMinute(point: point(minute: 39), radius: radius), 39)
307 | XCTAssertEqual(model.calculateMinute(point: point(minute: 40), radius: radius), 40)
308 | XCTAssertEqual(model.calculateMinute(point: point(minute: 41), radius: radius), 41)
309 | XCTAssertEqual(model.calculateMinute(point: point(minute: 42), radius: radius), 42)
310 | XCTAssertEqual(model.calculateMinute(point: point(minute: 43), radius: radius), 43)
311 | XCTAssertEqual(model.calculateMinute(point: point(minute: 44), radius: radius), 44)
312 |
313 | XCTAssertEqual(model.calculateMinute(point: point(minute: 46), radius: radius), 46)
314 | XCTAssertEqual(model.calculateMinute(point: point(minute: 47), radius: radius), 47)
315 | XCTAssertEqual(model.calculateMinute(point: point(minute: 48), radius: radius), 48)
316 | XCTAssertEqual(model.calculateMinute(point: point(minute: 49), radius: radius), 49)
317 | XCTAssertEqual(model.calculateMinute(point: point(minute: 50), radius: radius), 50)
318 | XCTAssertEqual(model.calculateMinute(point: point(minute: 51), radius: radius), 51)
319 | XCTAssertEqual(model.calculateMinute(point: point(minute: 52), radius: radius), 52)
320 | XCTAssertEqual(model.calculateMinute(point: point(minute: 53), radius: radius), 53)
321 | XCTAssertEqual(model.calculateMinute(point: point(minute: 54), radius: radius), 54)
322 | XCTAssertEqual(model.calculateMinute(point: point(minute: 55), radius: radius), 55)
323 | XCTAssertEqual(model.calculateMinute(point: point(minute: 56), radius: radius), 56)
324 | XCTAssertEqual(model.calculateMinute(point: point(minute: 57), radius: radius), 57)
325 | XCTAssertEqual(model.calculateMinute(point: point(minute: 58), radius: radius), 58)
326 | XCTAssertEqual(model.calculateMinute(point: point(minute: 59), radius: radius), 59)
327 | }
328 |
329 | func testRevisedByAcrossHourWithStartAngleMethod() {
330 | let model = AMClockModel()
331 | model.currentDate = dateFormatter.date(from: "20191210120022")!
332 | model.revisedByAcrossHour(startAngle: anglePerMinute * 55,
333 | endAngle: anglePerMinute * 5)
334 | XCTAssertEqual(model.currentDate, dateFormatter.date(from: "20191210130022"))
335 | model.currentDate = dateFormatter.date(from: "20191210120022")!
336 | model.revisedByAcrossHour(startAngle: anglePerMinute * 5,
337 | endAngle: anglePerMinute * 55)
338 | XCTAssertEqual(model.currentDate, dateFormatter.date(from: "20191210110022"))
339 | }
340 |
341 | func testCalculateElapsedTimeWithStartAngleMethod() {
342 | let model = AMClockModel()
343 | var hour = model.calculateElapsedTime(startAngle: anglePerMinute * 5,
344 | endAngle: anglePerMinute * 10)
345 | XCTAssertEqual(hour, 1)
346 | hour = model.calculateElapsedTime(startAngle: anglePerMinute * 5,
347 | endAngle: anglePerMinute * 8)
348 | XCTAssertEqual(hour, 0)
349 | hour = model.calculateElapsedTime(startAngle: anglePerMinute * 10,
350 | endAngle: anglePerMinute * 5)
351 | XCTAssertEqual(hour, -1)
352 | hour = model.calculateElapsedTime(startAngle: anglePerMinute * 8,
353 | endAngle: anglePerMinute * 5)
354 | XCTAssertEqual(hour, 0)
355 | hour = model.calculateElapsedTime(startAngle: anglePerMinute * 55,
356 | endAngle: anglePerMinute * 1)
357 | XCTAssertEqual(hour, 1)
358 | hour = model.calculateElapsedTime(startAngle: anglePerMinute * 5,
359 | endAngle: anglePerMinute * 59)
360 | XCTAssertEqual(hour, -1)
361 | }
362 |
363 | private func reviseAngle(_ angle: Float, byMinute minute: Float) -> Float {
364 | return angle + (minute / 60.0) * anglePerHour
365 | }
366 |
367 | private func angle(minute: Int) -> CGFloat {
368 | let angle = (Float(minute) + 0.001) * anglePerMinute + angle270
369 | return CGFloat(angle)
370 | }
371 |
372 | private func point(minute: Int) -> CGPoint {
373 | return .init(x: radius + cos(angle(minute: minute)) * radius,
374 | y: radius + sin(angle(minute: minute)) * radius)
375 | }
376 | }
377 |
--------------------------------------------------------------------------------
/Source/AMClockView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AMClockView.swift
3 | // AMClockView, https://github.com/adventam10/AMClockView
4 | //
5 | // Created by am10 on 2017/12/29.
6 | // Copyright © 2017年 am10. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public enum AMCVClockType {
12 | case none
13 | case arabic
14 |
15 | var times: [String] {
16 | switch self {
17 | case .none:
18 | return []
19 | case .arabic:
20 | return ["12", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"]
21 | }
22 | }
23 |
24 | func time(index: Int) -> String {
25 | switch self {
26 | case .none:
27 | return ""
28 | case .arabic:
29 | return times[index]
30 | }
31 | }
32 | }
33 |
34 | public protocol AMClockViewDelegate: AnyObject {
35 | func clockView(_ clockView: AMClockView, didChangeDate date: Date)
36 | }
37 |
38 | internal class AMClockModel {
39 |
40 | enum AMCVTimeEditType {
41 | case none
42 | case hour
43 | case minute
44 | }
45 |
46 | var timeZone: TimeZone? {
47 | didSet {
48 | if let timeZone = timeZone {
49 | calendar.timeZone = timeZone
50 | dateFormatter.timeZone = timeZone
51 | } else {
52 | calendar.timeZone = .current
53 | dateFormatter.timeZone = .current
54 | }
55 | }
56 | }
57 | var editType: AMCVTimeEditType = .none
58 | var startAngle: Float = 0.0
59 | var endAngle: Float = 0.0
60 | var currentDate = Date()
61 | var currentHourAngle: Float {
62 | return calculateAngle(hour: currentHour)
63 | }
64 | var currentMinuteAngle: Float {
65 | return angle270 + anglePerMinute * Float(currentMinute)
66 | }
67 | var currentMinute: Int {
68 | return currentComponents.minute!
69 | }
70 | var formattedTime: String {
71 | return dateFormatter.string(from: currentDate)
72 | }
73 |
74 | let angle30 = Float(Double.pi / 6)
75 | let angle270 = Float(Double.pi + Double.pi/2)
76 |
77 | private var currentHour: Int {
78 | return currentComponents.hour!
79 | }
80 | private var currentComponents: DateComponents {
81 | return calendar.dateComponents([.year, .month, .day, .hour, .minute, .second],
82 | from: currentDate)
83 | }
84 | private var calendar = Calendar(identifier: .gregorian)
85 |
86 | private let angle360 = Float(Double.pi * 2)
87 | private let anglePerHour = Float(Double.pi * 2) / 12
88 | private let anglePerMinute = Float(Double.pi * 2) / 60
89 | private let dateFormatter: DateFormatter = {
90 | var df = DateFormatter()
91 | df.dateFormat = "HH:mm"
92 | return df
93 | }()
94 |
95 | func adjustFont(rect: CGRect) -> UIFont {
96 | let length = (rect.width > rect.height) ? rect.height : rect.width
97 | return .systemFont(ofSize: length * 0.8)
98 | }
99 |
100 | func updateCurrentDate(minute: Int) {
101 | var components = currentComponents
102 | components.minute = minute
103 | currentDate = calendar.date(from: components)!
104 | }
105 |
106 | func appendHour(_ hour: Int) {
107 | currentDate = currentDate.addingTimeInterval(60 * 60 * Double(hour))
108 | }
109 |
110 | func calculateHourAngle(point: CGPoint, radius: CGFloat) -> Float {
111 | let radian = calculateRadian(point: point, radius: radius)
112 | let hour = Int((radian - angle270) / anglePerHour)
113 | return calculateAngle(hour: hour)
114 | }
115 |
116 | func calculateMinute(point: CGPoint, radius: CGFloat) -> Int {
117 | let radian = calculateRadian(point: point, radius: radius)
118 | return Int((radian - angle270) / anglePerMinute)
119 | }
120 |
121 | func revisedByAcrossHour(startAngle: Float, endAngle: Float) {
122 | if endAngle < startAngle {
123 | let gap = startAngle - endAngle
124 | if gap > angle270 {
125 | // case(through 12o'clock)
126 | // 1 hour ago
127 | appendHour(1)
128 | }
129 | } else {
130 | let gap = endAngle - startAngle
131 | if gap > angle270 {
132 | // case(through 12o'clock)
133 | // 1 hour later
134 | appendHour(-1)
135 | }
136 | }
137 | }
138 |
139 | func calculateElapsedTime(startAngle: Float, endAngle: Float) -> Int {
140 | var angleGap: Float = 0.0
141 | if startAngle > endAngle {
142 | let gap = startAngle - endAngle
143 | if gap > angle270 {
144 | angleGap = (endAngle + angle360) - startAngle
145 | } else {
146 | angleGap = endAngle - startAngle
147 | }
148 | } else {
149 | let gap = endAngle - startAngle
150 | if gap > angle270 {
151 | angleGap = ((startAngle + angle360) - endAngle) * -1
152 | } else {
153 | angleGap = endAngle - startAngle
154 | }
155 | }
156 |
157 | var degree = Int(angleGap*360 / angle360)
158 | degree = (degree < 0) ? degree - 5 : degree + 5
159 | let hour: Int = degree/30
160 | return hour
161 | }
162 |
163 | private func calculateRadian(point: CGPoint, radius: CGFloat) -> Float {
164 | // origin(view's center)
165 | let centerPoint = CGPoint(x: radius, y: radius)
166 |
167 | // Find difference in coordinates.Since the upper side of the screen is the Y coordinate +, the Y coordinate changes the sign.
168 | let x = Float(point.x - centerPoint.x)
169 | let y = -Float(point.y - centerPoint.y)
170 |
171 | var radian = atan2f(y, x)
172 |
173 | // To correct radian(3/2π~7/2π: 0 o'clock = 3/2π)
174 | radian = radian * -1
175 | if radian < 0 {
176 | radian += angle360
177 | }
178 |
179 | if radian >= 0 && radian < angle270 {
180 | radian += angle360
181 | }
182 | return radian
183 | }
184 |
185 | private func calculateAngle(hour: Int) -> Float {
186 | let hourInt = hour > 12 ? hour - 12 : hour
187 | let hourAngle = angle270 + anglePerHour * Float(hourInt)
188 | /// revise by minute
189 | return hourAngle + ((Float(currentMinute)/60.0) * anglePerHour)
190 | }
191 | }
192 |
193 | @IBDesignable public class AMClockView: UIView {
194 |
195 | @IBInspectable public var clockBorderLineWidth: CGFloat = 5.0
196 | @IBInspectable public var smallClockIndexWidth: CGFloat = 1.0
197 | @IBInspectable public var clockIndexWidth: CGFloat = 2.0
198 | @IBInspectable public var hourHandWidth: CGFloat = 5.0
199 | @IBInspectable public var minuteHandWidth: CGFloat = 3.0
200 | @IBInspectable public var clockBorderLineColor: UIColor = .black
201 | @IBInspectable public var centerCircleLineColor: UIColor = .darkGray
202 | @IBInspectable public var hourHandColor: UIColor = .black
203 | @IBInspectable public var minuteHandColor: UIColor = .black
204 | @IBInspectable public var selectedTimeLabelTextColor: UIColor = .black
205 | @IBInspectable public var timeLabelTextColor: UIColor = .black
206 | @IBInspectable public var smallClockIndexColor: UIColor = .black
207 | @IBInspectable public var clockIndexColor: UIColor = .black
208 | @IBInspectable public var clockColor: UIColor = .clear
209 | @IBInspectable public var clockImage: UIImage?
210 | @IBInspectable public var minuteHandImage: UIImage?
211 | @IBInspectable public var hourHandImage: UIImage?
212 | @IBInspectable public var isShowSelectedTime: Bool = false {
213 | didSet {
214 | selectedTimeLabel.isHidden = !isShowSelectedTime
215 | }
216 | }
217 |
218 | public weak var delegate: AMClockViewDelegate?
219 | /// watch dials
220 | public var clockType: AMCVClockType = .arabic
221 | /// Time zone
222 | ///
223 | /// default is TimeZone.current
224 | public var timeZone: TimeZone? {
225 | didSet {
226 | model.timeZone = timeZone
227 | }
228 | }
229 | public var selectedDate: Date? {
230 | didSet{
231 | model.currentDate = selectedDate ?? Date()
232 | redrawClock()
233 | }
234 | }
235 |
236 | override public var bounds: CGRect {
237 | didSet {
238 | redrawClock()
239 | }
240 | }
241 |
242 | private let clockView = UIView()
243 | private let clockImageView = UIImageView()
244 | private let minuteHandImageView = UIImageView()
245 | private let hourHandImageView = UIImageView()
246 | private let selectedTimeLabel = UILabel()
247 | private let model = AMClockModel()
248 |
249 | private var drawLayer: CAShapeLayer?
250 | private var hourHandLayer: CAShapeLayer?
251 | private var minuteHandLayer: CAShapeLayer?
252 | private var panHourLayer: CAShapeLayer?
253 | private var panMinuteLayer: CAShapeLayer?
254 | private var radius: CGFloat {
255 | return clockView.frame.width/2
256 | }
257 | private var clockCenter: CGPoint {
258 | return CGPoint(x: radius, y: radius)
259 | }
260 | private var hourHandLength: CGFloat {
261 | return radius * 0.6
262 | }
263 | private var minuteHandLength: CGFloat {
264 | return radius * 0.8
265 | }
266 |
267 | //MARK: - Initialize
268 | required public init?(coder aDecoder: NSCoder) {
269 | super.init(coder:aDecoder)
270 | }
271 |
272 | override public init(frame: CGRect) {
273 | super.init(frame: frame)
274 | backgroundColor = .clear
275 | }
276 |
277 | convenience init() {
278 | self.init(frame: .zero)
279 | }
280 |
281 | override public func draw(_ rect: CGRect) {
282 | redrawClock()
283 | }
284 |
285 | //MARK: - Prepare View
286 | private func prepareClockView() {
287 | let length = (frame.width < frame.height) ? frame.width : frame.height
288 | clockView.frame = CGRect(x: frame.width/2 - length/2,
289 | y: frame.height/2 - length/2,
290 | width: length, height: length)
291 | addSubview(clockView)
292 | }
293 |
294 | private func prepareClockImageViews() {
295 | clockImageView.frame = clockView.bounds
296 | minuteHandImageView.frame = clockView.bounds
297 | hourHandImageView.frame = clockView.bounds
298 |
299 | clockImageView.image = clockImage
300 | minuteHandImageView.image = minuteHandImage
301 | hourHandImageView.image = hourHandImage
302 |
303 | clockView.addSubview(clockImageView)
304 | clockView.addSubview(hourHandImageView)
305 | clockView.addSubview(minuteHandImageView)
306 | }
307 |
308 | private func prepareSelectedTimeLabel() {
309 | selectedTimeLabel.frame = CGRect(x: clockCenter.x - (radius/2)/2,
310 | y: clockCenter.y - radius/3,
311 | width: radius/2, height: radius/3)
312 | clockView.addSubview(selectedTimeLabel)
313 | selectedTimeLabel.font = model.adjustFont(rect: selectedTimeLabel.frame)
314 | selectedTimeLabel.adjustsFontSizeToFitWidth = true
315 | selectedTimeLabel.textColor = selectedTimeLabelTextColor
316 | selectedTimeLabel.textAlignment = .center
317 | selectedTimeLabel.isHidden = !isShowSelectedTime
318 | }
319 |
320 | private func prepareTimeLabel() {
321 | var angle = model.angle270
322 | var smallRadius = radius - (radius/10 + clockBorderLineWidth)
323 | let length = radius/4
324 | smallRadius -= length/2
325 |
326 | // draw line (from center to out)
327 | for i in 0..<12 {
328 | let label = makeTimeLabel(length: length)
329 | label.text = clockType.time(index: i)
330 | label.font = model.adjustFont(rect: label.frame)
331 | clockView.addSubview(label)
332 | let point = CGPoint(x: clockCenter.x + smallRadius * CGFloat(cosf(angle)),
333 | y: clockCenter.y + smallRadius * CGFloat(sinf(angle)))
334 | label.center = point
335 | angle += model.angle30
336 | }
337 | }
338 |
339 | private func makeTimeLabel(length: CGFloat) -> UILabel {
340 | let label = UILabel(frame: CGRect(x: 0, y: 0, width: length, height: length))
341 | label.adjustsFontSizeToFitWidth = true
342 | label.textAlignment = .center
343 | label.textColor = timeLabelTextColor
344 | return label
345 | }
346 |
347 | //MARK: - Make Layer
348 | private func makeDrawLayer() -> CAShapeLayer {
349 | let drawLayer = CAShapeLayer()
350 | drawLayer.frame = clockView.bounds
351 | drawLayer.cornerRadius = radius
352 | drawLayer.masksToBounds = true
353 | if clockImage == nil {
354 | drawLayer.borderWidth = clockBorderLineWidth
355 | drawLayer.borderColor = clockBorderLineColor.cgColor
356 | }
357 | return drawLayer
358 | }
359 |
360 | private func makeSmallClockIndexLayer() -> CAShapeLayer {
361 | let layer = CAShapeLayer()
362 | layer.frame = drawLayer!.bounds
363 | layer.strokeColor = smallClockIndexColor.cgColor
364 | layer.fillColor = UIColor.clear.cgColor
365 | layer.lineWidth = smallClockIndexWidth
366 |
367 | let smallRadius = radius - (radius/20 + clockBorderLineWidth)
368 | var angle = model.angle270
369 | let path = UIBezierPath()
370 | // draw line (from center to out)
371 | for i in 0..<60 {
372 | if i%5 == 0 {
373 | angle += Float(Double.pi/30)
374 | continue
375 | }
376 | let startPoint = CGPoint(x: clockCenter.x + radius * CGFloat(cosf(angle)),
377 | y: clockCenter.y + radius * CGFloat(sinf(angle)))
378 | path.move(to: startPoint)
379 | let endPoint = CGPoint(x: clockCenter.x + smallRadius * CGFloat(cosf(angle)),
380 | y: clockCenter.y + smallRadius * CGFloat(sinf(angle)))
381 | path.addLine(to: endPoint)
382 | angle += Float(Double.pi/30)
383 | }
384 | layer.path = path.cgPath
385 | return layer
386 | }
387 |
388 | private func makeClockIndexLayer() -> CAShapeLayer {
389 | let layer = CAShapeLayer()
390 | layer.frame = drawLayer!.bounds
391 | layer.strokeColor = clockIndexColor.cgColor
392 | layer.fillColor = UIColor.clear.cgColor
393 | layer.lineWidth = clockIndexWidth
394 |
395 | let smallRadius = radius - (radius/10 + clockBorderLineWidth)
396 | var angle = model.angle270
397 | let path = UIBezierPath()
398 | // draw line (from center to out)
399 | for _ in 0..<12 {
400 | let startPoint = CGPoint(x: clockCenter.x + radius * CGFloat(cosf(angle)),
401 | y: clockCenter.y + radius * CGFloat(sinf(angle)))
402 | path.move(to: startPoint)
403 | let endPoint = CGPoint(x: clockCenter.x + smallRadius * CGFloat(cosf(angle)),
404 | y: clockCenter.y + smallRadius * CGFloat(sinf(angle)))
405 | path.addLine(to: endPoint)
406 | angle += model.angle30
407 | }
408 | layer.path = path.cgPath
409 | return layer
410 | }
411 |
412 | private func makeHourHandLayer() -> CAShapeLayer {
413 | let hourHandLayer = CAShapeLayer()
414 | hourHandLayer.frame = drawLayer!.bounds
415 | hourHandLayer.strokeColor = hourHandColor.cgColor
416 | hourHandLayer.fillColor = UIColor.clear.cgColor
417 | hourHandLayer.lineWidth = hourHandWidth
418 | hourHandLayer.path = makeHandPath(length: hourHandLength,
419 | angle: model.angle270).cgPath
420 | return hourHandLayer
421 | }
422 |
423 | private func makeMinuteHandLayer() -> CAShapeLayer {
424 | let minuteHandLayer = CAShapeLayer()
425 | minuteHandLayer.frame = drawLayer!.bounds
426 | minuteHandLayer.strokeColor = minuteHandColor.cgColor
427 | minuteHandLayer.fillColor = UIColor.clear.cgColor
428 | minuteHandLayer.lineWidth = minuteHandWidth
429 | minuteHandLayer.path = makeHandPath(length: minuteHandLength,
430 | angle: model.angle270).cgPath
431 | return minuteHandLayer
432 | }
433 |
434 | private func makePanMinuteLayer() -> CAShapeLayer {
435 | let panMinuteLayer = CAShapeLayer()
436 | let path = UIBezierPath(ovalIn: CGRect(x: clockCenter.x - radius,
437 | y: clockCenter.y - radius,
438 | width: radius * 2, height: radius * 2))
439 | panMinuteLayer.frame = drawLayer!.bounds
440 | panMinuteLayer.strokeColor = UIColor.clear.cgColor
441 | panMinuteLayer.fillColor = clockColor.cgColor
442 | panMinuteLayer.path = path.cgPath
443 | return panMinuteLayer
444 | }
445 |
446 | private func makePanHourLayer() -> CAShapeLayer {
447 | let panHourLayer = CAShapeLayer()
448 | let smallRadius = radius/2
449 | let path = UIBezierPath(ovalIn: CGRect(x: clockCenter.x - smallRadius,
450 | y: clockCenter.y - smallRadius,
451 | width: smallRadius * 2,
452 | height: smallRadius * 2))
453 | panHourLayer.frame = drawLayer!.bounds
454 | panHourLayer.strokeColor = centerCircleLineColor.cgColor
455 | panHourLayer.fillColor = UIColor.clear.cgColor
456 | panHourLayer.path = path.cgPath
457 | return panHourLayer
458 | }
459 |
460 | private func makeHandPath(length: CGFloat, angle: Float) -> UIBezierPath {
461 | let path = UIBezierPath()
462 | let point = CGPoint(x: clockCenter.x + length * CGFloat(cosf(angle)),
463 | y: clockCenter.y + length * CGFloat(sinf(angle)))
464 | path.move(to: clockCenter)
465 | path.addLine(to: point)
466 | return path
467 | }
468 |
469 | //MARK: - Gesture Action
470 | @objc func panAction(gesture: UIPanGestureRecognizer) {
471 | guard let panMinuteLayer = panMinuteLayer,
472 | let panHourLayer = panHourLayer else {
473 | return
474 | }
475 |
476 | let point = gesture.location(in: clockView)
477 | if gesture.state == .began {
478 | /// Set edit mode
479 | if UIBezierPath(cgPath: panHourLayer.path!).contains(point) {
480 | model.editType = .hour
481 | model.startAngle = model.currentHourAngle
482 | } else if UIBezierPath(cgPath: panMinuteLayer.path!).contains(point) {
483 | model.editType = .minute
484 | model.startAngle = model.currentMinuteAngle
485 | } else {
486 | model.editType = .none
487 | }
488 | } else {
489 | switch model.editType {
490 | case .none:
491 | /// Set edit mode
492 | if UIBezierPath(cgPath: panHourLayer.path!).contains(point) {
493 | model.editType = .hour
494 | model.startAngle = model.currentHourAngle
495 | } else if UIBezierPath(cgPath: panMinuteLayer.path!).contains(point) {
496 | model.editType = .minute
497 | model.startAngle = model.currentMinuteAngle
498 | }
499 | case .hour:
500 | editTimeHour(point: point)
501 | case .minute:
502 | editTimeMinute(point: point)
503 | }
504 | }
505 | }
506 |
507 | private func editTimeHour(point: CGPoint) {
508 | model.endAngle = model.calculateHourAngle(point: point, radius: radius)
509 | if model.startAngle == model.endAngle {
510 | return
511 | }
512 |
513 | let hour = model.calculateElapsedTime(startAngle: model.startAngle,
514 | endAngle: model.endAngle)
515 | model.appendHour(hour)
516 |
517 | drawHourHandLayer(angle: model.currentHourAngle)
518 | changedTime()
519 | model.startAngle = model.endAngle
520 | }
521 |
522 | private func editTimeMinute(point: CGPoint) {
523 | let minuteInt = model.calculateMinute(point: point, radius: radius)
524 | if minuteInt == model.currentMinute {
525 | return
526 | }
527 |
528 | model.updateCurrentDate(minute: minuteInt)
529 | model.endAngle = model.currentMinuteAngle
530 | model.revisedByAcrossHour(startAngle: model.startAngle, endAngle: model.endAngle)
531 |
532 | drawMinuteHandLayer(angle: model.currentMinuteAngle)
533 | drawHourHandLayer(angle: model.currentHourAngle)
534 | changedTime()
535 | model.startAngle = model.endAngle
536 | }
537 |
538 | //MARK: - Draw Hand
539 | private func drawMinuteHandLayer(angle: Float) {
540 | if minuteHandImage != nil {
541 | let rotation = angle - model.angle270
542 | minuteHandImageView.transform = CGAffineTransform(rotationAngle: CGFloat(rotation))
543 | return
544 | }
545 |
546 | guard let minuteHandLayer = minuteHandLayer else {
547 | return
548 | }
549 | CATransaction.begin()
550 | CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
551 | minuteHandLayer.path = makeHandPath(length: minuteHandLength, angle: angle).cgPath
552 | CATransaction.commit()
553 | }
554 |
555 | private func drawHourHandLayer(angle: Float) {
556 | if hourHandImage != nil {
557 | clockImageView.image = clockImage
558 | minuteHandImageView.image = minuteHandImage
559 | hourHandImageView.image = hourHandImage
560 | let rotation = angle - model.angle270
561 | hourHandImageView.transform = CGAffineTransform(rotationAngle: CGFloat(rotation))
562 | return
563 | }
564 |
565 | guard let hourHandLayer = hourHandLayer else {
566 | return
567 | }
568 | CATransaction.begin()
569 | CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
570 | hourHandLayer.path = makeHandPath(length: hourHandLength, angle: angle).cgPath
571 | CATransaction.commit()
572 | }
573 |
574 | //MARK: - Shwo/Clear
575 | private func clear() {
576 | clockView.subviews.forEach { $0.removeFromSuperview() }
577 | selectedTimeLabel.removeFromSuperview()
578 | clockImageView.removeFromSuperview()
579 | hourHandImageView.removeFromSuperview()
580 | minuteHandImageView.removeFromSuperview()
581 | clockView.removeFromSuperview()
582 | drawLayer?.removeFromSuperlayer()
583 | minuteHandImageView.transform = CGAffineTransform.identity
584 | hourHandImageView.transform = CGAffineTransform.identity
585 |
586 | hourHandLayer = nil
587 | minuteHandLayer = nil
588 | panHourLayer = nil
589 | panMinuteLayer = nil
590 | drawLayer = nil
591 | }
592 |
593 | private func relodClock() {
594 | clear()
595 |
596 | prepareClockView()
597 | prepareClockImageViews()
598 |
599 | drawLayer = makeDrawLayer()
600 | clockView.layer.addSublayer(drawLayer!)
601 |
602 | if clockImage == nil {
603 | drawLayer!.addSublayer(makeSmallClockIndexLayer())
604 | drawLayer!.addSublayer(makeClockIndexLayer())
605 | prepareTimeLabel()
606 | }
607 |
608 | panMinuteLayer = makePanMinuteLayer()
609 | drawLayer!.insertSublayer(panMinuteLayer!, at: 0)
610 | panHourLayer = makePanHourLayer()
611 | drawLayer!.addSublayer(panHourLayer!)
612 | let pan = UIPanGestureRecognizer(target: self,
613 | action: #selector(self.panAction(gesture:)))
614 | clockView.addGestureRecognizer(pan)
615 |
616 | if hourHandImage == nil {
617 | hourHandLayer = makeHourHandLayer()
618 | drawLayer!.addSublayer(hourHandLayer!)
619 | }
620 |
621 | if minuteHandImage == nil {
622 | minuteHandLayer = makeMinuteHandLayer()
623 | drawLayer!.addSublayer(minuteHandLayer!)
624 | }
625 |
626 | prepareSelectedTimeLabel()
627 | }
628 |
629 | private func changedTime() {
630 | selectedTimeLabel.text = model.formattedTime
631 | delegate?.clockView(self, didChangeDate: model.currentDate)
632 | }
633 |
634 | public func redrawClock() {
635 | relodClock()
636 | drawMinuteHandLayer(angle: model.currentMinuteAngle)
637 | drawHourHandLayer(angle: model.currentHourAngle)
638 | selectedTimeLabel.text = model.formattedTime
639 | }
640 | }
641 |
--------------------------------------------------------------------------------