Swift

어서와 SwiftUI @FetchRequest는 처음이지?

Thor_yeom 2023. 3. 23. 16:21

SwiftUI에서 Core Data를 사용할때 같이 사용하면 좋은 @FetchRequest에 대해 설명해보겠습니다.

 

그럼 Core Data가 무엇인가요?

 

데이터를 디바이스에 저장하는 하는 방법으로는 @AppStorage, UserDefault가 있지만 이러한 것들은 간단한 정보(즉, 원시타입) 를 저장하기에 적합했다면... Core Data는 복잡하고 다른 객체간의 관계를 관리하며 user data를 저장하기에 적합합니다.

 

@AppStorage에 대해 궁금하다면 

https://yeomir.tistory.com/11

 

어서와(SwiftUi) AppStorage는 처음이지?

@AppStorage 란 무엇인가? - Swift에서는 앱을 빌드하고 실행할때 메모리에는 String과 Int형식으로 저장된다. 상태변화가 될때 사용하는 @State와 @StateObject랑 차이점은 뭔가요? A. 앱을 종료하면 메모리

yeomir.tistory.com

https://yeomir.tistory.com/20

 

SwiftUI @AppStorage 심화편

@AppStorage 심화편으로 돌아왔습니다. 이번 차트에서는 @AppStorage를 사용하여 프로젝트를 만들어 볼테데요. 기초편에서도 말했지만 간단히 짚고가지면 @AppStorage는 Int, String과 같은 원시타입만 저장

yeomir.tistory.com

 

 

Core Data에 대해 궁금하다면

https://zeddios.tistory.com/987

 

Core Data (1)

안녕하세요 :) Zedd입니다. Core Data를 사용할 일이 생겼는데...제가 옜날에 해봤단 말이죠..!?!?!? 근데 다시 하려니까 생각이 하나도 안나는거에요 그때는 문서 볼 생각도 안했었는데..ㅎㅎㅎㅎ 이

zeddios.tistory.com

https://velog.io/@nala/iOS-SwiftUI%EC%97%90%EC%84%9C-CoreData-%EC%8D%A8%EB%B3%B4%EA%B8%B0

 

[iOS] SwiftUI에서 CoreData 써보기

이 글을 쓰는 나와 나를 지켜보는 n년차 개발자

velog.io

 

자 그럼 @FetchRequest에 대해 알아보겠습니다.

일단 @FetchRequestSwifUI가 나올때 같이 나왔습니다. iOS13.0부터 Core Data를 사용할때 많이 활용되는 프로퍼티 래퍼입니다. 

 

그럼 @FetchRequest은 언제 사용하나요?

Cora Data에서 데이터를 불러와 List 형식으로 화면에 뿌려줄때 정렬 기준을 설정 할 수 있습니다.

 

즉, 정렬이 필요할 때 사용 됩니다 (Ex. 시간순, 오름차순, 내림차순 등등...)

 

Core Data를 만드는 법은 두가지가 있습니다만, 저는 만들어진 프로젝트에 Core Data를 추가하는 방식을 채택해서 프로젝트를 구현해 보겠습니다.

 

 

1. 파일을 만들때 com + n을 눌러 파일을 생성해줍니다. 검색창에 data라고 치면 Data Model 파일을 만들 수 있습니다.

 

 

2. 모델명을 Entity로 만들고, 하단에 Add Entity를 눌러 Atrributes에 두가지 속성을 추가해줍니다.

 

 

3. 파일 추가 ( CoreDataManager)   상단에 import CoreData 까먹지 말기 
(엥? 왜 자동완성이 안되지? 하지 ... or 왜 오류가 발생하지.. 하지 않도록! )

import CoreData

class CoreDataManager: ObservableObject {
    
    // 싱글톤 생성
    static let shared = CoreDataManager()
    
    //NSPersistentContainer : Core Data Stack을 나타내는 필요한 모든 것
    let container = NSPersistentContainer(name: "Entity")
    
