SwiftUI 포커스 시스템 & 키보드 내부

발행: (2025년 12월 28일 오전 06:36 GMT+9)
9 min read
원문: Dev.to

Source: Dev.to

Sebastien Lato

SwiftUI 포커스는 간단해 보입니다:

@FocusState var isFocused: Bool

하지만 실제로는 그렇지 않을 때가 있습니다.

그때 겪게 되는 문제들은 다음과 같습니다:

  • 포커스가 무작위로 사라짐
  • 키보드가 예기치 않게 사라짐
  • 포커스가 필드 사이를 뛰어넘음
  • 폼 내비게이션이 깨짐
  • ScrollView와 키보드가 서로 충돌
  • 접근성 포커스가 다르게 동작
  • 내비게이션 후 포커스가 복구되지 않음

이 글에서는 SwiftUI 포커스가 내부적으로 어떻게 동작하는지, 키보드, 내비게이션, 스크롤 뷰, 접근성과 어떻게 상호작용하는지, 그리고 실제 앱에서 올바르게 사용하는 방법을 설명합니다.

🧠 정신 모델: 포커스는 상태 + 라우팅

SwiftUI 포커스는 단순히 불리언이 아닙니다. 내부적으로는:

  • 포커스 그래프
  • 상태에 의해 구동
  • 뷰 계층 구조를 통해 해결
  • 키보드 + 접근성과 조정

포커스를 입력을 위한 네비게이션으로 생각하세요.

🧩 1. 포커스는 뷰 기반이 아니라 값 기반

가장 중요한 규칙.

잘못된 사고 모델: “이 TextField가 포커스를 소유한다.”
올바른 사고 모델: “포커스는 필드를 가리키는 상태이다.”

그래서 이렇게 동작한다:

enum Field {
    case email
    case password
}

@FocusState private var focusedField: Field?

SwiftUI는 포커스를 다음과 같이 해결한다:

  1. 포커스된 값을 매칭한다
  2. 뷰 트리를 순회한다
  3. 첫 번째 호환 가능한 포커스 대상을 찾는다

🧱 2. SwiftUI가 포커스 트리를 구축하는 방법

렌더링 시점에 SwiftUI는:

  • 포커스 가능한 뷰를 스캔합니다
  • 포커스 트리를 구축합니다
  • 각각에 포커스 아이덴티티를 할당합니다
  • 활성 포커스 상태를 해결합니다

포커스 가능한 뷰에는 다음이 포함됩니다:

  • TextField
  • SecureField
  • TextEditor
  • 사용자 정의 포커스 가능한 컨트롤
  • 접근성 요소

뷰가 사라지면 → 해당 포커스 노드가 제거됩니다.

🔄 3. 왜 포커스가 “무작위로” 사라지는가

포커스가 사라지는 경우:

  • 포커스된 뷰가 계층 구조를 떠날 때
  • 뷰의 식별자가 변경될 때
  • 포커스 상태 값이 변경될 때
  • 네비게이션이 뷰를 제거할 때
  • 부모가 재생성될 때
  • ScrollView가 콘텐츠를 다시 레이아웃할 때
  • 키보드 해제가 트리거될 때

무작위가 아니라 — 식별자 + 생명주기 때문입니다.

🧠 4. 포커스 vs. 뷰 아이덴티티 (중요 연결)

This breaks focus:

TextField("Email", text: $email)
    .id(UUID()) // ❌

Why?

  • identity changes → 아이덴티티가 변경됨
  • focus node destroyed → 포커스 노드가 파괴됨
  • focus state points to nothing → 포커스 상태가 아무것도 가리키지 않음
  • keyboard dismisses → 키보드가 사라짐

Rule: 📌 Focus requires stable view identity. → 포커스는 안정적인 뷰 아이덴티티가 필요합니다.

⌨️ 5. 키보드는 부수 효과이며, 원천이 아니다

SwiftUI 포커스가 키보드를 제어합니다 — 반대는 아닙니다.

Flow:

Focus change

Responder change

Keyboard presentation

그래서 포커스를 업데이트하지 않고 키보드를 수동으로 해제하면 버그가 발생합니다.

Correct dismissal:

focusedField = nil

Avoid:

  • 강제로 resignFirstResponder 호출
  • UIKit 해킹
  • 포커스 업데이트 없이 제스처 기반 해제

📜 6. ScrollView + Keyboard 내부 동작

키보드가 나타날 때 SwiftUI는:

  • 안전 영역 inset을 조정합니다
  • 포커스된 필드가 보이도록 시도합니다
  • 자동으로 스크롤할 수 있습니다
  • 레이아웃이 복잡하면 실패할 수 있습니다

일반적인 문제:

  • 중첩된 ScrollViews
  • GeometryReader 사용
  • 커스텀 레이아웃
  • 동적인 높이 변화

베스트 프랙티스:

  • 폼을 단순하게 유지합니다
  • 폼에서 GeometryReader 사용을 피합니다
  • 적절할 때 .scrollDismissesKeyboard(.interactively)를 사용합니다

🧭 7. 프로그래밍 방식 포커스 (올바른 방법)

올바른 패턴:

focusedField = .email

지연된 포커스 (네비게이션 / 애니메이션) 경우:

Task {
    try await Task.sleep(for: .milliseconds(100))
    focusedField = .email
}

왜 지연이 필요한가?

  • 포커스 트리가 존재해야 함
  • 뷰가 렌더링되어야 함
  • 네비게이션이 완료되어야 함

🧪 8. Focus & Navigation

탐색할 때:

  • 포커스가 자동으로 전환되지 않습니다
  • 새 뷰는 포커스가 없는 상태로 시작합니다
  • 이전 포커스는 사라집니다

포커스 복원을 원한다면:

  • 포커스 상태를 외부에 저장합니다
  • onAppear에서 복원합니다
.onAppear {
    focusedField = savedFocus
}

♿ 9. Focus vs. Accessibility Focus

이들은 서로 다른 시스템입니다.

  • Input focus → keyboard → 입력 포커스 → 키보드
  • Accessibility focus → VoiceOver → 접근성 포커스 → VoiceOver

SwiftUI가 이를 조정하지만,

  • 두 포커스가 달라질 수 있습니다.
  • 접근성이 독립적으로 포커스를 이동시킬 수 있습니다.
  • 접근성 포커스가 항상 키보드를 트리거하는 것은 아닙니다.

두 포커스가 동일하다고 가정하지 마세요.

🧠 10. 사용자 정의 포커스 가능한 뷰

You can make custom controls focusable:

.focusable()
.focused($focusedField, equals: .custom)

다음에 사용하세요:

  • 사용자 정의 입력
  • 게임 같은 UI
  • tvOS / visionOS
  • 고급 키보드 내비게이션

⚠️ 11. 가장 큰 포커스 안티‑패턴

Avoid:

  • 인라인 UUID 아이디
  • 폼 행 재생성
  • UIKit 응답자 혼합
  • 포커스 업데이트 없이 키보드 해제
  • ViewModel 대신 뷰에 포커스 로직 넣기
  • 포커스 전환 중 무거운 레이아웃 변경

These cause ~90 % of focus bugs.

🧠 Focus System Cheat Sheet

  • ✔ Focus is state → 포커스는 상태이다
  • ✔ Identity must be stable → 아이덴티티는 안정적이어야 한다
  • ✔ Keyboard follows focus → 키보드는 포커스를 따라간다
  • ✔ Navigation destroys focus → 네비게이션은 포커스를 파괴한다
  • ✔ Delay focus until views exist → 뷰가 존재할 때까지 포커스를 지연한다
  • ✔ Accessibility focus is separate → 접근성 포커스는 별개이다
  • ✔ Forms require layout stability → 폼은 레이아웃 안정성을 요구한다

🚀 최종 생각

SwiftUI 포커스는 약하지 않습니다 — 정확합니다. 다음을 이해하면:

  • 포커스를 상태로
  • 포커스‑트리 해석
  • 키보드, 내비게이션 및 접근성과의 관계

일반적인 골칫거리 없이도 신뢰할 수 있는 프로덕션‑레디 폼을 만들 수 있습니다.

정체성 + 라이프사이클

  • 키보드는 부수 효과로

Forms, editors, and input‑heavy screens become predictable and rock solid.

Back to Blog

관련 글

더 보기 »

SwiftUI 제스처 시스템 내부

!Sebastien Latohttps://media2.dev.to/dynamic/image/width=50,height=50,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%...

SwiftUI 접근성 내부

접근성은 Parallel View Tree이다. SwiftUI는 두 개의 트리를 구축한다: - visual view tree - accessibility tree 이들은 관련이 있지만 동일하지 않다. 하나의…

SwiftUI에서 ScrollView와 좌표 공간

스크롤 기반 UI는 현대 앱 어디에나 존재합니다 - collapsing headers - parallax effects - sticky toolbars - section pinning - scroll‑driven animations - pull‑to‑r...