条形(柱状)图以矩形条的形式呈现数据的类别,其宽度和高度与它们表示的值成比例。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 里取数据: Day Times 星期一 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% 打野 T1 凯 48.24% 打野 T1 李元芳 49.53% 打野 T1 澜 48.76% 打野 T2 兰陵王 47.08% 打野 T2 韩信 50.94% 打野 T2 赵云 50.34% 打野 T2 程咬金 50.47% 打野 T2 曜 51.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% 打野 T3 镜 47.00% 打野 T3 司马懿 52.20% 打野 T3 露娜 49.88% 打野 T3 猪八戒 48.41% 打野 T3 阿骨朵 54.28% 打野 T3 暃 49.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 
    var  body:  some  View  { 
        GeometryReader  {  gr in 
            let  barHeight =  ( Double ( fullBarHeight)  /  maxValue)  *  value
            let  textWidth =  gr. size. width *  0.80 
            VStack  { 
                Spacer ( ) 
                RoundedRectangle ( cornerRadius:  6.0 ) 
                    . fill ( Color . red) 
                    . frame ( width:  60 ,  height:  CGFloat ( barHeight) ,  alignment:  . trailing) 
                    . overlay ( 
                        Text ( " \( value,  specifier:  "%.1f"  ) " ) 
                            . font ( . footnote) 
                            . foregroundColor ( . white) 
                            . fontWeight ( . regular) 
                            . frame ( width:  textWidth) 
                            . offset ( y: 8 ) 
                        , 
                        alignment:  . top
                    ) 
                Text ( name) 
                    . font ( . system ( size:  11 ) ) 
                    . fontWeight ( . semibold) 
                    . lineLimit ( 5 ) 
                    . frame ( width:  textWidth,  alignment:  . center) 
            } 
            . padding ( . horizontal,  0 ) 
        } 
    } 
} 
 
将视图被嵌入到 ScrollView 中,以便在设备旋转时滚动: 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  { 
        
        ScrollView  { 
           VStack ( )  { 
               Text ( "2022王者荣耀英雄胜率排行榜" ) 
                   . font ( . title) 
               Spacer ( ) . frame ( height: 20 ) 
               
               Text ( "打野路英雄胜率(%)" ) 
                    . font ( . title3) 
               HistogramView ( 
                   title:  "打野路英雄胜率(%)" ,  data:  chartData) 
               . frame ( width:  UIScreen . main. bounds. width -  60 ,  height:  280 ,  alignment:  . center) 
               Spacer ( ) . frame ( height: 20 ) 
               Spacer ( ) 
           } 
           . padding ( ) 
       } 
    } 
}