版本記錄
| 版本號 | 時(shí)間 |
|---|---|
| V1.0 | 2020.01.10 星期五 |
前言
今天翻閱蘋果的API文檔,發(fā)現(xiàn)多了一個(gè)框架SwiftUI,這里我們就一起來看一下這個(gè)框架。感興趣的看下面幾篇文章。
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)航的實(shí)現(xiàn)(一)
7. SwiftUI框架詳細(xì)解析 (七) —— 基于SwiftUI的導(dǎo)航的實(shí)現(xiàn)(二)
8. SwiftUI框架詳細(xì)解析 (八) —— 基于SwiftUI的動畫的實(shí)現(xiàn)(一)
9. SwiftUI框架詳細(xì)解析 (九) —— 基于SwiftUI的動畫的實(shí)現(xiàn)(二)
10. SwiftUI框架詳細(xì)解析 (十) —— 基于SwiftUI構(gòu)建各種自定義圖表(一)
源碼
1. Swift
首先看下工程組織結(jié)構(gòu)

下面就是源碼了
1. TemperatureTab.swift
import SwiftUI
struct TemperatureTab: View {
var station: WeatherStation
var body: some View {
VStack {
Text("Temperatures for 2018")
TemperatureChart(measurements: station.measurements)
}.padding()
}
}
struct TemperatureTab_Previews: PreviewProvider {
static var previews: some View {
TemperatureTab(station: WeatherInformation()!.stations[0])
}
}
2. SnowfallTab.swift
import SwiftUI
struct SnowfallTab: View {
var station: WeatherStation
var body: some View {
VStack {
Text("Snowfall for 2018")
SnowfallChart(snowfall: self.station.measurements.filter { $0.snowfall > 0.0 })
}
}
}
struct SnowfallTab_Previews: PreviewProvider {
static var previews: some View {
SnowfallTab(station: WeatherInformation()!.stations[2])
}
}
3. PrecipitationTab.swift
import SwiftUI
struct PrecipitationTab: View {
var station: WeatherStation
func monthFromName(_ name: String) -> Int {
let df = DateFormatter()
df.dateFormat = "LLLL"
if let date = df.date(from: name) {
return Calendar.current.component(.month, from: date)
}
return 0
}
var body: some View {
VStack {
Text("Precipitation for 2018")
PrecipitationChart(measurements: station.measurements)
}
}
}
struct PrecipitationTab_Previews: PreviewProvider {
static var previews: some View {
PrecipitationTab(station: WeatherInformation()!.stations[2])
}
}
4. PrecipitationChart.swift
import SwiftUI
struct PrecipitationChart: View {
var measurements: [DayInfo]
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]
}
var body: some View {
// 1
HStack {
// 2
ForEach(0..<12) { month in
// 3
VStack {
// 4
Spacer()
Text("\(self.sumPrecipitation(month).stringToOneDecimal)")
.font(.footnote)
.rotationEffect(.degrees(-90))
.offset(y: self.sumPrecipitation(month) < 2.4 ? 0 : 35)
.zIndex(1)
// 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)
}
}
}
}
}
struct PrecipitationChart_Previews: PreviewProvider {
static var previews: some View {
PrecipitationChart(measurements: WeatherInformation()!.stations[2].measurements)
}
}
5. SnowfallChart.swift
import SwiftUI
struct SnowfallChart: View {
var snowfall: [DayInfo]
var body: some View {
// 1
List(snowfall.filter { $0.snowfall > 0.0 }) { measurement in
HStack {
// 2
Text("\(measurement.dateString)")
.frame(width: 100, alignment: .trailing)
// 3
ZStack(alignment: .leading) {
ForEach(0..<17) { mark in
Rectangle()
.fill(mark % 5 == 0 ? Color.black : Color.gray)
.offset(x: CGFloat(mark) * 10.0)
.frame(width: 1.0)
.zIndex(1)
}
Rectangle()
.fill(Color.blue)
.frame(width: CGFloat(measurement.snowfall * 10.0), height: 5.0)
}
// 4
Spacer()
Text("\(measurement.snowfall.stringToOneDecimal)\"")
}
}
}
}
struct SnowfallChart_Previews: PreviewProvider {
static var previews: some View {
SnowfallChart(snowfall: WeatherInformation()!.stations[2].measurements)
}
}
6. TemperatureChart.swift
import SwiftUI
struct TemperatureChart: View {
var measurements: [DayInfo]
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)
])
func degreeHeight(_ height: CGFloat, range: Int) -> CGFloat {
height / CGFloat(range)
}
func dayWidth(_ width: CGFloat, count: Int) -> CGFloat {
width / CGFloat(count)
}
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
}
func tempLabelOffset(_ line: Int, height: CGFloat) -> CGFloat {
height - self.tempOffset(Double(line * 10),
degreeHeight: self.degreeHeight(height, range: 110))
}
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]
}
var body: some View {
// 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)
// 5
let lowOffset = self.tempOffset(measurement.low, degreeHeight: dHeight)
let highOffset = self.tempOffset(measurement.high, degreeHeight: dHeight)
// 6
p.move(to: .init(x: dOffset, y: reader.size.height - lowOffset))
p.addLine(to: .init(x: dOffset, y: reader.size.height - highOffset))
// 7
}.stroke(LinearGradient(
gradient: self.tempGradient,
startPoint: .init(x: 0.0, y: 1.0),
endPoint: .init(x: 0.0, y: 0.0)))
}
// 1
ForEach(-1..<11) { line in
// 2
Group {
Path { path in
// 3
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))
}
}
}
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)
}
}
}
}
}
struct TemperatureChart_Previews: PreviewProvider {
static var previews: some View {
TemperatureChart(measurements: WeatherInformation()!.stations[2].measurements)
}
}
7. WeatherInformation.swift
import Foundation
class WeatherInformation {
var stations: [WeatherStation]
init?() {
// Init empty array
stations = []
// Converter for date string
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "M/d/yyyy"
// Get CSV data
guard let csvData = getCsvAsString() else { return nil }
var currentStationId = ""
var currentStation: WeatherStation?
// Parse each line
csvData.enumerateLines { (line, _) in
let cols = line.components(separatedBy: ",")
if currentStationId != cols[0] {
if let newStation = currentStation {
if newStation.name != "NAME" {
self.stations.append(newStation)
}
}
currentStationId = cols[0]
let name = cols[1].replacingOccurrences(of: "\"", with: "").replacingOccurrences(of: ";", with: ",")
let lat = Double(cols[2]) ?? 0
let lng = Double(cols[3]) ?? 0
let alt = Int((Double(cols[4]) ?? 0) * 3.28084) // m to ft.
currentStation = WeatherStation(id: currentStationId, name: name, latitude: lat, longitude: lng, altitude: alt, measurements: [])
}
let date = dateFormatter.date(from: cols[5]) ?? dateFormatter.date(from: "1/1/2018")!
let precip = Double(cols[6]) ?? 0
let snow = Double(cols[7]) ?? 0
let high = Double(cols[8]) ?? 0
let low = Double(cols[9]) ?? 0
let newData = DayInfo(date: date, precipitation: precip, snowfall: snow, high: high, low: low)
currentStation?.measurements.append(newData)
}
// Add the last station read
if let newStation = currentStation {
self.stations.append(newStation)
}
}
func getCsvAsString() -> String? {
guard let fileURL = Bundle.main.url(forResource: "weather-data", withExtension: "csv") else { return nil }
do {
let csvData = try String(contentsOf: fileURL)
return csvData
} catch {
return nil
}
}
}
8. WeatherStation.swift
import Foundation
struct WeatherStation: Identifiable {
var id: String
var name: String
var latitude: Double
var longitude: Double
var altitude: Int
var measurements: [DayInfo]
func measurementsInMonth(_ month: Int) -> [DayInfo] {
return measurements.filter {
return Calendar.current.component(.month, from: $0.date) == month
}
}
var lowTemperatureForYear: Double {
measurements.min(by: { $0.low < $1.low })!.low
}
var highTemperatureForYear: Double {
measurements.max(by: { $0.high < $1.high })!.high
}
}
9. DayInfo.swift
import Foundation
struct DayInfo : Identifiable {
var date: Date
var precipitation: Double
var snowfall: Double
var high: Double
var low: Double
var id: Date {
return date
}
var dateString: String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "M/d/yyyy"
return dateFormatter.string(from: date)
}
}
10. DoubleExtension.swift
import Foundation
extension Double {
var stringToOneDecimal: String {
String(format: "%.1f", self)
}
var stringToTwoDecimals: String {
String(format: "%.2f", self)
}
var stringRounded: String {
String(format: "%.f", self.rounded())
}
var asLatitude: String {
let deg = floor(self)
let min = fabs(self.truncatingRemainder(dividingBy: 1) * 60.0).rounded()
if self > 0 {
return String(format: "%.f° %.f\" N", deg, min)
} else if self < 0 {
return String(format: "%.f° %.f\" S", -deg, min)
}
return "0°"
}
var asLongitude: String {
let deg = floor(self)
let min = fabs(self.truncatingRemainder(dividingBy: 1) * 60.0).rounded()
if self > 0 {
return String(format: "%.f° %.f\" E", deg, min)
} else if self < 0 {
return String(format: "%.f° %.f\" W", -deg, min)
}
return "0°"
}
}
11. AppDelegate.swift
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
// MARK: - UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
}
12. SceneDelegate.swift
import UIKit
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
// Use a UIHostingController as window root view controller
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: ContentView())
self.window = window
window.makeKeyAndVisible()
}
}
}
13. ContentView.swift
import SwiftUI
struct ContentView: View {
let stations = WeatherInformation()
var body: some View {
NavigationView {
VStack {
List(stations!.stations) { station in
NavigationLink(destination: StationInfo(station: station)) {
Text("\(station.name)")
}
}
Text("Source: https://www.ncdc.noaa.gov/cdo-web/datasets")
.italic()
}.navigationBarTitle(Text("Weather Stations"))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
14. StationInfo.swift
import SwiftUI
struct StationInfo: View {
var station: WeatherStation
var body: some View {
VStack {
StationHeader(station: self.station)
.padding()
TabView {
TemperatureTab(station: self.station)
.tabItem({
Image(systemName: "thermometer")
Text("Temperatures")
})
SnowfallTab(station: self.station)
.tabItem({
Image(systemName: "snow")
Text("Snowfall")
})
PrecipitationTab(station: self.station)
.tabItem({
Image(systemName: "cloud.rain")
Text("Precipitation")
})
}
}.navigationBarTitle(Text("\(station.name)"), displayMode: .inline)
}
}
struct StationInfo_Previews: PreviewProvider {
static var previews: some View {
StationInfo(station: WeatherInformation()!.stations[0])
}
}
15. StationHeader.swift
import SwiftUI
struct StationHeader: View {
var station: WeatherStation
var body: some View {
HStack {
VStack(alignment: .leading) {
Text("Latitude: \(station.latitude.asLatitude)")
Text("Longitude: \(station.longitude.asLongitude)")
Text("Elevation: \(station.altitude) ft.")
}
Spacer()
MapView(latitude: station.latitude, longitude: station.longitude)
.frame(width: 200, height: 200)
}
}
}
struct StationHeader_Previews: PreviewProvider {
static var previews: some View {
StationHeader(station: WeatherInformation()!.stations[1])
}
}
16. MapView.swift
import SwiftUI
import MapKit
struct MapView: UIViewRepresentable {
var latitude: Double
var longitude: Double
func makeUIView(context: Context) -> MKMapView {
MKMapView(frame: .zero)
}
func updateUIView(_ view: MKMapView, context: Context) {
let coordinate = CLLocationCoordinate2D(
latitude: self.latitude,
longitude: self.longitude)
let span = MKCoordinateSpan(latitudeDelta: 0.15, longitudeDelta: 0.15)
let region = MKCoordinateRegion(center: coordinate, span: span)
view.setRegion(region, animated: true)
view.mapType = .hybrid
view.isScrollEnabled = false
}
}
struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapView(latitude: 34.011286, longitude: -116.166868)
}
}
后記
本篇主要講述了基于SwiftUI構(gòu)建各種自定義圖表,感興趣的給個(gè)贊或者關(guān)注~~~
