Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
//: 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
@jjjjjeffrey

This comment has been minimized.

Copy link

commented Apr 12, 2018

把 ToDoStore 抽象成泛型类,岂不是美滋滋,手动[微笑]

@BBBBBigRabbit

This comment has been minimized.

Copy link

commented Jun 4, 2018

代码居然可以写的这么美

@muchangqing

This comment has been minimized.

Copy link

commented Jun 8, 2018

刷新我的认知

@when50

This comment has been minimized.

Copy link

commented Jul 4, 2018

几乎所有的blog都会认真读一下,受益非浅,感谢分享

@meilulu

This comment has been minimized.

Copy link

commented Aug 17, 2018

为了进入这里花了好长时间找VPN,真悲伤

@Gusont

This comment has been minimized.

Copy link

commented Oct 31, 2018

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

@anthann

This comment has been minimized.

Copy link

commented Nov 26, 2018

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

@beforeold

This comment has been minimized.

Copy link

commented Dec 1, 2018

把 ToDoStore 抽象成泛型类,岂不是美滋滋,手动[微笑]

这是一个具体的业务 model,没那么泛啦

@beforeold

This comment has been minimized.

Copy link

commented Dec 1, 2018

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

@beforeold

This comment has been minimized.

Copy link

commented Dec 1, 2018

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

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

@beforeold

This comment has been minimized.

Copy link

commented Dec 3, 2018

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

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

@RookiePyl

This comment has been minimized.

Copy link

commented Sep 10, 2019

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

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

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

@zhangxi1017

This comment has been minimized.

Copy link

commented Sep 26, 2019

受益匪浅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.