Skip to content

Commit

Permalink
Merge pull request square#467 from square/add-tintAdjustmentMode-to-i…
Browse files Browse the repository at this point in the history
…mage

Add `TintAdjustmentMode` to layout attributes
  • Loading branch information
jacksoncheek authored Sep 22, 2023
2 parents fc19f17 + 4afb1cf commit 0d12b53
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 0 deletions.
16 changes: 16 additions & 0 deletions BlueprintUI/Sources/Layout/LayoutAttributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public struct LayoutAttributes {
/// Corresponds to `UIView.isHidden`.
public var isHidden: Bool

/// Corresponds to `UIView.tintAdjustmentMode`.
public var tintAdjustmentMode: UIView.TintAdjustmentMode

public init() {
self.init(center: .zero, bounds: .zero)
}
Expand All @@ -51,6 +54,7 @@ public struct LayoutAttributes {
alpha = 1.0
isUserInteractionEnabled = true
isHidden = false
tintAdjustmentMode = .automatic

validateBounds()
validateCenter()
Expand All @@ -64,6 +68,7 @@ public struct LayoutAttributes {
alpha = attributes.alpha
isUserInteractionEnabled = attributes.isUserInteractionEnabled
isHidden = attributes.isHidden
tintAdjustmentMode = attributes.tintAdjustmentMode
}

public var frame: CGRect {
Expand All @@ -88,6 +93,7 @@ public struct LayoutAttributes {
view.alpha = alpha
view.isUserInteractionEnabled = isUserInteractionEnabled
view.isHidden = isHidden
view.tintAdjustmentMode = tintAdjustmentMode
}


Expand Down Expand Up @@ -156,6 +162,15 @@ public struct LayoutAttributes {
result.isUserInteractionEnabled = layoutAttributes.isUserInteractionEnabled && isUserInteractionEnabled
result.isHidden = layoutAttributes.isHidden || isHidden

switch tintAdjustmentMode {
case .dimmed, .normal:
result.tintAdjustmentMode = tintAdjustmentMode
case .automatic:
result.tintAdjustmentMode = layoutAttributes.tintAdjustmentMode
@unknown default:
result.tintAdjustmentMode = layoutAttributes.tintAdjustmentMode
}

return result
}

Expand Down Expand Up @@ -267,6 +282,7 @@ extension LayoutAttributes: Equatable {
&& lhs.alpha == rhs.alpha
&& lhs.isUserInteractionEnabled == rhs.isUserInteractionEnabled
&& lhs.isHidden == rhs.isHidden
&& lhs.tintAdjustmentMode == rhs.tintAdjustmentMode
}

}
Expand Down
4 changes: 4 additions & 0 deletions BlueprintUI/Sources/Layout/LayoutSubelement.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import CoreGraphics
import Foundation
import QuartzCore
import UIKit

/// A collection of proxy values that represent the child elements of a layout.
public typealias LayoutSubelements = [LayoutSubelement]
Expand Down Expand Up @@ -179,6 +180,9 @@ extension LayoutSubelement {

/// Corresponds to `UIView.isHidden`.
public var isHidden: Bool = false

/// Corresponds to `UIView.tintAdjustmentMode`.
public var tintAdjustmentMode: UIView.TintAdjustmentMode = .automatic
}

@propertyWrapper
Expand Down
65 changes: 65 additions & 0 deletions BlueprintUI/Sources/Layout/TintAdjustmentMode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import CoreGraphics
import UIKit

/// `TintAdjustmentMode` conditionally modifies the tint adjustment mode of its wrapped element.
///
/// - Note: When a tint adjustment mode is applied, any elements within the wrapped element will adopt the parent's tint adjustment mode.
public struct TintAdjustmentMode: Element {
public var tintAdjustmentMode: UIView.TintAdjustmentMode

public var wrappedElement: Element

public init(_ tintAdjustmentMode: UIView.TintAdjustmentMode, wrapping element: Element) {
self.tintAdjustmentMode = tintAdjustmentMode
wrappedElement = element
}

public var content: ElementContent {
ElementContent(child: wrappedElement, layout: Layout(tintAdjustmentMode: tintAdjustmentMode))
}

public func backingViewDescription(with context: ViewDescriptionContext) -> ViewDescription? {
nil
}

private struct Layout: SingleChildLayout {
var tintAdjustmentMode: UIView.TintAdjustmentMode

func measure(in constraint: SizeConstraint, child: Measurable) -> CGSize {
child.measure(in: constraint)
}

func layout(size: CGSize, child: Measurable) -> LayoutAttributes {
var attributes = LayoutAttributes(size: size)
attributes.tintAdjustmentMode = tintAdjustmentMode
return attributes
}

func sizeThatFits(
proposal: SizeConstraint,
subelement: Subelement,
environment: Environment,
cache: inout Cache
) -> CGSize {
subelement.sizeThatFits(proposal)
}

func placeSubelement(
in size: CGSize,
subelement: Subelement,
environment: Environment,
cache: inout ()
) {
subelement.attributes.tintAdjustmentMode = tintAdjustmentMode
}
}
}

extension Element {
/// Conditionally modifies the tint adjustment mode of its wrapped element.
///
/// - Note: When a tint adjustment mode is applied, any elements within the wrapped element will adopt the parent's tint adjustment mode.
public func tintAdjustmentMode(_ tintAdjustmentMode: UIView.TintAdjustmentMode) -> TintAdjustmentMode {
TintAdjustmentMode(tintAdjustmentMode, wrapping: self)
}
}
70 changes: 70 additions & 0 deletions BlueprintUI/Tests/LayoutAttributesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ final class LayoutAttributesTests: XCTestCase {
XCTAssertNotEqual(attributes, other)
}

do {
/// tintAdjustmentMode
var other = attributes
other.tintAdjustmentMode = .normal
XCTAssertNotEqual(attributes, other)
}

}

func testConcatAlpha() {
Expand Down Expand Up @@ -179,6 +186,60 @@ final class LayoutAttributesTests: XCTestCase {
XCTAssertTrue(combined.isHidden)
}
}

func test_concat_tintAdjustmentMode() {
do {
/// combined adopts child attribute if child is non-`.automatic`
var a = LayoutAttributes()
a.tintAdjustmentMode = .automatic

var b = LayoutAttributes()
b.tintAdjustmentMode = .normal

let combined = b.within(a)

XCTAssertEqual(combined.tintAdjustmentMode, .normal)
}

do {
/// combined adopts child attribute if both child and parent are non-`.automatic`
var a = LayoutAttributes()
a.tintAdjustmentMode = .dimmed

var b = LayoutAttributes()
b.tintAdjustmentMode = .normal

let combined = b.within(a)

XCTAssertEqual(combined.tintAdjustmentMode, .normal)
}

do {
/// combined inherits from parent if child is `.automatic`
var a = LayoutAttributes()
a.tintAdjustmentMode = .normal

var b = LayoutAttributes()
b.tintAdjustmentMode = .automatic

let combined = b.within(a)

XCTAssertEqual(combined.tintAdjustmentMode, .normal)
}

do {
/// combined is `.automatic` if both parent and child attributes are `.automatic`
var a = LayoutAttributes()
a.tintAdjustmentMode = .automatic

var b = LayoutAttributes()
b.tintAdjustmentMode = .automatic

let combined = b.within(a)

XCTAssertEqual(combined.tintAdjustmentMode, .automatic)
}
}
}

