Skip to content

Commit

Permalink
[#21] HandyTextView 생성
Browse files Browse the repository at this point in the history
  • Loading branch information
wjdalswl committed Jan 2, 2025
1 parent fb0df99 commit 9a57a6e
Show file tree
Hide file tree
Showing 5 changed files with 460 additions and 0 deletions.
79 changes: 79 additions & 0 deletions Handy/Handy-Storybook/Atom/TextViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// TextViewController.swift
// Handy
//
// Created by 정민지 on 11/18/24.
//

import UIKit
import SnapKit

import Handy

final class TextViewController: BaseViewController {

private let defaultTextView: HandyTextView = {
let textView = HandyTextView()
textView.placeholder = "Input text"
textView.helperLabelText = "Helper text"
textView.placeholderColor = .lightGray
textView.minHeight = 187
textView.maxHeight = 187

return textView
}()


private let errorTextView: HandyTextView = {
let textView = HandyTextView()
textView.placeholder = "Input text"
textView.helperLabelText = "Helper text"
textView.placeholderColor = .lightGray
textView.isNegative = true
textView.maxHeight = 80

return textView
}()

private let disabledTextView: HandyTextView = {
let textView = HandyTextView()
textView.placeholder = "Input text"
textView.helperLabelText = "Helper text"
textView.placeholderColor = .lightGray
textView.isDisabled = true
textView.maxHeight = 187

return textView
}()


override func viewDidLoad() {
super.viewDidLoad()
setViewLayouts()
}

override func setViewHierarchies() {
[
defaultTextView, errorTextView, disabledTextView
].forEach {
view.addSubview($0)
}
}

override func setViewLayouts() {
defaultTextView.snp.makeConstraints {
$0.top.equalToSuperview().offset(100)
$0.horizontalEdges.equalToSuperview().inset(20)
}
errorTextView.snp.makeConstraints {
$0.top.equalTo(defaultTextView.snp.bottom).offset(20)
$0.horizontalEdges.equalToSuperview().inset(20)
}
disabledTextView.snp.makeConstraints {
$0.top.equalTo(errorTextView.snp.bottom).offset(20)
$0.horizontalEdges.equalToSuperview().inset(20)
}
}
}


24 changes: 24 additions & 0 deletions Handy/Handy.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
2D88118B2D2642BD00B0B517 /* HandyTextFieldConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D88118A2D2642BC00B0B517 /* HandyTextFieldConstants.swift */; };
2D88118D2D2642CE00B0B517 /* HandyBaseTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D88118C2D2642CE00B0B517 /* HandyBaseTextField.swift */; };
2D88118F2D2642F900B0B517 /* TextFieldViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D88118E2D2642F900B0B517 /* TextFieldViewController.swift */; };
2D8811912D26512600B0B517 /* TextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8811902D26512500B0B517 /* TextViewController.swift */; };
2D8811942D26515100B0B517 /* HandyTextViewConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8811932D26515000B0B517 /* HandyTextViewConstants.swift */; };
2D8811962D26516100B0B517 /* HandyTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8811952D26516000B0B517 /* HandyTextView.swift */; };
2D8811982D26517100B0B517 /* HandyBaseTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8811972D26517000B0B517 /* HandyBaseTextView.swift */; };
A56B3DE22C4E51D300C3610A /* HandyChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56B3DE12C4E51D300C3610A /* HandyChip.swift */; };
A5A12A7E2C57A6D900996916 /* ChipViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A12A7C2C57A6C200996916 /* ChipViewController.swift */; };
A5A12A7F2C57A92000996916 /* HandySematic.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5D02AFC2C46C5A70056CE7B /* HandySematic.swift */; };
Expand Down Expand Up @@ -123,6 +127,10 @@
2D88118A2D2642BC00B0B517 /* HandyTextFieldConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandyTextFieldConstants.swift; sourceTree = "<group>"; };
2D88118C2D2642CE00B0B517 /* HandyBaseTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandyBaseTextField.swift; sourceTree = "<group>"; wrapsLines = 0; };
2D88118E2D2642F900B0B517 /* TextFieldViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldViewController.swift; sourceTree = "<group>"; };
2D8811902D26512500B0B517 /* TextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewController.swift; sourceTree = "<group>"; };
2D8811932D26515000B0B517 /* HandyTextViewConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandyTextViewConstants.swift; sourceTree = "<group>"; };
2D8811952D26516000B0B517 /* HandyTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandyTextView.swift; sourceTree = "<group>"; };
2D8811972D26517000B0B517 /* HandyBaseTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandyBaseTextView.swift; sourceTree = "<group>"; };
A56B3DE12C4E51D300C3610A /* HandyChip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandyChip.swift; sourceTree = "<group>"; };
A5A12A7C2C57A6C200996916 /* ChipViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipViewController.swift; sourceTree = "<group>"; };
A5F6D36A2C96F32D00FB961F /* HandyDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandyDivider.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -181,6 +189,7 @@
2D41E8132C5A21930043161D /* FabViewController.swift */,
02ED764B2C57BD09001569F1 /* HandyBoxButtonViewController.swift */,
A5A12A7C2C57A6C200996916 /* ChipViewController.swift */,
2D8811902D26512500B0B517 /* TextViewController.swift */,
2D88118E2D2642F900B0B517 /* TextFieldViewController.swift */,
A5F6D36C2C97099C00FB961F /* DividerViewController.swift */,
E51FBF9A2C5399A00097B0DA /* CheckBoxViewController.swift */,
Expand Down Expand Up @@ -237,6 +246,7 @@
029E47FE2C49FD2E00D2F3B7 /* Atom */ = {
isa = PBXGroup;
children = (
2D8811922D26514B00B0B517 /* HandyTextView */,
2D8811872D26428500B0B517 /* HandyTextField */,
02ED762F2C52849A001569F1 /* HandyButton */,
029E47FC2C49FD1A00D2F3B7 /* HandyLabel.swift */,
Expand Down Expand Up @@ -333,6 +343,16 @@
path = HandyTextField;
sourceTree = "<group>";
};
2D8811922D26514B00B0B517 /* HandyTextView */ = {
isa = PBXGroup;
children = (
2D8811932D26515000B0B517 /* HandyTextViewConstants.swift */,
2D8811972D26517000B0B517 /* HandyBaseTextView.swift */,
2D8811952D26516000B0B517 /* HandyTextView.swift */,
);
path = HandyTextView;
sourceTree = "<group>";
};
E5650D412C4D30B9002790CC /* Asset */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -489,6 +509,7 @@
E51FBFA22C54CD350097B0DA /* RadioButtonViewController.swift in Sources */,
E51FBF9B2C5399A00097B0DA /* CheckBoxViewController.swift in Sources */,
025776352C4EA98C00272EC6 /* AppDelegate.swift in Sources */,
2D8811912D26512600B0B517 /* TextViewController.swift in Sources */,
02697A262C99DDA30027A362 /* HansySwitchViewController.swift in Sources */,
025776372C4EA98C00272EC6 /* SceneDelegate.swift in Sources */,
);
Expand All @@ -505,6 +526,7 @@
02150E4A2CC8D7AB00EE690E /* HandySnackbar.swift in Sources */,
E5D02B002C480A180056CE7B /* HandyPrimitive.swift in Sources */,
E5D02AFD2C46C5A70056CE7B /* HandySematic.swift in Sources */,
2D8811982D26517100B0B517 /* HandyBaseTextView.swift in Sources */,
E5D02B002C480A180056CE7B /* HandyPrimitive.swift in Sources */,
E51FBFA02C54CB260097B0DA /* HandyRadioButton.swift in Sources */,
2D88118B2D2642BD00B0B517 /* HandyTextFieldConstants.swift in Sources */,
Expand All @@ -516,9 +538,11 @@
02ED764A2C5779C3001569F1 /* UIImage+.swift in Sources */,
029E48002C49FD4000D2F3B7 /* HandyTypography.swift in Sources */,
E5650D432C4D326D002790CC /* HandyCheckBox.swift in Sources */,
2D8811962D26516100B0B517 /* HandyTextView.swift in Sources */,
2D88118D2D2642CE00B0B517 /* HandyBaseTextField.swift in Sources */,
029E47FD2C49FD1A00D2F3B7 /* HandyLabel.swift in Sources */,
A56B3DE22C4E51D300C3610A /* HandyChip.swift in Sources */,
2D8811942D26515100B0B517 /* HandyTextViewConstants.swift in Sources */,
E5650D472C512B07002790CC /* HandyIcon.swift in Sources */,
E5650D472C512B07002790CC /* HandyIcon.swift in Sources */,
2D8811892D2642A900B0B517 /* HandyTextFieldView.swift in Sources */,
Expand Down
179 changes: 179 additions & 0 deletions Handy/Handy/Source/Atom/HandyTextView/HandyBaseTextView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
//
// HandyBaseTextView.swift
// Handy
//
// Created by 정민지 on 11/18/24.
//

