Why this swift code does not show up line and the animation?

65 Views Asked by At

Why is the MyViewController does not get's shown in the window although the it's stated in the contentViewController: MyViewController() ?

I'm not on storyboard or XIB and I don't wanna use a nib file.

import Cocoa

@main
class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet var window: NSWindow!

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Create a new window
        window = NSWindow(contentViewController: MyViewController())
        // Make the window visible
        window.makeKeyAndOrderFront(nil)
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }

    func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
        return true
    }
}

    
class MyViewController: NSViewController {
    
    override func loadView() {
        let view = NSView(frame: NSMakeRect(0, 0, 400, 400))
        self.view = view
        
        // Create a shape layer for the rectangle
        let shapeLayer = CAShapeLayer()
        shapeLayer.strokeColor = NSColor.black.cgColor
        shapeLayer.lineWidth = 2
        shapeLayer.lineJoin = .round
        shapeLayer.lineDashPattern = [NSNumber(value: 6), NSNumber(value: 4)]
        
        // Create a path for the rectangle
        let path = CGMutablePath()
        path.addRect(NSRect(x: 50, y: 50, width: 100, height: 100))
        shapeLayer.path = path
        
        // Create an animation for the dashed line
        let animation = CABasicAnimation(keyPath: "lineDashPhase")
        animation.fromValue = 0
        animation.toValue = shapeLayer.lineDashPattern?.reduce(0) { $0 + $1.intValue }
        animation.duration = 1
        animation.repeatCount = .infinity
        shapeLayer.add(animation, forKey: "lineDashPhase")
        
        // Add the shape layer to the view hierarchy
        view.layer?.addSublayer(shapeLayer)
    }
    
}
2

There are 2 best solutions below

3
apodidae On BEST ANSWER

I'm not on storyboard or XIB and I don't wanna use a nib file.

If you want to use the programmatic approach then you don't need the controllers for a basic window as shown in the demo below. To run the following in Xcode first create a Swift project, add a 'main.swift' file, and then delete the pre-supplied AppDelegate file. Copy/paste this code into the 'main' file and run. I added a green background to your view so that you could see its bounds. You need to set .wantsLayer to true and then add the view as a subview to the window.contentView.

import Cocoa

class AppDelegate: NSObject, NSApplicationDelegate {
    var window:NSWindow!
    var view: NSView!
    
    func buildMenu() {
        let mainMenu = NSMenu()
        NSApp.mainMenu = mainMenu
        // **** App menu **** //
        let appMenuItem = NSMenuItem()
        mainMenu.addItem(appMenuItem)
        let appMenu = NSMenu()
        appMenuItem.submenu = appMenu
        appMenu.addItem(withTitle: "Quit", action:#selector(NSApplication.terminate), keyEquivalent: "q")
    }
    
    func buildWnd() {
        
        let _wndW : CGFloat = 400
        let _wndH : CGFloat = 400
        
        window = NSWindow(contentRect:NSMakeRect(0,0,_wndW,_wndH),styleMask:[.titled, .closable, .miniaturizable, .resizable], backing:.buffered, defer:false)
        window.center()
        window.title = "Swift Test Window"
        window.makeKeyAndOrderFront(window)
        
        // **** CAShapeLayer with Animation **** //
        view = NSView(frame: NSMakeRect(0, 0, 400, 400))
        window.contentView!.addSubview (view)
        view.wantsLayer = true
        view.layer?.backgroundColor = NSColor.green.cgColor
        
        // Create a shape layer for the rectangle
        let shapeLayer = CAShapeLayer()
        shapeLayer.strokeColor = NSColor.black.cgColor
        shapeLayer.lineWidth = 2
        shapeLayer.lineJoin = .round
        shapeLayer.lineDashPattern = [NSNumber(value: 6), NSNumber(value: 4)]
        
        // Create a path for the rectangle
        let path = CGMutablePath()
        path.addRect(NSRect(x: 50, y: 50, width: 100, height: 100))
        shapeLayer.path = path
        
        // Create an animation for the dashed line
        let animation = CABasicAnimation(keyPath: "lineDashPhase")
        animation.fromValue = 0
        animation.toValue = shapeLayer.lineDashPattern?.reduce(0) { $0 + $1.intValue }
        animation.duration = 1
        animation.repeatCount = .infinity
        shapeLayer.add(animation, forKey: "lineDashPhase")
        
        // Add the shape layer to the view hierarchy
        view.layer?.addSublayer(shapeLayer)
        
        // **** Quit btn **** //
        let quitBtn = NSButton (frame:NSMakeRect( _wndW - 50, 10, 40, 40 ))
        quitBtn.bezelStyle = .circular
        quitBtn.autoresizingMask = [.minXMargin,.maxYMargin]
        quitBtn.title = "Q"
        quitBtn.action = #selector(NSApplication.terminate)
        window.contentView!.addSubview(quitBtn)
    }
    
    func applicationDidFinishLaunching(_ notification: Notification) {
        buildMenu()
        buildWnd()
    }
    
    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
        return true
    }
    
}
let appDelegate = AppDelegate()

// **** main.swift **** //
let app = NSApplication.shared
app.delegate = appDelegate
app.setActivationPolicy(.regular)
app.activate(ignoringOtherApps:true)
app.run()

enter image description here

1
Duncan C On

It looks like your problem is at least in part the same as in the answer Willeke linked (From Matt)

The problem is that you never said

view.wantsLayer = true

So your view has no layer, and applying a background color to the layer has no effect because (as I just said) your view has no layer. So the view is there but you can't see it; it is transparent, like the Invisible Man.

Try adding the line view.wantsLayer = true after you create your view.