Skip to content

Commit fefeeea

Browse files
committed
View create methods updated & themes and flexibility added
1 parent 124cf9a commit fefeeea

File tree

2 files changed

+221
-119
lines changed

2 files changed

+221
-119
lines changed

Sources/SwiftUIPercentChart/Helpers.swift

Lines changed: 155 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import SwiftUI
1010
@available(iOS 13.0, *)
1111
extension View {
1212
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
13-
clipShape( RoundedCorner(radius: radius, corners: corners) )
13+
clipShape(RoundedCorner(radius: radius, corners: corners))
1414
}
1515
}
1616

@@ -24,51 +24,168 @@ struct RoundedCorner: Shape {
2424
}
2525
}
2626

27-
@available(iOS 13.0, *)
28-
class ColorHelper{
29-
let defaultColors = [LinearGradient(colors: [.red,.orange], startPoint: .top, endPoint: .bottom),LinearGradient(colors: [.blue.opacity(0.8),.blue], startPoint: .top, endPoint: .bottom),LinearGradient(colors: [.purple.opacity(0.8),.purple], startPoint: .top, endPoint: .bottom),LinearGradient(colors: [.green.opacity(0.8),.green], startPoint: .top, endPoint: .bottom)]
27+
public enum Themes: String, CaseIterable {
28+
case light = "Light"
29+
case dark = "Dark"
30+
case love = "Love"
31+
case ocean = "Ocean"
32+
case natural = "Natural"
33+
case colorful = "Colorful"
34+
case sunset = "Sunset"
35+
case neon = "Neon"
3036
}
3137

3238
@available(iOS 13.0, *)
33-
struct TouchGestureViewModifier: ViewModifier {
34-
let touchBegan: () -> Void
35-
let touchEnd: (Bool) -> Void
39+
public struct ColorThemes {
40+
static let light: [Color] = [
41+
.init(hex: "fec5bb"),
42+
.init(hex: "fcd5ce"),
43+
.init(hex: "fae1dd"),
44+
.init(hex: "f8edeb"),
45+
.init(hex: "e8e8e4"),
46+
.init(hex: "d8e2dc"),
47+
.init(hex: "ece4db"),
48+
.init(hex: "ffd7ba"),
49+
.init(hex: "fec89a")
50+
]
51+
52+
static let dark: [Color] = [
53+
.init(hex: "181818"),
54+
.init(hex: "282828"),
55+
.init(hex: "404048"),
56+
.init(hex: "505860"),
57+
.init(hex: "66707a"),
58+
.init(hex: "381820"),
59+
.init(hex: "501820"),
60+
.init(hex: "502028")
61+
]
62+
63+
static let love: [Color] = [
64+
.init(hex: "fff0f3"),
65+
.init(hex: "ffccd5"),
66+
.init(hex: "ffb3c1"),
67+
.init(hex: "ff8fa3"),
68+
.init(hex: "ff758f"),
69+
.init(hex: "ff4d6d"),
70+
.init(hex: "a4133c"),
71+
.init(hex: "800f2f"),
72+
.init(hex: "590d22")
73+
]
74+
75+
static let ocean: [Color] = [
76+
.init(hex: "a9d6e5"),
77+
.init(hex: "89c2d9"),
78+
.init(hex: "61a5c2"),
79+
.init(hex: "468faf"),
80+
.init(hex: "2c7da0"),
81+
.init(hex: "2a6f97"),
82+
.init(hex: "014f86"),
83+
.init(hex: "01497c"),
84+
.init(hex: "013a63"),
85+
.init(hex: "012a4a")
86+
]
3687

37-
@State private var hasBegun = false
38-
@State private var hasEnded = false
88+
static let natural: [Color] = [
89+
.init(hex: "d8f3dc"),
90+
.init(hex: "b7e4c7"),
91+
.init(hex: "95d5b2"),
92+
.init(hex: "74c69d"),
93+
.init(hex: "52b788"),
94+
.init(hex: "40916c"),
95+
.init(hex: "2d6a4f"),
96+
.init(hex: "1b4332"),
97+
.init(hex: "081c15")
98+
]
3999

40-
private func isTooFar(_ translation: CGSize) -> Bool {
41-
let distance = sqrt(pow(translation.width, 2) + pow(translation.height, 2))
42-
return distance >= 20.0
100+
static let colorful: [Color] = [
101+
.init(hex: "ffadad"),
102+
.init(hex: "ffd6a5"),
103+
.init(hex: "fdffb6"),
104+
.init(hex: "caffbf"),
105+
.init(hex: "9bf6ff"),
106+
.init(hex: "a0c4ff"),
107+
.init(hex: "bdb2ff"),
108+
.init(hex: "ffc6ff"),
109+
.init(hex: "fffffc")
110+
]
111+
112+
static let sunset: [Color] = [
113+
.init(hex: "ff7b00"),
114+
.init(hex: "ff8800"),
115+
.init(hex: "ff9500"),
116+
.init(hex: "ffa200"),
117+
.init(hex: "ffaa00"),
118+
.init(hex: "ffb700"),
119+
.init(hex: "ffd000"),
120+
.init(hex: "ffea00"),
121+
]
122+
123+
static let neon: [Color] = [
124+
.init(hex: "f72585"),
125+
.init(hex: "b5179e"),
126+
.init(hex: "7209b7"),
127+
.init(hex: "560bad"),
128+
.init(hex: "3a0ca3"),
129+
.init(hex: "3f37c9"),
130+
.init(hex: "4361ee"),
131+
.init(hex: "4895ef"),
132+
.init(hex: "4cc9f0")
133+
]
134+
}
135+
136+
@available(iOS 13.0, *)
137+
public extension Color {
138+
static func getColor(_ colorTheme: [Color], _ index: Int) -> Color {
139+
if colorTheme.indices.contains(index) {
140+
return colorTheme[index]
141+
} else {
142+
return colorTheme[index % colorTheme.count]
143+
}
43144
}
44145

45-
func body(content: Content) -> some View {
46-
content.gesture(DragGesture(minimumDistance: 0)
47-
.onChanged { event in
48-
guard !self.hasEnded else { return }
49-
50-
if self.hasBegun == false {
51-
self.hasBegun = true
52-
self.touchBegan()
53-
} else if self.isTooFar(event.translation) {
54-
self.hasEnded = true
55-
self.touchEnd(false)
56-
}
146+
static func themeColor(by index: Int, with theme: Themes) -> Color {
147+
switch theme {
148+
case .light:
149+
return getColor(ColorThemes.light, index)
150+
case .dark:
151+
return getColor(ColorThemes.dark, index)
152+
case .love:
153+
return getColor(ColorThemes.love, index)
154+
case .ocean:
155+
return getColor(ColorThemes.ocean, index)
156+
case .natural:
157+
return getColor(ColorThemes.natural, index)
158+
case .colorful:
159+
return getColor(ColorThemes.colorful, index)
160+
case .sunset:
161+
return getColor(ColorThemes.sunset, index)
162+
case .neon:
163+
return getColor(ColorThemes.neon, index)
57164
}
58-
.onEnded { event in
59-
if !self.hasEnded {
60-
let success = !self.isTooFar(event.translation)
61-
self.touchEnd(success)
62-
}
63-
self.hasBegun = false
64-
self.hasEnded = false
65-
})
66165
}
67-
}
68-
@available(iOS 13.0, *)
69-
extension View {
70-
func onTouchGesture(touchBegan: @escaping () -> Void,
71-
touchEnd: @escaping (Bool) -> Void) -> some View {
72-
modifier(TouchGestureViewModifier(touchBegan: touchBegan, touchEnd: touchEnd))
166+
167+
init(hex: String) {
168+
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
169+
var int: UInt64 = 0
170+
Scanner(string: hex).scanHexInt64(&int)
171+
let a, r, g, b: UInt64
172+
switch hex.count {
173+
case 3: // RGB (12-bit)
174+
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
175+
case 6: // RGB (24-bit)
176+
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
177+
case 8: // ARGB (32-bit)
178+
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
179+
default:
180+
(a, r, g, b) = (1, 1, 1, 0)
181+
}
182+
183+
self.init(
184+
.sRGB,
185+
red: Double(r) / 255,
186+
green: Double(g) / 255,
187+
blue: Double(b) / 255,
188+
opacity: Double(a) / 255
189+
)
73190
}
74191
}
Lines changed: 66 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,89 @@
11
import SwiftUI
22

3-
43
@available(iOS 13.0, *)
54
/// Creates a horizontal chart that calculates percentile slices
6-
public struct SwiftUIPercentChart : View {
7-
private var data: Array<Double>
8-
private var percentValue : Double
9-
private var applyPercent : Double{
10-
if self.data.reduce(0, +) > percentValue{
11-
return self.data.reduce(0, +)
12-
}else{
13-
return self.percentValue
14-
}
15-
}
16-
private var colorData : Array<LinearGradient>
17-
private var applyColor : Array<LinearGradient>{
18-
if self.colorData.isEmpty{
19-
return ColorHelper().defaultColors
20-
}else{
21-
return colorData
22-
}
23-
}
24-
@State var valueScale = CGFloat(1)
25-
private var screenSize = UIScreen.main.bounds
26-
private var screenRatio : Double
27-
private var backgroundColor : Color
28-
// Required variables
29-
public init(data:Array<Double> , percentValue : Double , colorData : Array<LinearGradient> , screenRatio : Double, backgroundColor : Color){
30-
// Data of the graphics
5+
public struct SwiftUIPercentChart: View {
6+
private var data: [Double]
7+
private var percentValue: Double
8+
private var theme: Themes
9+
10+
init(data: [Double] = [], percentValue: Double? = nil, theme: Themes = .dark) {
3111
self.data = data
32-
// The value that determines the percentage of the bar
33-
self.percentValue = percentValue
34-
// The value set the chart colors
35-
self.colorData = colorData
36-
// The value for setting the width of the chart according to the device screen (among to 0 - 1)
37-
self.screenRatio = screenRatio
38-
// The value set the backgorund bar color
39-
self.backgroundColor = backgroundColor
12+
self.percentValue = percentValue ?? 0 < Double(data.reduce(0, +)) ? Double(data.reduce(0, +)) : percentValue ?? Double(data.reduce(0, +))
13+
self.theme = theme
4014
}
41-
public var body: some View{
42-
ZStack(alignment:.leading){
43-
Rectangle()
44-
.foregroundColor(self.backgroundColor.opacity(0.3))
45-
.frame(width: screenSize.width * self.screenRatio, height: 20)
46-
.cornerRadius(16)
47-
HStack(spacing:0){
48-
if #available(iOS 15.0, *){
49-
if self.data.count > 0 {
50-
if self.data.count == 1{
15+
16+
public var body: some View {
17+
GeometryReader { proxy in
18+
ZStack {
19+
RoundedRectangle(cornerRadius: 16)
20+
.foregroundColor(.gray.opacity(0.2))
21+
HStack(spacing: 0) {
22+
ForEach(Array(zip(data.indices, data)), id: \.0) { index, value in
23+
if index == 0 {
24+
Rectangle()
25+
.cornerRadius(cellRadius(by: index), corners: [.topLeft, .bottomLeft])
26+
.foregroundColor(.themeColor(by: index, with: theme))
27+
.frame(width: cellWidth(by: index, proxy.size.width))
28+
} else if index == data.count - 1 {
5129
Rectangle()
52-
.frame(width: calWidth(value: self.data[0]) , height: 20)
53-
.cornerRadius(16)
54-
.foregroundStyle(self.applyColor[0])
55-
56-
}else{
30+
.cornerRadius(cellRadius(by: index), corners: [.topRight, .bottomRight])
31+
.foregroundColor(.themeColor(by: index, with: theme))
32+
.frame(width: cellWidth(by: index, proxy.size.width))
33+
} else {
5734
Rectangle()
58-
.frame(width: calWidth(value: self.data[0]), height: 20)
59-
.cornerRadius(16,corners:[.topLeft,.bottomLeft])
60-
.foregroundStyle(self.applyColor[0])
61-
ForEach(0..<self.data.count) {
62-
a in
63-
if a == 0{
64-
65-
}
66-
else if a == self.data.count - 1 {
67-
Rectangle()
68-
.frame(width: calWidth(value: self.data[a]), height: 20)
69-
.cornerRadius(16,corners:[.topRight,.bottomRight])
70-
.foregroundStyle(self.applyColor[a % self.applyColor.count])
71-
}
72-
else{
73-
Rectangle()
74-
.frame(width: calWidth(value: self.data[a]), height: 20)
75-
.foregroundStyle(self.applyColor[a % self.applyColor.count])
76-
77-
}
78-
}
35+
.foregroundColor(.themeColor(by: index, with: theme))
36+
.frame(width: cellWidth(by: index, proxy.size.width))
7937
}
8038
}
81-
}else
82-
{
83-
Text("Make the app version ios 15")
39+
40+
if percentValue > data.reduce(0, +) {
41+
Spacer()
42+
}
8443
}
85-
}.scaleEffect(self.valueScale)
86-
.onTouchGesture(
87-
touchBegan: { withAnimation { self.valueScale = 1.2 } },
88-
touchEnd: { _ in withAnimation { self.valueScale = 1.0 } }
89-
)
44+
}
9045
}
9146
}
92-
func calWidth(value : Double) -> Double{
93-
return (value * (screenSize.width * self.screenRatio))/applyPercent
47+
48+
private func cellCapacity(by index: Int) -> Double {
49+
return (data[index] * 100) / percentValue
50+
}
51+
52+
private func cellWidth(by index: Int, _ width: CGFloat) -> Double {
53+
return (cellCapacity(by: index) * width) / 100
54+
}
55+
56+
private func cellRadius(by index: Int) -> Double {
57+
return 500 / cellCapacity(by: index)
9458
}
9559
}
9660

9761
#if DEBUG
9862
struct SwiftUIPercentChart_Previews : PreviewProvider {
9963
@available(iOS 13.0, *)
10064
static var previews: some View {
101-
SwiftUIPercentChart(data: [10,20,30], percentValue: 100 , colorData: [] ,screenRatio: 0.8,backgroundColor: .primary)
65+
let screenSize = UIScreen.main.bounds
66+
67+
VStack {
68+
ForEach(Themes.allCases, id: \.self) { theme in
69+
if #available(iOS 15.0, *) {
70+
VStack(alignment: .leading) {
71+
SwiftUIPercentChart(data: [50, 40, 30, 20, 30, 50, 30, 10, 20, 50], percentValue: 350, theme: theme)
72+
.frame(width: screenSize.width * 0.7, height: 10)
73+
Text(theme.rawValue)
74+
.bold()
75+
}.padding()
76+
.background(.ultraThinMaterial)
77+
.cornerRadius(10)
78+
} else {
79+
VStack(alignment: .leading) {
80+
SwiftUIPercentChart(data: [50, 40, 30, 20, 30, 50, 30, 10, 20, 50], percentValue: 350, theme: theme)
81+
.frame(width: screenSize.width * 0.7, height: 10)
82+
Text(theme.rawValue)
83+
}
84+
}
85+
}
86+
}
10287
}
10388
}
10489
#endif

0 commit comments

Comments
 (0)