I'm trying to create an SKSpriteNode
with an image from the SF Symbols font, and while I can do it, I can't seem to make it any color other than black.
Here's my code:
let image = UIImage.init(systemName: "gear")
let colored = image!.withTintColor(.red)
let texture = SKTexture.init(image: colored)
let sprite = SKSpriteNode.init(texture: texture, size: CGSize.init(width: 32, height: 32))
Unfortunately, the resultant sprite always comes out in black (and not red).
What am I doing wrong?
I think the problem is the UIImage is a vector graphic, and only UIImageViews properly handle them. Perhaps we can force it to a bitmap image to get it to work properly.
Here is some experimental code you can try:
let image = UIImage(systemName: "gear").withTintColor(.red)
let data = image.pngData()
let newImage = UIImage(data:data)
let texture = SKTexture(image: newImage)
let sprite = SKSpriteNode(texture: texture,size: CGSize(width: 32, height: 32))
You have to set colorBlendFactor
on the sprite.
func makeSprite(symbolName: String) -> SKSpriteNode {
let image = Image(systemName: symbolName)
// Thanks Oskar!
// https://stackoverflow.com/a/69315037/1610473
// See my adapted version of his code further down; I
// think you won't need it if you're on iOS, meaning
// using UIImage, but that's all black magic to me.
let renderedByOskar = image.renderAsImage()!
let texture = SKTexture(image: renderedByOskar)
let sprite = SKSpriteNode(texture: texture)
sprite.color = .green
sprite.colorBlendFactor = 1 // <-- This
return sprite
}
A green symbol, thanks to black magic:
Many thanks to Oskar for the rendering extensions that enable me to do this on macOS, meaning, without UIImage
:
class NoInsetHostingView<V>: NSHostingView<V> where V: View {
override var safeAreaInsets: NSEdgeInsets {
return .init()
}
}
extension Image {
func renderAsImage() -> NSImage? {
let view = NoInsetHostingView(rootView: self)
view.setFrameSize(view.fittingSize)
return view.bitmapImage()
}
}
public extension NSView {
func bitmapImage() -> NSImage? {
guard let rep = bitmapImageRepForCachingDisplay(in: bounds) else {
return nil
}
cacheDisplay(in: bounds, to: rep)
guard let cgImage = rep.cgImage else {
return nil
}
return NSImage(cgImage: cgImage, size: bounds.size)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With