├── Example ├── WindowManagementProject │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── WindowManagementProject.entitlements │ ├── CodeFileDocument.swift │ ├── Info.plist │ ├── ContentView.swift │ └── WindowManagementProjectApp.swift └── WindowManagementProject.xcodeproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved │ └── project.pbxproj ├── .gitignore ├── Sources └── WindowManagement │ ├── DocumentGroup │ ├── NSDocumentWatcher.swift │ ├── AutoDismissView.swift │ ├── NSApp+openDocument.swift │ └── NSDocumentGroup.swift │ ├── OpenWindow │ ├── NSMenuItem+OpenWindowAction.swift │ ├── SceneID.swift │ ├── NSApp+openWindow.swift │ └── OpenWindowCommand.swift │ ├── Scene+Environment.swift │ ├── NSApp+RestoreWindow.swift │ ├── NSWindowEnvironmentKey.swift │ ├── WindowManagement.swift │ ├── View+Window.swift │ ├── NSWindow+Swizzle.swift │ ├── NSWindow+Extensions.swift │ └── Scene+Window.swift ├── Tests └── WindowManagementTests │ └── WindowManagementTests.swift ├── LICENSE ├── Package.swift └── README.md /Example/WindowManagementProject/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/WindowManagementProject/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/config/registries.json 8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 9 | .netrc 10 | -------------------------------------------------------------------------------- /Example/WindowManagementProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/WindowManagementProject/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Example/WindowManagementProject/WindowManagementProject.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Sources/WindowManagement/DocumentGroup/NSDocumentWatcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Wouter Hennen on 11/06/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | enum NSDocumentMapper { 11 | static var mapping: [URL: () -> NSDocument?] = [:] 12 | } 13 | -------------------------------------------------------------------------------- /Sources/WindowManagement/OpenWindow/NSMenuItem+OpenWindowAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Wouter Hennen on 11/06/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | extension NSMenuItem { 11 | static var openWindowAction: ((SceneID?, (any Codable & Hashable)?) -> Void)? 12 | } 13 | -------------------------------------------------------------------------------- /Example/WindowManagementProject.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/WindowManagement/OpenWindow/SceneID.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Wouter Hennen on 11/06/2023. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Unique ID of each scene. 11 | public struct SceneID { 12 | public let id: String 13 | 14 | public init(_ id: String) { 15 | self.id = id 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/WindowManagement/Scene+Environment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Wouter Hennen on 11/06/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public extension SwiftUI.Scene { 11 | @inlinable 12 | func modifier(_ modifier: T) -> ModifiedContent { 13 | return .init(content: self, modifier: modifier) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests/WindowManagementTests/WindowManagementTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import WindowManagement 3 | 4 | final class WindowManagementTests: XCTestCase { 5 | func testExample() throws { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Example/WindowManagementProject.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "swiftui-windowmanagement", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/Wouter01/SwiftUI-WindowManagement", 7 | "state" : { 8 | "revision" : "03642ad06a3aa51e8284eb22146a208269cdc1ca", 9 | "version" : "2.1.0" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /Sources/WindowManagement/NSApp+RestoreWindow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Wouter Hennen on 11/06/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public extension App { 11 | 12 | /// Enable or disable window restoring after quitting. This is the same behavior as when an app is restored after reboot or force quit. 13 | func enableWindowSizeSaveOnQuit(_ enabled: Bool = true) { 14 | UserDefaults.standard.setValue(enabled, forKey: "NSQuitAlwaysKeepsWindows") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/WindowManagement/NSWindowEnvironmentKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Wouter Hennen on 06/01/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public extension EnvironmentValues { 11 | var window: NSWindowEnvironmentKey.Value { 12 | get { self[NSWindowEnvironmentKey.self] } 13 | set { self[NSWindowEnvironmentKey.self] = newValue } 14 | } 15 | } 16 | 17 | public struct NSWindowEnvironmentKey: EnvironmentKey { 18 | public static var defaultValue = NSWindow() 19 | } 20 | -------------------------------------------------------------------------------- /Example/WindowManagementProject/CodeFileDocument.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CodeFileDocument.swift 3 | // WindowManagementProject 4 | // 5 | // Created by Wouter Hennen on 11/06/2023. 6 | // 7 | 8 | import AppKit 9 | import WindowManagement 10 | 11 | @objc(CodeFileDocument) 12 | final class CodeFileDocument: NSDocument { 13 | 14 | override nonisolated func read(from data: Data, ofType typeName: String) throws { 15 | 16 | } 17 | 18 | override func makeWindowControllers() { 19 | if let window = NSApp.openDocument(self), let windowController = window.windowController { 20 | addWindowController(windowController) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/WindowManagement/DocumentGroup/AutoDismissView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AutoDismissView.swift 3 | // 4 | // 5 | // Created by Wouter Hennen on 11/06/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct AutoDismissView: View { 11 | @Environment(\.dismiss) var dismissWindow 12 | 13 | var defaultAction: (() -> Void)? 14 | 15 | var body: some View { 16 | VStack {} 17 | .task { 18 | Task { 19 | if let defaultAction { 20 | defaultAction() 21 | } else { 22 | NSDocumentController.shared.openDocument(nil) 23 | } 24 | } 25 | dismissWindow() 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Example/WindowManagementProject/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDocumentTypes 6 | 7 | 8 | CFBundleTypeExtensions 9 | 10 | h 11 | 12 | CFBundleTypeName 13 | C header file 14 | CFBundleTypeOSTypes 15 | 16 | TEXT 17 | utxt 18 | TUTX 19 | **** 20 | 21 | CFBundleTypeRole 22 | Editor 23 | NSDocumentClass 24 | CodeFileDocument 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Example/WindowManagementProject/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // WindowManagementProject 4 | // 5 | // Created by Wouter Hennen on 11/06/2023. 6 | // 7 | 8 | import SwiftUI 9 | import WindowManagement 10 | 11 | struct ContentView: View { 12 | @Environment(\.window) var window 13 | var body: some View { 14 | VStack { 15 | Text("Window title: \(window.title)") 16 | Button("Open Document (only .h file supported)") { 17 | NSDocumentController.shared.openDocument(nil) 18 | } 19 | 20 | Button("Open Settings") { 21 | NSApp.openSettings() 22 | } 23 | } 24 | .padding() 25 | } 26 | } 27 | 28 | struct ContentView_Previews: PreviewProvider { 29 | static var previews: some View { 30 | ContentView() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/WindowManagement/WindowManagement.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct WM { 4 | static var currentIdentifier: String = "" { 5 | didSet { 6 | NSWindow.swizzleAll() 7 | } 8 | } 9 | static var availableWindows: [NSWindow] = [] 10 | static var modifications: [String: WindowModifications] = [:] 11 | static var didSwizzle = false 12 | } 13 | 14 | struct WindowModifications { 15 | var styleMask: NSWindow.StyleMask? 16 | var windowButtonsEnabled: [NSWindow.ButtonType: WindowButton] = [:] 17 | var collectionBehavior: NSWindow.CollectionBehavior? 18 | var movableByBackground: Bool? 19 | var movable: Bool? 20 | var tabbingMode: NSWindow.TabbingMode? 21 | var backgroundColor: NSColor? 22 | var titlebarAppearsTransparent: Bool? 23 | var disableRestoreOnLaunch: Bool? 24 | var animationBehavior: NSWindow.AnimationBehavior? 25 | 26 | struct WindowButton { 27 | var enabled = true 28 | var isHidden = false 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Wouter Hennen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Example/WindowManagementProject/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/WindowManagement/OpenWindow/NSApp+openWindow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSApp+openWindow.swift 3 | // 4 | // 5 | // Created by Wouter Hennen on 11/06/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public extension NSApplication { 11 | /// Open a SwiftUI Window with given ID. 12 | func openWindow(_ id: SceneID) { 13 | NSMenuItem.openWindowAction?(id, nil) 14 | } 15 | 16 | /// Open a SwiftUI Window with given ID and value. 17 | func openWindow(_ id: SceneID, value: any Codable & Hashable) { 18 | NSMenuItem.openWindowAction?(id, value) 19 | } 20 | 21 | /// Open a SwiftUI Window with given value. 22 | func openWindow(value: any Codable & Hashable) { 23 | NSMenuItem.openWindowAction?(nil, value) 24 | } 25 | 26 | /// Open the SwiftUI Settings window. 27 | func openSettings() { 28 | let eventSource = CGEventSource(stateID: .hidSystemState) 29 | let keyCommand = CGEvent(keyboardEventSource: eventSource, virtualKey: 0x2B, keyDown: true) 30 | guard let keyCommand else { return } 31 | 32 | keyCommand.flags = .maskCommand 33 | let event = NSEvent(cgEvent: keyCommand) 34 | guard let event else { return } 35 | 36 | NSApp.sendEvent(event) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.7 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "WindowManagement", 8 | platforms: [ 9 | .macOS(.v12) 10 | ], 11 | products: [ 12 | // Products define the executables and libraries a package produces, and make them visible to other packages. 13 | .library( 14 | name: "WindowManagement", 15 | targets: ["WindowManagement"]), 16 | ], 17 | dependencies: [ 18 | // Dependencies declare other packages that this package depends on. 19 | // .package(url: /* package url */, from: "1.0.0"), 20 | ], 21 | targets: [ 22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 23 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 24 | .target( 25 | name: "WindowManagement", 26 | dependencies: [], 27 | swiftSettings: []), 28 | .testTarget( 29 | name: "WindowManagementTests", 30 | dependencies: ["WindowManagement"]), 31 | ] 32 | ) 33 | -------------------------------------------------------------------------------- /Sources/WindowManagement/OpenWindow/OpenWindowCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Wouter Hennen on 11/06/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @available(macOS 13.0, *) 11 | struct OpenWindowCommand: Commands { 12 | @Environment(\.openWindow) var openWindow 13 | 14 | public var body: some Commands { 15 | CommandGroup(after: .windowArrangement) { 16 | Divider() 17 | .hidden() 18 | .task { 19 | NSMenuItem.openWindowAction = { scene, value in 20 | switch (scene, value) { 21 | case (.some(let id), .none): 22 | openWindow(id: id.id) 23 | case (.none, .some(let data)): 24 | openWindow(value: data) 25 | case let (.some(id), .some(data)): 26 | openWindow(id: id.id, value: data) 27 | default: 28 | break 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | 36 | public extension Scene { 37 | /// Required to enable window opening from NSApp. Call this method only once, from one scene. 38 | @available(macOS 13.0, *) 39 | func enableOpenWindow() -> some Scene { 40 | commands { 41 | OpenWindowCommand() 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/WindowManagement/DocumentGroup/NSApp+openDocument.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSApp+openDocument.swift 3 | // 4 | // 5 | // Created by Wouter Hennen on 11/06/2023. 6 | // 7 | 8 | import AppKit 9 | 10 | public extension SceneID { 11 | 12 | static func document(_ type: Document.Type) -> SceneID { 13 | SceneID("document" + String(describing: type)) 14 | } 15 | } 16 | 17 | public extension NSApplication { 18 | /// Open a new NSDocumentGroup Window with given type. 19 | func openDocument(_ type: Document.Type, _ document: Document) -> NSWindow? { 20 | guard let fileURL = document.fileURL else { return nil } 21 | NSDocumentMapper.mapping[fileURL] = { [weak document] in document } 22 | NSApp.openWindow(.document(type), value: fileURL) 23 | 24 | let window = NSApp 25 | .windows 26 | .filter { $0.identifier?.rawValue.starts(with: "document" + String(describing: Document.self)) ?? false } 27 | .filter { $0.windowController?.document == nil } 28 | .first 29 | 30 | window?.title = fileURL.absoluteString 31 | window?.representedURL = fileURL 32 | window?.identifier = .init(fileURL.absoluteString) 33 | 34 | return window 35 | } 36 | 37 | /// Open a new NSDocumentGroup Window. 38 | func openDocument(_ document: Document) -> NSWindow? { 39 | openDocument(Document.self, document) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/WindowManagement/DocumentGroup/NSDocumentGroup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSDocumentGroup.swift 3 | // 4 | // 5 | // Created by Wouter Hennen on 11/06/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | /// Creates a SwiftUI WindowGroup for an NSDocument class. This works similar to SwiftUI's DocumentGroup. 11 | /// The defaultAction closure gets called when the user clicks the "+" button on a window tab bar. 12 | @available(macOS 13.0, *) 13 | public struct NSDocumentGroup: Scene { 14 | 15 | var type: NSDocumentType.Type 16 | var content: (NSDocumentType) -> Content 17 | var defaultAction: (() -> Void)? 18 | 19 | public init(for type: NSDocumentType.Type, content: @escaping (NSDocumentType) -> Content, defaultAction: (() -> Void)? = nil) { 20 | self.type = type 21 | self.content = content 22 | self.defaultAction = defaultAction 23 | } 24 | 25 | public var body: some Scene { 26 | let id = "document" + String(describing: type) 27 | WindowGroup(id: id, for: URL.self) { url in 28 | if let url = url.wrappedValue { 29 | let document = NSDocumentMapper.mapping[url] 30 | if let document = document?() as? NSDocumentType { 31 | content(document) 32 | } else { 33 | Text("Could not open document (\(url))") 34 | } 35 | } else { 36 | AutoDismissView(defaultAction: defaultAction) 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/WindowManagement/View+Window.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Wouter Hennen on 06/01/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public extension View { 11 | /// Inject the settings window into the environment. 12 | func injectSettingsWindow() -> some View { 13 | if #available(macOS 13, *) { 14 | return self.modifier(Injectwindow(identifier: "com_apple_SwiftUI_Settings_window")) 15 | } else { 16 | return self.modifier(Injectwindow(identifier: "SwiftUIWindow")) 17 | } 18 | } 19 | 20 | /// Inject the current window into the environment. 21 | func injectWindow(_ identifier: String) -> some View { 22 | return self.modifier(Injectwindow(identifier: identifier)) 23 | } 24 | 25 | /// Inject the current window into the environment. 26 | func injectWindow(_ identifier: SceneID) -> some View { 27 | return self.modifier(Injectwindow(identifier: identifier.id)) 28 | } 29 | } 30 | 31 | struct Injectwindow: ViewModifier { 32 | var identifier: String 33 | @State var window = NSWindow() 34 | 35 | func body(content: Content) -> some View { 36 | content 37 | .environment(\.window, window) 38 | .task { 39 | if window.identifier == nil { 40 | window = WM.availableWindows.first { $0.identifier?.rawValue.starts(with: identifier) ?? false } ?? NSWindow() 41 | WM.availableWindows = Array(WM.availableWindows.dropFirst()) 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/WindowManagement/NSWindow+Swizzle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Wouter Hennen on 06/01/2023. 6 | // 7 | 8 | import AppKit 9 | 10 | extension NSWindow { 11 | 12 | static var didSwizzle = false 13 | 14 | /// Always use an active appearance for the window. 15 | /// This allows SwiftUI Materials to have an always active state, instead of following the window key state. 16 | /// WARNING: Enabling this can have unwanted sideeffects. 17 | /// This option is enabled globally for all windows. 18 | public static var alwaysUseActiveAppearance = false 19 | 20 | static func swizzleAll() { 21 | if !didSwizzle { 22 | didSwizzle = true 23 | swizzleMethod(#selector(setter: NSWindow.identifier), #selector(NSWindow.setSwizzledIdentifier)) 24 | swizzleMethod(#selector(setter: NSWindow.styleMask), #selector(NSWindow.setSwizzledStyleMask)) 25 | 26 | if alwaysUseActiveAppearance { 27 | swizzleMethod(Selector(("hasKeyAppearance")), #selector(getter: NSWindow.hasKeyAppearanceOverride)) 28 | swizzleMethod(Selector(("hasMainAppearance")), #selector(getter: NSWindow.hasKeyAppearanceOverride)) 29 | } 30 | } 31 | } 32 | 33 | @objc var hasKeyAppearanceOverride: Bool { 34 | true 35 | } 36 | 37 | static func swizzleMethod(_ original: Selector, _ replacement: Selector) { 38 | 39 | let originalMethodSet = class_getInstanceMethod(self, original) 40 | let swizzledMethodSet = class_getInstanceMethod(self, replacement) 41 | 42 | method_exchangeImplementations(originalMethodSet!, swizzledMethodSet!) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Example/WindowManagementProject/WindowManagementProjectApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WindowManagementProjectApp.swift 3 | // WindowManagementProject 4 | // 5 | // Created by Wouter Hennen on 11/06/2023. 6 | // 7 | 8 | import SwiftUI 9 | import WindowManagement 10 | 11 | extension SceneID { 12 | static let firstWindowGroup = SceneID("firstWindowGroup") 13 | } 14 | 15 | @main 16 | struct WindowManagementProjectApp: App { 17 | 18 | @NSApplicationDelegateAdaptor var delegate: AppDelegate 19 | 20 | init() { 21 | enableWindowSizeSaveOnQuit(true) 22 | } 23 | 24 | var body: some Scene { 25 | Group { 26 | WindowGroup(id: SceneID.firstWindowGroup.id) { 27 | ContentView() 28 | .frame(maxWidth: .infinity, maxHeight: .infinity) 29 | .background(.regularMaterial) 30 | .injectWindow(.firstWindowGroup) 31 | } 32 | .register(.firstWindowGroup) 33 | .titlebarAppearsTransparent() 34 | .movableByBackground() 35 | .windowButton(.closeButton, hidden: true) 36 | .backgroundColor(.systemGray.withAlphaComponent(0.001)) 37 | 38 | Settings { 39 | VStack { 40 | Text("HEllo") 41 | } 42 | .frame(minWidth: 300, minHeight: 300) 43 | } 44 | .enableOpenWindow() 45 | 46 | NSDocumentGroup(for: CodeFileDocument.self) { document in 47 | Text(document.fileURL?.absoluteString ?? "") 48 | } 49 | .register(.document(CodeFileDocument.self)) 50 | .transition(.documentWindow) 51 | .movableByBackground() 52 | .windowButton(.miniaturizeButton, hidden: true) 53 | } 54 | .environment(\.controlSize, .large) 55 | } 56 | } 57 | 58 | class AppDelegate: NSObject, NSApplicationDelegate { 59 | func applicationDidFinishLaunching(_ notification: Notification) { 60 | // NSApp.openSettings() 61 | // NSApp.openWindow(.firstWindowGroup) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/WindowManagement/NSWindow+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Wouter Hennen on 06/01/2023. 6 | // 7 | 8 | import AppKit 9 | 10 | extension NSWindow { 11 | 12 | @objc func setSwizzledStyleMask(_ newValue: StyleMask) { 13 | if let id = identifier { 14 | let key = id.rawValue.prefix(while: { $0 != "-"}) 15 | let value = WM.modifications[String(key)] 16 | if let styleMask = value?.styleMask { 17 | self.setSwizzledStyleMask(styleMask.union(newValue.intersection(.fullScreen))) 18 | } else { 19 | self.setSwizzledStyleMask(newValue) 20 | } 21 | } else { 22 | self.setSwizzledStyleMask(newValue) 23 | } 24 | } 25 | 26 | @objc func setSwizzledIdentifier(_ newValue: NSUserInterfaceItemIdentifier?) { 27 | self.setSwizzledIdentifier(newValue) 28 | 29 | if !WM.availableWindows.contains(self) { 30 | WM.availableWindows.append(self) 31 | } 32 | 33 | if let id = newValue { 34 | 35 | let key = id.rawValue.prefix(while: { $0 != "-"}) 36 | 37 | let value = WM.modifications[String(key)] 38 | 39 | guard let value else { return } 40 | 41 | if let styleMask = value.styleMask { 42 | self.setSwizzledStyleMask(styleMask.union(.titled)) // .titled is required for SwiftUI 43 | } 44 | 45 | if let behavior = value.collectionBehavior { 46 | collectionBehavior = behavior 47 | } 48 | 49 | if let movableByBackground = value.movableByBackground { 50 | isMovableByWindowBackground = movableByBackground 51 | } 52 | 53 | if let movable = value.movable { 54 | isMovable = movable 55 | } 56 | 57 | if let tabbingMode = value.tabbingMode { 58 | self.tabbingMode = tabbingMode 59 | } 60 | 61 | if let bgColor = value.backgroundColor { 62 | self.backgroundColor = bgColor 63 | } 64 | 65 | if let transparent = value.titlebarAppearsTransparent { 66 | self.titlebarAppearsTransparent = transparent 67 | } 68 | 69 | if let disableRestore = value.disableRestoreOnLaunch { 70 | self.isRestorable = !disableRestore 71 | } 72 | 73 | if let animationBehavior = value.animationBehavior { 74 | self.animationBehavior = animationBehavior 75 | } 76 | 77 | value.windowButtonsEnabled.forEach { 78 | standardWindowButton($0)?.isHidden = $1.isHidden 79 | standardWindowButton($0)?.isEnabled = $1.enabled 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Sources/WindowManagement/Scene+Window.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Wouter Hennen on 06/01/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public extension Scene { 11 | 12 | /// Registers a scene to be modified by the following scene modifiers. 13 | /// The identifier should be the same as in the Scene initializer. 14 | /// This modifier should be called before other modifiers of this package. 15 | func register(_ identifier: String) -> some Scene { 16 | WM.currentIdentifier = identifier 17 | WM.modifications[identifier] = WindowModifications() 18 | return self 19 | } 20 | 21 | /// Registers a scene to be modified by the following scene modifiers. 22 | /// The identifier should be the same as in the Scene initializer. 23 | /// This modifier should be called before other modifiers of this package. 24 | func register(_ identifier: SceneID) -> some Scene { 25 | WM.currentIdentifier = identifier.id 26 | WM.modifications[identifier.id] = WindowModifications() 27 | return self 28 | } 29 | 30 | /// Registers the settings scene to be modified by the following scene modifiers. 31 | /// The identifier should be the same as in the Scene initializer. 32 | /// This modifier should be called before other modifiers of this package. 33 | func registerSettingsWindow() -> some Scene { 34 | WM.currentIdentifier = "com_apple_SwiftUI_Settings_window" 35 | WM.modifications[WM.currentIdentifier] = WindowModifications() 36 | return self 37 | } 38 | 39 | /// Indicates whether the window can be moved by clicking and dragging its background. 40 | func movableByBackground(_ value: Bool = true) -> some Scene { 41 | WM.modifications[WM.currentIdentifier]?.movableByBackground = value 42 | return self 43 | } 44 | 45 | /// Indicates whether the view can be moved. 46 | func movable(_ value: Bool) -> some Scene { 47 | WM.modifications[WM.currentIdentifier]?.movable = value 48 | return self 49 | } 50 | 51 | /// Apply a certain stylemask to the window. 52 | /// Note that `NSWindow.StyleMask.titled` is always included, otherwise SwiftUI will crash. 53 | func styleMask(_ styleMask: NSWindow.StyleMask) -> some Scene { 54 | WM.modifications[WM.currentIdentifier]?.styleMask = styleMask 55 | return self 56 | } 57 | 58 | /// Indicated whether a window button should be enabled or not. 59 | /// If disabled, the button will be greyed out but still visible. 60 | /// The keyboard shortcut of the button won't be active. 61 | /// Use `windowButton(_:hidden:)` to hide a window button. 62 | func windowButton(_ button: NSWindow.ButtonType, enabled: Bool) -> some Scene { 63 | WM.modifications[WM.currentIdentifier]?.windowButtonsEnabled[button, default: .init()].enabled = enabled 64 | return self 65 | } 66 | 67 | /// Indicated whether a window button should be hidden or not. 68 | /// A hidden button can still be triggered with it's keyboard shortcut, e.g. Cmd+w for closing a window. 69 | /// Use `windowButton(_:hidden:)` to disable a window button. 70 | func windowButton(_ button: NSWindow.ButtonType, hidden: Bool) -> some Scene { 71 | WM.modifications[WM.currentIdentifier]?.windowButtonsEnabled[button, default: .init()].isHidden = hidden 72 | return self 73 | } 74 | 75 | /// Apply a certain collectionBehavior to the window. 76 | func collectionBehavior(_ behavior: NSWindow.CollectionBehavior) -> some Scene { 77 | WM.modifications[WM.currentIdentifier]?.collectionBehavior = behavior 78 | return self 79 | } 80 | 81 | /// Set the tabbingMode for the window. 82 | /// If set to preferred, new windows will be opened as tabs. 83 | func tabbingMode(_ mode: NSWindow.TabbingMode) -> some Scene { 84 | WM.modifications[WM.currentIdentifier]?.tabbingMode = mode 85 | return self 86 | } 87 | 88 | /// Sets the background color of the window. 89 | func backgroundColor(_ color: NSColor) -> some Scene { 90 | WM.modifications[WM.currentIdentifier]?.backgroundColor = color 91 | return self 92 | } 93 | 94 | /// Makes the titlebar transparent. 95 | func titlebarAppearsTransparent(_ value: Bool = true) -> some Scene { 96 | WM.modifications[WM.currentIdentifier]?.titlebarAppearsTransparent = value 97 | return self 98 | } 99 | 100 | /// This will stop windows from relaunching if they were open in the last active app state. 101 | func disableRestoreOnLaunch() -> some Scene { 102 | WM.modifications[WM.currentIdentifier]?.disableRestoreOnLaunch = true 103 | return self 104 | } 105 | 106 | func transition(_ transition: NSWindow.AnimationBehavior) -> some Scene { 107 | WM.modifications[WM.currentIdentifier]?.animationBehavior = transition 108 | return self 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftUI-WindowManagement 2 | 3 | The WindowManagement package allows you to control window behaviors of SwiftUI Scenes without doing weird tricks. 4 | Views can also access the NSWindow through the Environment. 5 | 6 | image 7 | 8 | 9 | ## Notice 10 | > This package uses and modifies some internals of SwiftUI. As SwiftUI changes frequently, implementations might break. 11 | 12 | > Note: DocumentGroup is not supported (yet) 13 | 14 | ## Compatibility 15 | - macOS 12.0 (Untested, but should work) 16 | - macOS 13.0 17 | - macOS 14.0 18 | 19 | ## Opening SwiftUI Windows from anywhere in your app. 20 | Using SwiftUI windows can be difficult when parts of your app rely on AppKit types. For example, you can't open a SwiftUI window from an `AppDelegate`. This package adds some functions to `NSApp` to allow this kind of behavior. 21 | 22 | #### Usage 23 | First, define a SceneID for each SwiftUI scene: 24 | ```swift 25 | extension SceneID { 26 | static let myWindow = SceneID("myWindow") 27 | } 28 | ``` 29 | Important: you must add `.enableOpenWindow()` to one scene (only one is required) to enable the functionality. 30 | ```swift 31 | @main 32 | struct MyApp: App { 33 | var body: some Scene { 34 | WindowGroup(id: SceneID.myWindow.id) { 35 | ... 36 | } 37 | .enableOpenWindow() 38 | } 39 | } 40 | ``` 41 | You can open a window by calling the following function: 42 | ```swift 43 | func applicationDidFinishLaunching(_ notification: Notification) { 44 | NSApp.openWindow(.myWindow) 45 | } 46 | ``` 47 | 48 | You can also open a SwiftUI Settings window (also works on macOS Sonoma): 49 | ```swift 50 | func applicationDidFinishLaunching(_ notification: Notification) { 51 | NSApp.openSettings() 52 | } 53 | ``` 54 | 55 | ## Passing an Environment Value to multiple Scenes 56 | ```swift 57 | var body: some Scene { 58 | Group { 59 | WindowGroup { 60 | ... 61 | } 62 | 63 | Settings { 64 | ... 65 | } 66 | 67 | NSDocumentGroup(for: ...) { _ in 68 | ... 69 | } 70 | } 71 | .environment(\.controlSize, .large) 72 | } 73 | ``` 74 | 75 | ## Scene Window UI Modifiers 76 | #### register 77 | This modifier enables the modification of the underlying `NSWindow` for this scene. 78 | It should always be called before any of the other modifiers. 79 | The identifier must be the same as the one set in the Scene initializer. 80 | For the `Settings` Scene, use `registerSettingsWindow`. 81 | 82 | ```swift 83 | func register(_ identifier: String) 84 | 85 | func register(_ identifier: SceneID) 86 | 87 | func registerSettingsWindow() 88 | ``` 89 | 90 | #### movableByBackground 91 | Indicates whether the window can be moved by clicking and dragging its background. 92 | ```swift 93 | func movableByBackground(_ value: Bool) 94 | ``` 95 | 96 | #### movable 97 | Indicates whether the view can be moved. 98 | ```swift 99 | func movable(_ value: Bool) 100 | ``` 101 | 102 | #### styleMask 103 | Apply a certain stylemask to the window. 104 | Note that `NSWindow.StyleMask.titled` is always included, otherwise SwiftUI will crash. 105 | ```swift 106 | func styleMask(_ styleMask: NSWindow.StyleMask) 107 | ``` 108 | 109 | #### windowButton 110 | Indicated whether a window button should be enabled or not. 111 | If disabled, the button will be greyed out but still visible. 112 | The keyboard shortcut of the button won't be active. 113 | Use `windowButton(_:hidden:)` to hide a window button. 114 | ```swift 115 | func windowButton(_ button: NSWindow.ButtonType, enabled: Bool) 116 | ``` 117 | 118 | Indicated whether a window button should be hidden or not. 119 | A hidden button can still be triggered with it's keyboard shortcut, e.g. Cmd+w for closing a window. 120 | Use `windowButton(_:hidden:)` to disable a window button. 121 | ```swift 122 | func windowButton(_ button: NSWindow.ButtonType, hidden: Bool) 123 | ``` 124 | 125 | #### collectionBehavior 126 | Apply a certain collectionBehavior to the window. 127 | ```swift 128 | func collectionBehavior(_ behavior: NSWindow.CollectionBehavior) 129 | ``` 130 | 131 | #### tabbingMode 132 | Set the tabbingMode for the window. 133 | If set to preferred, new windows will be opened as tabs. 134 | 135 | ```swift 136 | func tabbingMode(_ mode: NSWindow.TabbingMode) 137 | ``` 138 | 139 | #### backgroundColor 140 | Sets the background color of the window. 141 | ```swift 142 | func backgroundColor(_ color: NSColor) 143 | ``` 144 | 145 | #### titlebarAppearsTransparent 146 | Makes the titlebar transparent. 147 | ```swift 148 | func titlebarAppearsTransparent(_ value: Bool) 149 | ``` 150 | 151 | #### disableRestoreOnLaunch 152 | This will stop windows from relaunching if they were open in the last active app state. 153 | > Note: While this should work fine, I can't guarantee it. 154 | ```swift 155 | func disableRestoreOnLaunch() 156 | ``` 157 | 158 | #### transition 159 | Configure the NSWindow.AnimationBehavior for the Scene 160 | ```swift 161 | func transition(_ transition: NSWindow.AnimationBehavior) 162 | ``` 163 | 164 | ## Available View modifiers 165 | Inject the current window into the environment. 166 | ```swift 167 | func injectWindow(_ identifier: String) 168 | ``` 169 | 170 | Inject the settings window into the environment. 171 | ```swift 172 | func injectSettingsWindow() 173 | ``` 174 | 175 | Get the NSWindow from a View. 176 | ```swift 177 | @Environment(\.window) var window 178 | ``` 179 | 180 | ## NSDocumentGroup 181 | NSDocumentGroup is an alternative to SwiftUI's documentgroup. It allows you to use a SwiftUI Window with an NSDocument. This is useful for cases where you need SwiftUI (for example, to make `.focusedValue` work), but `FileDocument` or `ReferenceFileDocument` are not meeting your requirements. One of the issues I ran into with these types is that they become slow when opening large folders (for example, a node_modules folder). Using an NSDocument allows you to optimize this. 182 | 183 | #### Usage 184 | See the example folder for a working sample project. 185 | 186 | First, create a NSDocument class (make sure to add the filetype to the project config). 187 | Override the `makeWindowControllers()` type and open a new SwiftUI window by calling `openDocument`. 188 | ```swift 189 | override func makeWindowControllers() { 190 | if let window = NSApp.openDocument(self), let windowController = window.windowController { 191 | addWindowController(windowController) 192 | } 193 | } 194 | ``` 195 | Add a new NSDocumentGroup Scene to your app. The scene provides a reference to the opened NSDocument. 196 | ```swift 197 | NSDocumentGroup(for: CodeFileDocument.self) { document in 198 | Text(document.fileURL?.absoluteString ?? "") 199 | } 200 | ``` 201 | 202 | ## Extras 203 | 204 | Make SwiftUI Materials always active 205 | > Note: Set this once in the initializer of your implementation of the `App` protocol 206 | ```swift 207 | NSWindow.alwaysUseActiveAppearance = true 208 | ``` 209 | 210 | ## Example 211 | An example project can be found in the project repository. 212 | -------------------------------------------------------------------------------- /Example/WindowManagementProject.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6C11A8C02A36610500E86728 /* CodeFileDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C11A8BF2A36610500E86728 /* CodeFileDocument.swift */; }; 11 | 6C11A8C42A368C3F00E86728 /* WindowManagement in Frameworks */ = {isa = PBXBuildFile; productRef = 6C11A8C32A368C3F00E86728 /* WindowManagement */; }; 12 | 6C11A8C72A368E7B00E86728 /* WindowManagement in Frameworks */ = {isa = PBXBuildFile; productRef = 6C11A8C62A368E7B00E86728 /* WindowManagement */; }; 13 | 6CC243D12A3648FE003F9C1A /* WindowManagementProjectApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CC243D02A3648FE003F9C1A /* WindowManagementProjectApp.swift */; }; 14 | 6CC243D32A3648FE003F9C1A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CC243D22A3648FE003F9C1A /* ContentView.swift */; }; 15 | 6CC243D52A3648FF003F9C1A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6CC243D42A3648FF003F9C1A /* Assets.xcassets */; }; 16 | 6CC243D82A364900003F9C1A /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6CC243D72A364900003F9C1A /* Preview Assets.xcassets */; }; 17 | 6CC243E12A36491D003F9C1A /* WindowManagement in Frameworks */ = {isa = PBXBuildFile; productRef = 6CC243E02A36491D003F9C1A /* WindowManagement */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 6C11A8BF2A36610500E86728 /* CodeFileDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeFileDocument.swift; sourceTree = ""; }; 22 | 6CC243CD2A3648FE003F9C1A /* WindowManagementProject.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WindowManagementProject.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | 6CC243D02A3648FE003F9C1A /* WindowManagementProjectApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowManagementProjectApp.swift; sourceTree = ""; }; 24 | 6CC243D22A3648FE003F9C1A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 25 | 6CC243D42A3648FF003F9C1A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26 | 6CC243D72A364900003F9C1A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 27 | 6CC243D92A364900003F9C1A /* WindowManagementProject.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WindowManagementProject.entitlements; sourceTree = ""; }; 28 | 6CC243E22A365406003F9C1A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 6CC243CA2A3648FE003F9C1A /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | 6C11A8C72A368E7B00E86728 /* WindowManagement in Frameworks */, 37 | 6CC243E12A36491D003F9C1A /* WindowManagement in Frameworks */, 38 | 6C11A8C42A368C3F00E86728 /* WindowManagement in Frameworks */, 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXFrameworksBuildPhase section */ 43 | 44 | /* Begin PBXGroup section */ 45 | 6CC243C42A3648FE003F9C1A = { 46 | isa = PBXGroup; 47 | children = ( 48 | 6CC243CF2A3648FE003F9C1A /* WindowManagementProject */, 49 | 6CC243CE2A3648FE003F9C1A /* Products */, 50 | ); 51 | sourceTree = ""; 52 | }; 53 | 6CC243CE2A3648FE003F9C1A /* Products */ = { 54 | isa = PBXGroup; 55 | children = ( 56 | 6CC243CD2A3648FE003F9C1A /* WindowManagementProject.app */, 57 | ); 58 | name = Products; 59 | sourceTree = ""; 60 | }; 61 | 6CC243CF2A3648FE003F9C1A /* WindowManagementProject */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | 6CC243E22A365406003F9C1A /* Info.plist */, 65 | 6CC243D02A3648FE003F9C1A /* WindowManagementProjectApp.swift */, 66 | 6C11A8BF2A36610500E86728 /* CodeFileDocument.swift */, 67 | 6CC243D22A3648FE003F9C1A /* ContentView.swift */, 68 | 6CC243D42A3648FF003F9C1A /* Assets.xcassets */, 69 | 6CC243D92A364900003F9C1A /* WindowManagementProject.entitlements */, 70 | 6CC243D62A364900003F9C1A /* Preview Content */, 71 | ); 72 | path = WindowManagementProject; 73 | sourceTree = ""; 74 | }; 75 | 6CC243D62A364900003F9C1A /* Preview Content */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 6CC243D72A364900003F9C1A /* Preview Assets.xcassets */, 79 | ); 80 | path = "Preview Content"; 81 | sourceTree = ""; 82 | }; 83 | /* End PBXGroup section */ 84 | 85 | /* Begin PBXNativeTarget section */ 86 | 6CC243CC2A3648FE003F9C1A /* WindowManagementProject */ = { 87 | isa = PBXNativeTarget; 88 | buildConfigurationList = 6CC243DC2A364900003F9C1A /* Build configuration list for PBXNativeTarget "WindowManagementProject" */; 89 | buildPhases = ( 90 | 6CC243C92A3648FE003F9C1A /* Sources */, 91 | 6CC243CA2A3648FE003F9C1A /* Frameworks */, 92 | 6CC243CB2A3648FE003F9C1A /* Resources */, 93 | ); 94 | buildRules = ( 95 | ); 96 | dependencies = ( 97 | ); 98 | name = WindowManagementProject; 99 | packageProductDependencies = ( 100 | 6CC243E02A36491D003F9C1A /* WindowManagement */, 101 | 6C11A8C32A368C3F00E86728 /* WindowManagement */, 102 | 6C11A8C62A368E7B00E86728 /* WindowManagement */, 103 | ); 104 | productName = WindowManagementProject; 105 | productReference = 6CC243CD2A3648FE003F9C1A /* WindowManagementProject.app */; 106 | productType = "com.apple.product-type.application"; 107 | }; 108 | /* End PBXNativeTarget section */ 109 | 110 | /* Begin PBXProject section */ 111 | 6CC243C52A3648FE003F9C1A /* Project object */ = { 112 | isa = PBXProject; 113 | attributes = { 114 | BuildIndependentTargetsInParallel = 1; 115 | LastSwiftUpdateCheck = 1500; 116 | LastUpgradeCheck = 1500; 117 | TargetAttributes = { 118 | 6CC243CC2A3648FE003F9C1A = { 119 | CreatedOnToolsVersion = 15.0; 120 | }; 121 | }; 122 | }; 123 | buildConfigurationList = 6CC243C82A3648FE003F9C1A /* Build configuration list for PBXProject "WindowManagementProject" */; 124 | compatibilityVersion = "Xcode 14.0"; 125 | developmentRegion = en; 126 | hasScannedForEncodings = 0; 127 | knownRegions = ( 128 | en, 129 | Base, 130 | ); 131 | mainGroup = 6CC243C42A3648FE003F9C1A; 132 | packageReferences = ( 133 | 6C11A8C52A368E7B00E86728 /* XCRemoteSwiftPackageReference "SwiftUI-WindowManagement" */, 134 | ); 135 | productRefGroup = 6CC243CE2A3648FE003F9C1A /* Products */; 136 | projectDirPath = ""; 137 | projectRoot = ""; 138 | targets = ( 139 | 6CC243CC2A3648FE003F9C1A /* WindowManagementProject */, 140 | ); 141 | }; 142 | /* End PBXProject section */ 143 | 144 | /* Begin PBXResourcesBuildPhase section */ 145 | 6CC243CB2A3648FE003F9C1A /* Resources */ = { 146 | isa = PBXResourcesBuildPhase; 147 | buildActionMask = 2147483647; 148 | files = ( 149 | 6CC243D82A364900003F9C1A /* Preview Assets.xcassets in Resources */, 150 | 6CC243D52A3648FF003F9C1A /* Assets.xcassets in Resources */, 151 | ); 152 | runOnlyForDeploymentPostprocessing = 0; 153 | }; 154 | /* End PBXResourcesBuildPhase section */ 155 | 156 | /* Begin PBXSourcesBuildPhase section */ 157 | 6CC243C92A3648FE003F9C1A /* Sources */ = { 158 | isa = PBXSourcesBuildPhase; 159 | buildActionMask = 2147483647; 160 | files = ( 161 | 6CC243D32A3648FE003F9C1A /* ContentView.swift in Sources */, 162 | 6CC243D12A3648FE003F9C1A /* WindowManagementProjectApp.swift in Sources */, 163 | 6C11A8C02A36610500E86728 /* CodeFileDocument.swift in Sources */, 164 | ); 165 | runOnlyForDeploymentPostprocessing = 0; 166 | }; 167 | /* End PBXSourcesBuildPhase section */ 168 | 169 | /* Begin XCBuildConfiguration section */ 170 | 6CC243DA2A364900003F9C1A /* Debug */ = { 171 | isa = XCBuildConfiguration; 172 | buildSettings = { 173 | ALWAYS_SEARCH_USER_PATHS = NO; 174 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 175 | CLANG_ANALYZER_NONNULL = YES; 176 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 177 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 178 | CLANG_ENABLE_MODULES = YES; 179 | CLANG_ENABLE_OBJC_ARC = YES; 180 | CLANG_ENABLE_OBJC_WEAK = YES; 181 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 182 | CLANG_WARN_BOOL_CONVERSION = YES; 183 | CLANG_WARN_COMMA = YES; 184 | CLANG_WARN_CONSTANT_CONVERSION = YES; 185 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 186 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 187 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 188 | CLANG_WARN_EMPTY_BODY = YES; 189 | CLANG_WARN_ENUM_CONVERSION = YES; 190 | CLANG_WARN_INFINITE_RECURSION = YES; 191 | CLANG_WARN_INT_CONVERSION = YES; 192 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 193 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 194 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 195 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 196 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 197 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 198 | CLANG_WARN_STRICT_PROTOTYPES = YES; 199 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 200 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 201 | CLANG_WARN_UNREACHABLE_CODE = YES; 202 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 203 | COPY_PHASE_STRIP = NO; 204 | DEBUG_INFORMATION_FORMAT = dwarf; 205 | ENABLE_STRICT_OBJC_MSGSEND = YES; 206 | ENABLE_TESTABILITY = YES; 207 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 208 | GCC_C_LANGUAGE_STANDARD = gnu17; 209 | GCC_DYNAMIC_NO_PIC = NO; 210 | GCC_NO_COMMON_BLOCKS = YES; 211 | GCC_OPTIMIZATION_LEVEL = 0; 212 | GCC_PREPROCESSOR_DEFINITIONS = ( 213 | "DEBUG=1", 214 | "$(inherited)", 215 | ); 216 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 217 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 218 | GCC_WARN_UNDECLARED_SELECTOR = YES; 219 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 220 | GCC_WARN_UNUSED_FUNCTION = YES; 221 | GCC_WARN_UNUSED_VARIABLE = YES; 222 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 223 | MACOSX_DEPLOYMENT_TARGET = 14.0; 224 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 225 | MTL_FAST_MATH = YES; 226 | ONLY_ACTIVE_ARCH = YES; 227 | SDKROOT = macosx; 228 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 229 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 230 | }; 231 | name = Debug; 232 | }; 233 | 6CC243DB2A364900003F9C1A /* Release */ = { 234 | isa = XCBuildConfiguration; 235 | buildSettings = { 236 | ALWAYS_SEARCH_USER_PATHS = NO; 237 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 238 | CLANG_ANALYZER_NONNULL = YES; 239 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 240 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 241 | CLANG_ENABLE_MODULES = YES; 242 | CLANG_ENABLE_OBJC_ARC = YES; 243 | CLANG_ENABLE_OBJC_WEAK = YES; 244 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 245 | CLANG_WARN_BOOL_CONVERSION = YES; 246 | CLANG_WARN_COMMA = YES; 247 | CLANG_WARN_CONSTANT_CONVERSION = YES; 248 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 249 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 250 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 251 | CLANG_WARN_EMPTY_BODY = YES; 252 | CLANG_WARN_ENUM_CONVERSION = YES; 253 | CLANG_WARN_INFINITE_RECURSION = YES; 254 | CLANG_WARN_INT_CONVERSION = YES; 255 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 256 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 257 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 258 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 259 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 260 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 261 | CLANG_WARN_STRICT_PROTOTYPES = YES; 262 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 263 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 264 | CLANG_WARN_UNREACHABLE_CODE = YES; 265 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 266 | COPY_PHASE_STRIP = NO; 267 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 268 | ENABLE_NS_ASSERTIONS = NO; 269 | ENABLE_STRICT_OBJC_MSGSEND = YES; 270 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 271 | GCC_C_LANGUAGE_STANDARD = gnu17; 272 | GCC_NO_COMMON_BLOCKS = YES; 273 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 274 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 275 | GCC_WARN_UNDECLARED_SELECTOR = YES; 276 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 277 | GCC_WARN_UNUSED_FUNCTION = YES; 278 | GCC_WARN_UNUSED_VARIABLE = YES; 279 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 280 | MACOSX_DEPLOYMENT_TARGET = 14.0; 281 | MTL_ENABLE_DEBUG_INFO = NO; 282 | MTL_FAST_MATH = YES; 283 | SDKROOT = macosx; 284 | SWIFT_COMPILATION_MODE = wholemodule; 285 | }; 286 | name = Release; 287 | }; 288 | 6CC243DD2A364900003F9C1A /* Debug */ = { 289 | isa = XCBuildConfiguration; 290 | buildSettings = { 291 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 292 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 293 | CODE_SIGN_ENTITLEMENTS = WindowManagementProject/WindowManagementProject.entitlements; 294 | CODE_SIGN_STYLE = Automatic; 295 | COMBINE_HIDPI_IMAGES = YES; 296 | CURRENT_PROJECT_VERSION = 1; 297 | DEVELOPMENT_ASSET_PATHS = "\"WindowManagementProject/Preview Content\""; 298 | DEVELOPMENT_TEAM = 2MMGJGVTB4; 299 | ENABLE_HARDENED_RUNTIME = YES; 300 | ENABLE_PREVIEWS = YES; 301 | GENERATE_INFOPLIST_FILE = YES; 302 | INFOPLIST_FILE = WindowManagementProject/Info.plist; 303 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 304 | LD_RUNPATH_SEARCH_PATHS = ( 305 | "$(inherited)", 306 | "@executable_path/../Frameworks", 307 | ); 308 | MARKETING_VERSION = 1.0; 309 | PRODUCT_BUNDLE_IDENTIFIER = com.tweety.WindowManagementProject; 310 | PRODUCT_NAME = "$(TARGET_NAME)"; 311 | SWIFT_EMIT_LOC_STRINGS = YES; 312 | SWIFT_VERSION = 5.0; 313 | }; 314 | name = Debug; 315 | }; 316 | 6CC243DE2A364900003F9C1A /* Release */ = { 317 | isa = XCBuildConfiguration; 318 | buildSettings = { 319 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 320 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 321 | CODE_SIGN_ENTITLEMENTS = WindowManagementProject/WindowManagementProject.entitlements; 322 | CODE_SIGN_STYLE = Automatic; 323 | COMBINE_HIDPI_IMAGES = YES; 324 | CURRENT_PROJECT_VERSION = 1; 325 | DEVELOPMENT_ASSET_PATHS = "\"WindowManagementProject/Preview Content\""; 326 | DEVELOPMENT_TEAM = 2MMGJGVTB4; 327 | ENABLE_HARDENED_RUNTIME = YES; 328 | ENABLE_PREVIEWS = YES; 329 | GENERATE_INFOPLIST_FILE = YES; 330 | INFOPLIST_FILE = WindowManagementProject/Info.plist; 331 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 332 | LD_RUNPATH_SEARCH_PATHS = ( 333 | "$(inherited)", 334 | "@executable_path/../Frameworks", 335 | ); 336 | MARKETING_VERSION = 1.0; 337 | PRODUCT_BUNDLE_IDENTIFIER = com.tweety.WindowManagementProject; 338 | PRODUCT_NAME = "$(TARGET_NAME)"; 339 | SWIFT_EMIT_LOC_STRINGS = YES; 340 | SWIFT_VERSION = 5.0; 341 | }; 342 | name = Release; 343 | }; 344 | /* End XCBuildConfiguration section */ 345 | 346 | /* Begin XCConfigurationList section */ 347 | 6CC243C82A3648FE003F9C1A /* Build configuration list for PBXProject "WindowManagementProject" */ = { 348 | isa = XCConfigurationList; 349 | buildConfigurations = ( 350 | 6CC243DA2A364900003F9C1A /* Debug */, 351 | 6CC243DB2A364900003F9C1A /* Release */, 352 | ); 353 | defaultConfigurationIsVisible = 0; 354 | defaultConfigurationName = Release; 355 | }; 356 | 6CC243DC2A364900003F9C1A /* Build configuration list for PBXNativeTarget "WindowManagementProject" */ = { 357 | isa = XCConfigurationList; 358 | buildConfigurations = ( 359 | 6CC243DD2A364900003F9C1A /* Debug */, 360 | 6CC243DE2A364900003F9C1A /* Release */, 361 | ); 362 | defaultConfigurationIsVisible = 0; 363 | defaultConfigurationName = Release; 364 | }; 365 | /* End XCConfigurationList section */ 366 | 367 | /* Begin XCRemoteSwiftPackageReference section */ 368 | 6C11A8C52A368E7B00E86728 /* XCRemoteSwiftPackageReference "SwiftUI-WindowManagement" */ = { 369 | isa = XCRemoteSwiftPackageReference; 370 | repositoryURL = "https://github.com/Wouter01/SwiftUI-WindowManagement"; 371 | requirement = { 372 | kind = upToNextMajorVersion; 373 | minimumVersion = 2.0.0; 374 | }; 375 | }; 376 | /* End XCRemoteSwiftPackageReference section */ 377 | 378 | /* Begin XCSwiftPackageProductDependency section */ 379 | 6C11A8C32A368C3F00E86728 /* WindowManagement */ = { 380 | isa = XCSwiftPackageProductDependency; 381 | productName = WindowManagement; 382 | }; 383 | 6C11A8C62A368E7B00E86728 /* WindowManagement */ = { 384 | isa = XCSwiftPackageProductDependency; 385 | package = 6C11A8C52A368E7B00E86728 /* XCRemoteSwiftPackageReference "SwiftUI-WindowManagement" */; 386 | productName = WindowManagement; 387 | }; 388 | 6CC243E02A36491D003F9C1A /* WindowManagement */ = { 389 | isa = XCSwiftPackageProductDependency; 390 | productName = WindowManagement; 391 | }; 392 | /* End XCSwiftPackageProductDependency section */ 393 | }; 394 | rootObject = 6CC243C52A3648FE003F9C1A /* Project object */; 395 | } 396 | --------------------------------------------------------------------------------