AcaMate_iOS/AcaMate/1. View/10. Common/DropDownView.swift

166 lines
5.1 KiB
Swift

//
// DropDownView.swift
// AcaMate
//
// Created by TAnine on 3/25/25.
//
import SwiftUI
class DropdownManager: ObservableObject {
@Published var isPresented = false
@Published var items: [String] = []
@Published var anchor: CGRect = .zero
@Published var font: Font = .body
var onSelect: ((Int) -> Void)?
func show(items: [String], anchor: CGRect,
onSelect: @escaping (Int) -> Void) {
self.items = items
self.anchor = anchor
self.onSelect = onSelect
isPresented = true
}
func dismiss() {
isPresented = false
}
}
/// .coordinateSpace(name: "dropdownArea")
/// GlobalDropdownOverlay(manager: dropdownManager) ZStack
struct DropdownButton: View {
@ObservedObject var manager: DropdownManager
@State var title: String
var onSelect: ((Int) -> Void)?
let items: [String]
init(manager: DropdownManager, title: String, items: [String],
onSelect: @escaping (Int) -> Void) {
self.manager = manager
self.title = title
self.items = items
self.onSelect = onSelect
}
@State private var frame: CGRect = .zero
var body: some View {
Button {
endTextEditing()
manager.show(items: items, anchor: frame) { index in
self.onSelect?(index)
print("선택한 항목: \(items[index])")
title = items[index]
}
} label: {
HStack(alignment: .center) {
Spacer()
Text(title)
.font(manager.font)
.lineLimit(1)
.minimumScaleFactor(0.5)
Image(systemName: /*manager.isPresented ? "chevron.up" : */ "chevron.down")
.resizable()
.frame(width: 8, height: 4)
Spacer()
}
.padding(4)
.background{
RoundedRectangle(cornerRadius: 8)
.fill(Color.clear)
}
}
.buttonStyle(.plain)
.background(
GeometryReader { geo in
Color.clear
.onAppear {
frame = geo.frame(in: .named("dropdownArea"))
}
}
)
}
}
struct GlobalDropdownOverlay: View {
@ObservedObject var manager: DropdownManager
@State private var scrollOffset: CGPoint = .zero
private var maxVisibleItemCount: Int {
if manager.items.count > 5 {
return 5
} else { return manager.items.count }
}
private let itemHeight: CGFloat = 42
private var heightForDropBox: CGFloat {
CGFloat(maxVisibleItemCount) * itemHeight
}
var body: some View {
if manager.isPresented {
ZStack(alignment: .topLeading) {
Color.black.opacity(0.1)
.ignoresSafeArea()
.onTapGesture {
manager.dismiss()
}
VStack(spacing: 0) {
if manager.items.count > maxVisibleItemCount {
OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { _ in
dropdownContent()
}
} else {
dropdownContent()
}
}
.frame(width: manager.anchor.width, height: heightForDropBox)
.background{
RoundedRectangle(cornerRadius: 8)
.fill(Color(.Normal.normal))
.stroke(Color(.Second.normal))
.strokeBorder(lineWidth: 2)
}
.position(x: manager.anchor.midX,
y: manager.anchor.maxY+heightForDropBox/2)
.animation(.easeOut(duration: 0.25), value: manager.isPresented)
}
}
}
@ViewBuilder
private func dropdownContent() -> some View {
VStack(alignment: .leading, spacing: 0) {
ForEach(Array(manager.items.enumerated()), id: \.offset) { index, item in
Button {
manager.onSelect?(index)
manager.dismiss()
} label: {
Text(item)
.font(manager.font)
.lineLimit(1)
.minimumScaleFactor(0.4)
.padding([.leading, .trailing], 8)
.frame(maxWidth: .infinity, minHeight: 40, maxHeight: 40, alignment: .center)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
if index < manager.items.count - 1 {
Rectangle()
.fill(Color(.Second.normal).opacity(0.3))
.frame(maxWidth: .infinity, maxHeight: 2)
}
}
}
}
}