Skip to content

Instantly share code, notes, and snippets.

@gpdawson
Created May 5, 2021 03:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gpdawson/1fd61f0021c67ec6d6b42f5bb9c19cae to your computer and use it in GitHub Desktop.
Save gpdawson/1fd61f0021c67ec6d6b42f5bb9c19cae to your computer and use it in GitHub Desktop.
Similar to watchOS gauges but for iOS
//
// GaugeView.swift
// WidgetTest
//
// Created by Graham Dawson on 26/9/20.
//
import SwiftUI
struct GaugeView: View {
var gradientColors: [Color]
var minLabel: String = ""
var maxLabel: String = ""
var typeLabel: String = "RH"
var valueLabel: String = "71%"
var valueRatio: CGFloat = 0.71
var valueColor: Color = Color.primary
let trimRatio: CGFloat = 0.75
let ringWidthRatio: CGFloat = 0.18
var isProgressGauge = false
var ringColor: Color = Color.gray
var ringColorComplete = Color.black
var backgroundColor = userBackgroundColor // For dial progress ring
@ViewBuilder
var body: some View {
GeometryReader { geometry in
let center = CGPoint(x: geometry.size.width/2.0, y:geometry.size.height/2.0)
let extent = min(geometry.size.width, geometry.size.height) / 2.0
let ringWidth: CGFloat = extent * ringWidthRatio // 30
let radius = CGFloat(extent - ringWidth/2)
let valuePosition = positionOfValueRatio(center: center, radius: radius)
let trimFrom: CGFloat = (1.0 - trimRatio) * 0.5
let trimTo: CGFloat = 1.0 - trimFrom
ZStack {
if (isProgressGauge) {
Circle()
.trim(from: trimFrom, to: trimTo)
.stroke(
ringColor,
style: StrokeStyle(lineWidth: ringWidth, lineCap: .round)
)
.rotationEffect(.degrees(90))
.padding(ringWidth / 2)
let trimToB = trimTo - (1-valueRatio) * trimRatio
Circle()
.trim(from: trimFrom, to: trimToB)
.stroke(
ringColorComplete,
style: StrokeStyle(lineWidth: ringWidth, lineCap: .round)
)
.rotationEffect(.degrees(90))
.padding(ringWidth / 2)
} else {
Circle()
.trim(from: trimFrom, to: trimTo)
.stroke(
AngularGradient(
gradient: Gradient(stops: gradientStopsFromColors(colors: gradientColors)),
center: .center,
startAngle: .degrees(0),
endAngle: .degrees(360)
),
style: StrokeStyle(lineWidth: ringWidth, lineCap: .round)
)
.rotationEffect(.degrees(90))
.padding(ringWidth / 2)
}
if (minLabel.count > 0) {
Text(minLabel)
.position(x: center.x - extent * 0.35, y: center.y + extent * 0.84)
.font(Font.system(size: extent * 0.25, weight: .bold, design: .rounded))
.foregroundColor(gradientColors.first)
}
if (maxLabel.count > 0) {
Text(maxLabel)
.position(x: center.x + extent * 0.35, y: center.y + extent * 0.84)
.font(Font.system(size: extent * 0.25, weight: .bold, design: .rounded))
.foregroundColor(gradientColors.last)
}
if (typeLabel.count > 0) {
Text(typeLabel)
.padding(EdgeInsets(top: 0, leading: extent * 0.3, bottom: 0, trailing: extent * 0.3))
.position(x: center.x, y: center.y + extent * 0.72)
.font(Font.system(size: extent * 0.3, weight: .bold, design: .default))
.lineLimit(1)
.minimumScaleFactor(0.1)
}
if (valueLabel.count > 0) {
Text(valueLabel)
.foregroundColor(valueColor)
.frame(maxWidth: extent * 1.3)
.font(Font.system(size: extent * 0.6, weight: .bold, design: .rounded))
.lineLimit(1)
.minimumScaleFactor(0.01)
}
}
if (!isProgressGauge) {
//let color = Color(UIColor.systemBackground)
GaugeValueHighlightView(color:backgroundColor, radiusInner: ringWidth, radiusOuter: ringWidth * 1.8)
.position(x: valuePosition.x, y: valuePosition.y)
}
}
}
func positionOfValueRatio(center: CGPoint, radius: CGFloat) -> CGPoint {
let valueAngleDeg = -valueRatio * 360.0 * trimRatio - 45.0
let valueAngleRad = Double(valueAngleDeg) * Double.pi / 180.0
let xCoord = CGFloat(sin(valueAngleRad)) * radius
let yCoord = CGFloat(cos(valueAngleRad)) * radius
let valuePoint: CGPoint = CGPoint(x: center.x + xCoord, y: center.y + yCoord)
return valuePoint
}
func gradientStopsFromColors(colors: [Color]) -> [Gradient.Stop] {
var stops: [Gradient.Stop] = []
stops.append(Gradient.Stop(color: colors[0], location: 0.0))
for (index, color) in colors.enumerated() {
var loc : CGFloat = CGFloat(index) / CGFloat(colors.count-1)
// Compress into narrower range about 0.5
loc = (loc - 0.5) * trimRatio + 0.5
stops.append(Gradient.Stop(color: color, location: loc))
}
stops.append(Gradient.Stop(color: colors[colors.count-1], location: 1.0))
return stops
}
}
struct GaugeValueHighlightView: View {
// By default color is system background color - if background is non-standard then set it on this inut param
var color: Color = Color(UIColor.systemBackground)
var radiusInner: CGFloat = 30
var radiusOuter: CGFloat = 50
var body: some View {
GeometryReader { geometry in
let radiusSpan: CGFloat = (radiusOuter - radiusInner) / 2.0;
let radiusMid: CGFloat = (radiusInner + radiusOuter) / 2.0
Circle()
.stroke(
style: StrokeStyle(lineWidth: radiusSpan, lineCap: .round)
)
.frame(width: radiusMid, height: radiusMid, alignment: .center)
.position(x: geometry.size.width/2, y: geometry.size.height/2)
.foregroundColor(color)
}
}
}
@gpdawson
Copy link
Author

gpdawson commented May 5, 2021

Sample previews of gauges using above code

struct GaugeView_Previews: PreviewProvider {
    static var previews: some View {
        let colorsRH: [Color] = [Color.gray, Color.init(UIColor.cyan), Color.blue]
        let colorsUV: [Color] = [Color.gray, Color.green, Color.yellow, Color.orange, Color.red]
        VStack.init(alignment: .center, spacing: nil, content: {
            
            HStack {
            GaugeView(gradientColors: colorsUV, minLabel: "10", maxLabel: "19", typeLabel: "",
                      valueLabel: "15.6°", valueRatio: 0.2, valueColor: Color.black)            
            }
            GaugeView(gradientColors: colorsRH)
            
            HStack {
            GaugeView(gradientColors: [Color.blue, Color.blue], typeLabel: "💧",
                      valueLabel: "10%", valueRatio: 0.1, valueColor: Color.black,
                      isProgressGauge: true,
                      ringColor: Color.init(.sRGB, red: 0.0, green: 0.0, blue: 1.0, opacity: 0.25),
                      ringColorComplete: Color.init(.sRGB, red: 0.0, green: 0.0, blue: 1.0, opacity: 1.0)
            )
            
            GaugeView(gradientColors: colorsUV, typeLabel: "UV",
                      valueLabel: "1.2", valueRatio: 0.2, valueColor: Color.green)
            }
        })
        .padding(16)
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment