Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

Export Figma Assets to Xcode on a Remote Mac

If you design in Figma on Windows and your iOS developers want finished .xcassets bundles, you can do most of the work on your own machine: configure 1x/2x/3x export in Figma, convert the folder to the Xcode layout with a short Python script, then open the bundle in Xcode on a rented Mac to preview it in the iOS Simulator. Only the Xcode and Simulator step actually needs macOS.

What actually needs a Mac

The folder structure for an Xcode asset catalog is nothing but directories and Contents.json files — Windows or Linux can write them with ordinary file APIs. What needs macOS is Xcode itself: compiling the catalog, validating image sets, and launching the iOS Simulator so you can see an asset at a given device scale. That is the step a remote Mac is for.

Figment's $29 plan is a virtual Intel macOS environment (2 vCPU, 8 GiB RAM, 80 GiB disk, no GPU acceleration, no audio) reached through a browser console. There is no inbound SSH to the VM, and Xcode is not preinstalled — after you sign in you install Xcode yourself from Apple's Developer site or the App Store. Apple's porting guide is explicit that to run Xcode on an Intel processor you must use Xcode 26 or earlier, so this VM is fine for asset-catalog validation and any iOS Simulator runtime still supported by Xcode 26. If your team is on Xcode 27, needs visionOS, needs faster Simulator rendering on a real GPU, or just wants more RAM, choose the M-series plan from $95 — the exact chip, RAM and disk are confirmed in the quote. The $49 i3 Mac and the physical M-series tiers currently collect requests until supply is confirmed, so treat those as quote-first rather than instant stock.

Configure Figma slices at 1x, 2x, 3x

Figma's own export documentation, Export formats and settings, is the source of truth. Two rules matter for this workflow:

  • PNG and JPG accept any scale, including the 1x, 2x, 3x multipliers Apple expects. The suffix field is a plain string appended to the filename after export — Figma does not add the @ for you, you type it.
  • SVG and PDF only export at 1x. If your developers want vector assets, send a single PDF or SVG per symbol and let Xcode do the scaling; the multi-scale script below is only for raster PNG/JPG.

For each layer, frame, or component you want in the catalog:

  1. Select the item on the canvas.
  2. In the right panel under Export, add three rows.
  3. Row 1: scale 1x, suffix empty, format PNG — produces Name.png (the 1x file).
  4. Row 2: scale 2x, suffix @2x, format PNG — produces Name@2x.png.
  5. Row 3: scale 3x, suffix @3x, format PNG — produces Name@3x.png.
  6. Click Export. For a whole library, multi-select frames and export in one pass; each frame keeps its own export settings.

If a Figma layer name literally contains @2x or @3x (for example Icon@2x), rename the layer before exporting — otherwise the converter below will read the suffix as a scale.

Convert the export folder to .xcassets

Xcode expects each image set inside .xcassets to look like this:

MyAssets.xcassets/
  Contents.json                 (top-level)
  ButtonPrimary.imageset/
    Contents.json
    ButtonPrimary.png           (1x)
    ButtonPrimary@2x.png        (2x)
    ButtonPrimary@3x.png        (3x)

The script below is an untested reference implementation — run it against a small test folder first and open the result in Xcode before you point it at a real design library. It needs Python 3.8+, standard library only, and leaves your original export folder untouched because it copies files rather than moving them. On macOS, Python 3 arrives with the Command Line Tools; on Windows it is a separate install from python.org.

#!/usr/bin/env python3
"""figma_to_xcassets.py - Convert a Figma export folder to a .xcassets bundle.

Usage: python3 figma_to_xcassets.py <figma_export_dir> <output.xcassets>
"""

import json, re, shutil, sys
from collections import defaultdict
from pathlib import Path

PATTERNS = [
    (re.compile(r"^(.+)@3x\.(png|jpg)$", re.IGNORECASE), "3x"),
    (re.compile(r"^(.+)@2x\.(png|jpg)$", re.IGNORECASE), "2x"),
    (re.compile(r"^(.+)@1x\.(png|jpg)$", re.IGNORECASE), "1x"),
    (re.compile(r"^(.+)\.(png|jpg)$",    re.IGNORECASE), "1x"),  # bare == 1x
]

def classify(name):
    for pattern, scale in PATTERNS:
        m = pattern.match(name)
        if m:
            return m.group(1), scale, m.group(2).lower()
    return None

def contents_for(entries):
    images = []
    for scale in ("1x", "2x", "3x"):
        slot = {"idiom": "universal", "scale": scale}
        if scale in entries:
            slot["filename"] = entries[scale]
        images.append(slot)
    return {"images": images, "info": {"version": 1, "author": "xcode"}}

