Hello,
I have noticed a performance drop on SpriteKit-based projects running on iOS 26.0 (23A341).
Below is a SpriteKit scene used to test framerate on different devices:
import SpriteKit
import SwiftUI
class BareboneScene: SKScene {
    
    override func didMove(to view: SKView) {
        size = view.bounds.size
        anchorPoint = CGPoint(x: 0.5, y: 0.5)
        backgroundColor = .darkGray
        
        let roundedSquare = SKShapeNode(rectOf: CGSize(width: 150, height: 75), cornerRadius: 12)
        roundedSquare.fillColor = .systemRed
        roundedSquare.strokeColor = .black
        roundedSquare.lineWidth = 3
        addChild(roundedSquare)
        
        let action = SKAction.rotate(byAngle: .pi, duration: 1)
        roundedSquare.run(.repeatForever(action))
    }
    
}
struct BareboneSceneView: View {
    var body: some View {
        SpriteView(
            scene: BareboneScene(),
            debugOptions: [.showsFPS]
        )
        .ignoresSafeArea()
    }
}
#Preview {
    BareboneSceneView()
}
The scene is very simple, yet framerate drops to ~40 fps as shown by the Metal HUD. Tested on:
iPhone 13, iOS 26.0: framerate drops to 40 fps. Sometimes it runs at near 60fps. But if the screen is touched repeatedly, the framerate drops to 40-50 fps again.
iPhone 11 Pro, iOS 26.0: ~40fps.
iPad 9th Gen, iOS 18.6.2: 60fps, no issues.
See screenshots attached. These numbers were observed by me and members of our beloved SpriteKit Discord server.
Thank you for your attention.
                    
                  
                SpriteKit
RSS for tagDrawing shapes, particles, text, images, and video in two dimensions using SpriteKit.
  
    
    Selecting any option will automatically load the page
  
  
  
  
    
  
  
            Post
Replies
Boosts
Views
Activity
                    
                      I have published a number of games that use SpriteKit for everything important. Since the release of macOS Tahoe, I've had a lot of end user reports saying that sound effects have stopped working in many (but not all) of my titles.
I'm not doing anything unusual here – typical code is:
sndGameOver = [SKAction playSoundFileNamed:@"Audio/GameOver.wav" waitForCompletion:YES];
Then at the appropriate time:
[self runAction:sndGameOver];
Has anyone else encountered this? The code still works fine on previous operating systems, and appears to be fine on iOS too. Has something changed in macOS Tahoe?
I'm at a bit of a loss. There's nothing obviously different between the titles that do work and the titles that don't.
Suggestions welcomed!
Thanks
                    
                  
                
                    
                      Hi,
I've just moved my SpriteKit-based game from UIView to SwiftUI + SpriteView and I'm getting this mesage
Adding 'GCControllerView' as a subview of UIHostingController.view is not supported and may result in a broken view hierarchy. Add your view above UIHostingController.view in a common superview or insert it into your SwiftUI content in a UIViewRepresentable instead.
Here's how I'm doing this
struct ContentView: View {
    @State var alreadyStarted = false
    let initialScene = GKScene(fileNamed: "StartScene")!.rootNode as!  SKScene
    var body: some View {
        ZStack {
            SpriteView(scene: initialScene, transition: .crossFade(withDuration: 1), isPaused: false , preferredFramesPerSecond: 60)
                .edgesIgnoringSafeArea(.all)
                .onAppear {
                    if !self.alreadyStarted {
                        self.alreadyStarted.toggle()
                        initialScene.scaleMode = .aspectFit
                    }
                }
            VirtualControllerView()
                .onAppear {
                    let virtualController = BTTSUtilities.shared.makeVirtualController()
                    BTTSSharedData.shared.virtualGameController = virtualController
                    BTTSSharedData.shared.virtualGameController?.connect()
                }
                .onDisappear {
                    BTTSSharedData.shared.virtualGameController?.disconnect()
                }
        }
    }
}
struct VirtualControllerView: UIViewRepresentable {
    
    func makeUIView(context: Context) -> UIView {
        let result = PassthroughView()
        return result
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
    }
}
class PassthroughView: UIView {
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        for subview in subviews.reversed() {
            let convertedPoint = convert(point, to: subview)
            if let hitView = subview.hitTest(convertedPoint, with: event) {
                return hitView
            }
        }
        
        return nil
    }
}
                    
                  
                
                    
                      I've had no issue calling image files in my .swift files, but they are causing  crashes when used in my .SKS files. When I set a sprite texture to an image  in the inspector/ editor bar, at runtime when that sprite is being called I get the error: "Cannot get value with size 16. The type encoded as {CGRect={CGPoint=dd}{CGSize=dd}} is expected to be 32 bytes." From my research it has something to do with Apple switching from 32 to 64 bite machines. From chatGPT “SpriteKit under the hood uses NSKeyedUnarchiver to load your .sks file. That unarchiver decodes each archived property by reading a fixed‑size blob of bytes and mapping it into a C struct. In your case it ran into a mismatch”.  I am using a 64-bite machine to write my code and 64-bite simulators and physical devices, so there isn't a clear cause of the mismatch. My scenes play fine in Xcode 16's preview window and my code builds, it just crashes at runtime.
When I don’t use image textured assets in the SKS file it works fine. It loads animated labels, and plain color squares. I’ve been able to work around this for static things like a sprite with a background texture by. in a normal non-game swift file, writing code like:
if let scene = SKScene(fileNamed: "GameScene2") {
    let bg = SKSpriteNode(imageNamed: "YourBackgroundImage")
    bg.position = CGPoint(x: scene.frame.midX, y: scene.frame.midY)
    bg.zPosition = -1
    scene.addChild(bg)
}
The issue now is I want to make a particle emitter and other non static sprites, but my understanding of their properities isn’t deep enough to create them without the editor. Also when I set SKTexture in a swift file that causes the same runtime crash with the 16/32 error. Could you help me figure out how to fix the bug so I can use the editor again? Otherwise could you help me figure out how to write a workaround like I do for background images? I have a feeling the answer is in writing my own NSKeyedUnarchiver but I don’t know how to make sure it’s called instead of the default one. I've already tried cleaning my code multiple times and deleting and reading sprite nodes. Thank you.
                    
                  
                
                    
                      Can I use them in SK and do the animations work?
Thanks, Patrick
                    
                  
                
                    
                      Hello,
I'm getting this error when launching a SpriteKit Swift game in iOS 18+ on an iPhone 11 Pro, whose shell is partly damaged in the back:
CHHapticEngine.mm:1206  -[CHHapticEngine doStartWithCompletionHandler:]_block_invoke: ERROR: Player start failed: The operation couldn’t be completed. (com.apple.CoreHaptics error 1852797029.) 
Haptics do not work on this device, due to the damaged shell, so some error — which obviously occurs when calling start(completionHandler:) — is definitely expected; what is not expected is the main thread sometimes blocking for up to 5 seconds — although the method is not called from the main thread... the error itself is always displayed from some other secondary (system) thread. During this time, the main thread does not access the haptics engine at all; on average, it blocks once every four or five launches. In each launch (blocking or not), the 'nope' error is displayed ~5 seconds after trying to start the engine.
After going nuts with all kinds of breakpoints and instrumentation, I'm at a loss as to why the main thread would sometimes block...
Ideas, anyone?
Thank you,
D.
                    
                  
                
                    
                      Hello,
Could someone post code that shows how to implement GCVirtualController to move a box around the screen?
I've been poking around with GCVirtualController and gotten as far as having the D-pad and A B buttons appear on the display. But how do I make it do anything?
                    
                  
                
              
                
              
              
                
                Topic:
                  
	
		Graphics & Games
  	
                
                
                SubTopic:
                  
                    
	
		SpriteKit
		
  	
                  
                
              
              
              
  
  
    
    
  
  
              
                
                
              
            
          
                    
                      Given a graph with added obstacles I want to make a copy of it.
When I make the copy:
currentGrath added 20 obstacles.
var newGrapth = currentGrath.copy() as? GKObstacleGraph
newGrapth2.removeObstacles([newGrapth!.obstacles.first!])
This returns a BAD ACCESS.
I don't understand what's going on or what the problem is.
If I do this same thing with the main network there is no problem:
currentGrath.removeObstacles([currentGrath!.obstacles.first!])
Thanks for the help
                    
                  
                
              
                
              
              
                
                Topic:
                  
	
		Graphics & Games
  	
                
                
                SubTopic:
                  
                    
	
		SpriteKit
		
  	
                  
                
              
              
              
  
  
    
    
  
  
              
                
                
              
            
          
                    
                      I've just started working on my first SpriteKit game that will eventually run on both tvOS and iOS and am looking at how to build a "button". So far, I've got a custom node that looks like:
