From 91caf33371bd4427ca854ffc87d7a94d7127de2e Mon Sep 17 00:00:00 2001 From: devminseok Date: Fri, 16 Aug 2024 08:23:36 +0900 Subject: [PATCH] =?UTF-8?q?feature:=20WheelPicker=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/WheelPicker/WheelPicker.swift | 109 ++++++++++++++++++ .../Sources/WheelPicker/WheelPickerData.swift | 16 +++ 2 files changed, 125 insertions(+) create mode 100644 Projects/Feature/HomeFeature/Sources/WheelPicker/WheelPicker.swift create mode 100644 Projects/Feature/HomeFeature/Sources/WheelPicker/WheelPickerData.swift diff --git a/Projects/Feature/HomeFeature/Sources/WheelPicker/WheelPicker.swift b/Projects/Feature/HomeFeature/Sources/WheelPicker/WheelPicker.swift new file mode 100644 index 0000000..dbf51d2 --- /dev/null +++ b/Projects/Feature/HomeFeature/Sources/WheelPicker/WheelPicker.swift @@ -0,0 +1,109 @@ +// +// WheelPicker.swift +// DesignSystem +// +// Created by devMinseok on 8/13/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import SwiftUI + +import DesignSystem + +struct WheelPicker: View { + @Namespace var backgroundFrameID + @State var selectionID: D.ID? + @Binding var selection: D? + @State var backgroundRect: CGRect = .zero + let sources: [D] + let image: Image + + var itemHeight: CGFloat = 98 + var menuHeightMultiplier: CGFloat { + return round(backgroundRect.height / itemHeight) + } + + init( + image: Image, + sources: [D], + selection: Binding + ) { + self.image = image + self.sources = sources + _selection = selection + } + + var body: some View { + let itemsCountAbove = Double(Int((menuHeightMultiplier - 1) / 2)) + let pickerHeight = itemHeight * (itemsCountAbove * 2 + 1) + let paddingAdjustment = (Int(menuHeightMultiplier) % 2 == 0) ? itemHeight * 0.5 : 0 + + ZStack(alignment: .center) { + RoundedRectangle(cornerRadius: 16) + .fill(Alias.Color.Background.secondary) + .frame(width: 209, height: itemHeight) + .overlay(alignment: .leading) { + self.image + .padding(.leading, 20) + } + ScrollViewReader { scrollViewProxy in + ScrollView(.vertical) { + LazyVStack(spacing: .zero) { + ForEach(sources) { item in + GeometryReader { geometryProxy in + let itemCenterY = geometryProxy.frame(in: .global).midY + let viewCenterY = backgroundRect.midY + let distance = abs(viewCenterY - itemCenterY) + let scale = max(1, 1.4 - (distance / (itemHeight * 3))) + + Text(item.title) + .foregroundStyle( + item == selection ? Alias.Color.Text.primary : Alias.Color.Text.disabled + ) + .font(Typography.header2) + .lineLimit(1) + .frame(height: itemHeight) + .scaleEffect(x: scale, y: scale, anchor: .leading) + .frame(maxWidth: .infinity) + .id(item.id) + } + .frame(height: itemHeight) + } + } + .scrollTargetLayout() + .padding(.vertical, itemHeight * itemsCountAbove) + } + .scrollPosition(id: $selectionID, anchor: .center) + .frame(height: pickerHeight) + .padding(.vertical, paddingAdjustment) + .scrollTargetBehavior(.viewAligned(limitBehavior: .always)) + .scrollIndicators(.hidden) + .onChange(of: selection) { _, value in + guard let id = value?.id, + selectionID != id + else { return } + scrollViewProxy.scrollTo(id, anchor: .center) + } + } + + LinearGradient( + colors: [ + Alias.Color.Background.primary, + .clear, + Alias.Color.Background.primary + ], + startPoint: .top, + endPoint: .bottom + ) + .allowsHitTesting(false) + } + .setFrameMeasure(space: .global, identifier: backgroundFrameID) + .getFrameMeasure { value in + guard let frame = value[backgroundFrameID] else { return } + backgroundRect = frame + } + .onChange(of: selectionID) { _, value in + selection = sources.first { $0.id == value } + } + } +} diff --git a/Projects/Feature/HomeFeature/Sources/WheelPicker/WheelPickerData.swift b/Projects/Feature/HomeFeature/Sources/WheelPicker/WheelPickerData.swift new file mode 100644 index 0000000..89a147a --- /dev/null +++ b/Projects/Feature/HomeFeature/Sources/WheelPicker/WheelPickerData.swift @@ -0,0 +1,16 @@ +// +// WheelPickerData.swift +// HomeFeature +// +// Created by devMinseok on 8/15/24. +// Copyright © 2024 PomoNyang. All rights reserved. +// + +import Foundation + +protocol WheelPickerData: Equatable, Identifiable { + associatedtype Data: Equatable + + var title: String { get } + var data: Data { get } +}