[Swift] UIButton 텍스트 + 이미지 (이미지만 회전시키기)

이경은·2024년 4월 5일
0
post-thumbnail
post-custom-banner

날짜를 누르면 달력을 보여주고 날짜를 선택할 수 있는 버튼을 만들었는데, setTitle로 날짜만 기입해놓으니까 사용자가 버튼이라는 것을 인식하지 못한다는 피드백이 들어왔습니다.

프로젝트 마감기한 + 발표를 앞두고 있어서 우선 날짜 뒤에 "▼"을 추가해서 급히 마무리를 했었습니다.(이거 버튼이에요~ 라고 급히 마무리했습니다.)

발표도 끝났으니, 이제 이 버튼을 수정해보려고 합니다.

  1. "▼"이 아니라 이미지로 삽입하기
  2. 버튼을 눌렀을 때, 〉 → V 형태로 회전하도록 만들기

이렇게하면 사용자도 날짜 선택 화면을 펼칠 수 있다고 인식할 수 있을 겁니다.

UIButton에 텍스트+이미지를 삽입하기

아래는 기존의 버튼입니다.

    private lazy var datePickingButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("\(dateString) ▼", for: .normal)
        button.titleLabel?.font = UIFont(name: "SFProDisplay-Bold", size: 20)
        button.setTitleColor(.mainTheme, for: .normal)
        button.addTarget(self, action: #selector(datePickingButtonTapped), for: .touchUpInside)
        return button
    }()

setImage, setTitle로 버튼에 이미지와 텍스트를 함께 사용할 수 있습니다.

    private lazy var datePickingButton: UIButton = {
        let button = UIButton(type: .system)
        button.setImage(UIImage(systemName: "chevron.forward"), for: .normal)	// 이미지를 추가
        button.tintColor = .mainTheme
        button.setTitle("\(dateString)", for: .normal)
        button.titleLabel?.font = UIFont(name: "SFProDisplay-Bold", size: 20)
        button.setTitleColor(.mainTheme, for: .normal)
        button.addTarget(self, action: #selector(datePickingButtonTapped), for: .touchUpInside)
        return button
    }()

그러면 아래처럼 이미지가 왼쪽, 텍스트가 오른쪽으로 정렬됩니다.

순서를 바꿔주고 싶다면, semanticContentAttribute.forceRightToLeft로 사용하면 순서를 바꿔줄 수 있죠.

private lazy var datePickingButton: UIButton = {
    let button = UIButton(type: .system)
    button.setImage(UIImage(systemName: "chevron.forward"), for: .normal)
    button.tintColor = .mainTheme
    button.setTitle("\(dateString)", for: .normal)
    button.titleLabel?.font = UIFont(name: "SFProDisplay-Bold", size: 20)
    button.setTitleColor(.mainTheme, for: .normal)
    button.addTarget(self, action: #selector(datePickingButtonTapped), for: .touchUpInside)

    // 버튼의 타이틀과 이미지를 오른쪽에서 왼쪽으로 정렬
    button.semanticContentAttribute = .forceRightToLeft

    return button
}()

정렬만 바뀌길 바랬는데 이미지까지 좌우로 반전되었군요. 저는 화살표를 반대로 바꿔주면 되는 문제라 chevron.forward에서 chevron.backward로 바꿔서 반전문제를 해결했습니다. 그리고 간격도 적절하게 띄워줘야 겠네요.

    private lazy var datePickingButton: UIButton = {
        let button = UIButton(type: .system)
        button.setImage(UIImage(systemName: "chevron.backward"), for: .normal)
        button.tintColor = .mainTheme
        button.imageView?.contentMode = .scaleAspectFit
        button.setTitle("\(dateString)", for: .normal)
        button.titleLabel?.font = UIFont(name: "SFProDisplay-Bold", size: 20)
        button.setTitleColor(.mainTheme, for: .normal)
        button.addTarget(self, action: #selector(datePickingButtonTapped), for: .touchUpInside)
        
        // 버튼의 타이틀과 이미지 순서를 오른쪽에서 왼쪽으로(이미지 좌우 반전)
        button.semanticContentAttribute = .forceRightToLeft
        // 버튼의 이미지와 타이틀 간격을 조정
        let spacing: CGFloat = 6 // 원하는 간격
        button.imageEdgeInsets = UIEdgeInsets(top: 0, left: spacing, bottom: 0, right: -spacing)
        button.titleEdgeInsets = UIEdgeInsets(top: 0, left: -spacing, bottom: 0, right: spacing)
        button.contentEdgeInsets = UIEdgeInsets(top: 0, left: spacing + button.imageView!.frame.width, bottom: 0, right: spacing)
        return button
    }()

이미지를 "chevron.forward"에서 "backward"로 바꿔주어 원하는 형태로 출력되도록 하였습니다.

그리고 spacing의 2배만큼 title과 image에 간격이 생겼습니다.


원하는 형태가 되었군요. 이제 버튼을 탭했을 때, 이미지를 회전시켜보겠습니다.

UIButton 내 이미지 회전시키기

기존 UIDatePicker를 불러오는 메서드에 button.imageView를 회전시켜주었습니다.

    @objc func datePickingButtonTapped() {
    	// 날짜선택을 위해서 DateSelectVC를 popover로 불러옵니다.
        let dateSelectVC = DateSelectVC()
        dateSelectVC.selectedDate = self.selectedDate
        dateSelectVC.delegate = self    // 델리게이트 설정
        dateSelectVC.modalPresentationStyle = .popover
        if let popoverController = dateSelectVC.popoverPresentationController {
            popoverController.sourceView = self.datePickingButton
            popoverController.sourceRect = self.datePickingButton.bounds
            popoverController.permittedArrowDirections = [.up, .down]
            popoverController.delegate = self
        }
        dateSelectVC.preferredContentSize = CGSize(width: 400, height: 400)
        self.present(dateSelectVC, animated: true, completion: nil)
        
        // 이미지를 90도 회전
        UIView.animate(withDuration: 0.25) {
            self.datePickingButton.imageView?.transform = CGAffineTransform(rotationAngle: .pi / 2)
        }
    }

비율은 그대로이고, 이미지만 회전하고 있군요. button.imageView.contentMode를 .center로 지정해줍시다.

chevron의 끄트머리가 잘리면서 뾰족해지는군요. button.imageView?.clipsToBounds를 false로 설정합니다.
잘림없이 이미지가 회전합니다.

새로운 문제발견

그런데 뭔가 이상하네요.. 처음 datePickingButton을 호출할 때 이미지가 회전하면서 부들부들 떨리는데, 그 이후로는 아주 자연스럽게 회전합니다.


위쪽과 아래쪽의 차이가 보이시나요??
처음 메서드를 호출하면 아래처럼 부들부들 떨며 돌아가네요. 그런데 두번째부터는 위처럼 깔끔하게 회전합니다..

예상컨데 아래처럼 imageView frame을 벗어나지 않게 이미지가 회전하려다가 contentMode가 .center로 맞춰지는 것 같은데..

아직은 뚜렷한 해답은 찾지 못했습니다... 임시방편으로 viewDidLoad시점에 pi*2(180º) 미리 회전시켜놓으면 사용자가 저런 상황을 만날 일은 없도록 해두려고 합니다.

근본적인 해결책은 아니므로 스택오버플로우에 질문을 올려두었는데, 답을 찾게되면 답변을 추가해두도록 하겠습니다.

아래는 제가 작성한 코드입니다.

    private lazy var datePickingButton: UIButton = {
        let button = UIButton(type: .system)
        button.setImage(UIImage(systemName: "chevron.backward"), for: .normal)
        button.imageView?.contentMode = .center
        button.imageView?.clipsToBounds = false
        button.tintColor = .mainTheme
        
        button.setTitle("\(dateString)", for: .normal)
        button.titleLabel?.font = UIFont(name: "SFProDisplay-Bold", size: 20)
        button.addTarget(self, action: #selector(datePickingButtonTapped), for: .touchUpInside)
        
        // 버튼의 타이틀과 이미지 순서를 오른쪽에서 왼쪽으로(이미지 좌우 반전)
        button.semanticContentAttribute = .forceRightToLeft
        // 버튼의 이미지와 타이틀 간격을 조정
        let spacing: CGFloat = 6 // 원하는 간격
        button.imageEdgeInsets = UIEdgeInsets(top: 0, left: spacing, bottom: 0, right: -spacing)
        button.titleEdgeInsets = UIEdgeInsets(top: 0, left: -spacing, bottom: 0, right: spacing)
        button.contentEdgeInsets = UIEdgeInsets(top: 0, left: spacing + button.imageView!.frame.width, bottom: 0, right: spacing)
        return button
    }()
    // 날짜 선택 로직
    @objc func datePickingButtonTapped() {
        let dateSelectVC = DateSelectVC()
        dateSelectVC.selectedDate = self.selectedDate
        dateSelectVC.delegate = self    // 델리게이트 설정
        dateSelectVC.modalPresentationStyle = .popover
        if let popoverController = dateSelectVC.popoverPresentationController {
            popoverController.sourceView = self.datePickingButton
            popoverController.sourceRect = self.datePickingButton.bounds
            popoverController.permittedArrowDirections = [.up, .down]
            popoverController.delegate = self
        }
        dateSelectVC.preferredContentSize = CGSize(width: 400, height: 400)
        self.present(dateSelectVC, animated: true, completion: nil)
        // 이미지 회전 애니메이션
        UIView.animate(withDuration: 0.25, animations: {
            // 현재 transform 상태에서 90도 회전
            self.datePickingButton.imageView?.transform = self.datePickingButton.imageView?.transform.rotated(by: .pi / 2) ?? CGAffineTransform.identity
        })
    }
post-custom-banner

0개의 댓글