def main():
    if len(sys.argv) != 3:
        sys.exit("Usage: figma_to_xcassets.py <export_dir> <output.xcassets>")
    src, out = Path(sys.argv[1]), Path(sys.argv[2])
    if not src.is_dir():
        sys.exit(f"Not a directory: {src}")

    groups = defaultdict(dict)
    for f in sorted(src.iterdir()):
        if not f.is_file():
            continue
        r = classify(f.name)
        if not r:
            continue
        base, scale, ext = r
        groups[base].setdefault(scale, (f, ext))

    if not groups:
        sys.exit("No PNG/JPG files found in the export directory.")

    out.mkdir(parents=True, exist_ok=True)
    (out / "Contents.json").write_text(
        json.dumps({"info": {"version": 1, "author": "xcode"}}, indent=2),
        encoding="utf-8",
    )
    for base, scale_map in sorted(groups.items()):
        safe = re.sub(r"[^\w\s-]", "", base).strip().replace(" ", "_") or "Asset"
        imageset = out / f"{safe}.imageset"
        imageset.mkdir(exist_ok=True)
        entries = {}
        for scale, (src_file, ext) in scale_map.items():
            suffix = "" if scale == "1x" else f"@{scale}"
            dest_name = f"{safe}{suffix}.{ext}"
            shutil.copy2(src_file, imageset / dest_name)
            entries[scale] = dest_name
        (imageset / "Contents.json").write_text(
            json.dumps(contents_for(entries), indent=2), encoding="utf-8",
        )
        missing = [s for s in ("1x", "2x", "3x") if s not in entries]
        tail = f", missing {missing}" if missing else ""
        print(f"  {imageset.name}: have {sorted(entries)}{tail}")

if __name__ == "__main__":
    main()

Edge cases worth knowing before you run it:

  • The script always emits 1x/2x/3x slots in Contents.json and only fills in filename for scales that exist. Xcode flags any truly missing scale in the catalog editor so you can add it or switch the image set to Single Scale.
  • Characters outside letters, digits, underscore, hyphen and whitespace are stripped from folder names so the imageset directory is a valid identifier.
  • If a Figma export uses a bare name for 1x (Icon.png) plus Icon@2x.png and Icon@3x.png, the script groups them correctly under one Icon.imageset.

Move the bundle to the rented Mac and open it in Xcode

Because the $29 VM uses a browser console (no inbound SSH, no verified native VNC, no guaranteed bidirectional clipboard or drag-and-drop file upload), plan the transfer deliberately instead of expecting a desktop file dialog that reaches your Windows machine:

  • Commit MyAssets.xcassets to the same git repository as the Xcode project and git pull inside the VM's desktop terminal. This is usually the easiest option because the same version lands in source control for the developers.
  • Or zip the folder locally, upload it to a store the Mac can reach outbound (your own cloud drive, a private S3 bucket, an authenticated artifact server), then download it inside the VM's browser session.

Once Xcode is installed on the Mac, open your project, right-click the existing Assets.xcassets in the Project navigator, choose Show in Finder, drop the new imagesets next to the existing ones, then build to an iOS Simulator with Cmd+R. If the asset catalog editor flags a missing scale, either add it in Figma and re-export, or toggle the image set to Single Scale in the Attributes Inspector. Note that remote previews over a browser are not colour-calibrated and are not a substitute for checking the final build on real hardware before release.

Pick the right Figment plan for this job

The Figma configuration and the Python conversion run on Windows, Linux or macOS. Only the preview step needs a Mac, so size the plan by how often you preview and how heavy the Simulator work is.

What the Mac is doingSuggested Figment planMonthly list price
Catalog validation, Contents.json checks, occasional Simulator spot-checks on an Xcode 26-or-earlier toolchain you install yourselfVirtual Intel macOS VM$29
Daily interactive Simulator, GPU-backed previews, Xcode 27, or SDKs that require Apple siliconM-series Mac (chip/RAM/disk confirmed in quote)from $95
A shared design-handoff Mac with CI runners and managed configurationManaged Build Mac (one M-series Mac, CI setup, backups, monitoring, 30 minutes monthly support)$249

A short cost worksheet: a solo designer who previews two or three afternoons a month stays on the $29 VM for $29 x 12 = $348 a year. A designer pairing daily with iOS engineers on a current Xcode will typically want the M-series tier at $95 a month or more, priced per the quote, which is $1,140 a year at the entry list price. A team that wants the Mac to also run CI and keep backups should compare the $249 Managed Build Mac against the time an in-house admin would otherwise spend on setup and monitoring.

To request a configuration — including the exact Xcode version your project targets and whether you need Apple silicon — use /mac-hosting/start. The full lineup lives at /mac-hosting.

Acceptance checklist before you hand off

Walk through this list before calling the asset bundle done:

  • The output folder opens in Xcode as a single .xcassets and every imageset is visible in the Project navigator.
  • Each image set shows filled slots at the scales you intended and either a filled or deliberately Single-Scale state for the rest.
  • A build to the iOS Simulator succeeds and the asset renders at the expected size on a device at 2x (for example iPhone SE) and at 3x (for example iPhone 15 Pro).
  • The design source in Figma, the converted .xcassets in git, and the Xcode build all agree on the base filename.
  • Any renames you did to avoid @2x collisions are reflected back in the Figma layer names so the next export round-trip stays clean.

That is the whole pipeline: Figma for the slices, a short Python script for the Apple layout, and a Mac — virtual Intel for lightweight validation or M-series when the Simulator work gets real — for the one step that actually needs macOS.


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms