Jekyll-Admin-Mac 开发纪要-左侧菜单栏

本博客将在 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-MacUI我们采用网页的配色即可。

获取 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

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第1张图片

左边功能菜单我们设置宽度为 205

我们新建一个 SideMenuView继承 NSView

现在 NSView创建的时候不允许使用 XIB,我们自己新建一个 Xib

名字叫做 SideMenuView.xib

我们设置 SideMenuView的大小为 205x1000。宽度是固定的,但是高度不固定,我们使用自动布局。

最上线显示 Logo的地方大小为 205x75。我们采用 NSImageView。我们采用如下的布局。

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第2张图片
  • 左侧和父试图对其
  • 上侧和父试图对其
  • 宽度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自定义背景颜色参考下面的连接

我们设置 NSView为继承与 BaseView 背景颜色试图。我们设置背景颜色为 rgb343434

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第3张图片
Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第4张图片

布局参考之前 NSImageView的布局。

我们把刚才的 NSImageView作为子试图,布局设置下面。

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第5张图片
Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第6张图片

我们拖拽 NSView一个新的试图放置在 Main.storyboard-ViewController-View上面。

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第7张图片

我们设置刚才新建的 NSView继承我们新建的类 SideMenuView

使用 Xib 加载试图

到这里,我们新建的 NSView无法正常的显示出来。那是因为我们在 XIB进行初始化的时候走的是方法是

public init?(coder: NSCoder)

并且 SideMenuView这个类不知道从哪里加载试图。关于如何进行加载自定义的 XIB可以参考这一篇文章。

怎么让继承的类直接使用XIB的布局试图

我们新增一个绑定的属性

@IBOutlet weak var view: BaseView!

设置 XibFile'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的界面已经可以出现了。

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第8张图片

但是到目前来说我们几乎达到显示 Logo,但是我们的背景颜色设置白色不是我们所希望的,我们设置默认的为透明颜色。

我们还发现我们我们的试图并没有达到我们设置约束的大小。

我们可以点击 Xcode查看试图层次

我们看出SideMenuView试图的 View并没有达到我们随着父试图变化而变化。

设置 autoresizingMask属性

我们设置一下 autoresizingMask属性。关于 autoresizingMask一些用法可以看一下下面的资料。

iOS开发-自动布局之autoresizingMask使用详解(Storyboard&Code)

我们设置高度自适应。

self.view.autoresizingMask = .viewHeightSizable

我们设置 SideMenuViewview的背景为rgb515151,方便我们进行查看。

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第9张图片

我的试图已经能随着变化自动改变高度了。

这个时候我们还发现了一个问题,我们的 Window可以压缩宽度最小,这样左边的侧栏已经挡着了。

修改 Window的最小显示区域

我们可以通过下面设置 window的最小值。

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第10张图片

这样我们可以让 Window可以保持最小的尺寸是 600x500

我们修改 SideMenuViewview的试图背景颜色为 RGB444444

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第11张图片

上面的图可以明显看出来是需要封装控件的,但是封装完毕是试图依次叠加还是使用 NSTableView。试图依次叠加不利于扩展,我们采用 NSTableView

我们拖拽一个 NSTableView的控件放置在 SideMenuView剩余的位置。布局如下。

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第12张图片
Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第13张图片

如图所示的版本还不能达到我们的要求,有了标题,而且多了一个 Column

我们取消显示 Header和设置只有一个 Cloumn

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第14张图片

我们发现我们剩下的只有一个 Column的宽度只有 116并不是全屏显示的。

去掉 NSTableView的边框

我们设置宽度为 205

我们现在发现了一个问题,我们本来有205的宽度的。但是我们现在只能设置最大200,并且预览显示是全屏显示了。

我们在 NSTableView的属性里面看到这个。

我们的宽度留3大小。但是就算去掉了3还是只有 203,剩下的 2跑到那里去了。

我们观察到 NSTableView的父试图已经是 203的宽度了,既然这样我们就默认使用 200

可以设置最外层 Border为没有即可。

我们发现我们刚才创建的 NSTableView显示的背景颜色是白色的,我们可以关闭 NSScrollView的绘制背景颜色和设置 NSTableView的背景颜色为透明即可。

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第15张图片

虽然系统的 NSButton是符合图片加文字效果的,但是却无法修改文字的颜色。

我们创建一个类继承与 BaseView名字叫做 SideMenuItemView

我们按照上文所描述的方法创建一个 Xib文件。

我们设置 Xib里面的 NSView的宽度为 205,高度为 49。其实我们这个宽度和高度会随着改变的。

我们在最左侧放置一个 NSImageView布局如下。

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第16张图片

我们在 NSImageView的右侧放置一个 NSTextFiledLabel,布局如下。

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第17张图片
Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第18张图片

我们设置右侧 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

B06B6F83-FBBC-4069-802B-AFCF62389B8F

我们删除自动生成的试图,拖拽一个 NSView到 到 Column下面。我们关联 NSTableView的数据源。

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第19张图片
4758283F-C1DD-4C44-9C51-FEA669DADDA3

我们在 SideMenuView类里面实现 NSTableView的数据源方法。

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第20张图片
BC3FC205-DB67-4781-A977-FFC2DDFF1949

我们通过界面查看器可以看的出来,第一个 Row已经出来了,但是却因为没有设置无法显示。

OSX使用 font-awesome

左侧的图片网站采用 font-awesome框架。 OSX我们使用 FontAwesomeIconFactory框架。

使用 Cocoapods我强烈的建议使用 官方的 App使用

我们设置刚才我们封装的 SideMenuItemViewNSImageView的子类为 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报下面的错误。

E6E2C7AD-960E-4B7E-B418-AB327F3144AC

我们在 Debug IBDeisgnable时候发现抱错下面的代码。

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第21张图片
80C7068F-07EB-448D-BBBD-1B42358BEE81

因为我们绑定是对象属于 !类型,但是我们此时还不存在这个变量。故而强行当做存在的使用崩溃了。

到目前为止,我不清楚这个对象没有初始化是为什么导致的。但是只是在 Xib进行初始化 IBDeisgnable抱错,但是可以正常运行的。

但是这样可能不能满足我的要求,我们尽量解决就解决。我们之前的方法里面可以接受一个数组的指针。

我们看看数组里面元素如何。

var views:NSArray = NSArray()
Bundle.main.loadNibNamed("SideMenuItemView", owner: self, topLevelObjects: &views)
96D49D78-164D-4735-80F5-A92558454117

数组里面是有元素的,我们尝试从这里面的元素获取试一下。

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
}

我们发现之前报的错误果然消失了。我们可以采用这一种方式来加载试图,我们可以封装一下,方便我们用。

如何在 Swift3获取类名字符串。

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"

我们可以采用分割字符串使用最后一个。

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第22张图片
FE9DB89D-DB70-4F71-B280-8A65921752C2

我们将 SideMenuItemView改成继承与 NIKFontAwesomeImageView

NIKFontAwesomeImageViewIBDeisgnable不能在 Xib预览的。

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第23张图片

我们设置 NIKFontAwesomeImageView属性如下。

  • icon Hex : f02d

  • Size : 17

    生成的图片是正方形,并不能和网站的样式可以设置宽度和高度。

  • Color : EBDAC1

我们运行一下发现已经可以正常的运行了。

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第24张图片
D18987B2-B0A1-47F5-BBFB-682D44D61367

面向对象设计

我们配置一下 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()对其进行初始化。

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第25张图片
68A0CE15-89BA-4E79-A705-A431DCDF765C

上图是我们运行起来的效果。但是呢和我们网页的看起来还是有写差别的。

我们在 SideMenuItemView.xib上面的底部添加一条线。布局如下:

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第26张图片
54D99DC5-BD43-44EC-8F37-83D156C2C01C

线继承与 BaseView,我们设置颜色为 424242

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第27张图片
777EFD44-93A6-4A80-BD5E-92FB76E8A426

虽然线是出来了,但是我们不想让全部出现。

我们在 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
}

我们运行此时显示如下。

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第28张图片
B97289BE-AD47-4616-BAF0-0A62F77DE986

我们给 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
}
Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第29张图片
F042453B-FB43-481D-8CC0-07A50CD88F2B

但是我们运行起来,却发现全部都是选中的颜色,原来是我们默认颜色和选中颜色配置反了导致,我们修改过来即可。

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第30张图片
1F549B40-E8F1-4069-9B3A-7F0ADD6B87C3

此时我们的初始化配置恢复了正常,但是我们点击了没有任何的变化。让我们找一下出现这种现象原因是怎么导致的。

⚠️因为结构体没有被引用,所以便利出来的临时变量属于一个新的地址。我们需要修改临时变量之后替换掉之前数组里面的。

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

‼️此时需要注意的是我们需要修改我们的 menuItemsvar类型。

Jekyll-Admin-Mac 开发纪要-左侧菜单栏_第31张图片
11

此时我们的效果已经达到了,我们觉得默认启动显示的第一个界面是0元素。

我们绑定界面的元素 NSTableViewSideMenuView

@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)
}

你可能感兴趣的:(Jekyll-Admin-Mac 开发纪要-左侧菜单栏)