Skip to content

Commit c745fd0

Browse files
authored
Merge pull request #60 from SW-Maestro-OSS/feat/qty-text-expression
[CVW-046] 코인 디테일 화면 QTY, 가겨정보 텍스트 표현 변경
2 parents f934806 + d541219 commit c745fd0

File tree

5 files changed

+175
-28
lines changed

5 files changed

+175
-28
lines changed

Projects/Features/CoinDetail/Feature/Sources/CoinDetailPageViewModel.swift

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,12 @@ final class CoinDetailPageViewModel: UDFObservableObject, CoinDetailPageViewMode
127127
var newState = state
128128
switch action {
129129
case .updateOrderbook(let bids, let asks):
130-
guard let bigestQuantity = (bids + asks).map(\.quantity).max() else { break }
130+
guard let biggestQuantity = (bids + asks).map(\.quantity).max() else { break }
131131
newState.bidOrderbooks = bids.map {
132-
transform(bigestQuantity: bigestQuantity, orderbook: $0, type: .bid)
132+
transform(biggestQuantity: biggestQuantity, orderbook: $0, type: .bid)
133133
}
134134
newState.askOrderbooks = asks.map {
135-
transform(bigestQuantity: bigestQuantity, orderbook: $0, type: .ask)
135+
transform(biggestQuantity: biggestQuantity, orderbook: $0, type: .ask)
136136
}
137137
case .updateTickerInfo(let entity):
138138
let (changePercentText, changeType) = createChangePercentTextConfig(percent: entity.changedPercent)
@@ -142,9 +142,9 @@ final class CoinDetailPageViewModel: UDFObservableObject, CoinDetailPageViewMode
142142
)
143143
newState.tickerInfo =
144144
.init(
145-
currentPriceText: entity.price.roundDecimalPlaces(exact: 4),
146-
bestBidPriceText: entity.bestBidPrice.roundDecimalPlaces(exact: 4),
147-
bestAskPriceText: entity.bestAskPrice.roundDecimalPlaces(exact: 4)
145+
currentPriceText: entity.price.description,
146+
bestBidPriceText: entity.bestBidPrice.description,
147+
bestAskPriceText: entity.bestAskPrice.description
148148
)
149149
case .updateTrades(let trades):
150150
newState.trades = trades.map(convertToRO)
@@ -206,12 +206,12 @@ private extension CoinDetailPageViewModel {
206206
.subscribe(self.action)
207207
}
208208

209-
func transform(bigestQuantity: CVNumber, orderbook: Orderbook, type: OrderbookType) -> OrderbookCellRO {
209+
func transform(biggestQuantity: CVNumber, orderbook: Orderbook, type: OrderbookType) -> OrderbookCellRO {
210210
return OrderbookCellRO(
211211
type: type,
212-
priceText: orderbook.price.roundDecimalPlaces(exact: 4),
213-
quantityText: orderbook.quantity.roundDecimalPlaces(exact: 4),
214-
relativePercentOfQuantity: orderbook.quantity.double / bigestQuantity.double
212+
priceText: orderbook.price.description,
213+
quantityText: orderbook.quantity.formatCompactNumberWithSuffix(),
214+
relativePercentOfQuantity: orderbook.quantity.double / biggestQuantity.double
215215
)
216216
}
217217
}
@@ -234,8 +234,8 @@ private extension CoinDetailPageViewModel {
234234
dateFormatter.dateFormat = "HH:mm:ss"
235235
let renderObject: CoinTradeRO = .init(
236236
id: entity.tradeId,
237-
priceText: entity.price.roundDecimalPlaces(exact: 4),
238-
quantityText: entity.quantity.roundDecimalPlaces(exact: 4),
237+
priceText: entity.price.description,
238+
quantityText: entity.quantity.formatCompactNumberWithSuffix(),
239239
timeText: dateFormatter.string(from: entity.tradeTime),
240240
textColor: entity.tradeType == .buy ? .green : .red,
241241
backgroundEffectColor: (entity.tradeType == .buy ? .green : .red)
@@ -274,4 +274,3 @@ extension CoinDetailPageViewModel {
274274
}
275275
}
276276
}
277-

Projects/Features/CoinDetail/Feature/Sources/Views/OrderbookCellView.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ struct OrderbookCellView: View {
5151
case .priceFirst:
5252
Text(renderObject.priceText)
5353
.foregroundStyle(renderObject.priceTextColor)
54+
.lineLimit(1)
55+
.minimumScaleFactor(0.5)
5456
Spacer()
5557
Text(renderObject.quantityText)
5658
.foregroundStyle(.black)

Projects/Utils/CoreUtil/Project.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ let project = Project(
1818
D.ThirdParty.AdvancedSwift,
1919
]
2020
),
21+
22+
// Tests
23+
.target(
24+
name: "CoreUtilTests",
25+
destinations: .iOS,
26+
product: .unitTests,
27+
bundleId: "com.choijunios.feature.coreutil.tests",
28+
sources: ["Tests/**"],
29+
dependencies: [
30+
.target(name: "CoreUtil"),
31+
]
32+
),
2133
]
2234
)
2335

Projects/Utils/CoreUtil/Sources/DataStructure/CVNumber.swift

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,30 @@ public struct CVNumber: Sendable, Hashable, Comparable, CustomStringConvertible,
3232
self.wrappedNumber = Decimal(value)
3333
}
3434

