SwiftUI框架詳細(xì)解析 (十) —— 基于SwiftUI構(gòu)建各種自定義圖表(一)

版本記錄

版本號 時間
V1.0 2020.01.10 星期五

前言

今天翻閱蘋果的API文檔,發(fā)現(xiàn)多了一個框架SwiftUI,這里我們就一起來看一下這個框架。感興趣的看下面幾篇文章。
1. SwiftUI框架詳細(xì)解析 (一) —— 基本概覽(一)
2. SwiftUI框架詳細(xì)解析 (二) —— 基于SwiftUI的閃屏頁的創(chuàng)建(一)
3. SwiftUI框架詳細(xì)解析 (三) —— 基于SwiftUI的閃屏頁的創(chuàng)建(二)
4. SwiftUI框架詳細(xì)解析 (四) —— 使用SwiftUI進(jìn)行蘋果登錄(一)
5. SwiftUI框架詳細(xì)解析 (五) —— 使用SwiftUI進(jìn)行蘋果登錄(二)
6. SwiftUI框架詳細(xì)解析 (六) —— 基于SwiftUI的導(dǎo)航的實現(xiàn)(一)
7. SwiftUI框架詳細(xì)解析 (七) —— 基于SwiftUI的導(dǎo)航的實現(xiàn)(二)
8. SwiftUI框架詳細(xì)解析 (八) —— 基于SwiftUI的動畫的實現(xiàn)(一)
9. SwiftUI框架詳細(xì)解析 (九) —— 基于SwiftUI的動畫的實現(xiàn)(二)

開始

首先看下主要內(nèi)容:

主要內(nèi)容:在本SwiftUI教程中,您將學(xué)習(xí)如何構(gòu)建各種自定義圖表,以有效地為用戶建模iOS應(yīng)用數(shù)據(jù)。

下面看下寫作環(huán)境

Swift 5, iOS 13, Xcode 11

下面就是正文了。

圖表是向用戶顯示數(shù)據(jù)的絕佳方法。 它們幫助用戶掌握大量信息中固有的關(guān)系。 您可以使用圖表來吸引人們注意趨勢,弄清原因并幫助用戶真正地可視化信息。

在本SwiftUI教程中,您將學(xué)習(xí)如何創(chuàng)建各種自定義圖表來幫助可視化應(yīng)用程序的數(shù)據(jù)。

盡管SwiftUI不提供本機(jī)圖表庫,但它具有豐富的圖形功能,可用于構(gòu)建自定義圖表。 在本教程中,您將向應(yīng)用程序添加圖表,以顯示大霧山國家公園(Great Smoky Mountains National Park)及其周圍多個氣象站的歷史氣象數(shù)據(jù)。

1. Why Use a Chart?

查看一些數(shù)據(jù)可能會很有啟發(fā)性,但是盯著一長串?dāng)?shù)字并不是獲得洞察力的最佳方法。 數(shù)字列表并不能使您更容易了解某個月的溫暖程度或確定最干旱的月份。

以圖形方式呈現(xiàn)信息時,大多數(shù)人都可以更輕松地掌握信息。 圖表可以提供旨在告知查看者的數(shù)據(jù)的圖形表示。

Charts vs. Graphs

人們經(jīng)?;Q使用術(shù)語“圖表”和“圖形”,但他們不是同一回事。

圖形表示出值之間的任何關(guān)系。 一個簡單的圖形可以顯示給定x的y值。 生成的曲線可能很漂亮,但沒有提供任何可清晰的信息。

圖表應(yīng)該講一個故事。 它使觀看者更容易理解和解釋,從而更好地理解數(shù)據(jù)。 簡而言之,所有圖表都是圖形,但并非所有圖形都是圖表。

打開起始工程并運行

該應(yīng)用程序顯示五個站點的數(shù)據(jù):

  • 北卡羅來納州切諾基和田納西州加特林堡(Cherokee, NC and Gatlinburg, TN):穿過公園的主要道路上的兩個城市。
  • Newfound Gap:主要道路跨越的區(qū)域。
  • Townsend 5 S:公園西南部的區(qū)域。
  • Mount LeConte:公園里最高的山脈之一。

該數(shù)據(jù)集包含每個位置每天的降水,降雪和溫度范圍。

