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

左边功能菜单我们设置宽度为 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自定义背景颜色参考下面的连接

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

布局参考之前 NSImageView的布局。

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

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

我们设置刚才新建的 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的界面已经可以出现了。

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

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

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

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

设置 autoresizingMask属性

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

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

我们设置高度自适应。

self.view.autoresizingMask = .viewHeightSizable

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

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

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

修改 Window的最小显示区域

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

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

我们修改 SideMenuViewview的试图背景颜色为 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的右侧放置一个 NSTextFiledLabel,布局如下。

我们设置右侧 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的数据源。

4758283F-C1DD-4C44-9C51-FEA669DADDA3

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

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时候发现抱错下面的代码。

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"

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

FE9DB89D-DB70-4F71-B280-8A65921752C2

我们将 SideMenuItemView改成继承与 NIKFontAwesomeImageView

NIKFontAwesomeImageViewIBDeisgnable不能在 Xib预览的。

我们设置 NIKFontAwesomeImageView属性如下。

  • icon Hex : f02d
  • Size : 17 生成的图片是正方形,并不能和网站的样式可以设置宽度和高度。
  • Color : EBDAC1

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

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()对其进行初始化。

68A0CE15-89BA-4E79-A705-A431DCDF765C

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

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

54D99DC5-BD43-44EC-8F37-83D156C2C01C

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

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
}

我们运行此时显示如下。

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
}

F042453B-FB43-481D-8CC0-07A50CD88F2B

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

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类型。

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏向治洪

React Native在Android平台运行gif的解决方法

概述 目前RN在Android平台上不支持gif格式的图片,而在ios平台是支持的,期待以后的版本中系统也是可以默认支持Android的。首先说下在ios平台怎...

40450
来自专栏一个会写诗的程序员的博客

《React极简教程》第一章 Hello,World!React 第一个实例:Hello,World!react.min.jsreact-dom.min.jsbabel.min.jsReact.ren

React 是一个用于构建用户界面的 JAVASCRIPT 库。 React主要用于构建UI,很多人认为 React 是 MVC 中的 V(视图)。 Rea...

11330
来自专栏熊二哥

Markdown快速入门

现在博文写作次数渐渐变多,经常看到很多园友的博文样式都非常的美观,个人虽然是个土鳖,但对美也是有很强需求的,同时由于最近将要上线一个博客项目,因此也很关心如何可...

24860
来自专栏陈纪庚

手把手教你实现一个引导动画

最近看了一些文章,知道了实现引导动画的基本原理,所以决定来自己亲手做一个通用的引导动画类。

13010
来自专栏柠檬先生

移动设备HTML5页面布局

在HTML5标准添加的新元素中,用于常见页面结 构的包括header footer footer nav aside aside article section...

45480
来自专栏非著名程序员

基础篇章:关于 React Native之 ActivityIndicator 组件的讲解

(友情提示:RN学习,从最基础的开始,大家不要嫌弃太基础,会的同学请自行略过,希望不要耽误已经会的同学的宝贵时间) 今天我们讲解的这个控件的非常简单,那就是Ac...

22070
来自专栏Flutter知识集

Flutter实现雨滴动画

写了几个Flutter的demo,但是对Flutter的自定义view和动画都不太了解,看到一个类似效果在android的实现,就尝试用Flutter做一下。同...

1.2K50
来自专栏Vamei实验室

被解放的姜戈05 黑面管家

Django提供一个管理数据库的app,即django.contrib.admin。这是Django最方便的功能之一。通过该app,我们可以直接经由web页面,...

22390
来自专栏葡萄城控件技术团队

Spread for Windows Forms高级主题(6)---数据绑定管理

自定义列和区域的数据绑定 当表单被绑定到一个数据集时,表单中的列就会相继的被分配到数据集的区域上。例如,第一个数据域分配给列A,第二个数据区域分配给列B,等等。...

226100
来自专栏前端vue

SimpleMDE - Vue-Markdown编辑器

51020

扫码关注云+社区

领取腾讯云代金券