문제 상황
문서 스캔 앱을 만들면서 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]
깔끔해 보이죠? 그런데 빌드하면 컴파일 에러가 나요.
왜 안 되는가
#Predicate는 Swift 매크로인데, 내부적으로 predicate expression을 Core Data의 NSPredicate로 변환해야 하거든요. 이 과정에서 enum 타입은 직접 비교 대상으로 지원되지 않아요. #Predicate가 이해할 수 있는 타입은 String, Int, Bool 같은 기본 타입뿐이에요.
SwiftData가 내부적으로 enum을 rawValue로 저장하긴 하지만, #Predicate 매크로 레벨에서는 그 변환을 자동으로 해주지 않는 거죠.
해결: rawValue 저장 프로퍼티 패턴
@Model
class Document {
var statusRawValue: String // #Predicate 호환
var status: DocumentStatus { // 편의용 computed
get { DocumentStatus(rawValue: statusRawValue) ?? .inbox }
set { statusRawValue = newValue.rawValue }
}
init(title: String, imageData: Data) {
self.statusRawValue = DocumentStatus.inbox.rawValue
// ...
}
}
이제 #Predicate에서 String 비교로 필터링할 수 있어요.
@Query(
filter: #Predicate<Document> { $0.statusRawValue == "inbox" },
sort: \Document.createdAt,
order: .reverse
) private var documents: [Document]
앱 코드에서는 여전히 document.status = .archived 같은 enum 인터페이스를 쓸 수 있고, 저장과 쿼리만 rawValue String으로 처리하는 구조예요.
함정 2: 빈 @Query에서 modelContext가 nil
처음 시도한 코드
InboxView에서 스캔한 문서를 저장해야 하니까, modelContext가 필요했는데요. 처음엔 이렇게 접근했어요.
struct InboxView: View {
@Query(...) private var documents: [Document]
// documents에서 context 가져오기
func saveDocument() {
if let context = documents.first?.modelContext {
// 문서 저장...
}
}
}
Inbox가 비어있을 때 — 그러니까 앱을 처음 설치했을 때 — documents가 빈 배열이니까 documents.first가 nil이고, modelContext에 접근할 수 없어요.
처음 스캔하는 사용자가 문서를 저장할 수 없는 상황이 되는 거죠.
해결: @Environment
struct InboxView: View {
@Query(...) private var documents: [Document]
@Environment(\.modelContext) private var modelContext
func saveDocument() {
// modelContext는 항상 사용 가능
let document = Document(title: "New", imageData: data)
modelContext.insert(document)
}
}
@Environment(\.modelContext)는 SwiftData의 modelContainer가 뷰 계층에 주입되어 있으면 항상 접근 가능해요. 쿼리 결과와 무관하게요.
배운 점
#Predicate는 기본 타입만 비교 가능하다. enum을 필터링하려면 rawValue 저장 프로퍼티를 별도로 두고, computed property로 enum 인터페이스를 유지하는 패턴이 필요해요.@Query결과에 의존해서 modelContext를 얻지 마세요. 빈 목록에서는 접근 불가.@Environment(\.modelContext)가 항상 정답이에요.
두 가지 다 SwiftData 문서에 명확하게 경고되어 있지 않아서, 직접 빌드 에러를 만나봐야 알게 되는 함정이더라고요.