    init() {
        // loadPersistentStores: 데이터를 저장하고 로드하는 주요 기능
        container.loadPersistentStores { description, error in
            if let error {
                print("코어 데이터에서 에러 발생 \(error.localizedDescription)")
            }
        }
    }
}

 

SwiftUI는 Swift와 다르게  AppDelegate가 없는데 어디에 적용하면 좋을까? 

이런 생각을 하셨다면 당신은 개발자로 성장하기 딱 좋은 인재상... 

 

 

4. 바로 _APP단에서 설정해주면 됩니다. 참 쉽죠? ( SwiftUI 알럽유...)

import SwiftUI

@main
struct SwifUI_CoreDataFetchRequest_ExamApp: App {
    // StateObject를 활용하여 생성
    @StateObject private var coreDataManager = CoreDataManager.shared
    
    // 다른 방법 싱글톤 활용
    //let coreDataManager2 = CoreDataManager.shared
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, coreDataManager.container.viewContext)
                //.environment(\.managedObjectContext, coreDataManager2.container.viewContext)
        }
    }
}

 

 

5. ContentView에서 추가되는 것들을 Entity()에 저장하기 위해서 @Environment(~ ) @FetchRequest( ~ ) 를 추가해보겠습니다.

struct ContentView: View {
    
    // viewContext 불러오기 text추가하여 할때 필요 
    @Environment(\.managedObjectContext) private var viewContext
    @FetchRequest(entity: Entity.entity(), sortDescriptors:[] ) var testExample: FetchedResults<Entity>
    @State private var text: String = ""
    
    var body: some View {
        VStack {
       
            TextField("입력해주세요", text: $text)
            Button {
                // 데이터 추가하기 
            } label: {
                Text("추가하기")
            }
        }
    }
 }

 

@FetchRequest에 대해서 설명해보자면 공식문서에 보면 

 

코어 데이터 영구 저장소에서 Entity를 검색하는 프로퍼티 래퍼 유형임

 

fetch request를 하는 여러 방법이 있지만, 이중에서도 세 번째 방법은 init(entity:sortDescriptors:predicate:animation:) 을 사용해보겠습니다.

공식문서 출처

 

init(entity:sortDescriptors:predicate:animation:)에 대해 뜯어보자면 

공식 문서 출처

    @FetchRequest(entity: Entity.entity(), sortDescriptors: [] ) var testExample: FetchedResults<Entity>

이제 이 코드에 적용해서 뜯어보자

 

entitiy: 내가 만든 코어데이터 모델의 이름을 적고 .entity()로 가져온다.

sortDescriptiors: 아직 사용하지 않는 상태이니 [ ] 빈 배열로 만들어준다.

var 변수명 

 

해치웠나...? 

엥?? 뜬금 FetchResults<T> ... 이건또 뭐지...

 

모를땐 공식문서를 보자!

공식문서 출처

해석해보자면 Core Data store에서 검색된 결과 모음이다

 

쉽게 설명해서  Core Data 모델의 Entities 이름을 적어주면 된다.

Entity 파일의 Entites

 

자 그럼 이 구문에 대해선 이해가 되었으리라 생각한다. 

    @FetchRequest(entity: Entity.entity(), sortDescriptors: [] ) var testExample: FetchedResults<Entity>

 

휴... 해치웠나...?

천만의 말씀 만만의 콩떡... 이거 알면 최소 90년생...

 

5번의 파트가 꽤 길었지만 이제 마무리만 지으면 완성

 

6. 함수를 만들어 추가했을때 Entity에 저장되도록 설정해준다.

struct ContentView: View {
    
    @Environment(\.managedObjectContext) private var viewContext
    
    @FetchRequest(entity: Entity.entity(), sortDescriptors: [] ) var testExample: FetchedResults<Entity>
    
    @State private var text: String = ""
    
    
    var body: some View {
        VStack {
       
            TextField("입력해주세요", text: $text)
            Button {
              
            } label: {
                Text("추가하기")
            }
        }
    }
    
    func addData() {
        let data = Entity(context: viewContext)
        data.title = text
        try? viewContext.save()
    }
}

 

