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

版本記錄

版本號(hào) 時(shí)間
V1.0 2020.01.10 星期五

前言

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

開(kāi)始

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

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

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

Swift 5, iOS 13, Xcode 11

下面就是正文了。

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

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

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

1. Why Use a Chart?

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

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

Charts vs. Graphs

人們經(jīng)常互換使用術(shù)語(yǔ)“圖表”和“圖形”,但他們不是同一回事。

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

圖表應(yīng)該講一個(gè)故事。 它使觀看者更容易理解和解釋?zhuān)瑥亩玫乩斫鈹?shù)據(jù)。 簡(jiǎn)而言之,所有圖表都是圖形,但并非所有圖形都是圖表。

打開(kāi)起始工程并運(yùn)行

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

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

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

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

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


Refactoring for Charts

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

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

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

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

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

var measurements: [DayInfo]

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

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

首先,您將在此新視圖中復(fù)制現(xiàn)有函數(shù)。 首先,在measurements后添加兩個(gè)幫助函數(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ù)(從零開(kāi)始而不是從1開(kāi)始)。 reduce計(jì)算這些測(cè)量的降水值之和。

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

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

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

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

PrecipitationChart(measurements: station.measurements)

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


Raising the SwiftUI Bar

SwiftUI包含多個(gè)形狀視圖,其中包括一個(gè)矩形形狀,可以很好地構(gòu)建條形圖。 打開(kāi)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) 您可以對(duì)圖表中的每個(gè)條使用VStack來(lái)垂直堆疊元素。
  • 4) 您可以在堆棧中的其他視圖上指定大小,然后該Spacer將展開(kāi)以填充剩余的空間。實(shí)際上,它告訴SwiftUI將空白放在VStack的頂部。
  • 5) 您使用Rectangle SwiftUI shape原語(yǔ)。它創(chuàng)建一個(gè)與視圖包含的frame對(duì)齊的矩形,并用綠色填充它。您指定寬度和高度恒定的frame作為月份的總降水量(以英寸為單位)乘以15。一個(gè)月有1英寸的降雨會(huì)形成一個(gè)20點(diǎn)寬,15點(diǎn)長(zhǎng)的矩形。一個(gè)有七英寸大雨的月份顯示為一個(gè)矩形,寬20點(diǎn),長(zhǎng)105點(diǎn)。
  • 6) 您還為每個(gè)條提供標(biāo)簽,在這種情況下為一年的一個(gè)月。在堆棧的底部,“文本”視圖包含相應(yīng)月份的縮寫(xiě)名稱(chēng),并帶有.footnote字體和一個(gè)靜態(tài)高度。提供靜態(tài)高度可確保條形底部對(duì)齊。

Adding a drop more detail

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

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

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

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

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

SwiftUI按照閱讀順序渲染視圖。 這意味著降雨量通常會(huì)位于柵欄后面,因?yàn)樗加玫目臻g相同。

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

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

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

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

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

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

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


Building a Horizontal Bar Chart

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

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

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

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

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

var measurements: [DayInfo]

您將使用Mount LeConte進(jìn)行預(yù)覽,因?yàn)樗哂凶疃嗟慕笛┨旌妥畲蟮慕笛┝俊?將預(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)建一個(gè)List,其中包含每個(gè)降雪測(cè)量的條目。
  • 2) 您從下雪的日期開(kāi)始每一行。默認(rèn)情況下,Text視圖的大小適合其包含的文本,行的寬度卻保持變化。應(yīng)用恒定的寬度可確保條形圖從每一行的相同水平位置開(kāi)始。您將文本對(duì)齊到顯示降雪量的條形開(kāi)頭旁邊的frame.trailing一側(cè)。
  • 3) 您將藍(lán)色矩形用作條形柱。由于這是水平而不是垂直的圖表,因此請(qǐng)為條形圖賦予恒定的高度,并根據(jù)積雪量設(shè)置寬度。由于視圖上的水平空間較少,因此與上一個(gè)圖表相比,使用更少的點(diǎn)表示每一英寸的降雪。
  • 4) 在Spacer()填充欄后的空白區(qū)域之后,您將顯示以英寸為單位的降雪量,再次舍入為十分之一英寸。

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

SnowfallChart(measurements: station.measurements)

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


Adding Grid Lines

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

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

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

ZStack使您可以在同一空間中疊加多個(gè)子視圖。 在這種情況下,您將覆蓋條形圖和網(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)
}

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

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

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

ZStack(alignment: .leading) {

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

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

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

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

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


Creating a Heat Map

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

var measurements: [DayInfo]

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

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

該圖表應(yīng)傳達(dá)全年每個(gè)站點(diǎn)的高溫和低溫。 您將需要使用一些輔助函數(shù)來(lái)實(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)整以適合視圖,而不是使用通過(guò)反復(fù)試驗(yàn)確定的固定量。 對(duì)于圖表,這兩個(gè)函數(shù)計(jì)算在垂直方向上一度的溫度下獲取的點(diǎn),在水平方向上計(jì)算一日的水平點(diǎn)。 這兩個(gè)函數(shù)都將維度的大小除以元素?cái)?shù)。 結(jié)果給出了視圖中每個(gè)元素要使用的點(diǎn)數(shù)。

使用該結(jié)果,您可以確定給定日期和溫度的視圖中的點(diǎn)位置。 在前兩個(gè)函數(shù)之后添加以下兩個(gè)函數(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 :)從傳入的日期計(jì)算一年中的日期,然后乘以dWidth參數(shù)。 這將計(jì)算水平位置以在視圖中繪制此測(cè)量值。

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

現(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ù)簡(jiǎn)化了許多所需的計(jì)算。代碼的工作方式如下:

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

打開(kāi)TemperatureTab.swift并用它替換body以使用新視圖:

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

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

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


Adding Heat Map Color

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

SwiftUI中,您使用漸變表示顏色的過(guò)渡。 線性漸變可沿單個(gè)軸在兩種或多種顏色之間創(chuàng)建平滑的顏色過(guò)渡。 在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)
])

這定義了一個(gè)由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í)行幾乎神奇的事情。

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

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

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


Adding Grid Lines and Labels

現(xiàn)在剩下的就是通過(guò)添加網(wǎng)格線(類(lèi)似于您在條形圖中所做的操作)使觀看者的視覺(jué)看起來(lái)更容易一些。 在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))
}

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

body中的ForEach循環(huán)的右括號(hào)后添加以下代碼以繪制溫度網(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ù)來(lái)計(jì)算該線的溫度位置。 然后,在該垂直位置從視圖的左側(cè)到右側(cè)水平繪制線。
  • 4) 您將大多數(shù)網(wǎng)格線繪制為灰色。 為了使零度線突出,您可以將其顯示為黑色。
  • 5) 對(duì)于除第一條網(wǎng)格線以外的所有文本,您都將添加一個(gè)文本標(biāo)簽。 由于您不再位于Path閉包內(nèi),因此需要重新計(jì)算該線所代表的溫度的位置。 您再次使用tempLabelOffset(_:height :)函數(shù)來(lái)計(jì)算垂直位置。

完成溫度后,您需要數(shù)月的指示器和標(biāo)簽。 在現(xiàn)有的輔助函數(shù)之后添加以下兩個(gè)輔助函數(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]
}

添加以下代碼,將前一個(gè)ForEach循環(huán)的右括號(hào)后的月份網(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)
  }
}

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

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

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

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

后記

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容