검색 후 해당 셀을 클릭했을 때 상세화면이 나오도록 해야한다.
셀 클릭 시 상세화면 띄우기
- UICollectionViewDelegate 의 didSelectItemAt 메서드 활용
extension SearchTapViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let detailVC = DetailBookViewController()
// 알럿 delegate 설정
detailVC.delegate = self
switch indexPath.section {
case 0:
return
case 1:
detailVC.bookData = bookArrays[indexPath.row]
default:
return
}
present(detailVC, animated: true, completion: nil)
}
}
상세화면에 해당 셀의 데이터를 전달하고
present로 간단하게 화면을 띄울 수 있다.
기본적으로 모달형식으로 화면이 띄워지고 fullScreen 등의 설정을 할 수 있다.
담기 버튼 기능 구현 - CoreData
코어데이터 매니저
코어데이터를 위해 Entity 파일을 생성 후
// MARK: - [Create] 코어데이터에 데이터 생성 (Document -> BookSaved)
func saveBook(with book: Document, completion: @escaping () -> Void) {
// 임시저장소 있는지 확인
if let context = context {
// 임시저장소에 있는 데이터를 그려줄 형태 파악하기
if let entity = NSEntityDescription.entity(forEntityName: self.modelName, in: context) {
// 임시저장소에 올라가게 할 객체만들기 (NSManagedObject ===> BookSaved)
if let bookSaved = NSManagedObject(entity: entity, insertInto: context) as? BookSaved {
// MARK: - BookSaved에 실제 데이터 할당
bookSaved.title = book.title
bookSaved.authors = book.authors?.first
bookSaved.price = "\(book.price ?? 0)원"
appDelegate?.saveContext()
}
}
}
completion()
}
코어 데이터에 저장하는 메서드를 구현하였다.
담기 버튼 클로저 활용
- 뷰와 뷰컨트롤러를 나누어 놓았고 뷰에 버튼을 생성해두었다.
뷰컨트롤러에서 addTarget을 사용하기 위해 클로저를 활용하였다.
상세화면 뷰 컨트롤러
final class DetailBookViewController: UIViewController {
// 1. 버튼이 눌렸을 때 클로저 전달(할당)
private func setupButtonActions() {
detailBookView.saveButtonPressed = { [weak self] in
guard let self = self, let bookData = self.bookData else { return }
self.coreDataManager.saveBook(with: bookData) {
self.dismiss(animated: true)
self.delegate?.didSaveBook(title: bookData.title ?? "")
}
}
detailBookView.closeButtonPressed = { [weak self] in
self?.dismiss(animated: true)
}
}
}
뷰컨트롤러에서 상세화면의 버튼 클로저를 통해 행동 할당
saveBook 으로 셀 클릭시 넘어온 해당 데이터를 코어데이터에 저장
- 상세화면 뷰 클래스
final class DetailBookView: UIView {
// 2. 뷰컨에 있는 클로저 저장(할당)
var saveButtonPressed: () -> Void = { }
var closeButtonPressed: () -> Void = { }
lazy var saveButton: UIButton = {
let button = UIButton()
button.setTitle("담기", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .systemGreen
button.layer.cornerRadius = 20
button.addTarget(self, action: #selector(saveButtonTapped), for: .touchUpInside)
return button
}()
lazy var closeButton: UIButton = {
let button = UIButton()
button.setTitle("X", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .gray
button.layer.cornerRadius = 20
button.addTarget(self, action: #selector (closeButtonTapped), for: .touchUpInside)
return button
}()
// 3. 버튼이 눌리면 ButtonPressed 에 들어있는 클로저 실행
@objc func saveButtonTapped(_ sender: UIButton) {
saveButtonPressed()
}
@objc func closeButtonTapped(_ sender: UIButton) {
closeButtonPressed()
}
빈 클로저에 뷰컨트롤러에서 전달한 클로저가 할당되고
버튼을 누르면 해당 클로저가 실행된다.
Delegate로 알럿 띄우기
protocol SaveBookDelegate: AnyObject {
func didSaveBook(title: String)
}
Delegate 프로토콜을 생성하고
/// 알럿 delegate 프로토콜
extension SearchTapViewController: SaveBookDelegate {
func didSaveBook(title: String) {
let alert = UIAlertController(title: nil, message: "‘\(title)’\n책 담기 완료!", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "확인", style: .default))
present(alert, animated: true, completion: nil)
}
}
사용할 뷰컨트롤러에서 채택 및 알럿 구현
위의 버튼 클로저 에서
self.delegate?.didSaveBook(title: bookData.title ?? "") 으로 해당 책의 제목을 전달하고 있음
상세화면에서 delegate 변수 생성
Delegate를 사용하려면 채택한 곳에서 대리자 설정을 해주어야 한다.
didSelectAt 에서 상세화면의 대리자 = 메인화면 으로 설정
담기 버튼을 누르면 화면이 닫히고 알럿이 띄워진다.
담은 책 리스트 탭 구현 - CoreData
위에서 코어데이터에 저장한 데이터를 활용하여 담은 책 리스트에 보여지게 하면 된다.
코어데이터 Read
func getBookSavedArrayFromCoreData() -> [BookSaved] {
var savedBookList: [BookSaved] = []
// 임시저장소 있는지 확인
if let context = context {
// 요청서
let request = NSFetchRequest<NSManagedObject>(entityName: self.modelName)
do {
// 임시저장소에서 (요청서를 통해서) 데이터 가져오기
if let fetchedBookList = try context.fetch(request) as? [BookSaved] {
savedBookList = fetchedBookList
}
} catch {
print("가져오는 것 실패")
}
}
return savedBookList
}
SavedTapViewController 에서 이를 전달받을 변수를 생성하여 활용
final class SavedTapViewController: UIViewController {
private let savedTapView = SavedTapView()
var savedBooks: [BookSaved] = []
let coreDataManager = CoreDataManager.shared
override func loadView() {
self.view = savedTapView
}
// 뷰가 나타날때마다
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 코어데이터에 저장된 데이터 불러오기
savedBooks = coreDataManager.getBookSavedArrayFromCoreData()
savedTapView.tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
setupButtonAction()
savedTapView.tableView.dataSource = self
}
}
extension SavedTapViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
savedBooks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: SavedListCell.id, for: indexPath) as! SavedListCell
let bookSaved = savedBooks[indexPath.row]
cell.bookNameLabel.text = bookSaved.title
cell.authorNameLabel.text = bookSaved.authors
cell.bookPriceLabel.text = bookSaved.price
cell.selectionStyle = .none
return cell
}
}
코어데이터 전체삭제와 개별삭제
전체삭제
func deleteAllBooks(completion: @escaping () -> Void) {
if let context = context {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: self.modelName)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
// 전체 삭제 요청
try context.execute(deleteRequest)
context.reset()
completion()
} catch {
print("전체 삭제 실패")
completion()
}
}
}
try context.execute(deleteRequest)
context.reset()
를 활용하면 쉽게 전체삭제가 가능하다.
개별 삭제
func deleteBook(with book: BookSaved) {
if let context = context {
context.delete(book)
appDelegate?.saveContext()
}
}
해당하는 데이터를 삭제해야되기 때문에 해당 데이터를 삭제해야한다.
전체삭제, 추가 버튼 기능 구현
/// 버튼 클로저 할당
func setupButtonAction() {
savedTapView.deleteButtonPressed = { [weak self] in
guard let self = self else { return }
// 알럿 생성
let alert = UIAlertController(title: "⚠️ 주의", message: "정말 삭제하시겠습니까?", preferredStyle: .alert)
let succes = UIAlertAction(title: "확인", style: .default) { action in
// 코어데이터에서 전체 삭제
self.coreDataManager.deleteAllBooks {
DispatchQueue.main.async {
self.savedBooks = []
self.savedTapView.tableView.reloadData()
print("전체 삭제 완료")
}
}
}
let cancel = UIAlertAction(title: "취소", style: .cancel, handler: nil)
alert.addAction(succes)
alert.addAction(cancel)
self.present(alert, animated: true, completion: nil)
}
savedTapView.plusButtonPressed = { [weak self] in
guard let self = self else { return }
// 화면 이동
self.tabBarController?.selectedIndex = 0
// 기존 검색 탭 뷰 컨트롤러의 서치바 활성화
if let searchVC = self.tabBarController?.viewControllers?.first as? SearchTapViewController {
searchVC.searchTapView.searchBar.becomeFirstResponder()
}
}
}
전체삭제의 경우 알럿을 활용하였고
추가 버튼은 클릭 시 탭바의 index를 활용하여 쉽게 화면이동을 할 수 있었다.
그리고 탭바가 가지고 있는 뷰컨트롤러에 접근하여 서치바를 활성화 시킬 수 있었다.
테이블 뷰 스와이프삭제 기능
extension SavedTapViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let deleteBook = savedBooks[indexPath.row]
// 배열에서 삭제
savedBooks.remove(at: indexPath.row)
// 코어데이터에서 삭제
coreDataManager.deleteBook(with: deleteBook)
// 삭제 애니메이션
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? {
return "삭제"
}
}
UITableViewDelegate 에서 주어지는 기능으로 스와이프 기능을 구현할 수 있었다.
테이블 뷰 스와이프 기능의 두가지 방법
1. tableView(_:editingStyleForRowAt:)
이 메서드는 UITableView에서 기본 제공 편집 스타일(삭제, 삽입)을 설정하기 위한 메서드이다.
주로 삭제 또는 삽입 작업에 사용된다.
2. tableView(_:trailingSwipeActionsConfigurationForRowAt:)
iOS 11에서 추가된 메서드로, 스와이프 시 다양한 작업을 커스터마이징할 수 있다.
삭제뿐 아니라 **다양한 액션(예: 편집, 공유, 즐겨찾기 추가)**을 추가할 수 있다.
쉽게 말해 1번은 간단한 구현에서 사용
2번은 커스터마이징 등 복잡한 구현에 사용한다고 한다.
참고)
'내일배움캠프 iOS' 카테고리의 다른 글
iOS) TIL # 55 심화주차 팀프로젝트 - 1 (0) | 2025.01.07 |
---|---|
UIKit) TIL # 54 앱개발 심화주차 개인과제 - Lv 4,마무리 및 트러블 슈팅 (0) | 2025.01.06 |
UIKit) TIL # 52 앱개발 심화주차 개인과제 - Lv 2 API 연결 (0) | 2025.01.02 |
UIKit) TIL # 51 앱개발 심화주차 개인과제 - Lv 1,2 컬렉션뷰 CompositinalLayout (0) | 2024.12.31 |
UIKit) TIL # 50 앱개발 심화주차 개인과제 - 기획 (0) | 2024.12.30 |