跳过导航
Image showing stacked interfaces over a UI over a green background.
学习设计

深入探讨:使用链接数据更新您的设计

通过从您的设计中生成链接数据并与您的团队分享,加速与文案撰写人、翻译人员等的协作

在我们上次的深入探讨中,我们展示了如何使用链接数据将自定义数据集快速应用到您的设计中。现在,我们将重点关注如何显著加快与文案撰写人、翻译人员以及团队中其他帮助您处理设计特定内容成员的协作。对于那些渴望在工作流程中尝试这种技术的用户,我们还提供了一个现成的脚本。

先设计,后更新(数据)

Image showing a Travel app interface in Sketch with some custom data.

无论您是在为您的下一个营销活动设计营销材料,还是在进行您的下一个大型项目,您的项目中很有可能会涉及到其他利益相关者。例如,一位 UX 文案撰写人审查界面副本并提出更改建议,或者法律团队希望仔细审查您同意表格中的副本。

在您的设计中使用链接数据

一些组织拥有充满真实占位符数据的电子表格,因为您出于安全原因无法访问真实的 API。然而,更常见的情况是团队先设计品牌和营销资产,然后从主模板创建无数变体。这些资产与设计师原始资产之间的唯一区别是文本和图像。

当提供图像和文本的人不是设计师时,在他们和团队其他成员之间建立无缝的工作流程比以往任何时候都重要。为了解决这个问题,您可以从设计中提取纯文本数据,然后轻松地与您的团队共享。一旦他们完成,您可以将其拉回 Sketch 以更新您的设计。

工作流程步骤

  1. 创建设计
  2. 将设计中的数据提取为 JSON 文件
  3. 在 Sketch 外部更新数据
  4. 刷新 Sketch 文档中的链接数据

目前,我们将假设您已经有一个想要使用的设计,所以我们将从从文档中提取数据开始。链接数据文件必须遵循您设计的图层层次结构,并且您需要以与其预期图层相同的方式命名您的条目。正确构建结构是一个障碍。另一个障碍是编写 JSON 语法可能很繁琐且容易出错。

虽然有一些代码编辑器和命令行工具可以标记此类问题,但作为一名设计师,您可能不熟悉它们。您几乎肯定会遗漏某个逗号或引号。相反,我们可以让 Sketch 完成繁重的工作,并使用其 JavaScript 插件 API 从选择中生成有效的 JSON 数据。

将设计中的数据提取为 JSON

只想继续从您的文档中提取 JSON 数据? 直接跳到下面的完整脚本

Sketch 数据支持文本和图像。您可以将值分配给文本图层、图像填充以及任何一种符号覆盖,或者从中派生它们,包括嵌套符号的覆盖。要定义图像,请包含相对于 JSON 文件的图像文件的文件路径。

[
  {
    "name": "Anje Keizer",
    "avatar": "/Faces/109.jpg",
    "location": "Bangkok",
    "bio": "Dog lover 🐕, mahjong champion 🀄️, and traveler 🗺 ",
    "social": {
      "handle": "@akeizer01",
      "bio": "Loving life and living in Dallas, go Mavs!"
    }
  }
]

Sketch 将文档中的所有图像存储在该文档的包中,即 .sketch 文件本身。在本指南中,我们不会将这些图像与 JSON 一起导出,而是使用占位符值。

提取用户选择和所有子图层的数据

要从选择创建初始数据集,我们将使用一个脚本遍历所选图层及其所有子图层,以构建一个与图层具有相同结构和命名的数据对象。结果会自动复制到剪贴板,以便我们可以快速在 Sketch 外部使用它。

此脚本由两个部分组成——用于遍历的 walk 函数和一个从图层提取数据值的函数。

第一部分非常简单。它将每个图层的值添加到一个对象中,并确保以不同于单个图层的方式处理图层组。它只会将数据值分配给(并从中提取)文本图层、填充和符号实例的覆盖值。对于组,它会递归地调用 walk 函数。

const walk = (layers, extract, initialValue) => {
  var value = initialValue

  for (const l of Array.from(layers).reverse()) {
    // layer groups can only create nested data objects, not values
    let v = isLayerGroup(l) ? walk(l.layers, extract, undefined) : extract(l)

    if (v === undefined) continue
    value = { ...value, [l.name]: v }
  }
  return value
}

不同的数据类型

从图层获取数据稍微复杂一些。我们需要区分图层的类型,才能正确获取文本或图像数据,以及用作对象键的名称。

文本图层非常简单——我们只需要它们的文本值。

另一方面,符号实例需要更多的工作。您将看到它们的覆盖值作为一个平面列表,包括来自嵌套符号的任何覆盖。为了让 Sketch 稍后正确地应用数据,我们需要重建嵌套的数据结构。我们可以使用覆盖的 path ,其中包括所有符号 ID 和受影响的图层 ID,每个 ID 之间用斜杠分隔。

最后,对于任何不是文本或符号实例的图层,脚本将检查图像填充,并再次返回图像路径的占位符值。

将所有数据脚本放在一起

您将在下面找到完整的脚本,包括负责提取不同图层类型数据的 toData 函数。它还包括将 JSON 编码数据复制到剪贴板的机制,以及为用户提供反馈,例如当选择丢失以及指示 JSON 已成功生成时。

const { getSelectedDocument, Style } = require('sketch')
const { message } = require('sketch/ui')

function isLayerGroup(tbc) {
  return 'type' in tbc && tbc.type == 'Group'
}

