Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xE6",
"green" : "0xA6",
"red" : "0x8A"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x99",
"green" : "0x51",
"red" : "0x41"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"localizable" : true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x83",
"green" : "0xB1",
"red" : "0xFF"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x0E",
"green" : "0x67",
"red" : "0xFE"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ public extension UIColor {
// MARK: - Primary

static let primary = DesignSystemAsset.primary.color
static let primaryLight = DesignSystemAsset.primaryLight.color
static let primaryGradientStart = DesignSystemAsset.primaryGradientStart.color
static let primaryGradientEnd = DesignSystemAsset.primaryGradientEnd.color

// MARK: - Blue

static let blueGradientStart = DesignSystemAsset.blueGradientStart.color
static let blueGradientEnd = DesignSystemAsset.blueGradientEnd.color

// MARK: - Red Positive

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,30 @@ import UIKit

final class InsightPhotoStatsView: UIView {

// MARK: - Constants

private enum Constants {
static let maxBarHeight: CGFloat = 160
static let minBarHeight: CGFloat = 30
static let lineCount: Int = 6
}

// MARK: - UI Components

private let titleLabel = UILabel()
private let countLabel = UILabel()
private let changeRateLabel = UILabel()
private let descriptionLabel = UILabel()
private let changeRateStackView = UIStackView()
private let headerStackView = UIStackView()
private let chartContainerView = UIView()
private let linesStackView = UIStackView()
private let barsStackView = UIStackView()
private let monthLabelsStackView = UIStackView()

private struct BarInfo {
let barView: GradientBarView
let label: UIView
let barHeight: CGFloat
}
private var barInfos: [BarInfo] = []

// MARK: - Init

Expand All @@ -35,37 +54,185 @@ final class InsightPhotoStatsView: UIView {
layer.cornerRadius = 16
clipsToBounds = true

titleLabel.setText("📸 사진 기록", style: .hd18)
addSubview(titleLabel)
let descriptionText = photoStats.changeRate >= 0
? "먹기 전에\n카메라부터 찾았네요."
: "이번 달엔 음식에 더 집중하셨네요."
descriptionLabel.setText(descriptionText, style: .hd16, color: .gray050)
descriptionLabel.numberOfLines = 2

setupChangeRateLabel(photoStats: photoStats)

let countText = "\(month)에 \(photoStats.currentMonthCount)장의 사진을 기록했어요"
countLabel.setText(countText, style: .p15, color: .gray050)
countLabel.numberOfLines = 0
addSubview(countLabel)
headerStackView.axis = .vertical
headerStackView.spacing = 10
headerStackView.addArrangedSubview(descriptionLabel)
headerStackView.addArrangedSubview(changeRateStackView)
addSubview(headerStackView)

setupChart(photoStats: photoStats, month: month)
}

private func setupChangeRateLabel(photoStats: PhotoStats) {
let rate = photoStats.changeRate
let sign = rate >= 0 ? "+" : ""
let rateText = "지난 달(\(photoStats.previousMonthCount)장) 대비 \(sign)\(Int(rate))%"
let rateColor: UIColor = rate >= 0 ? .primary : .gray300
changeRateLabel.setText(rateText, style: .p14, color: rateColor)
addSubview(changeRateLabel)

let prefixLabel = UILabel()
prefixLabel.setText("지난 달 대비 기록된 사진이 ", style: .p10, color: .gray050)

let valueLabel = UILabel()
valueLabel.setText("\(Int(abs(rate)))%", style: .hd16, color: .primary)

let suffixLabel = UILabel()
suffixLabel.setText(rate >= 0 ? " 증가했어요." : " 감소했어요.", style: .p10, color: .gray050)

changeRateStackView.axis = .horizontal
changeRateStackView.alignment = .center
changeRateStackView.spacing = 0
changeRateStackView.addArrangedSubview(prefixLabel)
changeRateStackView.addArrangedSubview(valueLabel)
changeRateStackView.addArrangedSubview(suffixLabel)
}

private func setupChart(photoStats: PhotoStats, month: String) {
addSubview(chartContainerView)

setupLines()
setupBars(photoStats: photoStats, month: month)
setupMonthLabels(photoStats: photoStats, month: month)
}

private func setupLines() {
linesStackView.axis = .vertical
linesStackView.distribution = .equalSpacing
chartContainerView.addSubview(linesStackView)

for _ in 0..<Constants.lineCount {
let line = UIView()
line.backgroundColor = .sd800
line.translatesAutoresizingMaskIntoConstraints = false
line.heightAnchor.constraint(equalToConstant: 0.5).isActive = true
linesStackView.addArrangedSubview(line)
}
}

private func setupBars(photoStats: PhotoStats, month: String) {
barsStackView.axis = .horizontal
barsStackView.distribution = .fill
barsStackView.alignment = .bottom
barsStackView.spacing = 60
chartContainerView.addSubview(barsStackView)

let prevCount = CGFloat(photoStats.previousMonthCount)
let currCount = CGFloat(photoStats.currentMonthCount)
let total = prevCount + currCount

let counts = [prevCount, currCount]
let countInts = [Int(prevCount), Int(currCount)]
let gradientColors: [[UIColor]] = [
[.blueGradientStart, .blueGradientEnd],
[.primaryGradientStart, .primaryGradientEnd]
]

for (i, count) in counts.enumerated() {
let barHeight = total > 0 ? max((count / total) * Constants.maxBarHeight, Constants.minBarHeight) : 0
let barView = GradientBarView(colors: gradientColors[i])

let label = UILabel()
label.text = "\(countInts[i])"
label.font = .systemFont(ofSize: 11, weight: .semibold)
label.textColor = .white
label.textAlignment = .center

barView.addSubview(label)

barInfos.append(BarInfo(barView: barView, label: label, barHeight: barHeight))
barsStackView.addArrangedSubview(barView)
}
}

private func setupMonthLabels(photoStats: PhotoStats, month: String) {
monthLabelsStackView.axis = .horizontal
monthLabelsStackView.distribution = .fill
monthLabelsStackView.spacing = 60
addSubview(monthLabelsStackView)

let currentMonthNum = month.split(separator: "-").last.flatMap { Int($0) } ?? 1
let previousMonthNum = currentMonthNum == 1 ? 12 : currentMonthNum - 1

for name in ["\(previousMonthNum)월", "\(currentMonthNum)월"] {
let label = UILabel()
label.setText(name, style: .p10, color: .gray200)
label.textAlignment = .center
label.snp.makeConstraints { $0.width.equalTo(60) }
monthLabelsStackView.addArrangedSubview(label)
}
}

private func setupConstraints() {
titleLabel.snp.makeConstraints {
headerStackView.snp.makeConstraints {
$0.top.leading.equalToSuperview().inset(20)
$0.trailing.lessThanOrEqualToSuperview().inset(20)
}

countLabel.snp.makeConstraints {
$0.top.equalTo(titleLabel.snp.bottom).offset(12)
chartContainerView.snp.makeConstraints {
$0.top.equalTo(headerStackView.snp.bottom).offset(24)
$0.leading.trailing.equalToSuperview().inset(20)
$0.height.equalTo(Constants.maxBarHeight)
}

changeRateLabel.snp.makeConstraints {
$0.top.equalTo(countLabel.snp.bottom).offset(8)
$0.leading.trailing.equalToSuperview().inset(20)
linesStackView.snp.makeConstraints {
$0.edges.equalToSuperview()
}

barsStackView.snp.makeConstraints {
$0.centerX.bottom.equalToSuperview()
$0.top.greaterThanOrEqualToSuperview()
}

for info in barInfos {
info.barView.snp.makeConstraints {
$0.width.equalTo(60)
$0.height.equalTo(info.barHeight)
}

info.label.snp.makeConstraints {
$0.top.equalToSuperview().inset(6)
$0.centerX.equalToSuperview()
}
}

monthLabelsStackView.snp.makeConstraints {
$0.top.equalTo(chartContainerView.snp.bottom).offset(8)
$0.centerX.equalTo(barsStackView)
$0.width.equalTo(barsStackView)
$0.bottom.equalToSuperview().inset(20)
}
}
}

// MARK: - GradientBarView

private final class GradientBarView: UIView {

private let gradientLayer = CAGradientLayer()

init(colors: [UIColor]) {
super.init(frame: .zero)
clipsToBounds = true
layer.cornerRadius = 4
layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]

gradientLayer.colors = colors.map(\.cgColor)
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1)
layer.insertSublayer(gradientLayer, at: 0)
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
}