SketchのiOS UI Design Templateを🈲で埋め尽くすプラグイン

  • 14
    いいね
  • 0
    コメント

はじめに

そんなに真面目じゃないネタ記事です。
今回はタイトルのようなSketch Pluginを作ることでレイヤー操作や調査方法について学んでいきます。

作るプラグイン

実行するとレイヤーの背景をマル禁画像に書き換えます。

kin.png

以下は実際に実行したもの(今回の完成品)になります。
普通のレイヤーやシンボル、またレイヤー名も🈲に変わります。

5a5d9011f1ec96064c90878bdf2248b7.gif

🈲だとバカっぽいですが画像を「Confidential(機密)」にすればそれっぽい感じになりそうです。

Confidential.png

image2.gif

注意事項

  • これは開発練習用のプラグインです。
  • 大切なSketchファイルでは実行しないでください。
  • 使用者の責任でご使用ください。
  • 実行してもUndoすれば戻せます。

なお以下では、「レイヤーを🈲に変える」という表記が面倒なので「Kinする」という独自表記にさせていただきます。

ソース

以下に置いてあります。画像ファイルもあります。
https://github.com/hanamiju/SketchConfidentialPlugin

なおこのエントリーでは細かくsketch pluginの作り方は示しません。公式ドキュメント等をご覧ください。
今回いじるファイルは以下のファイルのみです。

Confidential.sketchplugin/Contents/Sketch/script.cocoascript

開発

画像を読み込む

まずはKin画像を読み込みます。
Cocoaの心得が少しあれば普通に理解できると思います。

ソース

script.cocoascript
    var plugin = context.plugin
    var imageFilePath = plugin.urlForResourceNamed("kin.png")         // 画像のURL取得
    var imageData = [NSData dataWithContentsOfURL:imageFilePath] // データに起こす
    var image = NSImage.alloc().initWithData(imageData)          // imageオブジェクトにする

解説

今回の.sketchpluginファイルは以下の構成になっています。

Confidential.sketchplugin
 └ Contents
    ├ Sketch
    │  ├ script.sketchscript
    │  └ manifest.json
    │
    └ Resource
       └ kin.png

urlForResourceNamed() でResourseフォルダのファイル名を指定すると画像のURL取得が取得できるのでこれをもとに画像をオブジェクト化します。

選択したShapeLayerをKinする

選択したレイヤーを取得してKinをしていきます。
Sketchの操作でいう、レイヤーを選択してFillの方法をImageにしてKin.pngを入れるのと同じことをします。

スクリーンショット_2016-12-17_21_22_24.png

ソース

script.cocoascript
var onRun = function(context) {
    var plugin = context.plugin
    var imageFilePath = plugin.urlForResourceNamed("kin.png")
    var imageData = NSData.dataWithContentsOfURL(imageFilePath)
    var image = NSImage.alloc().initWithData(imageData)

    changeLayers(context, image)
};

function changeLayers(context, image) {
    var selections = context.selection  // 選択したレイヤー
    // 選択したレイヤー分だけKinする
    change(selections, image)
}

function change(layers, image) {
    for(var i = 0; i < layers.length; i++){
        var layer = layers[i];
        if(layer.class() == MSShapeGroup){
            var fill = layer.style().fills().firstObject();
            if (fill != nil) {
                fill.setFillType(4);  // 塗りつぶし方法(画像)
                fill.setImage(MSImageData.alloc().initWithImage_convertColorSpace(image, false));
                fill.setPatternFillType(3);  // 塗りつぶしパターン(レイヤーにFit)
            }
        }
    }
}

解説

changeLayers関数

context.selection で現在選択されているレイヤーが取得できます。

change関数

レイヤーはMSShapeGroupというクラスなのでレイヤーにこいつが入っていたらKinしていきます。
ちなみに、ソースのコメントに書いてある塗りつぶし情報/塗りつぶしパターンはSketch.appでいう以下の選択項目です。

スクリーンショット_2016-12-17_16_45_39.png

スクリーンショット_2016-12-17_16_45_58.png

結果

