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

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
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 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 {
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() {
super.viewDidLoad()
webView()
}
func webView() {
let url = URL(string: "https://sean-59.github.io/Kakao-Postcode/")!
private func webView() {
let request = URLRequest(url: url)
let configuration = WKWebViewConfiguration()
let contentController = WKUserContentController()
contentController.add(self, name: "callBackHandler")
contentController.add(coordinator, name: "callBackHandler")
configuration.userContentController = contentController
let webview = WKWebView(frame: view.bounds, configuration: configuration)
webview.uiDelegate = self
webview.navigationDelegate = self
webview.navigationDelegate = coordinator
webview.autoresizingMask = [.flexibleWidth, .flexibleHeight]
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,134 +24,188 @@ struct RegisterView: View {
@State private var scrollOffset: CGPoint = .zero
@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()
let registerBtnID = UUID()
@State private var selected = ""
let options = ["Swift", "Kotlin", "Dart", "JavaScript"]
var body: some View {
// MARK: TO-DO
//
// , , , ,
VStack(spacing: 0) {
TopView(topVM: topVM)
OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { proxy in
ZStack {
VStack(spacing: 0) {
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))
//
HStack(spacing: 0){
HStack(spacing: 0) {
Text("이름")
.font(.nps(size: 16))
Text("*")
.font(.nps(size: 16))
.foregroundStyle(Color(.Other.red))
DropdownButton(title: "언어 선택", items: ["Swift", "Dart", "Kotlin"])
OffsetObservableScrollView(showsIndicators: false, scrollOffset: $scrollOffset) { proxy in
//
HStack(spacing: 0){
HStack(spacing: 0) {
Text("이름")
.font(.nps(size: 16))
Text("*")
.font(.nps(size: 16))
.foregroundStyle(Color(.Other.red))
}
.frame(width: 60, alignment: .center)
Spacer(minLength: 1)
CustomTextField(placeholder: "최대 10글자 입력", 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))
}
}
.padding(EdgeInsets(top: 8, leading: 16, bottom: 16, trailing: 16))
//
HStack(spacing: 0){
Text("생일")
.font(.nps(size: 16))
.frame(width: 60, alignment: .center)
Spacer(minLength: 1)
DatePicker("", selection: $registerVM.selectDate, displayedComponents: [.date])
.datePickerStyle(.compact)
.environment(\.locale, Locale(identifier: "ko_KR"))
.font(.nps(size: 16))
}
.frame(width: 60, alignment: .leading)
Spacer(minLength: 1)
CustomTextField(placeholder: "최대 10글자", text: $registerVM.nameText)
.frame(maxWidth: .infinity,maxHeight: 48)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
.background {
RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light))
}
}
.padding()
//
DatePicker("생일", selection: $registerVM.selectDate, displayedComponents: [.date])
.datePickerStyle(.compact)
.environment(\.locale, Locale(identifier: "ko_KR"))
.font(.nps(size: 16))
.padding()
// E-Mail
HStack(spacing: 0){
Text("이메일")
.font(.nps(size: 16))
.frame(width: 60, alignment: .leading)
Spacer(minLength: 1)
CustomTextField(placeholder: "앞부분 입력", text: $registerVM.emailFrontText)
.frame(maxWidth: .infinity,maxHeight: 48)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
.background {
RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light))
}
// Spacer(minLength: 1)
Text("@")
.font(.nps(font: .bold, size: 16))
.padding([.leading, .trailing], 4)
// Spacer(minLength: 1)
CustomTextField(placeholder: "뒷부분 입력", text: $registerVM.emailTailText)
.frame(maxWidth: .infinity,maxHeight: 48)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
.background {
RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light))
}
}
.padding()
// Phone
HStack(spacing: 0){
Text("연락처")
.font(.nps(size: 16))
.frame(width: 60, alignment: .leading)
CustomTextField(placeholder: "000", text: $registerVM.nameText)
.frame(maxWidth: .infinity,maxHeight: 48)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
.background {
RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light))
// E-Mail
HStack(spacing: 0){
HStack(spacing: 0) {
Text("이메일")
.font(.nps(size: 16))
Text("*")
.font(.nps(size: 16))
.foregroundStyle(Color(.Other.red))
}
Text("-")
.font(.nps(size: 16))
.padding([.leading, .trailing], 4)
CustomTextField(placeholder: "0000", text: $registerVM.nameText)
.frame(maxWidth: .infinity,maxHeight: 48)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
.background {
RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light))
}
Text("-")
.font(.nps(size: 16))
.padding([.leading, .trailing], 4)
CustomTextField(placeholder: "0000", text: $registerVM.nameText)
.frame(maxWidth: .infinity,maxHeight: 48)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
.background {
RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light))
}
}
.padding()
HStack(spacing: 0){
Text("연락처")
.font(.nps(size: 16))
.frame(width: 60, alignment: .leading)
Spacer(minLength: 1)
SimpleBtnView(vm: btnVM, id: addressBtnID)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
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 {
RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light))
}
// Spacer(minLength: 1)
Text("@")
.font(.nps(size: 16))
.padding([.leading, .trailing], 4)
// Spacer(minLength: 1)
CustomTextField(placeholder: "뒷부분 입력", text: $registerVM.emailTailText, 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))
}
}
.padding()
// Phone
HStack(spacing: 0){
HStack(spacing: 0) {
Text("연락처")
.font(.nps(size: 16))
Text("*")
.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))
.background {
RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light))
}
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))
.background {
RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light))
}
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))
.background {
RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light))
}
}
.padding()
HStack(spacing: 0){
Text("주소")
.font(.nps(size: 16))
.frame(width: 60, alignment: .center)
Spacer(minLength: 1)
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)
// .background(Color(.Normal.light))
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.sheet(isPresented: $showWebView) {
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 {
topVM.titleName = "회원가입"
@ -159,11 +213,12 @@ struct RegisterView: View {
topVM.setRightBtn(size: CGPoint(x: 40, y: 40), action: rightAct)
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) {
// self.appVM.isLoading.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 emailTailText: String = ""
@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 font = UIFont(name: "NPS-font-Regular", size: 16)
var alignment: NSTextAlignment = .left
// [] UIView
func makeUIView(context: Context) -> UITextField {
@ -37,6 +38,7 @@ struct CustomTextField: UIViewRepresentable {
txf.autocapitalizationType = .none
txf.smartInsertDeleteType = .no
txf.textContentType = .oneTimeCode
txf.textAlignment = self.alignment
txf.delegate = context.coordinator
return txf