點按某個位置可顯示有關(guān)該位置的信息,顯示該位置的地圖以及三個天氣信息選項卡。這三個標(biāo)簽顯示了每天的溫度范圍,每個月的總降水量以及每天有雪的降雪量。

首先,您需要在應(yīng)用中添加條形圖,以顯示降水量數(shù)據(jù)。


Refactoring for Charts

條形圖為每個數(shù)據(jù)點提供一個條形圖。每個條的長度代表一個數(shù)值,可以水平或垂直延伸以滿足您的需求。

展開Tabs組,然后打開PrecipitationTab.swift。您會看到一個標(biāo)準(zhǔn)的SwiftUI List(),該整數(shù)從011之間循環(huán),代表一年中的月份,并顯示每個月的總降雨量。包含的幫助器函數(shù)將整數(shù)更改為月份名稱,并對每個月份的數(shù)量求和。

右鍵單擊空的Charts組,然后選擇New File。選擇SwiftUI視圖,然后單擊下一步。將新視圖命名為PrecipitationChart

確保該組設(shè)置為Charts,然后單擊Create。打開新文件。如果“畫布”不可見,請從菜單中選擇Editor ? Canvas以將其打開,以便查看進(jìn)度。

PrecipitationChart結(jié)構(gòu)體的頂部添加以下代碼:

var measurements: [DayInfo]

您可以使用此變量將度量傳遞到圖表中。 現(xiàn)在更新PrecipitationChart_Previews以傳遞度量以進(jìn)行預(yù)覽。 在這種情況下,您將傳遞Mt. LeConte的測量值。

PrecipitationChart(measurements: WeatherInformation()!.stations[2].measurements)

首先,您將在此新視圖中復(fù)制現(xiàn)有函數(shù)。 首先,在measurements后添加兩個幫助函數(shù):

func sumPrecipitation(_ month: Int) -> Double {
  self.measurements.filter {
    Calendar.current.component(.month, from: $0.date) == month + 1
  }.reduce(0, { $0 + $1.precipitation })
}

func monthAbbreviationFromInt(_ month: Int) -> String {
  let ma = Calendar.current.shortMonthSymbols
  return ma[month]
}

sumPrecipitation(_ :)使用filter僅獲取傳遞給函數(shù)的月份的度量。 它調(diào)整傳入的整數(shù)(從零開始而不是從1開始)。 reduce計算這些測量的降水值之和。

monthAbbreviationFromInt(_ :)獲取當(dāng)前日歷的縮寫月份符號列表,并返回與傳遞的整數(shù)匹配的月份符號。

更新body以復(fù)制現(xiàn)有列表:

List(0..<12) { month in
  Text("\(self.monthAbbreviationFromInt(month)): " +
    "\(self.sumPrecipitation(month))\"")
}

打開PrecipitationTab.swift并刪除不再需要的sumPrecipitation(_ :)monthAbbreviationFromInt(_ :)方法。 在body內(nèi)部,使用對新視圖的調(diào)用來替換Listenclosure

PrecipitationChart(measurements: station.measurements)

注意:運行該應(yīng)用程序時,請確保在選擇要查看結(jié)果的位置后位于Precipitation選項卡上。


Raising the SwiftUI Bar

SwiftUI包含多個形狀視圖,其中包括一個矩形形狀,可以很好地構(gòu)建條形圖。 打開PrecipitationChart.swift并將其替換為:

// 1
HStack {
  // 2
  ForEach(0..<12) { month in
    // 3
    VStack {
      // 4
      Spacer()
      // 5
      Rectangle()
        .fill(Color.green)
        .frame(width: 20, height: CGFloat(self.sumPrecipitation(month)) * 15.0)
      // 6
      Text("\(self.monthAbbreviationFromInt(month))")
        .font(.footnote)
        .frame(height: 20)
    }
  }
}