const toData = (layer) => {
  switch (layer.type) {
    // text layers use the value
    case 'Text':
      return layer.text

    // symbol instances can have override values
    case 'SymbolInstance':
    case 'SymbolMaster':
      // ensure overrides for nested symbols won’t be processed before the
      // actual symbol override and filter out any override values that cannot
      // be used with data
      let supportedProperties = ['symbolID', 'stringValue', 'image']
      let overrides = layer.overrides
        .sort((a, b) => a.path.localeCompare(b.path))
        .filter((val) => supportedProperties.includes(val.property))

      var data = {}
      var dataGroupByPath = { '': data }
      var hasValues = false

      for (const o of overrides) {
        let pathComponents = o.path.split('/')
        pathComponents.pop()
        let parentPath = pathComponents.join('/')

        if (o.property === 'symbolID') {
          dataGroupByPath[o.path] = {}
          dataGroupByPath[parentPath][o.affectedLayer.name] =
            dataGroupByPath[o.path]
          continue
        }

        dataGroupByPath[parentPath][o.affectedLayer.name] =
          o.property === 'image' ? '/path/to/image.png' : o.value
        hasValues = true
      }
      // We need to remove the nodes that don’t have any values
      data = removeEmptyNodes(data)

      return hasValues ? data : undefined

    // other layers can have image fills, in case of multiple image fills only
    // the last one is used as override value
    default:
      let hasImageFill = layer.style?.fills.reduce((prev, curr) => {
        if (curr.type !== Style.FillType.Pattern) return prev
        return true
      }, false)

      if (!hasImageFill) break
      return '/path/to/image.png' // actual image not exported, placeholder instead
  }
  return undefined
}

const walk = (layer, extract, initialValue) => {
  if (!isLayerGroup(layer)) {
    return extract(layer)
  }

  var value = initialValue
  for (const l of Array.from(layer.layers).reverse()) {
    // layer groups can only create nested data objects, not values
    let v = isLayerGroup(l) ? walk(l.layers, extract, undefined) : extract(l)
    if (v === undefined) continue
    value = { ...value, [l.name]: v }
  }
  return value
}

let doc = getSelectedDocument()

if (doc.selectedLayers.length !== 1) {
  message('☝️ Select exactly one layer group to create data set.')
  return
}

let selected = doc.selectedLayers.layers[0]

let data = walk(selected, toData, undefined)

// `data` can be `undefined` if the symbol overrides
// in the selected layer are disabled
if (data === undefined) {
  message('☝️ No symbol overrides found.')
} else {
  // wrap data in array before encoding as JSON because Sketch expects a
  // set of values, not a single object
  let json = JSON.stringify([data], null, 2)

  // use native macOS pasteboard APIs to copy the JSON so it can be easily
  // pasted outside Sketch
  let pasteboard = NSPasteboard.generalPasteboard()
  pasteboard.clearContents()
  pasteboard.setString_forType(json, NSPasteboardTypeString)

  message('📋 Data copied to clipboard.')
}

function removeEmptyNodes(obj) {
  let hasEmptyNodes = false
  Object.entries(obj).forEach(([key, value]) => {
    if (Object.keys(value).length === 0) {
      delete obj[key]
      hasEmptyNodes = true
    } else if (typeof value === 'object') {
      obj[key] = removeEmptyNodes(value)
    }
  })
  return hasEmptyNodes ? removeEmptyNodes(obj) : obj
}

从您的 Sketch 文档中提取数据

为了将脚本付诸行动,我们将使用我们的旅行应用程序示例项目。它的酒店列表和详细信息屏幕是您可能希望要求您的同事提供代表性示例内容的设计的绝佳示例。我们将为酒店详细信息屏幕中的房间列表创建初始数据。

首先,将上面的脚本粘贴到脚本编辑器中,然后在 Sketch Mac 应用中选择插件 > 运行脚本。然后选择您想要转换的组并运行脚本以创建您的 JSON。

您可以随时关闭脚本编辑器并从插件菜单重新运行脚本,或者使用键盘快捷键进行进一步选择。只需确保一次只选择一个组。要保存 JSON 数据,请将其粘贴到任何文本编辑器中,例如 macOS 自己的 TextEdit 或 Visual Studio Code,然后保存它。

您可以从运行脚本…面板将脚本保存为插件,以便将来直接从插件菜单中使用它。

更新和刷新

现在您有了 JSON 文件,您可以与团队中的其他利益相关者分享。例如,文案撰写人可以直接在 JSON 文件中更改文本,然后将其发回给您以在设计中实施。为此,请打开 Sketch > 偏好设置,选择“数据”选项卡,然后添加 JSON 文件。从那里,您可以按住 Ctrl 并单击组,将鼠标悬停在 数据 上,然后选择您的来源,从而将其选择为数据源。

观看下面的视频,了解从头到尾的整个过程。

这个例子展示了如何使用 Sketch 的“链接数据”功能,而无需担心自己创建 JSON 文件。从设计中生成数据为您或您团队中的其他人提供了一个可以直接使用的模板。


尝试使用您自己的设计来运行脚本,并告诉我们效果如何。我们希望这些新增功能能让您在 Sketch 中更轻松地使用真实数据。如果您对使用数据进行设计有任何想法或反馈,请联系我们

您可能也喜欢

免费试用 Sketch

无论您是 Sketch 的新手,还是回来看看有什么新功能,我们都会让您在几分钟内完成设置并准备好做您最好的工作。

免费开始
免费开始