b5f65368b4432b6a685b66dd7eaf8522.gif

レイヤー単体ではKinできましたが、グループレイヤーには適用できていないようです。

選択したグループをKinする

グループにもKinを対応させていきます。

ソース

script.cocoascript
// onRun(), changeLayers()の記載は省略

function change(layers, image) {
    for(var i = 0; i < layers.length; i++){
        var layer = layers[i];
        if(layer.class() == MSShapeGroup){
            var fill = layer.style().fills().firstObject();
            if (fill != nil) {
                fill.setFillType(4);
                fill.setImage(MSImageData.alloc().initWithImage_convertColorSpace(image, false));
                fill.setPatternFillType(3);
            }
        // 追加
        } else if (isGroup(layer)) {
            change(layer.layers(), image)
        } 
    }
}

// 追加
var isGroup = function(layer){
    return layer.isMemberOfClass(MSLayerGroup.class())
};

解説

isGroup でレイヤーがグループ(MSLayerGroup)かどうかを判別する処理を実装します。
MSLayerGroupオブジェクトはプロパティにlayersという子レイヤーを持っているのでこれをまたchange関数に投げます。

結果

group.gif

グループにもKinできましたが、まだうまく動いていないようです。
動いていないのはシンボルです。シンボルはgroupとは別のやり方でKinしてあげないといけなそうです。

選択したシンボルをKinする

次はレイヤーがシンボルだった時の処理を書きたいです。
シンボルはグループとは構成が違うようで子レイヤーのプロパティが見当たりませんでした。

しょうがないのでシンボルをグループに強制的に戻します。
そして、グループレイヤーに対してKinします。

ソース

script.cocoascript
// onRun(), changeLayers(), isGroupの記載は省略

function change(layers, image) {
    for(var i = 0; i < layers.length; i++){
        var layer = layers[i];
        if(layer.class() == MSShapeGroup){
            var fill = layer.style().fills().firstObject();
            if (fill != nil) {
                fill.setFillType(4);
                fill.setImage(MSImageData.alloc().initWithImage_convertColorSpace(image, false));
                fill.setPatternFillType(3);
            }
        } else if (isGroup(layer)) {
            change(layer.layers(), image)
        // 追加
        } else if (isSymbol(layer)) {
            changeSymbolInstance(layer, image)
        }
    }
}

// 追加
function changeSymbolInstance(symbolInstance, image) {
    // シンボルをグループレイヤーに直す
    var layer = symbolInstance.detachByReplacingWithGroup()
    change(layer.layers(), image)
}

// 追加
var isSymbol = function(layer){
    return layer.isMemberOfClass(MSSymbolInstance.class())
};

解説

シンボルはMSSymbolInstanceクラスなのでisSymbol でレイヤーがMSSymbolInstanceクラスか判断します。
そしてMSSymbolInstance.detachByReplacingWithGroup()でシンボルをグループに戻します。
グループに直したものをchange関数でKinします。

結果

symbol.gif

これでシンボルも無理矢理Kinできました。
いい感じになってきたのでテキストもKinにしていきましょう。

選択したTextLayerをKinする

テキストは画像に変えられないので、文字を「🈲」に変えてしまいましょう。

ソース

script.cocoascript

function change(layers, image) {
    for(var i = 0; i < layers.length; i++){
        var layer = layers[i];
        if(layer.class() == MSShapeGroup){
            var fill = layer.style().fills().firstObject();
            if (fill != nil) {
                fill.setFillType(4);
                fill.setImage(MSImageData.alloc().initWithImage_convertColorSpace(image, false));
                fill.setPatternFillType(3);
            }
        // 追加
        } else if (layer.class() == MSTextLayer) {
            layer.stringValue = "🈲"
        }  else if (isGroup(layer)) {
            change(layer.layers(), image)
        } else if (isSymbol(layer)) {
            changeSymbolInstance(layer, image)
        }
    }
}

解説

テキストレイヤーのクラスはMSTextLayerです。こいつがレイヤーできたらstringValueプロパティにKinします。

結果

text.gif

