본문 바로가기

iOS 앱 개발자 프로젝트

[iOS] picker 버튼에 이미지 추가하기 (UIButton)

이미지가 있는 picker 버튼을 만들어봅니다.

앞서 만든 버튼과 달리 이미지 애셋이 추가된 버전으로

PeriodPickerBtnView라는 컴포넌트 뷰는 따로 빼서 연결해 주었습니다. 

 

+ TodoCalendarViewController에는 많은 요소들과 func들이 있지만

이 곳에서는 셀을 구성하는 collectionView와 PickerBtn과 관련된 코드만 빼서 기록해 둡니다.


 

1. TodoCalendarViewController

BaseViewController를 상속받은 클래스로 "할 일 캘린더" 기능을 담당하는 뷰 컨트롤러

→ 사용자에게 일정 관리와 할 일 목록을 월간, 주간, 일간 보기로 제공하는 UI를 관리

 

BaseViewController

:  BaseViewController 상속받아 기본적인  컨트롤러 설정  공통 기능을 사용 →   BaseViewController에서 정의된 configureUI(), configureConstraint(), configureUtil(), configureNotificationCenter() 메서드를 오버라이드하여 다른 팀원들과 일관성있는 메소드로 UI 구성과 제약 조건 설정했다. 

class TodoCalendarViewController: BaseViewController  {

 

 

2. PeriodPickerButtonView 인스턴스

private let periodBtnView = PeriodPickerButtonView()

: PeriodPickerButtonView의 인스턴스를 생성 →  기간을 선택하는 버튼들 포함 (월간, 주간, 일간 3개의 버튼)

private let periodBtnView = PeriodPickerButtonView()

 

 

3. periodBtnView.delegate

: PeriodPickerButtonView 델리게이트를 TodoCalendarViewController로 설정 →  버튼이 눌리면 TodoCalendarViewController가 알림을 받게 된다. 

periodBtnView.delegate = self

 

 

4. addSubview

: PeriodPickerButtonView를 현재 뷰에 추가

view.addSubview(periodBtnView)

 

 

5. periodBtnView.snp.makeConstraints

: SnapKit을 사용하여 PeriodPickerButtonView의 위치와 크기를 설정

periodBtnView.snp.makeConstraints

 

 

6. extension TodoCalendarViewController

PeriodPickerButtonViewDelegate 프로토콜을 구현하여, PeriodPickerButtonView에서 발생하는 이벤트 처리

extension TodoCalendarViewController : PeriodPickerButtonViewDelegate {
    // dailyButton이 눌렸을 때 호출되는 메서드

 

 

7. func didTapdailyButton()

: PeriodPickerButtonView dailyButton 눌렸을  호출되는 메서드 → DailyViewController 인스턴스를 생성하고 네비게이션 스택에 푸시하여 일 뷰로 이동

    func didTapdailyButton() {
        let weeklyVC = DailyViewController() // 일간 뷰컨트롤러 인스턴스 생성
        self.navigationController?.pushViewController(weeklyVC, animated: true) // 주간 뷰컨트롤러로 이동
    }
}

 

 

1-7 코드 

import UIKit
import SnapKit

class TodoCalendarViewController: BaseViewController  {
    // PeriodPickerButtonView 인스턴스를 생성
    private let periodBtnView = PeriodPickerButtonView() // 기간피커

    private var collectionView: UICollectionView!
    
    override func viewDidLoad() {

        configureCollectionView()
        super.viewDidLoad()
 
        periodBtnView.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) 
        
        // 기간피커의 제약 조건을 설정
        periodBtnView.snp.makeConstraints { make in
            make.width.equalTo(131)
            make.left.equalToSuperview().offset(16) // x 좌표 설정
            make.top.equalToSuperview().offset(104) // y 좌표 설정
        }
    }
}

// PeriodPickerButtonViewDelegate 프로토콜을 구현하여 기간피커 버튼이 눌렸을 때의 동작 정의
extension TodoCalendarViewController : PeriodPickerButtonViewDelegate {
    // dailyButton이 눌렸을 때 호출되는 메서드
    func didTapdailyButton() {
        let weeklyVC = DailyViewController() // 일간 뷰컨트롤러 인스턴스 생성
        self.navigationController?.pushViewController(weeklyVC, animated: true) // 주간 뷰컨트롤러로 이동
    }
}

 

 


 

 

 

이제 PeriodPickerBtnView의 UI를 아래 코드를 통해 그려보자.

 

 

1. PeriodPickerButtonViewDelegate 프로토콜

didTapdailyButton: PeriodPickerButtonView에서 dailyButton 눌렸을 호출되는 메서드를 정의

// 델리게이트 프로토콜 선언: PeriodPickerButtonView에서 버튼이 탭될 때 호출될 메서드를 정의
protocol PeriodPickerButtonViewDelegate: AnyObject {
    func didTapdailyButton()
}

 

 

2. PeriodPickerButtonView 클래스

  • delegate: 이 뷰의 델리게이트로, 버튼 탭 이벤트를 전달
  • button1, button2, button3: "월간", "주간", "일간" 버튼을 정의
  • topSeparator, bottomSeparator: 버튼 사이의 구분선을 정의
  • buttons: 세 버튼을 배열로 관리
class PeriodPickerButtonView: UIView {
    
    // 델리게이트 변수
    // 델리게이트를 통해 버튼 탭 이벤트를 전달
    weak var delegate: PeriodPickerButtonViewDelegate?
    
    // 첫 번째 버튼 정의
    // "월간" 버튼을 설정
    private let button1: UIButton = {
         let button = UIButton()
         var configuration = UIButton.Configuration.plain()
         configuration.title = "월간"
         configuration.image = UIImage(systemName: "calendar")
         configuration.imagePadding = 44 // 이미지와 텍스트 간의 간격을 넓힘
         configuration.imagePlacement = .trailing
         configuration.baseBackgroundColor = .clear // 배경 색상을 투명으로 설정
         button.configuration = configuration
         button.setTitleColor(.challendarBlack60, for: .normal)
         button.setTitleColor(.white, for: .selected)
         button.tintColor = .challendarBlack60
         button.contentHorizontalAlignment = .center
         return button
     }()
    
    // 두 번째 버튼 정의
    // "주간" 버튼을 설정
    private let button2: UIButton = {
        let button = UIButton()
        var configuration = UIButton.Configuration.plain()
        configuration.title = "주간"
        configuration.image = UIImage(systemName: "calendar")
        configuration.imagePadding = 44
        configuration.imagePlacement = .trailing
        configuration.baseBackgroundColor = .clear // 배경 색상을 투명으로 설정
        button.configuration = configuration
        button.setTitleColor(.challendarBlack60, for: .normal)
        button.setTitleColor(.white, for: .selected)
        button.tintColor = .challendarBlack60
        button.contentHorizontalAlignment = .center
        return button
    }()
    
    // 세 번째 버튼 정의
    // "일간" 버튼을 설정
    private let button3: UIButton = {
        let button = UIButton()
        var configuration = UIButton.Configuration.plain()
        configuration.title = "일간"
        configuration.image = UIImage(systemName: "calendar")
        configuration.imagePadding = 44
        configuration.imagePlacement = .trailing
        configuration.baseBackgroundColor = .clear // 배경 색상을 투명으로 설정
        button.configuration = configuration
        button.setTitleColor(.challendarBlack60, for: .normal)
        button.setTitleColor(.white, for: .selected)
        button.tintColor = .challendarBlack60
        button.contentHorizontalAlignment = .center
        return button
    }()
    
    // 상단 구분선
    private let topSeparator: UIView = {
        let view = UIView()
        view.backgroundColor = .challendarBlack60
        return view
    }()
    
    // 하단 구분선
    private let bottomSeparator: UIView = {
        let view = UIView()
        view.backgroundColor = .challendarBlack60
        return view
    }()
    
    // 버튼 배열
    // 버튼들을 배열로 관리
    private var buttons: [UIButton] {
        return [button1, button2, button3]
    }

 

 

 

3. init(frame:) 초기화 메서드

뷰를 초기화하고 configureUI(), configureConstraint(), configureUtil() 호출