class MyButton: SKSpriteNode {
  ...
#if os(tvOS)
    override var canBecomeFocused: Bool {
        true
    }
    override func didUpdateFocus(...) {
       ...
    }
#endif  
}
The above let me nicely handle focus changes in tvOS and now I'm looking at reacting to selecting the button.
Searching around, all the articles/questions/posts are from 2015-2016 - which is a LOOOONG time ago. Most of the guidance appears to be to add a tap gesture recognizer in the owning scene and getting the scene to hand it off to the button.  That seems pretty brittle and I'd much prefer if the button itself is responsible for its own tap management.
So, I guess my question is whether I should just add a gesture recognizer to my custom button class? Is this inefficient if I end up having 7-8 buttons on the screen and each one has its own gesture recognizer?
Somewhat related, all of the 10-year-old advice is that if we add recognizers to scenes, then they need to be removed from the view controller... however, in the modern day world with SwiftUI, my project doesn't even have a view controller (yet, anyway)... what gesture recognizer lifecycle management do I need in a SpriteKit scene that is presented within a SpriteKitView?
Or, is there a better way? I was kind of hoping that overriding pressesBegan() (or something similar) in my custom button might have been triggered on tvOS (like touchesBegan() lets me manage touches for the iOS variant of my app)
Any pointers or suggestions would be gladly received. Thanks.
                    
                  
                
                    
                      So I'm trying to use SpriteKit to make the background of my game. The walls have alpha 1.0, and the safe area alpha 0 and fully transparent. (e.g. a big black square with a smaller transparent square in the middle of it). Yet sprite kit always assume the entire image is either fully opaque or fully transparent. That defies its purpose isn't it? Is there a way to make this work?
                    
                  
                
                    
                      Hello reader,
I am facing an issue that I am not able to resolve. I have been able to create a demo project that demonstrates the issue, which I hope enables you to have a look as well and hopefully find a way to resolve it.
What is the issue:
I am using SKTileMapNode in order to draw tile maps. Instead of using the tilesets as you can use from within the Xcode editor, I prefer to do it all programmatically using tilesheets (for a plethora of reasons that I will leave out of this equation).
This is the code of the gameScene:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
    private let tileSize = CGSize(width: 32, height: 32)
    override func didMove(to view: SKView) {
        super.didMove(to: view)
        let tileSet = createTileSet()
        let tileMap = SKTileMapNode(tileSet: tileSet,
                                    columns: 100,
                                    rows: 100,
                                    tileSize: tileSize)
        for column in 0..<tileMap.numberOfColumns {
            for row in 0..<tileMap.numberOfRows {
                guard let tileGroup = tileSet.tileGroups.randomElement() else {
                    fatalError()
                }
                tileMap.setTileGroup(tileGroup, forColumn: column, row: row)
            }
        }
        addChild(tileMap)
    }
    private func createTileSet() -> SKTileSet {
        let tileSheetTexture = SKTexture(imageNamed: "terrain")
        var tileGroups = [SKTileGroup]()
        let relativeTileSize = CGSize(width: tileSize.width/tileSheetTexture.size().width,
                                      height: tileSize.height/tileSheetTexture.size().height)
        for idx in 0...2 {
            for jdx in 0...2 {
                let tileTexture = SKTexture(rect: .init(x: CGFloat(idx) * relativeTileSize.width,
                                                        y: CGFloat(jdx) * relativeTileSize.height,
                                                        width: relativeTileSize.width,
                                                        height: relativeTileSize.height),
                                            in: tileSheetTexture)
                let tileDefinition = SKTileDefinition(texture: tileTexture,
                                                      size: tileSize)
                let tileGroup = SKTileGroup(tileDefinition: tileDefinition)
                tileGroups.append(tileGroup)
            }
        }
        let tileSet = SKTileSet(tileGroups: tileGroups)
        return tileSet
    }
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        presentSceneAgain()
    }
    func presentSceneAgain() {
        if let frame = view?.frame {
            view?.presentScene(GameScene(size: frame.size),
                               transition: .doorsCloseHorizontal(withDuration: 1.0))
        }
    }
}
This demo project create a tilemapnode of 100 X 100 tiles. Then, it fills these 10.000 tiles with a random tile from the tilesheet named "terrain.png". This tile sheet contains many tiles, but I only take the 9 tiles (3 X 3) from the lower left corner as a random tile option.
Thus, the 10.000 tiles get filled with one of these 9 tiles. So it doesnt look pretty or anything, but that isnt the purpose.
Now, to create these 9 tile textures, I use the SKTexture(rectIn:) method on the source texture being "terrain.png".
I think the code is quite clear in itself, but so far the explanation. When you run it, you should see the map being rendered.
When you tap the scene, the scene will present a new instance of the scene. Not more than that.
Now, when you do this, have a look at the RAM usage of the app. You will see it steadily increases over time, each time you click the scene and a new scene is presented.
I looked deeper into what is happening, and what I see in the memory graph, is that for every present of the scene that is done, there are 3 SKTexture instances being created that are never released. The first time the scene is rendered, there 11 SKTexture instances allocated (I dont know why there are 11 though. I would expect 10: the source texture and the 9 tile textures).
But then as mentioned, after a tap and a new present, I get 14 SKTexture, of which 3 are zombies, see image leak_1.
Moreover, Xcode reports multiple additional leaks from Jet and Metal allocations, see image leak_all.
As far as I know, the code presented is not retaining any references that it should not, and I suspect this leaks are happening somewhere inside SpriteKit. But I am not able to find exactly where, or how to resolve it.
I hope someone can help with this issue.
                    
                  
                
                    
                      When running the sample code below, every 3 seconds the middle sprite is replaced by a new one. When this happens, most of the time a flicker is noticeable. When recording the screen and stepping through the recording frame by frame, I noticed that the flicker is caused by a temporary reordering of the nodes’. Below you find two screenshots of two consecutive frames where the reordering is clearly visible.
