Skip to content

Instantly share code, notes, and snippets.

@onevcat
Created April 12, 2018 06:58
Show Gist options
  • Star 60 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save onevcat/9e08111cebb1967cb96a737ed40f9f14 to your computer and use it in GitHub Desktop.
Save onevcat/9e08111cebb1967cb96a737ed40f9f14 to your computer and use it in GitHub Desktop.
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
extension Notification {
struct UserInfoKey<ValueType>: Hashable {
let key: String
}
func getUserInfo<T>(for key: Notification.UserInfoKey<T>) -> T {
return userInfo![key] as! T
}
}
extension Notification.Name {
static let toDoStoreDidChangedNotification = Notification.Name(rawValue: "com.onevcat.app.ToDoStoreDidChangedNotification")
}
extension Notification.UserInfoKey {
static var toDoStoreDidChangedChangeBehaviorKey: Notification.UserInfoKey<ToDoStore.ChangeBehavior> {
return Notification.UserInfoKey(key: "com.onevcat.app.ToDoStoreDidChangedNotification.ChangeBehavior")
}
}
extension NotificationCenter {
func post<T>(name aName: NSNotification.Name, object anObject: Any?, typedUserInfo aUserInfo: [Notification.UserInfoKey<T> : T]? = nil) {
post(name: aName, object: anObject, userInfo: aUserInfo)
}
}
struct ToDoItem {
typealias ItemId = UUID
let id: ItemId
let title: String
init(title: String) {
self.id = ItemId()
self.title = title
}
}
class ToDoStore {
enum ChangeBehavior {
case add([Int])
case remove([Int])
case reload
}
static let shared = ToDoStore()
static func diff(original: [ToDoItem], now: [ToDoItem]) -> ChangeBehavior {
let originalSet = Set(original)
let nowSet = Set(now)
if originalSet.isSubset(of: nowSet) { // Appended
let added = nowSet.subtracting(originalSet)
let indexes = added.compactMap { now.index(of: $0) }
return .add(indexes)
} else if (nowSet.isSubset(of: originalSet)) { // Removed
let removed = originalSet.subtracting(nowSet)
let indexes = removed.compactMap { original.index(of: $0) }
return .remove(indexes)
} else { // Both appended and removed
return .reload
}
}
private var items: [ToDoItem] = [] {
didSet {
let behavior = ToDoStore.diff(original: oldValue, now: items)
NotificationCenter.default.post(
name: .toDoStoreDidChangedNotification,
object: self,
typedUserInfo: [.toDoStoreDidChangedChangeBehaviorKey: behavior]
)
}
}
private init() {}
func append(item: ToDoItem) {
items.append(item)
}
func append(newItems: [ToDoItem]) {
items.append(contentsOf: newItems)
}
func remove(item: ToDoItem) {
guard let index = items.index(of: item) else { return }
remove(at: index)
}
func remove(at index: Int) {
items.remove(at: index)
}
func edit(original: ToDoItem, new: ToDoItem) {
guard let index = items.index(of: original) else { return }
items[index] = new
}
var count: Int {
return items.count
}
func item(at index: Int) -> ToDoItem {
return items[index]
}
}
extension ToDoItem: Hashable {
var hashValue: Int {
return id.hashValue
}
}
extension ToDoItem: Equatable {
public static func == (lhs: ToDoItem, rhs: ToDoItem) -> Bool {
return lhs.id == rhs.id
}
}
private let cellIdentifier = "ToDoItemCell"
class ToDoListViewController: UITableViewController {
weak var addButton: UIBarButtonItem?
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier)
navigationItem.rightBarButtonItem = .init(barButtonSystemItem: .add, target: self, action: #selector(addButtonPressed))
addButton = navigationItem.rightBarButtonItem
NotificationCenter.default.addObserver(self, selector: #selector(todoItemsDidChange), name: .toDoStoreDidChangedNotification, object: nil)
}
private func syncTableView(for behavior: ToDoStore.ChangeBehavior) {
switch behavior {
case .add(let indexes):
let indexPathes = indexes.map { IndexPath(row: $0, section: 0) }
tableView.insertRows(at: indexPathes, with: .automatic)
case .remove(let indexes):
let indexPathes = indexes.map { IndexPath(row: $0, section: 0) }
tableView.deleteRows(at: indexPathes, with: .automatic)
case .reload:
tableView.reloadData()
}
}
private func updateAddButtonState() {
addButton?.isEnabled = ToDoStore.shared.count < 10
}
@objc func todoItemsDidChange(_ notification: Notification) {
let behavior = notification.getUserInfo(for: .toDoStoreDidChangedChangeBehaviorKey)
syncTableView(for: behavior)
updateAddButtonState()
}
@objc func addButtonPressed(_ sender: Any) {
let store = ToDoStore.shared
let newCount = store.count + 1
let title = "ToDo Item \(newCount)"
store.append(item: .init(title: title))
}
}
extension ToDoListViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ToDoStore.shared.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
cell.textLabel?.text = ToDoStore.shared.item(at: indexPath.row).title
return cell
}
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { _, view, done in
ToDoStore.shared.remove(at: indexPath.row)
done(true)
}
return UISwipeActionsConfiguration(actions: [deleteAction])
}
}
let navigationViewController = UINavigationController(rootViewController: ToDoListViewController())
PlaygroundPage.current.liveView = navigationViewController
@beforeold
Copy link

extension NotificationCenter {} 中 post 方法的 typedUserInfo 是可选值,那么在 getUserInfo 时强制解包,则可能会抛异常了

@beforeold
Copy link

nice ,自己写又不是那么一回事了,有一点疑问用通知真的好吗,使用回调(OC)会不会更好,VC中刷新的代码

通知或者 KVO 会更好一些,便于多个控制器关心 model 变化。

@beforeold
Copy link

post的userInfo如果有两个key,line24的泛型就不够用了

有眼光啊,上手试了试确实只支持同一种类型的 value,想到的折中办法是将所有的 value 单独构造一个新结构体。: )

@RookiePyl
Copy link

还有人用OC开发的嘛,line 44 ,

enum ChangeBehavior {
case add([Int])
case remove([Int])
case reload
}

如何用OC改写。。 这里卡住了 。 求指教

@zhangxi1017
Copy link

受益匪浅

@RageCPP
Copy link

RageCPP commented Dec 16, 2021

getUserInfo 这个方法简直太巧妙了,非常感谢

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment