// // 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: ((String) -> Void)? func show(items: [String], anchor: CGRect, onSelect: @escaping (String) -> 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 let items: [String] init(manager: DropdownManager, title: String, items: [String]) { self.manager = manager self.title = title self.items = items } @State private var frame: CGRect = .zero var body: some View { Button { manager.show(items: items, anchor: frame) { selected in print("선택한 항목:", selected) title = selected } } 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).stroke(Color(.Normal.normal))) } .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?(item) manager.dismiss() } label: { Text(item) .font(manager.font) .lineLimit(1) .minimumScaleFactor(0.5) .frame(maxWidth: .infinity, minHeight: 40, maxHeight: 40, alignment: .center) .padding([.leading,.trailing], 2) .contentShape(Rectangle()) } .buttonStyle(.plain) if index < manager.items.count - 1 { Rectangle() .fill(Color(.Second.normal).opacity(0.3)) .frame(maxWidth: .infinity, maxHeight: 2) } } } } }