앱의 핵심 기능을 완성하고 나면 "이 정도면 됐다"는 생각이 드는 순간이 있습니다. 화면 전환도 잘 되고, 데이터도 잘 불러오고, 버튼도 잘 눌립니다. 하지만 iOS 시스템은 개발자가 명시적으로 지원해야만 동작하는 UX 기능들을 다수 제공합니다. 지원하지 않아도 앱이 동작하는 데는 지장이 없지만, 지원하는 순간 사용자 경험이 한 단계 달라집니다.
이 글은 iOS 개발자로서 알아두면 실질적으로 도움이 되는 시스템 UX 기능들을 정리합니다. 접근성부터 시스템 환경 대응, 인터랙션 품질, 상태 관리 UX까지 폭넓게 다룹니다.
접근성은 장애가 있는 사용자만을 위한 기능이 아닙니다. 고령자, 밝은 햇빛 아래에서 화면을 보는 사람, 일시적으로 손을 쓸 수 없는 상황의 사람 모두에게 해당합니다. Apple은 접근성을 플랫폼의 핵심 가치로 두고 있으며, App Store 심사에서도 접근성 지원 여부를 평가 기준으로 삼습니다.
사용자는 iOS 설정의 손쉬운 사용 > 디스플레이 및 텍스트 크기 > 더 큰 텍스트에서 원하는 글자 크기를 지정할 수 있습니다. 일반 접근성 크기 외에도 "더 큰 손쉬운 사용 크기"를 활성화하면 5단계가 추가됩니다(AX1~AX5).
그런데 앱이 글자 크기를 고정값으로 지정해두면 사용자가 시스템 설정을 아무리 바꿔도 앱 내부에서는 변화가 없습니다. 특히 시력이 약한 사용자에게 이는 앱 자체를 사용하기 어렵게 만드는 원인이 됩니다.
Dynamic Type을 지원하려면 시스템 폰트 스타일(UIFont.preferredFont(forTextStyle:))을 사용하고, adjustsFontForContentSizeCategory = true를 설정해야 합니다. 커스텀 폰트를 사용하는 경우에는 iOS 11에서 도입된 UIFontMetrics를 통해 스케일링을 직접 적용해야 합니다. 이렇게 설정하면 사용자가 글자 크기를 변경할 때 앱의 텍스트도 자동으로 따라서 조정됩니다.
단순히 글자가 커지는 것 외에도, 글자가 커졌을 때 레이아웃이 깨지지 않는지를 함께 확인해야 합니다. Xcode의 환경 오버라이드(Environment Overrides)에서 텍스트 크기를 AX5까지 올려보며 레이아웃이 유지되는지 검토하는 것을 권장합니다.
VoiceOver는 iOS의 스크린 리더입니다. 사용자가 화면의 요소를 탭하면 VoiceOver가 해당 요소를 음성으로 읽어줍니다. 개발자가 아무 설정도 하지 않아도 UIKit의 기본 컨트롤(버튼, 레이블, 텍스트필드)은 VoiceOver에서 기본적으로 읽히지만, 커스텀 뷰나 이미지, 아이콘으로만 구성된 버튼은 의미 있는 설명 없이 노출됩니다.
VoiceOver 지원의 핵심은 세 가지 속성입니다.
.button, .link, .adjustable, .header 등을 조합해서 VoiceOver가 요소를 어떻게 취급할지 알려줍니다.VoiceOver 지원 여부는 실제로 VoiceOver를 켜고 앱을 손으로 사용해봐야 확인할 수 있습니다. 설정 > 손쉬운 사용 > VoiceOver를 활성화하거나, 손쉬운 사용 단축키를 설정해두고 홈 버튼을 세 번 눌러 빠르게 전환하며 테스트할 수 있습니다.
사용자 중 일부는 화면의 빠른 움직임이나 시차(parallax) 효과에 어지러움이나 메스꺼움을 느낍니다. 이를 전정 기관 민감증(vestibular disorder)이라고 합니다. iOS는 이를 위해 설정 > 손쉬운 사용 > 동작 > 동작 감소 옵션을 제공합니다.
UIAccessibility.isReduceMotionEnabled를 통해 현재 설정값을 확인할 수 있습니다. SwiftUI에서는 @Environment(\.accessibilityReduceMotion) 환경값을 활용합니다.
이 설정이 켜져 있을 때 취해야 할 조치는 애니메이션의 성격에 따라 다릅니다.
Apple 자체 앱도 이 설정에 반응합니다. 예를 들어 앱 전환 시 확대/축소 애니메이션이 페이드로 변경됩니다. 이 설정을 존중하는 것은 특정 사용자에게는 앱을 쓸 수 있느냐 없느냐의 차이입니다.
설정 > 손쉬운 사용 > 디스플레이 및 텍스트 크기 > 대비 증가를 켜면 시스템 UI의 대비가 높아집니다. 개발자는 UIAccessibility.isDarkerSystemColorsEnabled를 확인하여 앱의 색상을 더 대비 높게 조정할 수 있습니다.
색맹 대응은 색상에만 의존한 정보 전달을 피하는 것에서 시작합니다. 예를 들어 성공/실패 상태를 초록색/빨간색으로만 구분하는 대신, 아이콘이나 텍스트를 함께 사용합니다. Apple Human Interface Guidelines는 최소 4.5:1의 대비율(텍스트와 배경)을 권장합니다.
iOS 13에서 Dark Mode가 도입된 이후, 많은 사용자가 야간이나 저조도 환경에서 다크 모드를 사용합니다. 앱이 다크 모드를 지원하지 않으면 밝은 배경이 그대로 유지되어 눈에 부담을 줍니다.
다크 모드 지원의 핵심은 하드코딩된 색상을 쓰지 않는 것입니다. UIColor(red:green:blue:alpha:)처럼 고정된 RGB 값 대신, UIColor.label, UIColor.systemBackground, UIColor.secondaryLabel 같은 시맨틱 컬러를 사용하면 시스템이 라이트/다크 모드에 맞춰 자동으로 적절한 색상을 적용합니다.
커스텀 컬러가 필요한 경우에는 Assets.xcassets에서 Appearance별로 다른 색상 값을 지정할 수 있습니다. 이렇게 하면 코드 수정 없이 모드 전환이 처리됩니다.
다크 모드 테스트는 Xcode의 Environment Overrides를 통해 런타임에서 즉시 전환하며 확인하거나, 시뮬레이터의 모드를 직접 바꿔보는 방법으로 진행합니다.
사용자가 텍스트 필드를 탭했을 때 소프트 키보드가 올라오면서 입력 필드를 가리는 경우가 있습니다. 특히 화면 하단에 위치한 텍스트 필드는 키보드에 완전히 가려지기도 합니다.
SwiftUI는 iOS 14부터 대부분의 경우 자동으로 키보드 회피를 처리합니다. ScrollView, List, Form 등의 컨테이너 안에서 텍스트 필드가 키보드에 가려지면 자동으로 위로 스크롤됩니다.
UIKit에서는 keyboardLayoutGuide를 활용하는 것이 방법입니다. iOS 15에서 도입되었으며, 키보드의 위치를 레이아웃 가이드로 제공하므로 제약 조건을 통해 뷰를 키보드 위로 자동 정렬할 수 있습니다.
좋지 않은 사용자 경험의 대표적인 패턴은 키보드가 올라올 때 전체 뷰가 위로 밀려서 상단 내비게이션 바가 화면 밖으로 사라지는 것입니다. 입력 필드만 보이도록 스크롤되는 것과 전체 뷰가 밀리는 것은 사용자 입장에서 전혀 다른 경험입니다.
Haptic Feedback은 iPhone의 Taptic Engine을 통해 미세한 진동으로 촉각 피드백을 제공하는 기능입니다. 버튼을 누르거나 선택이 변경되거나 오류가 발생했을 때 짧은 진동으로 반응하면, 사용자는 화면을 보지 않아도 자신의 행동이 인식되었다는 것을 느낄 수 있습니다.
UIKit에서는 세 가지 종류의 피드백 생성기를 제공합니다.
UIImpactFeedbackGenerator: 버튼 탭, 항목 선택 같은 물리적 충격감 표현에 사용합니다. .light, .medium, .heavy, .soft, .rigid 스타일을 제공합니다.UINotificationFeedbackGenerator: 작업 성공(.success), 실패(.error), 경고(.warning) 같은 시스템 알림에 대응합니다.UISelectionFeedbackGenerator: 슬라이더나 피커처럼 값이 연속적으로 변할 때 선택 변경을 표현합니다.Haptic Feedback을 사용할 때 주의할 점은 과도하게 사용하지 않는 것입니다. 모든 탭에 진동이 발생하면 오히려 사용자를 피로하게 만듭니다. 또한 저전력 모드에서는 Taptic Engine이 비활성화될 수 있으므로, UIDevice.current.batteryState를 확인하거나 피드백 없이도 기능이 완전히 동작하도록 설계해야 합니다.
시각 장애인에게도 햅틱은 유효한 피드백 채널입니다. 성공/실패 알림에 .success와 .error 패턴을 사용하면 화면을 보지 않아도 결과를 인지할 수 있습니다.
사용자가 리스트 아이템을 길게 누르거나(Long Press) 스와이프했을 때 추가적인 액션을 제공하면, 더 적은 화면 전환으로 기능에 접근할 수 있습니다.
컨텍스트 메뉴(UIContextMenuInteraction)는 iOS 13에서 도입되었으며, 길게 누르면 요소의 미리보기와 함께 메뉴가 표시됩니다. 공유, 저장, 삭제, 즐겨찾기 같은 빈번하게 사용하는 보조 기능에 적합합니다. 메뉴 항목은 UIAction과 UIMenu의 계층 구조로 구성하며, 파괴적인 액션(삭제 등)에는 .destructive 속성을 붙여 빨간 색상으로 시각적 경고를 줍니다.
스와이프 액션(UISwipeActionsConfiguration)은 테이블 뷰에서 셀을 좌우로 스와이프했을 때 나타나는 버튼입니다. 왼쪽에는 보조 액션(즐겨찾기, 읽음 표시), 오른쪽에는 삭제 같은 주요 액션을 배치하는 것이 관례입니다. 파괴적인 액션의 경우 performsFirstActionWithFullSwipe = false로 설정하여 실수로 풀 스와이프했을 때 자동 실행되지 않도록 막는 것이 중요합니다.
두 기능 모두 VoiceOver에서 접근 가능해야 합니다. 컨텍스트 메뉴는 VoiceOver 활성 시 길게 누르기 대신 두 손가락 더블 탭으로 트리거됩니다.
앱이 데이터를 불러오는 동안 빈 화면이나 스피너만 보이는 것은 사용자에게 불안감을 줍니다. 스켈레톤 UI는 실제 콘텐츠가 로드되기 전에 콘텐츠의 형태(레이아웃)를 회색 블록으로 미리 보여주는 패턴입니다. Twitter, LinkedIn, Facebook 앱 등에서 피드 로딩 시 사용하는 방식입니다.
스켈레톤 UI가 일반 스피너보다 체감 로딩 시간이 짧게 느껴지는 이유는, 사용자가 "무언가가 로드되고 있다"는 것을 인식하면서 동시에 화면의 구조를 파악할 수 있기 때문입니다. Nielsen Norman Group의 연구에 따르면, 스켈레톤 스크린은 진행 표시기보다 기다리는 시간을 덜 지루하게 느끼게 합니다.
스켈레톤 UI 적용 시 권장 사항은 다음과 같습니다.
화면에 표시할 데이터가 없거나(Empty State), 네트워크 오류가 발생했을 때(Error State) 어떤 화면을 보여줄지는 UX의 중요한 부분입니다.
Empty State는 데이터가 없는 이유와 다음 행동을 안내합니다. "아직 등록된 항목이 없습니다"와 함께 "항목 추가하기" 버튼을 제공하는 방식입니다. 빈 화면을 그대로 두면 사용자는 버그라고 생각하거나 무엇을 해야 할지 몰라 앱을 이탈합니다.
Error State는 무엇이 잘못됐는지와 사용자가 취할 수 있는 조치를 알려줍니다. "인터넷 연결을 확인해주세요"와 "다시 시도" 버튼이 대표적입니다. 오류 메시지에 기술적인 오류 코드나 서버 응답 메시지를 그대로 노출하는 것은 좋지 않습니다. 사용자가 이해할 수 있는 언어로 풀어서 전달해야 합니다.
세 가지 상태(로딩, 성공, 오류/빈 상태)를 일관된 방식으로 처리하면 앱 전체가 예측 가능하고 안정적으로 느껴집니다.
| 기능 | 카테고리 | 사용자 설정 연동 | iOS 최소 버전 | 핵심 API |
|---|---|---|---|---|
| Dynamic Type | 접근성 | 설정 > 손쉬운 사용 | iOS 7 | UIFont.preferredFont, UIFontMetrics |
| VoiceOver | 접근성 | 설정 > 손쉬운 사용 | iOS 3 | UIAccessibility, accessibilityLabel |
| Reduce Motion | 접근성 | 설정 > 손쉬운 사용 | iOS 8 | UIAccessibility.isReduceMotionEnabled |
| Dark Mode | 시스템 환경 | 설정 > 디스플레이 | iOS 13 | 시맨틱 컬러, UIUserInterfaceStyle |
| 키보드 회피 | 시스템 환경 | 자동 | iOS 15 (keyboardLayoutGuide) | keyboardLayoutGuide |
| Haptic Feedback | 인터랙션 | 자동 | iOS 10 | UIImpactFeedbackGenerator |
| 컨텍스트 메뉴 | 인터랙션 | 없음 | iOS 13 | UIContextMenuInteraction |
| 스와이프 액션 | 인터랙션 | 없음 | iOS 11 | UISwipeActionsConfiguration |
| 스켈레톤 UI | 상태 관리 | 없음 | — | 커스텀 구현 |
이 기능들은 대부분 "지원하지 않아도 앱이 동작하는" 것들입니다. 하지만 지원 여부가 실제 사용자 경험에 미치는 영향은 생각보다 큽니다. 특히 접근성 기능은 특정 사용자에게 앱을 쓸 수 있느냐 없느냐의 차이가 되기도 합니다. 기능 개발과 함께 이 목록을 체크리스트처럼 활용해보면 도움이 될 것입니다.
글에 대한 피드백이나 더 좋은 방법이 있다면 댓글로 공유해주세요.