将父视图的宽度设置为最小子视图的宽度吗?

时间:2019-11-06 19:31:31

标签: swiftui

如何使父视图的宽度等于最小子视图的宽度?

我可以使用this question的答案来了解子视图的宽度,如下所示:

struct WidthPreferenceKey: PreferenceKey {
    static var defaultValue = CGFloat(0)

    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = 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 width1: CGFloat = 0
    @State var width2: CGFloat = 0

    var body: some View {
        VStack {
            Text("Short")
              .background(TextGeometry())
              .onPreferenceChange(WidthPreferenceKey.self, perform: { self.width1 = $0 })
            Text("Loooooooooooong")
              .background(TextGeometry())
              .onPreferenceChange(WidthPreferenceKey.self, perform: { self.width2 = $0 })
        }
        .frame(width: min(self.width1, self.width2)) // This is always 0
    }

}

但是VStack的宽度始终为0。就像 .frame(width: min(self.width1, self.width2))行被调用 在设置宽度之前。

有什么想法吗?

1 个答案:

答案 0 :(得分:2)

这是一个逻辑错误,因为从技术上讲,系统完全按照您的指示进行操作,但是结果却不是您作为程序员所想要的。

基本上,VStack 想要尽可能缩小以适合其内容的大小。同样,文本视图愿意尽可能缩小(截断其内容)以适合其父视图。您给出的唯一硬性要求是VStack的初始帧的宽度为0。因此发生以下顺序:

  1. width1width2初始化为0
  2. VStack将其宽度设置为min(0, 0)
  3. 内部的Text视图缩小到宽度0
  4. WidthPreferenceKey.self设置为0
  5. .onPreferenceChange设置了width1width2,它们已经为0

所有约束都得到满足,SwiftUI会愉快地停止布局。

让我们使用以下内容修改代码:

  1. 使WidthPreferenceKey.Value成为[CGFloat]的别名,而不是CGFloat。这样,您可以根据需要设置任意数量的首选项键设置器,它们只会不断累积到一个数组中。
  2. 使用一个.onPreferenceChange调用,它将找到所有读取值中的最小值,并设置单个@State var width: CGFloat属性
  3. .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
        }
    }

}

现在发生以下情况:

  1. width初始化为0
  2. VStack将其宽度设置为0
  3. Text视图在VStack外部扩展到 ,因为我们已授予.fixedSize()
  4. 的权限
  5. WidthPreferenceKey.self设置为[42.5, 144.5](或近似值)
  6. .onPreferenceChangewidth设置为42.5
  7. VStack将其宽度设置为42.5,以满足其.frame()修饰符。

满足所有约束,并且SwiftUI停止布局。请注意,.clipped()会阻止显示长Text的边缘,即使它们在技术上不在VStack的范围之内。