diff --git a/Sources/UserInterfaceVariations/UIVariationEnvironment.swift b/Sources/UserInterfaceVariations/UIVariationEnvironment.swift new file mode 100644 index 0000000..e247e38 --- /dev/null +++ b/Sources/UserInterfaceVariations/UIVariationEnvironment.swift @@ -0,0 +1,72 @@ +import UIKit + +public protocol UIVariationEnvironment { } + +extension UIVariationEnvironment where Self: UITraitEnvironment { + + public var variations: NSMutableArray { + get { + let key = Unmanaged.passUnretained(association).toOpaque() + if let array = objc_getAssociatedObject(self, key) as! NSMutableArray? { return array } + let array = NSMutableArray() + objc_setAssociatedObject(self, key, array, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + return array + } + } + + public func addVariation(_ variation: UIVariation) { + variation.traitEnvironment = self + variations.add(variation) + variation.applyIfMatchesTraitEnvironment() + } + + public func removeVariation(_ variation: UIVariation) { + variations.remove(variation) + variation.traitEnvironment = nil + } + + public func addVariation(for object: Object, + property: ReferenceWritableKeyPath, + sizeClassDimension: SizeClassDimension, + whenCompact: Value) { + switch sizeClassDimension { + case .horizontal: + addVariation(UIVariation(object: object, + keyPath: property, + value: object[keyPath: property], + horizontalSizeClass: .regular, + verticalSizeClass: nil)) + addVariation(UIVariation(object: object, + keyPath: property, + value: whenCompact, + horizontalSizeClass: .compact, + verticalSizeClass: nil)) + case .vertical: + addVariation(UIVariation(object: object, + keyPath: property, + value: object[keyPath: property], + horizontalSizeClass: nil, + verticalSizeClass: .regular)) + addVariation(UIVariation(object: object, + keyPath: property, + value: whenCompact, + horizontalSizeClass: nil, + verticalSizeClass: .compact)) + } + } + + /// Call this method when trait collection changes to keep variations reacting to them. + /// + /// override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + /// super.traitCollectionDidChange(previousTraitCollection) + /// activateVariationsMatchingTraitEnvironment() + /// } + /// + public func activateVariationsMatchingTraitEnvironment() { + for variation in variations as! [UIVariationApplying] { + variation.applyIfMatchesTraitEnvironment() + } + } +} + +private let association = NSObject() diff --git a/Tests/UserInterfaceVariationsTests/UIVariationEnvironmentTests.swift b/Tests/UserInterfaceVariationsTests/UIVariationEnvironmentTests.swift new file mode 100644 index 0000000..8e46084 --- /dev/null +++ b/Tests/UserInterfaceVariationsTests/UIVariationEnvironmentTests.swift @@ -0,0 +1,101 @@ +import XCTest +@testable import UserInterfaceVariations + +final class UIVariationEnvironmentTests: XCTestCase { + + let label = UILabel() + let valueWhenCompact = "value when compact" + let valueWhenRegular = "value when regular" + + override func setUp() { + label.text = nil + } + + func test_When_variation_matches_traitEnvironment_Then_it_is_applied() { + let variationEnvironment = UIVariationEnvironmentFake(traitCollection: UITraitCollection(horizontalSizeClass: .compact)) + let variation = UIVariation(object: label, + keyPath: \.text, + value: valueWhenCompact, + horizontalSizeClass: .compact, + verticalSizeClass: nil) + variationEnvironment.addVariation(variation) + XCTAssertEqual(label.text, variation.value) + } + + func test_adding_variation_convenience_method_for_sizeClassDimension_horizontal() { + label.text = valueWhenRegular + + let variationEnvironment = UIVariationEnvironmentFake(traitCollection: UITraitCollection(horizontalSizeClass: .regular)) + variationEnvironment.addVariation(for: label, + property: \.text, + sizeClassDimension: .horizontal, + whenCompact: valueWhenCompact) + + let variation0 = variationEnvironment.variations[0] as! UIVariation + XCTAssertTrue(variation0.object === label) + XCTAssertTrue(variation0.property == \.text) + XCTAssertTrue(variation0.value == valueWhenRegular) + XCTAssertTrue(variation0.horizontalSizeClass == .regular) + XCTAssertTrue(variation0.verticalSizeClass == nil) + + let variation1 = variationEnvironment.variations[1] as! UIVariation + XCTAssertTrue(variation1.object === label) + XCTAssertTrue(variation1.property == \.text) + XCTAssertTrue(variation1.value == valueWhenCompact) + XCTAssertTrue(variation1.horizontalSizeClass == .compact) + XCTAssertTrue(variation1.verticalSizeClass == nil) + } + + func test_adding_variation_convenience_method_for_sizeClassDimension_vartical() { + label.text = valueWhenRegular + + let variationEnvironment = UIVariationEnvironmentFake(traitCollection: UITraitCollection(horizontalSizeClass: .regular)) + variationEnvironment.addVariation(for: label, + property: \.text, + sizeClassDimension: .vertical, + whenCompact: valueWhenCompact) + + let variation0 = variationEnvironment.variations[0] as! UIVariation + XCTAssertTrue(variation0.object === label) + XCTAssertTrue(variation0.property == \.text) + XCTAssertTrue(variation0.value == valueWhenRegular) + XCTAssertTrue(variation0.horizontalSizeClass == nil) + XCTAssertTrue(variation0.verticalSizeClass == .regular) + + let variation1 = variationEnvironment.variations[1] as! UIVariation + XCTAssertTrue(variation1.object === label) + XCTAssertTrue(variation1.property == \.text) + XCTAssertTrue(variation1.value == valueWhenCompact) + XCTAssertTrue(variation1.horizontalSizeClass == nil) + XCTAssertTrue(variation1.verticalSizeClass == .compact) + } + + func test_When_verticalSizeClass_in_traitCollection_changes_Then_variation_is_applied() { + let variationEnvironment = UIVariationEnvironmentFake(traitCollection: UITraitCollection(verticalSizeClass: .regular)) + label.text = valueWhenRegular + variationEnvironment.addVariation(for: label, + property: \.text, + sizeClassDimension: .vertical, + whenCompact: valueWhenCompact) + variationEnvironment.traitCollection = UITraitCollection(verticalSizeClass: .compact) + XCTAssertEqual(label.text, valueWhenCompact) + } +} + +class UIVariationEnvironmentFake: NSObject, UITraitEnvironment { + + var traitCollection = UITraitCollection() { + didSet { traitCollectionDidChange(oldValue) } + } + + init(traitCollection: UITraitCollection) { + self.traitCollection = traitCollection + super.init() + } + + func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + activateVariationsMatchingTraitEnvironment() + } +} + +extension UIVariationEnvironmentFake: UIVariationEnvironment { }