This only happens for a SpriteKit scene used as an overlay for a SceneKit scene. Commenting out
buttons.zPosition = 1
or avoiding the fade in/out animations solves the issue.
I have created FB15945016.
import SceneKit
import SpriteKit
class GameViewController: NSViewController {
    
    let overlay = SKScene()
    var buttons: SKNode!
    var previousButton: SKSpriteNode!
    var nextButton: SKSpriteNode!
    var pageContainer: SKNode!
    var pageViews = [SKNode]()
    var page = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let scene = SCNScene(named: "art.scnassets/ship.scn")!
        let scnView = self.view as! SCNView
        scnView.scene = scene
        
        overlay.anchorPoint = CGPoint(x: 0.5, y: 0.5)
        scnView.overlaySKScene = overlay
        
        buttons = SKNode()
        buttons.zPosition = 1
        overlay.addChild(buttons)
        
        previousButton = SKSpriteNode(systemImage: "arrow.uturn.backward.circle")
        previousButton.position = CGPoint(x: -100, y: 0)
        buttons.addChild(previousButton)
        
        nextButton = SKSpriteNode(systemImage: "arrow.uturn.forward.circle")
        nextButton.position = CGPoint(x: 100, y: 0)
        buttons.addChild(nextButton)
        
        pageContainer = SKNode()
        pageViews = [SKSpriteNode(systemImage: "square.and.arrow.up"), SKSpriteNode(systemImage: "eraser")]
        overlay.addChild(pageContainer)
        
        setPage(0)
        Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { [self] _ in
            setPage((page + 1) % 2)
        }
    }
    
    func setPage(_ page: Int) {
        pageViews[self.page].run(.sequence([
            .fadeOut(withDuration: 0.2),
            .removeFromParent()
        ]), withKey: "fade")
        self.page = page
        let pageView = pageViews[page]
        pageView.alpha = 0
        pageView.run(.fadeIn(withDuration: 0.2), withKey: "fade")
        pageContainer.addChild(pageView)
    }
    
    override func viewDidLayout() {
        overlay.size = view.frame.size
    }
    
}
extension SKSpriteNode {
    
    public convenience init(systemImage: String) {
        self.init()
        let width = 100.0
        let image = NSImage(systemSymbolName: systemImage, accessibilityDescription: nil)!.withSymbolConfiguration(.init(hierarchicalColor: NSColor.black))!
        let scale = NSScreen.main!.backingScaleFactor
        image.size = CGSize(width: width * scale, height: width / image.size.width * image.size.height * scale)
        texture = SKTexture(image: image)
        size = CGSize(width: width, height: width / image.size.width * image.size.height)
    }
    
}
                    
                  
                
                    
                      Into a SKScene, I add a SCNSphere as a child of SKShapeNode, as depicted below.
