일단 거의 구현은 완성되었으나 디테일 하게 몇몇 부분이 아직 완성되지 않아 최대한 오늘 안에 끝낼 수 있도록 한번 해 볼 생각이다.
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)
마이 페이지 구현
CoreData 불러와 마이페이지 테이블 뷰 내 구현
이전에 책 상세 페이지에서 CoreData에 책을 담아 저장했다면 이곳에 다시 그 데이터를 불러와 테이블 뷰에 보일 수 있도록 할 예정이다.
func getBookListFromCoreData() -> [BookCoreData] {
var bookList: [BookCoreData] = []
if let context = context {
let request = NSFetchRequest<NSManagedObject>(entityName: self.coreDataName)
let idOrder = NSSortDescriptor(key: "title", ascending: true)
request.sortDescriptors = [idOrder]
do {
if let fetchBookList = try context.fetch(request) as? [BookCoreData] {
bookList = fetchBookList
}
} catch {
print("가져오기 실패")
}
}
return bookList
}
이렇게 구현 한 코어데이터의 GET 메서드를 활용해 가져와서 쉽게 불러올 수 있다.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
bookList = CoreDataManager.shared.getBookListFromCoreData()
mypageTableView.reloadData()
}
viewWillAppear를 활용해 책을 저장할 때마다 마이페이지에서 확인할 때 데이터를 불러오고 테이블 뷰를 리로드 하는 방식으로 구현했다.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
bookList.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 70
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: MypageTableViewCell.identifier, for: indexPath) as? MypageTableViewCell else { fatalError("테이블 뷰 에러") }
let book = bookList[indexPath.row]
cell.mainTitle.text = book.title
cell.subTitle.text = book.authors
cell.priceTitle.text = formattedPrice(Int(book.price))
return cell
}
그리고 테이블 뷰의 데이터 소스를 활용해 이렇게 데이터를 넣으면 내가 저장한 데이터가 마이페이지에서 확인이 가능하다 🙂
책 리스트 전체 삭제 , 부분 삭제 구현
그리고 이제 저장된 데이터를 원하는 값을 삭제 혹은 전체를 삭제할 수 있도록 구현해 보자!
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let bookToDelete = bookList[indexPath.row]
CoreDataManager.shared.deleteBookList(bookToDelete) {
self.bookList.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
}
부분 삭제의 경우는 애플에서 이미 구현되어 있는 스와이프 를 활용해 삭제하는 방법을 사용했다.
특정 셀의 값을 스와이프하고 삭제하면 코어데이터에 있는 데이터도 삭제 될 수 있도록 구현했다.
func deleteAllBooks(completion: @escaping () -> Void) {
guard let context = context else {
print("CoreData context가 유효하지 않습니다.")
return
}
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "BookCoreData")
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
try context.execute(deleteRequest)
try context.save()
completion()
} catch {
print("전체 책 삭제에 실패했습니다: \\(error)")
}
}
전체 삭제하는 메서드는 CoreDataManager에 전체 삭제를 할 수 있는 메서드를 따로 만들어 언제든 사용할 수 있도록 구현했다.
leftButton.addTarget(self, action: #selector(deleteAllBooks), for: .touchUpInside)
전체 삭제 버튼을 미리 오토레이아웃 잡을때 만들어놨기 때문에 addTarget 을 통해 액션을 넣어주면 된다.
@objc func deleteAllBooks() {
let alertController = UIAlertController(title: "삭제 확인", message: "담은 책을 모두 삭제 하시겠습니까?", preferredStyle: .alert)
let deleteAction = UIAlertAction(title: "삭제", style: .destructive) { _ in
CoreDataManager.shared.deleteAllBooks {
self.bookList.removeAll()
self.mypageTableView.reloadData()
}
}
alertController.addAction(deleteAction)
let cancelAction = UIAlertAction(title: "취소", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
present(alertController, animated: true, completion: nil)
}
전체 삭제는 버튼 한번 눌렸을때 지워져버리면 안될 것 같아 알럿을 띄워 전체 삭제함을 다시 한번 선택 할 수 있도록 구현했다.
이렇게 하면 전체 삭제 및 부분 삭제 구현이 완료 된다 🙂
책 상세 페이지 추가 구현
플로팅 버튼 내 검색하기 구현
플로팅 버튼에서 확인되는 검색하기 버튼을 누르면 이전 책 검색하는 VC로 이동하여 검색 창이 활성화 되는 구현을 할 예정이다.
원래는 dismiss를 활용해 간편하게 넘긴 뒤 completion handler를 통해 구현하려고 했으나, 서치바 델리게이트로 작동을 하고 있는 문제인지.. 뭔지 모르겠지만 원하는 데로 구현이 잘 되지 않았다.
그래서 프로토콜을 활용해 델리게이트 패턴을 만들어 해당 내용으로 서치바를 활성화 시켜보고자 한다.
protocol SearchViewControllerDelegate: AnyObject {
func searchButtonPressed()
}
먼저 이렇게 프로토콜을 선언한 뒤 메서드를 구현할 수 있도록 틀을 만들어 구성한다.
extension SearchViewController: UICollectionViewDataSource, UICollectionViewDelegate, SearchViewControllerDelegate {
func searchButtonPressed() {
self.searchBar.becomeFirstResponder()
}
}
그 다음 VC를 확장한 곳에 동일하게 해당 프로토콜을 선언한 뒤 메서드에 원하는 구현 방식을 넣어준다. 나는 SearchViewController 에 넘어와서 서치바가 활성화 될 수 있도록 하고 싶기 때문에 becomeFirstResponder() 를 활용해 코드를 구성했다.
그 다음 이 기능을 사용할 책 상세 페이지 VC로 넘어가서
weak var delegate: SearchViewControllerDelegate?
해당 델리게이트를 선언할 수 있도록 불러온 뒤
actionButton.addItem(title: "검색하기", image: UIImage(systemName: "magnifyingglass")?.withRenderingMode(.alwaysTemplate)) { item in
self.dismiss(animated: true) {
self.delegate?.searchButtonPressed()
}
}
검색하기 버튼을 눌러 모달이 내려가면 completion 핸들러가 실행돼 searchButtonPressed() 가 실행될 수 있도록 코드를 구현하면 된다.
이렇게 검색하기 기능 구현이 완료!
책 담기 기능 성공 시 알럿으로 알림
원하는 책을 담으면 해당 책이 저장되었음을 알리는 알럿 기능을 넣어 책 상세페이지 구현을 마무리 하고자 한다.
func showAlert(message: String) {
let alert = UIAlertController(title: "알림", message: message, preferredStyle: .alert)
let okAction = UIAlertAction(title: "확인", style: .default) { _ in
self.dismiss(animated: true, completion: nil)
}
alert.addAction(okAction)
present(alert, animated: true, completion: nil)
}
어디서든 쉽게 알림 창으로 쓸 수 있는 범용성 있는 알럿 메서드를 하나 만든 뒤
actionButton.addItem(title: "책담기", image: UIImage(systemName: "book.fill")?.withRenderingMode(.alwaysTemplate)) { [self] item in
self.saveBookToCoreData()
let bookName = self.bookData?.title
if let bookName = bookName {
showAlert(message: "\\(bookName) 저장되었습니다.")
} else {
showAlert(message: "책이름 없이 저장되었습니다.")
}
}
플로팅 버튼 내 책 담기를 누르면 코어데이터를 저장하는 메서드가 실행되고 저장된 책 데이터의 title이 알럿 메시지에 포함되어 알럿이 뜨도록 구현했다.
혹시 책 제목이 없다면 없이 저장되는 예외처리도 같이 진행했다.
이렇게 해서 책 상세 페이지 구현 완료 🙂
최근 본 책 구현하기
최근 본 책 컬렉션 뷰 데이터 전달
최근 본 책의 데이터를 어떻게 전달할까 많이 고민하다가 이 내용은 코어데이터에 저장되어 있는 것 보다 휘발성이 있는 편이 나을 것 같다고 생각해 그냥 데이터만 전달하고 앱을 다시 키면 초기화 할 수 있도록 구현했다.
struct RecentlyBookInfo {
let title: String
let thumbnail: String
let authors: [String]
let price: Int
let contents: String
func authorsToString() -> String {
return authors.joined(separator: ", ")
}
func formattedPrice() -> String {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
let formattedPrice = numberFormatter.string(from: NSNumber(value: price)) ?? ""
return "\\(formattedPrice)원"
}
}
데이터 모델링을 새롭게 하나 다시 만들어 최근 본 책만 들어갈 수 있도록 만들었다.
func addToRecentlyViewedBook(indexPath: IndexPath) {
guard let selectedBook = bookData?.documents[indexPath.item] else { return }
guard !recentlyViewedBooks.contains(where: { $0.title == selectedBook.title }) else { return }
let recentlyBookInfo = RecentlyBookInfo(title: selectedBook.title, thumbnail: selectedBook.thumbnail, authors: selectedBook.authors, price: selectedBook.price, contents: selectedBook.contents)
recentlyViewedBooks.insert(recentlyBookInfo, at: 0)
collectionView.reloadData()
}
그리고 최근 본 책 데이터를 책 리스트에서 셀이 선택되었을 때 상세페이지로 넘어간 데이터가 최근 본 책에도 들어올 수 있도록 메서드를 하나 만들었다.
원래 데이터가 들어가는걸 append로 했다가 최근 본 책이 오른쪽으로 밀려나는 것 같아 insert를 통해 가장 왼쪽부터 데이터가 들어오도록 수정했다.
func showBookDetail(at indexPath: IndexPath) {
let detailVC = DetailViewController()
let bookData = bookData?.documents[indexPath.item]
detailVC.bookData = bookData
detailVC.delegate = self
detailVC.mainTitle.text = bookData?.title
detailVC.bookContents.text = bookData?.contents
detailVC.subTitle.text = bookData?.authorsToString()
detailVC.bookPrice.text = bookData?.formattedPrice()
if let imageURL = bookData?.thumbnail, let imageURL = URL(string: imageURL) {
detailVC.bookImageView.kf.setImage(with: imageURL)
}
present(detailVC, animated: true, completion: nil)
}
이렇게 메서드를 구현하고 셀이 선택되었을때 디테일 뷰에 데이터를 넘기는 방식도 메서드로 만들어 둔다.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
switch indexPath.section {
case 0:
showBookRecent(at: indexPath)
break
case 1:
addToRecentlyViewedBook(indexPath: indexPath)
showBookDetail(at: indexPath)
default:
break
}
}
이렇게 구현하면 BookList 의 책을 눌렀을 때 상세페이지로 이동하게 되면 상세 페이지에서 확인되는 데이터가 최근 본 책의 데이터로 들어가게 된다.
이렇게 만들어 주면 책을 선택해 상세페이지로 넘어가면 해당 데이터가 최근 본 책으로 넘어갈 수 있게 된다 🙂
최근 본 책 탭하면 상세페이지 이동
func showBookRecent(at indexPath: IndexPath) {
let detailVC = DetailViewController()
let recentBook = recentlyViewedBooks[indexPath.item]
let bookData = bookData?.documents[indexPath.item]
detailVC.bookData = bookData
detailVC.delegate = self
detailVC.mainTitle.text = recentBook.title
detailVC.subTitle.text = recentBook.authorsToString()
detailVC.bookContents.text = recentBook.contents
detailVC.bookPrice.text = recentBook.formattedPrice()
if let imageURL = URL(string: recentBook.thumbnail) {
detailVC.bookImageView.kf.setImage(with: imageURL)
}
present(detailVC, animated: true, completion: nil)
}
전달 받은 데이터를 DetailViewController와 연결해 이렇게 메서드를 구현하고 didSelectItemAt 의 최근 본 책 부분에 메서드를 실행 하면 이렇게 최근 본 책의 탭이 가능해지고 상세페이지도 동일하게 사용이 가능하다 🙂
남은 구현 모두 완성해서 MVVM 한번 도전 해보자!
'◽️ Programming > T I L' 카테고리의 다른 글
[Project 일지] 단어장 앱 만들기 (1) (0) | 2024.05.13 |
---|---|
책 검색 앱 만들기 (6) (2) | 2024.05.09 |
책 검색 앱 만들기 (4) (3) | 2024.05.07 |
책 검색 앱 만들기 (3) (0) | 2024.05.03 |
책 검색 앱 만들기 (2) (3) | 2024.05.01 |