如何在 GeometryReader 中放置旋转标签

How to place rotated labels within a GeometryReader

我有下面的,它试图用几组标签做一个条形图,一组旋转。在下面,我使用 GeometryReader 将 space 的一半分配给实际的柱。

然后我在下方放置了 2 组标签,第一组没问题,但第二组(已旋转)最终位于第一组之上。让第二组标签显示在第一组标签下方的简洁方法是什么?

struct ContentView: View {
    var body: some View {
        GeometryReader { geometry in
            let width = geometry.size.width / 3.0
            VStack {
                Text("Chart").font(.title)
                HStack(alignment: .bottom, spacing: 0.0) {
                    Rectangle().fill(.blue).frame(width: width, height: geometry.size.height/2.0)
                    Rectangle().fill(.orange).frame(width: width, height: geometry.size.height/2.0 * 0.7)
                    Rectangle().fill(.red).frame(width: width, height: geometry.size.height/2.0 * 0.88)
                }
                Divider()
                HStack {
                    Text("100%").frame(width: width)
                    Text("70%").frame(width: width)
                    Text("88%").frame(width: width)
                }
                Divider()
                HStack {
                    Text("1somelonglabel").rotationEffect(Angle(degrees: 90.0))
                    Text("2somelonglabel").rotationEffect(Angle(degrees: 90.0))
                    Text("3somelonglabel").rotationEffect(Angle(degrees: 90.0))
                }
            }
        }
    }
}

编辑:

这是一些更新后的代码,包含更多条形图。虽然旋转标签现在位于第一组下方,但它们被截断了,因为看起来即使在旋转后原始宽度也被考虑在内。整个旋转点是为了能够适应标签,所以不知道如何让它们在旋转后占据可用的space。

struct Entry: Identifiable {
    var id: Int
    var label: String
    var value: Double
    var color: Color
    
    func labelPercentage() -> String {
        let percentage = value * 100.0
        return String(format: "%.0f", percentage) + "%"
    }
}

struct Entries {
    var entries: [Entry] = []
    
    init() {
        entries.append( Entry(id: 1, label: "short", value: 0.3, color: .blue) )
        entries.append( Entry(id: 2, label: "medium", value: 0.5, color: .orange) )
        entries.append( Entry(id: 3, label: "label2", value: 0.0, color: .gray) )
        entries.append( Entry(id: 4, label: "longerlabel12", value: 0.3, color: .black) )
        entries.append( Entry(id: 5, label: "label12", value: 0.7, color: .red) )
        entries.append( Entry(id: 6, label: "another333", value: 0.6, color: .green) )
        entries.append( Entry(id: 7, label: "another", value: 0.0, color: .purple) )
        entries.append( Entry(id: 8, label: "medium123", value: 0.1, color: .yellow) )
    }
}
struct ContentView: View {
    var entries: Entries
    
    var body: some View {
        GeometryReader { geometry in
            let width = geometry.size.width / CGFloat(entries.entries.count) - 8.0
            let height = geometry.size.height / 2.0
            let offset = width / 2.0
            VStack {
                Text("Chart").font(.title)
                HStack(alignment: .bottom, spacing: 4.0) {
                    ForEach(entries.entries) { entry in
                        Bar(color: entry.color, percentage: entry.value, width: width, height: height)
                    }
                }
                Divider()
                HStack {
                    ForEach(entries.entries) { entry in
                        Text(entry.labelPercentage()).font(.caption).frame(width: width)
                    }
                }
                Divider()
                HStack {
                    ForEach(entries.entries) { entry in
                        Text(entry.label).lineLimit(1)
                            .frame(width: width, alignment: .topLeading)
                            .offset(x: offset, y: 0)
                            .rotationEffect(Angle(degrees: 90.0)).border(.red, width: 3)
                    }
                }
            }
        }
    }
}

struct Bar: View {
    var color: Color
    var percentage: Double
    var width: Double
    var height: Double
    
