Jekyll-Admin-Mac-列表

本文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方法

5C515420-600F-480D-B9FD-6F40380DA47B

關于 NSTableView的使用

接下來我們需要就是做出這個列表數據,我們可以使用 NSTableView來做出這個效果。

我們拖拽一個 NSTableView放在 BaseListView.xib的試圖上面。

DFDA8143-EEE0-43A3-A863-CFCDDCF41B14

設置布局如下。

C99F8039-E33D-47D0-98EA-459D0AF3E336

解決 NSTableViewHeaderXib無法正常顯示

有的時候我們發現 NSTableViewXib被隱藏了,但是我們顯示 Header的選項是開啟的。

我們只要重新勾選 Hader選項即可顯示出來。

2A43EA90-E3FB-400F-A286-11F990C01A24

我們可以看出來我們的列表分為三部分 標題 時間 操作,我們就設置 NSTableView3Column

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控件。

設置 NSTableViewHeader背景顏色。

參考資料:

??這里遇到了一個棘手的問題,如果使用 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

展示列表分為三種樣式。

  • 第一種是圖標加上文字并且是可以點擊的
  • 第二種是文字只做展示
  • 第三種是兩個按鈕

我們設置 NSTableViewCell的高度為 83

我們新建一個類 IconTitleTableCellView繼承與 NSTableCellView。我們在 IconTitleTableCellView.xib上面拖拽一個 NSView繼承于 SideMenuItemView

布局如下。

E0C1A309-B229-41B0-990B-E4ECED5D0988
9E630761-F95A-4122-BBA0-6196F851AEA1
FC3C5113-C10B-42FD-8554-AF842A147602

我們先暫時設置寬度為 100,因為標題不知道長度,所以我們需要動態改變長度。

為了設置默認的字體顏色,我們設置normalColorvar的變量。

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

我們拖拽一個 LabelDateTableCellView.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

我們的列表的樣式已經基本上搭建完畢了。

請求 JekyllPost文章的列表。

獲取 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這個庫,看了文檔確實比較簡單,我們就用這個庫替換掉 AlamofireObjectMapper

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())"
}

當后綴是空字符串的時候我們不拼接。

關于泛型參數

對于 泛型參數OCSwift一直沒有明白過來,也一直掌握精髓,到現在都不會用。

現在要封裝請求,對于代理回調應該需要用上 泛型參數,研究一下。

參考資料:

我們獲取數據主要分為兩種,一種是對象類型,一種是數組對象類型。

我們新建一個請求協議。

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

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

此時我們已經收到了一個錯誤信息。

參考資料:

查了很多的資料,這個技術難點倒是沒有找到合適的方法解決。是因為 @IBOutletOC里面使用的運行時,但是運行時不允許 @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
}

我們將 IconTitleTableCellViewconfigurationView方法修改如下。

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

    })
}

BaseListViewpublic 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方法試一下。

參考資料:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容

  • 簡書從6月底停止更新,請前往關注最新的博客地址。 君賞博客 本文5600字大約需要花費15分鐘閱讀,文章圖片過多,...
    君賞閱讀 704評論 0 1
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,761評論 25 708
  • 本博客將在 6月底停止在 簡書的更新,全新的博客地址請點擊前往-> 君賞博客 本文章文字大約 4500字,大概花費...
    君賞閱讀 1,899評論 0 3
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,198評論 30 471
  • 隨著現代科技的飛速發展,想做聞名于全球的人物太難又太容易。而當我在自己的世界里行走時,請不要用你的世界來看待我的選...
    panxiubin閱讀 209評論 0 0