카카오 도서 검색 API를 활용하여 책을 검색하고 관리할 수 있는 iOS 앱을 개발하였다. MVVM 패턴을 적용하고 CoreData를 활용한 데이터 관리, 무한 스크롤 등 다양한 기능을 구현하였다.
class BookSearchViewModel {
private let apiKey = "YOUR_API_KEY"
private(set) var books: [Book] = []
var onBooksUpdated: (() -> Void)?
func searchBooks(query: String) {
guard let encodedQuery = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return }
// API 호출 및 데이터 처리
}
}
extension BookSearchViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let position = scrollView.contentOffset.y
let contentHeight = scrollView.contentSize.height
let screenHeight = scrollView.bounds.height
if position > contentHeight - screenHeight - 100 {
viewModel.loadNextPageIfNeeded()
}
}
}
class CoreDataManager {
static let shared = CoreDataManager()
func saveBook(_ book: Book) {
let context = persistentContainer.viewContext
let bookEntity = BookEntity(context: context)
// 데이터 저장 로직
}
}
문제 상황
해결 방법
class ViewController: UITabBarController {
private func setupTabBar() {
let searchVC = BookSearchViewController()
let bookmarkVC = BookmarkViewController()
// Delegate 패턴을 통한 데이터 전달 구현
searchVC.bookmarkDelegate = bookmarkVC
let searchNav = UINavigationController(rootViewController: searchVC)
let bookmarkNav = UINavigationController(rootViewController: bookmarkVC)
searchNav.tabBarItem = UITabBarItem(
title: "검색",
image: UIImage(systemName: "magnifyingglass"),
selectedImage: UIImage(systemName: "magnifyingglass.fill")
)
bookmarkNav.tabBarItem = UITabBarItem(
title: "저장된 책",
image: UIImage(systemName: "bookmark"),
selectedImage: UIImage(systemName: "bookmark.fill")
)
self.viewControllers = [searchNav, bookmarkNav]
}
}
// BookmarkViewController에 viewWillAppear 추가로 데이터 갱신 문제 해결
extension BookmarkViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
bookmarkTableView.reloadData()
}
}
문제 상황
해결 방법
extension CoreDataManager {
func saveRecentBook(_ book: Book) {
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "RecentBook")
fetchRequest.predicate = NSPredicate(format: "isbn == %@", book.isbn)
do {
let results = try context.fetch(fetchRequest)
if let existing = results.first {
// 이미 존재하는 경우 날짜만 업데이트
existing.setValue(Date(), forKey: "viewedAt")
existing.setValue(book.thumbnail, forKey: "thumbnail")
existing.setValue(book.title, forKey: "title")
} else {
// 새로운 책 추가
let entity = NSEntityDescription.entity(forEntityName: "RecentBook", in: context)!
let recentBook = NSManagedObject(entity: entity, insertInto: context)
recentBook.setValue(book.isbn, forKey: "isbn")
recentBook.setValue(Date(), forKey: "viewedAt")
recentBook.setValue(book.thumbnail, forKey: "thumbnail")
recentBook.setValue(book.title, forKey: "title")
}
saveContext()
// 10개 제한 유지
cleanupOldRecentBooks()
} catch {
print("Error saving recent book: \(error)")
}
}
private func cleanupOldRecentBooks() {
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "RecentBook")
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "viewedAt", ascending: false)]
do {
let results = try context.fetch(fetchRequest)
if results.count > 10 {
for index in 10..<results.count {
context.delete(results[index])
}
saveContext()
}
} catch {
print("Error cleaning up old recent books: \(error)")
}
}
}