    var body: some View {
        ZStack(alignment: .bottom) {
            Rectangle().fill(.white).frame(width: width, height: height).opacity(0.0) // Want the full height
            Rectangle().fill(color).frame(width: width, height: height * percentage)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
       // ContentView(words: Words(numWords: 200))
        ContentView(entries: Entries())
    }
}

这是它的样子:

您遇到的问题是 .rotationEffect() 正在围绕其中心旋转 Text()。由于 Text() 在旋转时位于 Divider() 的正下方,它的一半遮住了上面的视图。因此,您只需将标签向右移动,当顺时针旋转时,标签会向下移动。我只是使用了你宽度的一半,这对于这个例子来说已经足够了。我也把它放在一个框架中,以防止标签变得太长。

struct ContentView: View {
    var body: some View {
        GeometryReader { geometry in
            let width = geometry.size.width / 3.0
            let offset = width / 2.0
            VStack {
                Text("Chart").font(.title)
                HStack(alignment: .bottom, spacing: 0.0) {
                    Rectangle().fill(.blue).frame(width: width, height: geometry.size.height/2.0)
                    Rectangle().fill(.orange).frame(width: width, height: geometry.size.height/2.0 * 0.7)
                    Rectangle().fill(.red).frame(width: width, height: geometry.size.height/2.0 * 0.88)
                }
                Divider()
                HStack {
                    Text("100%").frame(width: width)
                    Text("70%").frame(width: width)
                    Text("88%").frame(width: width)
                }
                Divider()
                HStack {
                    Text("1somelonglabel").frame(width: width).offset(x: offset, y: 0).rotationEffect(Angle(degrees: 90.0))
                    Text("2somelonglabel").frame(width: width).offset(x: offset, y: 0).rotationEffect(Angle(degrees: 90.0))
                    Text("3somelonglabel").frame(width: width).offset(x: offset, y: 0).rotationEffect(Angle(degrees: 90.0))
                }
            }
        }
    }
}

好的,根据其中一条评论中的信息(在矩形上使用叠加层),我终于得到了我想要的:


import SwiftUI

struct Entry: Identifiable {
    var id: Int
    var label: String
    var value: Double
    var color: Color
    
    func labelPercentage() -> String {
        let percentage = value * 100.0
        return String(format: "%.0f", percentage) + "%"
    }
}

struct Entries {
    var entries: [Entry] = []
    
    init() {
        entries.append( Entry(id: 1, label: "short", value: 0.3, color: .blue) )
        entries.append( Entry(id: 2, label: "medium", value: 0.5, color: .orange) )
        entries.append( Entry(id: 3, label: "label2", value: 0.0, color: .gray) )
        entries.append( Entry(id: 4, label: "longerlabel12", value: 0.3, color: .black) )
        entries.append( Entry(id: 5, label: "label12", value: 0.7, color: .red) )
        entries.append( Entry(id: 6, label: "another333", value: 0.6, color: .green) )
        entries.append( Entry(id: 7, label: "another", value: 0.0, color: .purple) )
        entries.append( Entry(id: 8, label: "medium123", value: 0.1, color: .yellow) )
    }
}
struct ContentView: View {
    var entries: Entries
    
    var body: some View {
        GeometryReader { geometry in
            let width = geometry.size.width / CGFloat(entries.entries.count) - 8.0
            let height = geometry.size.height / 2.0

            VStack {
                Text("Chart").font(.title)
                HStack(alignment: .bottom, spacing: 4.0) {
                    ForEach(entries.entries) { entry in
                        Bar(color: entry.color, percentage: entry.value, width: width, height: height)
                    }
                }
                Divider()
                HStack(spacing: 4) {
                    ForEach(entries.entries) { entry in
                        Text(entry.labelPercentage()).font(.caption).frame(width: width)
                    }
                }
                Divider()
                HStack(spacing: 4) {
                    ForEach(entries.entries) { entry in
                        Rectangle().foregroundColor(.white)
                            .frame(width: width, height: height/2.0)
                            .overlay(
                                Text(entry.label)
                                    .font(.caption)
                                    .fontWeight(.bold)
                                    .fixedSize()
                                    .rotationEffect(Angle(degrees: 90), anchor: .leading)
                                    .offset(x: width / 2.0, y: 0)
                                , alignment: .topLeading)
                    }
                }
            }
        }
    }
}

struct Bar: View {
    var color: Color
    var percentage: Double
    var width: Double
    var height: Double
    
    var body: some View {
        ZStack(alignment: .bottom) {
            Rectangle().fill(.white).frame(width: width, height: height).opacity(0.0) // Want the full height
            Rectangle().fill(color).frame(width: width, height: height * percentage)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(entries: Entries())
    }
}