When the sphere hit another node (the fence in the example) the sphere is deformed as it were elastic.
I didn't found any information about elastic properties.
Someone know a way to avoid the deformation?
import SwiftUI
import SpriteKit
import SceneKit
@main struct MyApp: App
{
	var body: some Scene
	{
		WindowGroup {SpriteView(scene: GameSceneSK(size: UIScreen.main.bounds.size))}
	}
}
class GameSceneSK: SKScene
{
	override func sceneDidLoad() {
		var fencePoints = [
			CGPoint(x: 300, y: 0), CGPoint(x: 300, y: 400), CGPoint(x: 0, y: 400)
		]
		let fence = SKShapeNode(points: &fencePoints,
								count: fencePoints.count)
		fence.physicsBody = SKPhysicsBody(edgeChainFrom: fence.path!)
		addChild(fence)
		let sphereGeometry = SCNSphere(radius: 20)
		let sphereNode = SCNNode(geometry: sphereGeometry)
		let sphereScnScene = SCNScene()
		sphereScnScene.rootNode.addChildNode(sphereNode)
		let ball3D = SK3DNode(viewportSize: CGSize(width: 40,
												   height: 40))
		ball3D.scnScene = sphereScnScene
		let ball = SKShapeNode(circleOfRadius: 20)
		ball.physicsBody = SKPhysicsBody(circleOfRadius: 20)
		ball.addChild(ball3D)
		physicsWorld.gravity = CGVector(dx: 0.2, dy: 0.2)
		addChild(ball)
	}
}
                    
                  
                
                    
                      I have an oval UIBezierPath with a moving SKSpriteNode,
I stop its motion and record the stopped position. I then restart this motion and want it to restart where it initially stopped.
Works great if motion is not stopped. Movement is great around entire oval Path.
Also works great as long as this stop-restart sequence occurs along the top half of the oval UIBezierPath. However, I have problems along the bottom half of this Path --  it stops okay, but the restart position is not where it previously stopped.
My method to create this oval UIBezierePath is as follows:
func createTrainPath() {
    
    trainRect = CGRect(x: tracksPosX - tracksWidth/2,
                       y: tracksPosY - tracksHeight/2,
                       width: tracksWidth,
                       height: tracksHeight)
    // these methods come from @DonMag
    trainPoints = generatePoints(inRect: trainRect,
                                 withNumberOfPoints: nbrPathPoints)
    trainPath = generatePathFromPoints(trainPoints!,
                                       startingAtIDX: savedTrainIndex)
            
}   // createTrainPath
My method to stop this motion is as follows:
func stopFollowTrainPath() {
    
    guard (myTrain != nil) else { return }
    myTrain.isPaused = true
    savedTrainPosition = myTrain.position
    // also from @DonMag
    savedTrainIndex = closestIndexInPath(
                                  trainPath,
                                  toPoint: savedTrainPosition) ?? 0
}   // stopFollowTrainPath
Finally, I call this to re-start this motion:
func startFollowTrainPath() {
                           
    var trainAction = SKAction.follow(trainPath.cgPath,
                                      asOffset: false,
                                      orientToPath: true,
                                      speed: thisSpeed)
    trainAction = SKAction.repeatForever(trainAction)
    myTrain.run(trainAction, withKey: runTrainKey)
    myTrain.isPaused = false
}   // startFollowTrainPath
Again, great if motion is not stopped. Movement is great around entire oval Path.
Again, no problem for stopping and then restarting along top half of oval .. the ohoh occurs along bottom half.
Is there something I need to do within GameScene's update method that I am missing? For example, do I need to reconstruct my UIBezierPath? every time my node moves between the top half and the bottom half and therein account for the fact that the node is traveling in the opposite direction from the top half?
                    
                  
                
              
                
              
              
                
                Topic:
                  
	
		Graphics & Games
  	
                
                
                SubTopic:
                  
                    
	
		SpriteKit
		
  	
                  
                
              
              
              
  
  
    
    
  
  
              
                
                
              
            
          
                    
                      I'm trying to make a magnifying glass that shows up when the user presses a button and follows the user's finger as it's dragged across the screen.
