SwiftData @Query 실전 함정 두 가지 — enum #Predicate와 빈 목록의 modelContext

문제 상황 문서 스캔 앱을 만들면서 Inbox/Archive 목록을 구현하고 있었는데요. SwiftData @Query로 문서 상태별 필터링을 하려던 순간, 예상치 못한 컴파일 에러 두 개를 연달아 만났습니다. 함정 1: #Predicate에서 enum 비교가 안 된다 처음 시도한 코드 자연스럽게 이렇게 작성했거든요. @Model class Document { var status: DocumentStatus // enum 저장 프로퍼티 // ... } enum DocumentStatus: String, Codable { case inbox, archived } 그리고 @Query에서 필터링: @Query( filter: #Predicate<Document> { $0.status == .inbox }, sort: \Document.createdAt, order: .reverse ) private var documents: [Document] 깔끔해 보이죠? 그런데 빌드하면 컴파일 에러가 나요. ...

2026년 4월 26일 · 2 분 · Young

SwiftData 모델명 Tag가 빌드를 깨뜨리는 이유 (+ Xcode 16 .gitkeep 함정)

뭘 하려고 했냐면 문서 스캔 앱을 새로 시작했는데요. SwiftData로 Document와 Tag 모델을 만들고, 테스트를 먼저 작성하는 TDD로 진행하고 있었어요. 모델 코드는 심플했거든요: @Model class Tag { var name: String var color: String @Relationship(inverse: \Document.tags) var documents: [Document] } 여기까지는 문제없었어요. 빌드도 잘 되고. 첫 번째 삽질: Tag가 내 Tag가 아닌데요 테스트에서 FetchDescriptor를 쓰는 순간 터졌어요: let fetched = try context.fetch(FetchDescriptor<Tag>()) error: 'Tag' is ambiguous for type lookup in this context 처음엔 뭔 소린가 했는데요. SwiftUI에 이미 Tag라는 타입이 있더라고요. TabView나 Picker에서 선택 항목을 식별할 때 쓰는 그 Tag요. ...

2026년 4월 26일 · 3 분 · Young

SwiftUI에서 이미지 줌이 자연스럽지 않은 이유 — UIScrollView 래핑이 정답인 경우

문제 상황 문서 스캔 앱에서 스캔 이미지를 전체화면으로 보여주는 뷰어가 필요했는데요. 핀치 줌으로 확대하고, 확대 상태에서 드래그로 다른 부분을 볼 수 있어야 했어요. SwiftUI로 당연히 될 줄 알았습니다. 삽질 과정 1차: MagnifyGesture만 처음에는 단순하게 MagnifyGesture만 붙였거든요. Image(uiImage: uiImage) .scaleEffect(scale) .gesture( MagnifyGesture() .onChanged { value in scale = max(1.0, value.magnification) } .onEnded { _ in withAnimation { scale = 1.0 } } ) 손가락을 떼면 원래 크기로 돌아가니까 확대 상태를 유지할 수가 없었어요. 그래서 lastScale을 추가해서 확대를 유지하도록 했는데, 이번엔 확대된 이미지를 이동할 방법이 없더라고요. ...

2026년 4월 26일 · 2 분 · Young

SwiftUI @Observable에 클로저를 저장하면 EXC_BAD_ACCESS가 발생하는 이유

뭘 하려고 했냐면 iOS 음악 스트리밍 앱을 만들고 있었는데요. NavigationDestination으로 이동하는 상세 화면마다 ViewModel을 두고, API 호출 로직을 클로저로 주입하는 구조를 쓰고 있었어요. 대충 이런 식이었거든요: @Observable final class PlaylistDetailVM { var songs: [Song] = [] var isLoading = false // API 클라이언트의 메서드를 클로저로 주입 private let fetchSongs: (String) async throws -> [Song] private let addToPlaylist: (String, [String]) async throws -> Void init( fetchSongs: @escaping (String) async throws -> [Song], addToPlaylist: @escaping (String, [String]) async throws -> Void ) { self.fetchSongs = fetchSongs self.addToPlaylist = addToPlaylist } } NavigationDestination에서 @State로 VM을 생성하고요: ...

2026년 4월 21일 · 3 분 · Young

SwiftUI 이미지 깜빡임 버그 3개를 하나씩 벗겨낸 과정

문제 상황 음악 스트리밍 앱을 만들고 있었는데요. 앨범 아트워크가 계속 깜빡이는 거예요. 곡이 바뀔 때만이 아니라 같은 곡을 재생하고 있는데도 아트워크가 순간적으로 사라졌다 나타나고요. 특히 풀 플레이어의 blur 배경이 심했어요. “이미지 로딩이 느린가?” 싶어서 캐시를 넣었는데 여전히 깜빡이더라고요. 원인을 파고들었더니 버그가 3개 겹쳐 있었어요. 버그 1: NSCache 조회 순서가 반대 커스텀 CachedAsyncImage를 만들어서 NSCache에 이미지를 저장하고 있었는데요. 문제는 캐시 조회 순서였어요. ...

2026년 4월 21일 · 3 분 · Young