[2026-04-28] Rename apple-browsers rules .mdc → .md for Obsidian compatibility
This commit is contained in:
@@ -0,0 +1,307 @@
|
||||
---
|
||||
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.
|
||||
Reference in New Issue
Block a user