Skip to content

fix(macos): convert Main.storyboard using programmatic AppKit APIs - #2836

Merged
Tommy Nguyen (tido64) merged 1 commit into
trunkfrom
tido/replace-storyboard
Jul 7, 2026
Merged

fix(macos): convert Main.storyboard using programmatic AppKit APIs#2836
Tommy Nguyen (tido64) merged 1 commit into
trunkfrom
tido/replace-storyboard

Conversation

@tido64

@tido64Tommy Nguyen (tido64) commented Jul 3, 2026

Copy link
Copy Markdown
Member

Description

Resolves#111.

Platforms affected

  • Android
  • iOS
  • macOS
  • visionOS
  • Windows

Test plan

cd packages/example-macos/
pod install --project-directory=macos
yarn macos
# In a separate terminal
yarn start

Screenshots

image

More below…

@tido64Tommy Nguyen (tido64) left a comment

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main menu screenshots 👇

Comment on lines +76 to +93
return NSMenuItem(submenu: title, items: [
NSMenuItem(title: "About \(title)", action: #selector(NSApplication.orderFrontStandardAboutPanel(_:))),
.separator(),
NSMenuItem(title: "Preferences…", keyEquivalent: ","),
.separator(),
NSMenuItem(submenu: servicesMenu),
.separator(),
NSMenuItem(title: "Hide \(title)", action: #selector(NSApplication.hide(_:)), keyEquivalent: "h"),
NSMenuItem(
title: "Hide Others",
action: #selector(NSApplication.hideOtherApplications(_:)),
keyEquivalent: "h",
keyEquivalentModifierMask: [.option, .command]
),
NSMenuItem(title: "Show All", action: #selector(NSApplication.unhideAllApplications(_:))),
.separator(),
NSMenuItem(title: "Quit \(title)", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"),
])

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Comment on lines +97 to +116
NSMenuItem(submenu: "File", items: [
NSMenuItem(title: "New", action: #selector(NSDocumentController.newDocument(_:)), keyEquivalent: "n"),
NSMenuItem(title: "Open…", action: #selector(NSDocumentController.openDocument(_:)), keyEquivalent: "o"),
NSMenuItem(submenu: "Open Recent", items: [
NSMenuItem(title: "Clear Menu", action: #selector(NSDocumentController.clearRecentDocuments(_:))),
]),
.separator(),
NSMenuItem(title: "Close", action: #selector(NSWindow.performClose(_:)), keyEquivalent: "w"),
NSMenuItem(title: "Save…", action: #selector(NSDocument.save(_:)), keyEquivalent: "s"),
NSMenuItem(title: "Save As…", action: #selector(NSDocument.saveAs(_:)), keyEquivalent: "S"),
NSMenuItem(title: "Revert to Saved", action: #selector(NSDocument.revertToSaved(_:)), keyEquivalent: "r"),
.separator(),
NSMenuItem(
title: "Page Setup…",
action: #selector(NSDocument.runPageLayout(_:)),
keyEquivalent: "P",
keyEquivalentModifierMask: [.shift, .command]
),
NSMenuItem(title: "Print…", action: #selector(NSView.printView(_:)), keyEquivalent: "p"),
])

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Comment on lines +120 to +239
NSMenuItem(submenu: "Edit", items: [
NSMenuItem(title: "Undo", action: Selector(("undo:")), keyEquivalent: "z"),
NSMenuItem(title: "Redo", action: Selector(("redo:")), keyEquivalent: "Z"),
.separator(),
NSMenuItem(title: "Cut", action: #selector(NSText.cut(_:)), keyEquivalent: "x"),
NSMenuItem(title: "Copy", action: #selector(NSText.copy(_:)), keyEquivalent: "c"),
NSMenuItem(title: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v"),
NSMenuItem(
title: "Paste and Match Style",
action: #selector(NSTextView.pasteAsPlainText(_:)),
keyEquivalent: "V",
keyEquivalentModifierMask: [.option, .command]
),
NSMenuItem(title: "Delete", action: #selector(NSText.delete(_:))),
NSMenuItem(title: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a"),
.separator(),
NSMenuItem(submenu: "Find", items: [
NSMenuItem(
title: "Find…",
action: #selector(NSTextView.performFindPanelAction(_:)),
keyEquivalent: "f",
tag: 1
),
NSMenuItem(
title: "Find and Replace…",
action: #selector(NSTextView.performFindPanelAction(_:)),
keyEquivalent: "f",
keyEquivalentModifierMask: [.option, .command],
tag: 12
),
NSMenuItem(
title: "Find Next",
action: #selector(NSTextView.performFindPanelAction(_:)),
keyEquivalent: "g",
tag: 2
),
NSMenuItem(
title: "Find Previous",
action: #selector(NSTextView.performFindPanelAction(_:)),
keyEquivalent: "G",
tag: 3
),
NSMenuItem(
title: "Use Selection for Find",
action: #selector(NSTextView.performFindPanelAction(_:)),
keyEquivalent: "e",
tag: 7
),
NSMenuItem(
title: "Jump to Selection",
action: #selector(NSTextView.centerSelectionInVisibleArea(_:)),
keyEquivalent: "j"
),
]),
NSMenuItem(submenu: "Spelling and Grammar", items: [
NSMenuItem(
title: "Show Spelling and Grammar",
action: #selector(NSText.showGuessPanel(_:)),
keyEquivalent: ":"
),
NSMenuItem(
title: "Check Document Now",
action: #selector(NSText.checkSpelling(_:)),
keyEquivalent: ";"
),
.separator(),
NSMenuItem(
title: "Check Spelling While Typing",
action: #selector(NSTextView.toggleContinuousSpellChecking(_:))
),
NSMenuItem(
title: "Check Grammar With Spelling",
action: #selector(NSTextView.toggleGrammarChecking(_:))
),
NSMenuItem(
title: "Correct Spelling Automatically",
action: #selector(NSTextView.toggleAutomaticSpellingCorrection(_:))
),
]),
NSMenuItem(submenu: "Substitutions", items: [
NSMenuItem(
title: "Show Substitutions",
action: #selector(NSTextView.orderFrontSubstitutionsPanel(_:))
),
.separator(),
NSMenuItem(
title: "Smart Copy/Paste",
action: #selector(NSTextView.toggleSmartInsertDelete(_:))
),
NSMenuItem(
title: "Smart Quotes",
action: #selector(NSTextView.toggleAutomaticQuoteSubstitution(_:))
),
NSMenuItem(
title: "Smart Dashes",
action: #selector(NSTextView.toggleAutomaticDashSubstitution(_:))
),
NSMenuItem(
title: "Smart Links",
action: #selector(NSTextView.toggleAutomaticLinkDetection(_:))
),
NSMenuItem(
title: "Data Detectors",
action: #selector(NSTextView.toggleAutomaticDataDetection(_:))
),
NSMenuItem(
title: "Text Replacement",
action: #selector(NSTextView.toggleAutomaticTextReplacement(_:))
),
]),
NSMenuItem(submenu: "Transformations", items: [
NSMenuItem(title: "Make Upper Case", action: #selector(NSTextView.uppercaseWord(_:))),
NSMenuItem(title: "Make Lower Case", action: #selector(NSTextView.lowercaseWord(_:))),
NSMenuItem(title: "Capitalize", action: #selector(NSTextView.capitalizeWord(_:))),
]),
NSMenuItem(submenu: "Speech", items: [
NSMenuItem(title: "Start Speaking", action: #selector(NSTextView.startSpeaking(_:))),
NSMenuItem(title: "Stop Speaking", action: #selector(NSTextView.stopSpeaking(_:))),
]),
])

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Comment on lines +243 to +267
NSMenuItem(submenu: "View", items: [
NSMenuItem(
title: "Show Toolbar",
action: #selector(NSWindow.toggleToolbarShown(_:)),
keyEquivalent: "t",
keyEquivalentModifierMask: [.option, .command]
),
NSMenuItem(
title: "Customize Toolbar…",
action: #selector(NSWindow.runToolbarCustomizationPalette(_:))
),
.separator(),
NSMenuItem(
title: "Show Sidebar",
action: #selector(NSSplitViewController.toggleSidebar(_:)),
keyEquivalent: "s",
keyEquivalentModifierMask: [.control, .command]
),
NSMenuItem(
title: "Enter Full Screen",
action: #selector(NSWindow.toggleFullScreen(_:)),
keyEquivalent: "f",
keyEquivalentModifierMask: [.control, .command]
),
])

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

Comment on lines +271 to +298
let rememberLastComponentMenuItem = NSMenuItem(
title: "Remember Last Opened Component",
action: #selector(onRememberLastComponentSelected(_:)),
target: self
)
rememberLastComponentMenuItem.isEnabled = false
self.rememberLastComponentMenuItem = rememberLastComponentMenuItem

let reactMenu = NSMenu(title: "React", items: [
NSMenuItem(
title: "Load Embedded JS Bundle",
action: #selector(onLoadEmbeddedBundleSelected(_:)),
target: self
),
NSMenuItem(
title: "Load From Dev Server",
action: #selector(onLoadFromDevServerSelected(_:)),
target: self
),
rememberLastComponentMenuItem,
.separator(),
])
reactMenu.autoenablesItems = false
self.reactMenu = reactMenu

let reactMenuItem = NSMenuItem(submenu: reactMenu)
reactMenuItem.isHidden = true
return reactMenuItem

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Comment on lines +302 to +311
let windowMenu = NSMenu(title: "Window", items: [
NSMenuItem(title: "Minimize", action: #selector(NSWindow.performMiniaturize(_:)), keyEquivalent: "m"),
NSMenuItem(title: "Zoom", action: #selector(NSWindow.performZoom(_:))),
.separator(),
NSMenuItem(title: "Bring All to Front", action: #selector(NSApplication.arrangeInFront(_:))),
])

NSApp.windowsMenu = windowMenu

return NSMenuItem(submenu: windowMenu)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Comment on lines +315 to +321
let helpMenu = NSMenu(title: "Help", items: [
NSMenuItem(title: "\(title) Help", action: #selector(NSApplication.showHelp(_:)), keyEquivalent: "?"),
])

NSApp.helpMenu = helpMenu

return NSMenuItem(submenu: helpMenu)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

@tido64
Tommy Nguyen (tido64)force-pushed the tido/replace-storyboard branch 2 times, most recently from 2298886 to c761fd2CompareJuly 3, 2026 13:03
@Saadnajmi

Copy link
Copy Markdown
Collaborator

I think I have a WIP in RNM where I changed the remote to SwiftUI with much of the same fratutes. I can dig it up?

@tido64

Tommy Nguyen (tido64) commented Jul 6, 2026

Copy link
Copy Markdown
MemberAuthor

I think I have a WIP in RNM where I changed the remote to SwiftUI with much of the same fratutes. I can dig it up?

What are "much of the same features"? And how much code is there? We have hooks in the AppDelegate that we need to keep for config-plugins but besides that, most of the code is just 1:1 converting the menu bar from the storyboard (that we probably should trim down). There aren't much to maintain so to speak.

@tido64
Tommy Nguyen (tido64) merged commit 9057a4f into trunkJul 7, 2026
23 checks passed
@tido64
Tommy Nguyen (tido64) deleted the tido/replace-storyboard branch July 7, 2026 10:17
Saad Najmi (Saadnajmi) added a commit to Saadnajmi/react-native-test-app that referenced this pull request Jul 7, 2026
Replace the programmatic AppKit menu bar (added in microsoft#2836) with the SwiftUI
App lifecycle. SwiftUI provides the standard App/File/Edit/View/Window/Help
menus for free, so the ~320 lines of makeMainMenu/make*MenuItem in
WindowController.swift are deleted.
The dynamic "React" menu is now declarative: ReactCommands renders a
CommandMenu("React") driven by an observable AppModel. Publishing
components/componentsEnabled/rememberLastComponent rebuilds the menu, so the
manual NSMenuItem add/remove bookkeeping is gone. An AppDelegate is kept via
@NSApplicationDelegateAdaptor for RN setup and lifecycle notifications.
- AppDelegate.swift: @main App struct + adaptor + AppModel
- ReactCommands.swift: dynamic React menu (Cmd-Shift-1..9 component shortcuts)
- ViewController.swift: SwiftUI content hosting + placeholder
- WindowController.swift: deleted
- project.pbxproj / pack.test.mts: swap WindowController.swift for ReactCommands.swift
Both ENABLE_SINGLE_APP_MODE and multi-app behavior are preserved.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Saad Najmi (Saadnajmi) added a commit to Saadnajmi/react-native-test-app that referenced this pull request Jul 7, 2026
Replace the programmatic AppKit menu bar (added in microsoft#2836) with the SwiftUI
App lifecycle. SwiftUI provides the standard App/File/Edit/View/Window/Help
menus for free, so the ~320 lines of makeMainMenu/make*MenuItem in
WindowController.swift are deleted.
The dynamic "React" menu is now declarative: ReactCommands renders a
CommandMenu("React") driven by an observable AppModel. Publishing
components/componentsEnabled/rememberLastComponent rebuilds the menu, so the
manual NSMenuItem add/remove bookkeeping is gone. An AppDelegate is kept via
@NSApplicationDelegateAdaptor for RN setup and lifecycle notifications.
- AppDelegate.swift: @main App struct + adaptor + AppModel
- ReactCommands.swift: dynamic React menu (Cmd-Shift-1..9 component shortcuts)
- ViewController.swift: SwiftUI content hosting + placeholder
- WindowController.swift: deleted
- project.pbxproj / pack.test.mts: swap WindowController.swift for ReactCommands.swift
Both ENABLE_SINGLE_APP_MODE and multi-app behavior are preserved.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Saad Najmi (Saadnajmi) added a commit to Saadnajmi/react-native-test-app that referenced this pull request Jul 10, 2026
Replace the programmatic AppKit menu bar (added in microsoft#2836) with the SwiftUI
App lifecycle. SwiftUI provides the standard App/File/Edit/View/Window/Help
menus for free, so the ~320 lines of makeMainMenu/make*MenuItem in
WindowController.swift are deleted.
The dynamic "React" menu is now declarative: ReactCommands renders a
CommandMenu("React") driven by an observable AppModel. Publishing
components/componentsEnabled/rememberLastComponent rebuilds the menu, so the
manual NSMenuItem add/remove bookkeeping is gone. An AppDelegate is kept via
@NSApplicationDelegateAdaptor for RN setup and lifecycle notifications.
- AppDelegate.swift: @main App struct + adaptor + AppModel
- ReactCommands.swift: dynamic React menu (Cmd-Shift-1..9 component shortcuts)
- ViewController.swift: SwiftUI content hosting + placeholder
- WindowController.swift: deleted
- project.pbxproj / pack.test.mts: swap WindowController.swift for ReactCommands.swift
Both ENABLE_SINGLE_APP_MODE and multi-app behavior are preserved.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

platform: iOSThis affects iOSplatform: macOSThis affects macOSplatform: visionOS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

macOS: Migrate to SwiftUI and deprecate Main.storyboard

2 participants

@tido64@Saadnajmi