[] 드롭다운 컴포넌트 생성 및 회원가입 화면에 적용중

This commit is contained in:
김선규 2025-03-26 17:58:05 +09:00
parent f11d3b417a
commit 4875427e24
4 changed files with 137 additions and 41 deletions

View File

@ -6,12 +6,13 @@
//
import SwiftUI
class DropdownManager: ObservableObject {
static let shared = DropdownManager()
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) {
@ -26,30 +27,49 @@ class DropdownManager: ObservableObject {
}
}
/// .coordinateSpace(name: "dropdownArea")
/// GlobalDropdownOverlay(manager: dropdownManager) ZStack
struct DropdownButton: View {
let title: String
@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 {
DropdownManager.shared.show(items: items, anchor: frame) { selected in
manager.show(items: items, anchor: frame) { selected in
print("선택한 항목:", selected)
title = selected
}
} label: {
HStack {
HStack(alignment: .center) {
Spacer()
Text(title)
Image(systemName: "chevron.down")
.font(manager.font)
.lineLimit(1)
.minimumScaleFactor(0.5)
Image(systemName: /*manager.isPresented ? "chevron.up" : */ "chevron.down")
.resizable()
.frame(width: 8, height: 4)
Spacer()
}
.padding()
.background(RoundedRectangle(cornerRadius: 8).stroke(Color.gray))
.padding(4)
.background(RoundedRectangle(cornerRadius: 8).stroke(Color(.Normal.normal)))
}
.buttonStyle(.plain)
.background(
GeometryReader { geo in
Color.clear
.onAppear {
frame = geo.frame(in: .global)
frame = geo.frame(in: .named("dropdownArea"))
}
}
)
@ -58,38 +78,78 @@ struct DropdownButton: View {
struct GlobalDropdownOverlay: View {
@ObservedObject var manager = DropdownManager.shared
@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.001)
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(manager.items, id: \.self) { item in
ForEach(Array(manager.items.enumerated()), id: \.offset) { index, item in
Button {
manager.onSelect?(item)
manager.dismiss()
} label: {
Text(item)
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.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)
.background(Color.white)
}
}
.frame(width: manager.anchor.width)
.background(RoundedRectangle(cornerRadius: 8).stroke(Color.gray))
.shadow(radius: 5)
.position(x: manager.anchor.midX, y: manager.anchor.maxY + 10)
}
}
}
}
if index < manager.items.count - 1 {
Rectangle()
.fill(Color(.Second.normal).opacity(0.3))
.frame(maxWidth: .infinity, maxHeight: 2)
}
}
}
}
}

View File

@ -28,11 +28,13 @@ struct RegisterView: View {
@State private var isSelectAddr: Bool = false
@StateObject private var dropdownManager = DropdownManager()
private let addressBtnID = UUID()
private let registerBtnID = UUID()
@State private var selected = ""
let options = ["Swift", "Kotlin", "Dart", "JavaScript"]
let options = ["Swift", "Kotlin", "Dart", "JavaScript", "C#","C++","C"]
var body: some View {
// MARK: TO-DO
@ -51,9 +53,6 @@ struct RegisterView: View {
}
.padding(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 16))
DropdownButton(title: "언어 선택", items: ["Swift", "Dart", "Kotlin"])
OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { proxy in
//
@ -66,6 +65,7 @@ struct RegisterView: View {
.foregroundStyle(Color(.Other.red))
}
.frame(width: 60, alignment: .center)
.padding(.trailing,12)
Spacer(minLength: 1)
CustomTextField(placeholder: "최대 10글자 입력", text: $registerVM.nameText, alignment: .center)
.frame(maxWidth: .infinity,maxHeight: 48)
@ -82,6 +82,7 @@ struct RegisterView: View {
Text("생일")
.font(.nps(size: 16))
.frame(width: 60, alignment: .center)
.padding(.trailing,12)
Spacer(minLength: 1)
DatePicker("", selection: $registerVM.selectDate, displayedComponents: [.date])
.datePickerStyle(.compact)
@ -99,10 +100,10 @@ struct RegisterView: View {
.font(.nps(size: 16))
.foregroundStyle(Color(.Other.red))
}
.frame(width: 60, alignment: .center)
.padding(.trailing,12)
Spacer(minLength: 1)
CustomTextField(placeholder: "앞부분 입력", text: $registerVM.emailFrontText, alignment: .center)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
.background {
@ -114,13 +115,25 @@ struct RegisterView: View {
.font(.nps(size: 16))
.padding([.leading, .trailing], 4)
// Spacer(minLength: 1)
CustomTextField(placeholder: "뒷부분 입력", text: $registerVM.emailTailText, alignment: .center)
.frame(maxWidth: .infinity,maxHeight: 48)
DropdownButton(manager: dropdownManager, title: "도메인 선택",
items: registerVM.emailTailList)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background {
RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light))
}
//
/*
CustomTextField(placeholder: "뒷부분 입력", text: $registerVM.emailFrontText, alignment: .center)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
.background {
RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light))
}
*/
}
.padding()
@ -133,10 +146,17 @@ struct RegisterView: View {
.font(.nps(size: 16))
.foregroundStyle(Color(.Other.red))
}
CustomTextField(placeholder: "000", text: $registerVM.nameText, alignment: .center)
.frame(maxWidth: .infinity,maxHeight: 48)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
.frame(width: 60, alignment: .center)
.padding(.trailing,12)
// DropdownButton(manager: dropdownManager, title: "", items: options)
// CustomTextField(placeholder: "000", text: $registerVM.nameText, alignment: .center)
// .frame(maxWidth: .infinity,maxHeight: 48)
// .padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
// .background {
// RoundedRectangle(cornerRadius: 24)
// .foregroundStyle(Color(.Normal.light))
// }
DropdownButton(manager: dropdownManager, title: "선택", items: registerVM.numberHeadList)
.background {
RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light))
@ -144,6 +164,7 @@ struct RegisterView: View {
Text("-")
.font(.nps(size: 16))
.padding([.leading, .trailing], 4)
CustomTextField(placeholder: "0000", text: $registerVM.nameText, alignment: .center)
.frame(maxWidth: .infinity,maxHeight: 48)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
@ -168,6 +189,7 @@ struct RegisterView: View {
Text("주소")
.font(.nps(size: 16))
.frame(width: 60, alignment: .center)
.padding(.trailing,12)
Spacer(minLength: 1)
VStack(spacing: 0) {
@ -189,8 +211,9 @@ struct RegisterView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity)
// .background(Color(.Normal.light))
}
GlobalDropdownOverlay()
GlobalDropdownOverlay(manager: dropdownManager)
}
.coordinateSpace(name: "dropdownArea")
.sheet(isPresented: $showWebView) {
WebView(url: URL(string: "https://sean-59.github.io/Kakao-Postcode/")!,
showWebView: $showWebView,
@ -217,6 +240,7 @@ struct RegisterView: View {
btnVM.setAction(for: addressBtnID) {
self.showWebView.toggle()
}
dropdownManager.font = .nps(size: 16)
}
.onChange(of: registerVM.addressText) { _, new in

View File

@ -28,5 +28,17 @@ class RegisterViewModel: ObservableObject {
@Published var addressText: String = "주소 입력"
@State var addrDetailText: String = ""
let numberHeadList = ["010","011","016","017","018","019"]
let emailTailList = ["gmail.com",
"naver.com",
"daum.net",
"hanmail.net",
"nate.com",
"outlook.com",
"icloud.com",
"kakao.com",
"yahoo.com",
"protonmail.com",
"직접 입력"]
}