本博客將在 6
月底停止在 簡書
的更新,全新的博客地址請點擊前往-> 君賞博客
本文章文字大約 4500
字,大概花費 10
分鐘閱讀。本文章設計的圖片比較多,流量黨慎入!。
本教程屬于 制作 Jekyll-Admin-Mac
的教程系列,可以關于 君賞博客關注以后教程。
本文章一些知識點,不感興趣可以提前關閉!
-
Curl
下載命令 - 使用
Xib
- 使用
Autolayout
-
OSX
開發 -
@IBDesignable
和@IBInspectable
draw()
- 自定義
NSView
的背景顏色 - 使用
Xib
加載試圖 - 設置
autoresizingMask
屬性 - 修改
NSWindow
的最小顯示區域 - 去掉
NSTableView
的邊框 -
NSTableView
使用View Base
試圖 - 在
OSX
使用 `font-awesome - 如何在
Swift3
獲取類名字符串 - 解決
Cocoapods
不能使用IBDeisgnable
- 面向對象思想
?為什么要開發
Jekyll-Admin-Mac
?因為接觸到使用
Jekyll
構建博客十分的方便,但是Jekyll-Admin
里面的功能又差強人意。如果修改
Jekyll-Admin
里面的源碼代價是巨大的,不如用自己擅長的語言來寫,正好還有自動生成的 API 可以用。對于
Jekyll-Admin-Mac
的UI
我們采用網頁的配色即可。
獲取 Jekyll-Admin
的圖標。
經過網絡抓包,我們抓取到 Jekyll-Admin
的圖標是經過連接
../admin/847c038a8202754b465604459e16715d.png來獲取的。
我們直接保存到本地,在工程里面使用。
我們新建一個 Mac
的工程保存到本地名字叫做- Jekyll-Admin-Mac
。
我們打開終端 terminal.app
cd /Users/用戶名稱/Downloads
curl -o jekyll-admin-logo.png ../admin/847c038a8202754b465604459e16715d.png
??這里我們用到了
curl
命令,更多的想知道curl
命令可以去谷歌和百度。
設置左側的 Logo
我們拖拽文件 jekyll-admin-logo.png
到工程 Assets.xcassets
。
左邊功能菜單我們設置寬度為 205
。
我們新建一個 SideMenuView
繼承 NSView
。
現在 NSView
創建的時候不允許使用 XIB
,我們自己新建一個 Xib
。
名字叫做 SideMenuView.xib
。
我們設置 SideMenuView
的大小為 205x1000
。寬度是固定的,但是高度不固定,我們使用自動布局。
最上線顯示 Logo
的地方大小為 205x75
。我們采用 NSImageView
。我們采用如下的布局。
- 左側和父試圖對其
- 上側和父試圖對其
- 寬度205
- 高度75
??我們發現我們的圖片是正常的顯示出來了,但是背景顏色無法顯示。那是因為在
OSX
開發和iOS
不太一樣。對于正常的NSView
,NSImageView
是無法進行設置背景顏色的。
@IBDesignable和@IBInspectable
為了可以自定義背景顏色,我們創建一個繼承 NSView
的子類 BaseView
。
@IBDesignable class BaseView: NSView {
}
我們在 BaseView
新增一個屬性。
@IBInspectable var backgroundColor:NSColor! = NSColor.white {
didSet {
self.needsToDraw(self.bounds)
}
}
自定義draw()
我們在 func draw(_ dirtyRect: NSRect)
方法里面進行填充顏色。
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
self.backgroundColor.setFill()
NSRectFill(dirtyRect)
}
關于怎么在 XIB
及時預覽界面可以參考下面的連接。
在Xcode6中使用IBDesignable創建自定義控件(翻譯)
我們設置 NSView
為繼承與 BaseView
背景顏色試圖。我們設置背景顏色為 rgb343434
。
布局參考之前 NSImageView
的布局。
我們把剛才的 NSImageView
作為子試圖,布局設置下面。
我們拖拽 NSView
一個新的試圖放置在 Main.storyboard-ViewController-View
上面。
我們設置剛才新建的 NSView
繼承我們新建的類 SideMenuView
。
使用 Xib 加載試圖
到這里,我們新建的 NSView
無法正常的顯示出來。那是因為我們在 XIB
進行初始化的時候走的是方法是
public init?(coder: NSCoder)
并且 SideMenuView
這個類不知道從哪里加載試圖。關于如何進行加載自定義的 XIB
可以參考這一篇文章。
我們新增一個綁定的屬性
@IBOutlet weak var view: BaseView!
設置 Xib
的 File's Owner
類為 SideMenuView
,綁定 view
。
我們在 SideMenuView
類里面新增一個方法,用來加載自定義的試圖。
func loadXibView() {
Bundle.main.loadNibNamed("SideMenuView", owner: self, topLevelObjects: nil)
self.view.frame = self.bounds
self.addSubview(self.view)
}
我們重寫 init?(coder: NSCoder)
方法。
required init?(coder: NSCoder) {
super.init(coder: coder)
self.loadXibView()
}
當我們再次的運行,我們自定義 Xib
的界面已經可以出現了。
但是到目前來說我們幾乎達到顯示 Logo
,但是我們的背景顏色設置白色不是我們所希望的,我們設置默認的為透明顏色。
我們還發現我們我們的試圖并沒有達到我們設置約束的大小。
我們可以點擊 Xcode
查看試圖層次
我們看出SideMenuView
試圖的 View
并沒有達到我們隨著父試圖變化而變化。
設置 autoresizingMask
屬性
我們設置一下 autoresizingMask
屬性。關于 autoresizingMask
一些用法可以看一下下面的資料。
iOS開發-自動布局之autoresizingMask使用詳解(Storyboard&Code)
我們設置高度自適應。
self.view.autoresizingMask = .viewHeightSizable
我們設置 SideMenuView
的view
的背景為rgb515151,方便我們進行查看。
我的試圖已經能隨著變化自動改變高度了。
這個時候我們還發現了一個問題,我們的 Window
可以壓縮寬度最小,這樣左邊的側欄已經擋著了。
修改 Window
的最小顯示區域
我們可以通過下面設置 window
的最小值。
這樣我們可以讓 Window
可以保持最小的尺寸是 600x500
。
我們修改 SideMenuView
的 view
的試圖背景顏色為 RGB444444
。
上面的圖可以明顯看出來是需要封裝控件的,但是封裝完畢是試圖依次疊加還是使用 NSTableView
。試圖依次疊加不利于擴展,我們采用 NSTableView
。
我們拖拽一個 NSTableView
的控件放置在 SideMenuView
剩余的位置。布局如下。
如圖所示的版本還不能達到我們的要求,有了標題,而且多了一個 Column
。
我們取消顯示 Header
和設置只有一個 Cloumn
我們發現我們剩下的只有一個 Column
的寬度只有 116
并不是全屏顯示的。
去掉 NSTableView
的邊框
我們設置寬度為 205
。
我們現在發現了一個問題,我們本來有205
的寬度的。但是我們現在只能設置最大200
,并且預覽顯示是全屏顯示了。
我們在 NSTableView
的屬性里面看到這個。
我們的寬度留3
大小。但是就算去掉了3
還是只有 203
,剩下的 2
跑到那里去了。
我們觀察到 NSTableView
的父試圖已經是 203
的寬度了,既然這樣我們就默認使用 200
;
可以設置最外層
Border
為沒有即可。
我們發現我們剛才創建的 NSTableView
顯示的背景顏色是白色的,我們可以關閉 NSScrollView
的繪制背景顏色和設置 NSTableView
的背景顏色為透明即可。
雖然系統的 NSButton
是符合圖片加文字效果的,但是卻無法修改文字的顏色。
我們創建一個類繼承與 BaseView
名字叫做 SideMenuItemView
。
我們按照上文所描述的方法創建一個 Xib
文件。
我們設置 Xib
里面的 NSView
的寬度為 205
,高度為 49
。其實我們這個寬度和高度會隨著改變的。
我們在最左側放置一個 NSImageView
布局如下。
我們在 NSImageView
的右側放置一個 NSTextFiled
的 Label
,布局如下。
我們設置右側 Label
的字體顏色為 ebdac1
,字體大小為 17px
。
我們利用 Xib
創建下面的關聯屬性。
@IBOutlet weak var iconImageView: NSImageView!
@IBOutlet weak var itemTitle: NSTextField!
我們按照之前寫 SideMenuView
試圖的方法把 Xib
的對象加載進來,具體的方法可以參考上面。
我們設置 View
的試圖按照寬度和高度自動約束。
self.view.autoresizingMask = [.viewWidthSizable,.viewHeightSizable]
這里說明一點,可選型不是如
Objective-C
那樣一般用|
連接,多個需要放在數組里面。
我們需要的控件已經封裝好了,我們現在要做的就是設置 NSTableView
的樣式為 View Base
。
我們刪除自動生成的試圖,拖拽一個 NSView
到 到 Column
下面。我們關聯 NSTableView
的數據源。
我們在 SideMenuView
類里面實現 NSTableView
的數據源方法。
我們通過界面查看器可以看的出來,第一個 Row
已經出來了,但是卻因為沒有設置無法顯示。
在 OSX
使用 font-awesome
左側的圖片網站采用 font-awesome
框架。 OSX
我們使用 FontAwesomeIconFactory
框架。
使用
Cocoapods
我強烈的建議使用 官方的App
使用
我們設置剛才我們封裝的 SideMenuItemView
的 NSImageView
的子類為 NIKFontAwesomeImageView
解決 Cocoapods
不能使用 IBDeisgnable
我們在使用
Cocoapods
時候不能使用IBDeisgnable
的解決辦法。post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['CONFIGURATION_BUILD_DIR'] = '$PODS_CONFIGURATION_BUILD_DIR' end end end
很不幸的是在另外的 Xib
使用 SideMenuItemView
報下面的錯誤。
我們在 Debug IBDeisgnable
時候發現抱錯下面的代碼。
因為我們綁定是對象屬于 !
類型,但是我們此時還不存在這個變量。故而強行當做存在的使用崩潰了。
到目前為止,我不清楚這個對象沒有初始化是為什么導致的。但是只是在 Xib
進行初始化 IBDeisgnable
抱錯,但是可以正常運行的。
但是這樣可能不能滿足我的要求,我們盡量解決就解決。我們之前的方法里面可以接受一個數組的指針。
我們看看數組里面元素如何。
var views:NSArray = NSArray()
Bundle.main.loadNibNamed("SideMenuItemView", owner: self, topLevelObjects: &views)
數組里面是有元素的,我們嘗試從這里面的元素獲取試一下。
func loadXibView() {
guard let xibView = self.getXibView(nibName: "SideMenuItemView") else {
return
}
xibView.autoresizingMask = [.viewWidthSizable,.viewHeightSizable]
xibView.frame = self.bounds
self.addSubview(xibView)
}
func getXibView(nibName:String) -> NSView? {
var views:NSArray = NSArray()
Bundle.main.loadNibNamed(nibName, owner: self, topLevelObjects: &views)
var xibView:NSView?
for any in views {
guard let view = any as? NSView else {
continue
}
xibView = view
}
return xibView
}
我們發現之前報的錯誤果然消失了。我們可以采用這一種方式來加載試圖,我們可以封裝一下,方便我們用。
NSStringFromClass(type(of:self))
extension NSView {
func loadXibView() {
guard let xibView = self.getXibView(nibName: NSStringFromClass(type(of:self))) else {
return
}
xibView.autoresizingMask = [.viewWidthSizable,.viewHeightSizable]
xibView.frame = self.bounds
self.addSubview(xibView)
}
func getXibView(nibName:String) -> NSView? {
var views:NSArray = NSArray()
Bundle.main.loadNibNamed(nibName, owner: self, topLevelObjects: &views)
var xibView:NSView?
for any in views {
guard let view = any as? NSView else {
continue
}
xibView = view
}
return xibView
}
}
但是發現竟然加載不出來任何數據,原來我們發現自動生成的類名帶有工程前綴。
"Jekyll_Admin_Mac.SideMenuView"
我們可以采用分割字符串使用最后一個。
我們將 SideMenuItemView
改成繼承與 NIKFontAwesomeImageView
。
NIKFontAwesomeImageView
的IBDeisgnable
不能在Xib
預覽的。
我們設置 NIKFontAwesomeImageView
屬性如下。
icon Hex
:f02d
-
Size
:17
生成的圖片是正方形,并不能和網站的樣式可以設置寬度和高度。
Color
:EBDAC1
我們運行一下發現已經可以正常的運行了。
面向對象設計
我們配置一下 NSTableView
的數據源如下:
let menuItemDict = [
"文章":"F02D",
"頁面":"F15C",
"數據":"F1C0",
"文件":"F15B",
"配置":"F013",
]
我們設置一下 NSTableView
數據代理。
public func numberOfRows(in tableView: NSTableView) -> Int {
return menuItemDict.keys.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
guard let view = tableView.make(withIdentifier: "SideMenuItemView", owner: self) as? SideMenuItemView else {
return nil
}
view.itemTitle.stringValue = Array(menuItemDict.keys)[row]
view.iconImageView.iconHex = Array(menuItemDict.values)[row] as NSString
return view
}
??對于
Swift3
里面的Dictionary
的屬性Keys
無法作為正常的Array
使用,我們需要用Array()
對其進行初始化。
上圖是我們運行起來的效果。但是呢和我們網頁的看起來還是有寫差別的。
我們在 SideMenuItemView.xib
上面的底部添加一條線。布局如下:
線繼承與 BaseView
,我們設置顏色為 424242
。
雖然線是出來了,但是我們不想讓全部出現。
我們在 SideMenuItemView
關聯剛才的線。
@IBOutlet weak var lineView: BaseView!
我們修改配置如下。
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
guard let view = tableView.make(withIdentifier: "SideMenuItemView", owner: self) as? SideMenuItemView else {
return nil
}
view.itemTitle.stringValue = Array(menuItemDict.keys)[row]
let values = Array(menuItemDict.values)[row]
if let hexString = values[0] as? NSString {
view.iconImageView.iconHex = hexString
}
if let hidden = values[1] as? Bool {
view.lineView.isHidden = hidden
}
return view
}
??因為字典的取值是無序的,所以我們這樣的寫法會導致我們的顯示出現問題。
我們修改我們的數據源為一個 Array
數組。
let menuItems = [
["文章", "F02D", false],
["頁面", "F15C", true],
["數據", "F1C0", false],
["文件", "F15B", true],
["配置", "F013", false],
]
我們需要修改對應數據賦值。
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
guard let view = tableView.make(withIdentifier: "SideMenuItemView", owner: self) as? SideMenuItemView else {
return nil
}
let values = menuItems[row]
guard values.count == 3 else {
return nil
}
if let title = values[0] as? String {
view.itemTitle.stringValue = title
}
if let hexIcon = values[1] as? NSString {
view.iconImageView.iconHex = hexIcon
}
if let hidden = values[2] as? Bool {
view.lineView.isHidden = !hidden
}
return view
}
我們運行此時顯示如下。
我們給 NSTableView
綁定一個方法事件。
@IBAction func didClickRow(_ sender: NSTableView) {
}
我們給 NSTableView
新增一個屬性是否被選中。然而現在一個問題已經出現,現在這么多的配置需要配置豈不是很麻煩。
這就涉及到面向對象
思想,但是我們可以在 Swift
中使用 Struct
作為我們的配置數據源。
struct SideMenuItemConfiguration {
let title:String ///< 標題
let iconHex:String ///< icon 的十六進制字符串
let hidden:Bool ///< 是否隱藏底部線
let selected:Bool ///< 是否被選中
}
我們修改我們的數據源:
let menuItems = [
// ["文章", "F02D", false],
// ["頁面", "F15C", true],
// ["數據", "F1C0", false],
// ["文件", "F15B", true],
// ["配置", "F013", false],
SideMenuItemConfiguration(title: "文章", iconHex: "F02D", hidden: true, selected: false),
SideMenuItemConfiguration(title: "頁面", iconHex: "F15C", hidden: false, selected: false),
SideMenuItemConfiguration(title: "數據", iconHex: "F1C0", hidden: true, selected: false),
SideMenuItemConfiguration(title: "文件", iconHex: "F15B", hidden: false, selected: false),
SideMenuItemConfiguration(title: "配置", iconHex: "F013", hidden: true, selected: false),
]
再次修改我們的賦值代碼。
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
guard let view = tableView.make(withIdentifier: "SideMenuItemView", owner: self) as? SideMenuItemView else {
return nil
}
let configuration = menuItems[row]
view.itemTitle.stringValue = configuration.title
view.iconImageView.iconHex = configuration.iconHex as NSString
view.lineView.isHidden = configuration.hidden
return view
}
我們的代碼比之前要精簡一些。
我們在點擊 NSTableView
點擊方法獲取選中的 Row
,之后讓選中數據源狀態被選中,其他取消選中。
@IBAction func didClickRow(_ sender: NSTableView) {
let row = sender.selectedRow
for (index, configuration) in menuItems.enumerated() {
configuration.selected = index == row
}
sender.reloadData()
}
??這段代碼會被抱錯,因為我們修改了被
let
標記的常量,我們修改成var
即可。而且我們
enumerated()
出來的竟然是也是Let
標記的,我們用var
標記。81D12FF2-A02E-496A-80D9-BC3994745199
我們設置選中的顏色為 ff9900
。默認的顏色為 EBDAC1
。
我們在 SideMenuItemConfiguration
新增默認顏色和選中顏色的屬性。
let normalColor:NSColor = NSColor(red:1.000, green:0.600, blue:0.000, alpha:1.000) ///< 默認狀態顏色
let selectedColor:NSColor = NSColor(red:0.922, green:0.855, blue:0.757, alpha:1.000) ///< 選中的顏色
我們設置默認值這樣 之前的代碼也可以 正常的編譯通過。
我們需要根據選中狀態設置圖標的顏色還有文字的顏色,這樣就要增加一下邏輯。這些都是修改 SideMenuItemView
類的內容,為啥不采用賦值,讓 SideMenuItemView
內部處理呢?
我們說做就做。
var menuItemConfiguration:SideMenuItemConfiguration? {
didSet {
guard let configuration = self.menuItemConfiguration else {
return
}
self.itemTitle.stringValue = configuration.title
self.iconImageView.iconHex = configuration.iconHex as NSString
self.lineView.isHidden = configuration.hidden
let color = configuration.selected ? configuration.selectedColor : configuration.normalColor
self.iconImageView.color = color
self.itemTitle.textColor = color
}
}
我們給 SideMenuItemView
類新增 menuItemConfiguration
屬性,當給這個屬性設置值的時候我們做出對應處理。
我們現在可以給我們 NSTableView
的代碼精簡如下:
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
guard let view = tableView.make(withIdentifier: "SideMenuItemView", owner: self) as? SideMenuItemView else {
return nil
}
let configuration = menuItems[row]
view.menuItemConfiguration = configuration
return view
}
但是我們運行起來,卻發現全部都是選中的顏色,原來是我們默認顏色和選中顏色配置反了導致,我們修改過來即可。
此時我們的初始化配置恢復了正常,但是我們點擊了沒有任何的變化。讓我們找一下出現這種現象原因是怎么導致的。
??因為結構體沒有被引用,所以便利出來的臨時變量屬于一個新的地址。我們需要修改臨時變量之后替換掉之前數組里面的。
@IBAction func didClickRow(_ sender: NSTableView) {
let row = sender.selectedRow
for (index, var configuration) in menuItems.enumerated() {
configuration.selected = index == row
menuItems[index] = configuration
}
sender.reloadData()
}
??此時需要注意的是我們需要修改我們的
menuItems
為var
類型。
此時我們的效果已經達到了,我們覺得默認啟動顯示的第一個界面是0元素。
我們綁定界面的元素 NSTableView
到 SideMenuView
。
@IBOutlet weak var tableView: NSTableView!
我們把 didClickRow
邏輯封裝成下面的對象。
func changeTabeleViewState(row:Int, tableView:NSTableView) {
for (index, var configuration) in menuItems.enumerated() {
configuration.selected = index == row
menuItems[index] = configuration
}
tableView.reloadData()
}
我們修改 didClickRow
的調用。
@IBAction func didClickRow(_ sender: NSTableView) {
let row = sender.selectedRow
changeTabeleViewState(row: row, tableView: sender)
}
我們修改 required init?(coder: NSCoder)
的代碼如下:
required init?(coder: NSCoder) {
super.init(coder: coder)
self.loadXibView()
changeTabeleViewState(row: 0, tableView: self.tableView)
}