며칠 전 만든 이미지 없는 picker 버튼 기억하시나요?
"아니, 전혀 모르겠는데?" → 이곳으로
오늘은 망망대해에 홀로 떠있는 이 버튼 뷰에게
'언제' '어디서' '어떻게' 모습을 드러내라! 하고 생명의 바람을 후! 넣어 줍니다.
picker 버튼을 불러올 '중간다리', dropdown 만들기
1. DropdownButtonViewDelegate 프로토콜: 두 개의 메서드 정의
- didSelectOption(_ option: String): 옵션이 선택되었을 때 호출
- dropdownButtonTapped(): 드롭다운 버튼이 눌렸을 때 호출
protocol DropdownButtonViewDelegate: AnyObject {
func didSelectOption(_ option: String)
func dropdownButtonTapped()
}
2. DropdownButtonView 클래스
- DropdownButtonView 클래스는 커스텀 뷰로, 드롭다운 버튼과 옵션 목록을 포함한다.
class DropdownButtonView: UIView {
3. 변수 선언: delegate, button, options, tableView, isOpen
- delegate: DropdownButtonViewDelegate 프로토콜을 따르는 델리게이트 객체 저장
- button: 드롭다운 버튼을 정의 → 기본 설정, 타이틀, 색상, 폰트, 이미지 등을 설정
- options: 드롭다운에 표시될 옵션들을 저장하는 배열
- tableView: 옵션 목록을 표시할 UITableView 저장
- isOpen: 드롭다운이 열려 있는지 여부를 나타내는 플래그
weak var delegate: DropdownButtonViewDelegate?
private let button: UIButton = {
let button = UIButton(type: .system)
button.setTitle("최신순", for: .normal)
button.setTitleColor(.challendarBlack60, for: .normal)
button.titleLabel?.font = .pretendardMedium(size: 14)
button.tintColor = .challendarGrey50
button.setImage(UIImage(named: "arrowDown")?.resizeImage(to: CGSize(width: 16, height: 16)), for: .normal)
button.semanticContentAttribute = .forceRightToLeft
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.challendarBlack60.cgColor
button.layer.cornerRadius = 12
return button
}()
private let options = ["기한임박", "등록순", "최신순"]
private var tableView: UITableView!
private var isOpen = false
4. 초기화 메서드: DropdownButtonView의 인스턴스 초기화
- configureUI(): UI 요소들 설정
- configureConstraint(): UI 요소들의 레이아웃 제약 조건을 설정
override init(frame: CGRect) {
super.init(frame: frame)
configureUI()
configureConstraint()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
5. UI 구성
- button: 버튼을 뷰에 추가하고, 버튼이 눌렸을 때 toggleDropdown 메서드가 호출되도록 설정
- tableView: UITableView를 초기화하고 델리게이트와 데이터 소스를 설정 → 초기 상태에서는 숨김(isHidden = true) 상태. 테이블 뷰의 스타일과 셀을 등록하고 마지막으로 테이블 뷰를 뷰에 추가했다.
func configureUI() {
addSubview(button)
button.addTarget(self, action: #selector(toggleDropdown), for: .touchUpInside)
tableView = UITableView()
tableView.delegate = self
tableView.dataSource = self
tableView.isHidden = true
tableView.layer.cornerRadius = 5
tableView.backgroundColor = .black
tableView.separatorStyle = .none
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
addSubview(tableView)
}
6. 제약 조건 설정: NSLayoutConstraint를 사용하여 버튼과 테이블 뷰의 제약 조건 설정
- button: 버튼의 너비, 높이, leading, trailing, topAnchor, bottonAnchor 제약 조건 설정
- tableView: 테이블 뷰의 topAncho, leading, trailing, 높이 제약 조건 설정
func configureConstraint() {
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: 69),
button.heightAnchor.constraint(equalToConstant: 24),
button.leadingAnchor.constraint(equalTo: leadingAnchor),
button.trailingAnchor.constraint(equalTo: trailingAnchor),
button.topAnchor.constraint(equalTo: topAnchor),
button.bottomAnchor.constraint(equalTo: bottomAnchor)
])
tableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: button.bottomAnchor, constant: 5),
tableView.leadingAnchor.constraint(equalTo: leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: trailingAnchor),
tableView.heightAnchor.constraint(equalToConstant: 132)
])
}
7. toggleDropdown
- 드롭다운 버튼이 눌렸을 때 호출 → isOpen 상태를 토글하고, 테이블 뷰의 숨김 상태를 토글. 델리게이트 메서드 dropdownButtonTapped()를 호출.
@objc private func toggleDropdown() {
isOpen.toggle()
tableView.isHidden = !isOpen
delegate?.dropdownButtonTapped() // 드롭다운 버튼이 눌렸을 때 델리게이트 호출
}
8. UITableViewDelegate & UITableViewDataSource (extension)
- UITableViewDelegate 및 UITableViewDataSource 메서드:
- numberOfRowsInSection(section:): 테이블 뷰의 섹션에 있는 행의 수를 반환 (여기서는 옵션의 개수)
- cellForRowAt(indexPath:): 테이블 뷰의 각 행에 대한 셀을 설정 → 셀의 텍스트를 설정하고, 셀의 텍스트 색상과 배경 색상을 설정
- didSelectRowAt(indexPath:): 테이블 뷰의 행이 선택되었을 때 호출. 선택 옵션을 버튼의 타이틀로 설정하고, 드롭다운을 닫고, 델리게이트 메서드 didSelectOption(option:) 호출
extension DropdownButtonView: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return options.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = options[indexPath.row]
cell.textLabel?.textColor = .white
cell.backgroundColor = .black
cell.selectionStyle = .none
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let option = options[indexPath.row]
button.setTitle(option, for: .normal)
isOpen = false
tableView.isHidden = true
delegate?.didSelectOption(option)
}
}
9. UIImage 확장: UIImage의 크기를 변경하는 메서드를 추가
- resizeImage(to size:): 지정된 크기로 이미지를 리사이즈한다. 컨텍스트에서 이미지를 가져오고, 컨텍스트를 종료. 리사이즈된 이미지를 반환
extension UIImage {
func resizeImage(to size: CGSize) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
draw(in: CGRect(origin: .zero, size: size))
let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return resizedImage
}
}
여기까지 DropdownButtonView 컴포넌트 만들기 끝.
TodoCalendarViewController 에서 DropdownButtonView 를 연결하기
1. DropdownButtonView의 인스턴스를 dropdownButtonView 변수로 선언
class TodoCalendarViewController: BaseViewController {
private let periodBtnView = PeriodPickerButtonView() // 기간피커
private let dropdownButtonView = DropdownButtonView() // 드롭다운 버튼 뷰
// 나머지 변수들...
}
2. configureUI() 메서드에서 추가
- view.addSubview(dropdownButtonView)를 통해 DropdownButtonView를 뷰 계층에 추가
- 제약 조건 설정: dropdownButtonView.snp.makeConstraints를 사용하여 드롭다운 버튼의 위치와 크기를 설정. 위치는 make.top.equalToSuperview().offset(150)와 같이 값을 설정하여 화면에서 원하는 위치에 배치
override func configureUI() {
super.configureUI()
collectionView.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide.snp.top)
make.leading.equalToSuperview().offset(16)
make.trailing.equalToSuperview().offset(-16)
make.bottom.equalToSuperview()
}
view.addSubview(periodBtnView) // 기간피커
view.addSubview(dropdownButtonView) // 드롭다운 버튼 뷰 추가
periodBtnView.snp.makeConstraints { make in
make.width.equalTo(131)
make.left.equalToSuperview().offset(16)
make.top.equalToSuperview().offset(104)
}
dropdownButtonView.snp.makeConstraints { make in
make.width.equalTo(69)
make.height.equalTo(24)
make.top.equalToSuperview().offset(150) // 적절한 위치로 조정
make.left.equalToSuperview().offset(16)
}
}
3. 델리게이트 설정
- viewDidLoad() 메서드에서 dropdownButtonView.delegate = self를 통해 DropdownButtonView의 델리게이트를 TodoCalendarViewController로 설정 → 드롭다운 버튼에서 발생하는 이벤트를 컨트롤러에서 처리할 수 있다.
override func viewDidLoad() {
filterTodoitems()
configureCollectionView()
super.viewDidLoad()
configureFloatingButton()
configureTitleNavigationBar(title: "월간")
periodBtnView.delegate = self
dropdownButtonView.delegate = self // 드롭다운 버튼 뷰의 델리게이트 설정
}
4. DropdownButtonViewDelegate 구현: 프로토콜을 구현하여 드롭다운 버튼 뷰의 델리게이트 메서드 정의
- didSelectOption(_ option: String): 드롭다운에서 옵션이 선택되었을 때 호출. 선택된 옵션을 처리하는 로직을 여기에 작성
- dropdownButtonTapped(): 드롭다운 버튼이 눌렸을 때 호출. 드롭다운 버튼이 눌렸을 때의 동작을 여기에 정의.
extension TodoCalendarViewController: DropdownButtonViewDelegate {
func didSelectOption(_ option: String) {
// 드롭다운에서 선택된 옵션을 처리합
print("Selected option: \(option)")
}
func dropdownButtonTapped() {
// 드롭다운 버튼이 눌렸을 때의 동작을 정의
print("Dropdown button tapped")
}
}
DropdownButtonView 코드 전체 ▽
import UIKit
protocol DropdownButtonViewDelegate: AnyObject {
func didSelectOption(_ option: String)
func dropdownButtonTapped()
}
class DropdownButtonView: UIView {
weak var delegate: DropdownButtonViewDelegate?
private let button: UIButton = {
let button = UIButton(type: .system)
button.setTitle("최신순", for: .normal)
button.setTitleColor(.challendarBlack60, for: .normal)
button.titleLabel?.font = .pretendardMedium(size: 14)
button.tintColor = .challendarGrey50
button.setImage(UIImage(named: "arrowDown")?.resizeImage(to: CGSize(width: 16, height: 16)), for: .normal)
button.semanticContentAttribute = .forceRightToLeft
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.challendarBlack60.cgColor
button.layer.cornerRadius = 12
return button
}()
private let options = ["기한임박", "등록순", "최신순"]
private var tableView: UITableView!
private var isOpen = false
override init(frame: CGRect) {
super.init(frame: frame)
configureUI()
configureConstraint()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureUI() {
addSubview(button)
button.addTarget(self, action: #selector(toggleDropdown), for: .touchUpInside)
tableView = UITableView()
tableView.delegate = self
tableView.dataSource = self
tableView.isHidden = true
tableView.layer.cornerRadius = 5
tableView.backgroundColor = .black
tableView.separatorStyle = .none
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
addSubview(tableView)
}
func configureConstraint() {
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: 69),
button.heightAnchor.constraint(equalToConstant: 24),
button.leadingAnchor.constraint(equalTo: leadingAnchor),
button.trailingAnchor.constraint(equalTo: trailingAnchor),
button.topAnchor.constraint(equalTo: topAnchor),
button.bottomAnchor.constraint(equalTo: bottomAnchor)
])
tableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: button.bottomAnchor, constant: 5),
tableView.leadingAnchor.constraint(equalTo: leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: trailingAnchor),
tableView.heightAnchor.constraint(equalToConstant: 132)
])
}
@objc private func toggleDropdown() {
isOpen.toggle()
tableView.isHidden = !isOpen
delegate?.dropdownButtonTapped() // 드롭다운 버튼이 눌렸을 때 델리게이트 호출
}
}
extension DropdownButtonView: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return options.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = options[indexPath.row]
cell.textLabel?.textColor = .white
cell.backgroundColor = .black
cell.selectionStyle = .none
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let option = options[indexPath.row]
button.setTitle(option, for: .normal)
isOpen = false
tableView.isHidden = true
delegate?.didSelectOption(option)
}
}
extension UIImage {
func resizeImage(to size: CGSize) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
draw(in: CGRect(origin: .zero, size: size))
let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return resizedImage
}
}
TodoCalendarViewController에서 dropdownBtn 과 periodPicker 연결 코드 전제 ▽
class TodoCalendarViewController: BaseViewController {
private let periodBtnView = PeriodPickerButtonView() // 기간피커
private let dropdownButtonView = DropdownButtonView() // 드롭다운 버튼 뷰
private var todoItems: [Todo] = CoreDataManager.shared.fetchTodos()
private var completedTodo : [Todo] = []
private var inCompletedTodo: [Todo] = []
var days : [Day] = []
var changedMonth : Date?
var currentDate : Date?
private var collectionView: UICollectionView!
override func viewDidLoad() {
filterTodoitems()
configureCollectionView()
super.viewDidLoad()
configureFloatingButton()
configureTitleNavigationBar(title: "월간")
periodBtnView.delegate = self
dropdownButtonView.delegate = self // 드롭다운 버튼 뷰의 델리게이트 설정
}
override func configureUI() {
super.configureUI()
collectionView.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide.snp.top)
make.leading.equalToSuperview().offset(16)
make.trailing.equalToSuperview().offset(-16)
make.bottom.equalToSuperview()
}
view.addSubview(periodBtnView) // 기간피커
view.addSubview(dropdownButtonView) // 드롭다운 버튼 뷰 추가
periodBtnView.snp.makeConstraints { make in
make.width.equalTo(131)
make.left.equalToSuperview().offset(16)
make.top.equalToSuperview().offset(104)
}
dropdownButtonView.snp.makeConstraints { make in
make.width.equalTo(69)
make.height.equalTo(24)
make.top.equalToSuperview().offset(150) // 적절한 위치로 조정
make.left.equalToSuperview().offset(16)
}
}
}
extension TodoCalendarViewController: DropdownButtonViewDelegate {
func didSelectOption(_ option: String) {
// 드롭다운에서 선택된 옵션을 처리합니다.
print("Selected option: \(option)")
}
func dropdownButtonTapped() {
// 드롭다운 버튼이 눌렸을 때의 동작을 정의합니다.
print("Dropdown button tapped")
}
}
'iOS 앱 개발자 프로젝트' 카테고리의 다른 글
[iOS] header에 deleteButton 추가하기 (+ custom header) (0) | 2024.06.09 |
---|---|
[iOS] collectionView의 custom header 설정하기 (0) | 2024.06.08 |
[iOS] picker 버튼을 UIView로 만들기 (UIButton → UIView) (0) | 2024.06.08 |
[iOS] picker 버튼에 이미지 추가하기 (UIButton) (0) | 2024.06.05 |
[iOS] 옵션 선택을 위한 picker 버튼 만들기 (0) | 2024.06.05 |