Help replacing an image via script

Ahoy. I was sad to see that the old sketchplugins.com forum was gone. Glad to be here. Hoping someone can help.

I have a plugin that updates placed images after they’ve been changed. An automated “Replace Image…” if you will. A recent Sketch update broke the old Obj-C methods I used so I’m trying to get it working again. I think I’m on the right track, but what I’ve come up with still doesn’t work.

The script doesn’t throw an error, nor does the image appear to change visually. It looks like nothing happened. But if you save the document and re-open it, the image is seemingly gone. The layer is still there, it has the correct dimensions, you can move it via keyboard, mouse, or inspector. It’s just totally transparent.

I’ve pasted below reduced version of what I have so far. To use it, first place any image as a layer, select that layer, then run the following script, changing the imagePath variable to a different image file on your computer.

const sketch = require("sketch");

function updateBitmapLayer(layer,imagePath) {
  const Image = require('sketch/dom').Image;

  const newImage = new Image({image: imagePath});
  const newImageData = newImage.image;

  layer.image = newImageData;
}

let selectedLayer = context.selection.firstObject();
let imagePath = "/path/to/image.png";

updateBitmapLayer(selectedLayer,imagePath);

Thanks in advance.

Hey there,

I think you’re on the right track with this one – the only thing that prevents it from working is the way you access the currently selected image layer.

You’re mixing the old context.selection Objective-C API with the new JS API; try this instead:

const { Document } = require('sketch/dom');

function updateBitmapLayer(imageLayer, imagePath) {
  imageLayer.image = imagePath
}

let selectedLayer = Document.getSelectedDocument().selectedLayers.layers[0];
let newImagePath = "/path/to/image.png";

updateBitmapLayer(selectedLayer, newImagePath)
2 Likes

Also RIP http://sketchplugins.com :saluting_face:

1 Like

Great, that was helpful. I appreciate your help. That worked via the Run Script… command, but to integrate it into my plug-in, it seems that one problem solved leads to another problem to fix.

So I originally wrote the plugin ( GitHub - frankko/Place-Linked-Bitmap: A plugin to place external bitmap files into Sketch and update Sketch layers after external bitmaps are updated ) almost ten years ago, so it’s mostly Obj-C.

The way it works is the file path for the image is stored as a key/value on the layer. That used to be a thing? I thought it was called tagging, but I could be wrong. I don’t know if there’s a modern way to do that. It might not have caught on. Anyway, the file path is stored, and when the command is run, all layers that have that data are iterated through and the file reloaded and replace the original.

This is how I’m doing it currently. This happens in one part of the script:

var doc = context.document;
var plugin = context.plugin;
var command = context.command;
var allLayers = [page children];
var validLayers = PlaceLinkedBitmap.findAllTaggedLayers(context,allLayers);

That function is defined like so:

"findAllTaggedLayers": function(context,layers) {
  var command = context.command;
  var foundLayers = [];

  for (var i = 0; i < [layers count]; i++) {
    var layer = layers[i];
    if ([command valueForKey:"originalURL" onLayer:layer]) {
      foundLayers.push(layer);
    }
  }
  return foundLayers;
},

Then the validLayers aka foundLayers array is looped through, and each item runs the following, which is mostly what I posted yesterday, which now looks like this, thanks to you:

"updateBitmapLayer": function(context,layer,url) {
  layer.image = url;
},

So if the old context is the problem, how can I translate a layer referenced in the old Obj-C style into the new JS API style?

Or am I just totally heading down the wrong path here?

Thanks again.

Quite the opposite, you’re on the right track to get the plugin working again – and there’s nothing wrong with actually mixing both Objective-C and JS APIs to get the job done as long as you’re careful not to break the boundaries between the two.

Here’s how to create a JS API object from a native Sketch layer and vice-versa:

const { Image } = require('sketch/dom');

// take a native Objective-C layer
let nativeImageLayer = context.selection[0]
// convert it to a JS object and call some useful methods 
let jsImageLayer = Image.fromNative(nativeImageLayer)
jsImageLayer.image = "/path/to/image.png"

// and back to a native layer
let backToNativeImageLayer = jsImageLayer.sketchObject

UPDATED:

Oh and one more thing: make sure this url you’re passing to an Image.image property is an actual JS string and not an NSString. You may convert the latter to the former with this little trick:

let anNSString = [NSString stringWithString: "/path/to/image.png"];
let jsString = anNSString + "";

log(typeof anNSString) // => object
log(typeof jsString)   // => string
1 Like

My hero. All set for now. Thanks so much for your help.

2 Likes

Just dropping by to say thanks to you both for providing and updating this plugin. :pray:

1 Like