Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Fixed:
- Don't measure the height of `RedwoodUIView` as zero when measured with `sizeThatFits()` or `intrinsicContentSize()`. We had been using `UIStackView` which doesn't support these functions.
- Correctly signal `RedwoodUIView` size changes of to callers who measure it with `intrinsicContentSize()`.
- The Redwood Gradle plugin is now compatible with Gradle 9.1.
- Don't incorrectly size items to 0 when using the `Flex()` modifier on a `Wrap` container.


## [0.18.0] - 2025-08-01
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,43 @@ abstract class AbstractFlexContainerTest<T : Any> {

snapshotter(row.value).snapshot()
}

/**
* We had a bug where putting `flex(1.0)` on a child of a column with `height(Constraint.Wrap)`
* would cause that child to get a height of 0.
*
* https://github.com/cashapp/redwood/issues/2753
*/
@Test
fun testFlexOnChildOfWrappingColumn() {
val root = row().apply {
width(Constraint.Fill)
height(Constraint.Fill)
}

val fixedSize = row().apply {
width(Constraint.Fill)
height(Constraint.Fill)
modifier = SizeImpl(width = 300.dp, height = 500.dp)
}.also {
root.children.insert(0, it)
}

val column = column().apply {
width(Constraint.Fill)
}.also {
fixedSize.children.insert(0, it)
}

widgetFactory.text("hello").apply {
modifier = FlexImpl(1.0)
bgColor(Blue)
}.also {
column.children.insert(0, it)
}

snapshotter(root.value).snapshot()
}
}

interface TestFlexContainer<T : Any> :
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import kotlinx.cinterop.CValue
import kotlinx.cinterop.useContents
import platform.CoreGraphics.CGSize
import platform.CoreGraphics.CGSizeMake
import platform.UIKit.UILayoutPriorityDefaultLow
import platform.UIKit.UILayoutPriorityRequired
import platform.UIKit.UIView
import platform.UIKit.UIViewNoIntrinsicMetric

Expand All @@ -44,15 +46,18 @@ internal object UIViewMeasureCallback : MeasureCallback {
else -> height.toDouble()
}

// The default implementation of sizeThatFits: returns the existing size of
// the view. That means that if we want to layout an empty UIView, which
// already has a frame set, its measured size should be CGSizeZero, but
// UIKit returns the existing size. See https://github.com/facebook/yoga/issues/606
// for more information.
// The default implementation of sizeThatFits: returns the existing size of the view. That means
// that if we want to layout an empty UIView, which already has a frame set, its measured size
// should be CGSizeZero, but UIKit returns the existing size. For more information, see
// https://github.com/facebook/yoga/issues/606
val sizeThatFits = if (view.isMemberOfClass(UIView.`class`()) && view.typedSubviews.isEmpty()) {
Size(0f, 0f)
} else {
view.sizeThatFits(CGSizeMake(constrainedWidth, constrainedHeight)).toSize()
view.systemLayoutSizeFittingSize(
targetSize = CGSizeMake(constrainedWidth, constrainedHeight),
withHorizontalFittingPriority = widthMode.toFittingPriority(),
verticalFittingPriority = heightMode.toFittingPriority(),
).toSize()
}

return Size(
Expand All @@ -65,3 +70,8 @@ internal object UIViewMeasureCallback : MeasureCallback {
private fun CValue<CGSize>.toSize() = useContents {
Size(width.toFloat(), height.toFloat())
}

private fun MeasureMode.toFittingPriority() = when (this) {
MeasureMode.Exactly -> UILayoutPriorityRequired
else -> UILayoutPriorityDefaultLow
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import platform.CoreGraphics.CGRectMake
import platform.CoreGraphics.CGRectZero
import platform.CoreGraphics.CGSize
import platform.CoreGraphics.CGSizeMake
import platform.UIKit.UILayoutPriority
import platform.UIKit.UILayoutPriorityRequired
import platform.UIKit.UIScrollView
import platform.UIKit.UIScrollViewContentInsetAdjustmentBehavior.UIScrollViewContentInsetAdjustmentNever
import platform.UIKit.UIScrollViewDelegateProtocol
Expand Down Expand Up @@ -94,19 +96,36 @@ internal class YogaUIView : UIScrollView(cValue { CGRectZero }), UIScrollViewDel
override fun intrinsicContentSize(): CValue<CGSize> {
return calculateLayout(
width = fillWidth.toYogaWithWidthConstraint(),
maxWidth = fillWidth.toYoga(),
height = fillHeight.toYogaWithWidthConstraint(),
maxHeight = fillHeight.toYoga(),
)
}

override fun sizeThatFits(size: CValue<CGSize>): CValue<CGSize> {
return size.useContents<CGSize, CValue<CGSize>> {
calculateLayout(
width = width.toYogaWithWidthConstraint(),
maxWidth = width.toYoga(),
height = height.toYogaWithHeightConstraint(),
maxHeight = height.toYoga(),
)
}
}

override fun systemLayoutSizeFittingSize(
targetSize: CValue<CGSize>,
withHorizontalFittingPriority: UILayoutPriority,
verticalFittingPriority: UILayoutPriority,
): CValue<CGSize> {
return targetSize.useContents<CGSize, CValue<CGSize>> {
calculateLayout(
width = when {
withHorizontalFittingPriority == UILayoutPriorityRequired -> width.toYoga()
widthConstraint == Constraint.Fill -> width.toYoga()
else -> Size.UNDEFINED
},
height = when {
verticalFittingPriority == UILayoutPriorityRequired -> height.toYoga()
heightConstraint == Constraint.Fill -> height.toYoga()
else -> Size.UNDEFINED
},
)
}
}
Expand Down Expand Up @@ -160,14 +179,12 @@ internal class YogaUIView : UIScrollView(cValue { CGRectZero }), UIScrollViewDel

private fun calculateLayout(
width: Float = Size.UNDEFINED,
maxWidth: Float = Size.UNDEFINED,
height: Float = Size.UNDEFINED,
maxHeight: Float = Size.UNDEFINED,
): CValue<CGSize> {
rootNode.requestedWidth = width
rootNode.requestedMaxWidth = maxWidth
rootNode.requestedMaxWidth = Size.UNDEFINED
rootNode.requestedHeight = height
rootNode.requestedMaxHeight = maxHeight
rootNode.requestedMaxHeight = Size.UNDEFINED

rootNode.measureOnly(Size.UNDEFINED, Size.UNDEFINED)

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading