Creating Instance of Class of type NSWindowController

I have a program that allows me to programmatically bounce a ball around a window. Everything works as desired. What I would like to be able to do is work with the window itself to do such things as remove the title, change the background color and make it non resizable. I think I do this by creating a class of type NSWindosController which I have done. I create an instance of it in the ContentView but it does not work. Is my class wrong or am I not creating an instance in the right place or is there some other problem. Thank you.

import Cocoa
class WindowController: NSWindowController {
    override func windowDidLoad() {
        super.windowDidLoad()
        window?.styleMask.remove(.titled)
        window?.styleMask.remove(.resizable)
        window?.backgroundColor = .red
    }
}
import SwiftUI
struct ColorShading: Identifiable {
    let value: GraphicsContext.Shading
    let id = UUID()
}
struct ContentView: View {
    @State private var isPaused: Bool = true
    @State private var ballPosition = BallPosition()
    @State private var winController = WindowController()
    var body: some View {
        let ballColors = [
            ColorShading(value: GraphicsContext.Shading.color(.orange)),
            ColorShading(value: GraphicsContext.Shading.color(.red)),
            ColorShading(value: GraphicsContext.Shading.color(.blue)),
            ColorShading(value: GraphicsContext.Shading.color(.green)),
            ColorShading(value: GraphicsContext.Shading.color(.gray)),
            ColorShading(value: GraphicsContext.Shading.color(.yellow)),
            ColorShading(value: GraphicsContext.Shading.color(.purple))
        ]
        TimelineView(.animation(minimumInterval: 0.0001, paused: isPaused)) { timeline in
            Canvas { context, size in
                _ = timeline.date.timeIntervalSinceNow
                let circleDiameter: Double = (size.width * 0.2)
                let circleRadius: Double = circleDiameter / 2
                if isPaused == true {
                    ballPosition.updateBaseData(maxX: size.width, maxY: size.height, circleRadius: circleRadius)
                }
                let wheelOrigin = CGPoint(x: ballPosition.xPosition, y: ballPosition.yPostion)
                context.stroke(
                    Path { path in
                        path.addArc(center: wheelOrigin, radius: circleRadius, startAngle: .degrees(0), endAngle: .degrees(360), clockwise: false)
                    }, with: ballColors[ballPosition.randomColor].value, lineWidth: 2
                ) // end context stroke
                ballPosition.updatePosition()
            } // end canvas
            .frame(width: 800, height: 800)
        } // end time line view
        .onAppear() {
            Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { timer in
                isPaused = false
                timer.invalidate()
            } // end timer
        } // end on appear
    }
}

Answered by ChrisMH in 746814022

Looks like I was heading down the wrong path. I find that I need to work with NSWindow, not NSWindowController, and among other things, its style mask. I found some code on line that lets me position the window on the screen. I made some modifications to it and added a function setStyleMask which is an extension to NSWndow. It allows me to make the modifications I want. Below is the updated code. My ContentView does not change with the exception of the elimination of @State private var winController = WinController().

import SwiftUI
@main
struct BouncingWheelApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .hostingWindowPosition(
                    vertical: .center,
                    horizontal: .center,
                    screen: .main)
        }
    }
}

import SwiftUI
import AppKit
extension NSWindow {
    func setStyleMask() {
        titlebarAppearsTransparent = true
        titleVisibility = .hidden
        backgroundColor = .gray
        styleMask.remove(.resizable)
    }
    func setPosition(_ position: Position, in screen: NSScreen?) {
        setStyleMask()
        guard let visibleFrame = (screen ?? self.screen)?.visibleFrame else { return }
        let origin = position.value(forWindow: frame, inScreen: visibleFrame)
        let myRect = CGRect(x: origin.x, y: origin.y, width: 800.0, height: 800.0)
        setFrame(myRect, display: true)
    } // end function set position
    struct Position {
        static let defaultPadding: CGFloat = 16
        var vertical: Vertical
        var horizontal: Horizontal
        var padding = Self.defaultPadding
        enum Horizontal { 
            case left, center, right
        }
        enum Vertical {
            case top, center, bottom
        }
        func value(forWindow windowRect: CGRect, inScreen screenRect: CGRect) -> CGPoint {
            let xPosition = horizontal.valueFor(screenRange: screenRect.minX..<screenRect.maxX, width: windowRect.width, padding: padding)
            let yPosition = vertical.valueFor(screenRange: screenRect.minY..<screenRect.maxY, height: windowRect.height, padding: padding)
            return CGPoint(x: xPosition, y: yPosition)
        }
    } // end structure Position
}
extension NSWindow.Position.Horizontal {
    func valueFor(screenRange: Range<CGFloat>, width: CGFloat, padding: CGFloat) -> CGFloat {
        switch self {
        case .left:
            return screenRange.lowerBound + padding
        case .center:
            return (screenRange.upperBound + screenRange.lowerBound - width) / 2
        case .right:
            return screenRange.upperBound - width - padding
        }
    }
}
extension NSWindow.Position.Vertical {
    func valueFor(screenRange: Range<CGFloat>, height: CGFloat, padding: CGFloat) -> CGFloat {
        switch self {
        case .top:
            return screenRange.upperBound - height - padding
        case .center:
            return (screenRange.upperBound + screenRange.lowerBound - height) / 2
        case .bottom:
            return screenRange.lowerBound + padding
        }
    }
}
struct HostingWindowFinder: NSViewRepresentable {
    var callback: (NSWindow?) -> ()
    func makeNSView(context: Self.Context) -> NSView {
        let view = NSView()
        DispatchQueue.main.async { self.callback(view.window) }
        return view
    }
    func updateNSView(_ nsView: NSView, context: Context) {
        DispatchQueue.main.async { self.callback(nsView.window) }
    }
}
private struct WindowPositionModifier: ViewModifier {
    let position: NSWindow.Position
    let screen: NSScreen?
    func body(content: Content) -> some View {
        content
            .background(HostingWindowFinder {
                $0?.setPosition(position, in: screen)
            }
            )
    }
}
extension View {
    func hostingWindowPosition(vertical: NSWindow.Position.Vertical, horizontal: NSWindow.Position.Horizontal, padding: CGFloat = NSWindow.Position.defaultPadding, screen: NSScreen? = nil) -> some View {
        modifier(
            WindowPositionModifier(
                position: NSWindow.Position(vertical: vertical, horizontal: horizontal, padding: padding),
                screen: screen
            )
        )
    }
}

Accepted Answer

Looks like I was heading down the wrong path. I find that I need to work with NSWindow, not NSWindowController, and among other things, its style mask. I found some code on line that lets me position the window on the screen. I made some modifications to it and added a function setStyleMask which is an extension to NSWndow. It allows me to make the modifications I want. Below is the updated code. My ContentView does not change with the exception of the elimination of @State private var winController = WinController().

import SwiftUI
@main
struct BouncingWheelApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .hostingWindowPosition(
                    vertical: .center,
                    horizontal: .center,
                    screen: .main)
        }
    }
}

import SwiftUI
import AppKit
extension NSWindow {
    func setStyleMask() {
        titlebarAppearsTransparent = true
        titleVisibility = .hidden
        backgroundColor = .gray
        styleMask.remove(.resizable)
    }
    func setPosition(_ position: Position, in screen: NSScreen?) {
        setStyleMask()
        guard let visibleFrame = (screen ?? self.screen)?.visibleFrame else { return }
        let origin = position.value(forWindow: frame, inScreen: visibleFrame)
        let myRect = CGRect(x: origin.x, y: origin.y, width: 800.0, height: 800.0)
        setFrame(myRect, display: true)
    } // end function set position
    struct Position {
        static let defaultPadding: CGFloat = 16
        var vertical: Vertical
        var horizontal: Horizontal
        var padding = Self.defaultPadding
        enum Horizontal { 
            case left, center, right
        }
        enum Vertical {
            case top, center, bottom
        }
        func value(forWindow windowRect: CGRect, inScreen screenRect: CGRect) -> CGPoint {
            let xPosition = horizontal.valueFor(screenRange: screenRect.minX..<screenRect.maxX, width: windowRect.width, padding: padding)
            let yPosition = vertical.valueFor(screenRange: screenRect.minY..<screenRect.maxY, height: windowRect.height, padding: padding)
            return CGPoint(x: xPosition, y: yPosition)
        }
    } // end structure Position
}
extension NSWindow.Position.Horizontal {
    func valueFor(screenRange: Range<CGFloat>, width: CGFloat, padding: CGFloat) -> CGFloat {
        switch self {
        case .left:
            return screenRange.lowerBound + padding
        case .center:
            return (screenRange.upperBound + screenRange.lowerBound - width) / 2
        case .right:
            return screenRange.upperBound - width - padding
        }
    }
}
extension NSWindow.Position.Vertical {
    func valueFor(screenRange: Range<CGFloat>, height: CGFloat, padding: CGFloat) -> CGFloat {
        switch self {
        case .top:
            return screenRange.upperBound - height - padding
        case .center:
            return (screenRange.upperBound + screenRange.lowerBound - height) / 2
        case .bottom:
            return screenRange.lowerBound + padding
        }
    }
}
struct HostingWindowFinder: NSViewRepresentable {
    var callback: (NSWindow?) -> ()
    func makeNSView(context: Self.Context) -> NSView {
        let view = NSView()
        DispatchQueue.main.async { self.callback(view.window) }
        return view
    }
    func updateNSView(_ nsView: NSView, context: Context) {
        DispatchQueue.main.async { self.callback(nsView.window) }
    }
}
private struct WindowPositionModifier: ViewModifier {
    let position: NSWindow.Position
    let screen: NSScreen?
    func body(content: Content) -> some View {
        content
            .background(HostingWindowFinder {
                $0?.setPosition(position, in: screen)
            }
            )
    }
}
extension View {
    func hostingWindowPosition(vertical: NSWindow.Position.Vertical, horizontal: NSWindow.Position.Horizontal, padding: CGFloat = NSWindow.Position.defaultPadding, screen: NSScreen? = nil) -> some View {
        modifier(
            WindowPositionModifier(
                position: NSWindow.Position(vertical: vertical, horizontal: horizontal, padding: padding),
                screen: screen
            )
        )
    }
}

Creating Instance of Class of type NSWindowController
 
 
Q