[] 회원가입 페이지 및 드롭다운 기능 추가중

This commit is contained in:
김선규 2025-03-25 17:53:08 +09:00
parent 5a2c237e3a
commit f11d3b417a
6 changed files with 329 additions and 145 deletions

View File

@ -9,12 +9,51 @@ import SwiftUI
import WebKit import WebKit
struct WebView: UIViewControllerRepresentable { struct WebView: UIViewControllerRepresentable {
@Binding var isLoding: Bool
var url: URL
@Binding var showWebView: Bool
@Binding var isLoading: Bool
var complete: ((Any) -> Void)?
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}
class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler {
var parent: WebView
init(parent: WebView) {
self.parent = parent
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("웹뷰 로딩 완료")
parent.isLoading = false
}
func userContentController(_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage) {
if let data = message.body as? [String: Any]
{
if let road = data["roadAddress"], let code = data["zonecode"] {
printLog("도로명 주소: \(road) // ZIP CODE: \(code)")
parent.complete?((road,code))
}
}
parent.showWebView.toggle()
}
}
func updateUIViewController(_ uiViewController: WebViewController, context: Context) { func updateUIViewController(_ uiViewController: WebViewController, context: Context) {
} }
func makeUIViewController(context: Context) -> WebViewController { func makeUIViewController(context: Context) -> WebViewController {
return WebViewController() return WebViewController(url: url, isLoading: $isLoading,
complete: complete, coordinator: context.coordinator)
} }
@ -22,24 +61,41 @@ struct WebView: UIViewControllerRepresentable {
class WebViewController: UIViewController, WKUIDelegate { class WebViewController: UIViewController, WKUIDelegate {
var url: URL
@Binding var isLoading: Bool
var complete: ((Any) -> Void)?
var coordinator: WebView.Coordinator
init(url: URL, isLoading: Binding<Bool>, complete: ((Any) -> Void)?, coordinator: WebView.Coordinator) {
self.url = url
_isLoading = isLoading
self.complete = complete
self.coordinator = coordinator
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
webView() webView()
} }
func webView() { private func webView() {
let url = URL(string: "https://sean-59.github.io/Kakao-Postcode/")!
let request = URLRequest(url: url) let request = URLRequest(url: url)
let configuration = WKWebViewConfiguration() let configuration = WKWebViewConfiguration()
let contentController = WKUserContentController() let contentController = WKUserContentController()
contentController.add(self, name: "callBackHandler") contentController.add(coordinator, name: "callBackHandler")
configuration.userContentController = contentController configuration.userContentController = contentController
let webview = WKWebView(frame: view.bounds, configuration: configuration) let webview = WKWebView(frame: view.bounds, configuration: configuration)
webview.uiDelegate = self webview.uiDelegate = self
webview.navigationDelegate = self webview.navigationDelegate = coordinator
webview.autoresizingMask = [.flexibleWidth, .flexibleHeight] webview.autoresizingMask = [.flexibleWidth, .flexibleHeight]
webview.isUserInteractionEnabled = true webview.isUserInteractionEnabled = true
@ -51,28 +107,3 @@ class WebViewController: UIViewController, WKUIDelegate {
} }
} }
extension WebViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
print("웹뷰 로딩 시작")
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("웹뷰 로딩 완료")
// (: ) .
}
}
extension WebViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage) {
if let data = message.body as? [String: Any] {
print(data)
print(data["jibunAddress"] ?? "jibunAddress 없음")
print(data["roadAddress"] ?? "roadAddress 없음")
print(data["zonecode"] ?? "zonecode 없음")
}
}
}

View File

@ -0,0 +1,95 @@
//
// DropDownView.swift
// AcaMate
//
// Created by TAnine on 3/25/25.
//
import SwiftUI
class DropdownManager: ObservableObject {
static let shared = DropdownManager()
@Published var isPresented = false
@Published var items: [String] = []
@Published var anchor: CGRect = .zero
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
}
}
struct DropdownButton: View {
let title: String
let items: [String]
@State private var frame: CGRect = .zero
var body: some View {
Button {
DropdownManager.shared.show(items: items, anchor: frame) { selected in
print("선택한 항목:", selected)
}
} label: {
HStack {
Text(title)
Image(systemName: "chevron.down")
}
.padding()
.background(RoundedRectangle(cornerRadius: 8).stroke(Color.gray))
}
.background(
GeometryReader { geo in
Color.clear
.onAppear {
frame = geo.frame(in: .global)
}
}
)
}
}
struct GlobalDropdownOverlay: View {
@ObservedObject var manager = DropdownManager.shared
var body: some View {
if manager.isPresented {
ZStack(alignment: .topLeading) {
Color.black.opacity(0.001)
.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)
}
.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)
}
}
}
}

View File