import UIKit
import SnapKit

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

/**
텍스트 뷰를 비활성화 시킬 때 사용합니다.
*/
@Invalidating(.layout) public var isDisabled: Bool = false {
didSet { updateState() }
}

/**
텍스트 필드의 오류 상태를 나타낼 때 사용합니다.
*/
@Invalidating(.layout) public var isNegative: Bool = false {
didSet { updateState() }
}

/**
텍스트 뷰의 최소 높이를 설정할 때 사용합니다.
*/
@Invalidating(.layout) public var minHeight: CGFloat? = 48

/**
텍스트 뷰의 최대 높이를 설정할 때 사용합니다.
*/
@Invalidating(.layout) public var maxHeight: CGFloat? = nil

/**
텍스트 뷰의 플레이스홀더를 설정할 때 사용합니다.
*/
public var placeholder: String? {
didSet { setupPlaceholder() }
}

/**
플레이스홀더 텍스트 색상을 설정할 때 사용합니다.
*/
public var placeholderColor: UIColor = HandySemantic.textBasicTertiary {
didSet {
placeholderLabel?.textColor = placeholderColor
}
}

/**
플레이스홀더 텍스트를 설정할 때 사용합니다.
*/
private var placeholderLabel: UILabel?

// MARK: - 메소드

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

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

