diff --git a/AcaMate.xcodeproj/project.xcworkspace/xcuserdata/tanine.xcuserdatad/UserInterfaceState.xcuserstate b/AcaMate.xcodeproj/project.xcworkspace/xcuserdata/tanine.xcuserdatad/UserInterfaceState.xcuserstate index 7c2929d..d006572 100644 Binary files a/AcaMate.xcodeproj/project.xcworkspace/xcuserdata/tanine.xcuserdatad/UserInterfaceState.xcuserstate and b/AcaMate.xcodeproj/project.xcworkspace/xcuserdata/tanine.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/AcaMate/1. View/10. Common/101. Button/CircleBtnView.swift b/AcaMate/1. View/10. Common/101. Button/CircleBtnView.swift index a679888..3a78e60 100644 --- a/AcaMate/1. View/10. Common/101. Button/CircleBtnView.swift +++ b/AcaMate/1. View/10. Common/101. Button/CircleBtnView.swift @@ -45,6 +45,7 @@ struct CircleBtnView: View { } .frame(width: state.width, height: state.height) .onTapGesture { + endTextEditing() guard let action = state.action else {return} action() } diff --git a/AcaMate/1. View/10. Common/101. Button/SimpleBtnView.swift b/AcaMate/1. View/10. Common/101. Button/SimpleBtnView.swift index 4a155e3..7e0d46e 100644 --- a/AcaMate/1. View/10. Common/101. Button/SimpleBtnView.swift +++ b/AcaMate/1. View/10. Common/101. Button/SimpleBtnView.swift @@ -23,6 +23,7 @@ struct SimpleBtnView: View { .frame(width: state.width, height: state.height) .onTapGesture { if state.isUsable { + endTextEditing() guard let action = state.action else { return } action() } @@ -30,6 +31,7 @@ struct SimpleBtnView: View { } else { Button{ + endTextEditing() guard let action = state.action else { return } action() } label: { diff --git a/AcaMate/1. View/10. Common/DropDownView.swift b/AcaMate/1. View/10. Common/DropDownView.swift index e166836..3f6023f 100644 --- a/AcaMate/1. View/10. Common/DropDownView.swift +++ b/AcaMate/1. View/10. Common/DropDownView.swift @@ -13,9 +13,10 @@ class DropdownManager: ObservableObject { @Published var anchor: CGRect = .zero @Published var font: Font = .body - var onSelect: ((String) -> Void)? + var onSelect: ((Int) -> Void)? - func show(items: [String], anchor: CGRect, onSelect: @escaping (String) -> Void) { + func show(items: [String], anchor: CGRect, + onSelect: @escaping (Int) -> Void) { self.items = items self.anchor = anchor self.onSelect = onSelect @@ -32,22 +33,28 @@ class DropdownManager: ObservableObject { struct DropdownButton: View { @ObservedObject var manager: DropdownManager @State var title: String + + var onSelect: ((Int) -> Void)? let items: [String] - init(manager: DropdownManager, title: String, items: [String]) { + init(manager: DropdownManager, title: String, items: [String], + onSelect: @escaping (Int) -> Void) { self.manager = manager self.title = title self.items = items + self.onSelect = onSelect } @State private var frame: CGRect = .zero var body: some View { Button { - manager.show(items: items, anchor: frame) { selected in - print("선택한 항목:", selected) - title = selected + endTextEditing() + manager.show(items: items, anchor: frame) { index in + self.onSelect?(index) + print("선택한 항목: \(items[index])") + title = items[index] } } label: { HStack(alignment: .center) { @@ -62,7 +69,10 @@ struct DropdownButton: View { Spacer() } .padding(4) - .background(RoundedRectangle(cornerRadius: 8).stroke(Color(.Normal.normal))) + .background{ + RoundedRectangle(cornerRadius: 8) + .fill(Color.clear) + } } .buttonStyle(.plain) .background( @@ -131,16 +141,16 @@ struct GlobalDropdownOverlay: View { VStack(alignment: .leading, spacing: 0) { ForEach(Array(manager.items.enumerated()), id: \.offset) { index, item in Button { - manager.onSelect?(item) + manager.onSelect?(index) manager.dismiss() } label: { Text(item) .font(manager.font) .lineLimit(1) - .minimumScaleFactor(0.5) + .minimumScaleFactor(0.4) + .padding([.leading, .trailing], 8) .frame(maxWidth: .infinity, minHeight: 40, maxHeight: 40, alignment: .center) - .padding([.leading,.trailing], 2) - .contentShape(Rectangle()) + .contentShape(Rectangle()) } .buttonStyle(.plain) diff --git a/AcaMate/1. View/11. Intro & Login/RegisterView.swift b/AcaMate/1. View/11. Intro & Login/RegisterView.swift index a5b966a..9358e4f 100644 --- a/AcaMate/1. View/11. Intro & Login/RegisterView.swift +++ b/AcaMate/1. View/11. Intro & Login/RegisterView.swift @@ -13,12 +13,10 @@ struct RegisterView: View { @StateObject var btnVM = ButtonViewModel() @StateObject var registerVM: RegisterViewModel - private let responseValue: (SNSLoginType, String) +// private let responseValue: (SNSLoginType, String) init(_ appVM: AppViewModel, type: SNSLoginType, snsID: String) { - _registerVM = StateObject(wrappedValue: RegisterViewModel(appVM)) - self.responseValue.0 = type - self.responseValue.1 = snsID + _registerVM = StateObject(wrappedValue: RegisterViewModel(appVM, type: type, snsID: snsID)) } @@ -30,11 +28,9 @@ struct RegisterView: View { @StateObject private var dropdownManager = DropdownManager() - private let addressBtnID = UUID() - private let registerBtnID = UUID() - @State private var selected = "" - let options = ["Swift", "Kotlin", "Dart", "JavaScript", "C#","C++","C"] + @State private var onDomainTxf = false + var body: some View { // MARK: TO-DO @@ -67,13 +63,13 @@ struct RegisterView: View { .frame(width: 60, alignment: .center) .padding(.trailing,12) Spacer(minLength: 1) - CustomTextField(placeholder: "최대 10글자 입력", text: $registerVM.nameText, alignment: .center) + CustomTxfView(placeholder: "이름 입력 (10 글자)", text: $registerVM.nameText, maxLength: 10, alignment: .center) .frame(maxWidth: .infinity,maxHeight: 48) - .padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20)) + .padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)) .background { RoundedRectangle(cornerRadius: 24) .foregroundStyle(Color(.Normal.light)) - } + }.clipped() } .padding(EdgeInsets(top: 8, leading: 16, bottom: 16, trailing: 16)) @@ -103,37 +99,48 @@ struct RegisterView: View { .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 { - RoundedRectangle(cornerRadius: 24) - .foregroundStyle(Color(.Normal.light)) - } - // Spacer(minLength: 1) - Text("@") - .font(.nps(size: 16)) - .padding([.leading, .trailing], 4) - // Spacer(minLength: 1) - DropdownButton(manager: dropdownManager, title: "도메인 선택", - items: registerVM.emailTailList) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background { - RoundedRectangle(cornerRadius: 24) - .foregroundStyle(Color(.Normal.light)) + VStack (spacing: 4) { + CustomTxfView(placeholder: "이메일 입력(30 글자)", text: $registerVM.emailFrontText, maxLength: 30, alignment: .center) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)) + .background { + RoundedRectangle(cornerRadius: 24) + .foregroundStyle(Color(.Normal.light)) + } + + HStack(spacing: 0) { + Text("@") + .font(.nps(size: 16)) + .padding([.leading, .trailing], 4) + // Spacer(minLength: 1) + + DropdownButton(manager: dropdownManager, title: "도메인 선택", + items: registerVM.emailTailList){ index in + if registerVM.emailTailList.count-1 == index { + onDomainTxf = true + } else { + registerVM.emailTailText = registerVM.emailTailList[index] + onDomainTxf = false + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background { + RoundedRectangle(cornerRadius: 24) + .foregroundStyle(Color(.Normal.light)) + } } + if onDomainTxf { + CustomTxfView(placeholder: "도메인 직접 입력(30 글자)", text: $registerVM.emailTailText, maxLength: 30, alignment: .center) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)) + .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() @@ -148,26 +155,20 @@ struct RegisterView: View { } .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)) - } + DropdownButton(manager: dropdownManager, title: "선택", items: registerVM.numberHeadList){ index in + registerVM.phoneTextSet.0 = registerVM.numberHeadList[index] + } + .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) + CustomTxfView(placeholder: "0000", text: $registerVM.phoneTextSet.1, maxLength: 4, alignment: .center) .frame(maxWidth: .infinity,maxHeight: 48) - .padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20)) + .padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)) .background { RoundedRectangle(cornerRadius: 24) .foregroundStyle(Color(.Normal.light)) @@ -175,9 +176,9 @@ 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)) + CustomTxfView(placeholder: "0000", text: $registerVM.phoneTextSet.2, maxLength: 4, alignment: .center) + .frame(maxWidth: .infinity, maxHeight: 48) + .padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)) .background { RoundedRectangle(cornerRadius: 24) .foregroundStyle(Color(.Normal.light)) @@ -185,31 +186,44 @@ struct RegisterView: View { } .padding() - HStack(spacing: 0){ + HStack(alignment: .center, spacing: 0){ Text("주소") .font(.nps(size: 16)) .frame(width: 60, alignment: .center) .padding(.trailing,12) - 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)) + SimpleBtnView(vm: btnVM, id: registerVM.addressBtnID) + .frame(maxWidth: .infinity, maxHeight: 48) + .padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)) .background { RoundedRectangle(cornerRadius: 24) .foregroundStyle(Color(.Normal.light)) } - .opacity(isSelectAddr ? 1.0 : 0.0) + .padding(.bottom, isSelectAddr ? 4:0) + if isSelectAddr { + CustomTxfView(placeholder: "상세 주소 입력 (50 글자)", text: $registerVM.addrDetailText, maxLength: 50, alignment: .center) + .frame(maxWidth: .infinity, + maxHeight: isSelectAddr ? 48 : 0) + .padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)) + .background { + RoundedRectangle(cornerRadius: 24) + .foregroundStyle(Color(.Normal.light)) + } + } } } .padding() } .frame(maxWidth: .infinity, maxHeight: .infinity) - // .background(Color(.Normal.light)) + + SimpleBtnView(vm: btnVM, id: registerVM.registerBtnID) + .frame(maxWidth: .infinity, maxHeight: 48) + .padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)) +// .background { +// RoundedRectangle(cornerRadius: 24) +// .foregroundStyle(Color(.Normal.light)) +// } } GlobalDropdownOverlay(manager: dropdownManager) } @@ -221,30 +235,36 @@ struct RegisterView: View { 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)) + btnVM.setSize(for: registerVM.addressBtnID, newWidth: .infinity, newHeight: .infinity) + btnVM.setText(for: registerVM.addressBtnID, newText: "\(registerVM.addressText)", newFont: .nps(size: 16)) } } } .onAppear { topVM.titleName = "회원가입" + topVM.setLeftBtn(Image(.Icon.left), size: CGPoint(x: 40, y: 40), action: leftAct) topVM.setRightBtn(size: CGPoint(x: 40, y: 40), action: rightAct) - btnVM.setSize(for: addressBtnID, newWidth: 80, newHeight: 24) - btnVM.setText(for: addressBtnID, newText: "\(registerVM.addressText)", newFont: .nps(size: 16)) - btnVM.setAction(for: addressBtnID) { - self.showWebView.toggle() - } + btnVM.setSize(for: registerVM.addressBtnID, newWidth: 80, newHeight: 24) + btnVM.setText(for: registerVM.addressBtnID, newText: "\(registerVM.addressText)", newFont: .nps(size: 16)) + btnVM.setTextColor(for: registerVM.addressBtnID, newColor: Color.Text.black) + btnVM.setAction(for: registerVM.addressBtnID) { self.showWebView.toggle() } + + btnVM.setSize(for: registerVM.registerBtnID, newWidth: .infinity, newHeight: 48) + btnVM.setText(for: registerVM.registerBtnID, newText: "회원가입", newFont: .nps(font: .bold, size: 24)) + btnVM.setTextColor(for: registerVM.registerBtnID, newColor: Color.Point.dark) + btnVM.setAction(for: registerVM.registerBtnID) { registerVM.registerUser() } + + dropdownManager.font = .nps(size: 16) } - .onChange(of: registerVM.addressText) { _, new in - - } + // .onChange(of: dropdownManager.onSelect) { _, new in + // printLog("CHANGE: \(new)") + // + // } } func leftAct() { diff --git a/AcaMate/3. ViewModel/AppViewModel.swift b/AcaMate/3. ViewModel/AppViewModel.swift index 14086fb..8c68cbe 100644 --- a/AcaMate/3. ViewModel/AppViewModel.swift +++ b/AcaMate/3. ViewModel/AppViewModel.swift @@ -16,7 +16,7 @@ class AppViewModel: ObservableObject { @Published var menuName: MenuName = .Home @Published var naviState: NaviState = .init(act: .NONE, path: .Intro) - var alertData: AlertData = .init(body: "") + @Published var alertData: AlertData = .init(body: "") /// 항상 최신값을 가지고 있다가 구독자 추가 되면 그 즉시 값을 전달하고 이후 업데이트 되는 값을 계속 보내주는 역할을 함 let alertAction = CurrentValueSubject(nil) diff --git a/AcaMate/3. ViewModel/RegisterViewModel.swift b/AcaMate/3. ViewModel/RegisterViewModel.swift index 2b4125c..5f605f2 100644 --- a/AcaMate/3. ViewModel/RegisterViewModel.swift +++ b/AcaMate/3. ViewModel/RegisterViewModel.swift @@ -9,24 +9,38 @@ import SwiftUI import Combine class RegisterViewModel: ObservableObject { - private let appVM: AppViewModel private var cancellables = Set() + private let appVM: AppViewModel + private let responseValue: (SNSLoginType, String) - init(_ appVM: AppViewModel) { + init(_ appVM: AppViewModel, type: SNSLoginType, snsID: String) { self.appVM = appVM + self.responseValue = (type, snsID) } - @State var selectDate: Date = { - let calendar = Calendar.current - return calendar.date(byAdding: .year, value: -12, to: Date()) ?? Date() - }() + let addressBtnID = UUID() + let registerBtnID = UUID() - @State var nameText: String = "" - @State var emailFrontText: String = "" - @State var emailTailText: String = "" - @State var phoneArray: [Int] = [] + @Published var selectDate: Date = { + let calendar = Calendar.current + return calendar.date(byAdding: .year, value: 0, to: Date()) ?? Date() + }() { + didSet { + if selectDate != oldValue { + changeDate = true + } + } + } + + @Published var changeDate: Bool = false + + @Published var nameText: String = "" + @Published var emailFrontText: String = "" + @Published var emailTailText: String = "" + @Published var numberHead: String = "" + @Published var phoneTextSet: (String,String,String) = ("","","") @Published var addressText: String = "주소 입력" - @State var addrDetailText: String = "" + @Published var addrDetailText: String = "" let numberHeadList = ["010","011","016","017","018","019"] let emailTailList = ["gmail.com", @@ -41,4 +55,30 @@ class RegisterViewModel: ObservableObject { "protonmail.com", "직접 입력"] + func registerUser() { + // 필수 값 + + if nameText != "" && emailFrontText != "" && emailTailText != "" && phoneTextSet.0 != "" && phoneTextSet.1 != "" && phoneTextSet.2 != "" { + if !changeDate || addressText == "주소 입력" { + appVM.alertData = AlertData( + title: "알림", + body: "\(changeDate ? "":"[생일]")\((addressText != "주소 입력") ? "":"[주소]")의 내용이 없습니다.\n 계속해서 진행할까요?", + button: [ButtonType(name: "확인", role: .cancel, function: nil)]) + appVM.showAlert.toggle() + // 넘어가는 로직도 추가 + } else { + //정상 동작 + } + + } + else { + appVM.alertData = AlertData( + title: "경고", body: "필수 입력 사항이 누락되었습니다.", + button: [ButtonType(name: "확인", role: .cancel, function: nil)]) + appVM.showAlert.toggle() + } + + + } + } diff --git a/AcaMate/6. Modifier/TextField.swift b/AcaMate/6. Modifier/TextField.swift index df72883..f9c61e8 100644 --- a/AcaMate/6. Modifier/TextField.swift +++ b/AcaMate/6. Modifier/TextField.swift @@ -8,10 +8,69 @@ import SwiftUI import UIKit -struct CustomTextField: UIViewRepresentable { +struct FixedSizeWrapper: UIViewRepresentable { + let content: () -> Content + let width: CGFloat + let height: CGFloat + + func makeUIView(context: Context) -> Content { + let view = content() + view.translatesAutoresizingMaskIntoConstraints = false + + + NSLayoutConstraint.activate([ + view.widthAnchor.constraint(equalToConstant: width), + view.heightAnchor.constraint(equalToConstant: height) + ]) + return view + } + + func updateUIView(_ uiView: Content, context: Context) {} +} + +struct CustomTxfView: View { + var placeholder: String @Binding var text: String + var maxLength: Int = 100 + var isSecure: Binding = .constant(false) + var alignment: TextAlignment = .leading + + var textColor = Color(.Text.detail) + var font: Font = .nps(size:16) +// UIFont(name: "NPS-font-Regular", size: 16) + + + var body: some View { + TextField(placeholder, text: $text) +// Binding( +// get: { text }, +// set: { newValue in +// text = String(newValue.prefix(maxLength)) +// } +// )) + .font(font) + .tint(textColor) + .lineLimit(1) + .multilineTextAlignment(alignment) + .minimumScaleFactor(0.5) + .truncationMode(.tail) + .clipped() + + .onChange(of: text) { old, new in + if new.count > maxLength { + text = String(new.prefix(maxLength)) + } + } + } +} + + +struct CustomTextField: UIViewRepresentable { + var placeholder: String + @Binding var text: String + var maxLength: Int? var isSecure: Binding = .constant(false) var textColor = UIColor(Color(.Text.detail)) @@ -21,24 +80,31 @@ struct CustomTextField: UIViewRepresentable { // [필수] 초기화 시 UIView 생성 func makeUIView(context: Context) -> UITextField { let txf = UITextField() -// txf.placeholder = placeholder - txf.attributedPlaceholder = NSAttributedString(string: "\(placeholder)", attributes: [NSAttributedString.Key.foregroundColor : UIColor(.Text.border)]) + + + // txf.placeholder = placeholder + txf.attributedPlaceholder = NSAttributedString( + string: "\(placeholder)", + attributes: [NSAttributedString.Key.foregroundColor : UIColor(.Text.border)]) txf.isSecureTextEntry = isSecure.wrappedValue - + + txf.adjustsFontSizeToFitWidth = true + txf.minimumFontSize = 4 + txf.textColor = textColor - txf.font = font - - txf.translatesAutoresizingMaskIntoConstraints = false - let height = (font?.lineHeight ?? 10) + 8 txf.frame.size.height = height + txf.borderStyle = .none txf.autocorrectionType = .no txf.autocapitalizationType = .none txf.smartInsertDeleteType = .no txf.textContentType = .oneTimeCode txf.textAlignment = self.alignment + txf.translatesAutoresizingMaskIntoConstraints = false + txf.setContentHuggingPriority(.required, for: .horizontal) + txf.setContentCompressionResistancePriority(.required, for: .horizontal) txf.delegate = context.coordinator return txf @@ -51,19 +117,34 @@ struct CustomTextField: UIViewRepresentable { } func makeCoordinator() -> Coordinator { - Coordinator(self) + Coordinator(self, max: maxLength) } class Coordinator: NSObject, UITextFieldDelegate { var parent: CustomTextField + var maxLength: Int? - init(_ parent: CustomTextField) { + init(_ parent: CustomTextField, max: Int?) { self.parent = parent + self.maxLength = max } func textFieldDidChangeSelection(_ textField: UITextField) { parent.text = textField.text ?? "" } + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + let current = textField.text ?? "" + guard let stringRange = Range(range, in: current) else { return false} + let updated = current.replacingCharacters(in: stringRange, with: string) + return updated.count <= (maxLength ?? 9999) + } + + // func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + // let currentText = textField.text ?? "" + // guard let stringRange = Range(range, in: currentText) else { return false } + // let updatedText = currentText.replacingCharacters(in: stringRange, with: string) + // return updatedText.count <= maxLength + // } } }