Skip to content

Commit

Permalink
Update Image sizing to respect aspect ratio if possible
Browse files Browse the repository at this point in the history
  • Loading branch information
lkzhao committed Dec 18, 2024
1 parent e9ff46d commit 38235a0
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Sources/UIComponent/Components/View/Image.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public struct Image: Component {
/// - Parameter constraint: The constraints to use for sizing the image.
/// - Returns: An `ImageRenderNode` that represents the laid out image.
public func layout(_ constraint: Constraint) -> ImageRenderNode {
ImageRenderNode(image: image, size: image.size.bound(to: constraint))
ImageRenderNode(image: image, size: image.size.boundWithAspectRatio(to: constraint))
}
}

Expand Down
32 changes: 32 additions & 0 deletions Sources/UIComponent/Core/Model/Constraint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,36 @@ extension CGSize {
height: height.clamp(constraint.minSize.height, constraint.maxSize.height)
)
}

/// Constrains the current size to the limits defined by the given `Constraint`, while trying to maintain the aspect ratio.
/// - Parameter constraint: The `Constraint` to which the current size should be bound.
/// - Returns: A new `CGSize` instance that is within the bounds of the given `Constraint`, while trying maintaining the aspect ratio.
public func boundWithAspectRatio(to constraint: Constraint) -> CGSize {
let clampedSize = bound(to: constraint)
if clampedSize.width != width {
let scale = clampedSize.width / width
let preferredHeight = height * scale
let clampedHeight = preferredHeight.clamp(constraint.minSize.height, constraint.maxSize.height)
if clampedHeight != preferredHeight {
let scale = clampedHeight / height
let preferredWidth = width * scale
let clampedWidth = preferredWidth.clamp(constraint.minSize.width, constraint.maxSize.width)
return CGSize(width: clampedWidth, height: clampedHeight)
}
return CGSize(width: clampedSize.width, height: clampedHeight)
} else if clampedSize.height != height {
let scale = clampedSize.height / height
let preferredWidth = width * scale
let clampedWidth = preferredWidth.clamp(constraint.minSize.width, constraint.maxSize.width)
if clampedWidth != preferredWidth {
let scale = clampedWidth / width
let preferredHeight = height * scale
let clampedHeight = preferredHeight.clamp(constraint.minSize.height, constraint.maxSize.height)
return CGSize(width: clampedWidth, height: clampedHeight)
}
return CGSize(width: clampedWidth, height: clampedSize.height)
} else {
return clampedSize
}
}
}
2 changes: 1 addition & 1 deletion Tests/UIComponentTests/DataCachingTest.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// File.swift
// DataCachingTest.swift
// UIComponent
//
// Created by Luke Zhao on 11/8/24.
Expand Down
37 changes: 37 additions & 0 deletions Tests/UIComponentTests/SizingTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// SizingTest.swift
// UIComponent
//
// Created by Luke Zhao on 11/8/24.
//

import Testing
@testable import UIComponent
import UIKit

public struct AspectSpace: Component {
let size: CGSize

public init(width: CGFloat = 0, height: CGFloat = 0) {
size = CGSize(width: width, height: height)
}

public func layout(_ constraint: Constraint) -> some RenderNode {
SpaceRenderNode(size: size.boundWithAspectRatio(to: constraint))
}
}

@Suite("Sizing")
@MainActor
struct SizingTest {

@Test func testAspectRatioSizing() throws {
let base = AspectSpace(width: 100, height: 100)
#expect(base.layout(Constraint(maxSize: CGSize(width: 200.0, height: .infinity))).size == CGSize(width: 100, height: 100))
#expect(base.layout(Constraint(maxSize: CGSize(width: 50.0, height: .infinity))).size == CGSize(width: 50, height: 50))
#expect(base.layout(Constraint(maxSize: CGSize(width: 50.0, height: 40.0))).size == CGSize(width: 40, height: 40))

#expect(base.size(width: .fill).layout(Constraint(maxSize: CGSize(width: 200.0, height: .infinity))).size == CGSize(width: 200, height: 200))
#expect(base.size(width: .fill).layout(Constraint(maxSize: CGSize(width: 50.0, height: .infinity))).size == CGSize(width: 50, height: 50))
}
}

0 comments on commit 38235a0

Please sign in to comment.