From ed60a059c03e86f62b85997ddd6a616d9c7b53af Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Wed, 29 Nov 2023 21:33:09 -0500 Subject: [PATCH 1/2] UIStackViewTest --- .../testing/AbstractWidgetChildrenTest.kt | 10 +++++ .../redwood/widget/UIStackViewChildrenTest.kt | 38 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 redwood-widget/src/iosTest/kotlin/app/cash/redwood/widget/UIStackViewChildrenTest.kt diff --git a/redwood-widget-testing/src/commonMain/kotlin/app/cash/redwood/widget/testing/AbstractWidgetChildrenTest.kt b/redwood-widget-testing/src/commonMain/kotlin/app/cash/redwood/widget/testing/AbstractWidgetChildrenTest.kt index 918f5f322e..cd57f45f35 100644 --- a/redwood-widget-testing/src/commonMain/kotlin/app/cash/redwood/widget/testing/AbstractWidgetChildrenTest.kt +++ b/redwood-widget-testing/src/commonMain/kotlin/app/cash/redwood/widget/testing/AbstractWidgetChildrenTest.kt @@ -140,6 +140,16 @@ public abstract class AbstractWidgetChildrenTest { assertThat(names()).containsExactly("one", "two", "three") } + @Test public fun replace() { + // Models what happens when a conditional flips from the second branch to the first. + // The new item will be added and then the old item will be removed. + // From https://github.com/cashapp/redwood/pull/1706. + children.insert(0, widget("one")) + children.insert(0, widget("new one")) + children.remove(1, 1) + assertThat(names()).containsExactly("new one") + } + private fun Widget.Children.insert(index: Int, widget: W) { insert( index = index, diff --git a/redwood-widget/src/iosTest/kotlin/app/cash/redwood/widget/UIStackViewChildrenTest.kt b/redwood-widget/src/iosTest/kotlin/app/cash/redwood/widget/UIStackViewChildrenTest.kt new file mode 100644 index 0000000000..8b9b6796b8 --- /dev/null +++ b/redwood-widget/src/iosTest/kotlin/app/cash/redwood/widget/UIStackViewChildrenTest.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.cash.redwood.widget + +import app.cash.redwood.widget.testing.AbstractWidgetChildrenTest +import platform.UIKit.UILabel +import platform.UIKit.UIStackView +import platform.UIKit.UIView + +/** + * [UIViewChildren] has special support for [UIStackView] containers which (somewhat annoyingly) + * use a different collection for managing the children. + */ +class UIStackViewChildrenTest : AbstractWidgetChildrenTest() { + private val parent = UIStackView() + override val children = UIViewChildren(parent) + + override fun widget(name: String): UIView { + return UILabel().apply { text = name } + } + + override fun names(): List { + return parent.arrangedSubviews.map { (it as UILabel).text!! } + } +} From 061b90f695c4e4e493d7ca038e61c59e200a88c7 Mon Sep 17 00:00:00 2001 From: Aleksey Mikhailov Date: Wed, 29 Nov 2023 20:16:36 +0700 Subject: [PATCH 2/2] fix bug replacing of root widget on UIKit --- .../kotlin/app/cash/redwood/widget/UIViewChildren.kt | 9 +++++++-- .../src/iosMain/kotlin/app/cash/redwood/widget/utils.kt | 5 +++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/UIViewChildren.kt b/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/UIViewChildren.kt index 38898ef960..a5b37e6352 100644 --- a/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/UIViewChildren.kt +++ b/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/UIViewChildren.kt @@ -28,8 +28,13 @@ public class UIViewChildren( else -> { view, index -> parent.insertSubview(view, index.convert()) } }, private val remove: (index: Int, count: Int) -> Array = { index, count -> - Array(count) { - parent.typedSubviews[index].also(UIView::removeFromSuperview) + // Per the docs, these properties are read-only copies we can use stable indexing into. + val subviews = when (parent) { + is UIStackView -> parent.typedArrangedSubviews + else -> parent.typedSubviews + } + Array(count) { offset -> + subviews[index + offset].also(UIView::removeFromSuperview) } }, ) : Widget.Children { diff --git a/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/utils.kt b/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/utils.kt index 02665d182b..01e2964e04 100644 --- a/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/utils.kt +++ b/redwood-widget/src/iosMain/kotlin/app/cash/redwood/widget/utils.kt @@ -15,9 +15,14 @@ */ package app.cash.redwood.widget +import platform.UIKit.UIStackView import platform.UIKit.UIView import platform.UIKit.subviews @Suppress("UNCHECKED_CAST") internal val UIView.typedSubviews: List get() = subviews as List + +@Suppress("UNCHECKED_CAST") +internal val UIStackView.typedArrangedSubviews: List + get() = arrangedSubviews as List