在SwiftUI中,如何将一个视图的大小设置为其父视图的大小?

4
我想显示一个数学分数。为此,我有一个包含分子视图(称为ContainerView)、用作分数线的矩形和分母视图(与分子相同类型)的VStack。由于该分数是可编辑的,因此分子和分母的大小可能会发生变化,因此分数线必须相应地适应。我的第一次尝试是将分数线放在背景中,并使用几何读取器仅基于分子和分母的大小来获取分数的框架。这样,分数线位于背景的中心,大小也符合我的要求。以下是代码:
struct FractionView: View {

@EnvironmentObject var buffer: Buffer
var numeratorBufferIndex: Int
var denominatorBufferIndex: Int

var body: some View {
    VStack(alignment: .center, spacing: 0) {
        ContainerView(activeBufferIndex: numeratorBufferIndex)
            .environmentObject(self.buffer)
        
        ContainerView(activeBufferIndex: denominatorBufferIndex)
            .environmentObject(self.buffer)
    }
    .background(GeometryReader { geometry in
        Rectangle()
            .frame(width: geometry.frame(in: .local).width, height: 2)
            .foregroundColor(Color(.systemGray))
    })
}
}

三个属性用于渲染分子和分母。结果就是这样。然而,由于它在背景中,它不知道分子和分母的高度。当它们大小不同时,会出现问题,就像在这种情况下。在图像中,您可以看到分子中有另一个分数,因此其视图的高度更大。这会导致一个问题,因为第一条分数线穿过了一部分分子。
为了避免这种情况,我认为最好的方法是直接将分数线放在主 VStack 中,这样它就可以在不考虑它们的高度的情况下划分分子和分母。但是,矩形形状占用了所有可用的空间,我不知道如何将其宽度限制为父级的宽度。这是新代码和它的样子
struct FractionView: View {

@EnvironmentObject var buffer: Buffer
var numeratorBufferIndex: Int
var denominatorBufferIndex: Int

var body: some View {
    VStack(alignment: .center, spacing: 0) {
        ContainerView(activeBufferIndex: numeratorBufferIndex)
            .environmentObject(self.buffer)
        
        Rectangle()
            .frame(height: 2)
            .foregroundColor(Color(.systemGray))
        
        ContainerView(activeBufferIndex: denominatorBufferIndex)
            .environmentObject(self.buffer)
    }
}
}

总结一下,我需要找到一种方法来限制矩形的宽度,使其不超过没有该矩形时 VStack 的宽度。我尝试了使用偏好设置,按照this article的示例操作。
以下是我的尝试:
struct FractionView: View {

@EnvironmentObject var buffer: Buffer
@State var fractionFrame: CGFloat = 0
var numeratorBufferIndex: Int
var denominatorBufferIndex: Int

var body: some View {
    VStack(alignment: .center, spacing: 0) {
        ContainerView(activeBufferIndex: numeratorBufferIndex)
            .environmentObject(self.buffer)
        
        Rectangle()
            .frame(width: fractionFrame, height: 2)
            .foregroundColor(Color(.systemGray))
        
        ContainerView(activeBufferIndex: denominatorBufferIndex)
            .environmentObject(self.buffer)
    }
.coordinateSpace(name: "VStack")
    .background(GeometryObteiner())
    .onPreferenceChange(MyFractionPreferenceKey.self) { (value) in
            print("Il valore della larghezza è \(value)")
            self.fractionFrame = value
    }
    
}
}

struct GeometryObteiner: View {
var body: some View {
    GeometryReader { geometry in
        Rectangle()
            .frame(width: geometry.frame(in: .global).width, height: geometry.frame(in: .global).height)
            .foregroundColor(.clear)
            .preference(key: MyFractionPreferenceKey.self, value: geometry.frame(in: .named("VStack")).width)
    }
}
}

struct MyFractionPreferenceKey: PreferenceKey {
typealias Value = CGFloat

static var defaultValue: CGFloat = 10

static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
    value = nextValue()
}
}

然而,看起来onPreferenceChange从未被调用,因为result总是分数线具有默认值(10)的宽度。

也许解决方案比我想象的要简单得多,但我想不出来。我知道我写的可能有点令人困惑,如果您需要任何澄清,请在评论中向我提问。


您可能会发现这篇文章 如何在SwiftUI中编写分数 会很有帮助。 - Asperi
谢谢你的建议,但我试图完成的任务相当不同且更复杂。实际上,我还需要显示嵌套分数和变量,而不仅仅是两个整数。然而,我的问题并不是关于那个的,而更多的是关于如何在我的实现中管理父视图和子视图。 - GiulioFerDev
2个回答

5
这里应用了与您的方案相同的思路。当然,这只是草图,但我认为它可以根据需要进行调整并变得通用(将当前使用的 Text 替换为 ViewFullFraction 本身以构建更复杂的分数)。
演示(使用 DispatchQueue 来避免在视图绘制警告期间进行修改,请运行它以查看结果): SwiftUI math fraction 代码(截图演示):
struct FullFraction: View {
    var whole: String = ""
    var num: String
    var denom: String

    @State private var numWidth: CGFloat = 12.0 // just initial
    @State private var denomWidth: CGFloat = 12.0 // just initial
    var body: some View {
        HStack {
            Text(whole)
            VStack {
                numerator
                divider
                denominator
            }
        }
    }
    var numerator: some View {
        Text(num)
            .offset(x: 0, y: 8)
            .alignmentGuide(HorizontalAlignment.center, computeValue: { d in
                DispatchQueue.main.async {
                    self.numWidth = d.width
                }
                return d[HorizontalAlignment.center]
            })
    }
    var denominator: some View {
        Text(denom)
        .offset(x: 0, y: -4)
        .alignmentGuide(HorizontalAlignment.center, computeValue: { d in
            DispatchQueue.main.async {
                self.denomWidth = d.width
            }
            return d[HorizontalAlignment.center]
        })
    }

    var divider: some View {
        Rectangle().fill(Color.black).frame(width:max(self.numWidth, self.denomWidth), height: 2.0)
    }
}

struct DemoFraction: View {
    var body: some View {
        VStack {
            FullFraction(whole: "F", num: "(a + b)", denom: "c")
            Divider()
            FullFraction(whole: "ab12", num: "13", denom: "761")
            Divider()
            FullFraction(num: "1111", denom: "3")
        }
    }
}

1
谢谢你的回答,你马上理解了我的问题,并提供了一个我从未见过的解决方案。我在我的实现中尝试了它,它运行良好,唯一的问题是它在SwiftUI预览中无法运行,我只能在模拟器或设备上看到它运行。 - GiulioFerDev
如果你是为预览版开发的话,那么当然会遇到许多异步特性方面的问题。)) - Asperi

1
您可以通过将其包装在ZStack中并使用layoutPriority修饰符来使形状保持在文本范围内,同时仍保持声明性/支持预览,从而实现相同的效果。

最终变得简单了:

struct FullFraction: View {
    var whole: String = ""
    var num: String
    var denom: String
    
    var body: some View {
        HStack {
            Text(whole)
            ZStack{
                VStack {
                    numerator
                    denominator
                }
                divider
                    .layoutPriority(-1)
            }
        }
        
    }

    var numerator: some View {
        Text(num)
            .padding(2)
    }

    var denominator: some View {
        Text(denom)
            .padding(2)
    }
    
    var divider: some View {
        Rectangle()
            .fill(Color.black)
            .frame(height:2.0)
    }
}

在预览中运行:

Running in preview


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接