UIKit으로 사용자 정의 TextInput View 만들기
UIKit에서 UITextfiled 등, 줄 문자를 입력하기 위한 View들은 제공하고 있습니다. 그런데 말이죠, 줄 문자가 아닌, 위의 스크린샷과 같은 비밀번호 뷰는 도대체 어떻게 만드는 걸까요? 내키지는 않지만, UITextfiled를 hidden 한 다음에, 커스텀뷰를 방금 전 텍스트 필드의 인풋과 연동 시키면 되는 걸까요?
답은 Apple developer 사이트에
https://developer.apple.com/documentation/uikit/keyboards_and_input
한 방에 찾지는 않고 구글에 키워드 검색하다가 찾게되었지만, 이미 API 컬렉션으로 keyboads and input이 제공되고 있었습니다!
API Collection
Keyboards and Input (키보드와 입력)
Configure the system keyboard, create your own keyboards to handle input, or detect key presses on a physical keyboard. (시스템 키보드를 설정하고, 입력을 받기 위한 키보드를 만드세요. 또는 물리적인 키보드로부터 키 입력을 인식하세요.)
프로토콜을 채택하고, 구현만 하면되는 간단한 일이지만, 예제 코드로 따라가봅시다.
코드 작성하기
패스워드 뷰를 작성하기 위해서는 네가지 일을 해주면 됩니다.
- 패드워드 뷰의 View 요소 작성하기
- 패스워드 뷰의 상황을 전달 받을 딜리게이트 프로토콜 작성하기
- UITextInputTraits를 패스워드 뷰에 채택하고 구현하기
- UIKeyInput를 패스워드 뷰에 채택하고 구현하기
아래는 예제 코드입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
public protocol PasscodeFieldDelegate: AnyObject { // 패스코드 글자 수 만큼 입력하면 호출된다. func passcodeFieldDidEndEditing(_ passcodeFiled: PasscodeField) } final public class PasscodeField: UIView { public var keyboardType: UIKeyboardType = .numberPad public var textContentType: UITextContentType = .password public var isSecureTextEntry: Bool = true public var text: String? { get { _text } set { if let text = newValue, text.count >= length { _text = String(text[text.startIndex..<min(text.endIndex, text.index(text.startIndex, offsetBy: length))]) if text.count == length { delegate?.passcodeFieldDidEndEditing(self) } } else { _text = newValue } } } private var _text: String? { didSet { updateIndicatorViews() } } public override var canBecomeFocused: Bool { true } public override var canBecomeFirstResponder: Bool { true } public weak var delegate: PasscodeFieldDelegate? public let length: Int public var foregroundColor: UIColor = .black { didSet { updateForegroundColor() } } private let indicatorsStackView: UIStackView private let indicatorViews: [UIImageView] private func updateIndicatorViews() { let text = self.text ?? "" for index in (0..<length) { indicatorViews[index].image = (index < text.count) ? Const.filledImage : Const.emptyImage } } private func updateForegroundColor() { indicatorViews.forEach { view in view.tintColor = foregroundColor } } init(frame: CGRect = .zero, length: Int = 6) { self.length = length self.indicatorsStackView = UIStackView() var indicatorViews: [UIImageView] = [] for _ in (0..<length) { let indicatorView = UIImageView(image: Const.emptyImage) indicatorsStackView.addArrangedSubview(indicatorView) indicatorViews.append(indicatorView) } self.indicatorViews = indicatorViews super.init(frame: frame) setupViews() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupViews() { indicatorsStackView.distribution = .equalSpacing addSubview(indicatorsStackView) indicatorsStackView.snp.makeConstraints { make in make.edges.equalToSuperview() } let gesture = UITapGestureRecognizer(target: self, action: #selector(onClick)) addGestureRecognizer(gesture) updateForegroundColor() } @objc private func onClick() { becomeFirstResponder() } private enum Const { static let emptyImage: UIImage? = UIImage(systemName: "circle") static let filledImage: UIImage? = UIImage(systemName: "circle.fill") } } extension PasscodeField: UITextInputTraits { } extension PasscodeField: UIKeyInput { public var hasText: Bool { return text?.isEmpty ?? false } public func insertText(_ text: String) { self.text = (self.text ?? "") + text } public func deleteBackward() { if self.text?.count ?? 0 > 0 { self.text?.removeLast() } } } |
간단하네요!