SwiftUI之深入解析如何使用组合矩形GeometryReader创建条形(柱状)图

Posted ╰つ栺尖篴夢ゞ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SwiftUI之深入解析如何使用组合矩形GeometryReader创建条形(柱状)图相关的知识,希望对你有一定的参考价值。

一、图表布局

  • 条形(柱状)图以矩形条的形式呈现数据的类别,其宽度和高度与它们表示的值成比例。SwiftUI 对探索不同布局和预览实时视图结果是很友好的,很容易将部分内容提取到子视图中,以便每个部分都很小且易于维护。
  • 从包含 HistogramView 以及可能的其它文本或数据的视图开始,HistogramView 包含一个标题和一个图表区,它们由文本和圆角矩形表示。
  • HistogramView 的创建:
struct HistogramView: View 
    var title: String

    var body: some View 
        GeometryReader  gr in
            let headHeight = gr.size.height * 0.10
            VStack 
                HistogramHeaderView(title: title, height: headHeight)
                HistogramAreaView()
            
        
    


struct HistogramHeaderView: View 
    var title: String
    var height: CGFloat

    var body: some View 
        Text(title)
            .frame(height: height)
    


struct HistogramAreaView: View 
    var body: some View 
        ZStack 
            RoundedRectangle(cornerRadius: 5.0)
                .fill(Color(#colorLiteral(red: 0.80, green: 0.90, blue: 0.80, alpha: 1)))
        
    

  • 在 ContentView 中调用 HistogramView:
struct ContentView_Previews: PreviewProvider 
    static var previews: some View 
        ContentView()
    


struct ContentView: View 
    var body: some View 
        VStack 
            Text("数据分析")
                .font(.title)

            HistogramView(
                title: "柱状图")
                .frame(width: 300, height: 300, alignment: .center)

            Spacer()
        
        .padding()
    

  • 效果如下所示:

二、添加条形(柱状)图

  • 定义一些简单的数据类别,如一周内每天看手机的时间分钟数,以下表数据被作为主视图的项目数据,每一条数据包含一个键值对(名称和值)。在平常的 App 里,这些数据应该通过 ViewModel 从 model 里取数据:
DayTimes
星期一188
星期二209
星期三300
星期四150
星期五198
星期六488
星期日409
  • 修改图表区并设置数据:
struct ContentView: View 
    
    let chartData: [DataItem] = [
        DataItem(name: "星期一", value: 188),
        DataItem(name: "星期二", value: 209),
        DataItem(name: "星期三", value: 300),
        DataItem(name: "星期四", value: 150),
        DataItem(name: "星期五", value: 198),
        DataItem(name: "星期六", value: 488),
        DataItem(name: "星期日", value: 409)
    ]

    var body: some View 
        VStack 
            Text("屏幕使用时间")
                .font(.title)

            HistogramView(
                title: "一周数据分析", data: chartData)
                .frame(width: 350, height: 500, alignment: .center)

            Spacer()
        
    

  • 更新 HistogramView 使数据可以作为参数传递到 HistogramAreaView:
struct HistogramView: View 
    
    var title: String
    var data: [DataItem]

    var body: some View 
        GeometryReader  gr in
            let headHeight = gr.size.height * 0.10
            VStack 
                HistogramHeaderView(title: title, height: headHeight)
                HistogramAreaView(data: data)
            
        
    

  • 更新后的 HistogramView 需要一个 DataItem 的列表,GeometryReader 被用来确定条形图的可用高度,数据中的最大值得到后并传递给每个 ColumnView,主图表区域保持原来的圆角矩形,并以水平堆叠的方式叠加一系列条形,每个 DataItem 对应一个条形:
struct HistogramAreaView: View 
    
    var data: [DataItem]
    
    var body: some View 
        GeometryReader  gr in
            let fullBarHeight = gr.size.height * 0.90
            let maxValue = data.map  $0.value .max()!
            
            ZStack 
                RoundedRectangle(cornerRadius: 5.0)
                    .fill(Color(#colorLiteral(red: 0.80, green: 0.90, blue: 0.80, alpha: 1)))
                VStack 
                    HStack(spacing:0) 
                        ForEach(data)  item in
                            ColumnView(
                                name: item.name,
                                value: item.value,
                                maxValue: maxValue,
                                fullBarHeight: Double(fullBarHeight))
                        
                    
                    .padding(4)
                
                
            
        
    

  • 为 ColumnView 创建一个新的视图,该视图为每条数据创建一个条形图,它需要每一条数据的名称和值以及最大值和可用的条形高度,每个条形图都表示为圆角矩形,条形高度相对于最大条形高度设置,条形的颜色设置为红色:
struct ColumnView: View 
    
    var name: String
    var value: Double
    var maxValue: Double
    var fullBarHeight: Double

    var body: some View 
        let barHeight = (Double(fullBarHeight) / maxValue) * value
        VStack 
            Spacer()
            ZStack 
                VStack 
                    Spacer()
                    RoundedRectangle(cornerRadius:5.0)
                        .fill(Color.red)
                        .frame(height: CGFloat(barHeight), alignment: .trailing)
                

                VStack 
                    Spacer()
                    Text("\\(value, specifier: "%.0F")")
                        .font(.footnote)
                        .foregroundColor(.white)
                        .fontWeight(.bold)
                
            
            Text(name).font(Font.system(size: 13))
        
        .padding(.horizontal, 4)
    

  • 最终效果如下:

三、横屏显示(屏幕旋转)

  • 条形(柱状)图在使用样本数据时看起来不错,图表会调整到适合它所处的容器视图之中。同样的图表可以放到任何没有其他视图的新视图上,当设备旋转时,图标将会充满空间并调整大小:
struct ContentView: View 
    
    let chartData: [DataItem] = [
        DataItem(name: "星期一", value: 188),
        DataItem(name: "星期二", value: 209),
        DataItem(name: "星期三", value: 300),
        DataItem(name: "星期四", value: 150),
        DataItem(name: "星期五", value: 198),
        DataItem(name: "星期六", value: 488),
        DataItem(name: "星期日", value: 409)
    ]

    var body: some View 
        
        VStack() 
            
            Text("屏幕使用时间")
                .font(.title)
            
            HistogramView(
               title: "一周数据分析", data: chartData)
            Spacer()
       
       .padding()
    

  • 效果如下:

四、王者荣耀英雄胜率的柱状图渲染分析

  • 王者荣耀里面有很多的英雄,根据版本的不同英雄的胜率也会有所不同,肯定有很多小伙伴儿好奇哪些英雄的胜率高,哪些英雄的胜率低? 现在我们就用条形图的形式来清晰的展示出 2022 王者荣耀英雄胜率排行。
  • 2022 王者荣耀的打野路英雄的最新胜率,如下所示:
位置热度英雄胜率
打野T0孙悟空49.40%
打野T1李信50.68%
打野T1亚瑟48.41%
打野T1典韦52.40%
打野T148.24%
打野T1李元芳49.53%
打野T148.76%
打野T2兰陵王47.08%
打野T2韩信50.94%
打野T2赵云50.34%
打野T2程咬金50.47%
打野T251.80%
打野T2李白50.49%
打野T2阿珂50.77%
打野T2钟无艳50.68%
打野T2宫本武藏49.17%
打野T3芈月50.17%
打野T3刘备52.85%
打野T3娜可露露48.39%
打野T3橘右京49.69%
打野T3云樱49.66%
打野T347.00%
打野T3司马懿52.20%
打野T3露娜49.88%
打野T3猪八戒48.41%
打野T3阿骨朵54.28%
打野T349.67%
打野T3达摩49.85%
打野T3杨戬52.44%
打野T3百里玄策51.75%
打野T3曹操48.91%
打野T3雅典娜56.35%
打野T3裴擒虎47.92%
打野T3云中君50.68%
打野T3盘古48.66%
  • 使用英雄名在条形图中绘制:
struct DataItem: Identifiable 
    let name: String
    let value: Double
    let id = UUID()


struct ContentView: View 
    
    let chartData: [DataItem] = [
        DataItem(name: "孙悟空", value: 49.40),
        DataItem(name: "李信", value: 50.68),
        DataItem(name: "亚瑟", value: 48.41),
        DataItem(name: "典韦", value: 52.40),
        DataItem(name: "凯", value: 48.24),
        DataItem(name: "李元芳", value: 49.53),
        DataItem(name: "澜", value: 48.76),
        DataItem(name: "兰陵王", value: 47.08),
        DataItem(name: "韩信", value: 50.94),
        DataItem(name: "赵云", value: 50.34),
        DataItem(name: "程咬金", value: 50.47),
        DataItem(name: "曜", value: 51.80),
        DataItem(name: "李白", value: 50.49),
        DataItem(name: "阿珂", value: 50.77),
        DataItem(name: "钟无艳", value: 50.68),
        DataItem(name: "宫本武藏", value: 49.17),
        DataItem(name: "芈月", value: 50.17),
        DataItem(name: "刘备", value: 52.85),
        DataItem(name: "娜可露露", value: 48.39),
        DataItem(name: "橘右京", value: 49.69),
        DataItem(name: "云樱", value: 49.66),
        DataItem(name: "镜", value: 47.00),
        DataItem(name: "司马懿", value: 52.20),
        DataItem(name: "露娜", value: 49.88),
        DataItem(name: "猪八戒", value: 48.41),
        DataItem(name: "阿骨朵", value: 54.28),
        DataItem(name: "暃", value: 49.67),
        DataItem(name: "达摩", value: 49.85),
        DataItem(name: "杨戬", value: 52.44),
        DataItem(name: "百里玄策", value: 51.75),
        DataItem(name: "曹操", value: 48.91),
        DataItem(name: "雅典娜", value: 56.35),
        DataItem(name: "裴擒虎", value: 47.92),
        DataItem(name: "云中君", value: 50.68),
        DataItem(name: "盘古", value: 48.66)
    ]

    var body: some View 
        
        VStack() 
            
            Text("2022王者荣耀英雄胜率排行榜")
                .font(.title)
            
            HistogramView(
                title: "打野路英雄胜率(%)", data: chartData)
            Spacer()
       
       .padding()
    

  • 对 HistogramView 做出了一些改动,条形图上的值使用叠加视图修改移到了条形图的顶部,这个值是偏移的,因此文本不会离条形图的顶部太近。数据名称的字体大小和字重也可以被设置,像部分英雄名称那样较长的文本,显示出条形图下面的文本将条形图推到了线外。文本视图的宽度被限制在条形图宽度的范围内,而且条形图的标签文本会被截断,条形图的文本视图也被限制在条形宽度的范围内,并且文本可以被隐藏起来:
struct HistogramView: View 
    
    var title: String
    var data: [DataItem]

    var body: some View 
        GeometryReader  gr in
            let headHeight = gr.size.height * 0.10
            VStack 
                HistogramHeaderView(title: title, height: headHeight)
                HistogramAreaView(data: data)
                    .frame(width: CGFloat(data.count) * 70)
            
        
    

struct HistogramAreaView: View 
    
    var data: [DataItem]
    
    var body: some View 
        GeometryReader  gr in
            let fullBarHeight = gr.size.height * 0.90
            let maxValue = data.map  $0.value .max()!
            
            ZStack 
                RoundedRectangle(cornerRadius: 5.0)
                    .fill(Color(#colorLiteral(red: 0.80, green: 0.90, blue: 0.80, alpha: 1)))
                VStack 
                    HStack(spacing:0) 
                        ForEach(data)  item in
                            ColumnView(
                                name: item.name,
                                value: item.value,
                                maxValue: maxValue,
                                fullBarHeight: Double(fullBarHeight))
                        
                    
                    .padding(4)
                
            
        
    

struct ColumnView: View 
    
    var name: String
    var value: Double
    var maxValue: Double
    var fullBarHeight: Double

 

以上是关于SwiftUI之深入解析如何使用组合矩形GeometryReader创建条形(柱状)图的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI之深入解析如何创建和组合视图

Swift之深入解析如何使用Swift UI实现3D Scroll效果

SwiftUI之深入解析如何创建列表展示页和导航跳转详情页

SwiftUI之深入解析如何处理特定的数据和如何在视图中适配数据模型对象

SwiftUI之深入解析高级动画的时间轴TimelineView

Swift之深入解析如何结合Core Data和SwiftUI