[] 드롭다운 최종 완성, 커스텀 텍스트 필드 전체 변경, 회원가입 로직 추가 중

This commit is contained in:
김선규 2025-03-27 17:55:32 +09:00
parent 4875427e24
commit 63031172a1
8 changed files with 266 additions and 112 deletions

View File

@ -45,6 +45,7 @@ struct CircleBtnView: View {
} }
.frame(width: state.width, height: state.height) .frame(width: state.width, height: state.height)
.onTapGesture { .onTapGesture {
endTextEditing()
guard let action = state.action else {return} guard let action = state.action else {return}
action() action()
} }

View File

@ -23,6 +23,7 @@ struct SimpleBtnView: View {
.frame(width: state.width, height: state.height) .frame(width: state.width, height: state.height)
.onTapGesture { .onTapGesture {
if state.isUsable { if state.isUsable {
endTextEditing()
guard let action = state.action else { return } guard let action = state.action else { return }
action() action()
} }
@ -30,6 +31,7 @@ struct SimpleBtnView: View {
} }
else { else {
Button{ Button{
endTextEditing()
guard let action = state.action else { return } guard let action = state.action else { return }
action() action()
} label: { } label: {

View File

@ -13,9 +13,10 @@ class DropdownManager: ObservableObject {
@Published var anchor: CGRect = .zero @Published var anchor: CGRect = .zero
@Published var font: Font = .body @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.items = items
self.anchor = anchor self.anchor = anchor
self.onSelect = onSelect self.onSelect = onSelect
@ -32,22 +33,28 @@ class DropdownManager: ObservableObject {
struct DropdownButton: View { struct DropdownButton: View {
@ObservedObject var manager: DropdownManager @ObservedObject var manager: DropdownManager
@State var title: String @State var title: String
var onSelect: ((Int) -> Void)?
let items: [String] 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.manager = manager
self.title = title self.title = title
self.items = items self.items = items
self.onSelect = onSelect
} }
@State private var frame: CGRect = .zero @State private var frame: CGRect = .zero
var body: some View { var body: some View {
Button { Button {
manager.show(items: items, anchor: frame) { selected in endTextEditing()
print("선택한 항목:", selected) manager.show(items: items, anchor: frame) { index in
title = selected self.onSelect?(index)
print("선택한 항목: \(items[index])")
title = items[index]
} }
} label: { } label: {
HStack(alignment: .center) { HStack(alignment: .center) {
@ -62,7 +69,10 @@ struct DropdownButton: View {
Spacer() Spacer()
} }
.padding(4) .padding(4)
.background(RoundedRectangle(cornerRadius: 8).stroke(Color(.Normal.normal))) .background{
RoundedRectangle(cornerRadius: 8)
.fill(Color.clear)
}
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.background( .background(
@ -131,15 +141,15 @@ struct GlobalDropdownOverlay: View {
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
ForEach(Array(manager.items.enumerated()), id: \.offset) { index, item in ForEach(Array(manager.items.enumerated()), id: \.offset) { index, item in
Button { Button {
manager.onSelect?(item) manager.onSelect?(index)
manager.dismiss() manager.dismiss()
} label: { } label: {
Text(item) Text(item)
.font(manager.font) .font(manager.font)
.lineLimit(1) .lineLimit(1)
.minimumScaleFactor(0.5) .minimumScaleFactor(0.4)
.padding([.leading, .trailing], 8)
.frame(maxWidth: .infinity, minHeight: 40, maxHeight: 40, alignment: .center) .frame(maxWidth: .infinity, minHeight: 40, maxHeight: 40, alignment: .center)
.padding([.leading,.trailing], 2)
.contentShape(Rectangle()) .contentShape(Rectangle())
} }
.buttonStyle(.plain) .buttonStyle(.plain)

View File

@ -13,12 +13,10 @@ struct RegisterView: View {
@StateObject var btnVM = ButtonViewModel() @StateObject var btnVM = ButtonViewModel()
@StateObject var registerVM: RegisterViewModel @StateObject var registerVM: RegisterViewModel
private let responseValue: (SNSLoginType, String) // private let responseValue: (SNSLoginType, String)
init(_ appVM: AppViewModel, type: SNSLoginType, snsID: String) { init(_ appVM: AppViewModel, type: SNSLoginType, snsID: String) {
_registerVM = StateObject(wrappedValue: RegisterViewModel(appVM)) _registerVM = StateObject(wrappedValue: RegisterViewModel(appVM, type: type, snsID: snsID))
self.responseValue.0 = type
self.responseValue.1 = snsID
} }
@ -30,11 +28,9 @@ struct RegisterView: View {
@StateObject private var dropdownManager = DropdownManager() @StateObject private var dropdownManager = DropdownManager()
private let addressBtnID = UUID()
private let registerBtnID = UUID()
@State private var selected = "" @State private var onDomainTxf = false
let options = ["Swift", "Kotlin", "Dart", "JavaScript", "C#","C++","C"]
var body: some View { var body: some View {
// MARK: TO-DO // MARK: TO-DO
@ -67,13 +63,13 @@ struct RegisterView: View {
.frame(width: 60, alignment: .center) .frame(width: 60, alignment: .center)
.padding(.trailing,12) .padding(.trailing,12)
Spacer(minLength: 1) 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) .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 { .background {
RoundedRectangle(cornerRadius: 24) RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light)) .foregroundStyle(Color(.Normal.light))
} }.clipped()
} }
.padding(EdgeInsets(top: 8, leading: 16, bottom: 16, trailing: 16)) .padding(EdgeInsets(top: 8, leading: 16, bottom: 16, trailing: 16))
@ -103,37 +99,48 @@ struct RegisterView: View {
.frame(width: 60, alignment: .center) .frame(width: 60, alignment: .center)
.padding(.trailing,12) .padding(.trailing,12)
Spacer(minLength: 1) Spacer(minLength: 1)
CustomTextField(placeholder: "앞부분 입력", text: $registerVM.emailFrontText, alignment: .center)
VStack (spacing: 4) {
CustomTxfView(placeholder: "이메일 입력(30 글자)", text: $registerVM.emailFrontText, maxLength: 30, alignment: .center)
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20)) .padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8))
.background { .background {
RoundedRectangle(cornerRadius: 24) RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light)) .foregroundStyle(Color(.Normal.light))
} }
// Spacer(minLength: 1)
HStack(spacing: 0) {
Text("@") Text("@")
.font(.nps(size: 16)) .font(.nps(size: 16))
.padding([.leading, .trailing], 4) .padding([.leading, .trailing], 4)
// Spacer(minLength: 1) // Spacer(minLength: 1)
DropdownButton(manager: dropdownManager, title: "도메인 선택", DropdownButton(manager: dropdownManager, title: "도메인 선택",
items: registerVM.emailTailList) 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) .frame(maxWidth: .infinity, maxHeight: .infinity)
.background { .background {
RoundedRectangle(cornerRadius: 24) RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light)) .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() .padding()
@ -148,15 +155,9 @@ struct RegisterView: View {
} }
.frame(width: 60, alignment: .center) .frame(width: 60, alignment: .center)
.padding(.trailing,12) .padding(.trailing,12)
// DropdownButton(manager: dropdownManager, title: "", items: options) DropdownButton(manager: dropdownManager, title: "선택", items: registerVM.numberHeadList){ index in
// CustomTextField(placeholder: "000", text: $registerVM.nameText, alignment: .center) registerVM.phoneTextSet.0 = registerVM.numberHeadList[index]
// .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 { .background {
RoundedRectangle(cornerRadius: 24) RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light)) .foregroundStyle(Color(.Normal.light))
@ -165,9 +166,9 @@ struct RegisterView: View {
.font(.nps(size: 16)) .font(.nps(size: 16))
.padding([.leading, .trailing], 4) .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) .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 { .background {
RoundedRectangle(cornerRadius: 24) RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light)) .foregroundStyle(Color(.Normal.light))
@ -175,9 +176,9 @@ 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, alignment: .center) CustomTxfView(placeholder: "0000", text: $registerVM.phoneTextSet.2, maxLength: 4, 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: 8, bottom: 4, trailing: 8))
.background { .background {
RoundedRectangle(cornerRadius: 24) RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light)) .foregroundStyle(Color(.Normal.light))
@ -185,31 +186,44 @@ struct RegisterView: View {
} }
.padding() .padding()
HStack(spacing: 0){ HStack(alignment: .center, spacing: 0){
Text("주소") Text("주소")
.font(.nps(size: 16)) .font(.nps(size: 16))
.frame(width: 60, alignment: .center) .frame(width: 60, alignment: .center)
.padding(.trailing,12) .padding(.trailing,12)
Spacer(minLength: 1)
VStack(spacing: 0) { VStack(spacing: 0) {
SimpleBtnView(vm: btnVM, id: addressBtnID) SimpleBtnView(vm: btnVM, id: registerVM.addressBtnID)
.padding(.bottom, isSelectAddr ? 4:0) .frame(maxWidth: .infinity, maxHeight: 48)
CustomTextField(placeholder: "상세 주소 입력", text: $registerVM.addrDetailText, alignment: .center) .padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8))
.frame(maxWidth: .infinity,
maxHeight: isSelectAddr ? 48 : 0)
.padding(EdgeInsets(top: 4, leading: 20, bottom: 4, trailing: 20))
.background { .background {
RoundedRectangle(cornerRadius: 24) RoundedRectangle(cornerRadius: 24)
.foregroundStyle(Color(.Normal.light)) .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() .padding()
} }
.frame(maxWidth: .infinity, maxHeight: .infinity) .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) GlobalDropdownOverlay(manager: dropdownManager)
} }
@ -221,30 +235,36 @@ struct RegisterView: View {
if let result = result as? (String, String) { if let result = result as? (String, String) {
print(result.0) print(result.0)
isSelectAddr = true isSelectAddr = true
// printLog(registerVM.addressText)
registerVM.addressText = result.0 registerVM.addressText = result.0
btnVM.setSize(for: addressBtnID, newWidth: .infinity, newHeight: .infinity) btnVM.setSize(for: registerVM.addressBtnID, newWidth: .infinity, newHeight: .infinity)
// printLog(registerVM.addressText) btnVM.setText(for: registerVM.addressBtnID, newText: "\(registerVM.addressText)", newFont: .nps(size: 16))
btnVM.setText(for: addressBtnID, newText: "\(registerVM.addressText)", newFont: .nps(size: 16))
} }
} }
} }
.onAppear { .onAppear {
topVM.titleName = "회원가입" topVM.titleName = "회원가입"
topVM.setLeftBtn(Image(.Icon.left), size: CGPoint(x: 40, y: 40), action: leftAct) topVM.setLeftBtn(Image(.Icon.left), size: CGPoint(x: 40, y: 40), action: leftAct)
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: registerVM.addressBtnID, newWidth: 80, newHeight: 24)
btnVM.setText(for: addressBtnID, newText: "\(registerVM.addressText)", newFont: .nps(size: 16)) btnVM.setText(for: registerVM.addressBtnID, newText: "\(registerVM.addressText)", newFont: .nps(size: 16))
btnVM.setAction(for: addressBtnID) { btnVM.setTextColor(for: registerVM.addressBtnID, newColor: Color.Text.black)
self.showWebView.toggle() 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) dropdownManager.font = .nps(size: 16)
} }
.onChange(of: registerVM.addressText) { _, new in // .onChange(of: dropdownManager.onSelect) { _, new in
// printLog("CHANGE: \(new)")
} //
// }
} }
func leftAct() { func leftAct() {

View File

@ -16,7 +16,7 @@ class AppViewModel: ObservableObject {
@Published var menuName: MenuName = .Home @Published var menuName: MenuName = .Home
@Published var naviState: NaviState = .init(act: .NONE, path: .Intro) @Published var naviState: NaviState = .init(act: .NONE, path: .Intro)
var alertData: AlertData = .init(body: "") @Published var alertData: AlertData = .init(body: "")
/// ///
let alertAction = CurrentValueSubject<String?, Never>(nil) let alertAction = CurrentValueSubject<String?, Never>(nil)

View File

@ -9,24 +9,38 @@ import SwiftUI
import Combine import Combine
class RegisterViewModel: ObservableObject { class RegisterViewModel: ObservableObject {
private let appVM: AppViewModel
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
private let appVM: AppViewModel
private let responseValue: (SNSLoginType, String)
init(_ appVM: AppViewModel) { init(_ appVM: AppViewModel, type: SNSLoginType, snsID: String) {
self.appVM = appVM self.appVM = appVM
self.responseValue = (type, snsID)
} }
@State var selectDate: Date = { let addressBtnID = UUID()
let calendar = Calendar.current let registerBtnID = UUID()
return calendar.date(byAdding: .year, value: -12, to: Date()) ?? Date()
}()
@State var nameText: String = "" @Published var selectDate: Date = {
@State var emailFrontText: String = "" let calendar = Calendar.current
@State var emailTailText: String = "" return calendar.date(byAdding: .year, value: 0, to: Date()) ?? Date()
@State var phoneArray: [Int] = [] }() {
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 = "주소 입력" @Published var addressText: String = "주소 입력"
@State var addrDetailText: String = "" @Published var addrDetailText: String = ""
let numberHeadList = ["010","011","016","017","018","019"] let numberHeadList = ["010","011","016","017","018","019"]
let emailTailList = ["gmail.com", let emailTailList = ["gmail.com",
@ -41,4 +55,30 @@ class RegisterViewModel: ObservableObject {
"protonmail.com", "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()
}
}
} }

View File

@ -8,10 +8,69 @@
import SwiftUI import SwiftUI
import UIKit import UIKit
struct CustomTextField: UIViewRepresentable { struct FixedSizeWrapper<Content: UIView>: 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 var placeholder: String
@Binding var text: String @Binding var text: String
var maxLength: Int = 100
var isSecure: Binding<Bool> = .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<String>(
// 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<Bool> = .constant(false) var isSecure: Binding<Bool> = .constant(false)
var textColor = UIColor(Color(.Text.detail)) var textColor = UIColor(Color(.Text.detail))
@ -21,24 +80,31 @@ struct CustomTextField: UIViewRepresentable {
// [] UIView // [] UIView
func makeUIView(context: Context) -> UITextField { func makeUIView(context: Context) -> UITextField {
let txf = 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.isSecureTextEntry = isSecure.wrappedValue
txf.adjustsFontSizeToFitWidth = true
txf.minimumFontSize = 4
txf.textColor = textColor txf.textColor = textColor
txf.font = font
txf.translatesAutoresizingMaskIntoConstraints = false
let height = (font?.lineHeight ?? 10) + 8 let height = (font?.lineHeight ?? 10) + 8
txf.frame.size.height = height txf.frame.size.height = height
txf.borderStyle = .none
txf.autocorrectionType = .no txf.autocorrectionType = .no
txf.autocapitalizationType = .none txf.autocapitalizationType = .none
txf.smartInsertDeleteType = .no txf.smartInsertDeleteType = .no
txf.textContentType = .oneTimeCode txf.textContentType = .oneTimeCode
txf.textAlignment = self.alignment txf.textAlignment = self.alignment
txf.translatesAutoresizingMaskIntoConstraints = false
txf.setContentHuggingPriority(.required, for: .horizontal)
txf.setContentCompressionResistancePriority(.required, for: .horizontal)
txf.delegate = context.coordinator txf.delegate = context.coordinator
return txf return txf
@ -51,19 +117,34 @@ struct CustomTextField: UIViewRepresentable {
} }
func makeCoordinator() -> Coordinator { func makeCoordinator() -> Coordinator {
Coordinator(self) Coordinator(self, max: maxLength)
} }
class Coordinator: NSObject, UITextFieldDelegate { class Coordinator: NSObject, UITextFieldDelegate {
var parent: CustomTextField var parent: CustomTextField
var maxLength: Int?
init(_ parent: CustomTextField) { init(_ parent: CustomTextField, max: Int?) {
self.parent = parent self.parent = parent
self.maxLength = max
} }
func textFieldDidChangeSelection(_ textField: UITextField) { func textFieldDidChangeSelection(_ textField: UITextField) {
parent.text = textField.text ?? "" 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
// }
} }
} }