本文5500
字,大约阅读 15
分钟。 6
月底会停止在 简书
更新,最新的博客地址
5C515420-600F-480D-B9FD-6F40380DA47B
NSTableView
的使用接下来我们需要就是做出这个列表数据,我们可以使用 NSTableView
来做出这个效果。
我们拖拽一个 NSTableView
放在 BaseListView.xib
的试图上面。
DFDA8143-EEE0-43A3-A863-CFCDDCF41B14
设置布局如下。
C99F8039-E33D-47D0-98EA-459D0AF3E336
NSTableView
的 Header
在 Xib
无法正常显示有的时候我们发现 NSTableView
在 Xib
被隐藏了,但是我们显示 Header
的选项是开启的。
我们只要重新勾选
Hader
选项即可显示出来。
2A43EA90-E3FB-400F-A286-11F990C01A24
我们可以看出来我们的列表分为三部分 标题
时间
操作
,我们就设置 NSTableView
有 3
个 Column
。
6E7EFC46-9753-4F4A-B497-1CE2C5FCBCD2
因为名字的长度是不固定的,我们就设置 NSTableView
的第一个 Column
的宽度随着 NSTableView
的宽度变化。
69975981-2F29-4E1B-BCD8-165C1D033148
我们设置其余的 Column
的宽度固定为 100
。
FE8BFBD3-51D7-42C9-92F1-749290356794
1804A265-0CAF-40DE-8F1B-7711A4F3E340
我们的基本结构已经出现了,现在我们要设置 Header
的背景颜色为黑色。
我们关联一下 Xib
上面的 NSTableView
控件。
NSTableView
的 Header
背景颜色。参考资料:
⛔️这里遇到了一个棘手的问题,如果使用
NSTableHeaderView
的子类,在Draw
绘制虽然颜色是设置了,但是标题已经被覆盖掉了。 如果我们使用下面的方法进行设置的话 public func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { if let headerCell = tableColumn?.headerCell { headerCell.drawsBackground = true headerCell.backgroundColor = NSColor.black } return nil } 如果数据源为0就无法设置,并且还有下面的问题。 如果就算有数据也是这样的状态。
76FE2C1F-AA2D-4335-AB34-B0236959D216 中间有间隙并没有完全的黑掉。 我们暂时没有找到合适设置背景颜色的方案,我们暂时使用系统自带的。
1F191849-C907-46C8-B64C-E96BB867D78F
展示列表分为三种样式。
我们设置 NSTableView
的 Cell
的高度为 83
。
我们新建一个类 IconTitleTableCellView
继承与 NSTableCellView
。我们在 IconTitleTableCellView.xib
上面拖拽一个 NSView
继承于 SideMenuItemView
。
布局如下。
E0C1A309-B229-41B0-990B-E4ECED5D0988
9E630761-F95A-4122-BBA0-6196F851AEA1
FC3C5113-C10B-42FD-8554-AF842A147602
我们先暂时设置宽度为 100
,因为标题不知道长度,所以我们需要动态改变长度。
为了设置默认的字体颜色,我们设置normalColor
为 var
的变量。
30CFC0BB-A3E7-4599-A0D5-02F9B82C36BF
NSView
如何 sizeThatFits:
为了让标题显示完全,我们绑定一下设定宽度的约束。
@IBOutlet weak var itemViewWidthConstraint: NSLayoutConstraint!
我们发现 sizeThatFits
并不是 NSView
只有 NSControl
或者子类才可以使用。但是对于我们的需求已经够了。
我们给 SideMenuItemView
写一个 sizeThatFits
方法。
func sizeThatFits(_ size: NSSize) -> NSSize { let labelSize = self.itemTitle.sizeThatFits(size) let sizeWidth = size.height + 10 + labelSize.width + 10 return NSSize(width: sizeWidth, height: size.height) }
我们通过计算出 SideMenuItemView
的宽度。
func configurationView() { let configuration = SideMenuItemConfiguration(title: "这是测试标题", iconHex: "F0F6", hidden: true, selected: false, normalColor: NSColor(red:0.267, green:0.267, blue:0.267, alpha:1.000)) self.itemView.menuItemConfiguration = configuration let size = self.itemView.sizeThatFits(NSSize(width: Int.max, height: 20)) self.itemViewWidthConstraint.constant = CGFloat(size.width) }
此时我们已经正常可以显示标题了。
F1BB28D0-A970-4CC5-BCDA-FD302A0A1E21
App
我们现在的 App
运行,假设一个应用遮挡着我们的应用,我们点击 App
图标是无法再次显示出来 App
面板的。
class AppDelegate: NSObject, NSApplicationDelegate { func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { for window in sender.windows { window.makeKeyAndOrderFront(self) } return true } }
13
此时我们已经可以再次点击 App
图标让界面显示最前面了。
我们再创建一个 DateTableCellView
继承与 NSTableCellView
。
我们拖拽一个 Label
到 DateTableCellView.xib
布局设置如下。
D82D8855-6B02-4B37-A27A-FB329FE29B4E
我们让 cloumn
第二个使用 DateTableCellView
。
我们新建一个类 ActionTableCellView
继承于 NSTableCellView
。
我们在 ActionTableCellView.xib
上面拖拽一个 NSView
继承与 SideMenuItemView
。布局设置如下:
8872C43C-B65D-42A2-A6F5-220CE643BECE
22DC95EE-D8B2-405F-819A-5158E4D0E592
1CF68D9F-647B-4F5A-94F0-11794CEE3002
我们再在右边放置一个按钮,布局如下。
B2AE1F40-E784-4A65-AAE9-EAC2DA474681
25950EC1-D246-42F1-844C-0D21B62A24A5
AF0179FB-FFBE-4C2F-9FAE-546C5C017044
我们 Column
第三个为 ActionTableCellView
。
676001A0-FB2F-4A91-9AD6-57B5120C0F9C
我们设置按钮的 Cloumn
的宽度为 200
。
5353EFD4-E07D-4053-BADB-68D761AB8A43
显示效果似乎还是不足,原因是 80的宽度不足以正常的显示出来。
设置 ActionTableCellView
中按钮的宽度都为 100
。
我们给 DateTableCellView
连接 label
的属性用于设置时间。
@IBOutlet weak var dateLabel: NSTextField!
我们分别给 ActionTableCellView
两个自定义控件设置圆角和背景颜色。
@IBOutlet weak var deleteItemView: SideMenuItemView! @IBOutlet weak var lookItemView: SideMenuItemView!
02DC506D-3B0A-49A7-BAF1-4AC5ED6137DC
我们的列表的样式已经基本上搭建完毕了。
Jekyll
的 Post
文章的列表。我们新建一个 GetPostListApi
类用于获取文章页列表。
我们新建一个类 PostDetail
用于显示文章的信息详情。
class PostDetail: Mappable { var path:String? var url:String? var id:String? var collection:String? var relativePath:String? var draft:Bool = false var categories:[String] = [] var title:String? var date:String? var slug:String? var ext:String? var tags:[String] = [] var layout:String? var httpURL:String? var apiURL:String? var name:String? required init?(map: Map) { } func mapping(map: Map) { path <- map["path"] url <- map["url"] id <- map["id"] collection <- map["collection"] relativePath <- map["relative_path"] draft <- map["draft"] categories <- map["categories"] title <- map["title"] date <- map["date"] slug <- map["slug"] ext <- map["ext"] tags <- map["tags"] layout <- map["layout"] httpURL <- map["http_url"] apiURL <- map["api_url"] name <- map["name"] } }
我没有找到 ObjectMapper直接转成 模型数组的,应该需要自己单独封装添加数组里面,但是却无意发现了这个。
30C53E57-9AE4-43C3-B8E3-E29F9349F8A9
官方建议我们使用 AlamofireObjectMapper
这个库,看了文档确实比较简单,我们就用这个库替换掉 Alamofire
和 ObjectMapper
。
class GetPostListApi { func loadRequest(success:GetPostListApiSuccessCompletionHandle?, failure:GetPostListApiFailureCompletionHandle?) { let URL = "http://localhost:4000/_api/collections/posts/entries" Alamofire.request(URL).responseArray { (response:DataResponse<[PostDetail]>) in if let list = response.value { self.completionHandle(success: success, failure: nil, postList: list, error: nil) } else { self.completionHandle(success: nil, failure: failure, postList: nil, error: response.error) } } } func completionHandle(success:GetPostListApiSuccessCompletionHandle?, failure:GetPostListApiFailureCompletionHandle?, postList:[PostDetail]?, error:Error?) { if let success = success , let postList = postList { success(postList) } else if let failure = failure { failure(error) } } }
写到这里,我们会发现 GetPostListApi
这个类和 GetConfigurationApi
有太多的相似代码。我们不妨创建一个 BaseRequestApi
的请求子类去掉一些多余的代码。
我们现在请求的地址是基于 http://localhost:4000/_api/
这个地址,大部分的 Jekyll
本地都是 4000
端口也可能是其他的。
我们就在 BaseRequestApi
定义一个 URL
的变量默认为 http://localhost:4000/_api/
。
为了能够请求到数据,我们创建一个发起请求的方法。
我们发起请求需要完整的请求地址我们新建一个方法传递 http://localhost:4000/_api/
的后缀。
func URLPath() -> String { return "" }
我们新建一个方法用于拼接完整的请求地址。
func URLFullPath() -> String { guard self.URLPath().characters.count > 0 else { return self.URL } return "\(self.URL)/\(self.URLPath())" }
当后缀是空字符串的时候我们不拼接。
对于 泛型参数
在 OC
和 Swift
一直没有明白过来,也一直掌握精髓,到现在都不会用。
现在要封装请求,对于代理回调应该需要用上 泛型参数
,研究一下。
参考资料:
我们获取数据主要分为两种,一种是对象类型,一种是数组对象类型。
我们新建一个请求协议。
protocol BaseRequestProtocol { associatedtype R:BaseMappable func loadObjectRequest(success:BaseRequestResponseObjectCompletionHandle<R>, failure:BaseRequestFailureCompletionHandle) func loadArrayRequest(success:BaseRequestResponseArrayCompletionHandle<R>, failure:BaseRequestFailureCompletionHandle) }
typealias BaseRequestResponseObjectCompletionHandle<T:BaseMappable> = (_ model:T) -> Void typealias BaseRequestResponseArrayCompletionHandle<T:BaseMappable> = (_ models:[T]) -> Void typealias BaseRequestFailureCompletionHandle = (_ error:Error) -> Void
我们让请求的基类 BaseRequestApi
实现 BaseRequestProtocol
的协议。
class BaseRequestApi<T:BaseMappable>: BaseRequestProtocol
我们实现一下 BaseRequestProtocol
的方法。
func loadObjectRequest(success: @escaping (T) -> Void, failure: @escaping (Error?) -> Void) { Alamofire.request(self.URLFullPath()).responseObject { (response:DataResponse<R>) in guard let value = response.value else { failure(response.error) } success(value) } }
我们返回确保返回的对象存在,当不存在就返回错误信息。
public var error: Error? { return result.error }
因为 error
可能不存在,我们就回调 BaseRequestFailureCompletionHandle
设置可选型。
@escaping
我们在网络请求完成之后进行回调编译器会提示我们加上 @escaping
。关于 @escaping
我们可以参考下面资料。
参考资料: swift3.0中@escaping 和 @noescape 的含义。
看过资料我们可以知道,系统默认是 @noescape
。只要被 @noescape
标记的 闭包
我们都是不需要关心内存管理的。
但是如果在方法执行完毕才执行 闭包
我们就需要用 @escaping
标识,这样系统自动在调用时候提示用户对于直接使用 self
进行内存管理。
func loadArrayRequest(success: @escaping ([T]) -> Void, failure: @escaping BaseRequestFailureCompletionHandle) { Alamofire.request(self.URLFullPath()).responseArray { (response:DataResponse<[R]>) in guard let value = response.value else { failure(response.error) return } success(value) } } func loadObjectRequest(success: @escaping (T) -> Void, failure: @escaping (Error?) -> Void) { Alamofire.request(self.URLFullPath()).responseObject { (response:DataResponse<R>) in guard let value = response.value else { failure(response.error) return } success(value) } }
我们现在的请求基类基本上已经可以正常的运行了,我们已经迫不及待的准备尝试一下。
我们设置 GetConfigurationApi
父类为 BaseRequestApi
。
class GetConfigurationApi: BaseRequestApi<JekyllConfiguration> { override func URLPath() -> String { return "configuration" } }
我们此时子类的代码就变成这么的简单。但是现在有一个问题就是我们配置的数据在子数据里面。
我们需要使用 Path
进行获取,我们就为 BaseRequestApi
设置一个属性可以让外接设置 Path
。
var responseKeyPath:String?
class GetConfigurationApi: BaseRequestApi<JekyllConfiguration> { override func URLPath() -> String { return "configuration" } var responseKeyPath: String? = "content" }
此时我们会受到编译器通知我们的错误。
795C05DA-D33F-4B3E-96E1-7661DE196969
参考资料:
override var responseKeyPath: String? { get { return "content" } set { self.responseKeyPath = newValue } }
我们此时在 ViewController
的请求代码可以设置如下。
let getConfigurationApi = GetConfigurationApi() getConfigurationApi.loadObjectRequest(success: { [weak self] (configuration) in guard let title = configuration.title else { return } self?.navigationBar.blogMenuItem.itemTitle.stringValue = title }, failure: { (error) in })
我们就可以请求到数据了,是不是代码更加的简洁了呢?
我们配置 GetPostListApi
类的代码如下。
class GetPostListApi: BaseRequestApi<PostDetail> { override func URLPath() -> String { return "collections/posts/entries" } }
我们在 PostsView
新写一个方法用于获取文章列表。
func loadData() { let api = GetPostListApi() api.loadArrayRequest(success: { (list:[PostDetail]) in }) { (error) in } }
有了数据我们需要在列表里面展示出来。
BaseListView
作为列表的基类,我们的数据源的结构可能不太一样,我们不可能让我们自定义的数据源传入 BaseListView
。
这个时候我们的 泛型参数
又可以登场了。
我们给 BaseListView
新建一个泛型参数,必须是 BaseMappable
的子类。
class BaseListView<M:BaseMappable>
我们新建一个属性存储 M
数组,当用户重新设置就刷新表格。
var models:[M] = [] { didSet { self.tableView.reloadData() } }
此时我们已经收到了一个错误信息。
参考资料:
查了很多的资料,这个技术难点倒是没有找到合适的方法解决。是因为 @IBOutlet
在 OC
里面使用的运行时,但是运行时不允许 @IBOutlet
绑定一个泛型的对象。
我还尝试过在 BaseListView
使用其他的泛型类间接代理,但是依然无法解决我们的问题。
我现在唯一能够想到的方案就是所谓的协议,用协议声明泛型参数。
我们希望别人继承我们的协议可以把数据转换成我们想要的数据。
protocol BaseListViewDataSource { associatedtype M:BaseMappable ///< 泛型类型 static func converModels(models:[M]) -> [BaseListViewDataModel] ///< 将其他类型对象数组转换成BaseListViewDataModel对象数组 static func converModel(model:M) -> BaseListViewDataModel ///< 将其他类型转换成BaseListViewDataModel对象 } extension BaseListViewDataSource { static func converModels(models:[M]) -> [BaseListViewDataModel] { var datas:[BaseListViewDataModel] = [] for model in models { let data = self.converModel(model: model) datas.append(data) } return datas } } class BaseListViewDataModel { var title:String? ///< 显示标题 var date:String? ///< 显示时间 }
我们 PostDetail
实现我们刚才的协议 BaseListViewDataSource
。
static func converModel(model: PostDetail) -> BaseListViewDataModel { let data = BaseListViewDataModel() data.title = model.title data.date = model.date return data } typealias M = PostDetail
我们在 loadData
方法实现我们刚才的方法。
func loadData() { let api = GetPostListApi() api.loadArrayRequest(success: { (list:[PostDetail]) in self.listView.models = PostDetail.converModels(models: list) }) { (error) in } }
36426B52-B1F6-4C1F-BE2E-77806B868657
我们已经可以发现我们的界面已经可以正常的显示我们数据条数,现在剩下做的就是给我们界面正确的赋值了。
public func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { guard let identifier = tableColumn?.identifier else { return nil } let model = self.models[row] let view = tableView.make(withIdentifier: identifier, owner: self) if let iconTitle = view as? IconTitleTableCellView, let title = model.title { } return view }
我们将 IconTitleTableCellView
中 configurationView
方法修改如下。
func configurationView(title:String) { let configuration = SideMenuItemConfiguration(title: title, iconHex: "F0F6", hidden: true, selected: false, normalColor: NSColor(red:0.267, green:0.267, blue:0.267, alpha:1.000)) self.itemView.menuItemConfiguration = configuration let size = self.itemView.sizeThatFits(NSSize(width: Int.max, height: 20)) self.itemViewWidthConstraint.constant = CGFloat(size.width) }
public func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { guard let identifier = tableColumn?.identifier else { return nil } let model = self.models[row] let view = tableView.make(withIdentifier: identifier, owner: self) if let iconTitle = view as? IconTitleTableCellView, let title = model.title { iconTitle.configurationView(title: title) } return view }
E7D6285D-3D63-4C92-9BFF-B8DE754E48C3
我们的界面就可以正常的显示标题了。同样我们我们赋值一下时间。
if let dateView = view as? DateTableCellView, let date = model.date { dateView.dateLabel.stringValue = date }
97394D1F-3DFC-4939-9111-283A3A18A7CB
我们发现时间显示的格式不正确。我们给 DateTableCellView
写一个转换时间格式的方法。
func configuration(dateString:String) { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd hh:mm:ss zzzz" guard let date = formatter.date(from: dateString) else { return } formatter.dateFormat = "MMM dd,yyyy" self.dateLabel.stringValue = formatter.string(from: date) }
2D3D00B6-EC09-4A1B-9FFA-57298DBCDDBD
我们看到显示竟然是中文六月
,不是我们希望看到的 Jun
。
formatter.locale = Locale(identifier: "en_US")
我们还是按照默认的比较好,我们中文用起来比较方便。
现在要做的就是 删除
查看
两个方法了。我们封装的 SideMenuItemView
控件是无法响应我们的事件的。
NSView
添加 NSGestureRecognizer
时间参考资料:
15D2ADAE-B1B7-495C-AA6A-E7C9677CFE0D
一共有五个 NSGestureRecognizer
的子类可以使用。我们使用 NSClickGestureRecognizer
来处理点击。
func addClick() { let click = NSClickGestureRecognizer(target: self, action:#selector(self.clickAction)) self.addGestureRecognizer(click) } func clickAction() { }
我们的方法无法告诉外接什么时候点击了,如果有一个回调就好了。
typealias SideMenuItemViewClickCompletionHandle = (_ view:SideMenuItemView) -> Void func addClick(completionHandle:@escaping SideMenuItemViewClickCompletionHandle) { self.clickCompletionHandle = completionHandle let click = NSClickGestureRecognizer(target: self, action:#selector(self.clickAction)) self.addGestureRecognizer(click) } func clickAction() { guard let completionHandle = self.clickCompletionHandle else { return } completionHandle(self) } var clickCompletionHandle:SideMenuItemViewClickCompletionHandle?
参考资料:
我们新建一个类 DeletePostDetail
继承与我们 BaseRequestApi
。
class DeletePostDetail: BaseRequestApi<DeletePostDetailResponse> { override func URLPath() -> String { return "collections/posts/{name}" } } class DeletePostDetailResponse: BaseMappable { func mapping(map: Map) { } }
这样是不符合我们请求的标准的,我们的地址需要一个真实的 name
。
我们就给 DeletePostDetail
初始化带一个 name
的参数。
override func URLPath() -> String { return "collections/posts/\(self.name)" } let name:String init(name:String) { self.name = name }
我们删除的请求是 delete
请求,我们底层封装的默认为 Get
请求,我们还需要稍微的修改一下。
func requestMethod() -> HTTPMethod { return HTTPMethod.get } Alamofire.request(self.URLFullPath(), method:self.requestMethod())
这样我们父类默认是 Get
请求,子类如果需要 delete
请求,我们只需要重写这个方法即可。
我们需要点击删除的按钮提示用户是否要删除这个文章,所以我们需要传入一个文章的文件名称。
///BaseListViewDataModel类 var fileName:String? ///< Markdown 的文件名称
///PostDetail类 static func converModel(model: PostDetail) -> BaseListViewDataModel { let data = BaseListViewDataModel() data.title = model.title data.date = model.date data.fileName = model.name return data }
///ActionTableCellView var fileName:String? ///< 用来知道要删除那个文件
NSAlert
对于弹出框我们可以使用 NSAlert
控件
参考资料:
在
ActionTableCellView
类增加代码如下
self.deleteItemView.addClick { (view) in guard let fileName = self.fileName, let window = NSApplication.shared().keyWindow else { return } let alert = NSAlert() alert.messageText = "确定要删除\(fileName)" alert.beginSheetModal(for: window, completionHandler: { (response) in }) }
在
BaseListView
的public func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?
方法 增加代码如下 if let actionView = view as? ActionTableCellView { actionView.fileName = model.fileName }
2C2B5648-FEF8-4467-B32B-121AE71C5ABF
此时只有一个确定,没有取消按钮,到时候误删就 GG 了。
self.deleteItemView.addClick { (view) in guard let fileName = self.fileName, let window = NSApplication.shared().keyWindow else { return } let alert = NSAlert() alert.messageText = "确定要删除\(fileName)" alert.addButton(withTitle: "删除") alert.addButton(withTitle: "取消") alert.beginSheetModal(for: window, completionHandler: { (response) in }) }
当我们点击删除按钮我们需要执行删除的请求。
if response == NSAlertFirstButtonReturn { self.deletePost(fileName: fileName) }
func deletePost(fileName:String) { let api = DeletePostDetail(name: fileName) api.loadObjectRequest(success: { (response) in }) { (error) in } }
当我们删除完毕我们需要刷新我们的表格,我就给 ActionTableCellView
新写一个回调用于删除完毕更新表格的内容。
typealias ActionTableCellViewDeleteSuccessCompletionHandle = (_ view:ActionTableCellView) -> Void
var deleteSuccessCompletionHandle:ActionTableCellViewDeleteSuccessCompletionHandle?
func deletePost(fileName:String) { let api = DeletePostDetail(name: fileName) api.loadObjectRequest(success: { (response) in guard let completionHandle = self.deleteSuccessCompletionHandle else { return } completionHandle(self) }) { (error) in } }
我们发现我们的表格并没有刷新,因为对于 Delete
请求是没有任何信息回调的。我们只用知道状态吗是200
就可以知道成功了。
func loadObjectRequest(success: @escaping (T?) -> Void, failure: @escaping (Error?) -> Void) { Alamofire.request(self.URLFullPath(),method:self.requestMethod()).responseObject(keyPath:self.responseKeyPath) { (response:DataResponse<R>) in guard let code = response.response?.statusCode, code == 200 else { failure(response.error) return } success(response.value) } }
当我们当识别状态吗为 200
果然成功了。
OSX
平台代码打开一个地址我们做完 删除
功能,还剩下一个 查看
功能,当用户点击 查看
按钮。
我们给 ActionTableCellView
新增一个方法用于配置 查看
按钮的点击方法。
func addLookView() { self.lookItemView.addClick { (view) in guard let urlString = self.httpURL, let url = URL(string: urlString) else { return } NSWorkspace.shared().open(url) } }
界面上面的搜索功能,说简单不简单,说复杂不复杂。那要你需要实现的搜索到什么程度。
参考资料:
我们做先做一个简单版本的,就直接匹配就好了。
我们给 BaseListView
增加一个搜索过滤之后的数组。
private var filterModels:[BaseListViewDataModel] = []
我们用 filterModels
来作为我们暂时数据的数据源。
我们给 ContentHeaderValue1
关联一下搜索输入框。
@IBOutlet weak var searchFiled: NSTextField!
我们设置一下 searchFiled
代理对象为 BaseListView
。
@IBOutlet weak var header: ContentHeader! { didSet { guard let headerValue1 = self.header.headerContent as? ContentHeaderValue1 else { return } headerValue1.searchFiled.delegate = self } }
经过研究如果要监听输入框文字变化需要用通知。我们声明一个方法监听输入框通知变化。
func searchFiledTextChanged(notification:Notification) { guard let filed = notification.object as? NSTextField else { return } guard let headerValue1 = self.header.headerContent as? ContentHeaderValue1 else { return } guard filed == headerValue1.searchFiled else { return } }
我们新建一个方法处理字符串改变过滤数据源。
func filterDataModels(filter:String) { self.filterModels.removeAll() if filter.characters.count == 0 { self.filterModels.append(contentsOf: self.models) } else { for model in self.models { if let _ = model.title?.range(of: filter) { self.filterModels.append(model) } } } self.tableView.reloadData() }
我们在 searchFiledTextChanged
方法里面调用我们刚才的过滤的方法。
func searchFiledTextChanged(notification:Notification) { guard let filed = notification.object as? NSTextField else { return } guard let headerValue1 = self.header.headerContent as? ContentHeaderValue1 else { return } guard filed == headerValue1.searchFiled else { return } self.filterDataModels(filter: filed.stringValue) }
因为我们初始化的时候,我们还没有输入任何的搜索字符串,设置 models
我们要初始化我们的 filterDataModels
数组。
我们新建一个方法用于初始化 filterDataModels
。
func settingFilterModels() { guard let headerValue1 = self.header.headerContent as? ContentHeaderValue1 else { return } let filterText = headerValue1.searchFiled.stringValue self.filterDataModels(filter: filterText) }
我们在设置 models
时候进行重新设置 filterModels
。
我们在 header
的方法 didSet
进行注册通知。
201706201728
我们的搜索功能已经可以用了。
deinit
方法我们在 Objective-C
开发里面经常在 dealloc
注销通知,减少资源消耗。我们在 Swift
里面可以使用 deinit
函数。
参考资料:
deinit { NotificationCenter.default.removeObserver(self) }
刚才无意间发现下面系统自带的方法
extension NSObject { open func controlTextDidBeginEditing(_ obj: Notification) open func controlTextDidEndEditing(_ obj: Notification) open func controlTextDidChange(_ obj: Notification) }
这是 NSObject
的扩展,我们去掉我们注册的通知,用 controlTextDidChange
方法试一下。
参考资料:
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句