From dfacf82445ea87da2cc28ad5d261e4e069d697df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=92=E1=85=A7=E1=86=AB=E1=84=8C=E1=85=B5=E1=86=AB?= Date: Fri, 24 Oct 2025 20:51:43 +0900 Subject: [PATCH 1/2] =?UTF-8?q?refactor:=20LineChart=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=20Component=EB=A1=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AIProject/iCOWidget/CoinWidget.swift | 55 +------------- AIProject/iCo.xcodeproj/project.pbxproj | 1 + .../iCo/Features/Base/LineChartView.swift | 74 +++++++++++++++++++ 3 files changed, 77 insertions(+), 53 deletions(-) create mode 100644 AIProject/iCo/Features/Base/LineChartView.swift diff --git a/AIProject/iCOWidget/CoinWidget.swift b/AIProject/iCOWidget/CoinWidget.swift index 27c0eb43..364d8d7e 100644 --- a/AIProject/iCOWidget/CoinWidget.swift +++ b/AIProject/iCOWidget/CoinWidget.swift @@ -237,8 +237,8 @@ struct CoinCardView: View { .frame(maxWidth: .infinity, alignment: .trailing) } - - SparklineView(prices: coin.history) + + LineChartView(values: coin.history) .padding(8) HStack { @@ -255,54 +255,3 @@ struct CoinCardView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) } } - -struct SparklineView: View { - let prices: [Double] - - private var normalized: [CGFloat] { - guard prices.count > 1 else { return [] } - guard let min = prices.min(), let max = prices.max(), min != max else { - return Array(repeating: 0.5, count: prices.count) - } - return prices.map { CGFloat(($0 - min) / (max - min)) } - } - - var body: some View { - GeometryReader { geo in - if normalized.isEmpty { - Path { path in - let midY = geo.size.height / 2 - path.move(to: CGPoint(x: 0, y: midY)) - path.addLine(to: CGPoint(x: geo.size.width, y: midY)) - } - .stroke(Color.gray.opacity(0.4), lineWidth: 1) - } else { - ZStack { - Path { path in - for (index, value) in normalized.enumerated() { - let x = geo.size.width * CGFloat(index) / CGFloat(normalized.count - 1) - let y = geo.size.height * (1 - value) - if index == 0 { - path.move(to: CGPoint(x: x, y: y)) - } else { - path.addLine(to: CGPoint(x: x, y: y)) - } - } - } - .stroke(Color.iCoAccent, style: StrokeStyle(lineWidth: 2, lineJoin: .round)) - - if let last = normalized.last { - let x = geo.size.width - let y = geo.size.height * (1 - last) - - Circle() - .fill(Color.blue) - .frame(width: 6, height: 6) - .position(x: x, y: y) - } - } - } - } - //.frame(height: 36) - } -} diff --git a/AIProject/iCo.xcodeproj/project.pbxproj b/AIProject/iCo.xcodeproj/project.pbxproj index 70d478a7..0fe7f714 100644 --- a/AIProject/iCo.xcodeproj/project.pbxproj +++ b/AIProject/iCo.xcodeproj/project.pbxproj @@ -97,6 +97,7 @@ App/Resource/Assets.xcassets, Core/Local/UserDefaults/AppStorageKey.swift, "Core/Util/Double+Util.swift", + Features/Base/LineChartView.swift, ); target = B7C71D282E7BD9D6000E1191 /* iCOWidgetExtension */; }; diff --git a/AIProject/iCo/Features/Base/LineChartView.swift b/AIProject/iCo/Features/Base/LineChartView.swift new file mode 100644 index 00000000..8e1b06f2 --- /dev/null +++ b/AIProject/iCo/Features/Base/LineChartView.swift @@ -0,0 +1,74 @@ +// +// LineChartView.swift +// iCo +// +// Created by 백현진 on 10/24/25. +// + +import SwiftUI + +/// 공통으로 사용할 수 있는 라인 차트(미니 차트) 컴포넌트 +struct LineChartView: View { + /// 차트에 표시할 값 목록 (예: 가격 데이터) + let values: [Double] + + /// 차트 선 색상 (기본값: aiCoAccent) + var lineColor: Color = .iCoAccent + + /// 마지막 지점 표시 여부 + var showsLastDot: Bool = true + + /// 선 두께 + var lineWidth: CGFloat = 2 + + /// 내부 계산용 정규화된 값 + private var normalizedValues: [CGFloat] { + guard values.count > 1 else { return [] } + guard let min = values.min(), let max = values.max(), min != max else { + return Array(repeating: 0.5, count: values.count) + } + return values.map { CGFloat(($0 - min) / (max - min)) } + } + + var body: some View { + GeometryReader { geo in + if normalizedValues.isEmpty { + // 값이 없을 때 — 가운데 회색 선 표시 + Path { path in + let midY = geo.size.height / 2 + path.move(to: CGPoint(x: 0, y: midY)) + path.addLine(to: CGPoint(x: geo.size.width, y: midY)) + } + .stroke(Color.gray.opacity(0.4), lineWidth: 1) + } else { + ZStack { + // 스파크라인 + Path { path in + for (index, value) in normalizedValues.enumerated() { + let x = geo.size.width * CGFloat(index) / CGFloat(normalizedValues.count - 1) + let y = geo.size.height * (1 - value) + if index == 0 { + path.move(to: CGPoint(x: x, y: y)) + } else { + path.addLine(to: CGPoint(x: x, y: y)) + } + } + } + .stroke(lineColor, style: StrokeStyle(lineWidth: lineWidth, lineJoin: .round)) + + // 마지막 점 + if showsLastDot, let last = normalizedValues.last { + let x = geo.size.width + let y = geo.size.height * (1 - last) + + Circle() + .fill(lineColor) + .frame(width: 6, height: 6) + .position(x: x, y: y) + } + } + } + } + } +} + From 8426aa9cad6e462822c14375b2e9986b2fd3e801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=92=E1=85=A7=E1=86=AB=E1=84=8C=E1=85=B5=E1=86=AB?= Date: Fri, 24 Oct 2025 21:19:46 +0900 Subject: [PATCH 2/2] =?UTF-8?q?chore:=20=EB=AC=B8=EC=84=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AIProject/iCo/Features/Base/LineChartView.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/AIProject/iCo/Features/Base/LineChartView.swift b/AIProject/iCo/Features/Base/LineChartView.swift index 8e1b06f2..71898151 100644 --- a/AIProject/iCo/Features/Base/LineChartView.swift +++ b/AIProject/iCo/Features/Base/LineChartView.swift @@ -8,6 +8,12 @@ import SwiftUI /// 공통으로 사용할 수 있는 라인 차트(미니 차트) 컴포넌트 +/// +/// - Parameters: +/// - values: 차트에 표시할 값 목록 (예: 가격 데이터) +/// - lineColor: 차트 선 색상 (기본값은 `Color.iCoAccent`) +/// - showsLastDot: 마지막 지점(최근 데이터)에 점을 표시할지 여부 (`true` 시 표시) +/// - lineWidth: 선의 두께 (기본값: `2`) struct LineChartView: View { /// 차트에 표시할 값 목록 (예: 가격 데이터) let values: [Double]