-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAdaptizer.swift
416 lines (315 loc) · 15 KB
/
Adaptizer.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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
152
153
154
155
156
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
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
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
//
// Adaptizer
//
// Created by Anton on 15/07/2017.
// Copyright © 2017 Anton Lovchikov. All rights reserved.
//
import Foundation
import UIKit
// MARK: USAGE
//
// # Scaling through a list of explicit break points
// 1. Form a dictionary where
// key is in screenIdentifier format (see bellow)
// value is in any fomat. Only that all values should be in one format
// values should store EXACT what you want to get on different screens sizes
// values can be Doubles for constraint counstants for exmple of font size
// values can be Strings for adaptive lable text
// values can be Bools for hiding some elements or whatever
// 2. Call .scaled in your dictionary to get a value for current screen size
// # Scaling through a list of multipliers applied to an original value
// 1. Form a dictionary likewise described previously ↑
// only values should store MULTIPLIERS for different screen sizes
// 2. Call scaled-function simply by adding .scaled to a Double / Float /CGFloat / Int value.
// MARK: LIST OF BREAK POINTS
// Screen identifiers used for the list of break points.
// w320 > wC iPhone iPad
// w375 > wC iPhone iPad
// w414 > wC iPhone
// w438 > wC iPad
// w504 > wC iPad
// w507 > wC iPad
// w551 > wC iPad
// w568 > wC iPhone
// w639 > wC iPad
// w667 > wC iPhone
// -------------------------
// w678 > wR iPad
// w694 > wR iPad
// w736 > wR iPhone
// w768 > wR iPad
// w782 > wR iPad
// w834 > wR iPad
// w981 > wR iPad
// w1024 > wR iPad
// w1112 > wR iPad
// w1366 > wR iPad
// You can use only those you care about.
// F.x [w375 : 100, w1366.default : 200, wR.pad : 300].
// For screen sizes you haven't set value .default value will be used.
// If you haven't set .default value, well the first dictionary value will be used.
//
// Conflicts:
// Are solved towards more specified identifier.
// F.x [w320 : 100, wC.iPhone : 120, wC.all : 140] will be soleveed this way:
// On screen width of 320, value of 100 will be used as the most specified.
// On screen width of 375 for iPhone, value 120 will be used as more specified than wC.all.
// On screen width of 414 for all devices, value of 140 will be used.
//
// iPhones
let wAny = ScreenIdentifier("wAny") // Any screen, used as default
let w320 = ScreenIdentifier("w320") // iPhone 5, 5s, SE in Portrait
let w375 = ScreenIdentifier("w375") // iPhone 6, 7 in Portrait
let w414 = ScreenIdentifier("w414") // iPhone 6+, 7+ in Portrait
let w568 = ScreenIdentifier("w568") // iPhone 5, 5s, SE in Landscape
let w667 = ScreenIdentifier("w667") // iPhone 6, 7 in Landscape
let w736 = ScreenIdentifier("w736") // iPhone 6+, 7+ in Landscape
// iPads
// w320 // iPad mini, iPad in Port/Land split view 1/3
// w375 // iPad Pro 12.9" in Port/Land split view 1/3
let w438 = ScreenIdentifier("w438") // iPad mini, iPad in Portrait split view 2/3
let w504 = ScreenIdentifier("w504") // iPad Pro 10.5" in Portrait split view 2/3
let w507 = ScreenIdentifier("w507") // iPad mini, iPad in Landscape split view 1/2
let w551 = ScreenIdentifier("w551") // iPad Pro 10.5" in Landscape split view 1/2
let w639 = ScreenIdentifier("w639") // iPad Pro 12.9"in Portrait split view 2/3
let w678 = ScreenIdentifier("w678") // iPad Pro 12.9"in Landscape split view 1/2
let w694 = ScreenIdentifier("w694") // iPad mini, iPad in Landscape split view 2/3
let w768 = ScreenIdentifier("w768") // iPad mini, iPad in Portrait full screen
let w782 = ScreenIdentifier("w782") // iPad Pro 10.5" in Landscape split view 3/4
let w834 = ScreenIdentifier("w834") // iPad Pro 10.5" in Portrait full screen
let w981 = ScreenIdentifier("w981") // iPad Pro 12.9" in Landscape split view 3/4
let w1024 = ScreenIdentifier("w1024") // iPad Pro 12.9"in Portrait or iPad, iPad mini in Landscape full screen
let w1112 = ScreenIdentifier("w1112") // iPad Pro 10.5" in Landscape full screen
let w1366 = ScreenIdentifier("w1366") // iPad Pro 12.9"in Landscape full screen
// Compact size class
struct wC {
static var all : ScreenIdentifier { get { return ScreenIdentifier("wC_all") }} // iPhones and iPads
static var phone : ScreenIdentifier { get { return ScreenIdentifier("wC_phone") }} // iPhones only
static var pad : ScreenIdentifier { get { return ScreenIdentifier("wC_pad") }} // iPads only
}
// Regular size class
struct wR {
static var all : ScreenIdentifier { get { return ScreenIdentifier("wR_all") }} // iPhones and iPad
static var phone : ScreenIdentifier { get { return ScreenIdentifier("wR_phone") }} // iPhones only
static var pad : ScreenIdentifier { get { return ScreenIdentifier("wR_pad") }} // iPads only
}
// Container with screen identifier
struct ScreenIdentifier : Hashable {
// Call .default to set this identifier as default value
// It will be used in cases when there is no explicit identifier set
var `default` : ScreenIdentifier {
get {
var updSelf = self
updSelf.isDefault = true
return updSelf }
}
// Technical stuff
//
var hashValue: Int {
get { return stringValue.hash }
}
static func == (lhs: ScreenIdentifier, rhs: ScreenIdentifier) -> Bool {
return
lhs.hashValue == rhs.hashValue
}
private(set) var stringValue : String
fileprivate init(_ value: String) {
self.stringValue = value
}
fileprivate var isDefault : Bool = false
}
// Call .scaled to get one value out of the dictionary formed
extension Dictionary where Key == ScreenIdentifier {
var scaled : Dictionary.Value {
get {
let value : Value = Adaptizer.valueForScreenWidth(self)
return value
}
}
}
// MARK: LIST OF MULTIPLIERS
// Call .scaleRule to save the list of multipliers as rule for scaling
// round parameter determines if the result value should be rounded to the whole value
extension Dictionary where Key == ScreenIdentifier {
func scalingRule(rounding: Bool = true) {
if let dictionary = self as? [ScreenIdentifier : Double] {
if dictionary.values.count > 0 {
Adaptizer.setScalingRule(dictionary, withRounding:rounding)
} else {
print("Adaptizer can't set the scale rule: dictionary shouldn't be empty. Original value is used for all screens")
}
} else {
print("Adaptizer can't set the scale rule. Expected dictionary format is [String : Double]. Provided dictionary format is [\(Dictionary.Key.self) : \(Dictionary.Value.self)]. Original value will be used for all screens")
}
}
}
// Calling scaled metrics.
// You can use Double, Float, CGFloat and Int.
// 20.scaled / 20.0.scaled / CGFloat(20).scaled
// Adaptizer returns the result value in the original value type
// Call .cgFloat right after .scaled for more convenience conversion
//
extension Double {
var scaled : Double {
get { return Adaptizer.scale(Double(self)) }
}
var cgFloat : CGFloat {
return CGFloat(self)
}
}
extension Float {
var scaled : Float {
get { return Float(Adaptizer.scale(Double(self))) }
}
var cgFloat : CGFloat {
return CGFloat(self)
}
}
extension CGFloat {
var scaled : CGFloat {
get { return CGFloat(Adaptizer.scale(Double(self))) }
}
}
extension Int {
var scaled : Int {
get { return Int(Adaptizer.scale(Double(self))) }
}
var cgFloat : CGFloat {
return CGFloat(self)
}
}
// MARK: PRIVATE
//
fileprivate class Adaptizer {
fileprivate static func setScalingRule(_ rule: Dictionary<ScreenIdentifier, Double>, withRounding rounding: Bool) {
self.scalingRule = rule
self.rounding = rounding
}
fileprivate static var scalingRule : [ScreenIdentifier : Double]?
fileprivate static var rounding : Bool = true
fileprivate static func scale(_ originalValue: Double) -> Double {
if let rule = scalingRule {
let multiplier = valueForScreenWidth(rule)
let semiresult = originalValue * multiplier
let result = rounding ? round(semiresult) : semiresult
return result
} else {
print("Adaptizer scale rule hasn't been set. Original value is used for all screens")
return originalValue
}
}
// Function shooses exact value based on screen width
fileprivate static func valueForScreenWidth<T>(_ rule:[ScreenIdentifier : T]) -> T {
// Device parameters
let screenWidth = UIScreen.main.bounds.width
let sizeClass = UIScreen.main.traitCollection.horizontalSizeClass
let isPhone = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad ? false : true
// Find default value
let defaultValue : T = findDefaultValue(rule)
// Find instructions fit to the current device
let sizeClassValue : T? = sift(sizeClass:sizeClass, rule:rule)
let sizeClassDeviceValue : T? = sift(sizeClass:sizeClass, isPhone:isPhone, rule:rule)
let widthOrientationValue : T? = sift(screenWidth:screenWidth, rule:rule)
// Choose one of the instruction
if widthOrientationValue != nil {
return widthOrientationValue!
} else if sizeClassDeviceValue != nil {
return sizeClassDeviceValue!
} else if sizeClassValue != nil {
return sizeClassValue!
} else {
return defaultValue
}
}
// Finding the default value
fileprivate static func findDefaultValue<T>(_ rule:[ScreenIdentifier : T]) -> T {
var defaultValue : T = rule.first!.value
if rule[ScreenIdentifier("wAny")] != nil {
defaultValue = rule[ScreenIdentifier("wAny")]!
} else {
for (key,value) in rule {
if key == ScreenIdentifier("wAny") { defaultValue = value ; break }
else if key.isDefault { defaultValue = value ; break }
}
}
return defaultValue
}
// Searching if there is wC.all or wR.all in the rule provided
fileprivate static func sift<T>(sizeClass:UIUserInterfaceSizeClass, rule:[ScreenIdentifier : T]) -> T? {
var targetValue : T?
switch sizeClass {
case .compact:
if let value = rule[ScreenIdentifier("wC_all")] { targetValue = value }
case .regular:
if let value = rule[ScreenIdentifier("wR_all")] { targetValue = value }
default:
targetValue = nil
}
return targetValue
}
// Searching if there is wC.phone or wC.pad or wR.phone or wR.pad in the rule provided
fileprivate static func sift<T>(sizeClass:UIUserInterfaceSizeClass, isPhone:Bool, rule:[ScreenIdentifier : T]) -> T? {
var targetValue : T?
switch sizeClass {
case .compact:
if isPhone { if let value = rule[ScreenIdentifier("wC_phone")] { targetValue = value }}
else { if let value = rule[ScreenIdentifier("wC_pad")] { targetValue = value }}
case .regular:
if isPhone { if let value = rule[ScreenIdentifier("wR_phone")] { targetValue = value }}
else { if let value = rule[ScreenIdentifier("wR_pad")] { targetValue = value }}
default:
targetValue = nil
}
return targetValue
}
// Searching if there is w3_5, w4_0, w4_7, w5_0, w7_9, w9_7, w10_5 or w12_9 in the rule provided
fileprivate static func sift<T>(screenWidth:CGFloat, rule:[ScreenIdentifier : T]) -> T? {
var targetValue : T?
if screenWidth < 320, let value = rule[ScreenIdentifier("w320")] {
targetValue = value
} else if screenWidth >= 320 && screenWidth < 375, let value = rule[ScreenIdentifier("w320")] {
targetValue = value
} else if screenWidth >= 375 && screenWidth < 414, let value = rule[ScreenIdentifier("w375")] {
targetValue = value
} else if screenWidth >= 414 && screenWidth < 438, let value = rule[ScreenIdentifier("w414")] {
targetValue = value
} else if screenWidth >= 438 && screenWidth < 504, let value = rule[ScreenIdentifier("w438")] {
targetValue = value
} else if screenWidth >= 504 && screenWidth < 507, let value = rule[ScreenIdentifier("w504")] {
targetValue = value
} else if screenWidth >= 507 && screenWidth < 551, let value = rule[ScreenIdentifier("w507")] {
targetValue = value
} else if screenWidth >= 551 && screenWidth < 568, let value = rule[ScreenIdentifier("w551")] {
targetValue = value
} else if screenWidth >= 568 && screenWidth < 639, let value = rule[ScreenIdentifier("w568")] {
targetValue = value
} else if screenWidth >= 639 && screenWidth < 667, let value = rule[ScreenIdentifier("w639")] {
targetValue = value
} else if screenWidth >= 667 && screenWidth < 678, let value = rule[ScreenIdentifier("w667")] {
targetValue = value
} else if screenWidth >= 678 && screenWidth < 694, let value = rule[ScreenIdentifier("w678")] {
targetValue = value
} else if screenWidth >= 694 && screenWidth < 736, let value = rule[ScreenIdentifier("w694")] {
targetValue = value
} else if screenWidth >= 736 && screenWidth < 768, let value = rule[ScreenIdentifier("w736")] {
targetValue = value
} else if screenWidth >= 768 && screenWidth < 782, let value = rule[ScreenIdentifier("w768")] {
targetValue = value
} else if screenWidth >= 782 && screenWidth < 834, let value = rule[ScreenIdentifier("w782")] {
targetValue = value
} else if screenWidth >= 834 && screenWidth < 981, let value = rule[ScreenIdentifier("w834")] {
targetValue = value
} else if screenWidth >= 981 && screenWidth < 1024, let value = rule[ScreenIdentifier("w981")] {
targetValue = value
} else if screenWidth >= 1024 && screenWidth < 1112, let value = rule[ScreenIdentifier("w1024")] {
targetValue = value
} else if screenWidth >= 1112 && screenWidth < 1366, let value = rule[ScreenIdentifier("w1112")] {
targetValue = value
} else if screenWidth >= 1366, let value = rule[ScreenIdentifier("w1366")] {
targetValue = value
} else {
targetValue = nil
}
return targetValue
}
}