Skip to content

Commit

Permalink
[#21] Delegate 커스터마이징 제공
Browse files Browse the repository at this point in the history
  • Loading branch information
wjdalswl committed Jan 14, 2025
1 parent 326a8d1 commit 6a9531f
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 7 deletions.
37 changes: 36 additions & 1 deletion Handy/Handy-Storybook/Atom/TextViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ final class TextViewController: BaseViewController {
private let defaultTextView: HandyTextView = {
let textView = HandyTextView()
textView.placeholder = "Input text"
textView.helperLabelText = "Helper text"
textView.helperLabelText = "알파벳과 숫자만 허용합니다."
textView.placeholderColor = .lightGray

return textView
Expand Down Expand Up @@ -53,6 +53,13 @@ final class TextViewController: BaseViewController {
setViewLayouts()
}

override func setViewProperties() {
self.view.backgroundColor = .white
defaultTextView.editingDelegate = self
defaultTextView.validationDelegate = self
defaultTextView.textChangeDelegate = self
}

override func setViewHierarchies() {
[
defaultTextView, noHelperLabelTextView, errorTextView, disabledTextView
Expand Down Expand Up @@ -85,3 +92,31 @@ final class TextViewController: BaseViewController {
}
}
}

extension TextViewController: HandyTextViewEditingDelegate {
func handyTextViewDidBeginEditing(_ handyTextView: HandyTextView) {
print("입력 시작")
}

func handyTextViewDidEndEditing(_ handyTextView: HandyTextView) {
print("입력 끝")
}
}

extension TextViewController: HandyTextViewValidationDelegate {
func handyTextView(_ handyTextView: HandyTextView, isValidText text: String) -> Bool {
let regex = "^[a-zA-Z0-9]*$"
return NSPredicate(format: "SELF MATCHES %@", regex).evaluate(with: text)
}

func handyTextView(_ handyTextView: HandyTextView, didFailValidationWithError error: String) {
print("유효성 검사 에러: \(error)")
}
}

extension TextViewController: HandyTextViewTextChangeDelegate {
func handyTextViewDidChange(_ handyTextView: HandyTextView, text: String) {
print("입력된 텍스트: \(text)")
}
}

45 changes: 41 additions & 4 deletions Handy/Handy/Source/Atom/HandyTextView/HandyBaseTextView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,25 @@ import SnapKit
public class HandyBaseTextView: UITextView {
// MARK: - 외부에서 지정할 수 있는 속성

/**
외부에서 지정한 UITextViewDelegate를 저장합니다.
*/
public weak var externalDelegate: UITextViewDelegate?

/**
UITextView의 delegate속성을 오버라이드하여, 외부 Delegate와 내부 Delegate의 충돌을 방지합니다.
- 외부에서 Delegate를 설정할 경우, 내부적으로 externalDelegate에 저장하고, HandyBaseTextView가 실제 Delegate 역할을 수행합니다.
- HandyBaseTextView가 Delegate 메서드를 먼저 처리한 후, 외부 Delegate가 호출됩니다.
*/
public override var delegate: UITextViewDelegate? {
didSet {
if delegate !== self {
externalDelegate = delegate
super.delegate = self
}
}
}

/**
텍스트 뷰를 비활성화 시킬 때 사용합니다.
*/
Expand Down Expand Up @@ -48,17 +67,24 @@ public class HandyBaseTextView: UITextView {
*/
private var placeholderLabel: UILabel?

// MARK: - 메소드
// MARK: - 초기화

public init() {
super.init(frame: .zero, textContainer: nil)
setDelegate()
setupView()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: - 메소드

private func setDelegate() {
super.delegate = self
}

private func setupView() {
self.delegate = self
self.font = HandyFont.B3Rg14
Expand Down Expand Up @@ -148,16 +174,27 @@ public class HandyBaseTextView: UITextView {

extension HandyBaseTextView: UITextViewDelegate {
public func textViewDidBeginEditing(_ textView: UITextView) {
if !isNegative {
self.layer.borderColor = HandySemantic.lineStatusPositive.cgColor
}
self.layer.borderColor = HandySemantic.lineStatusPositive.cgColor
externalDelegate?.textViewDidBeginEditing?(textView)
}

public func textViewDidEndEditing(_ textView: UITextView) {
updateState()
externalDelegate?.textViewDidEndEditing?(textView)
}

public func textViewDidChange(_ textView: UITextView) {
textDidChange()
externalDelegate?.textViewDidChange?(textView)

if let isValid = (self.superview as? HandyTextView)?.validationDelegate?.handyTextView(self.superview as! HandyTextView, isValidText: textView.text) {
isNegative = !isValid
}

if isNegative {
self.layer.borderColor = HandySemantic.lineStatusNegative.cgColor
} else {
self.layer.borderColor = HandySemantic.lineStatusPositive.cgColor
}
}
}
70 changes: 68 additions & 2 deletions Handy/Handy/Source/Atom/HandyTextView/HandyTextView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,42 @@
import UIKit
import SnapKit


// MARK: - Delegate
/**
HandyTextView의 Delegate를 통해 텍스트 변경, 유효성 검사, 편집 시작/종료 등의 이벤트를 처리할 수 있습니다.
*/

public protocol HandyTextViewEditingDelegate: AnyObject {
/**
텍스트가 시작 또는 종료될 때 호출합니다.
*/
func handyTextViewDidBeginEditing(_ handyTextView: HandyTextView)
func handyTextViewDidEndEditing(_ handyTextView: HandyTextView)
}

public protocol HandyTextViewValidationDelegate: AnyObject {
/**
입력 텍스트가 특정 조건에 만족하지 않을 때 호출합니다.
*/
func handyTextView(_ handyTextView: HandyTextView, isValidText text: String) -> Bool
func handyTextView(_ handyTextView: HandyTextView, didFailValidationWithError error: String)
}

public protocol HandyTextViewTextChangeDelegate: AnyObject {
/**
텍스트가 변경될 때 호출합니다.
*/
func handyTextViewDidChange(_ handyTextView: HandyTextView, text: String)
}

public class HandyTextView: UIView {
// MARK: - 외부에서 지정할 수 있는 속성

public weak var editingDelegate: HandyTextViewEditingDelegate?
public weak var validationDelegate: HandyTextViewValidationDelegate?
public weak var textChangeDelegate: HandyTextViewTextChangeDelegate?

/**
텍스트 필드를 비활성화 시킬 때 사용합니다.
*/
Expand All @@ -26,7 +59,8 @@ public class HandyTextView: UIView {
*/
public var text: String? {
get { return textView.text }
set { textView.text = newValue }
set { textView.text = newValue
validateText() }
}

/**
Expand Down Expand Up @@ -75,6 +109,7 @@ public class HandyTextView: UIView {

public init() {
super.init(frame: .zero)
setDelegate()
setupView()
updateState()
}
Expand All @@ -85,6 +120,10 @@ public class HandyTextView: UIView {

// MARK: - 메소드

private func setDelegate() {
textView.delegate = self
}

private func setupView() {
addSubview(stackView)

Expand Down Expand Up @@ -119,7 +158,17 @@ public class HandyTextView: UIView {
textView.isDisabled = isDisabled
textView.isNegative = isNegative
helperLabel.textColor = isNegative ? HandySemantic.lineStatusNegative : HandySemantic.textBasicTertiary

}

private func validateText() {
guard let text = textView.text else { return }
if let isValid = validationDelegate?.handyTextView(self, isValidText: text) {
isNegative = !isValid

if !isValid {
validationDelegate?.handyTextView(self, didFailValidationWithError: "유효하지 않은 입력입니다.")
}
}
}

// MARK: - Overridden Methods
Expand All @@ -146,3 +195,20 @@ public class HandyTextView: UIView {
updateState()
}
}

// MARK: - UITextViewDelegate

extension HandyTextView: UITextViewDelegate {
public func textViewDidChange(_ textView: UITextView) {
textChangeDelegate?.handyTextViewDidChange(self, text: textView.text ?? "")
validateText()
}

public func textViewDidBeginEditing(_ textView: UITextView) {
editingDelegate?.handyTextViewDidBeginEditing(self)
}

public func textViewDidEndEditing(_ textView: UITextView) {
editingDelegate?.handyTextViewDidEndEditing(self)
}
}

0 comments on commit 6a9531f

Please sign in to comment.