forked from AcaMate/AcaMate_iOS
[✨] 드롭다운 컴포넌트 생성 및 회원가입 화면에 적용중
This commit is contained in:
parent
f11d3b417a
commit
4875427e24
Binary file not shown.
|
@ -6,12 +6,13 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
class DropdownManager: ObservableObject {
|
||||
static let shared = DropdownManager()
|
||||
|
||||
@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(alignment: .leading, spacing: 0) {
|
||||
ForEach(manager.items, id: \.self) { item in
|
||||
Button {
|
||||
manager.onSelect?(item)
|
||||
manager.dismiss()
|
||||
} label: {
|
||||
Text(item)
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
VStack(spacing: 0) {
|
||||
if manager.items.count > maxVisibleItemCount {
|
||||
OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { _ in
|
||||
dropdownContent()
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.background(Color.white)
|
||||
} else {
|
||||
dropdownContent()
|
||||
}
|
||||
}
|
||||
.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)
|
||||
.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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",
|
||||
"직접 입력"]
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user