I came across a UIKit-based solution (https://github.com/niczyja/MagnifyingGlass-Swift), but when implemented in my SKScene, only the crosshairs are shown. Through experimentation I've found that magnifiedView?.layer.render(in: context)  in:
   public override func draw(_ rect: CGRect) {
        guard let context = UIGraphicsGetCurrentContext() else { return }
        
        context.translateBy(x: radius, y: radius)
        context.scaleBy(x: scale, y: scale)
        context.translateBy(x: -magnifiedPoint.x, y: -magnifiedPoint.y)
        
        removeFromSuperview() 
        magnifiedView?.layer.render(in: context) 
        magnifiedView?.addSubview(self)
    }
can be removed without altering the situation, suggesting that line is not working as it should. But this is where I hit a brick wall. The view below is shown but not offset or magnified, and any attempt to add something to context  results in a black magnifying glass.
Does anyone know why this is? I don't think it's an issue with the code, so I'm suspecting its something specific to SpriteKit or SKScene, likely related to how CALayers work.
Any pointers would be greatly appreciated.
.
.
.
Full code below:
import UIKit
public class MagnifyingGlassView: UIView {
    public weak var magnifiedView: UIView? = nil {
        didSet {
            removeFromSuperview()
            magnifiedView?.addSubview(self)
        }
    }
    
    public var magnifiedPoint: CGPoint = .zero {
        didSet {
            center = .init(x: magnifiedPoint.x + offset.x, y: magnifiedPoint.y + offset.y)
        }
    }
    
    public var offset: CGPoint = .zero
    
    public var radius: CGFloat = 50 {
        didSet {
            frame = .init(origin: frame.origin, size: .init(width: radius * 2, height: radius * 2))
            layer.cornerRadius = radius
            crosshair.path = crosshairPath(for: radius)
        }
    }
    
    public var scale: CGFloat = 2
    
    public var borderColor: UIColor = .lightGray {
        didSet {
            layer.borderColor = borderColor.cgColor
        }
    }
    
    public var borderWidth: CGFloat = 3 {
        didSet {
            layer.borderWidth = borderWidth
        }
    }
    
    public var showsCrosshair = true {
        didSet {
            crosshair.isHidden = !showsCrosshair
        }
    }
    
    public var crosshairColor: UIColor = .lightGray {
        didSet {
            crosshair.strokeColor = crosshairColor.cgColor
        }
    }
    
    public var crosshairWidth: CGFloat = 5 {
        didSet {
            crosshair.lineWidth = crosshairWidth
        }
    }
    
    private let crosshair: CAShapeLayer = CAShapeLayer()
    
    public convenience init(offset: CGPoint = .zero, radius: CGFloat = 50, scale: CGFloat = 2, borderColor: UIColor = .lightGray, borderWidth: CGFloat = 3, showsCrosshair: Bool = true, crosshairColor: UIColor = .lightGray, crosshairWidth: CGFloat = 0.5) {
        self.init(frame: .zero)
        layer.masksToBounds = true
        layer.addSublayer(crosshair)
        
        defer {
            self.offset = offset
            self.radius = radius
            self.scale = scale
            self.borderColor = borderColor
            self.borderWidth = borderWidth
            self.showsCrosshair = showsCrosshair
            self.crosshairColor = crosshairColor
            self.crosshairWidth = crosshairWidth
        }
    }
    
    public func magnify(at point: CGPoint) {
        guard magnifiedView != nil else { return }
        magnifiedPoint = point
        layer.setNeedsDisplay()
    }
    
    private func crosshairPath(for radius: CGFloat) -> CGPath {
        let path = CGMutablePath()
        path.move(to: .init(x: radius, y: 0))
        path.addLine(to: .init(x: radius, y: bounds.height))
        path.move(to: .init(x: 0, y: radius))
        path.addLine(to: .init(x: bounds.width, y: radius))
        return path
    }
    
    public override func draw(_ rect: CGRect) {
        guard let context = UIGraphicsGetCurrentContext() else { return }
        
        context.translateBy(x: radius, y: radius)
        context.scaleBy(x: scale, y: scale)
        context.translateBy(x: -magnifiedPoint.x, y: -magnifiedPoint.y)
        
        removeFromSuperview() 
        magnifiedView?.layer.render(in: context) 
        //If above disabled, no change
        //Possible that nothing's being rendered into context
        //Could it be that SKScene view has no layer?
        magnifiedView?.addSubview(self)
    }
}