func addData() 함수에 대해 부가적인 설명을 하자면

    func addData() {
    	// Entity 매개 변수로 viewCotext를 설정해준다.
        let data = Entity(context: viewContext)
        // Entity(Core data 모델에서 만들었던 속성들을 사용할 수 있음 
        data.title = text
        // 작업이 마무리 되었으면 save()
        try? viewContext.save()
    }

 

의문이 있는데... 왜 Entity(context: )이 문장이 자동완성되지 않을까요...? ㅠㅠ 

 

추가, 삭제, 업데이트 를 할때 가장 중요한것은 마지막에 꼭 viewContext.save()를 해줘야 된다는 것이다. 

 

7. 만들어진 함수를 button action에 적용해보고, 우리가 만든 @FetchRequest를 적용해서 버튼을 눌렀을 때 하나씩 저장되도록 만들어보자

 

import SwiftUI

struct ContentView: View {
    
    @Environment(\.managedObjectContext) private var viewContext
    
    @FetchRequest(entity: Entity.entity(), sortDescriptors: [] ) var testExample: FetchedResults<Entity>
    
    @State private var text: String = ""
    
    
    var body: some View {
        VStack {
            // CoreData는 optional로 기본값이 저장되기 때문에 바인딩을 통해 나타내줘야한다.
            List(testExample) { text in
                Text(text.title ?? "")
            }
            // List 스타일 적용
            .listStyle(.plain)
       
            TextField("입력해주세요", text: $text)
            	// 직관적인 UI를 위해 padding 추가
                .padding()
                
            Button {
              addData()
              // 입력을 받고 다시 TextField 빈값으로 만들어주기
              text = ""
            } label: {
                Text("추가하기")
            }
        }
    }
    
    func addData() {
        let data = Entity(context: viewContext)
         // TextField에서 작성한것 CoreData.title에 넣기
        data.title = text
        try? viewContext.save()
    }
}

 

자 이렇게 @FetchRequest에 대해서 알아봤는데요

 

그렇다면  @FetchRequest 에서 정렬 기능은 어떻게 해나하나요?

공식 문서 출처

sortDescriptors를 보면 정렬의 순서를 정의 할수 있다고 나옵니다. 

공식문서 출처

sortDescriptors를 자세히 뜯어보면  [NSSortDescriptors] 라고 되어있습니다. 또 찾아보면...

공식문서에서 계속 타고 들어가면 이렇게 나옵니다. 

아항... 공식문서를 이렇게 쓰는거구나

 

해석: 지정된 키 경로 및 순서로 정렬 설명자를 만든다고 합니다. 그렇다면 코드로 나타내면 어떻게 표현할까요? 

import SwiftUI

struct ContentView: View {
    
    @Environment(\.managedObjectContext) private var viewContext
    // [] 빈배열이었던 sortDescriptors안에  NSSortDescriptor 메소드를 사용해서 Core Data에서 만들어준 date를 기준으로 정렬하였습니다.
    // 여기서 ascending 이 true면 오름차순 , false 면 내림차순
    @FetchRequest(entity: Entity.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Entity.date, ascending: true)] ) var testExample: FetchedResults<Entity>
    
    @State private var text: String = ""
    
    
    var body: some View {
        VStack {
            
            List(testExample) { text in
                Text(text.title ?? "")
            }
            .listStyle(.plain)
       
            TextField("입력해주세요", text: $text)
                .padding()
                
            Button {
              addData()
              text = ""
            } label: {
                Text("추가하기")
            }
        }
    }
    
    func addData() {
        let data = Entity(context: viewContext)
        data.title = text
        try? viewContext.save()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

 

 

 

 

드디어 해치웠습니다!!

'Swift' 카테고리의 다른 글

iOS Custom Calendar 구현하기2  (0) 2023.04.17
iOS Custom Calender 구현하기  (2) 2023.04.17
SwiftUI @AppStorage 심화편  (0) 2023.03.22
SwiftUI OpenAPI 적용시키기  (0) 2023.02.14
우당탕탕 앱 출시 리젝...  (0) 2023.02.11