From 008ae67343827551601dc3ec77a000d734d806a8 Mon Sep 17 00:00:00 2001 From: Wojciech Nagrodzki <278594+wnagrodzki@users.noreply.github.com> Date: Thu, 3 Jun 2021 11:28:53 +0200 Subject: [PATCH] Add UIVariation, UIVariationApplying and unit tests --- .../UserInterfaceVariations/UIVariation.swift | 90 ++++++++++++ .../UIVariationApplying.swift | 6 + .../UIVariationTests.swift | 136 ++++++++++++++++++ 3 files changed, 232 insertions(+) create mode 100644 Sources/UserInterfaceVariations/UIVariation.swift create mode 100644 Sources/UserInterfaceVariations/UIVariationApplying.swift create mode 100644 Tests/UserInterfaceVariationsTests/UIVariationTests.swift diff --git a/Sources/UserInterfaceVariations/UIVariation.swift b/Sources/UserInterfaceVariations/UIVariation.swift new file mode 100644 index 0000000..b5050d0 --- /dev/null +++ b/Sources/UserInterfaceVariations/UIVariation.swift @@ -0,0 +1,90 @@ +import UIKit + +public enum SizeClassDimension { + case horizontal, vertical +} + +public class UIVariation: NSObject { + + public weak var traitEnvironment: UITraitEnvironment? + + public let object: Object + public let property: ReferenceWritableKeyPath + public var value: Value { + didSet { applyIfMatchesTraitEnvironment() } + } + public let horizontalSizeClass: UIUserInterfaceSizeClass? + public let verticalSizeClass: UIUserInterfaceSizeClass? + + public static func make(for object: Object, + property: ReferenceWritableKeyPath, + sizeClassDimension: SizeClassDimension, + whenCompact: Value, + whenRegular: Value) -> [UIVariation] { + switch sizeClassDimension { + case .horizontal: + return [ + UIVariation(object: object, keyPath: property, value: whenCompact, horizontalSizeClass: .compact, verticalSizeClass: nil), + UIVariation(object: object, keyPath: property, value: whenRegular, horizontalSizeClass: .regular, verticalSizeClass: nil), + ] + case .vertical: + return [ + UIVariation(object: object, keyPath: property, value: whenCompact, horizontalSizeClass: nil, verticalSizeClass: .compact), + UIVariation(object: object, keyPath: property, value: whenRegular, horizontalSizeClass: nil, verticalSizeClass: .regular), + ] + } + } + + public init(object: Object, + keyPath: ReferenceWritableKeyPath, + value: Value, + horizontalSizeClass: UIUserInterfaceSizeClass? = nil, + verticalSizeClass: UIUserInterfaceSizeClass? = nil) { + self.object = object + self.property = keyPath + self.value = value + self.horizontalSizeClass = horizontalSizeClass + self.verticalSizeClass = verticalSizeClass + } + + public override var description: String { + """ + { + object: \(object) + keyPath: \(property._kvcKeyPathString ?? String(describing: property)) + value: \(value) + horizontalSizeClass: \(horizontalSizeClass?.name ?? "nil") + verticalSizeClass: \(verticalSizeClass?.name ?? "nil") + } + """ + } +} + +extension UIVariation: UIVariationApplying { + + func applyIfMatchesTraitEnvironment() { + guard let traitCollection = traitEnvironment?.traitCollection else { + print("Missing traitEnvironment when trying to apply \(self)") + return + } + if let horizontalSizeClass = horizontalSizeClass { + if traitCollection.horizontalSizeClass != horizontalSizeClass { return } + } + if let verticalSizeClass = verticalSizeClass { + if traitCollection.verticalSizeClass != verticalSizeClass { return } + } + object[keyPath: property] = value + } +} + +extension UIUserInterfaceSizeClass { + + fileprivate var name: String { + switch self { + case .unspecified: return "unspecified" + case .compact: return "compact" + case .regular: return "regular" + @unknown default: return "unknown" + } + } +} diff --git a/Sources/UserInterfaceVariations/UIVariationApplying.swift b/Sources/UserInterfaceVariations/UIVariationApplying.swift new file mode 100644 index 0000000..74ebb83 --- /dev/null +++ b/Sources/UserInterfaceVariations/UIVariationApplying.swift @@ -0,0 +1,6 @@ +import Foundation + +protocol UIVariationApplying: NSObject { + + func applyIfMatchesTraitEnvironment() +} diff --git a/Tests/UserInterfaceVariationsTests/UIVariationTests.swift b/Tests/UserInterfaceVariationsTests/UIVariationTests.swift new file mode 100644 index 0000000..c4bcd2d --- /dev/null +++ b/Tests/UserInterfaceVariationsTests/UIVariationTests.swift @@ -0,0 +1,136 @@ +import XCTest +@testable import UserInterfaceVariations + +final class UIVariationTests: XCTestCase { + + let label = UILabel() + let traitEnvironment = TraitEnvironmentStub() + let valueWhenCompact = "value when compact" + let valueWhenRegular = "value when regular" + + override func setUp() { + label.text = nil + traitEnvironment.traitCollection = UITraitCollection() + } + + func test_When_horizontalSizeClass_matches_traitEnvironment_Then_variation_is_applied() { + let sut = UIVariation(object: label, + keyPath: \.text, + value: valueWhenCompact, + horizontalSizeClass: .compact, + verticalSizeClass: nil) + traitEnvironment.traitCollection = UITraitCollection(horizontalSizeClass: .compact) + sut.traitEnvironment = traitEnvironment + sut.applyIfMatchesTraitEnvironment() + XCTAssertEqual(label.text, sut.value) + } + + func test_When_verticalSizeClass_matches_traitEnvironment_Then_variation_is_applied() { + let sut = UIVariation(object: label, + keyPath: \.text, + value: valueWhenCompact, + horizontalSizeClass: nil, + verticalSizeClass: .compact) + traitEnvironment.traitCollection = UITraitCollection(verticalSizeClass: .compact) + sut.traitEnvironment = traitEnvironment + sut.applyIfMatchesTraitEnvironment() + XCTAssertEqual(label.text, sut.value) + } + + func test_When_horizontalSizeClass_do_not_match_traitEnvironment_Then_variation_is_NOT_applied() { + let sut = UIVariation(object: label, + keyPath: \.text, + value: valueWhenCompact, + horizontalSizeClass: .compact, + verticalSizeClass: nil) + traitEnvironment.traitCollection = UITraitCollection(horizontalSizeClass: .regular) + sut.traitEnvironment = traitEnvironment + sut.applyIfMatchesTraitEnvironment() + XCTAssertNotEqual(label.text, sut.value) + } + + func test_When_verticalSizeClass_do_not_match_traitEnvironment_Then_variation_is_NOT_applied() { + let sut = UIVariation(object: label, + keyPath: \.text, + value: valueWhenCompact, + horizontalSizeClass: nil, + verticalSizeClass: .compact) + traitEnvironment.traitCollection = UITraitCollection(verticalSizeClass: .regular) + sut.traitEnvironment = traitEnvironment + sut.applyIfMatchesTraitEnvironment() + XCTAssertNotEqual(label.text, sut.value) + } + + func test_When_horizontalSizeClass_matches_traitEnvironment_and_value_is_modified_Then_variation_is_applied() { + let newValue = "value when compact after modification" + let sut = UIVariation(object: label, + keyPath: \.text, + value: "value when compact before modification", + horizontalSizeClass: .compact, + verticalSizeClass: nil) + traitEnvironment.traitCollection = UITraitCollection(horizontalSizeClass: .compact) + sut.traitEnvironment = traitEnvironment + sut.value = newValue + XCTAssertEqual(label.text, newValue) + } + + func test_When_traitEnvironment_is_nil_Then_variation_is_NOT_applied() { + let sut = UIVariation(object: label, + keyPath: \.text, + value: valueWhenCompact, + horizontalSizeClass: .compact, + verticalSizeClass: nil) + traitEnvironment.traitCollection = UITraitCollection(horizontalSizeClass: .compact) + sut.applyIfMatchesTraitEnvironment() + XCTAssertNotEqual(label.text, sut.value) + } + + func test_factory_method_for_sizeClassDimension_horizontal() { + let variations = UIVariation.make(for: label, + property: \.text, + sizeClassDimension: .horizontal, + whenCompact: valueWhenCompact, + whenRegular: valueWhenRegular) + + XCTAssertTrue(variations[0].object === label) + XCTAssertTrue(variations[0].property == \.text) + XCTAssertTrue(variations[0].value == valueWhenCompact) + XCTAssertTrue(variations[0].horizontalSizeClass == .compact) + XCTAssertTrue(variations[0].verticalSizeClass == nil) + + XCTAssertTrue(variations[1].object === label) + XCTAssertTrue(variations[1].property == \.text) + XCTAssertTrue(variations[1].value == valueWhenRegular) + XCTAssertTrue(variations[1].horizontalSizeClass == .regular) + XCTAssertTrue(variations[1].verticalSizeClass == nil) + } + + func test_factory_method_for_sizeClassDimension_vertical() { + let variations = UIVariation.make(for: label, + property: \.text, + sizeClassDimension: .vertical, + whenCompact: valueWhenCompact, + whenRegular: valueWhenRegular) + + XCTAssertTrue(variations[0].object === label) + XCTAssertTrue(variations[0].property == \.text) + XCTAssertTrue(variations[0].value == valueWhenCompact) + XCTAssertTrue(variations[0].horizontalSizeClass == nil) + XCTAssertTrue(variations[0].verticalSizeClass == .compact) + + XCTAssertTrue(variations[1].object === label) + XCTAssertTrue(variations[1].property == \.text) + XCTAssertTrue(variations[1].value == valueWhenRegular) + XCTAssertTrue(variations[1].horizontalSizeClass == nil) + XCTAssertTrue(variations[1].verticalSizeClass == .regular) + } +} + +class TraitEnvironmentStub: NSObject, UITraitEnvironment { + + var traitCollection = UITraitCollection() + + func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + + } +}