반응형
SwiftUI Study – FocusState를 안전하게 설계하는 방법
1. 왜 중요한가 (문제 배경)
SwiftUI의 @FocusState는 키보드 포커스를 선언적으로 제어할 수 있게 해주는 강력한 도구이지만,
설계를 잘못하면 다음과 같은 문제가 쉽게 발생합니다.
- 포커스가 특정 TextField에 안 잡힘 / 튐 / 사라짐
- 화면 이동 후 포커스가 엉뚱한 필드에 들어감
- 로컬 FocusState를 여러 곳에서 선언해 포커스 흐름이 꼬임
- Bool 기반 FocusState를 여러 필드에 공유하여 예측 불가능한 동작 발생
이 문제들은 대부분 “포커스의 단일 소유자”를 두지 않았기 때문에 발생합니다.
2. 잘못된 패턴 예시
❌ 예시 1: Bool 하나로 여러 TextField 포커스를 제어
struct WrongFocusView: View {
@State private var name = ""
@State private var email = ""
@FocusState private var isFocused: Bool // ❌ 어떤 필드를 위한 것인지 구분 불가
var body: some View {
VStack {
TextField("이름", text: $name)
.focused($isFocused)
TextField("이메일", text: $email)
.focused($isFocused)
Button("포커스 주기") {
isFocused = true // ❌ 어떤 필드가 포커스될지 예측 어려움
}
}
}
}
문제점
- 특정 필드에 포커스를 지정할 방법이 없음
- UI 상황에 따라 다른 필드가 선택되는 예측 불가한 현상 발생
❌ 예시 2: 컴포넌트 내부에서 로컬 FocusState 선언
struct NameField: View {
@Binding var text: String
@FocusState private var focused: Bool // ❌ 로컬에 종속된 포커스
var body: some View {
TextField("이름", text: $text)
.focused($focused)
.onAppear {
focused = true // 화면 전체 흐름과 무관하게 강제 포커스
}
}
}
문제점
- 특정 화면 로직과 관계없이 컴포넌트가 스스로 포커스를 강제로 잡음
- 상위 화면에서 포커스 흐름을 제어하기 어려움
3. 올바른 패턴 예시
✅ 예시 1: enum 기반 FocusState로 명확하게 제어
struct CorrectFocusView: View {
enum Field: Hashable {
case name
case email
}
@State private var name = ""
@State private var email = ""
@FocusState private var focusedField: Field?
var body: some View {
VStack {
TextField("이름", text: $name)
.focused($focusedField, equals: .name)
TextField("이메일", text: $email)
.focused($focusedField, equals: .email)
Button("이메일로 이동") {
focusedField = .email
}
}
.onAppear { focusedField = .name }
}
}
장점
- 포커스 대상이 명확하고 예측 가능
- 화면 이동, 시트, Navigation과 조합해도 흐름이 깨지지 않음
✅ 예시 2: 화면 단위에서 FocusState를 관리하고, 자식 뷰에는 Binding으로 전달
struct ParentView: View {
enum Field: Hashable { case name, email }
@State private var name = ""
@State private var email = ""
@FocusState private var focusedField: Field?
var body: some View {
VStack {
NameField(
text: $name,
isFocused: Binding(
get: { focusedField == .name },
set: { $0 ? focusedField = .name : focusedField = nil }
)
)
EmailField(
text: $email,
isFocused: Binding(
get: { focusedField == .email },
set: { $0 ? focusedField = .email : focusedField = nil }
)
)
}
.onAppear { focusedField = .name }
}
}
struct NameField: View {
@Binding var text: String
@Binding var isFocused: Bool
var body: some View {
TextField("이름", text: $text)
.focused($isFocused)
}
}
장점
- 포커스 주체(부모)가 명확해져 흐름이 안정됨
- 자식은 단순히 포커스를 표현만 할 뿐 제어하지 않음
4. 실전 적용 팁
✔ 팁 1 – FocusState는 화면 단위 하나만 두는 것이 가장 안전
포커스의 소유권을 단일화하면 포커스 튐/소실 문제 대부분이 해결됩니다.
✔ 팁 2 – 어떤 필드에 포커스를 줄지 명확하게 표현할 수 있는 enum 기반 설계를 사용
Bool 기반 FocusState는 예측할 수 없는 동작을 만들기 쉽습니다.
✔ 팁 3 – 자식 컴포넌트에서 onAppear로 강제 포커스를 잡지 말 것
포커스 흐름은 부모에서 통제해야 합니다.
✔ 팁 4 – NavigationStack, sheet, fullScreenCover와 함께 사용할 때 더 빛남
다양한 화면 전환 상황에서도 포커스가 일관적으로 유지됩니다.
5. 정리
- FocusState는 “누가 포커스를 제어하는가”를 명확히 해야 안정적으로 동작합니다.
- Bool 기반 포커스는 절대 여러 필드에 공유하지 말 것.
- enum 기반 FocusState가 가장 예측 가능하고 확장 가능하며 실무 친화적입니다.
- 자식 뷰는 포커스를 표현만 하고, 제어는 부모가 담당하도록 분리하는 것이 가장 안전합니다.
반응형
'Dev Study > SwiftUI' 카테고리의 다른 글
| SwiftUI Study – View 중첩이 깊어지지 않도록 컴포넌트를 분리하는 실전 패턴 (0) | 2025.12.09 |
|---|---|
| SwiftUI Study – offset / position으로 레이아웃을 잡지 말아야 하는 이유 (0) | 2025.12.09 |
| SwiftUI Study – GeometryReader에 의존하지 않고 레이아웃을 안정적으로 구성하는 방법 (0) | 2025.12.09 |
| SwiftUI Study – 상태 변경을 한 곳으로 모으는 패턴 (0) | 2025.12.09 |
| SwiftUI Study – 도메인 상태와 UI 상태를 분리하는 방법 (0) | 2025.12.09 |
| SwiftUI Study – @State / @Binding / @ObservedObject / @StateObject / @EnvironmentObject 정확한 역할 이해하기 (0) | 2025.12.09 |
| SwiftUI에서 많이 하는 실수 - Button 안에서 상태 변경 로직이 여러 계층에 중첩되어 UI가 비정상 업데이트되는 문제 (0) | 2025.12.08 |
| SwiftUI에서 많이 하는 실수 - 하나의 상태를 여러 곳에서 동시에 수정해 충돌이 나는 실수 (1) | 2025.12.05 |

