forked from AcaMate/AcaMate_iOS
166 lines
5.1 KiB
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|