以下是逐步進(jìn)行的細(xì)分:

  • 1) 您已經(jīng)創(chuàng)建了垂直條,因此您可以使用HStack在設(shè)備上水平排列子視圖。
  • 2) 您可以使用ForEach遍歷月份。
  • 3) 您可以對圖表中的每個條使用VStack來垂直堆疊元素。
  • 4) 您可以在堆棧中的其他視圖上指定大小,然后該Spacer將展開以填充剩余的空間。實際上,它告訴SwiftUI將空白放在VStack的頂部。
  • 5) 您使用Rectangle SwiftUI shape原語。它創(chuàng)建一個與視圖包含的frame對齊的矩形,并用綠色填充它。您指定寬度和高度恒定的frame作為月份的總降水量(以英寸為單位)乘以15。一個月有1英寸的降雨會形成一個20點寬,15點長的矩形。一個有七英寸大雨的月份顯示為一個矩形,寬20點,長105點。
  • 6) 您還為每個條提供標(biāo)簽,在這種情況下為一年的一個月。在堆棧的底部,“文本”視圖包含相應(yīng)月份的縮寫名稱,并帶有.footnote字體和一個靜態(tài)高度。提供靜態(tài)高度可確保條形底部對齊。

Adding a drop more detail

通過利用SwiftUI提供的功能,您已經(jīng)構(gòu)建了不錯的條形圖。 外部HStack均勻分布圖表的條形圖,這有助于提高可讀性。 條形圖的高度顯示一年中降雨的比例。

但是,該圖表并未明確指出確切的降水量。 將以下代碼添加到body中的Spacer后面以顯示該數(shù)據(jù):

Text("\(self.sumPrecipitation(month).stringToOneDecimal)")
  .font(.footnote)
  .rotationEffect(.degrees(-90))
  .offset(y: 35)
  .zIndex(1)

您已在每個欄中添加了文本視圖。 使用Double類型的擴(kuò)展方法,它顯示該月的總降水量,四舍五入到小數(shù)點后一位。 您可以在DoubleExtension.swift中找到它。

文本視圖的字體設(shè)置為與月份標(biāo)簽匹配,并逆時針旋轉(zhuǎn)文本90度,使其平行于條形流動。 然后將視圖向下偏移35個點,并將其放置在條形圖內(nèi)。

SwiftUI按照閱讀順序渲染視圖。 這意味著降雨量通常會位于柵欄后面,因為它占用的空間相同。

zIndex屬性設(shè)置為默認(rèn)零值以外的值會告訴SwiftUI覆蓋該默認(rèn)順序。 將其設(shè)置為1會告訴SwiftUI使用默認(rèn)的zIndex(包括欄)在視圖頂部繪制Text。

構(gòu)建并運行應(yīng)用程序以測試此新文本視圖。 然后去北卡羅來納州切諾基站(Cherokee, NC),選擇降水(precipitation tab)標(biāo)簽,看一個有趣的bug。 2018年7月幾乎沒有下雨,使得條形太短而無法包含其文字。

要修復(fù)此錯誤,您需要通過以下方式替換文本視圖中的偏移量,從而對偏移量進(jìn)行檢查:

.offset(y: self.sumPrecipitation(month) < 2.4 ? 0 : 35)

如果一個月的降水量少于2.4英寸,這將導(dǎo)致條形圖長36點,文本將保留在條形圖的頂部。

很好!您現(xiàn)在已經(jīng)成功地用條形圖替換了列表。此圖表使查看者可以查看所有原始列表數(shù)據(jù),并獲得更清晰的指南,以了解每個月的降水差異。

有了降水圖,您就可以創(chuàng)建降雪的水平條形圖了。


Building a Horizontal Bar Chart

煙山山脈(Smoky Mountains)是美國東部海拔最高的地區(qū)。但是,在那些海拔較高的地方,它們收到的積雪比您預(yù)期的要少。

雪的稀缺意味著像降水圖那樣按月分組的圖表將在年初和年底顯示變化而中間沒有任何變化。取而代之的是,您將使用水平條形圖繪制雪狀圖,該條形圖僅顯示一年中降雪的日子。

右鍵單擊Xco??de中的Charts組,然后選擇New File。選擇SwiftUI視圖,然后單擊Next。

將新視圖命名為SnowfallChart,并確保該組設(shè)置為Charts。單擊Create并打開新文件。

您需要通過將以下代碼添加到該結(jié)構(gòu)的頂部來再次將measurements傳遞到該視圖:

var measurements: [DayInfo]

您將使用Mount LeConte進(jìn)行預(yù)覽,因為它具有最多的降雪天和最大的降雪量。 將預(yù)覽更改為:

SnowfallChart(measurements: WeatherInformation()!.stations[2].measurements)

