Ch.13 Gold: Support Multiple Windows
# 멀티 윈도우 지원하고 싱크 맞추기
- LootLogger 가 여러 윈도우에서 열릴 수 있게 하고, 한 곳에서의 편집 사항이 다른 곳에서도 적용되게 하는 게 과제
# 프로젝트 설정에서 멀티윈도우 지원하기
- Project settings - Deployment Info - Supports multiple windows
# 공유 ItemStore
- 솔직히 맞게 한 건지는 모르겠는데...암튼 모든 scene 에서 item store 를 공유하게 해야 된대서 SceneDelegate 에서 itemStore 를 static 하게 하나 선언하고, 새로운 유저 인터페이스가 생성되거나 다시 로딩될 때 호출되는 scene(_:willConnectTo:options:) 메서드에서 이 static itemStore 를 주입하는 방식...!
- 공식 문서를 보면 데이터 주입 등을 해당 메서드에서 하라고 하고 있다..
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
static let itemStore = ItemStore() // static 으로 선언
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
// Access the ItemsViewController and set its item store
let navController = window!.rootViewController as! UINavigationController
let itemsController = navController.topViewController as! ItemsViewController
itemsController.itemStore = SceneDelegate.itemStore
}
}
# notification
-
위에처럼 하니까 itemStore 를 모든 scene 에서 공유하기 때문에 다른 창을 켜던가 아님 다른 뷰에 넘어갔다 오면 모든 창에서 업데이트가 되어 있는데 화면 전환 없이는 한 창에서의 변화가 다른 창에서 즉각적으로 적용되지 않았다.
-
그래서 변화가 있을 때마다 앱에서 공유 중인 NotificationCenter.default 를 통해 알리고, 알림을 받을 수 있도록 해야겠다고 생각했는데 여기서부터 멍청함과 가보자고 마인드의 환장의 콜라보...
# 구독
-
일단 NotificaitonCenter.default 가 앱 단위로 하나씩 존재하기 때문에 하나의 앱에서 여러 window(scene) 을 허용한 현재의 경우 얘를 쓰면 되는데 멍청한 착각으로 서로 다른 앱 간의 알림을 가능케 하는 DistributedNotificationCenter 를 써야한다고 생각했다...절대 선언이 안돼서 이것 때문에 시간 순삭 굿...
-
암튼 뭔가 한 윈도우에서 아이템 스토어에 변화가 생기면 다른 윈도우에서도 모두 뷰를 업데이트 해줘야 하므로
itemStoreDidUpdate
라는 Notification 을 새로 만들어주고, ItemsViewController 와 DetailViewController 에서 해당 notification 에 구독하도록 했다.- 뭔가 작업마다 다른 notification 을 만들어줄까 생각도 했는데(특히 Detail View 에서 아이템을 편집하는 경우), ItemStore 변화라는 키워드로 묶을 수 있기도 하고, 이 시점에는 이미 너무 많은 고통을 받아서 수정하기 싫어서 걍 넘어갔다...
-
ItemsViewController 에서는 알림이 오면 tableView.reloadData() 를 통해 화면을 업데이트했고 DetailViewController 에서는 모든 라벨을 다시 업데이트 하도록 했다.
- 특정 행이 아닌 전체를 다 reload 한 이유는 이전에도 최애템 과제에서 비슷한 내용을 얘기한 적 있는데, 추가/삭제 작업의 경우 업데이트가 필요한 창에서는 아직 추가/삭제할 행이 생성되지 않아 프로그램이 터지기 때문!
extension Notification.Name {
static let itemStoreDidUpdate = Notification.Name("itemStoreDidUpdate")
}
class ItemsViewController: UITableViewController {
required init?(coder: NSCoder) {
super.init(coder: coder)
navigationItem.leftBarButtonItem = editButtonItem
NotificationCenter.default.addObserver(self,
selector: #selector(reloadData(for:)),
name: .itemStoreDidUpdate,
object: nil)
}
@objc private func reloadData(for notification: Notification) {
if let notifyingController = notification.object as? UIViewController,
self != notifyingController {
tableView.reloadData()
}
}
}
class DetailViewController: UIViewController, UITextFieldDelegate {
required init?(coder: NSCoder) {
super.init(coder: coder)
NotificationCenter.default.addObserver(self,
selector: #selector(updateLabels),
name: .itemStoreDidUpdate,
object: nil)
}
@objc private func updateLabels(for notification: Notification) {
if let notifyingController = notification.object as? UIViewController,
self != notifyingController {
updateLablesToMatchItem()
}
}
private func updateLablesToMatchItem() {
nameField.text = item.name
serialNumberField.text = item.serialNumber
valueField.text = numberFormatter.string(from: NSNumber(value: item.valueInDollars))
dateLabel.text = dateFormatter.string(from: item.dateCreated)
}
}
# 알림
- 변화가 있었음을 알릴 부분은 총 4군데로, itemsView 에서 아이템을 추가/삭제/이동하는 경우와 detailView 에서 내용을 편집하는 경우였다. 각 경우에 모든 작업이 끝나고 알림을 보내도록 했다. 정확한 이유는 모르겠는데 작업 중간에 알림을 보내면 어쨌든 현재 편집 중이던 윈도우도 해당 알림을 받아서 이 과정에서 꼬이는 것 같았다...나중에 다른 이유로 알림을 받았을 때 내가 보낸 알림인 경우에는 (이미 뷰가 업데이트 되었으므로) 뷰를 업데이트 하지 않도록 하는 처리를 했는데, 이렇게 했더니 작업 시작, 중간, 끝 어느 시점에서 알림을 보내든 상관이 없었다.
class ItemsViewController: UITableViewController {
var itemStore: ItemStore!
@IBAction func addNewItem(_ sender: UIBarButtonItem) {
let newItem = itemStore.createItem()
if let index = itemStore.allItems.firstIndex(of: newItem) {
let indexPath = IndexPath(row: index, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
// 알림
NotificationCenter.default.post(name: .itemStoreDidUpdate,
object: self)
}
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let item = itemStore.allItems[indexPath.row]
itemStore.removeItem(item)
tableView.deleteRows(at: [indexPath], with: .automatic)
// 알림
NotificationCenter.default.post(name: .itemStoreDidUpdate,
object: self)
}
}
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
itemStore.moveItem(from: sourceIndexPath.row, to: destinationIndexPath.row)
// 알림
NotificationCenter.default.post(name: .itemStoreDidUpdate,
object: self)
}
}
class DetailViewController: UIViewController, UITextFieldDelegate {
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
view.endEditing(true)
item.name = nameField.text ?? ""
item.serialNumber = serialNumberField.text ?? ""
if let valueText = valueField.text,
let value = numberFormatter.number(from: valueText) {
item.valueInDollars = value.intValue
} else {
item.valueInDollars = 0
}
// 알림
NotificationCenter.default.post(name: .itemStoreDidUpdate,
object: self)
}
}
- 그리고 현재 알림을 보내는 view controller 를 notification object 로 설정했는데, 앞서 말했듯이 실제 편집이 최초로 발생해서 굳이 뷰를 또 업데이트할 필요 없는 윈도우도 알림을 받기 때문에 reloadData() 메서드가 실행되면서 애니메이션이 씹혔다. 그래서 이를 방지하기 위해 알림을 보낸 view controller 가 이를 받은 view controller 가 다른 경우에만 뷰를 업데이트하도록 했다.
- 여기서 또 구독하면 뭘 리턴받는지 몰라서 고통받았다...Notification 객체를 받는다....
@objc private func reloadData(for notification: Notification) {
if let notifyingController = notification.object as? UIViewController,
self != notifyingController {
tableView.reloadData()
}
}
Author And Source
이 문제에 관하여(Ch.13 Gold: Support Multiple Windows), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@sunnysideup/Ch.13-Gold-Support-Multiple-Windows저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)