本文5500
字,大約閱讀 15
分鐘。 6
月底會停止在 簡書
更新,最新的博客地址
關于 NSTableView的使用
解決 NSTableView的 Header在 Xib無法正常顯示
設置 NSTableView的 Header背景顏色。
NSView如何 sizeThatFits:
再次激活 App
請求 Jekyll的 Post文章的列表。
關于泛型參數
關于 @escaping
精簡請求子類
cannot override with a stored property
請求文章列表
@IBOutlet Property cannot have non-‘@objc’ class type
中文系統格式化時間顯示英文字符
給 NSView添加 NSGestureRecognizer時間
刪除文章
關于 NSAlert
OSX平臺代碼打開一個地址
deinit方法
關于 NSTableView
的使用
接下來我們需要就是做出這個列表數據,我們可以使用 NSTableView
來做出這個效果。
我們拖拽一個 NSTableView
放在 BaseListView.xib
的試圖上面。
設置布局如下。
解決 NSTableView
的 Header
在 Xib
無法正常顯示
有的時候我們發現 NSTableView
在 Xib
被隱藏了,但是我們顯示 Header
的選項是開啟的。
我們只要重新勾選
Hader
選項即可顯示出來。
我們可以看出來我們的列表分為三部分 標題
時間
操作
,我們就設置 NSTableView
有 3
個 Column
。
因為名字的長度是不固定的,我們就設置 NSTableView
的第一個 Column
的寬度隨著 NSTableView
的寬度變化。
我們設置其余的 Column
的寬度固定為 100
。
我們的基本結構已經出現了,現在我們要設置 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中間有間隙并沒有完全的黑掉。
我們暫時沒有找到合適設置背景顏色的方案,我們暫時使用系統自帶的。
展示列表分為三種樣式。
- 第一種是圖標加上文字并且是可以點擊的
- 第二種是文字只做展示
- 第三種是兩個按鈕
我們設置 NSTableView
的 Cell
的高度為 83
。
我們新建一個類 IconTitleTableCellView
繼承與 NSTableCellView
。我們在 IconTitleTableCellView.xib
上面拖拽一個 NSView
繼承于 SideMenuItemView
。
布局如下。
我們先暫時設置寬度為 100
,因為標題不知道長度,所以我們需要動態改變長度。
為了設置默認的字體顏色,我們設置normalColor
為 var
的變量。
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)
}
此時我們已經正常可以顯示標題了。
再次激活 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
}
}
此時我們已經可以再次點擊 App
圖標讓界面顯示最前面了。
我們再創建一個 DateTableCellView
繼承與 NSTableCellView
。
我們拖拽一個 Label
到 DateTableCellView.xib
布局設置如下。
我們讓 cloumn
第二個使用 DateTableCellView
。
我們新建一個類 ActionTableCellView
繼承于 NSTableCellView
。
我們在 ActionTableCellView.xib
上面拖拽一個 NSView
繼承與 SideMenuItemView
。布局設置如下:
我們再在右邊放置一個按鈕,布局如下。
我們 Column
第三個為 ActionTableCellView
。
我們設置按鈕的 Cloumn
的寬度為 200
。
顯示效果似乎還是不足,原因是 80的寬度不足以正常的顯示出來。
設置 ActionTableCellView
中按鈕的寬度都為 100
。
我們給 DateTableCellView
連接 label
的屬性用于設置時間。
@IBOutlet weak var dateLabel: NSTextField!
我們分別給 ActionTableCellView
兩個自定義控件設置圓角和背景顏色。
@IBOutlet weak var deleteItemView: SideMenuItemView!
@IBOutlet weak var lookItemView: SideMenuItemView!
我們的列表的樣式已經基本上搭建完畢了。
請求 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直接轉成 模型數組的,應該需要自己單獨封裝添加數組里面,但是卻無意發現了這個。
官方建議我們使用 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"
}
此時我們會受到編譯器通知我們的錯誤。
cannot override with a stored property
參考資料:
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 Property cannot have non-‘@objc’ class type
此時我們已經收到了一個錯誤信息。
參考資料:
查了很多的資料,這個技術難點倒是沒有找到合適的方法解決。是因為 @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 }
}
我們已經可以發現我們的界面已經可以正常的顯示我們數據條數,現在剩下做的就是給我們界面正確的賦值了。
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
}
我們的界面就可以正常的顯示標題了。同樣我們我們賦值一下時間。
if let dateView = view as? DateTableCellView, let date = model.date {
dateView.dateLabel.stringValue = date
}
我們發現時間顯示的格式不正確。我們給 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)
}
我們看到顯示竟然是中文六月
,不是我們希望看到的 Jun
。
中文系統格式化時間顯示英文字符
formatter.locale = Locale(identifier: "en_US")
我們還是按照默認的比較好,我們中文用起來比較方便。
現在要做的就是 刪除
查看
兩個方法了。我們封裝的 SideMenuItemView
控件是無法響應我們的事件的。
給 NSView
添加 NSGestureRecognizer
時間
參考資料:
一共有五個 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 }
此時只有一個確定,沒有取消按鈕,到時候誤刪就 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
進行注冊通知。
我們的搜索功能已經可以用了。
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
方法試一下。
參考資料: