Это логическая ошибка в том, что система технически делает именно то, что вы ей сказали, но этот результат не тот, который вы, как программист, намеревались.
По сути, VStack хочет максимально сжать, чтобы соответствовать размеру его содержимого. Аналогично, текстовые представления желают максимально сжать (урезать их содержимое), чтобы они соответствовали их родительскому представлению. Единственное жесткое требование, которое вы задали, это то, что начальный кадр VStack имеет ширину 0. Таким образом, происходит следующая последовательность:
width1
и width2
инициализируются равными 0 -
VStack
устанавливает свою ширину на min(0, 0)
Text
Вид изнутри сжимается до ширины 0 WidthPreferenceKey.self
установлен на 0 .onPreferenceChange
устанавливает width1
и width2
, которые уже равны 0
Все ограничения выполнены, и SwiftUI успешно останавливает макет.
Давайте изменим ваш код следующим образом:
- Сделайте
WidthPreferenceKey.Value
typealias до [CGFloat]
вместо CGFloat
. Таким образом, вы можете установить столько установщиков ключей предпочтения, сколько захотите, и они будут просто накапливаться в массив. - Используйте один вызов
.onPreferenceChange
, который найдет минимум всех прочитанных значений, иустановите одно @State var width: CGFloat
свойство - Добавьте
.fixedSize()
к Text
представлениям.
Примерно так:
struct WidthPreferenceKey: PreferenceKey {
static var defaultValue = [CGFloat]()
static func reduce(value: inout [CGFloat], nextValue: () -> [CGFloat]) {
value.append(contentsOf: nextValue())
}
typealias Value = [CGFloat]
}
struct TextGeometry: View {
var body: some View {
GeometryReader { geometry in
return Rectangle()
.fill(Color.clear)
.preference(key: WidthPreferenceKey.self,
value: [geometry.size.width])
}
}
}
struct Content: View {
@State var width: CGFloat = 0
var body: some View {
VStack {
Text("Short")
.fixedSize()
.background(TextGeometry())
Text("Loooooooooooong")
.fixedSize()
.background(TextGeometry())
}
.frame(width: self.width)
.clipped()
.background(Color.red.opacity(0.5))
.onPreferenceChange(WidthPreferenceKey.self) { preferences in
print(preferences)
self.width = preferences.min() ?? 0
}
}
}
Теперь происходит следующее:
width
устанавливается в 0 VStack
устанавливает ширину в 0 Text
представления расширяются снаружи VStack, поскольку мы дали разрешение с .fixedSize()
WidthPreferenceKey.self
установлено на [42.5, 144.5]
(или что-то близкое) .onPreferenceChange
устанавливает width
на 42.5
VStack
устанавливает свою ширину 42.5
, чтобы удовлетворить свой модификатор .frame()
.
Все ограничения выполнены, и SwiftUI останавливает макет. Обратите внимание, что .clipped()
не позволяет отображать края длинного вида Text
, даже если они технически находятся за пределами VStack
.