    // 초기화 메서드
    // 뷰의 초기 설정
    override init(frame: CGRect) {
        super.init(frame: frame)
        configureUI()
        configureConstraint()
        configureUtil()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

 

 

4. configureUI()

뷰에 버튼과 구분선을 추가하고, 뷰의 기본 속성을 설정

    // UI 설정 메서드
    // 뷰에 UI 요소들을 추가하고 기본 속성을 설정
    private func configureUI() {
        addSubview(button1)
        addSubview(button2)
        addSubview(button3)
        addSubview(topSeparator)
        addSubview(bottomSeparator)
        
        layer.cornerRadius = 12
        clipsToBounds = true
        backgroundColor = .black // 챌린지100으로 추후 수정
    }

 

 

5. configureConstraint()

SnapKit 사용하여 UI 요소의 제약 조건을 설정

    // 제약 조건 설정 메서드
    // SnapKit을 사용하여 UI 요소들의 제약 조건을 설정
    private func configureConstraint() {
        button1.snp.makeConstraints { make in
            make.top.leading.trailing.equalToSuperview()
            make.height.equalTo(44)
        }
        
        button2.snp.makeConstraints { make in
            make.top.equalTo(button1.snp.bottom)
            make.leading.trailing.equalToSuperview()
            make.height.equalTo(44)
        }
        
        button3.snp.makeConstraints { make in
            make.top.equalTo(button2.snp.bottom)
            make.leading.trailing.bottom.equalToSuperview()
            make.height.equalTo(44)
        }
        
        topSeparator.snp.makeConstraints { make in
            make.top.equalTo(button2.snp.top)
            make.leading.trailing.equalToSuperview()
            make.height.equalTo(0.2) // 얇은 실선의 높이
        }
        
        bottomSeparator.snp.makeConstraints { make in
            make.top.equalTo(button2.snp.bottom)
            make.leading.trailing.equalToSuperview()
            make.height.equalTo(0.2) // 얇은 실선의 높이
        }
    }

 

 

6. configureUtil()

setupActions() 호출하여 버튼 액션을 설정

    // 유틸리티 설정 메서드
    // 액션 설정 등의 추가 설정을 담당
    private func configureUtil() {
        setupActions()
    }

 

 

7. setupActions()

버튼에 대한 액션을 설정

    // 버튼 액션 설정 메서드
    // 버튼들에 대한 액션을 설정
    private func setupActions() {
        button1.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
        button2.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
        button3.addTarget(self, action: #selector(dailyButtonTapped), for: .touchUpInside)
    }

 

 

8. dailyButtonTapped()

델리게이트를 통해 dailyButton 이벤트를 전달

    // dailyButton 탭 메서드
    // 델리게이트를 통해 dailyButton 탭 이벤트를 전달
    @objc private func dailyButtonTapped() {
         delegate?.didTapdailyButton()
     }

 

 

9. buttonTapped(_:)

탭된 버튼을 선택 상태로 만들고 다른 버튼들은 선택 해제 + 선택된 버튼의 색상을 변경

    // 버튼 탭 메서드
    // 버튼이 탭되었을 때의 동작을 정의
    @objc private func buttonTapped(_ sender: UIButton) {
        buttons.forEach {
            $0.isSelected = false
            $0.tintColor = .challendarBlack60 // 기본 색상으로 되돌림
        }
        sender.isSelected = true
        sender.tintColor = .white // 선택된 버튼 색상 변경
    }
}

 

 

1-9 코드 ▽

//
//  PeriodPickerButtonView.swift
//  Challendar
//
//  Created by 채나연 on 6/5/24.
//

import UIKit
import SnapKit

// 델리게이트 프로토콜 선언: PeriodPickerButtonView에서 버튼이 탭될 때 호출될 메서드를 정의
protocol PeriodPickerButtonViewDelegate: AnyObject {
    func didTapdailyButton()
}

class PeriodPickerButtonView: UIView {
    
    // 델리게이트 변수
    // 델리게이트를 통해 버튼 탭 이벤트를 전달
    weak var delegate: PeriodPickerButtonViewDelegate?
    
    // 첫 번째 버튼 정의
    // "월간" 버튼을 설정
    private let button1: UIButton = {
         let button = UIButton()
         var configuration = UIButton.Configuration.plain()
         configuration.title = "월간"
         configuration.image = UIImage(systemName: "calendar")
         configuration.imagePadding = 44 // 이미지와 텍스트 간의 간격을 넓힘
         configuration.imagePlacement = .trailing
         configuration.baseBackgroundColor = .clear // 배경 색상을 투명으로 설정
         button.configuration = configuration
         button.setTitleColor(.challendarBlack60, for: .normal)
         button.setTitleColor(.white, for: .selected)
         button.tintColor = .challendarBlack60
         button.contentHorizontalAlignment = .center
         return button
     }()
    
    // 두 번째 버튼 정의
    // "주간" 버튼을 설정
    private let button2: UIButton = {
        let button = UIButton()
        var configuration = UIButton.Configuration.plain()
        configuration.title = "주간"
        configuration.image = UIImage(systemName: "calendar")
        configuration.imagePadding = 44
        configuration.imagePlacement = .trailing
        configuration.baseBackgroundColor = .clear // 배경 색상을 투명으로 설정
        button.configuration = configuration
        button.setTitleColor(.challendarBlack60, for: .normal)
        button.setTitleColor(.white, for: .selected)
        button.tintColor = .challendarBlack60
        button.contentHorizontalAlignment = .center
        return button
    }()
    
    // 세 번째 버튼 정의
    // "일간" 버튼을 설정
    private let button3: UIButton = {
        let button = UIButton()
        var configuration = UIButton.Configuration.plain()
        configuration.title = "일간"
        configuration.image = UIImage(systemName: "calendar")
        configuration.imagePadding = 44
        configuration.imagePlacement = .trailing
        configuration.baseBackgroundColor = .clear // 배경 색상을 투명으로 설정
        button.configuration = configuration
        button.setTitleColor(.challendarBlack60, for: .normal)
        button.setTitleColor(.white, for: .selected)
        button.tintColor = .challendarBlack60
        button.contentHorizontalAlignment = .center
        return button
    }()
    
    // 상단 구분선
    private let topSeparator: UIView = {
        let view = UIView()
        view.backgroundColor = .challendarBlack60
        return view
    }()
    
    // 하단 구분선
    private let bottomSeparator: UIView = {
        let view = UIView()
        view.backgroundColor = .challendarBlack60
        return view
    }()
    
    // 버튼 배열
    // 버튼들을 배열로 관리
    private var buttons: [UIButton] {
        return [button1, button2, button3]
    }
    
    // 초기화 메서드
    // 뷰의 초기 설정
    override init(frame: CGRect) {
        super.init(frame: frame)
        configureUI()
        configureConstraint()
        configureUtil()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // UI 설정 메서드
    // 뷰에 UI 요소들을 추가하고 기본 속성을 설정
    private func configureUI() {
        addSubview(button1)
        addSubview(button2)
        addSubview(button3)
        addSubview(topSeparator)
        addSubview(bottomSeparator)
        
        layer.cornerRadius = 12
        clipsToBounds = true
        backgroundColor = .black // 챌린지100으로 추후 수정
    }
    
    // 제약 조건 설정 메서드
    // SnapKit을 사용하여 UI 요소들의 제약 조건을 설정
    private func configureConstraint() {
        button1.snp.makeConstraints { make in
            make.top.leading.trailing.equalToSuperview()
            make.height.equalTo(44)
        }
        
        button2.snp.makeConstraints { make in
            make.top.equalTo(button1.snp.bottom)
            make.leading.trailing.equalToSuperview()
            make.height.equalTo(44)
        }
        
        button3.snp.makeConstraints { make in
            make.top.equalTo(button2.snp.bottom)
            make.leading.trailing.bottom.equalToSuperview()
            make.height.equalTo(44)
        }
        
        topSeparator.snp.makeConstraints { make in
            make.top.equalTo(button2.snp.top)
            make.leading.trailing.equalToSuperview()
            make.height.equalTo(0.2) // 얇은 실선의 높이
        }
        
        bottomSeparator.snp.makeConstraints { make in
            make.top.equalTo(button2.snp.bottom)
            make.leading.trailing.equalToSuperview()
            make.height.equalTo(0.2) // 얇은 실선의 높이
        }
    }
    
    // 유틸리티 설정 메서드
    // 액션 설정 등의 추가 설정을 담당
    private func configureUtil() {
        setupActions()
    }
    
    // 버튼 액션 설정 메서드
    // 버튼들에 대한 액션을 설정
    private func setupActions() {
        button1.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
        button2.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
        button3.addTarget(self, action: #selector(dailyButtonTapped), for: .touchUpInside)
    }
    
    // dailyButton 탭 메서드
    // 델리게이트를 통해 dailyButton 탭 이벤트를 전달
    @objc private func dailyButtonTapped() {
         delegate?.didTapdailyButton()
     }
    
    // 버튼 탭 메서드
    // 버튼이 탭되었을 때의 동작을 정의
    @objc private func buttonTapped(_ sender: UIButton) {
        buttons.forEach {
            $0.isSelected = false
            $0.tintColor = .challendarBlack60 // 기본 색상으로 되돌림
        }
        sender.isSelected = true
        sender.tintColor = .white // 선택된 버튼 색상 변경
    }
}