跳過導航
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 自身的文字編輯或 Visual Studio Code,然後儲存。

您可以從 _執行腳本…_ 工作表中將腳本儲存為外掛,使其可以直接從 _外掛_ 選單中使用,以便日後使用。

更新和刷新

現在您有了 JSON 檔案,您可以與團隊中的其他利益相關者分享。例如,文案可以修改 JSON 檔案中的文字,然後將其發送回給您以在設計中實作。為此,請開啟 Sketch > 偏好設定,選取「數據」標籤,然後新增 JSON 檔案。然後,您可以透過 Ctrl-點擊群組、將滑鼠懸停在 **數據** 上並選取您的來源來將其選取為數據來源。

觀看以下影片,了解從頭到尾的整個過程。

此範例顯示了如何在不擔心自行建立 JSON 檔案的情況下使用 Sketch 的連結數據功能。從您的設計中產生數據提供了一個範本,您可以或您團隊中的其他人可以直接使用。


歡迎您使用自己的設計來測試這個腳本,並讓我們知道您的使用體驗。我們希望這些新增功能可以讓您在 Sketch 中更輕鬆地使用真實數據。如果您對數據設計有任何想法或回饋,歡迎與我們聯繫

您可能也會喜歡

免費試用 Sketch

無論您是 Sketch 新手,還是回來查看新功能,我們都能讓您在幾分鐘內設定完成並準備好開始最佳創作。

免費開始使用
免費開始使用