deinit {
NotificationCenter.default.removeObserver(self)
}

private func setupView() {
self.delegate = self
self.font = HandyFont.B3Rg14
self.backgroundColor = HandySemantic.bgBasicLight
self.isScrollEnabled = false
self.layer.cornerRadius = HandySemantic.radiusM
self.layer.borderWidth = 1
self.layer.borderColor = HandySemantic.bgBasicLight.cgColor
self.textContainer.lineFragmentPadding = 0
self.textContainerInset = UIEdgeInsets(
top: HandyTextViewConstants.Dimension.textContainerInset,
left: HandyTextViewConstants.Dimension.textContainerInset,
bottom: HandyTextViewConstants.Dimension.textContainerInset,
right: HandyTextViewConstants.Dimension.textContainerInset
)
}

private func setupPlaceholder() {
if placeholderLabel == nil {
placeholderLabel = UILabel()
placeholderLabel?.textColor = placeholderColor
placeholderLabel?.font = self.font
placeholderLabel?.numberOfLines = 0
placeholderLabel?.text = placeholder
guard let label = placeholderLabel else { return }
self.addSubview(label)

label.snp.makeConstraints {
$0.edges.equalToSuperview().inset(textContainerInset)
}

NotificationCenter.default.addObserver(self,
selector: #selector(textDidChange),
name: UITextView.textDidChangeNotification,
object: nil)
} else {
placeholderLabel?.text = placeholder
}
}

private func updateState() {
if isDisabled {
self.isEditable = false
self.layer.borderColor = HandySemantic.bgBasicLight.cgColor
placeholderLabel?.textColor = HandySemantic.textBasicDisabled
} else if isNegative {
self.isEditable = true
self.layer.borderColor = HandySemantic.lineStatusNegative.cgColor
placeholderLabel?.textColor = HandySemantic.textBasicTertiary
} else {
self.isEditable = true
self.layer.borderColor = HandySemantic.bgBasicLight.cgColor
placeholderLabel?.textColor = HandySemantic.textBasicTertiary
}
}


public override func layoutSubviews() {
super.layoutSubviews()

if let minHeight = minHeight, let maxHeight = maxHeight {
isScrollEnabled = contentSize.height > maxHeight || contentSize.height < minHeight
} else if let maxHeight = maxHeight {
isScrollEnabled = contentSize.height > maxHeight
}

else if let minHeight = minHeight, bounds.height < minHeight {
invalidateIntrinsicContentSize()
frame.size.height = minHeight
}

else if let maxHeight = maxHeight, bounds.height > maxHeight {
invalidateIntrinsicContentSize()
frame.size.height = maxHeight
}

scrollIndicatorInsets = UIEdgeInsets(
top: 0,
left: 0,
bottom: 0,
right: HandyTextViewConstants.Dimension.scrollIndicatorInsets
)
}

@objc private func textDidChange() {
placeholderLabel?.isHidden = !text.isEmpty
}
}

// MARK: - UITextViewDelegate

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

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

public func textViewDidChange(_ textView: UITextView) {
textDidChange()
}
}
Loading

0 comments on commit 9a57a6e

Please sign in to comment.