@ -24,19 +24,35 @@ struct RegisterView: View {
@State private var scrollOffset: CGPoint = .zero @State private var scrollOffset: CGPoint = .zero
@State private var showWebView = false @State private var showWebView = false
@State private var isLoading = false
@State private var isSelectAddr: Bool = false
private let addressBtnID = UUID()
private let registerBtnID = UUID()
let addressBtnID = UUID() @State private var selected = ""
let registerBtnID = UUID() let options = ["Swift", "Kotlin", "Dart", "JavaScript"]
var body: some View { var body: some View {
// MARK: TO-DO // MARK: TO-DO
// //
// , , , , // , , , ,
ZStack {
VStack(spacing: 0) { VStack(spacing: 0) {
TopView(topVM: topVM) TopView(topVM: topVM)
HStack(spacing: 0) {
Spacer(minLength: 1)
Text("*")
.font(.nps(size: 12))
.foregroundStyle(Color(.Other.red))
Text("는 필수 입력 사항입니다.")
.font(.nps(size: 12))
}
.padding(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 16))
DropdownButton(title: "언어 선택", items: ["Swift", "Dart", "Kotlin"])
OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { proxy in OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { proxy in
@ -49,9 +65,9 @@ struct RegisterView: View {
.font(.nps(size: 16)) .font(.nps(size: 16))
.foregroundStyle(Color(.Other.red)) .foregroundStyle(Color(.Other.red))
} }
.frame(width: 60, alignment: .leading) .frame(width: 60, alignment: .center)
Spacer(minLength: 1) Spacer(minLength: 1)
CustomTextField(placeholder: "최대 10글자", text: $registerVM.nameText) CustomTextField(placeholder: "최대 10글자 입력", text: $registerVM.nameText, alignment: .center)
.frame(maxWidth: .infinity,maxHeight: 48) .frame(maxWidth: .infinity,maxHeight: 48)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20)) .padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
.background { .background {
@ -59,23 +75,35 @@ struct RegisterView: View {
.foregroundStyle(Color(.Normal.light)) .foregroundStyle(Color(.Normal.light))
} }
} }
.padding() .padding(EdgeInsets(top: 8, leading: 16, bottom: 16, trailing: 16))
// //
DatePicker("생일", selection: $registerVM.selectDate, displayedComponents: [.date]) HStack(spacing: 0){
Text("생일")
.font(.nps(size: 16))
.frame(width: 60, alignment: .center)
Spacer(minLength: 1)
DatePicker("", selection: $registerVM.selectDate, displayedComponents: [.date])
.datePickerStyle(.compact) .datePickerStyle(.compact)
.environment(\.locale, Locale(identifier: "ko_KR")) .environment(\.locale, Locale(identifier: "ko_KR"))
.font(.nps(size: 16)) .font(.nps(size: 16))
}
.padding() .padding()
// E-Mail // E-Mail
HStack(spacing: 0){
HStack(spacing: 0) { HStack(spacing: 0) {
Text("이메일") Text("이메일")
.font(.nps(size: 16)) .font(.nps(size: 16))
.frame(width: 60, alignment: .leading) Text("*")
.font(.nps(size: 16))
.foregroundStyle(Color(.Other.red))
}
Spacer(minLength: 1) Spacer(minLength: 1)
CustomTextField(placeholder: "앞부분 입력", text: $registerVM.emailFrontText) CustomTextField(placeholder: "앞부분 입력", text: $registerVM.emailFrontText, alignment: .center)
.frame(maxWidth: .infinity,maxHeight: 48)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20)) .padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
.background { .background {
RoundedRectangle(cornerRadius: 24) RoundedRectangle(cornerRadius: 24)
@ -83,10 +111,10 @@ struct RegisterView: View {
} }
// Spacer(minLength: 1) // Spacer(minLength: 1)
Text("@") Text("@")
.font(.nps(font: .bold, size: 16)) .font(.nps(size: 16))
.padding([.leading, .trailing], 4) .padding([.leading, .trailing], 4)
// Spacer(minLength: 1) // Spacer(minLength: 1)
CustomTextField(placeholder: "뒷부분 입력", text: $registerVM.emailTailText) CustomTextField(placeholder: "뒷부분 입력", text: $registerVM.emailTailText, alignment: .center)
.frame(maxWidth: .infinity,maxHeight: 48) .frame(maxWidth: .infinity,maxHeight: 48)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20)) .padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
.background { .background {
@ -97,12 +125,16 @@ struct RegisterView: View {
.padding() .padding()
// Phone // Phone
HStack(spacing: 0){
HStack(spacing: 0) { HStack(spacing: 0) {
Text("연락처") Text("연락처")
.font(.nps(size: 16)) .font(.nps(size: 16))
.frame(width: 60, alignment: .leading) Text("*")
.font(.nps(size: 16))
.foregroundStyle(Color(.Other.red))
}
CustomTextField(placeholder: "000", text: $registerVM.nameText) CustomTextField(placeholder: "000", text: $registerVM.nameText, alignment: .center)
.frame(maxWidth: .infinity,maxHeight: 48) .frame(maxWidth: .infinity,maxHeight: 48)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20)) .padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
.background { .background {
@ -112,7 +144,7 @@ struct RegisterView: View {
Text("-") Text("-")
.font(.nps(size: 16)) .font(.nps(size: 16))
.padding([.leading, .trailing], 4) .padding([.leading, .trailing], 4)
CustomTextField(placeholder: "0000", text: $registerVM.nameText) CustomTextField(placeholder: "0000", text: $registerVM.nameText, alignment: .center)
.frame(maxWidth: .infinity,maxHeight: 48) .frame(maxWidth: .infinity,maxHeight: 48)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20)) .padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
.background { .background {
@ -122,7 +154,7 @@ struct RegisterView: View {
Text("-") Text("-")
.font(.nps(size: 16)) .font(.nps(size: 16))
.padding([.leading, .trailing], 4) .padding([.leading, .trailing], 4)
CustomTextField(placeholder: "0000", text: $registerVM.nameText) CustomTextField(placeholder: "0000", text: $registerVM.nameText, alignment: .center)
.frame(maxWidth: .infinity,maxHeight: 48) .frame(maxWidth: .infinity,maxHeight: 48)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20)) .padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
.background { .background {
@ -133,25 +165,47 @@ struct RegisterView: View {
.padding() .padding()
HStack(spacing: 0){ HStack(spacing: 0){
Text("연락처") Text("주소")
.font(.nps(size: 16)) .font(.nps(size: 16))
.frame(width: 60, alignment: .leading) .frame(width: 60, alignment: .center)
Spacer(minLength: 1) Spacer(minLength: 1)
SimpleBtnView(vm: btnVM, id: addressBtnID)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
VStack(spacing: 0) {
SimpleBtnView(vm: btnVM, id: addressBtnID)
.padding(.bottom, isSelectAddr ? 4:0)
CustomTextField(placeholder: "상세 주소 입력", text: $registerVM.addrDetailText, alignment: .center)
.frame(maxWidth: .infinity,
maxHeight: isSelectAddr ? 48 : 0)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
.background {
RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light))
}
.opacity(isSelectAddr ? 1.0 : 0.0)
}
} }
.padding() .padding()
// address
} }
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
.sheet(isPresented: $showWebView) { // .background(Color(.Normal.light))
WebView(isLoding: $showWebView)
.edgesIgnoringSafeArea(.all)
} }
GlobalDropdownOverlay()
}
.sheet(isPresented: $showWebView) {
WebView(url: URL(string: "https://sean-59.github.io/Kakao-Postcode/")!,
showWebView: $showWebView,
isLoading: $isLoading) { result in
if let result = result as? (String, String) {
print(result.0)
isSelectAddr = true
// printLog(registerVM.addressText)
registerVM.addressText = result.0
btnVM.setSize(for: addressBtnID, newWidth: .infinity, newHeight: .infinity)
// printLog(registerVM.addressText)
btnVM.setText(for: addressBtnID, newText: "\(registerVM.addressText)", newFont: .nps(size: 16))
}
}
} }
.onAppear { .onAppear {
topVM.titleName = "회원가입" topVM.titleName = "회원가입"
@ -159,11 +213,12 @@ struct RegisterView: View {
topVM.setRightBtn(size: CGPoint(x: 40, y: 40), action: rightAct) topVM.setRightBtn(size: CGPoint(x: 40, y: 40), action: rightAct)
btnVM.setSize(for: addressBtnID, newWidth: 80, newHeight: 24) btnVM.setSize(for: addressBtnID, newWidth: 80, newHeight: 24)
btnVM.setText(for: addressBtnID, newText: "주소 입력", newFont: .nps(size: 16)) btnVM.setText(for: addressBtnID, newText: "\(registerVM.addressText)", newFont: .nps(size: 16))
btnVM.setAction(for: addressBtnID) { btnVM.setAction(for: addressBtnID) {
// self.appVM.isLoading.toggle()
self.showWebView.toggle() self.showWebView.toggle()
} }
}
.onChange(of: registerVM.addressText) { _, new in
} }
} }

View File

@ -25,7 +25,8 @@ class RegisterViewModel: ObservableObject {
@State var emailFrontText: String = "" @State var emailFrontText: String = ""
@State var emailTailText: String = "" @State var emailTailText: String = ""
@State var phoneArray: [Int] = [] @State var phoneArray: [Int] = []
@State var addressText: String = "" @Published var addressText: String = "주소 입력"
@State var addrDetailText: String = ""
} }

View File

@ -16,6 +16,7 @@ struct CustomTextField: UIViewRepresentable {
var textColor = UIColor(Color(.Text.detail)) var textColor = UIColor(Color(.Text.detail))
var font = UIFont(name: "NPS-font-Regular", size: 16) var font = UIFont(name: "NPS-font-Regular", size: 16)
var alignment: NSTextAlignment = .left
// [] UIView // [] UIView
func makeUIView(context: Context) -> UITextField { func makeUIView(context: Context) -> UITextField {
@ -37,6 +38,7 @@ struct CustomTextField: UIViewRepresentable {
txf.autocapitalizationType = .none txf.autocapitalizationType = .none
txf.smartInsertDeleteType = .no txf.smartInsertDeleteType = .no
txf.textContentType = .oneTimeCode txf.textContentType = .oneTimeCode
txf.textAlignment = self.alignment
txf.delegate = context.coordinator txf.delegate = context.coordinator
return txf return txf