308 lines
8.2 KiB
Markdown
308 lines
8.2 KiB
Markdown
---
|
|
source: ~/DuckDuckGo/apple-browsers.git/main/.cursor/rules/macos-window-management.mdc
|
|
confidence: 0.9
|
|
namespace: work
|
|
last_synced: 2026-04-28
|
|
alwaysApply: false
|
|
---
|
|
|
|
# macOS Window Management and AppKit Patterns
|
|
|
|
## WindowsManager for Window Operations
|
|
ALWAYS use WindowsManager for creating and managing browser windows:
|
|
|
|
```swift
|
|
// ✅ CORRECT - WindowsManager usage
|
|
@MainActor
|
|
final class FeatureCoordinator {
|
|
func openNewWindow() {
|
|
let tabCollection = TabCollectionViewModel()
|
|
WindowsManager.openNewWindow(
|
|
with: tabCollection,
|
|
burnerMode: .regular,
|
|
droppingPoint: nil
|
|
)
|
|
}
|
|
|
|
func openWindowWithURL(_ url: URL) {
|
|
let tabCollection = TabCollectionViewModel()
|
|
let window = WindowsManager.openNewWindow(with: tabCollection)
|
|
window?.tabCollectionViewModel.addTab(with: url)
|
|
}
|
|
}
|
|
|
|
// ❌ INCORRECT - Direct window creation
|
|
final class FeatureCoordinator {
|
|
func openNewWindow() {
|
|
let window = NSWindow() // Don't create windows directly
|
|
window.makeKeyAndOrderFront(nil)
|
|
}
|
|
}
|
|
```
|
|
|
|
## Window Controller Architecture
|
|
Use NSWindowController for complex window management:
|
|
|
|
```swift
|
|
// ✅ CORRECT - NSWindowController pattern
|
|
final class FeatureWindowController: NSWindowController {
|
|
private let viewModel: FeatureViewModel
|
|
|
|
init(viewModel: FeatureViewModel) {
|
|
self.viewModel = viewModel
|
|
super.init(window: nil)
|
|
setupWindow()
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
private func setupWindow() {
|
|
let contentViewController = FeatureViewController(viewModel: viewModel)
|
|
|
|
window = NSWindow(contentViewController: contentViewController)
|
|
window?.setContentSize(NSSize(width: 800, height: 600))
|
|
window?.minSize = NSSize(width: 400, height: 300)
|
|
window?.center()
|
|
window?.title = "Feature Window"
|
|
|
|
// Configure window behavior
|
|
window?.isRestorable = true
|
|
window?.identifier = NSUserInterfaceItemIdentifier("FeatureWindow")
|
|
}
|
|
|
|
override func windowDidLoad() {
|
|
super.windowDidLoad()
|
|
|
|
// Additional window setup
|
|
window?.delegate = self
|
|
setupToolbar()
|
|
}
|
|
}
|
|
|
|
// MARK: - NSWindowDelegate
|
|
extension FeatureWindowController: NSWindowDelegate {
|
|
func windowWillClose(_ notification: Notification) {
|
|
// Clean up resources
|
|
viewModel.cleanup()
|
|
}
|
|
|
|
func windowDidBecomeMain(_ notification: Notification) {
|
|
// Handle window becoming main
|
|
viewModel.windowDidBecomeActive()
|
|
}
|
|
}
|
|
```
|
|
|
|
## Multi-Window State Management
|
|
Use TabCollectionViewModel for window-specific state:
|
|
|
|
```swift
|
|
// ✅ CORRECT - Window-specific state management
|
|
@MainActor
|
|
final class WindowCoordinator {
|
|
private let tabCollectionViewModel: TabCollectionViewModel
|
|
private weak var windowController: NSWindowController?
|
|
|
|
init(tabCollectionViewModel: TabCollectionViewModel) {
|
|
self.tabCollectionViewModel = tabCollectionViewModel
|
|
}
|
|
|
|
func currentTab() -> Tab? {
|
|
return tabCollectionViewModel.selectedTab
|
|
}
|
|
|
|
func addNewTab(with url: URL? = nil) {
|
|
tabCollectionViewModel.addTab(with: url)
|
|
}
|
|
|
|
func closeCurrentTab() {
|
|
guard let currentTab = tabCollectionViewModel.selectedTab else { return }
|
|
tabCollectionViewModel.removeTab(currentTab)
|
|
}
|
|
|
|
func closeWindow() {
|
|
windowController?.close()
|
|
}
|
|
}
|
|
```
|
|
|
|
## Window State Restoration
|
|
Implement proper state restoration:
|
|
|
|
```swift
|
|
// ✅ CORRECT - Window state restoration
|
|
extension FeatureWindowController {
|
|
override func restoreState(with coder: NSCoder) {
|
|
super.restoreState(with: coder)
|
|
|
|
// Restore window-specific state
|
|
if let savedData = coder.decodeObject(forKey: "viewModelState") as? Data {
|
|
viewModel.restoreState(from: savedData)
|
|
}
|
|
}
|
|
|
|
override func encodeRestorableState(with coder: NSCoder) {
|
|
super.encodeRestorableState(with: coder)
|
|
|
|
// Save window-specific state
|
|
if let stateData = viewModel.encodeState() {
|
|
coder.encode(stateData, forKey: "viewModelState")
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## NSViewController and SwiftUI Integration
|
|
Use NSHostingView for SwiftUI integration:
|
|
|
|
```swift
|
|
// ✅ CORRECT - NSHostingView integration
|
|
final class FeatureViewController: NSViewController {
|
|
private let viewModel: FeatureViewModel
|
|
|
|
init(viewModel: FeatureViewModel) {
|
|
self.viewModel = viewModel
|
|
super.init(nibName: nil, bundle: nil)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func loadView() {
|
|
view = NSHostingView(rootView: FeatureView(viewModel: viewModel))
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
title = "Feature"
|
|
preferredContentSize = NSSize(width: 400, height: 300)
|
|
}
|
|
|
|
override func viewWillAppear() {
|
|
super.viewWillAppear()
|
|
viewModel.viewWillAppear()
|
|
}
|
|
|
|
override func viewDidAppear() {
|
|
super.viewDidAppear()
|
|
viewModel.viewDidAppear()
|
|
}
|
|
}
|
|
```
|
|
|
|
## View Controller Lifecycle
|
|
Follow AppKit view controller patterns:
|
|
|
|
```swift
|
|
// ✅ CORRECT - AppKit lifecycle management
|
|
final class FeatureViewController: NSViewController {
|
|
private var observations: Set<NSObjectProtocol> = []
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
setupUI()
|
|
bindViewModel()
|
|
setupNotifications()
|
|
}
|
|
|
|
override func viewWillAppear() {
|
|
super.viewWillAppear()
|
|
viewModel.refreshData()
|
|
}
|
|
|
|
override func viewDidAppear() {
|
|
super.viewDidAppear()
|
|
// View is fully visible
|
|
viewModel.trackViewAppearance()
|
|
}
|
|
|
|
override func viewWillDisappear() {
|
|
super.viewWillDisappear()
|
|
viewModel.saveUserChanges()
|
|
}
|
|
|
|
private func setupNotifications() {
|
|
let observation = NotificationCenter.default.addObserver(
|
|
forName: .dataUpdated,
|
|
object: nil,
|
|
queue: .main
|
|
) { [weak self] _ in
|
|
self?.viewModel.refreshData()
|
|
}
|
|
observations.insert(observation)
|
|
}
|
|
|
|
deinit {
|
|
observations.forEach { NotificationCenter.default.removeObserver($0) }
|
|
observations.removeAll()
|
|
}
|
|
}
|
|
```
|
|
|
|
## Memory Management for Multiple Windows
|
|
Implement proper cleanup for window controllers:
|
|
|
|
```swift
|
|
// ✅ CORRECT - Window memory management
|
|
final class FeatureWindowController: NSWindowController {
|
|
private var observers: Set<NSObjectProtocol> = []
|
|
|
|
deinit {
|
|
// Clean up resources
|
|
observers.forEach { NotificationCenter.default.removeObserver($0) }
|
|
observers.removeAll()
|
|
viewModel.cleanup()
|
|
}
|
|
|
|
override func close() {
|
|
// Prepare for closure
|
|
viewModel.saveState()
|
|
super.close()
|
|
}
|
|
|
|
func cleanupBeforeClose() {
|
|
// Cancel any ongoing operations
|
|
viewModel.cancelOngoingOperations()
|
|
|
|
// Remove from window tracking
|
|
WindowTracker.shared.removeWindow(self)
|
|
}
|
|
}
|
|
```
|
|
|
|
## Window Cascading and Positioning
|
|
Handle window positioning properly:
|
|
|
|
```swift
|
|
// ✅ CORRECT - Window positioning
|
|
extension WindowsManager {
|
|
static func positionNewWindow(_ window: NSWindow) {
|
|
if let lastWindow = NSApp.orderedWindows.first {
|
|
let origin = lastWindow.frame.origin
|
|
let offset: CGFloat = 30
|
|
|
|
let newOrigin = NSPoint(
|
|
x: origin.x + offset,
|
|
y: origin.y - offset
|
|
)
|
|
|
|
// Ensure window stays on screen
|
|
let screenFrame = window.screen?.visibleFrame ?? NSScreen.main?.visibleFrame ?? .zero
|
|
|
|
if screenFrame.contains(NSRect(origin: newOrigin, size: window.frame.size)) {
|
|
window.setFrameOrigin(newOrigin)
|
|
} else {
|
|
window.center()
|
|
}
|
|
} else {
|
|
window.center()
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
See `macos-system-integration.md` for system-level integration patterns and `macos-preferences.md` for preferences management.
|