├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── Demo
├── Ticker Label 2.xcodeproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── swiftpm
│ │ └── Package.resolved
├── Ticker Label.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
├── Ticker Label
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Info 2.plist
│ ├── Info.plist
│ ├── SceneDelegate.swift
│ └── ViewController.swift
├── Ticker LabelTests
│ ├── Info.plist
│ └── Ticker_LabelTests.swift
└── Ticker LabelUITests
│ ├── Info.plist
│ └── Ticker_LabelUITests.swift
├── Package.swift
├── README.md
├── Sources
└── MOTickerLabel
│ ├── Dollar.swift
│ ├── MOSingleCounterLabel.swift
│ ├── MOTickerLabel.swift
│ ├── UIImage+Extensions.swift
│ ├── UILabel+Extensions.swift
│ └── UIView+Extensions.swift
└── Tests
├── LinuxMain.swift
└── MOTickerLabelTests
├── MOTickerLabelTests.swift
└── XCTestManifests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Demo/Ticker Label 2.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Demo/Ticker Label 2.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "MOTickerLabel",
6 | "repositoryURL": "https://github.com/mosaic-io/MOTickerLabel",
7 | "state": {
8 | "branch": null,
9 | "revision": "5bf460582362529fa24a125f00aa10b8d752f98b",
10 | "version": "1.0.0"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/Demo/Ticker Label.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 52;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | CAC1E015240B8D8100ABFD34 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC1E014240B8D8100ABFD34 /* AppDelegate.swift */; };
11 | CAC1E017240B8D8100ABFD34 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC1E016240B8D8100ABFD34 /* SceneDelegate.swift */; };
12 | CAC1E019240B8D8100ABFD34 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC1E018240B8D8100ABFD34 /* ViewController.swift */; };
13 | CAC1E01C240B8D8100ABFD34 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CAC1E01A240B8D8100ABFD34 /* Main.storyboard */; };
14 | CAC1E01E240B8D8F00ABFD34 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CAC1E01D240B8D8F00ABFD34 /* Assets.xcassets */; };
15 | CAC1E021240B8D8F00ABFD34 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CAC1E01F240B8D8F00ABFD34 /* LaunchScreen.storyboard */; };
16 | CAC1E02C240B8D8F00ABFD34 /* Ticker_LabelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC1E02B240B8D8F00ABFD34 /* Ticker_LabelTests.swift */; };
17 | CAC1E037240B8D9000ABFD34 /* Ticker_LabelUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC1E036240B8D9000ABFD34 /* Ticker_LabelUITests.swift */; };
18 | CAE30914240C45F200C30E61 /* MOTickerLabel in Frameworks */ = {isa = PBXBuildFile; productRef = CAE30913240C45F200C30E61 /* MOTickerLabel */; };
19 | /* End PBXBuildFile section */
20 |
21 | /* Begin PBXContainerItemProxy section */
22 | CAC1E028240B8D8F00ABFD34 /* PBXContainerItemProxy */ = {
23 | isa = PBXContainerItemProxy;
24 | containerPortal = CAC1E009240B8D8100ABFD34 /* Project object */;
25 | proxyType = 1;
26 | remoteGlobalIDString = CAC1E010240B8D8100ABFD34;
27 | remoteInfo = "Ticker Label";
28 | };
29 | CAC1E033240B8D9000ABFD34 /* PBXContainerItemProxy */ = {
30 | isa = PBXContainerItemProxy;
31 | containerPortal = CAC1E009240B8D8100ABFD34 /* Project object */;
32 | proxyType = 1;
33 | remoteGlobalIDString = CAC1E010240B8D8100ABFD34;
34 | remoteInfo = "Ticker Label";
35 | };
36 | /* End PBXContainerItemProxy section */
37 |
38 | /* Begin PBXFileReference section */
39 | CAC1E011240B8D8100ABFD34 /* Ticker Label.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Ticker Label.app"; sourceTree = BUILT_PRODUCTS_DIR; };
40 | CAC1E014240B8D8100ABFD34 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
41 | CAC1E016240B8D8100ABFD34 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
42 | CAC1E018240B8D8100ABFD34 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
43 | CAC1E01B240B8D8100ABFD34 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
44 | CAC1E01D240B8D8F00ABFD34 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
45 | CAC1E020240B8D8F00ABFD34 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
46 | CAC1E022240B8D8F00ABFD34 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
47 | CAC1E027240B8D8F00ABFD34 /* Ticker LabelTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Ticker LabelTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
48 | CAC1E02B240B8D8F00ABFD34 /* Ticker_LabelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ticker_LabelTests.swift; sourceTree = ""; };
49 | CAC1E02D240B8D8F00ABFD34 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
50 | CAC1E032240B8D9000ABFD34 /* Ticker LabelUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Ticker LabelUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
51 | CAC1E036240B8D9000ABFD34 /* Ticker_LabelUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ticker_LabelUITests.swift; sourceTree = ""; };
52 | CAC1E038240B8D9000ABFD34 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
53 | CAC1E050240B939C00ABFD34 /* MOTickerLabel */ = {isa = PBXFileReference; lastKnownFileType = folder; name = MOTickerLabel; path = ../MOTickerLabel; sourceTree = ""; };
54 | /* End PBXFileReference section */
55 |
56 | /* Begin PBXFrameworksBuildPhase section */
57 | CAC1E00E240B8D8100ABFD34 /* Frameworks */ = {
58 | isa = PBXFrameworksBuildPhase;
59 | buildActionMask = 2147483647;
60 | files = (
61 | CAE30914240C45F200C30E61 /* MOTickerLabel in Frameworks */,
62 | );
63 | runOnlyForDeploymentPostprocessing = 0;
64 | };
65 | CAC1E024240B8D8F00ABFD34 /* Frameworks */ = {
66 | isa = PBXFrameworksBuildPhase;
67 | buildActionMask = 2147483647;
68 | files = (
69 | );
70 | runOnlyForDeploymentPostprocessing = 0;
71 | };
72 | CAC1E02F240B8D8F00ABFD34 /* Frameworks */ = {
73 | isa = PBXFrameworksBuildPhase;
74 | buildActionMask = 2147483647;
75 | files = (
76 | );
77 | runOnlyForDeploymentPostprocessing = 0;
78 | };
79 | /* End PBXFrameworksBuildPhase section */
80 |
81 | /* Begin PBXGroup section */
82 | CAC1E008240B8D8100ABFD34 = {
83 | isa = PBXGroup;
84 | children = (
85 | CAC1E050240B939C00ABFD34 /* MOTickerLabel */,
86 | CAC1E013240B8D8100ABFD34 /* Ticker Label */,
87 | CAC1E02A240B8D8F00ABFD34 /* Ticker LabelTests */,
88 | CAC1E035240B8D9000ABFD34 /* Ticker LabelUITests */,
89 | CAC1E012240B8D8100ABFD34 /* Products */,
90 | );
91 | sourceTree = "";
92 | };
93 | CAC1E012240B8D8100ABFD34 /* Products */ = {
94 | isa = PBXGroup;
95 | children = (
96 | CAC1E011240B8D8100ABFD34 /* Ticker Label.app */,
97 | CAC1E027240B8D8F00ABFD34 /* Ticker LabelTests.xctest */,
98 | CAC1E032240B8D9000ABFD34 /* Ticker LabelUITests.xctest */,
99 | );
100 | name = Products;
101 | sourceTree = "";
102 | };
103 | CAC1E013240B8D8100ABFD34 /* Ticker Label */ = {
104 | isa = PBXGroup;
105 | children = (
106 | CAC1E014240B8D8100ABFD34 /* AppDelegate.swift */,
107 | CAC1E016240B8D8100ABFD34 /* SceneDelegate.swift */,
108 | CAC1E018240B8D8100ABFD34 /* ViewController.swift */,
109 | CAC1E01A240B8D8100ABFD34 /* Main.storyboard */,
110 | CAC1E01D240B8D8F00ABFD34 /* Assets.xcassets */,
111 | CAC1E01F240B8D8F00ABFD34 /* LaunchScreen.storyboard */,
112 | CAC1E022240B8D8F00ABFD34 /* Info.plist */,
113 | );
114 | path = "Ticker Label";
115 | sourceTree = "";
116 | };
117 | CAC1E02A240B8D8F00ABFD34 /* Ticker LabelTests */ = {
118 | isa = PBXGroup;
119 | children = (
120 | CAC1E02B240B8D8F00ABFD34 /* Ticker_LabelTests.swift */,
121 | CAC1E02D240B8D8F00ABFD34 /* Info.plist */,
122 | );
123 | path = "Ticker LabelTests";
124 | sourceTree = "";
125 | };
126 | CAC1E035240B8D9000ABFD34 /* Ticker LabelUITests */ = {
127 | isa = PBXGroup;
128 | children = (
129 | CAC1E036240B8D9000ABFD34 /* Ticker_LabelUITests.swift */,
130 | CAC1E038240B8D9000ABFD34 /* Info.plist */,
131 | );
132 | path = "Ticker LabelUITests";
133 | sourceTree = "";
134 | };
135 | /* End PBXGroup section */
136 |
137 | /* Begin PBXNativeTarget section */
138 | CAC1E010240B8D8100ABFD34 /* Ticker Label */ = {
139 | isa = PBXNativeTarget;
140 | buildConfigurationList = CAC1E03B240B8D9000ABFD34 /* Build configuration list for PBXNativeTarget "Ticker Label" */;
141 | buildPhases = (
142 | CAC1E00D240B8D8100ABFD34 /* Sources */,
143 | CAC1E00E240B8D8100ABFD34 /* Frameworks */,
144 | CAC1E00F240B8D8100ABFD34 /* Resources */,
145 | );
146 | buildRules = (
147 | );
148 | dependencies = (
149 | );
150 | name = "Ticker Label";
151 | packageProductDependencies = (
152 | CAE30913240C45F200C30E61 /* MOTickerLabel */,
153 | );
154 | productName = "Ticker Label";
155 | productReference = CAC1E011240B8D8100ABFD34 /* Ticker Label.app */;
156 | productType = "com.apple.product-type.application";
157 | };
158 | CAC1E026240B8D8F00ABFD34 /* Ticker LabelTests */ = {
159 | isa = PBXNativeTarget;
160 | buildConfigurationList = CAC1E03E240B8D9000ABFD34 /* Build configuration list for PBXNativeTarget "Ticker LabelTests" */;
161 | buildPhases = (
162 | CAC1E023240B8D8F00ABFD34 /* Sources */,
163 | CAC1E024240B8D8F00ABFD34 /* Frameworks */,
164 | CAC1E025240B8D8F00ABFD34 /* Resources */,
165 | );
166 | buildRules = (
167 | );
168 | dependencies = (
169 | CAC1E029240B8D8F00ABFD34 /* PBXTargetDependency */,
170 | );
171 | name = "Ticker LabelTests";
172 | productName = "Ticker LabelTests";
173 | productReference = CAC1E027240B8D8F00ABFD34 /* Ticker LabelTests.xctest */;
174 | productType = "com.apple.product-type.bundle.unit-test";
175 | };
176 | CAC1E031240B8D8F00ABFD34 /* Ticker LabelUITests */ = {
177 | isa = PBXNativeTarget;
178 | buildConfigurationList = CAC1E041240B8D9000ABFD34 /* Build configuration list for PBXNativeTarget "Ticker LabelUITests" */;
179 | buildPhases = (
180 | CAC1E02E240B8D8F00ABFD34 /* Sources */,
181 | CAC1E02F240B8D8F00ABFD34 /* Frameworks */,
182 | CAC1E030240B8D8F00ABFD34 /* Resources */,
183 | );
184 | buildRules = (
185 | );
186 | dependencies = (
187 | CAC1E034240B8D9000ABFD34 /* PBXTargetDependency */,
188 | );
189 | name = "Ticker LabelUITests";
190 | productName = "Ticker LabelUITests";
191 | productReference = CAC1E032240B8D9000ABFD34 /* Ticker LabelUITests.xctest */;
192 | productType = "com.apple.product-type.bundle.ui-testing";
193 | };
194 | /* End PBXNativeTarget section */
195 |
196 | /* Begin PBXProject section */
197 | CAC1E009240B8D8100ABFD34 /* Project object */ = {
198 | isa = PBXProject;
199 | attributes = {
200 | LastSwiftUpdateCheck = 1130;
201 | LastUpgradeCheck = 1130;
202 | ORGANIZATIONNAME = Mosaic;
203 | TargetAttributes = {
204 | CAC1E010240B8D8100ABFD34 = {
205 | CreatedOnToolsVersion = 11.3.1;
206 | };
207 | CAC1E026240B8D8F00ABFD34 = {
208 | CreatedOnToolsVersion = 11.3.1;
209 | TestTargetID = CAC1E010240B8D8100ABFD34;
210 | };
211 | CAC1E031240B8D8F00ABFD34 = {
212 | CreatedOnToolsVersion = 11.3.1;
213 | TestTargetID = CAC1E010240B8D8100ABFD34;
214 | };
215 | };
216 | };
217 | buildConfigurationList = CAC1E00C240B8D8100ABFD34 /* Build configuration list for PBXProject "Ticker Label" */;
218 | compatibilityVersion = "Xcode 9.3";
219 | developmentRegion = en;
220 | hasScannedForEncodings = 0;
221 | knownRegions = (
222 | en,
223 | Base,
224 | );
225 | mainGroup = CAC1E008240B8D8100ABFD34;
226 | packageReferences = (
227 | CAE30912240C45F200C30E61 /* XCRemoteSwiftPackageReference "MOTickerLabel" */,
228 | );
229 | productRefGroup = CAC1E012240B8D8100ABFD34 /* Products */;
230 | projectDirPath = "";
231 | projectRoot = "";
232 | targets = (
233 | CAC1E010240B8D8100ABFD34 /* Ticker Label */,
234 | CAC1E026240B8D8F00ABFD34 /* Ticker LabelTests */,
235 | CAC1E031240B8D8F00ABFD34 /* Ticker LabelUITests */,
236 | );
237 | };
238 | /* End PBXProject section */
239 |
240 | /* Begin PBXResourcesBuildPhase section */
241 | CAC1E00F240B8D8100ABFD34 /* Resources */ = {
242 | isa = PBXResourcesBuildPhase;
243 | buildActionMask = 2147483647;
244 | files = (
245 | CAC1E021240B8D8F00ABFD34 /* LaunchScreen.storyboard in Resources */,
246 | CAC1E01E240B8D8F00ABFD34 /* Assets.xcassets in Resources */,
247 | CAC1E01C240B8D8100ABFD34 /* Main.storyboard in Resources */,
248 | );
249 | runOnlyForDeploymentPostprocessing = 0;
250 | };
251 | CAC1E025240B8D8F00ABFD34 /* Resources */ = {
252 | isa = PBXResourcesBuildPhase;
253 | buildActionMask = 2147483647;
254 | files = (
255 | );
256 | runOnlyForDeploymentPostprocessing = 0;
257 | };
258 | CAC1E030240B8D8F00ABFD34 /* Resources */ = {
259 | isa = PBXResourcesBuildPhase;
260 | buildActionMask = 2147483647;
261 | files = (
262 | );
263 | runOnlyForDeploymentPostprocessing = 0;
264 | };
265 | /* End PBXResourcesBuildPhase section */
266 |
267 | /* Begin PBXSourcesBuildPhase section */
268 | CAC1E00D240B8D8100ABFD34 /* Sources */ = {
269 | isa = PBXSourcesBuildPhase;
270 | buildActionMask = 2147483647;
271 | files = (
272 | CAC1E019240B8D8100ABFD34 /* ViewController.swift in Sources */,
273 | CAC1E015240B8D8100ABFD34 /* AppDelegate.swift in Sources */,
274 | CAC1E017240B8D8100ABFD34 /* SceneDelegate.swift in Sources */,
275 | );
276 | runOnlyForDeploymentPostprocessing = 0;
277 | };
278 | CAC1E023240B8D8F00ABFD34 /* Sources */ = {
279 | isa = PBXSourcesBuildPhase;
280 | buildActionMask = 2147483647;
281 | files = (
282 | CAC1E02C240B8D8F00ABFD34 /* Ticker_LabelTests.swift in Sources */,
283 | );
284 | runOnlyForDeploymentPostprocessing = 0;
285 | };
286 | CAC1E02E240B8D8F00ABFD34 /* Sources */ = {
287 | isa = PBXSourcesBuildPhase;
288 | buildActionMask = 2147483647;
289 | files = (
290 | CAC1E037240B8D9000ABFD34 /* Ticker_LabelUITests.swift in Sources */,
291 | );
292 | runOnlyForDeploymentPostprocessing = 0;
293 | };
294 | /* End PBXSourcesBuildPhase section */
295 |
296 | /* Begin PBXTargetDependency section */
297 | CAC1E029240B8D8F00ABFD34 /* PBXTargetDependency */ = {
298 | isa = PBXTargetDependency;
299 | target = CAC1E010240B8D8100ABFD34 /* Ticker Label */;
300 | targetProxy = CAC1E028240B8D8F00ABFD34 /* PBXContainerItemProxy */;
301 | };
302 | CAC1E034240B8D9000ABFD34 /* PBXTargetDependency */ = {
303 | isa = PBXTargetDependency;
304 | target = CAC1E010240B8D8100ABFD34 /* Ticker Label */;
305 | targetProxy = CAC1E033240B8D9000ABFD34 /* PBXContainerItemProxy */;
306 | };
307 | /* End PBXTargetDependency section */
308 |
309 | /* Begin PBXVariantGroup section */
310 | CAC1E01A240B8D8100ABFD34 /* Main.storyboard */ = {
311 | isa = PBXVariantGroup;
312 | children = (
313 | CAC1E01B240B8D8100ABFD34 /* Base */,
314 | );
315 | name = Main.storyboard;
316 | sourceTree = "";
317 | };
318 | CAC1E01F240B8D8F00ABFD34 /* LaunchScreen.storyboard */ = {
319 | isa = PBXVariantGroup;
320 | children = (
321 | CAC1E020240B8D8F00ABFD34 /* Base */,
322 | );
323 | name = LaunchScreen.storyboard;
324 | sourceTree = "";
325 | };
326 | /* End PBXVariantGroup section */
327 |
328 | /* Begin XCBuildConfiguration section */
329 | CAC1E039240B8D9000ABFD34 /* Debug */ = {
330 | isa = XCBuildConfiguration;
331 | buildSettings = {
332 | ALWAYS_SEARCH_USER_PATHS = NO;
333 | CLANG_ANALYZER_NONNULL = YES;
334 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
335 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
336 | CLANG_CXX_LIBRARY = "libc++";
337 | CLANG_ENABLE_MODULES = YES;
338 | CLANG_ENABLE_OBJC_ARC = YES;
339 | CLANG_ENABLE_OBJC_WEAK = YES;
340 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
341 | CLANG_WARN_BOOL_CONVERSION = YES;
342 | CLANG_WARN_COMMA = YES;
343 | CLANG_WARN_CONSTANT_CONVERSION = YES;
344 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
345 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
346 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
347 | CLANG_WARN_EMPTY_BODY = YES;
348 | CLANG_WARN_ENUM_CONVERSION = YES;
349 | CLANG_WARN_INFINITE_RECURSION = YES;
350 | CLANG_WARN_INT_CONVERSION = YES;
351 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
352 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
353 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
354 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
355 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
356 | CLANG_WARN_STRICT_PROTOTYPES = YES;
357 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
358 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
359 | CLANG_WARN_UNREACHABLE_CODE = YES;
360 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
361 | COPY_PHASE_STRIP = NO;
362 | DEBUG_INFORMATION_FORMAT = dwarf;
363 | ENABLE_STRICT_OBJC_MSGSEND = YES;
364 | ENABLE_TESTABILITY = YES;
365 | GCC_C_LANGUAGE_STANDARD = gnu11;
366 | GCC_DYNAMIC_NO_PIC = NO;
367 | GCC_NO_COMMON_BLOCKS = YES;
368 | GCC_OPTIMIZATION_LEVEL = 0;
369 | GCC_PREPROCESSOR_DEFINITIONS = (
370 | "DEBUG=1",
371 | "$(inherited)",
372 | );
373 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
374 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
375 | GCC_WARN_UNDECLARED_SELECTOR = YES;
376 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
377 | GCC_WARN_UNUSED_FUNCTION = YES;
378 | GCC_WARN_UNUSED_VARIABLE = YES;
379 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
380 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
381 | MTL_FAST_MATH = YES;
382 | ONLY_ACTIVE_ARCH = YES;
383 | SDKROOT = iphoneos;
384 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
385 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
386 | };
387 | name = Debug;
388 | };
389 | CAC1E03A240B8D9000ABFD34 /* Release */ = {
390 | isa = XCBuildConfiguration;
391 | buildSettings = {
392 | ALWAYS_SEARCH_USER_PATHS = NO;
393 | CLANG_ANALYZER_NONNULL = YES;
394 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
395 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
396 | CLANG_CXX_LIBRARY = "libc++";
397 | CLANG_ENABLE_MODULES = YES;
398 | CLANG_ENABLE_OBJC_ARC = YES;
399 | CLANG_ENABLE_OBJC_WEAK = YES;
400 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
401 | CLANG_WARN_BOOL_CONVERSION = YES;
402 | CLANG_WARN_COMMA = YES;
403 | CLANG_WARN_CONSTANT_CONVERSION = YES;
404 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
405 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
406 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
407 | CLANG_WARN_EMPTY_BODY = YES;
408 | CLANG_WARN_ENUM_CONVERSION = YES;
409 | CLANG_WARN_INFINITE_RECURSION = YES;
410 | CLANG_WARN_INT_CONVERSION = YES;
411 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
412 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
413 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
414 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
415 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
416 | CLANG_WARN_STRICT_PROTOTYPES = YES;
417 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
418 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
419 | CLANG_WARN_UNREACHABLE_CODE = YES;
420 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
421 | COPY_PHASE_STRIP = NO;
422 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
423 | ENABLE_NS_ASSERTIONS = NO;
424 | ENABLE_STRICT_OBJC_MSGSEND = YES;
425 | GCC_C_LANGUAGE_STANDARD = gnu11;
426 | GCC_NO_COMMON_BLOCKS = YES;
427 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
428 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
429 | GCC_WARN_UNDECLARED_SELECTOR = YES;
430 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
431 | GCC_WARN_UNUSED_FUNCTION = YES;
432 | GCC_WARN_UNUSED_VARIABLE = YES;
433 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
434 | MTL_ENABLE_DEBUG_INFO = NO;
435 | MTL_FAST_MATH = YES;
436 | SDKROOT = iphoneos;
437 | SWIFT_COMPILATION_MODE = wholemodule;
438 | SWIFT_OPTIMIZATION_LEVEL = "-O";
439 | VALIDATE_PRODUCT = YES;
440 | };
441 | name = Release;
442 | };
443 | CAC1E03C240B8D9000ABFD34 /* Debug */ = {
444 | isa = XCBuildConfiguration;
445 | buildSettings = {
446 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
447 | CODE_SIGN_STYLE = Automatic;
448 | INFOPLIST_FILE = "Ticker Label/Info.plist";
449 | LD_RUNPATH_SEARCH_PATHS = (
450 | "$(inherited)",
451 | "@executable_path/Frameworks",
452 | );
453 | PRODUCT_BUNDLE_IDENTIFIER = "com.mosaic.Ticker-Label";
454 | PRODUCT_NAME = "$(TARGET_NAME)";
455 | SWIFT_VERSION = 5.0;
456 | TARGETED_DEVICE_FAMILY = "1,2";
457 | };
458 | name = Debug;
459 | };
460 | CAC1E03D240B8D9000ABFD34 /* Release */ = {
461 | isa = XCBuildConfiguration;
462 | buildSettings = {
463 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
464 | CODE_SIGN_STYLE = Automatic;
465 | INFOPLIST_FILE = "Ticker Label/Info.plist";
466 | LD_RUNPATH_SEARCH_PATHS = (
467 | "$(inherited)",
468 | "@executable_path/Frameworks",
469 | );
470 | PRODUCT_BUNDLE_IDENTIFIER = "com.mosaic.Ticker-Label";
471 | PRODUCT_NAME = "$(TARGET_NAME)";
472 | SWIFT_VERSION = 5.0;
473 | TARGETED_DEVICE_FAMILY = "1,2";
474 | };
475 | name = Release;
476 | };
477 | CAC1E03F240B8D9000ABFD34 /* Debug */ = {
478 | isa = XCBuildConfiguration;
479 | buildSettings = {
480 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
481 | BUNDLE_LOADER = "$(TEST_HOST)";
482 | CODE_SIGN_STYLE = Automatic;
483 | INFOPLIST_FILE = "Ticker LabelTests/Info.plist";
484 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
485 | LD_RUNPATH_SEARCH_PATHS = (
486 | "$(inherited)",
487 | "@executable_path/Frameworks",
488 | "@loader_path/Frameworks",
489 | );
490 | PRODUCT_BUNDLE_IDENTIFIER = "com.mosaic.Ticker-LabelTests";
491 | PRODUCT_NAME = "$(TARGET_NAME)";
492 | SWIFT_VERSION = 5.0;
493 | TARGETED_DEVICE_FAMILY = "1,2";
494 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Ticker Label.app/Ticker Label";
495 | };
496 | name = Debug;
497 | };
498 | CAC1E040240B8D9000ABFD34 /* Release */ = {
499 | isa = XCBuildConfiguration;
500 | buildSettings = {
501 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
502 | BUNDLE_LOADER = "$(TEST_HOST)";
503 | CODE_SIGN_STYLE = Automatic;
504 | INFOPLIST_FILE = "Ticker LabelTests/Info.plist";
505 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
506 | LD_RUNPATH_SEARCH_PATHS = (
507 | "$(inherited)",
508 | "@executable_path/Frameworks",
509 | "@loader_path/Frameworks",
510 | );
511 | PRODUCT_BUNDLE_IDENTIFIER = "com.mosaic.Ticker-LabelTests";
512 | PRODUCT_NAME = "$(TARGET_NAME)";
513 | SWIFT_VERSION = 5.0;
514 | TARGETED_DEVICE_FAMILY = "1,2";
515 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Ticker Label.app/Ticker Label";
516 | };
517 | name = Release;
518 | };
519 | CAC1E042240B8D9000ABFD34 /* Debug */ = {
520 | isa = XCBuildConfiguration;
521 | buildSettings = {
522 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
523 | CODE_SIGN_STYLE = Automatic;
524 | INFOPLIST_FILE = "Ticker LabelUITests/Info.plist";
525 | LD_RUNPATH_SEARCH_PATHS = (
526 | "$(inherited)",
527 | "@executable_path/Frameworks",
528 | "@loader_path/Frameworks",
529 | );
530 | PRODUCT_BUNDLE_IDENTIFIER = "com.mosaic.Ticker-LabelUITests";
531 | PRODUCT_NAME = "$(TARGET_NAME)";
532 | SWIFT_VERSION = 5.0;
533 | TARGETED_DEVICE_FAMILY = "1,2";
534 | TEST_TARGET_NAME = "Ticker Label";
535 | };
536 | name = Debug;
537 | };
538 | CAC1E043240B8D9000ABFD34 /* Release */ = {
539 | isa = XCBuildConfiguration;
540 | buildSettings = {
541 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
542 | CODE_SIGN_STYLE = Automatic;
543 | INFOPLIST_FILE = "Ticker LabelUITests/Info.plist";
544 | LD_RUNPATH_SEARCH_PATHS = (
545 | "$(inherited)",
546 | "@executable_path/Frameworks",
547 | "@loader_path/Frameworks",
548 | );
549 | PRODUCT_BUNDLE_IDENTIFIER = "com.mosaic.Ticker-LabelUITests";
550 | PRODUCT_NAME = "$(TARGET_NAME)";
551 | SWIFT_VERSION = 5.0;
552 | TARGETED_DEVICE_FAMILY = "1,2";
553 | TEST_TARGET_NAME = "Ticker Label";
554 | };
555 | name = Release;
556 | };
557 | /* End XCBuildConfiguration section */
558 |
559 | /* Begin XCConfigurationList section */
560 | CAC1E00C240B8D8100ABFD34 /* Build configuration list for PBXProject "Ticker Label" */ = {
561 | isa = XCConfigurationList;
562 | buildConfigurations = (
563 | CAC1E039240B8D9000ABFD34 /* Debug */,
564 | CAC1E03A240B8D9000ABFD34 /* Release */,
565 | );
566 | defaultConfigurationIsVisible = 0;
567 | defaultConfigurationName = Release;
568 | };
569 | CAC1E03B240B8D9000ABFD34 /* Build configuration list for PBXNativeTarget "Ticker Label" */ = {
570 | isa = XCConfigurationList;
571 | buildConfigurations = (
572 | CAC1E03C240B8D9000ABFD34 /* Debug */,
573 | CAC1E03D240B8D9000ABFD34 /* Release */,
574 | );
575 | defaultConfigurationIsVisible = 0;
576 | defaultConfigurationName = Release;
577 | };
578 | CAC1E03E240B8D9000ABFD34 /* Build configuration list for PBXNativeTarget "Ticker LabelTests" */ = {
579 | isa = XCConfigurationList;
580 | buildConfigurations = (
581 | CAC1E03F240B8D9000ABFD34 /* Debug */,
582 | CAC1E040240B8D9000ABFD34 /* Release */,
583 | );
584 | defaultConfigurationIsVisible = 0;
585 | defaultConfigurationName = Release;
586 | };
587 | CAC1E041240B8D9000ABFD34 /* Build configuration list for PBXNativeTarget "Ticker LabelUITests" */ = {
588 | isa = XCConfigurationList;
589 | buildConfigurations = (
590 | CAC1E042240B8D9000ABFD34 /* Debug */,
591 | CAC1E043240B8D9000ABFD34 /* Release */,
592 | );
593 | defaultConfigurationIsVisible = 0;
594 | defaultConfigurationName = Release;
595 | };
596 | /* End XCConfigurationList section */
597 |
598 | /* Begin XCRemoteSwiftPackageReference section */
599 | CAE30912240C45F200C30E61 /* XCRemoteSwiftPackageReference "MOTickerLabel" */ = {
600 | isa = XCRemoteSwiftPackageReference;
601 | repositoryURL = "https://github.com/mosaic-io/MOTickerLabel";
602 | requirement = {
603 | kind = upToNextMajorVersion;
604 | minimumVersion = 1.0.0;
605 | };
606 | };
607 | /* End XCRemoteSwiftPackageReference section */
608 |
609 | /* Begin XCSwiftPackageProductDependency section */
610 | CAE30913240C45F200C30E61 /* MOTickerLabel */ = {
611 | isa = XCSwiftPackageProductDependency;
612 | package = CAE30912240C45F200C30E61 /* XCRemoteSwiftPackageReference "MOTickerLabel" */;
613 | productName = MOTickerLabel;
614 | };
615 | /* End XCSwiftPackageProductDependency section */
616 | };
617 | rootObject = CAC1E009240B8D8100ABFD34 /* Project object */;
618 | }
619 |
--------------------------------------------------------------------------------
/Demo/Ticker Label.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Demo/Ticker Label.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Demo/Ticker Label.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "MOTickerLabel",
6 | "repositoryURL": "https://github.com/mosaic-io/MOTickerLabel",
7 | "state": {
8 | "branch": null,
9 | "revision": "5bf460582362529fa24a125f00aa10b8d752f98b",
10 | "version": "1.0.0"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/Demo/Ticker Label/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Ticker Label
4 | //
5 | // Created by Mike Choi on 2/29/20.
6 | // Copyright © 2020 Mosaic. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 |
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | return true
19 | }
20 |
21 | // MARK: UISceneSession Lifecycle
22 |
23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
24 | // Called when a new scene session is being created.
25 | // Use this method to select a configuration to create the new scene with.
26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
27 | }
28 |
29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
30 | // Called when the user discards a scene session.
31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
33 | }
34 |
35 |
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/Demo/Ticker Label/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 | }
--------------------------------------------------------------------------------
/Demo/Ticker Label/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Demo/Ticker Label/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 |
--------------------------------------------------------------------------------
/Demo/Ticker Label/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 |
--------------------------------------------------------------------------------
/Demo/Ticker Label/Info 2.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 | UISceneStoryboardFile
37 | Main
38 |
39 |
40 |
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIMainStoryboardFile
45 | Main
46 | UIRequiredDeviceCapabilities
47 |
48 | armv7
49 |
50 | UISupportedInterfaceOrientations
51 |
52 | UIInterfaceOrientationPortrait
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 | UISupportedInterfaceOrientations~ipad
57 |
58 | UIInterfaceOrientationPortrait
59 | UIInterfaceOrientationPortraitUpsideDown
60 | UIInterfaceOrientationLandscapeLeft
61 | UIInterfaceOrientationLandscapeRight
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Demo/Ticker Label/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 | UISceneStoryboardFile
37 | Main
38 |
39 |
40 |
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIMainStoryboardFile
45 | Main
46 | UIRequiredDeviceCapabilities
47 |
48 | armv7
49 |
50 | UISupportedInterfaceOrientations
51 |
52 | UIInterfaceOrientationPortrait
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 | UISupportedInterfaceOrientations~ipad
57 |
58 | UIInterfaceOrientationPortrait
59 | UIInterfaceOrientationPortraitUpsideDown
60 | UIInterfaceOrientationLandscapeLeft
61 | UIInterfaceOrientationLandscapeRight
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Demo/Ticker Label/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // Ticker Label
4 | //
5 | // Created by Mike Choi on 2/29/20.
6 | // Copyright © 2020 Mosaic. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
12 |
13 | var window: UIWindow?
14 |
15 |
16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
20 | guard let _ = (scene as? UIWindowScene) else { return }
21 | }
22 |
23 | func sceneDidDisconnect(_ scene: UIScene) {
24 | // Called as the scene is being released by the system.
25 | // This occurs shortly after the scene enters the background, or when its session is discarded.
26 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
27 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
28 | }
29 |
30 | func sceneDidBecomeActive(_ scene: UIScene) {
31 | // Called when the scene has moved from an inactive state to an active state.
32 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
33 | }
34 |
35 | func sceneWillResignActive(_ scene: UIScene) {
36 | // Called when the scene will move from an active state to an inactive state.
37 | // This may occur due to temporary interruptions (ex. an incoming phone call).
38 | }
39 |
40 | func sceneWillEnterForeground(_ scene: UIScene) {
41 | // Called as the scene transitions from the background to the foreground.
42 | // Use this method to undo the changes made on entering the background.
43 | }
44 |
45 | func sceneDidEnterBackground(_ scene: UIScene) {
46 | // Called as the scene transitions from the foreground to the background.
47 | // Use this method to save data, release shared resources, and store enough scene-specific state information
48 | // to restore the scene back to its current state.
49 | }
50 |
51 |
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/Demo/Ticker Label/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Ticker Label
4 | //
5 | // Created by Mike Choi on 2/29/20.
6 | // Copyright © 2020 Mosaic. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Combine
11 | import MOTickerLabel
12 |
13 | final class ViewController: UIViewController {
14 |
15 | let slider = UISlider()
16 | let label = MOTickerLabel(frame: CGRect(x: 0, y: 0, width: 300, height: 50), value: Dollar(float: 12.03))
17 |
18 | @Published var value: Float?
19 | var sliderStream: AnyCancellable?
20 |
21 | override func viewDidLoad() {
22 | super.viewDidLoad()
23 |
24 | view.backgroundColor = .white
25 |
26 | slider.minimumValue = 9
27 | slider.maximumValue = 999
28 | slider.addTarget(self, action: #selector(sliderChanged(_:)), for: .valueChanged)
29 | slider.translatesAutoresizingMaskIntoConstraints = false
30 | label.translatesAutoresizingMaskIntoConstraints = false
31 |
32 | view.addSubview(slider)
33 | view.addSubview(label)
34 |
35 | NSLayoutConstraint.activate([
36 | slider.topAnchor.constraint(equalTo: view.topAnchor, constant: 100),
37 | slider.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
38 | slider.widthAnchor.constraint(equalToConstant: 350),
39 | label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
40 | label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
41 | ])
42 |
43 |
44 | sliderStream = $value
45 | .compactMap { $0 }
46 | .map { Dollar(float: $0) }
47 | .removeDuplicates()
48 | .debounce(for: 0.005, scheduler: RunLoop.main)
49 | .assign(to: \.value, on: self.label)
50 | }
51 |
52 | @objc func sliderChanged(_ sender: UISlider) {
53 | value = sender.value
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Demo/Ticker LabelTests/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 |
--------------------------------------------------------------------------------
/Demo/Ticker LabelTests/Ticker_LabelTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Ticker_LabelTests.swift
3 | // Ticker LabelTests
4 | //
5 | // Created by Mike Choi on 2/29/20.
6 | // Copyright © 2020 Mosaic. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Ticker_Label
11 |
12 | class Ticker_LabelTests: XCTestCase {
13 |
14 | override func setUp() {
15 | // Put setup code here. This method is called before the invocation of each test method in the class.
16 | }
17 |
18 | override func tearDown() {
19 | // Put teardown code here. This method is called after the invocation of each test method in the class.
20 | }
21 |
22 | func testExample() {
23 | // This is an example of a functional test case.
24 | // Use XCTAssert and related functions to verify your tests produce the correct results.
25 | }
26 |
27 | func testPerformanceExample() {
28 | // This is an example of a performance test case.
29 | self.measure {
30 | // Put the code you want to measure the time of here.
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/Demo/Ticker LabelUITests/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 |
--------------------------------------------------------------------------------
/Demo/Ticker LabelUITests/Ticker_LabelUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Ticker_LabelUITests.swift
3 | // Ticker LabelUITests
4 | //
5 | // Created by Mike Choi on 2/29/20.
6 | // Copyright © 2020 Mosaic. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class Ticker_LabelUITests: XCTestCase {
12 |
13 | override func setUp() {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 |
16 | // In UI tests it is usually best to stop immediately when a failure occurs.
17 | continueAfterFailure = false
18 |
19 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
20 | }
21 |
22 | override func tearDown() {
23 | // Put teardown code here. This method is called after the invocation of each test method in the class.
24 | }
25 |
26 | func testExample() {
27 | // UI tests must launch the application that they test.
28 | let app = XCUIApplication()
29 | app.launch()
30 |
31 | // Use recording to get started writing UI tests.
32 | // Use XCTAssert and related functions to verify your tests produce the correct results.
33 | }
34 |
35 | func testLaunchPerformance() {
36 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
37 | // This measures how long it takes to launch your application.
38 | measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) {
39 | XCUIApplication().launch()
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/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 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "MOTickerLabel",
8 | platforms: [
9 | .iOS(.v13)
10 | ],
11 | products: [
12 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
13 | .library(
14 | name: "MOTickerLabel",
15 | targets: ["MOTickerLabel"]),
16 | ],
17 | dependencies: [
18 | // Dependencies declare other packages that this package depends on.
19 | // .package(url: /* package url */, from: "1.0.0"),
20 | ],
21 | targets: [
22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
23 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
24 | .target(
25 | name: "MOTickerLabel",
26 | dependencies: []),
27 | .testTarget(
28 | name: "MOTickerLabelTests",
29 | dependencies: ["MOTickerLabel"]),
30 | ]
31 | )
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MOTickerLabel
2 |
3 | Ticker Label that animates changes in monetary value. To learn more about how `MOTickerLabel` works, checkout this [Mosaic Enginnering blog post](https://blog.getmosaic.io/The-Ticker).
4 |
5 | > The label currently only supports US dollar.
6 |
7 | ## Sample Code
8 |
9 | ```swift
10 | let label = TickerLabel(frame: CGRect(x: 0, y: 0, width: 300, height: 50), value: Dollar(float: 12.03))
11 | view.addSubview(label)
12 | label.value = Dollar(float: 10.19)
13 |
14 | // Animate change after two seconds!
15 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
16 | self.label.value = Dollar(float: 940.39)
17 | }
18 | ```
19 |
20 | ## Demo
21 |
22 | 
23 |
24 | # LICENSE
25 |
26 | MIT License
27 |
28 | Copyright (c) [2020] [MOTickerLabel by Mosaic Engineering]
29 |
30 | Permission is hereby granted, free of charge, to any person obtaining a copy
31 | of this software and associated documentation files (the "Software"), to deal
32 | in the Software without restriction, including without limitation the rights
33 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
34 | copies of the Software, and to permit persons to whom the Software is
35 | furnished to do so, subject to the following conditions:
36 |
37 | The above copyright notice and this permission notice shall be included in all
38 | copies or substantial portions of the Software.
39 |
40 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
41 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
42 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
43 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
44 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
45 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
46 | SOFTWARE.
47 |
--------------------------------------------------------------------------------
/Sources/MOTickerLabel/Dollar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Dollar.swift
3 | // Ticker Label
4 | //
5 | // Created by Mike Choi on 2/29/20.
6 | // Copyright © 2020 Mosaic. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct Dollar: Comparable, CustomStringConvertible {
12 | static var commaFormatter: NumberFormatter = {
13 | let formatter = NumberFormatter()
14 | formatter.numberStyle = .currency
15 | formatter.maximumFractionDigits = 2
16 | formatter.locale = Locale(identifier: "en_US")
17 | return formatter
18 | }()
19 |
20 | var amount: Float
21 |
22 | var numberOfDigits: Int {
23 | if amount < 1 { return 3 }
24 | else { return Int(floor(log10(amount))) + 1 + 2 }
25 | }
26 |
27 | public var description: String {
28 | Dollar.commaFormatter.string(from: NSNumber(value: Double(amount))) ?? "$---"
29 | }
30 |
31 | public init(float: Float) {
32 | amount = Float(round(100 * float) / 100)
33 | }
34 |
35 | public static func < (lhs: Dollar, rhs: Dollar) -> Bool {
36 | lhs.amount < rhs.amount
37 | }
38 |
39 | /**
40 | Algorithm is `(1234 // (10 ** 2)) % 10`
41 |
42 | - Returns: nth element of `amount`, where `0`th element is the least significant digit
43 | - Note: Returns `nil` if provided index is out of bounds
44 | */
45 | func digit(at i: Int) -> Int? {
46 | if amount == 0.00 { return 0 }
47 | if i >= numberOfDigits { return nil }
48 |
49 | let decimalsRemoved = Int(amount * 100)
50 | let divisionFactor = Int(pow(Double(10), Double(numberOfDigits - i - 1)))
51 | let movedToOne = decimalsRemoved / divisionFactor
52 | return movedToOne % 10
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Sources/MOTickerLabel/MOSingleCounterLabel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MOSingleCounterLabel.swift
3 | // MOTickerLabel
4 | //
5 | // Created by Mosaic Engineering on 2/29/20.
6 | // Copyright © 2020 Mosaic. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Foundation
11 |
12 | public class MOSingleCounterLabel: UIView {
13 | lazy var runningAnimator: UIViewPropertyAnimator = {
14 | let animator = UIViewPropertyAnimator(duration: 0.5, dampingRatio: 1.0, animations: nil)
15 | animator.scrubsLinearly = false
16 | return animator
17 | }()
18 |
19 | public var textColor: UIColor = .label {
20 | didSet {
21 | numberWheel.arrangedSubviews.forEach {
22 | ($0 as? UILabel)?.textColor = textColor
23 | }
24 | }
25 | }
26 |
27 | public var font: UIFont = .boldSystemFont(ofSize: 14) {
28 | didSet {
29 | numberWheel.arrangedSubviews.compactMap { $0 as? UILabel }.forEach {
30 | $0.font = font
31 | $0.setFontSizeToFill()
32 | $0.sizeToFit()
33 | }
34 |
35 | scrollView.setNeedsLayout()
36 | scrollView.layoutIfNeeded()
37 | setNeedsLayout()
38 | layoutIfNeeded()
39 |
40 | if let val = value {
41 | scrollView.setContentOffset(offset(of: val), animated: true)
42 | }
43 | }
44 | }
45 |
46 | public var value: Int? {
47 | didSet {
48 | guard let newValue = value else {
49 | return
50 | }
51 |
52 | if let oldValue = oldValue, oldValue != newValue {
53 | animateChange(old: oldValue, new: newValue)
54 | }
55 | }
56 | }
57 |
58 | lazy var numberLabels: [UILabel] = {
59 | return (0...9).map {
60 | let label = UILabel(frame: self.frame)
61 | label.translatesAutoresizingMaskIntoConstraints = false
62 | label.textAlignment = .center
63 | label.text = "\($0)"
64 | label.font = font
65 | label.textColor = textColor
66 | label.setFontSizeToFill()
67 | label.sizeToFit()
68 | return label
69 | }
70 | }()
71 |
72 | lazy var numberWheel: UIStackView = {
73 | let stackView = UIStackView(frame: self.frame)
74 | stackView.axis = .vertical
75 | stackView.spacing = 0
76 | stackView.alignment = .center
77 | stackView.distribution = .fillEqually
78 | stackView.translatesAutoresizingMaskIntoConstraints = false
79 | numberLabels.forEach {
80 | stackView.addArrangedSubview($0)
81 | }
82 | stackView.addArrangedSubview(UIView())
83 | return stackView
84 | }()
85 |
86 | lazy var scrollView: UIScrollView = {
87 | let scrollView = UIScrollView(frame: self.frame)
88 | scrollView.translatesAutoresizingMaskIntoConstraints = false
89 | scrollView.addSubview(numberWheel)
90 | scrollView.showsHorizontalScrollIndicator = false
91 | scrollView.showsVerticalScrollIndicator = false
92 | scrollView.isUserInteractionEnabled = false
93 | scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 8, right: 0.0)
94 | numberWheel.pin(to: scrollView)
95 | numberWheel.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
96 | return scrollView
97 | }()
98 |
99 | init(frame: CGRect, value: Int, font: UIFont? = nil) {
100 | super.init(frame: frame)
101 | self.value = value
102 |
103 | if let customFont = font {
104 | self.font = customFont
105 | }
106 |
107 | addSubview(scrollView)
108 | scrollView.pin(to: self)
109 | scrollView.setNeedsLayout()
110 | scrollView.layoutIfNeeded()
111 | scrollView.setContentOffset(offset(of: value), animated: false)
112 | }
113 |
114 | required init?(coder: NSCoder) {
115 | fatalError("init(coder:) has not been implemented")
116 | }
117 |
118 | func offset(of number: Int) -> CGPoint {
119 | let label = numberLabels[number]
120 | return CGPoint(x: 0, y: label.frame.minY)
121 | }
122 |
123 | func animateChange(old: Int, new: Int) {
124 | let destinationOffset = offset(of: new)
125 |
126 | if runningAnimator.isRunning {
127 | runningAnimator.stopAnimation(true)
128 | scrollView.contentOffset = self.scrollView.contentOffset
129 | }
130 |
131 | runningAnimator.addAnimations {
132 | self.scrollView.setContentOffset(destinationOffset, animated: true)
133 | }
134 | runningAnimator.startAnimation()
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/Sources/MOTickerLabel/MOTickerLabel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MOTickerLabel.swift
3 | // MOTickerLabel
4 | //
5 | // Created by Mosaic Engineering on 2/29/20.
6 | // Copyright © 2020 Mosaic. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public class MOTickerLabel: UIView {
12 |
13 | lazy var runningAnimator: UIViewPropertyAnimator = {
14 | let animator = UIViewPropertyAnimator(duration: 0.5, dampingRatio: 1.0, animations: nil)
15 | animator.scrubsLinearly = false
16 | return animator
17 | }()
18 |
19 | lazy var arrayModificationAnimator: UIViewPropertyAnimator = {
20 | let animator = UIViewPropertyAnimator(duration: 0.5, dampingRatio: 1.0, animations: nil)
21 | animator.scrubsLinearly = false
22 | return animator
23 | }()
24 |
25 | lazy var stackView: UIStackView = {
26 | let stackView = UIStackView(frame: self.frame)
27 | stackView.translatesAutoresizingMaskIntoConstraints = false
28 | stackView.axis = .horizontal
29 | stackView.spacing = -2
30 | stackView.alignment = .fill
31 | return stackView
32 | }()
33 |
34 | lazy var approximateFrame: CGRect = {
35 | CGRect(x: 0, y: 0, width: frame.height, height: frame.height)
36 | }()
37 |
38 | public var textColor: UIColor = .label {
39 | didSet {
40 | stackView.arrangedSubviews.forEach {
41 | if let label = $0 as? MOSingleCounterLabel {
42 | label.textColor = textColor
43 | } else {
44 | ($0 as? UILabel)?.textColor = textColor
45 | }
46 | }
47 | }
48 | }
49 |
50 | public var font: UIFont = .boldSystemFont(ofSize: 14) {
51 | didSet {
52 | stackView.arrangedSubviews.forEach {
53 | if let label = $0 as? MOSingleCounterLabel {
54 | label.font = font
55 | } else if let staticLabel = $0 as? UILabel {
56 | staticLabel.font = font
57 | }
58 | }
59 | }
60 | }
61 |
62 | public var value: Dollar {
63 | didSet {
64 | animateDigitInsertion(from: oldValue, to: value)
65 | animate(from: oldValue, to: value)
66 | }
67 | }
68 |
69 | public init(frame: CGRect, value: Dollar) {
70 | self.value = value
71 | super.init(frame: frame)
72 | commonInit()
73 | }
74 |
75 | required init?(coder: NSCoder) {
76 | self.value = Dollar(float: 0.0)
77 | super.init(coder: coder)
78 | commonInit()
79 | }
80 |
81 | func commonInit() {
82 | backgroundColor = .clear
83 |
84 | addSubview(stackView)
85 | stackView.pin(to: self)
86 | setupInitialLabels()
87 | }
88 |
89 | func animate(from old: Dollar, to new: Dollar) {
90 | if runningAnimator.isRunning {
91 | runningAnimator.stopAnimation(false)
92 | runningAnimator.finishAnimation(at: .end)
93 | }
94 |
95 | runningAnimator.addAnimations {
96 | (0.. 0:
118 | (0.. MOSingleCounterLabel in
121 | let label = MOSingleCounterLabel(frame: self.approximateFrame, value: value, font: self.font)
122 | label.textColor = textColor
123 | return label
124 | }
125 | .forEach { label in
126 | self.stackView.insertArrangedSubview(label, at: 1)
127 | }
128 | default:
129 | break
130 | }
131 |
132 | stride(from: stackView.arrangedSubviews.count - 4, through: 0, by: -4).dropFirst().forEach { i in
133 | let view = stackView.arrangedSubviews[i]
134 | if view as? UILabel == nil {
135 | stackView.insertArrangedSubview(staticLabel(with: ","), at: i + 1)
136 | }
137 | }
138 |
139 | stackView.layoutIfNeeded()
140 | }
141 |
142 | func label(at idx: Int) -> MOSingleCounterLabel {
143 | stackView.arrangedSubviews.compactMap { $0 as? MOSingleCounterLabel }[idx]
144 | }
145 |
146 | func setupInitialLabels() {
147 | let numberLabels = (0.. (MOSingleCounterLabel) in
150 | let label = MOSingleCounterLabel(frame: self.approximateFrame, value: value, font: self.font)
151 | label.font = self.font
152 | label.textColor = textColor
153 | return label
154 | }
155 |
156 | numberLabels.enumerated().forEach { (offset, label) in
157 | if offset % 3 == 1 && offset > 3 {
158 | stackView.addArrangedSubview(staticLabel(with: ","))
159 | }
160 | stackView.addArrangedSubview(label)
161 | }
162 |
163 | insertNotations()
164 | stackView.addArrangedSubview(UIView())
165 | }
166 |
167 | func insertNotations() {
168 | let dollarSign = staticLabel(with: "$")
169 | let period = staticLabel(with: ".")
170 | stackView.insertArrangedSubview(dollarSign, at: 0)
171 | stackView.insertArrangedSubview(period, at: stackView.arrangedSubviews.count - 2)
172 | }
173 |
174 | func staticLabel(with string: String) -> UILabel {
175 | let label = UILabel(frame: approximateFrame)
176 | label.translatesAutoresizingMaskIntoConstraints = false
177 | label.text = string
178 | label.font = font
179 | label.textAlignment = .center
180 | label.setFontSizeToFill()
181 | label.sizeToFit()
182 | label.textColor = textColor
183 | return label
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/Sources/MOTickerLabel/UIImage+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImageView+Extensions.swift
3 | // MOTickerLabel
4 | //
5 | // Created by Mosaic Engineering on 2/29/20.
6 | // Copyright © 2020 Mosaic. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIImage {
12 | static func imageWithLabel(label: UILabel) -> UIImage {
13 | UIGraphicsBeginImageContextWithOptions(label.bounds.size, false, 0.0)
14 | label.layer.render(in: UIGraphicsGetCurrentContext()!)
15 | let img = UIGraphicsGetImageFromCurrentImageContext()
16 | UIGraphicsEndImageContext()
17 | return img!.withRenderingMode(.alwaysTemplate)
18 | }
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/Sources/MOTickerLabel/UILabel+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UILabel+Extensions.swift
3 | // MOTickerLabel
4 | //
5 | // Created by Mike Choi on 2/29/20.
6 | // Copyright © 2020 Mosaic. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UILabel {
12 | func setFontSizeToFill() {
13 | let frameSize = self.bounds.size
14 | guard frameSize.height>0 && frameSize.width>0 && self.text != nil else {return}
15 |
16 | var fontPoints = self.font.pointSize
17 | var fontSize = self.text!.size(withAttributes: [NSAttributedString.Key.font: self.font.withSize(fontPoints)])
18 | var increment = CGFloat(0)
19 |
20 | if fontSize.width > frameSize.width || fontSize.height > frameSize.height {
21 | increment = -1
22 | } else {
23 | increment = 1
24 | }
25 |
26 | while true {
27 | fontSize = self.text!.size(withAttributes: [NSAttributedString.Key.font: self.font.withSize(fontPoints+increment)])
28 | if increment < 0 {
29 | if fontSize.width < frameSize.width && fontSize.height < frameSize.height {
30 | fontPoints += increment
31 | break
32 | }
33 | } else {
34 | if fontSize.width > frameSize.width || fontSize.height > frameSize.height {
35 | break
36 | }
37 | }
38 | fontPoints += increment
39 | }
40 |
41 | self.font = self.font.withSize(fontPoints)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Sources/MOTickerLabel/UIView+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Extensions.swift
3 | // MOTickerLabel
4 | //
5 | // Created by Mosaic Engineering on 2/29/20.
6 | // Copyright © 2020 Mosaic. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIView {
12 | func pin(to view: UIView) {
13 | NSLayoutConstraint.activate([
14 | leadingAnchor.constraint(equalTo: view.leadingAnchor),
15 | trailingAnchor.constraint(equalTo: view.trailingAnchor),
16 | topAnchor.constraint(equalTo: view.topAnchor),
17 | bottomAnchor.constraint(equalTo: view.bottomAnchor)
18 | ])
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import MOTickerLabelTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += MOTickerLabelTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/Tests/MOTickerLabelTests/MOTickerLabelTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import MOTickerLabel
3 |
4 | final class MOTickerLabelTests: XCTestCase {
5 | func testExample() {
6 | // This is an example of a functional test case.
7 | // Use XCTAssert and related functions to verify your tests produce the correct
8 | // results.
9 | }
10 |
11 | static var allTests = [
12 | ("testExample", testExample),
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/Tests/MOTickerLabelTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(MOTickerLabelTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------