Skip to content

Commit 08a0d4d

Browse files
SahilSainiYMLMark Pospesel
andauthored
[CM-1267] String size calculation (#79)
* [ISSUE] added method to size String using typography * [ISSUE] updated according to TV OS * Modify size(withFont:) and expand unit tests * Fix failing tvOS tests --------- Co-authored-by: Mark Pospesel <[email protected]>
1 parent b18dab2 commit 08a0d4d

File tree

2 files changed

+198
-0
lines changed

2 files changed

+198
-0
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//
2+
// String+textSize.swift
3+
// YMatterType
4+
//
5+
// Created by Sahil Saini on 27/03/23.
6+
// Copyright © 2022 Y Media Labs. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
extension String {
12+
/// Calculates the size of the string rendered with the specified typography.
13+
/// - Parameters:
14+
/// - typography: the typography to be used to calculate the string size
15+
/// - traitCollection: the trait collection to apply
16+
/// - Returns: the size of the string
17+
public func size(
18+
withTypography typography: Typography,
19+
compatibleWith traitCollection: UITraitCollection?
20+
) -> CGSize {
21+
let layout = typography.generateLayout(compatibleWith: traitCollection)
22+
let valueSize = self.size(withFont: layout.font)
23+
return CGSize(
24+
width: valueSize.width,
25+
height: max(valueSize.height, layout.lineHeight)
26+
)
27+
}
28+
29+
/// Calculates the size of the string rendered with the specified font.
30+
///
31+
/// The returned size will be rounded up to the nearest pixel in both width and height.
32+
/// - Parameters:
33+
/// - font: the font to be used to calculate the string size
34+
/// - traitCollection: the trait collection to apply (used for `displayScale`)
35+
/// - Returns: the size of the string
36+
public func size(
37+
withFont font: UIFont,
38+
compatibleWith traitCollection: UITraitCollection? = nil
39+
) -> CGSize {
40+
let scale: CGFloat
41+
if let displayScale = traitCollection?.displayScale,
42+
displayScale != 0.0 {
43+
scale = displayScale
44+
} else {
45+
scale = UIScreen.main.scale
46+
}
47+
48+
let fontAttributes = [NSAttributedString.Key.font: font]
49+
let size = size(withAttributes: fontAttributes)
50+
return CGSize(
51+
width: ceil(size.width * scale) / scale,
52+
height: ceil(size.height * scale) / scale
53+
)
54+
}
55+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
//
2+
// String+textSizeTests.swift
3+
// YMatterTypeTests
4+
//
5+
// Created by Sahil Saini on 27/03/23.
6+
//
7+
8+
import XCTest
9+
@testable import YMatterType
10+
11+
final class StringTextSizeTests: XCTestCase {
12+
func test_sizeWithFont_deliversRoundedValues() {
13+
// Given
14+
let scale = getScale()
15+
let pointSize = CGFloat(Int.random(in: 10...24))
16+
let traits = UITraitCollection(displayScale: scale)
17+
let font = UIFont.systemFont(ofSize: pointSize, weight: .regular)
18+
let sut = "Hello"
19+
20+
// When
21+
let size = sut.size(withFont: font, compatibleWith: traits)
22+
23+
// Then
24+
XCTAssertGreaterThan(size.width, 0)
25+
XCTAssertGreaterThan(size.height, 0)
26+
XCTAssertEqual(size.width, ceil(size.width * scale) / scale)
27+
XCTAssertEqual(size.height, ceil(size.height * scale) / scale)
28+
}
29+
30+
func test_sizeWithTypography_deliversRoundedValues() throws {
31+
// Given
32+
let scale = getScale()
33+
let traits = UITraitCollection(displayScale: scale)
34+
let typography = try XCTUnwrap(getTypographies().randomElement())
35+
let sut = "Hello"
36+
37+
// When
38+
let size = sut.size(withTypography: typography, compatibleWith: traits)
39+
40+
// Then
41+
XCTAssertGreaterThan(size.width, 0)
42+
XCTAssertGreaterThan(size.height, 0)
43+
XCTAssertEqual(size.width, ceil(size.width * scale) / scale)
44+
XCTAssertEqual(size.height, ceil(size.height * scale) / scale)
45+
}
46+
47+
func test_sizeWithTypography_deliversLineHeight() {
48+
// Given
49+
let typography = Typography(
50+
fontFamily: Typography.systemFamily,
51+
fontWeight: .bold,
52+
fontSize: 24,
53+
lineHeight: 32,
54+
textStyle: .title1
55+
)
56+
let sut = "testString"
57+
58+
// When
59+
let size = sut.size(withTypography: typography, compatibleWith: .default)
60+
61+
// Then
62+
XCTAssertGreaterThan(size.height, 0)
63+
XCTAssertEqual(size.height, typography.lineHeight)
64+
}
65+
66+
#if !os(tvOS)
67+
func test_sizeWithTypography_deliversScaledSize() throws {
68+
// Given
69+
let typography = try XCTUnwrap(getTypographies().randomElement())
70+
let sut = "testString"
71+
let traits = UITraitCollection(preferredContentSizeCategory: .accessibilityMedium)
72+
73+
// When
74+
let size = sut.size(withTypography: typography, compatibleWith: traits)
75+
76+
// Then
77+
XCTAssertGreaterThan(size.height, typography.lineHeight)
78+
}
79+
#endif
80+
81+
func test_longerStrings_deliverGreaterWidths() {
82+
// Given
83+
let typography = Typography(
84+
fontFamily: Typography.systemFamily,
85+
fontWeight: .bold,
86+
fontSize: 24,
87+
lineHeight: 32,
88+
textStyle: .body
89+
)
90+
let sut1 = "testString1"
91+
let sut2 = "testString"
92+
93+
// When
94+
let sutSize1 = sut1.size(withTypography: typography, compatibleWith: .default)
95+
let sutSize2 = sut2.size(withTypography: typography, compatibleWith: .default)
96+
97+
// Then
98+
XCTAssertGreaterThan(sutSize1.height, 0)
99+
XCTAssertGreaterThan(sutSize2.height, 0)
100+
XCTAssertGreaterThan(sutSize1.width, sutSize2.width)
101+
XCTAssertEqual(sutSize1.height, sutSize2.height)
102+
}
103+
104+
func test_emptyString_deliversZeroWidth() {
105+
// Given
106+
let typography = Typography(
107+
fontFamily: Typography.systemFamily,
108+
fontWeight: .bold,
109+
fontSize: 24,
110+
lineHeight: 32,
111+
textStyle: .caption1
112+
)
113+
let sut = ""
114+
115+
// When
116+
let sutSize = sut.size(withTypography: typography, compatibleWith: nil)
117+
118+
// Then
119+
XCTAssertGreaterThan(sutSize.height, 0)
120+
XCTAssertEqual(sutSize.width, 0)
121+
}
122+
}
123+
124+
extension StringTextSizeTests {
125+
func getScale() -> CGFloat {
126+
let scale: CGFloat
127+
#if os(tvOS)
128+
scale = UIScreen.main.scale
129+
#else
130+
scale = CGFloat(Int.random(in: 1...3))
131+
#endif
132+
return scale
133+
}
134+
135+
func getTypographies() -> [Typography] {
136+
var typographies: [Typography] = [.systemButton, .systemLabel]
137+
#if !os(tvOS)
138+
typographies.append(.system)
139+
typographies.append(.smallSystem)
140+
#endif
141+
return typographies
142+
}
143+
}

0 commit comments

Comments
 (0)