이모지 선택 기능 구현

hyun·2025년 9월 1일
0

iOS

목록 보기
47/54

타이머 생성/편집 화면에서 사용자가 이모지를 선택할 수 있는 기능 구현

1. 커스텀 EmojiTextField 클래스

final class EmojiTextField: UITextField {
  override var textInputContextIdentifier: String? { "" }
  
  override var textInputMode: UITextInputMode? {
    if let emojiMode = UITextInputMode.activeInputModes.first(where: { $0.primaryLanguage == "emoji" }) {
      return emojiMode
    }
    return super.textInputMode
  }
}

textInputMode를 오버라이드해서 이모지 키보드를 우선적으로 표시
activeInputModes에서 primaryLanguage == emoji인 모드를 찾아 반환
이모지 키보드가 없으면 기본 키보드로 폴백

2. 숨겨진 텍스트필드 패턴

private lazy var emojiInputField = EmojiTextField().then {
  $0.autocorrectionType = .no
  $0.spellCheckingType = .no
  $0.autocapitalizationType = .none
  $0.returnKeyType = .done
  $0.tintColor = .clear // 커서 숨김
  $0.textColor = .clear // 텍스트 숨김
  $0.backgroundColor = .clear
  $0.isHidden = true
  $0.delegate = self
  $0.addTarget(self, action: #selector(emojiTextChanged), for: .editingChanged)
}

실제 UI에는 보이지 않지만 키보드 입력을 받는 숨겨진 텍스트필드
모든 시각적 요소를 숨김 (tintColor, textColor clear, isHidden true)
editingChanged 이벤트로 입력 감지

3. 이모지 버튼 상태 관리

@objc private func emojiButtonTapped() {
  emojiInputField.becomeFirstResponder()
}

@objc private func emojiTextChanged(_ sender: UITextField) {
  let text = sender.text ?? ""
  guard let firstGrapheme = text.first else { return }
  let emoji = String(firstGrapheme)
  
  setEmojiOnButton(emoji)
  
  // 햅틱 & 정리
  UIImpactFeedbackGenerator(style: .soft).impactOccurred()
  sender.text = ""
  sender.resignFirstResponder()
}

버튼 탭 시 숨겨진 텍스트필드가 first responder가 되어 키보드 표시
첫 번째 grapheme만 추출하여 이모지로 사용
입력 후 즉시 텍스트필드를 정리하고 키보드 닫기

4. 이모지 표시 및 리셋 기능

private func setEmojiOnButton(_ emoji: String) {
  selectedEmoji = emoji
  
  // 이미지 제거 후 타이틀로 이모지 표시
  emojiButton.setImage(nil, for: .normal)
  emojiButton.setAttributedTitle(
    Typography.attributed(emoji, style: .headingXl(weight: .semibold), color: .appBlack),
    for: .normal
  )
  emojiButton.tintColor = Palette.Primary.p600
  dashedCircleView.isHidden = true
  
  // 살짝 튀는 애니메이션
  UIView.animate(withDuration: 0.08, animations: {
    self.emojiButton.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
  }) { _ in
    UIView.animate(withDuration: 0.18) {
      self.emojiButton.transform = .identity
    }
  }
}

@objc private func resetEmoji(_ gr: UILongPressGestureRecognizer) {
  guard gr.state == .began else { return }
  let image = UIImage(named: "plus")?.withRenderingMode(.alwaysTemplate)
  emojiButton.setAttributedTitle(nil, for: .normal)
  emojiButton.setImage(image, for: .normal)
  emojiButton.tintColor = Palette.Primary.p600
  dashedCircleView.isHidden = false
  UIImpactFeedbackGenerator(style: .light).impactOccurred()
}

이모지 선택 시 : 기본 plus 이미지 제거 → 이모지를 title로 설정 → 점선 원 숨김
롱프레스로 리셋 : 이모지 제거 → plus 이미지 복원 → 점선 원 표시
스케일 애니메이션으로 시각적 피드백 제공

UITextInputMode를 활용해 특정 키보드 모드를 프로그래밍적으로 선택 가능
시스템에서 활성화된 입력 모드 중 이모지 키보드를 우선적으로 찾는 방식

UI 요소와 입력 처리를 분리하는 깔끔한 방법
버튼은 시각적 표현만 담당, 실제 입력은 숨겨진 텍스트필드가 처리
복잡한 커스텀 입력 뷰를 만들지 않고도 원하는 UX 구현 가능

String.first로 첫 번째 grapheme을 안전하게 추출
이모지는 여러 유니코드 문자로 구성될 수 있어 character 단위 처리가 중요

저장 시 아이콘 결정 로직

let attributed = emojiButton.attributedTitle(for: .normal)?.string
let icon = selectedEmoji ?? (attributed?.isEmpty == false ? attributed! : "🟣")

selectedEmoji (사용자가 새로 선택한 이모지)
기존 button의 attributedTitle (편집 모드에서 기존값)
기본값 🟣

0개의 댓글