final class LayoutAttributesTests_CGRect: XCTestCase {
Expand Down Expand Up @@ -243,4 +304,13 @@ final class LayoutAttributesTests_Apply: XCTestCase {
attributes.apply(to: view)
XCTAssertTrue(view.isHidden)
}

func test_apply_tintAdjustmentMode() {
var attributes = LayoutAttributes()
attributes.tintAdjustmentMode = .normal

let view = UIView()
attributes.apply(to: view)
XCTAssertEqual(view.tintAdjustmentMode, .normal)
}
}
45 changes: 45 additions & 0 deletions BlueprintUI/Tests/TintAdjustmentModeTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import XCTest
@testable import BlueprintUI

final class TintAdjustmentModeTests: XCTestCase {
func test() throws {
do {
let wrapped = TintAdjustmentMode(.normal, wrapping: TestElement())
let layout = wrapped.layout(frame: CGRect(origin: .zero, size: .init(width: 10, height: 10)))
if let child = layout.findLayout(of: TestElement.self) {
XCTAssertEqual(
child.layoutAttributes.tintAdjustmentMode,
.normal
)
} else {
XCTFail("TestElement should be a child element")
}
}
}

func test_convenience() throws {
do {
let wrapped = TestElement().tintAdjustmentMode(.normal)
let layout = wrapped.layout(frame: CGRect(origin: .zero, size: .init(width: 10, height: 10)))
if let child = layout.findLayout(of: TestElement.self) {
XCTAssertEqual(
child.layoutAttributes.tintAdjustmentMode,
.normal
)
} else {
XCTFail("TestElement should be a child element")
}
}
}

/// A view-backed box to generate a native view node
struct TestElement: Element {
var content: ElementContent {
ElementContent(intrinsicSize: .init(width: 10, height: 10))
}

func backingViewDescription(with context: ViewDescriptionContext) -> ViewDescription? {
UIView.describe { _ in }
}
}
}
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Added a `TintAdjustmentMode` element and `.tintAdjustmentMode(:)` modifier for finer control of tint color during modal presentations.

### Removed

### Changed
Expand Down

0 comments on commit 0d12b53

Please sign in to comment.