-
Notifications
You must be signed in to change notification settings - Fork 0
[#20] HandySearchBar 추가 #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
b4a9634
16ea7c4
dae286d
4f1d325
88ebc0c
dba342e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| // | ||
| // HandySearchBarViewController.swift | ||
| // Handy-Storybook | ||
| // | ||
| // Created by 성현주 on 1/1/25. | ||
| // | ||
|
|
||
| import Handy | ||
| import UIKit | ||
|
|
||
| final class HandySearchBarViewController: BaseViewController { | ||
|
|
||
| private let searchBar1: HandySearchBar = { | ||
| let uiSearchBar = HandySearchBar() | ||
| return uiSearchBar | ||
| }() | ||
| private let searchBar2: HandySearchBar = { | ||
| let uiSearchBar = HandySearchBar() | ||
| uiSearchBar.rightIcon = false | ||
| return uiSearchBar | ||
| }() | ||
| private let searchBar3: HandySearchBar = { | ||
| let uiSearchBar = HandySearchBar() | ||
| uiSearchBar.leftIcon = false | ||
| return uiSearchBar | ||
| }() | ||
| private let searchBar4: HandySearchBar = { | ||
| let uiSearchBar = HandySearchBar() | ||
| uiSearchBar.leftIcon = false | ||
| uiSearchBar.rightIcon = false | ||
| uiSearchBar.placeholder = "Custom" | ||
| return uiSearchBar | ||
| }() | ||
|
|
||
|
|
||
| override func viewDidLoad() { | ||
| super.viewDidLoad() | ||
|
|
||
| self.view.backgroundColor = .white | ||
| setupButtonTargets() | ||
| } | ||
|
|
||
| override func setViewHierarchies() { | ||
| self.view.addSubview(searchBar1) | ||
| self.view.addSubview(searchBar2) | ||
| self.view.addSubview(searchBar3) | ||
| self.view.addSubview(searchBar4) | ||
|
|
||
| } | ||
|
|
||
| override func setViewLayouts() { | ||
| searchBar1.snp.makeConstraints { | ||
| $0.bottom.equalTo(searchBar2.snp.top).offset(-20) | ||
| $0.horizontalEdges.equalToSuperview().inset(20) | ||
| } | ||
| searchBar2.snp.makeConstraints { | ||
| $0.center.equalToSuperview() | ||
| $0.horizontalEdges.equalToSuperview().inset(20) | ||
| } | ||
| searchBar3.snp.makeConstraints { | ||
| $0.top.equalTo(searchBar2.snp.bottom).offset(20) | ||
| $0.horizontalEdges.equalToSuperview().inset(20) | ||
| } | ||
| searchBar4.snp.makeConstraints { | ||
| $0.top.equalTo(searchBar3.snp.bottom).offset(20) | ||
| $0.horizontalEdges.equalToSuperview().inset(20) | ||
| } | ||
| } | ||
| private func setupButtonTargets() { | ||
| // searchBar1 | ||
| searchBar1.leftButton.addTarget(self, action: #selector(leftButtonTapped(_:)), for: .touchUpInside) | ||
| searchBar1.rightButton.addTarget(self, action: #selector(rightButtonTapped(_:)), for: .touchUpInside) | ||
|
|
||
| // searchBar2 | ||
| searchBar2.leftButton.addTarget(self, action: #selector(leftButtonTapped(_:)), for: .touchUpInside) | ||
|
|
||
| // searchBar3 | ||
| searchBar3.rightButton.addTarget(self, action: #selector(rightButtonTapped(_:)), for: .touchUpInside) | ||
| } | ||
|
|
||
| @objc private func leftButtonTapped(_ sender: UIButton) { | ||
| print("Left button tapped") | ||
| } | ||
|
|
||
| @objc private func rightButtonTapped(_ sender: UIButton) { | ||
| print("Right button tapped") | ||
| } | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
| // | ||
| // HandySearchBar.swift | ||
| // Handy | ||
| // | ||
| // Created by 성현주 on 1/1/25. | ||
| // | ||
|
|
||
| import UIKit | ||
| import SnapKit | ||
|
|
||
| final public class HandySearchBar: UIView { | ||
|
|
||
| // MARK: - 외부에서 설정할 수 있는 속성 | ||
|
|
||
| /** | ||
| 왼쪽 버튼의 활성화 여부를 설정합니다. | ||
| */ | ||
| @Invalidating(wrappedValue: true, .layout) public var leftIcon: Bool { | ||
| didSet { | ||
| leftButton.isHidden = !leftIcon | ||
| } | ||
| } | ||
|
||
|
|
||
| /** | ||
| 오른쪽 버튼의 활성화 여부를 설정합니다. | ||
| */ | ||
| @Invalidating(wrappedValue: true, .layout) public var rightIcon: Bool { | ||
| didSet { | ||
| rightButton.isHidden = !rightIcon | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| 텍스트 필드의 플레이스홀더를 설정합니다. | ||
| */ | ||
| @Invalidating(wrappedValue: "", .layout) public var placeholder: String { | ||
| didSet { | ||
| textField.placeholder = placeholder | ||
| } | ||
| } | ||
|
|
||
| // MARK: - Properties | ||
|
|
||
| public let leftButton: UIButton = { | ||
| let button = UIButton() | ||
| button.setImage(HandyIcon.arrowsChevronLeftFilled.withRenderingMode(.alwaysTemplate), for: .normal) | ||
| button.tintColor = HandySemantic.iconBasicSecondary | ||
| return button | ||
| }() | ||
|
|
||
| public let rightButton: UIButton = { | ||
| let button = UIButton() | ||
| button.setImage(HandyIcon.filterBarFilled.withRenderingMode(.alwaysTemplate), for: .normal) | ||
| button.tintColor = HandySemantic.iconBasicSecondary | ||
| return button | ||
| }() | ||
|
|
||
| private let clearButton: UIButton = { | ||
| let button = UIButton(type: .system) | ||
| button.setImage(HandyIcon.cancelFilled, for: .normal) | ||
| button.tintColor = HandySemantic.iconBasicTertiary | ||
| button.isHidden = true | ||
| return button | ||
| }() | ||
|
|
||
| private let searchIconImageView: UIImageView = { | ||
| let imageView = UIImageView() | ||
| imageView.image = HandyIcon.searchLine.withRenderingMode(.alwaysTemplate) | ||
| imageView.tintColor = HandySemantic.iconBasicTertiary | ||
| imageView.contentMode = .scaleAspectFit | ||
| return imageView | ||
| }() | ||
|
|
||
| private let textField: UITextField = { | ||
| let textField = UITextField() | ||
| textField.placeholder = "Text input" | ||
| textField.font = HandyFont.B1Rg16 | ||
| textField.textColor = HandySemantic.textBasicPrimary | ||
| textField.borderStyle = .none | ||
| textField.backgroundColor = HandySemantic.bgBasicLight | ||
| textField.layer.cornerRadius = HandySemantic.radiusM | ||
| textField.leftViewMode = .always | ||
| textField.rightViewMode = .whileEditing | ||
| return textField | ||
| }() | ||
|
|
||
| private let stackView: UIStackView = { | ||
| let stackView = UIStackView() | ||
| stackView.axis = .horizontal | ||
| stackView.alignment = .center | ||
| stackView.distribution = .fill | ||
| stackView.spacing = 8 | ||
| return stackView | ||
| }() | ||
|
|
||
| // MARK: - Initialization | ||
|
|
||
| override init(frame: CGRect) { | ||
| super.init(frame: frame) | ||
| setupView() | ||
| } | ||
|
|
||
| required init?(coder: NSCoder) { | ||
| super.init(coder: coder) | ||
| setupView() | ||
| } | ||
|
|
||
| // MARK: - Private Methods | ||
|
|
||
| private func setupView() { | ||
| setupLeftView() | ||
| setupRightView() | ||
|
|
||
| stackView.addArrangedSubview(leftButton) | ||
| stackView.addArrangedSubview(textField) | ||
| stackView.addArrangedSubview(rightButton) | ||
| addSubview(stackView) | ||
|
|
||
| // 기본 레이아웃 설정 | ||
| stackView.snp.makeConstraints { | ||
| $0.edges.equalToSuperview() | ||
| } | ||
|
|
||
| leftButton.snp.makeConstraints { | ||
| $0.width.height.equalTo(24) | ||
| } | ||
|
|
||
| rightButton.snp.makeConstraints { | ||
| $0.width.height.equalTo(24) | ||
| } | ||
|
|
||
| textField.snp.makeConstraints { | ||
| $0.height.equalTo(48) | ||
| } | ||
|
|
||
| textField.addTarget(self, action: #selector(textDidChange), for: .editingChanged) | ||
| clearButton.addTarget(self, action: #selector(clearText), for: .touchUpInside) | ||
| } | ||
|
|
||
| /** | ||
| 텍스트 필드의 왼쪽에 표시되는 아이콘 뷰를 설정합니다. | ||
| */ | ||
| private func setupLeftView() { | ||
| let leftViewContainer = UIView() | ||
| leftViewContainer.addSubview(searchIconImageView) | ||
| searchIconImageView.snp.makeConstraints { | ||
| $0.center.equalToSuperview() | ||
| $0.width.height.equalTo(20) | ||
| } | ||
| leftViewContainer.snp.makeConstraints { | ||
| $0.width.height.equalTo(40) | ||
| } | ||
| textField.leftView = leftViewContainer | ||
| } | ||
|
|
||
| /** | ||
| 텍스트 필드의 오른쪽에 표시되는 clearButton 뷰를 설정합니다. | ||
| */ | ||
| private func setupRightView() { | ||
| let rightViewContainer = UIView() | ||
| rightViewContainer.addSubview(clearButton) | ||
| clearButton.snp.makeConstraints { | ||
| $0.center.equalToSuperview() | ||
| $0.width.height.equalTo(20) | ||
| } | ||
| rightViewContainer.snp.makeConstraints { | ||
| $0.width.height.equalTo(40) | ||
| } | ||
| textField.rightView = rightViewContainer | ||
| } | ||
| } | ||
|
|
||
| private extension HandySearchBar { | ||
| /** | ||
| 텍스트 필드의 내용을 지우고, clearButton을 숨깁니다. | ||
| */ | ||
| @objc func clearText() { | ||
| textField.text = "" | ||
| clearButton.isHidden = true | ||
| } | ||
|
|
||
| /** | ||
| 텍스트 필드의 내용이 변경될 때 clearButton의 표시 여부를 업데이트합니다. | ||
| */ | ||
| @objc func textDidChange() { | ||
| clearButton.isHidden = textField.text?.isEmpty ?? true | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| { | ||
| "images" : [ | ||
| { | ||
| "filename" : "size=36, filled=true.svg", | ||
| "idiom" : "universal", | ||
| "scale" : "1x" | ||
| }, | ||
| { | ||
| "idiom" : "universal", | ||
| "scale" : "2x" | ||
| }, | ||
| { | ||
| "idiom" : "universal", | ||
| "scale" : "3x" | ||
| } | ||
| ], | ||
| "info" : { | ||
| "author" : "xcode", | ||
| "version" : 1 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| { | ||
| "images" : [ | ||
| { | ||
| "filename" : "size=36, filled=true.svg", | ||
| "idiom" : "universal", | ||
| "scale" : "1x" | ||
| }, | ||
| { | ||
| "idiom" : "universal", | ||
| "scale" : "2x" | ||
| }, | ||
| { | ||
| "idiom" : "universal", | ||
| "scale" : "3x" | ||
| } | ||
| ], | ||
| "info" : { | ||
| "author" : "xcode", | ||
| "version" : 1 | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 컴포넌트를 final로 규정짓는 것은 살짝 위험할 수도 있다고 생각해요!
HandySearchBar를 상속받아서 더 꾸밀 수도 있다고 생각하기 때문에 final public보다는 필요한 곳에 open 접근제어를 걸어두는 것도 좋다고 생각합니다~!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
앗 맞습니다. 추가적인 커스텀이 필요한 상황이 분명 발생할거기 때문에 final은 과하다라는 생각이 듭니다. 필요한 부분에 open으로 수정했습니다!