책 검색 앱 만들기 (6)

⏰ 지금까지 진행 한 과정 다시보기

2024.04.30 - [◽️ Programming/T I L] - 책 검색 앱 만들기 (1)

2024.05.01 - [◽️ Programming/T I L] - 책 검색 앱 만들기 (2)

2024.05.03 - [◽️ Programming/T I L] - 책 검색 앱 만들기 (3)

2024.05.07 - [◽️ Programming/T I L] - 책 검색 앱 만들기 (4)

2024.05.08 - [◽️ Programming/T I L] - 책 검색 앱 만들기 (5)

코어데이터 추가 기능 구현

코어데이터 저장 순서 date 설정 하여 정렬하기

코어데이터가 저장되어 마이페이지에 저장된 코어데이터가 보일때 순서가 정렬되지 않고 마구잡이로 들어오는 문제가 있었다.

 

그 문제를 해결하기 위해 저장한 시간대를 같이 코어데이터에 저장해 이 시간대를 기준으로 정렬을 하면 될 것 같아 그렇게 한번 구현해 보았다 🙂

extension BookCoreData {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<BookCoreData> {
        return NSFetchRequest<BookCoreData>(entityName: "BookCoreData")
    }

    @NSManaged public var title: String?
    @NSManaged public var authors: String?
    @NSManaged public var price: Int64
    @NSManaged public var date: Date

}

extension BookCoreData : Identifiable {

}

코어데이터 entity에 사진과 코드와 같이 새로운 데이터를 넣을 수 있도록 추가한다

let newProduct = NSManagedObject(entity: entity, insertInto: context)
let authorsString = booklist.authors.joined(separator: ", ")
newProduct.setValue(authorsString, forKey: "authors")
newProduct.setValue(booklist.title, forKey: "title")
newProduct.setValue(booklist.price, forKey: "price")
newProduct.setValue(Date(), forKey: "date")

그리고 코어데이터를 저장하는 메서드에 해당 내용을 추가하면 저장되는 시간도 같이 코어데이터에 저장이 완료 된다!

func getBookListFromCoreData() -> [BookCoreData] {
    var bookList: [BookCoreData] = []
    
    if let context = context {
        let request = NSFetchRequest<BookCoreData>(entityName: self.coreDataName)
        let dateOrder = NSSortDescriptor(key: "date", ascending: true)
        request.sortDescriptors = [dateOrder]
        
        do {
            bookList = try context.fetch(request)
        } catch {
            print("데이터를 가져오는데 실패했습니다:", error)
        }
    }
    return bookList
}

그리고 코어데이터를 가져와 마이페이지에 데이터를 GET 할 때 date를 기준으로 정렬하는 코드를 넣어주면 책을 저장하는 순서대로 마이페이지에서 저장된 책을 볼 수 있다.

 

 

코어 데이터 내 이미 있는 책 정보 확인하여 중복 저장 방지하기

코어 데이터 내 이미 저장되어 있는 책이라면 동일한 책은 저장되지 않도록 하는 기능을 구현했다. 해당 내용은 코드로 한번 살펴보자!

guard let context = context else {
          print("context를 가져올 수 없습니다.")
          completion(false)
          return
      }
      
      let request = NSFetchRequest<NSFetchRequestResult>(entityName: coreDataName)
      request.predicate = NSPredicate(format: "title == %@", booklist.title)
      
      do {
          let count = try context.count(for: request)
          guard count == 0 else {
              print("이미 저장된 책입니다.")
              completion(false)
              return
          }
      } catch {
          print("중복 체크 실패:", error)
          completion(false)
          return
      }

코어 데이터를 저장하는 메서드 내 context 안에 동일한 title을 가지고 있다면 이미 저장된 책이라는 안내와 컨텐츠 저장이 되지 않도록 구현했다 🙂

func saveBookToCoreData(completion: @escaping (Bool) -> Void) {
    guard let bookData = bookData else {
        print("bookData가 없습니다.")
        return
    }

    CoreDataManager.shared.saveBookListData(bookData) { isSaved in
        if isSaved {
            print("코어데이터에 저장되었습니다.")
            completion(true)
        } else {
            print("이미 저장된 책입니다.")
            completion(false)
        }
    }
}

DetailViewController에 해당 내용을 선언해주면 구현이 완료된다 🙂

 

 

무한 스크롤 구현

NetworkingManager 세팅하기

이제 검색한 책을 스크롤 할 때 마다 끝에 도착하면 다음 페이지의 데이터를 가져와 끊임없이 스크롤이 될 수 있도록 구현할 예정이다 🙂

먼저 코어데이터 매니저에 page 값을 파라미터로 넣을 수 있도록 세팅한다.

class NetworkingManager {
    
    static let shared = NetworkingManager()
    private init() {}
    
    var isEnd = false
    let pageSize = 40
    var page = 1
    
    func fetchBookData(withQuery query: String, page: Int, completion: @escaping (Result<bookdata, error="">) -> Void) {
        let url = "<https://dapi.kakao.com/v3/search/book>"
        let headers: HTTPHeaders = ["Authorization": "인증키"]
        let parameters: [String: Any] = ["query": query, "size": pageSize, "page": page]
        
        AF.request(url, parameters: parameters, headers: headers).responseData { [weak self] response in
            guard let self = self else { return }
            
            switch response.result {
            case .success(let data):
                do {
                    let decodedData = try JSONDecoder().decode(BookData.self, from: data)
                    
                    let nextPageExists = decodedData.meta.isEnd
                    self.isEnd = !nextPageExists
                    
                    completion(.success(decodedData))
                } catch {
                    completion(.failure(error))
                }
            case .failure(let error):
                completion(.failure(error))
            }
        }
    }
}
</bookdata,>

그 이후 책 목록이 나오는 컬렉션 뷰 내 데이터를 불러 오는 메서드를 수정하면된다.

var currentPage = 1
var isEnd = false

func fetchBookData(withQuery query: String, page: Int) {
    networkingManager.fetchBookData(withQuery: query, page: currentPage) { [weak self] result in
        guard let self = self else { return }
        switch result {
        case .success(let bookData):
            if self.currentPage == 1 {
                self.bookData = bookData
            } else {
                self.bookData?.documents += bookData.documents
            }
            self.currentPage += 1
            self.isEnd = bookData.meta.isEnd
            DispatchQueue.main.async {
                self.collectionView.reloadData()
            }
        case .failure(let error):
            print("Failed to fetch book data: \\(error)")
        }
    }
}

현재 페이지를 나타내는 수를 넣어 page 내 파라미터에 들어갈 수 있도록 설정하고 currentPage가 1일때 데이터를 받아온 뒤 page가 1을 넘어가면 다음 페이지의 데이터를 추가해서 나올 수 있도록 설정했다.

func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    if !isEnd && indexPath.item == (collectionView.numberOfItems(inSection: indexPath.section) - 1) {
        fetchNextPage()
    }
}

func fetchNextPage() {
    guard let query = searchBar.text else { return }
    fetchBookData(withQuery: query, page: currentPage)
}

그 다음 collectionView willDisplay를 활용해 스크롤이 마지막에 닿을 시 fetchNextPage가 실행되도록 설정하고 fetchNextPage에는 서치바의 텍스트가 들어가 있을때 해당 하는 텍스트의 책 데이터를 가져오도록 설정하면

마지막 스크롤이 닿을때 해당 메서드가 실행되고 무한 스크롤이 작동되어 다음페이지로 넘어가지 않고 하나의 스크롤로 모든 정보를 확인할 수 있다. 🙂

 

 

이제 MVVM 적용만 남아있으니 MVVM에 대해서 공부하고 한번 적용하는 과정을 거쳐보자!!