🐦 까마구 GAMAGU (App Store)
CoreData
의Relationship
사용하여 엔티티 간의 1:N 관계 설정
UICollectionViewCompositionalLayout
사용하여 동적 크기 캐러셀 구현NSCollectionLayoutSection
에boundarySupplementaryItems
과decorationItems
속성 사용하여 헤더와 백그라운드 구현
- 사용자 설정한 알림 주기에 따라
UNNotificationRequest
랜덤 배치 UNUserNotificationCenterDelegate
프로토콜의 메서드로 푸시 알림 클릭 시 해당 셀로 이동
대상 Relationship이 복수일 경우 추가/삭제 시 extension으로 구현된 메서드 사용
// NSManagedObject
extension Category {
@objc(addItemsObject:)
@NSManaged public func addToItems(_ value: Item) // 아이템 1개 추가 시 사용
@objc(removeItemsObject:)
@NSManaged public func removeFromItems(_ value: Item)
@objc(addItems:)
@NSManaged public func addToItems(_ values: NSSet)
@objc(removeItems:)
@NSManaged public func removeFromItems(_ values: NSSet) // 아이템 N개 추가 시 사용
}
let itemsList = samples.map { (title: String, content: String) in
let item = Item(context: context)
item.title = title
item.content = content
item.category = category1
item.createdDate = Date()
return item
}
category1.addToItems(NSSet(array: itemsList)) // 아이템 N개 추가
컬렉션뷰 생성 시 NSCollectionLayoutDecorationItem 백그라운드 재사용 뷰 추가
public let collectionView: UICollectionView = {
let layout = UICollectionViewCompositionalLayout { _, _ in
// item, group, 설정...
let section = NSCollectionLayoutSection(group: horizontalGroup)
// 섹션 객체의 decorationItems에 백그라운드 재사용 뷰 추가
section.decorationItems = [NSCollectionLayoutDecorationItem.background(elementKind: HomeCollectionBackgroundView.identifier)]
return section
}
// 레이아웃 객체에 백그라운드 재사용 뷰 등록
layout.register(HomeCollectionBackgroundView.self, forDecorationViewOfKind: HomeCollectionBackgroundView.identifier)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
return collectionView
}()
백그라운드 UICollectionReusableView 안에 실제로 사용될 백그라운드 UIView 설정
// 백그라운드 재사용 뷰
final class HomeCollectionBackgroundView: UICollectionReusableView {
static let identifier = "CollectionBackgroundView"
private let backgroundView: UIView = { // 실제 백그라운드로 사용되는 뷰
let view = UIView()
view.backgroundColor = .primary80
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(backgroundView)
NSLayoutConstraint.activate([
backgroundView.topAnchor.constraint(equalTo: topAnchor, constant: 48),
backgroundView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -48),
backgroundView.leadingAnchor.constraint(equalTo: leadingAnchor),
backgroundView.trailingAnchor.constraint(equalTo: trailingAnchor),
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
userNotificationCenter(_:didReceive:withCompletionHandler:)메서드에서 홈 ViewController 접근하여 스크롤
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
// 해당 알림의 제목을 키워드로 아이템 조회
guard let item = CoreDataManager.shared.getItem(title: response.notification.request.content.title) else { return }
// 해당 알림의 아이템이 속한 카테고리 및 아이템의 인덱스를 추출
let categoryIndex = CoreDataManager.shared.getCategoryIndex(category: item.category!) ?? 0
let itemIndex = CoreDataManager.shared.getItemIndexOfCategory(item: item) ?? 0
// SceneDelegate 객체의 window 객체로부터 홈 ViewController 추출하여 컬렉션 뷰와 테이블 뷰 해당 아이템의 셀 위치로 스크롤
if let tabBarController = window?.rootViewController as? UITabBarController {
tabBarController.selectedIndex = 0
if let navigationController = tabBarController.viewControllers?.first as? UINavigationController,
let homeVC = navigationController.viewControllers.first as? HomeViewController {
homeVC.collectionView.scrollToItem(
at: IndexPath(item: itemIndex, section: categoryIndex),
at: [.centeredHorizontally, .centeredVertically],
animated: true
)
homeVC.tableView.scrollToRow(
at: IndexPath(item: itemIndex, section: categoryIndex),
at: .middle,
animated: true
)
}
}
}