35-
public func roundToTwoDecimalPlaces() -> String {
35+
public var description: String { wrappedNumber.description }
36+
public var double: Double { (wrappedNumber as NSDecimalNumber).doubleValue }
37+
}
38+
39+
40+
// MARK: Calc
41+
public extension CVNumber {
42+
static func + (lhs: Self, rhs: Self) -> Self {
43+
CVNumber(lhs.wrappedNumber + rhs.wrappedNumber)
44+
}
45+
46+
static func < (lhs: CVNumber, rhs: CVNumber) -> Bool {
47+
lhs.wrappedNumber < rhs.wrappedNumber
48+
}
49+
50+
static func / (lhs: CVNumber, rhs: CVNumber) -> CVNumber {
51+
CVNumber(lhs.wrappedNumber / rhs.wrappedNumber)
52+
}
53+
}
54+
55+
56+
// MARK: Rounded expression
57+
public extension CVNumber {
58+
func roundToTwoDecimalPlaces() -> String {
3659

3760
let formatter = NumberFormatter()
3861
formatter.maximumFractionDigits = 2
@@ -44,7 +67,7 @@ public struct CVNumber: Sendable, Hashable, Comparable, CustomStringConvertible,
4467
return formattedString ?? "-1.0"
4568
}
4669

47-
public func roundDecimalPlaces(exact: Int) -> String {
70+
func roundDecimalPlaces(exact: Int) -> String {
4871

4972
let formatter = NumberFormatter()
5073
formatter.maximumFractionDigits = exact
@@ -55,21 +78,64 @@ public struct CVNumber: Sendable, Hashable, Comparable, CustomStringConvertible,
5578

5679
return formattedString ?? "-1." + Array(repeating: "0", count: (exact-1))
5780
}
58-
59-
public var description: String { wrappedNumber.description }
60-
public var double: Double { (wrappedNumber as NSDecimalNumber).doubleValue }
6181
}
6282

83+
84+
// MARK: Compact expression
6385
public extension CVNumber {
64-
static func + (lhs: Self, rhs: Self) -> Self {
65-
CVNumber(lhs.wrappedNumber + rhs.wrappedNumber)
66-
}
67-
68-
static func < (lhs: CVNumber, rhs: CVNumber) -> Bool {
69-
lhs.wrappedNumber < rhs.wrappedNumber
70-
}
71-
72-
static func / (lhs: CVNumber, rhs: CVNumber) -> CVNumber {
73-
CVNumber(lhs.wrappedNumber / rhs.wrappedNumber)
86+
func formatCompactNumberWithSuffix() -> String {
87+
let thousand = Decimal(1_000)
88+
let million = Decimal(1_000_000)
89+
let billion = Decimal(1_000_000_000)
90+
let value = wrappedNumber
91+
92+
var unit = ""
93+
var base = value
94+
95+
switch value {
96+
case billion...:
97+
base = value / billion
98+
unit = "B"
99+
case million...:
100+
base = value / million
101+
unit = "M"
102+
case thousand...:
103+
base = value / thousand
104+
unit = "K"
105+
default:
106+
base = value
107+
unit = ""
108+
}
109+
110+
for precision in (0...4).reversed() {
111+
let formatter = NumberFormatter()
112+
formatter.roundingMode = .down
113+
formatter.maximumFractionDigits = precision
114+
let minFraction = unit.isEmpty ? 4 : 3
115+
if minFraction > precision {
116+
formatter.minimumFractionDigits = precision
117+
} else {
118+
formatter.minimumFractionDigits = minFraction
119+
}
120+
formatter.numberStyle = .decimal
121+
122+
if unit.isEmpty {
123+
// 1000미만 수인 경우
124+
if let numString = formatter.string(from: base as NSNumber),
125+
numString.count <= 6 {
126+
return "\(numString)\(unit)"
127+
}
128+
} else {
129+
if let numString = formatter.string(from: base as NSNumber),
130+
numString.count <= 5 {
131+
return "\(numString)\(unit)"
132+
}
133+
}
134+
}
135+
136+
// 소수점 없애고 자른 뒤 리턴
137+
let maxSize = unit.isEmpty ? 6 : 5
138+
let slicedStr = "\(base.description.prefix(maxSize))"
139+
return "\(slicedStr)\(unit)"
74140
}
75141
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//
2+
// CVNumberTests.swift
3+
// CoreUtil
4+
//
5+
// Created by choijunios on 5/12/25.
6+
//
7+
8+
import Testing
9+
@testable import CoreUtil
10+
11+
@Suite("CVNumber테스트")
12+
struct CVNumberTests {
13+
@Test("K/M/B간소화 표현 테스트")
14+
func checkCompactExpression() {
15+
// Given
16+
let numbers: [CVNumber] = [
17+
CVNumber(0.0000001),
18+
CVNumber(0.0001000),
19+
20+
CVNumber(1.0),
21+
CVNumber(10.0),
22+
CVNumber(100.0),
23+
24+
CVNumber(1_234.0),
25+
CVNumber(12_345.0),
26+
CVNumber(123_456.0),
27+
28+
CVNumber(1_234_567.0),
29+
CVNumber(12_345_678.0),
30+
CVNumber(123_456_789.0),
31+
32+
CVNumber(1_234_567_890.0),
33+
CVNumber(12_345_678_900.0),
34+
CVNumber(123_456_789_000.0),
35+
]
36+
37+
38+
// When
39+
let results = numbers.map({ $0.formatCompactNumberWithSuffix() })
40+
41+
42+
// Then
43+
let expectedResults: [String] = [
44+
"0.0000",
45+
"0.0001",
46+
"1.0000",
47+
"10.000",
48+
"100.00",
49+
50+
"1.234K",
51+
"12.34K",
52+
"123.4K",
53+
54+
"1.234M",
55+
"12.34M",
56+
"123.4M",
57+
58+
"1.234B",
59+
"12.34B",
60+
"123.4B",
61+
]
62+
results.enumerated().forEach { index, str in
63+
print(str, expectedResults[index], terminator: " ")
64+
print("")
65+
#expect(str == expectedResults[index])
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)