Skip to content

Commit

Permalink
add data source
Browse files Browse the repository at this point in the history
  • Loading branch information
jxcom committed Oct 15, 2023
1 parent 7530d99 commit d90831e
Show file tree
Hide file tree
Showing 6 changed files with 974 additions and 1 deletion.
694 changes: 694 additions & 0 deletions appledev/bind-datasource.html

Large diffs are not rendered by default.

Binary file added appledev/bind-datasource.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions appledev/swift-binding4.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
276 changes: 276 additions & 0 deletions appledev/【swift基础】增删查改之排序.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
[Home](https://wecache.com)

# 【swift基础】增删查改之排序和selector

[toc]

## 按之前的思路去想

现在我们要对 table view 中的某一列进行排序,比如年龄这一列,那么怎么办呢?比如用户点击年龄这一列,就会按年龄排序,重新展示列表。

按之前的总结的经验,应该是这样的:

1、在 delegate 中找到点击 column 的回调函数;

2、再回调函数中,对 NSArrayController 进行排序;

3、然后调用 table view 对 reload data 函数重新显示列表。

看上去应该是这样,那么,实际去做的时候会遇到什么问题呢?

## 排序的方法

### NSSortDescriptor

进入 UI 设置之前,先简单了解一下 swift 的 sort descriptor。这个如同 C++ 的谓词概念, compare 的时候根据谓词逻辑得出谁先谁后,因此,我们只需要关心“谁先谁后”即可,排序的细节由 swift 操心即可。下面就是一个简单的使用谓词逻辑进行排序的例子:

```swift
class Student: NSObject {
var id: uint64
@objc var name: String

init(id: uint64, name: String) {
self.id = id
self.name = name
}

override var description: String {
return "Student(id = \(self.id), name = \(self.name))\n"
}
}

var myarray = NSArray.init(array: [
Student(id: 1, name: "jack"),
Student(id: 2, name: "rose"),
Student(id: 3, name: "claire"),
Student(id: 4, name: "jenny"),
Student(id: 5, name: "sally"),
Student(id: 6, name: "nina"),
])

let nameSortDescriptor = NSSortDescriptor.init(key: "name", ascending: true)

print(myarray.sortedArray(using: [nameSortDescriptor]))
```

需要注意的是,`name` 字段是加上了 `@objc` 关键字的,因为如果 swift 代码需要和 objc 框架互动,比如让 cocoa 知道 `name` 这个字段,那么就需要加上标识符 `@objc` 否则会引起运行时崩溃。

有了 `@objc` 标识,`NSSortDescriptor` 才可以初始化,进而使用这个 descriptor 进行排序。

上面的 key 也可以用 keypath 来代替,也似乎更 swifty:

```swift
let nameSortDescriptor = NSSortDescriptor.init(keyPath: \Student.name, ascending: true)
```

那么,如果有一个 VipStudent,且其包含了一个 student 字段,若对 VipStudent 进行排序则依赖 student 字段排序,且 name 字段一样的情况下,按照 id 来排序怎么写呢?可以这样:

```swift
class Student: NSObject {
@objc var id: uint64
@objc var name: String

init(id: uint64, name: String) {
self.id = id
self.name = name
}

override var description: String {
return "Student(id = \(self.id), name = \(self.name))\n"
}
}

class VipStudent: NSObject {
@objc let student: Student

init(student: Student) {
self.student = student
}

override var description: String {
return "in vip student, Student(id = \(self.student.id), name = \(self.student.name))\n"
}
}

var myarray = NSArray.init(array: [
VipStudent(student: Student(id: 1, name: "jack")),
VipStudent(student: Student(id: 4, name: "rose")),
VipStudent(student: Student(id: 3, name: "claire")),
VipStudent(student: Student(id: 2, name: "rose")),
VipStudent(student: Student(id: 5, name: "sally")),
VipStudent(student: Student(id: 6, name: "nina")),
])

let nameSortDescriptor = NSSortDescriptor.init(keyPath: \VipStudent.student.name, ascending: true)
let idSortDescriptor = NSSortDescriptor.init(keyPath: \VipStudent.student.id, ascending: true)

print(myarray.sortedArray(using: [nameSortDescriptor, idSortDescriptor]))
```

上面主要有这几个地方值得关注:

1、使用了 `keyPath` 来指定排序字段;这里对 `keyPath` 的理解又进一步了,看起来像个对字段的引用。

2、排序的时候,传入的 descriptor 是有顺序的,若前一项相同,则用后一项排。

3、swift 的字段给到 cocoa 引用的话,则必须加上 `@objc` 标识。

### compare 方法

NSSortDescriptor 给了一个很简单的解决方案,但有个缺点,就是如果 class 或者 struct 嵌套很深的话, `keyPath` 会变得很长,或者有时候我们并不想关心 Student 的排序方式,这时怎么办呢?那么就可以用 `compare` 方法来实现,一方面不需要写很长的 `keyPath`,另一方面也隐藏了 Student 的排序方式,以后有什么变动外界无需感知。

```swift
@objc class Student: NSObject {
@objc var id: uint64
@objc var name: String

init(id: uint64, name: String) {
self.id = id
self.name = name
}

override var description: String {
return "Student(id = \(self.id), name = \(self.name))\n"
}

@objc func compare(_ other: Student) -> ComparisonResult {
if self.name == other.name {
return self.id < other.id ? .orderedAscending : .orderedDescending
}
return self.name.compare(other.name)
}
}

class VipStudent: NSObject {
@objc let student: Student

init(student: Student) {
self.student = student
}

override var description: String {
return "in vip student, Student(id = \(self.student.id), name = \(self.student.name))\n"
}
}

var myarray = NSArray.init(array: [
VipStudent(student: Student(id: 1, name: "jack")),
VipStudent(student: Student(id: 4, name: "rose")),
VipStudent(student: Student(id: 3, name: "claire")),
VipStudent(student: Student(id: 2, name: "rose")),
VipStudent(student: Student(id: 5, name: "sally")),
VipStudent(student: Student(id: 6, name: "nina")),
])

let nameSortDescriptor = NSSortDescriptor.init(keyPath: \VipStudent.student, ascending: true)

print(myarray.sortedArray(using: [nameSortDescriptor]))
```

和之前相比,增加了 `compare` 方法,返回一个枚举 `ComparisonResult` ,表示是升序还是降序,后面的 VipStudent 则直接用 `student` 字段排序即可,这样就可以隐藏排序的内部逻辑和字段,减少耦合, `keyPath` 也不需要写太长的字段了。

这里同样注意 `compare` 函数之前有 `@obj` 标识,因为这是需要给 cocoa 引用的函数,因此需要加上这个标识。



## 用 delegate 触发 column 点击事件

回到 UI 上,那么有没有上面 delegate 回调函数可以监听到点击 column 的事件呢?这个监听回调实际是放在 dataSource delegate 上的,即事件的 delegate 是 NSXXXXDelegate,而关于数据变化的 deleagte 是 NSXXXXDataSource。在 table view 的 dataSource delegate 上就有点击 column 触发数据变化的回调,在这个回调中,我们如之前,设置其 sortDescriptor,然后调用相关的函数获得 sorted 后的数据即可:

```swift
func tableView(
_ tableView: NSTableView,
sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]
) {
self.myArrayController.sortDescriptors = tableView.sortDescriptors
self.students = self.myArrayController.arrangedObjects as! [Student]

tableView.reloadData()
}
```

上面的代码中,鼠标点击 column 就会调用到 `NSTableViewDataSource` 的这个 `tablewView` 回调函数,其中把 `tableView``sortDescriptors` 给到 `myArrayController``sortDescriptors`,然后其`arrangedObjects` 数组对象就是排序后(或 filtered)后的对象,赋值给后台的 students 对象即可。最后 `tableView` 需要 reload 一下触发 UI 变化。

那么我们怎么知道点击了哪个 column呢? 做法是初始化的时候,给这个 column 赋值 sort descriptor,这样,点击的时候,若 column 有设置,则触发上面的回调,一般是在 `viewDidLoad` 中初始化的:

```swift
if let column = self.myTableView.tableColumn(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "age")) {
column.sortDescriptorPrototype = NSSortDescriptor.init(key: "age", ascending: true)
logger.debug("set the sort descriptor successfully")
}
```

上面的代码中,可以看到若我们需要引用一个 UI 对象中的对象,需要使用一个 identifier(即 `NSUserInterfaceItemIdentifier`) 对象,而初始化一个 identifier 对象则需要它有一个 String 类型的 rawValue,其是在属性面板中设置的,比如 age,就是在 storyboard 中,对应的 column 中设置:

![](https://wecache.com/appledev/bind-datasource.png)

前面的代码中,获得 age 对应的 column 对象后,则可以设置它的 `sortDescriptorPrototype` 这样,在点击这个 column 的时候就可以相应对应的回调,然后设置给 array controller 对象的 `sortDescriptors`对象了。

## selector

讲一个和排序无关的但很重要的特性,selector。之前讲过,swift 中通知的方式有两种: 1、delegate;2、在 storyboard 上建立事件响应关系。那么,这里的 selector 算是第三种通知方式。当然,storyboard 上的事件响应关系绑定,底层实现很可能就是 selector。只不过相较于在 storyboard 中通过拖拉建立关系,直接使用 selecor 是在代码中实现。

### 基本思想和用法

本质上,其实就是建立 sender 和 receiver 之间的关系,伪代码:

```swift
class receiver {
func doSomething(sender) {
// ...
}
}

class sender {
var selector: Selector?
}

sender.selector = receiver.doSomething
receiver.perfrom(selector, sender)
```

那么,为什么不直接这么做呢?

```swift
receiver.doSomething(sender)
```

关键之处就在于前面伪代码的第 11 行:写 sender 的人不知道 receiver 到底是谁。比如 sender 是一个 cocoa 的 UI 控件,那么很明显, UI 控件的作者不知道谁会使用这个控件,因此只有使用者,也就是 receiver 才能写出第 11 行这段代码。UI 框架才能有机会调用第 12 行这段代码。

### 实现双击事件响应

现在我们实现双击 table view 的响应。cocoa 给 table view UI 控件提供了一个 `doubleAction``Selector`。即若有谁想监听双击事件,只需要注册到这个 doubleAction即可。

```swift
extension ViewController: NSTableViewDelegate,
NSTableViewDataSource,
NSTextFieldDelegate {
override func viewDidLoad() {
super.viewDidLoad()

// Do any additional setup after loading the view.
self.myTableView.doubleAction = #selector(self.doubleClick)

logger.debug("finish view loading")
}

@objc func doubleClick(sender: NSTableView) {
let s = self.students[sender.selectedRow]
logger.debug("clicked: \(s)")
}
}
```

上面的代码中,在初始化完成后的函数回调 `viewDidLoad` 中设置了 `doubleAction` ,可以看到设置 selector 的时候使用的是 #selector,其会把 `doubleClick` 引用赋值给 `doubleAction`(实际上,是传了 `ViewController` 对象引用和其成员函数`doubleClick`的地址),cocoa 系统框架会保证,在 table view 被双击后回调 `doubleClick`,即在双击的情况 table view 对象在其合适的地方调用(伪代码):

```swift
aController.perform(self.doubleAction, with: self)
```

上面的 aController 即一个抽象的 view controller,self 即 table view 对象。注意 doubleClick 是有 `@objc` 标识的,只要与 cocoa 交互,就必须有这个标识。

另外,selector 的用法并不止于 UI 交互,还可以用于 timer,此处不展开讲。

## 总结与脑图

![](https://wecache.com/appledev/swift-binding4.svg)
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,6 @@
</style><title>如卓</title>
</head>
<body class='typora-export'><div class='typora-export-content'>
<div id='write' class=''><h1 id='这里是如卓的生活数学学习和编程分享'><span>这里是如卓的生活,数学学习和编程分享</span></h1><p><span>社交媒体主页:</span><a href='https://www.douban.com/people/173354647'><span>豆瓣</span></a><span> </span><a href='https://twitter.com/ruzhuo14881'><span>X(Twitter)</span></a></p><ul><li><h1 id='数学学习'><span>数学学习</span></h1></li></ul><p><a href='https://wecache.com/math-pages/lawcosines.html'><span>余弦定理证明</span></a></p><p><a href='https://wecache.com/math-pages/pow.html'><mjx-container class="MathJax" jax="SVG" style="position: relative;"><svg xmlns="http://www.w3.org/2000/svg" width="1.29ex" height="1ex" role="img" focusable="false" viewBox="0 -431 570 442" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" style="vertical-align: -0.025ex;"><defs><path id="MJX-2-TEX-I-1D70B" d="M132 -11Q98 -11 98 22V33L111 61Q186 219 220 334L228 358H196Q158 358 142 355T103 336Q92 329 81 318T62 297T53 285Q51 284 38 284Q19 284 19 294Q19 300 38 329T93 391T164 429Q171 431 389 431Q549 431 553 430Q573 423 573 402Q573 371 541 360Q535 358 472 358H408L405 341Q393 269 393 222Q393 170 402 129T421 65T431 37Q431 20 417 5T381 -10Q370 -10 363 -7T347 17T331 77Q330 86 330 121Q330 170 339 226T357 318T367 358H269L268 354Q268 351 249 275T206 114T175 17Q164 -11 132 -11Z"></path></defs><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><use data-c="1D70B" xlink:href="#MJX-2-TEX-I-1D70B"></use></g></g></g></svg><mjx-assistive-mml unselectable="on" display="inline"><math xmlns="http://www.w3.org/1998/Math/MathML"><mi>π</mi></math></mjx-assistive-mml></mjx-container><script type="math/tex">\pi</script><span> 的推理</span></a></p><p><a href='https://wecache.com/math-pages/limby.html'><span>使用无穷级数计算一道求极限题</span></a></p><p><a href='https://wecache.com/math-pages/derivativex0.html'><span>偶函数在x=0处的导数</span></a></p><p>&nbsp;</p><ul><li><h1 id='苹果开发'><span>苹果开发</span></h1></li></ul><p><a href='https://wecache.com/appledev/appledev-customizedbutton.html'><span>制作一个可以响应down,up事件和大范围点击事件的按钮</span></a></p><p><a href='https://wecache.com/appledev/label-problem.html'><span>解决 Label 对齐时出现漂移的问题</span></a></p><p><a href='https://wecache.com/appledev/swift-binding-show.html'><span>【swift基础】增删查改之显示列表(table view)初始化</span></a></p><p><a href='https://wecache.com/appledev/bindtomore.html'><span>【swift基础】增删查改之删(更多核心概念的理解)</span></a></p><p><a href='https://wecache.com/appledev/swift-editable.html'><span>【swift基础】增删查改之改(用delegate做出做出更多细节)</span></a></p><p>&nbsp;</p></div></div>
<div id='write' class=''><h1 id='这里是如卓的生活数学学习和编程分享'><span>这里是如卓的生活,数学学习和编程分享</span></h1><p><span>社交媒体主页:</span><a href='https://www.douban.com/people/173354647'><span>豆瓣</span></a><span> </span><a href='https://twitter.com/ruzhuo14881'><span>X(Twitter)</span></a></p><ul><li><h1 id='数学学习'><span>数学学习</span></h1></li></ul><p><a href='https://wecache.com/math-pages/lawcosines.html'><span>余弦定理证明</span></a></p><p><a href='https://wecache.com/math-pages/pow.html'><mjx-container class="MathJax" jax="SVG" style="position: relative;"><svg xmlns="http://www.w3.org/2000/svg" width="1.29ex" height="1ex" role="img" focusable="false" viewBox="0 -431 570 442" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" style="vertical-align: -0.025ex;"><defs><path id="MJX-2-TEX-I-1D70B" d="M132 -11Q98 -11 98 22V33L111 61Q186 219 220 334L228 358H196Q158 358 142 355T103 336Q92 329 81 318T62 297T53 285Q51 284 38 284Q19 284 19 294Q19 300 38 329T93 391T164 429Q171 431 389 431Q549 431 553 430Q573 423 573 402Q573 371 541 360Q535 358 472 358H408L405 341Q393 269 393 222Q393 170 402 129T421 65T431 37Q431 20 417 5T381 -10Q370 -10 363 -7T347 17T331 77Q330 86 330 121Q330 170 339 226T357 318T367 358H269L268 354Q268 351 249 275T206 114T175 17Q164 -11 132 -11Z"></path></defs><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><use data-c="1D70B" xlink:href="#MJX-2-TEX-I-1D70B"></use></g></g></g></svg><mjx-assistive-mml unselectable="on" display="inline"><math xmlns="http://www.w3.org/1998/Math/MathML"><mi>π</mi></math></mjx-assistive-mml></mjx-container><script type="math/tex">\pi</script><span> 的推理</span></a></p><p><a href='https://wecache.com/math-pages/limby.html'><span>使用无穷级数计算一道求极限题</span></a></p><p><a href='https://wecache.com/math-pages/derivativex0.html'><span>偶函数在x=0处的导数</span></a></p><p>&nbsp;</p><ul><li><h1 id='苹果开发'><span>苹果开发</span></h1></li></ul><p><a href='https://wecache.com/appledev/appledev-customizedbutton.html'><span>制作一个可以响应down,up事件和大范围点击事件的按钮</span></a></p><p><a href='https://wecache.com/appledev/label-problem.html'><span>解决 Label 对齐时出现漂移的问题</span></a></p><p><a href='https://wecache.com/appledev/swift-binding-show.html'><span>【swift基础】增删查改之显示列表(table view)初始化</span></a></p><p><a href='https://wecache.com/appledev/bindtomore.html'><span>【swift基础】增删查改之删(更多核心概念的理解)</span></a></p><p><a href='https://wecache.com/appledev/swift-editable.html'><span>【swift基础】增删查改之改(用delegate做出做出更多细节)</span></a></p><p><a href='https://wecache.com/appledev/bind-datasource.html'><span>【swift基础】增删查改之排序和selector</span></a></p><p>&nbsp;</p></div></div>
</body>
</html>
2 changes: 2 additions & 0 deletions 如卓.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@
[【swift基础】增删查改之删(更多核心概念的理解)](https://wecache.com/appledev/bindtomore.html)

[【swift基础】增删查改之改(用delegate做出做出更多细节)](https://wecache.com/appledev/swift-editable.html)

[【swift基础】增删查改之排序和selector](https://wecache.com/appledev/bind-datasource.html)

0 comments on commit d90831e

Please sign in to comment.