本文做者是来自凹凸实验室
高露
, 他在作内部设计中台 quark 项目中,协做开发了Quark for Sketch
插件,在开发过程有许多经验总结,在本文里与你们分享。javascript
Sketch 是很是流行的 UI 设计工具,2014年随着 Sketch V43 版本增长 Symbols 功能、开放开发者权限,吸引了大批开发者的关注。html
目前 Sketch 开发有两大热门课题:① React 组件渲染成 sketch 由 airbnb 团队发起,② 使用 skpm 构建开发 Sketch 插件。java
Sketch 插件开发相关资料较少且不太完善,咱们开发插件过程当中能够重点参考官方文档,只是有些陈旧。官方有提供 JavaScript API 借助 CocoaScript bridge 访问内部 Sketch API 和 macOS 框架进行开发插件(Sketch 53~56 版 JS API 在 native MacOS 和 Sketch API 暴露的特殊环境中运行),提供的底层 API 功能有些薄弱,更深刻的就须要了解掌握 Objective-C 、 CocoaScript 、AppKit、Sketch-Headers。react
Sketch Plugin 是一个或多个 scripts 的集合,每一个 script 定义一个或多个 commands。Sketch Plugin 是以 .sketchplugin
扩展名的文件夹,包含文件和子文件夹。严格来讲,Plugin 其实是 OS X package,用做为 OS X bundle。ios
Bundle 具备标准化分层结构的目录,其保存可执行代码和该代码使用的资源。git
Bundles 包含一个 manifest.json
文件,一个或多个 scripts 文件(包含用 CocoaScript 或 JavaScript 编写的脚本),它实现了 Plugins 菜单中显示的命令,以及任意数量的共享库脚本和资源文件。github
mrwalker.sketchplugin
Contents/
Sketch/
manifest.json
shared.js
Select Circles.cocoascript
Select Rectangles.cocoascript
Resources/
Screenshot.png
Icon.png
复制代码
最关键的文件是 manifest.json
文件,提供有关插件的信息。web
小贴士:json
Sketch 插件包可使用 skpm 在构建过程当中生成,skpm 提供 Sketch 官方插件模版:canvas
skpm/skpm
- The simplest possible plugin setup. (default)skpm/with-prettier
- A plugin setup featuring linting with ESLint and code formatting with Prettier.skpm/with-datasupplier
- A template to create DataSupplier plugins (check our blog for more info)skpm/with-webview
- A template to create plugins displaying some rich UI in a WebView (check sketch-module-web-view for more info)💁 Tip: Any Github repo with a 'template' folder can be used as a custom template:
skpm create <project-name> --template=<username>/<repository>
manifest.json
文件提供有关插件的信息,例如做者,描述,图标、从何处获取最新更新、定义的命令 **(commands) **、调用菜单项 (menu) 以及资源的元数据。
{
"name": "Select Shapes",
"description": "Plugins to select and deselect shapes",
"author": "Joe Bloggs",
"homepage": "https://github.com/example/sketchplugins",
"version": "1.0",
"identifier": "com.example.sketch.shape-plugins",
"appcast": "https://excellent.sketchplugin.com/excellent-plugin-appcast.xml",
"compatibleVersion": "3",
"bundleVersion": 1,
"commands": [
{
"name": "All",
"identifier": "all",
"shortcut": "ctrl shift a",
"script": "shared.js",
"handler": "selectAll"
},
{
"name": "Circles",
"identifier": "circles",
"script": "Select Circles.cocoascript"
},
{
"name": "Rectangles",
"identifier": "rectangles",
"script": "Select Rectangles.cocoascript"
}
],
"menu": {
"items": ["all", "circles", "rectangles"]
}
}
复制代码
声明一组 command 的信息,每一个 command 以 Dictionary
数据结构形式存在。
context
上下文参数。若未指定 handler,Sketch 会默认调用对应 script 中 onRun
函数com.xxxx.xxx
格式,不要过长Sketch 加载插件会根据指定的信息,在菜单栏中有序显示命令名。
在了解了 Sketch 插件结构以后,咱们再来了解一下,sketch提供的官方 API: Actions API, Javascript API。
Sketch Actions API 用于监听用户操做行为而触发事件,例如 OpenDocumen(打开文档)、CloseDocument(关闭文档)、Shutdown(关闭插件)、TextChanged(文本变化)等,具体详见官网:developer.sketch.com/reference/a…
manifest.json 文件,配置相应 handlers。
示例:当 OpenDocument 事件被触发时调用 onOpenDocument handler 。
"commands" : [
...
{
"script" : "my-action-listener.js",
"name" : "My Action Listener",
"handlers" : {
"actions": {
"OpenDocument": "onOpenDocument"
}
},
"identifier" : "my-action-listener-identifier"
}
...
],
复制代码
**my-action-listener.js **
export function onOpenDocument(context) {
context.actionContext.document.showMessage('Document Opened')
}
复制代码
Action 事件触发时会将 context.actionContext
传递给相应 handler
。注意有些 Action 包含两个状态begin
和 finish
,例如 SelectionChanged
,需分别订阅 SelectionChanged.begin
和 SelectionChanged.finish
,不然会触发两次事件。
Sketch 插件开发大概有以下三种方式:① 纯使用 CocoaScript 脚本进行开发,② 经过 Javascript + CocoaScript 的混合开发模式, ③ 经过 AppKit + Objective-C 进行开发。Sketch 官方建议使用 JavaScript API 编写 Sketch 插件,且官方针对 Sketch Native API 封装了一套 JS API,目前还未涵盖全部场景, 若须要更丰富的底层 API 需结合 CocoaScript 进行实现。经过 JS API 能够很方便的对 Sketch 中 Document
、Artboard
、Group
、Layer
进行相关操做以及导入导出等,可能须要考虑兼容性, JS API 原理图以下:
CocoaScript 实现 JavaScript 运行环境到 Objective-C 运行时的桥接功能,可经过桥接器编写 JavaScript 外部脚本访问内部 Sketch API 和 macOS 框架底层丰富的 API 功能。
小贴士:
Mocha 实现提供 JavaScript 运行环境到 Objective-C 运行时的桥接功能已包含在CocoaScript中。
CocoaScript 创建在 Apple 的 JavaScriptCore 之上,而 JavaScriptCore 是为 Safari 提供支持的 JavaScript 引擎,使用 CocoaScript 编写代码实际上就是在编写 JavaScript。CocoaScript 包括桥接器,能够从 JavaScript 访问 Apple 的 Cocoa 框架。
借助 CocoaScript 使用 JavaScript 调 Objective-C 语法:
object.name()
object.setName('Sketch')
,object.name='sketch'
var/const/let
设置类型注意:详细 Objective-C to JavaScript 请参考 Mocha 文档
示例:
// oc: MSPlugin 的接口 valueForKey:onLayer:
NSString * value = [command valueForKey:kAutoresizingMask onLayer:currentLayer];
// cocoascript:
const value = command.valueForKey_onLayer(kAutoresizingMask, currentLayer);
// oc:
const app = [NSApplication sharedApplication];
[app displayDialog:msg withTitle:title];
// cocoascript:
const app = NSApplication.sharedApplication();
app.displayDialog_withTitle(msg, title)
// oc:
const openPanel = [NSOpenPanel openPanel]
[openPanel setTitle: "Choose a location…"]
[openPanel setPrompt: "Export"];
// cocoascript:
const openPanel = NSOpenPanel.openPanel
openPanel.setTitle("Choose a location…")
openPanel.setPrompt("Export")
复制代码
Sketch 插件系统能够彻底访问应用程序的内部结构和 macOS 中的核心框架。Sketch 是用 Objective-C 构建的,其 Objective-C 类经过 Bridge (CocoaScript/mocha) 提供 Javascript API 调用,简单的了解 Sketch 暴露的相关类以及类方法,对咱们开发插件很是有帮助。
使用 Bridge 定义的一些内省方法来访问如下信息:
String(context.document.class()) // MSDocument
const mocha = context.document.class().mocha()
mocha.properties() // array of MSDocument specific properties defined on a MSDocument instance
mocha.propertiesWithAncestors() // array of all the properties defined on a MSDocument instance
mocha.instanceMethods() // array of methods defined on a MSDocument instance
mocha.instanceMethodsWithAncestors()
mocha.classMethods() // array of methods defined on the MSDocument class
mocha.classMethodsWithAncestors()
mocha.protocols() // array of protocols the MSDocument class inherits from
mocha.protocolsWithAncestors()
复制代码
当输入插件定制的命令时,Sketch 会去寻找改命令对应的实现函数, 并传入 context
变量。context
包含如下变量:
MSPluginCommand
对象,当前执行命令MSDocument
对象 ,当前文档MSPluginBundle
对象,当前的插件 bundle,包含当前运行的脚本NSString
当前执行脚本的绝对路径NSURL
对象NSArray
对象,包含了当前选择的全部图层。数组中的每个元素都是 MSLayer
对象小贴士:MS 打头类名为 Sketch 封装类如图层基类 MSLayer、文本层基类 MSTextLayer 、位图层基类 MSBitmapLayer,NS 打头为 AppKit 中含有的类
const app = NSApplication.sharedApplication()
function initContext(context) {
context.document.showMessage('初始执行脚本')
const doc = context.document
const page = doc.currentPage()
const artboards = page.artboards()
const selectedArtboard = page.currentArtboard() // 当前被选择的画板
const plugin = context.plugin
const command = context.command
const scriptPath = context.scriptPath
const scriptURL = context.scriptURL
const selection = context.selection // 被选择的图层
}
复制代码
##Sketch 插件开发上手
前面咱们了解了许多 Sketch 插件开发知识,那接下来实际上手两个小例子: ① 建立辅助内容面板窗口, ② 侧边栏导航。为了方便开发,咱们在开发前需先进行以下操做:
崩溃保护
当 Sketch 运行发生崩溃,它会停用全部插件以免循环崩溃。对于使用者,每次崩溃重启后手动在菜单栏启用所需插件很是繁琐。所以能够经过以下命令禁用该特性。
defaults write com.bohemiancoding.sketch3 disableAutomaticSafeMode true
复制代码
插件缓存
经过配置启用或禁用缓存机制:
defaults write com.bohemiancoding.sketch3 AlwaysReloadScript -bool YES
复制代码
该方法对于某些场景并不适用,如设置 COScript.currentCOScript().setShouldKeepAround(true)
区块会保持常驻在内存,那么则须要经过 coscript.setShouldKeepAround(false)
进行释放。
WebView 调试
若是插件实现方案使用 WebView 作界面,可经过如下配置开启调试功能。
defaults write com.bohemiancoding.sketch3 WebKitDeveloperExtras -bool YES
复制代码
首先咱们先熟悉一下 macOS 下的辅助内容面板, 以下图最左侧 NSPanel 样例, 它是有展现区域,可设置样式效果,左上角有可操做按钮的辅助窗口。
Sketch 中要建立以下内容面板,须要使用 macOS 下 AppKit
框架中 NSPanel
类,它是 NSWindow
的子类,用于建立辅助窗口。内容面板外观样式设置,可经过 NSPanel
类相关属性进行设置, 也可经过 AppKit
的NSVisualEffectView
类添加模糊的背景效果。内容区域则可经过 AppKit
的 WKWebView
类,单开 webview
渲染网页内容展现。
const panelWidth = 80;
const panelHeight = 240;
// Create the panel and set its appearance
const panel = NSPanel.alloc().init();
panel.setFrame_display(NSMakeRect(0, 0, panelWidth, panelHeight), true);
panel.setStyleMask(NSTexturedBackgroundWindowMask | NSTitledWindowMask | NSClosableWindowMask | NSFullSizeContentViewWindowMask);
panel.setBackgroundColor(NSColor.whiteColor());
// Set the panel's title and title bar appearance
panel.title = "";
panel.titlebarAppearsTransparent = true;
// Center and focus the panel
panel.center();
panel.makeKeyAndOrderFront(null);
panel.setLevel(NSFloatingWindowLevel);
// Make the plugin's code stick around (since it's a floating panel)
COScript.currentCOScript().setShouldKeepAround(true);
// Hide the Minimize and Zoom button
panel.standardWindowButton(NSWindowMiniaturizeButton).setHidden(true);
panel.standardWindowButton(NSWindowZoomButton).setHidden(true);
复制代码
// Create the blurred background
const vibrancy = NSVisualEffectView.alloc().initWithFrame(NSMakeRect(0, 0, panelWidth, panelHeight));
vibrancy.setAppearance(NSAppearance.appearanceNamed(NSAppearanceNameVibrantLight));
vibrancy.setBlendingMode(NSVisualEffectBlendingModeBehindWindow);
// Add it to the panel
panel.contentView().addSubview(vibrancy);
复制代码
webview
渲染const wkwebviewConfig = WKWebViewConfiguration.alloc().init()
const webView = WKWebView.alloc().initWithFrame_configuration(
CGRectMake(0, 0, panelWidth, panelWidth),
wkwebviewConfig
)
// Add it to the panel
panel.contentView().addSubview(webView);
// load file URL
webview.loadFileURL_allowingReadAccessToURL(
NSURL.URLWithString(url),
NSURL.URLWithString('file:///')
)
复制代码
咱们开发复杂的 Sketch 插件,通常都要开发侧边栏导航展现插件功能按钮,点击触发相关操做。那开发侧边栏导航,咱们主要使用 AppKit
中的那些类呢,有 NSStackView
、 NSBox
、NSImage
、 NSImageView
、NSButton
等,大体核心代码以下:
// create toolbar
const toolbar = NSStackView.alloc().initWithFrame(NSMakeRect(0, 0, 40, 400))
threadDictionary[SidePanelIdentifier] = toolbar
toolbar.identifier = SidePanelIdentifier
toolbar.setSpacing(8)
toolbar.setFlipped(true)
toolbar.setBackgroundColor(NSColor.windowBackgroundColor())
toolbar.orientation = 1
// add element
toolbar.addView_inGravity(createImageView(NSMakeRect(0, 0, 40, 22), 'transparent', NSMakeSize(40, 22)), 1)
const Logo = createImageView(NSMakeRect(0, 0, 40, 30), 'logo', NSMakeSize(40, 28))
toolbar.addSubview(Logo)
const contentView = context.document.documentWindow().contentView()
const stageView = contentView.subviews().objectAtIndex(0)
const views = stageView.subviews()
const existId = views.find(d => ''.concat(d.identifier()) === identifier)
const finalViews = []
for (let i = 0; i < views.count(); i++) {
const view = views[i]
if (existId) {
if (''.concat(view.identifier()) !== identifier) finalViews.push(view)
} else {
finalViews.push(view)
if (''.concat(view.identifier()) === 'view_canvas') {
finalViews.push(toolbar)
}
}
}
// add to main Window
stageView.subviews = finalViews
stageView.adjustSubviews()
复制代码
详细见开源代码: github.com/o2team/sket… (欢迎 star 交流)
当插件运行时,Sketch 将会建立一个与其关联的 JavaScript 上下文,可使用 Safari 来调试该上下文。
在 Safari 中, 打开 Developer
> 你的机器名称 > Automatically Show Web Inspector for JSContexts
,同时启用选项 Automatically Pause Connecting to JSContext
,不然检查器将在能够交互以前关闭(当脚本运行完时上下文会被销毁)。
如今就能够在代码中使用断点了,也能够在运行时检查变量的值等等。
JavaScriptCore 运行 Sketch 插件的环境 也有提供相似调试 JavaScript 代码打 log 的方式,咱们能够在关键步骤处放入一堆 console.log/console.error
等进行落点日志查看。
有如下几种选择能够查看日志:
~/Library/Logs/com.bohemiancoding.sketch3/Plugin Output.log
文件skpm log
命令,该命令能够输出上面的文件(执行 skpm log -f
能够流式地输出日志)console.log
打日志查看。SketchTool 包含在 Sketch 中的 CLI 工具,经过 SketchTool 可对 Sketch 文档执行相关操做:
sketchtool 二进制文件位于 Sketch 应用程序包中:
Sketch.app/Contents/Resources/sketchtool/bin/sketchtool
复制代码
设置 alias
:
alias sketchtool="/Applications/Sketch.app/Contents/Resources/sketchtool/bin/sketchtool"
复制代码
使用:
sketchtool -h # 查看帮助
sketchtool export artboards path/to/document.sketch # 导出画板
sketchtool dump path/to/document.sketch # 导出 Sketch 文档 JSON data
sketchtool metadata path/to/document.sketch # 查看 Sketch 文档元数据
sketchtool run [Plugin path] # 运行插件
复制代码
注意
:SketchTool 须要 OSX 10.11或更高版本。
AppKit, 构建 Sketch 的一个主要 Apple 框架
Foundation(基础), 更重要的 Apple 课程和服务
欢迎关注凹凸实验室博客:aotu.io
或者关注凹凸实验室公众号(AOTULabs),不定时推送文章: