DetailBookViewController의 UI 값을 업데이트 하도록 함.func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// 객체 생성
let detailVC = DetailBookViewController()
// UI 값 업데이트
detailVC.bookTitle.text = data[indexPath.item].title
print(data[indexPath.item].title)
detailVC.authors.text = data[indexPath.item].authors.joined(separator: ", ")
detailVC.priceLabel.text = String(data[indexPath.item].price)
let urlString = data[indexPath.item].thumbnail
guard let url = URL(string: urlString) else { return }
DispatchQueue.global().async {
if let data = try? Data(contentsOf: url) {
if let image = UIImage(data: data) {
DispatchQueue.main.sync {
detailVC.thumbnail.image = image
}
}
}
}
}
DetailBookViewController 에서 셀을 업데이트 하는 것처럼 configure 메소드 생성func configure(data: BookData.Documents) {
self.authors.text = data.authors.joined(separator: ",")
self.bookTitle.text = data.title
}
configure 메소드 실행되어 UI를 업데이트 하도록 함. func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let detailVC = DetailBookViewController()
detailVC.configure(data: data[indexPath.item])
}
👨🏫 오류 원인
ViewDidLoad가 실행되기 전에 configure 메소드가 실행되어 UI 값이 업데이트가 일어나지 않은것해결 : 프로퍼티 주입을 사용하여 뷰->뷰 로 데이터를 전달
SearchTabViewController 에서 프로퍼티 주입 사용func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let detailVC = DetailBookViewController()
// 책 상세 보기 화면에 데이터 전달
detailVC.setBookData(data: data[indexPath.item])
}
DetailBookViewController에서 setBookData 메소드로 데이터를 받아옴viewDidLoad 에서 updateUI 메소드를 동해 UI를 업데이트 하여 처리override func viewDidLoad() {
super.viewDidLoad()
if let data = data {
updateUI(data: data) // 검색 화면에서 받아온 데이터로 UI 업데이트
}
}
func setBookData(data: BookData.Documents) {
self.data = data
}
// 위에서 가져온 선택한 셀 데이터로 UI 값 업데이트
func updateUI(data: BookData.Documents) {
self.bookTitle.text = data.title
self.authors.text = data.authors.joined(separator: ",")
if let url = URL(string: data.thumbnail) {
DispatchQueue.global().async {
if let imageData = try? Data(contentsOf: url),
let image = UIImage(data: imageData) {
DispatchQueue.main.async {
self.thumbnail.image = image
}
}
}
}
priceLabel.text = "\(data.price.formattedWithComma)원"
detailLabel.text = data.contents
}
}
protocol Alertable: AnyObject {
func makeAlert(bookTitle: String)
}
class AlertManager: Alertable {
func makeAlert() {
let alert = UIAlertController(title: "로그인 실패", message: "로그인에 실패하였습니다.\n아이디와 비밀번호를 확인해주세요.", preferredStyle: .alert)
let ok = UIAlertAction(title: "확인", style: .default, handler: nil)
alert.addAction(ok)
present(alert, animated: true, completion: nil)
}
}
weak var delegate: Alertable?
@objc private func didTapSaveButton() {
delegate?.makeAlert(bookTitle: data.title)
self.dismiss(animated: true)
}
👉 이렇게 했는데 present(alert, animated: true, completion: nil) 구문에서 에러 발생
👨🏫 오류 원인
present()는 UIViewController의 메서드AlertManager가 UIViewController를 상속받지 않았기 때문에 present(alert, animated:completion:) 부분에서 에러 발생!present()는 UIViewController의 메서드이기 때문에, AlertManager에서 직접 호출할 수 없다!protocol Alertable {
func makeAlert(bookTitle: String)
}
class AlertManager: UIViewController, Alertable {
func makeAlert(bookTitle: String) {
let alert = UIAlertController(title: "책 담기 완료", message: "\(bookTitle) 책 담기 완료!", preferredStyle: .alert)
let ok = UIAlertAction(title: "확인", style: .default, handler: nil)
alert.addAction(ok)
present(alert, animated: true, completion: nil)
}
}
@objc
private func didTappedSaveButton() {
self.dismissModal()
guard let alert = delegate?.makeAlert(bookTitle: "fjfj") else { return }
print(bookTitle)
}
👨🏫 오류 원인
AlertManager가 UIViewController를 상속받았지만, 실제로 화면에 표시되는 ViewController가 아님present()는 현재 화면에 표시되고 있는 UIViewController가 호출해야 작동함.AlertManager는 직접 화면에 표시되지 않기 때문에 present()를 호출해도 아무 일도 안 일어남.SearchTabViewController에서 delegate 설정 및 구현DetailBookViewController 객체에 delegate를 SearchTabViewController 로 설정detailVC.delegate = self
SearchTabViewController 에서 Alertable 채택한 알림창을 띄우는 함수를 선언SearchTabViewController는 UIViewController를 상속받고 있으므로 present() 함수를 호출할수있며, 알림창을 띄우는 화면이므로 알림창이 정상적으로 띄워진다!extension SearchTabViewController: Alertable {
func makeAlert(bookTitle: String) {
let alert = UIAlertController(
title: "책 담기 완료",
message: "\(bookTitle) 책 담기 완료!",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "확인", style: .default))
present(alert, animated: true)
}
}
처음에 단일 데이터를 for문을 사용하여 삭제하였으나, 너무 번거롭다는 생각이 들어 로직을 변경함.
NSBatchDeleteRequest방식을 사용하여 메소드 수정
기존 삭제 방식 (context.delete(...))
for object in fetchedResults {
context.delete(object)
}
try context.save()
NSBatchDeleteRequest 방식
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = BookEntity.fetchRequest()
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
try context.execute(deleteRequest)
언제 NSBatchDeleteRequest를 써야 하나?
| 상황 | 추천 방식 |
|---|---|
| UI에서 하나씩 삭제하거나 상태 반영 필요 | context.delete(object) |
| 전체 삭제, 대량 삭제, 초기화 등 | NSBatchDeleteRequest |
전체 삭제 코드
func deleteAllBooks() {
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = BookEntity.fetchRequest()
let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
try context.execute(batchDeleteRequest)
try context.save()
bookEntityData.removeAll() // 배열 동기화도 함께
} catch {
print("전체 삭제 실패: \(error)")
}
}
먼저 First Responder 란?
First Responder는 iOS에서 가장 먼저 사용자 입력(터치, 키보드, 제스처 등)을 받는 객체를 의미주요 역할
역할 설명 입력 이벤트 처리 터치나 키보드 입력을 가장 먼저 받음 키보드 표시 제어 textField.becomeFirstResponder()→ 키보드 표시textField.resignFirstResponder()→ 키보드 내림UIResponder 체인의 시작점 이벤트가 처리되지 않으면 위로 전달됨 (예: 뷰 → 뷰컨트롤러 → 윈도우 등) 사용 예시
textField.becomeFirstResponder() // 키보드 올라옴 textField.resignFirstResponder() // 키보드 내려감기억하면 좋은 점
First Responder는 오직 하나만 존재- 일반적으로 텍스트 입력 필드나 버튼이 가장 많이 사용
- 터치나 키보드 입력을 적절한 UI 요소에 전달하기 위한 중요한 개념
becomeFirstResponder() 사용하여 검색바 활성화let searchTabVC = SearchTabViewController()
searchTabVC.searchBar.becomeFirstResponder()
👨🏫 오류 원인
self.tabBarController?.selectedIndex = 0
let tabBarVC = TabbarController()
tabBarVC.searchTabVC.searchBar.becomeFirstResponder()
becomeFirstResponder() 동작하지 않음👨🏫 오류 원인
becomeFirstResponder()가 동작하지 않음해결
UIViewController에는 기본적으로 tabBarController 라는 프로퍼티 지원tabBarController는 UIViewController의 computed property로,UITabBarController의 childViewController로 들어가 있을 때만 유효if let tabBarVC = self.tabBarController as? TabbarController {
tabBarVC.searchTabVC.searchBar.becomeFirstResponder()
}
self.tabBarController : as? TabbarController : tabBarController는 일반적인 UITabBarController 타입TabbarController로 다운캐스팅해서, searchTabVC 프로퍼티에 접근할 수 있도록 함UITableView에서 셀을 왼쪽으로 스와이프해서 삭제하려면, UITableViewDataSource의 메서드인 tableView(_:commit:forRowAt:)를 구현하면 됨.func tableView(_ tableView: UITableView,
commit editingStyle: UITableViewCell.EditingStyle,
forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// 1. CoreData 삭제
let target = CoreDataManager.shared.bookEntityData[indexPath.row]
CoreDataManager.shared.deleteBook(target)
// 2. 배열 업데이트
CoreDataManager.shared.bookEntityData.remove(at: indexPath.row)
// 3. 테이블 뷰 갱신
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}