テキストもKinできました。
ここまでくるとサイドバーのレイヤー名もKinしたいところです。

選択したレイヤー名をKinする

レイヤー名もテキストと同じく文字を「🈲」に変えます。

ソース

script.cocoascript

function change(layers, image) {
    for(var i = 0; i < layers.length; i++){
        var layer = layers[i];
        // 追加
        layer.setName("🈲")
        if(layer.class() == MSShapeGroup){
            var fill = layer.style().fills().firstObject();
            if (fill != nil) {
                fill.setFillType(4);
                fill.setImage(MSImageData.alloc().initWithImage_convertColorSpace(image, false));
                fill.setPatternFillType(3);
            }
        } else if (layer.class() == MSTextLayer) {
            layer.stringValue = "🈲"
        }  else if (isGroup(layer)) {
            change(layer.layers(), image)
        } else if (isSymbol(layer)) {
            changeSymbolInstance(layer, image)
        }
    }
}

解説

レイヤーのsetName()メソッドにKinをするだけです。

結果

layername.gif

ここまでくるといよいよ狂気じみてきました。

今まではわざわざ選択したレイヤーに対してのみKinしてきましたが、いちいち選択するのもかったりいので次はpage内の全レイヤーを強制的にKinしていきます。

選択していないLayerもKinする

現在のpageのすべてのlayerが取れるのでこれをKinすればいちいち選択することもなさそうです。

ソース

script.cocoascript
function changeLayers(context, image) {
    var doc = context.document
    // 変更
    var layers = doc.currentPage().layers()
    change(layers, image)
}

解説

なし

結果

currerntpage.gif

壮観ですね。

宿題

他にも色々Kinしてみたい感じではありますが、これより先は僭越ながらここまでお読みいただいた方への宿題とさせていただきたいです。

  • SketchファイルすべてのpageをKinする: 10点
  • ShapeLayerのBorderを全て赤に変える: 5点
  • 画像Layerがあったら、画像Layer削除して同じ位置に同じサイズのLayerを作り、それをKinする: 30点
  • Undoできなくさせる(UndoManagerを見つけ出してUndoをリセットすればできるはず...): 50点

↑のいずれかでもできましたらプルリクいただけると嬉しいです。

https://github.com/hanamiju/SketchConfidentialPlugin

プロパティ/メソッドの調査方法

Sketchの独自クラスを駆使していかないと、Kinがうまくできないのですが、公式リファレンスを見てもあまり詳しい情報が載っていません。

そこでSketch.appのヘッダーを見て良さそうなプロパティ/メソッドを探します。
class-dumpする方法もありますが、私は面倒なのでSketch-Headersにお世話になっています。

Sketch-Headersはclass-dumpしたヘッダーファイル群のリポジトリです。
割と頻繁に更新されています。

例

シンボルをKinした時に必要だったMSSymbolInstance.detachByReplacingWithGroup()をどう調べたかの例です。

レイヤーのクラス名を調べる

最初の時点ではMSSymbolInstanceクラスがあることも知りません。
まずKinできなかったレイヤーのクラス名を調べます。
普通に以下のようにログを仕掛けてプラグインを実行します。

log(layer)

コンソールを確認すると以下のように表示されます。

2016/12/17 18:11:02.025 Confidential (Sketch Plugin)[2158]: <MSSymbolInstance: 0x7f9a81041c00> hoge (BAFD957A-AAE2-4D58-B330-792B6CEECEB7)

ここでMSSymbolInstanceクラスの存在を知ります。

Sketch-Headersを見る

クラス名がわかったのでSketch-Headerを見ます。
ここで子レイヤーの情報あるかなとか、それっぽい関数ないかなと調べて色々試します。

その結果、今回はdetachByReplacingWithGroup()使ってシンボルをぶっ壊そうという結論になりました。

むすび

Kinできるレイヤーが増えるということは、あなたがPluginで操作できる領域が増えるということです。ぜひ、あらゆるSketchファイルをKinしてPlugin力を上げていってください。

参考

この投稿は Goodpatch Advent Calendar 2016 の 18日目の記事です。