下面,將body更改為以下:

// 1
List(measurements.filter { $0.snowfall > 0.0 }) { measurement in
  HStack {
    // 2
    Text("\(measurement.dateString)")
      .frame(width: 100, alignment: .trailing)
    // 3
    Rectangle()
      .fill(Color.blue)
      .frame(width: CGFloat(measurement.snowfall * 10.0), height: 5.0)
    // 4
    Spacer()
    Text("\(measurement.snowfall.stringToOneDecimal)\"")
  }
}

以下是分步細(xì)分:

  • 1) 您創(chuàng)建一個List,其中包含每個降雪測量的條目。
  • 2) 您從下雪的日期開始每一行。默認(rèn)情況下,Text視圖的大小適合其包含的文本,行的寬度卻保持變化。應(yīng)用恒定的寬度可確保條形圖從每一行的相同水平位置開始。您將文本對齊到顯示降雪量的條形開頭旁邊的frame.trailing一側(cè)。
  • 3) 您將藍(lán)色矩形用作條形柱。由于這是水平而不是垂直的圖表,因此請為條形圖賦予恒定的高度,并根據(jù)積雪量設(shè)置寬度。由于視圖上的水平空間較少,因此與上一個圖表相比,使用更少的點表示每一英寸的降雪。
  • 4) 在Spacer()填充欄后的空白區(qū)域之后,您將顯示以英寸為單位的降雪量,再次舍入為十分之一英寸。

返回SnowfallTab.swift,使用對新視圖的調(diào)用替換List及其在body內(nèi)部的閉包:

SnowfallChart(measurements: station.measurements)

圖表現(xiàn)在顯示了一年的降雪量。 看看12月會有特別大的降雪。


Adding Grid Lines

由于降雪量差異很大,因此您可以通過添加網(wǎng)格線進(jìn)一步闡明圖表。 這些是在圖表或圖形上以恒定值放置的線。 這使觀察者更容易測量條的長度。

首先,將SnowfallChartRectangle()的代碼更改為:

ZStack {
  Rectangle()
    .fill(Color.blue)
    .frame(width: CGFloat(measurement.snowfall * 10.0), height: 5.0)
}

ZStack使您可以在同一空間中疊加多個子視圖。 在這種情況下,您將覆蓋條形圖和網(wǎng)格線。 您將以1英寸的間隔繪制網(wǎng)格線,以達(dá)到16英寸的最大尺寸。

Rectangle之后的ZStack中添加以下代碼:

ForEach(0..<17) { mark in
  Rectangle()
    .fill(Color.gray)
    .offset(x: CGFloat(mark) * 10.0)
    .frame(width: 1.0)
    .zIndex(1)
}

在這里,您為每個月的數(shù)據(jù)繪制了一個用灰色填充的矩形。 offset(x:y :)修飾符將每條線向右移動適當(dāng)?shù)牧?,然后設(shè)置一個寬度為1的frame,將矩形變成一條線。 再次設(shè)置RectanglezIndex,使其顯示在條形頂部。

請注意,通過不為frame設(shè)置高度,將擴(kuò)展為包含frame的視圖的高度。 如果查看當(dāng)前狀態(tài),您會發(fā)現(xiàn)有些不對勁。

網(wǎng)格線和條形不一定總是正好對齊。 默認(rèn)情況下,ZStack將其子視圖對齊在中心,但是您可以通過稍作修改來顯式指定子視圖的對齊方式。 將聲明ZStack的行更改為:

ZStack(alignment: .leading) {

現(xiàn)在條形和網(wǎng)格都正確顯示了

如果您使用許多網(wǎng)格線,則可以通過定期提供視覺提示來幫助查看器。 將對fill(_:style :)的調(diào)用更改為:

.fill(mark % 5 == 0 ? Color.black : Color.gray)

這使用Swift三元運算符,使用余數(shù)運算符將每五個指示器上黑。

現(xiàn)在,您已經(jīng)獲得了創(chuàng)建幾個基本圖表的經(jīng)驗,現(xiàn)在可以繼續(xù)創(chuàng)建用于溫度數(shù)據(jù)的更復(fù)雜的熱圖。


Creating a Heat Map

Charts組中創(chuàng)建一個新的SwiftUI視圖,并將新視圖命名為TemperatureChart。 打開TemperatureChart.swift并在結(jié)構(gòu)的開頭添加一個用于測量數(shù)據(jù)的變量。

var measurements: [DayInfo]

更改預(yù)覽以提供以下信息:

TemperatureChart(measurements: WeatherInformation()!.stations[1].measurements)

該圖表應(yīng)傳達(dá)全年每個站點的高溫和低溫。 您將需要使用一些輔助函數(shù)來實現(xiàn)這種可視化。 在Measurements變量之后,將以下方法添加到結(jié)構(gòu)中:

func degreeHeight(_ height: CGFloat, range: Int) -> CGFloat {
  height / CGFloat(range)
}

func dayWidth(_ width: CGFloat, count: Int) -> CGFloat {
  width / CGFloat(count)
}

該圖表將調(diào)整以適合視圖,而不是使用通過反復(fù)試驗確定的固定量。 對于圖表,這兩個函數(shù)計算在垂直方向上一度的溫度下獲取的點,在水平方向上計算一日的水平點。 這兩個函數(shù)都將維度的大小除以元素數(shù)。 結(jié)果給出了視圖中每個元素要使用的點數(shù)。

使用該結(jié)果,您可以確定給定日期和溫度的視圖中的點位置。 在前兩個函數(shù)之后添加以下兩個函數(shù):

func dayOffset(_ date: Date, dWidth: CGFloat) -> CGFloat {
  CGFloat(Calendar.current.ordinality(of: .day, in: .year, for: date)!) * dWidth
}

func tempOffset(_ temperature: Double, degreeHeight: CGFloat) -> CGFloat {
  CGFloat(temperature + 10) * degreeHeight
}

dayOffset(_:dWidth :)從傳入的日期計算一年中的日期,然后乘以dWidth參數(shù)。 這將計算水平位置以在視圖中繪制此測量值。

tempOffset(_:degreeHeight :)進(jìn)行類似的計算以獲取給定溫度的點。 由于溫度范圍是從-10度開始的,因此在相乘之前將溫度加10。 這會將范圍的底部移至零點。

現(xiàn)在將body更改為以下內(nèi)容:

// 1
GeometryReader { reader in
  ForEach(self.measurements) { measurement in
    // 2
    Path { p in
      // 3
      let dWidth = self.dayWidth(reader.size.width, count: 365)
      let dHeight = self.degreeHeight(reader.size.height, range: 110)
      // 4
      let dOffset = self.dayOffset(measurement.date, dWidth: dWidth)
      let lowOffset = self.tempOffset(measurement.low, degreeHeight: dHeight)
      let highOffset = self.tempOffset(measurement.high, degreeHeight: dHeight)
      // 5
      p.move(to: CGPoint(x: dOffset, y: reader.size.height - lowOffset))
      p.addLine(to: CGPoint(x: dOffset, y: reader.size.height - highOffset))
      // 6
    }.stroke()
  }
}

這里有很多東西,但是函數(shù)簡化了許多所需的計算。代碼的工作方式如下:

  • 1) 您創(chuàng)建GeometryReader來包裝圖表。 GeometryReader展開以填充包含它的視圖。該閉包還提供了GeometryProxy參數(shù),該參數(shù)包含有關(guān)視圖大小的信息。
    在先前的圖表中,您使用了恒定大小來生成看起來正確的東西。現(xiàn)在,您可以使用帶有早期函數(shù)的這些值來計算圖表的最佳值。
  • 2) 路徑Path提供了一種創(chuàng)建二維形狀的方法。在這里,您將創(chuàng)建一條垂直線,連接每天的低溫和高溫。路徑在SwiftUI中還具有一些獨特功能,您可以在其中定義變量,從而簡化路徑點的計算。
  • 3) 在這里,您可以使用這兩個函數(shù)使用GeometryReader中的尺寸,以1度溫度和1天為單位計算尺寸。您使用的溫度范圍是110,因為-10100華氏度涵蓋了今年數(shù)據(jù)中所有位置的溫度范圍。
  • 4) 現(xiàn)在,您可以使用這些功能確定日期的垂直點以及高溫和低溫。
  • 5) 這些線將路徑移至低溫點,并向高溫添加線。垂直視圖坐標(biāo)從視圖頂部開始,然后向下增加。當(dāng)您希望點從底部開始并向上移動時,可以從reader.size.height中減去垂直位置以獲得所需的位置。
  • 6) stroke()告訴SwiftUI以當(dāng)前系統(tǒng)顏色概述您創(chuàng)建的路徑。

打開TemperatureTab.swift并用它替換body以使用新視圖:

VStack {
  Text("Temperatures for 2018")
  TemperatureChart(measurements: station.measurements)
}.padding()

構(gòu)建并運行該應(yīng)用程序。 選擇任意位置,然后查看溫度tab。 請注意,該圖表可以適應(yīng)較小的應(yīng)用內(nèi)視圖和較大的預(yù)覽。

圖表的形狀很好地顯示了溫度的變化,但看起來有些平淡。 接下來,通過將圖表轉(zhuǎn)換為熱圖來使其變得更加有趣,該熱圖使用顏色更清楚地指示溫度。


Adding Heat Map Color

熱圖使用顏色以圖形方式表示值。 天氣圖通常使用多種顏色來表示溫度,低溫時從紫色和藍(lán)色陰影開始,低溫時向黃色,橙色和紅色陰影移動。 計算這些顏色和變化可能涉及一些復(fù)雜的數(shù)學(xué)運算,但此處不涉及。

SwiftUI中,您使用漸變表示顏色的過渡。 線性漸變可沿單個軸在兩種或多種顏色之間創(chuàng)建平滑的顏色過渡。 在measurements之后和輔助函數(shù)之前,在TemperatureChart.swift中添加以下內(nèi)容:

let tempGradient = Gradient(colors: [
  .purple,
  Color(red: 0, green: 0, blue: 139.0/255.0),
  .blue,
  Color(red: 30.0/255.0, green: 144.0/255.0, blue: 1.0),
  Color(red: 0, green: 191/255.0, blue: 1.0),
  Color(red: 135.0/255.0, green: 206.0/255.0, blue: 250.0/255.0),
  .green,
  .yellow,
  .orange,
  Color(red: 1.0, green: 140.0/255.0, blue: 0.0),
  .red,
  Color(red: 139.0/255.0, green: 0.0, blue: 0.0)
])

這定義了一個由12種顏色組成的漸變,以110度的溫度范圍以十度的增量均勻劃分,從紫色(-10度)到深紅色(100度)。

現(xiàn)在,在主體視圖的注釋六處將stroke()更改為:

.stroke(LinearGradient(
  gradient: self.tempGradient,
  startPoint: UnitPoint(x: 0.0, y: 1.0),
  endPoint: UnitPoint(x: 0.0, y: 0.0)))

您可以使用先前定義的漸變色將純色替換為線性漸變。 使用startPointendPoint參數(shù),您可以執(zhí)行幾乎神奇的事情。

這兩個參數(shù)都是UnitPoint,它們以點無關(guān)的方式定義空間,其中0.01.0標(biāo)記視圖的邊緣。 每個方向的零點位于原點:視圖的左上角。

您可以將漸變的起點設(shè)置在視圖的左下角,將端點設(shè)置在視圖的左上角。 由于它是線性漸變,因此漸變僅在垂直方向上變化。 每種顏色在每個點的整個視圖中水平延伸。

將其應(yīng)用于路徑意味著梯度僅顯示在stroked部分:低溫和高溫之間的范圍。


Adding Grid Lines and Labels

現(xiàn)在剩下的就是通過添加網(wǎng)格線(類似于您在條形圖中所做的操作)使觀看者的視覺看起來更容易一些。 在TemperatureChart.swift中的現(xiàn)有輔助函數(shù)之后添加以下輔助函數(shù):

func tempLabelOffset(_ line: Int, height: CGFloat) -> CGFloat {
  height - self.tempOffset(
    Double(line * 10),
    degreeHeight: self.degreeHeight(height, range: 110))
}

這會將網(wǎng)格劃分為十度的塊,并傳遞一個整數(shù),該整數(shù)表示該塊的起始溫度除以十,再加上視圖的總高度。 該函數(shù)計算適當(dāng)?shù)拇怪逼啤?/p>

body中的ForEach循環(huán)的右括號后添加以下代碼以繪制溫度網(wǎng)格線和標(biāo)簽:

// 1
ForEach(-1..<11) { line in
  // 2
  Group {
    Path { path in
      let y = self.tempLabelOffset(line, height: reader.size.height)
      path.move(to: CGPoint(x: 0, y: y))
      path.addLine(to: CGPoint(x: reader.size.width, y: y))
      // 4
    }.stroke(line == 0 ? Color.black : Color.gray)
    // 5
    if line >= 0 {
      Text("\(line * 10)°")
        .offset(x: 10, y: self.tempLabelOffset(line, height: reader.size.height))
    }
  }
}

這是新代碼的細(xì)分:

  • 1) 您可以在-110的范圍內(nèi)循環(huán),代表-10100華氏度的溫度。
  • 2) Group視圖在SwiftUI中起到了一些粘合作用,它結(jié)合了其子視圖,但不直接呈現(xiàn)元素。 在這里,它允許您在循環(huán)內(nèi)使用PathText()視圖。
  • 3) 您可以使用剛添加的函數(shù)來計算該線的溫度位置。 然后,在該垂直位置從視圖的左側(cè)到右側(cè)水平繪制線。
  • 4) 您將大多數(shù)網(wǎng)格線繪制為灰色。 為了使零度線突出,您可以將其顯示為黑色。
  • 5) 對于除第一條網(wǎng)格線以外的所有文本,您都將添加一個文本標(biāo)簽。 由于您不再位于Path閉包內(nèi),因此需要重新計算該線所代表的溫度的位置。 您再次使用tempLabelOffset(_:height :)函數(shù)來計算垂直位置。

完成溫度后,您需要數(shù)月的指示器和標(biāo)簽。 在現(xiàn)有的輔助函數(shù)之后添加以下兩個輔助函數(shù):

func offsetFirstOfMonth(_ month: Int, width: CGFloat) -> CGFloat {
  let dateFormatter = DateFormatter()
  dateFormatter.dateFormat = "M/d/yyyy"
  let foM = dateFormatter.date(from: "\(month)/1/2018")!
  let dayWidth = self.dayWidth(width, count: 365)
  return self.dayOffset(foM, dWidth: dayWidth)
}

func monthAbbreviationFromInt(_ month: Int) -> String {
  let ma = Calendar.current.shortMonthSymbols
  return ma[month - 1]
}

添加以下代碼,將前一個ForEach循環(huán)的右括號后的月份網(wǎng)格線和標(biāo)簽添加到body的末尾:

ForEach(1..<13) { month in
  Group {
    Path { path in
      let dOffset = self.offsetFirstOfMonth(month, width: reader.size.width)

      path.move(to: CGPoint(x: dOffset, y: reader.size.height))
      path.addLine(to: CGPoint(x: dOffset, y: 0))
    }.stroke(Color.gray)
    Text("\(self.monthAbbreviationFromInt(month))")
      .font(.subheadline)
      .offset(
        x: self.offsetFirstOfMonth(month, width: reader.size.width) +
          5 * self.dayWidth(reader.size.width, count: 365),
        y: reader.size.height - 25.0)
  }
}

這里沒有您以前沒有用過的東西。 與以前一樣,Group會包裝網(wǎng)格線和月份標(biāo)簽。 然后,在與每個月的第一天相對應(yīng)的偏移處繪制一條垂直線。

然后,您將獲得每個月的文本縮寫,并以相同的偏移量加上一點點的偏移量進(jìn)行繪制,以將文本移動到月中。 您將獲得每個月的文本縮寫,并以相同的偏移量加上一點點的偏移量進(jìn)行繪制,以將文本移至月中。

現(xiàn)在,您的圖表可以很好地概述每個位置的溫度范圍。 每條垂直線的頂部和底部均與顏色相結(jié)合,以清楚地顯示一年中不同時間的溫度。 網(wǎng)格線和標(biāo)簽可幫助觀看者確定一年中的某個時間或溫度范圍。

如果您想了解更多信息,那么對于所有UI內(nèi)容,Apple人機(jī)界面指南都是一個不錯的起點。 您將在《人機(jī)界面指南》中找到有關(guān)Charts的簡短部分。 為圖表選擇顏色時,還應(yīng)該閱讀Color準(zhǔn)則。

后記

本篇主要講述了基于SwiftUI構(gòu)建各種自定義圖表,感興趣的給個贊或者關(guān)注~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容