在SwiftUI

时间:2019-12-22 15:19:57

标签: ios swift layout swiftui

我想显示一个数学分数。为此,我有一个VStack,其中包含分子视图(称为ContainerView),用作分数线的矩形和分母视图(分子的相同类型)。由于分数是可编辑的,因此分子和分母的大小可以更改,因此分数线必须适应。我的第一个尝试是将分数线置于背景中,并使用几何读取器仅基于分子和分母的大小来获取分数的框架。这样,分数线位于背景的中心,具有我想要的大小。这是代码:

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))
    })
}
}

这三个属性用于渲染分子和分母。The result is this。 但是,由于它在背景中,因此不知道分子和分母的高度。当它们的大小like in this case不同时,这会带来问题。在图像中,您可以看到分子中还有另一个分数,因此其视图的高度更大。这引起问题,因为第一分数线穿过分子的一部分。

为避免这种情况,我认为最好的方法是将分数线直接放在主VStack中,因此它将分子和分母分开而不关心它们的高度。但是,矩形会占用所有可用空间,并且我不知道如何将其宽度限制为父级的宽度。这是新代码和what it looks like

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)的宽度。

也许解决方案比我想的要容易得多,但我无法弄清楚。我知道我写的内容可能有些混乱,如果您需要任何澄清,请在评论中问我。 预先感谢。

1 个答案:

答案 0 :(得分:0)

此处对您的方案应用了与注释相同的想法。当然,它只是从头开始的,但是IMO可以根据需要进行调整并使其通用(例如,将当前使用的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")
        }
    }
}

struct DemoFraction_Previews: PreviewProvider {
    static var previews: some View {
        DemoFraction()
    }
}