Skip to content

fix: support Swift builds that use an Objective-C bridging header - #4364

Merged
Saad Najmi (Saadnajmi) merged 4 commits into
microsoft:mainfrom
Saadnajmi:sanajmi-microsoft-furn-bridging-header-compatibility
Oct 14, 2026
Merged

Saad Najmi (Saadnajmi) merged 4 commits into
microsoft:mainfrom
Saadnajmi:sanajmi-microsoft-furn-bridging-header-compatibility

Conversation

@Saadnajmi

@Saadnajmi Saad Najmi (Saadnajmi) commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Platforms Impacted

  • iOS
  • macOS
  • win32 (Office)
  • windows
  • android

Description of changes

Office builds FURN's native Swift with its own build system and is moving React Native macOS 0.81 to its Swift Package Manager layout, which no longer exports a React clang module. There, FURN's Swift targets see React Native's headers through an Objective-C bridging header: each target's existing manager header, which already imports <React/...>. Office is also dropping its export of React Native headers by bare name. These changes let the same sources build both upstream (CocoaPods, React module) and in Office:

  • packages/experimental/Avatar/ios/AvatarViewManager.swift, packages/experimental/NativeDatePicker/ios/DatePickerManager.swift, and DatePickerDelegate.swift: wrap import React in #if canImport(React). The macOS Swift sources already guard it with #if USE_REACT_AS_MODULE, which upstream iOS CocoaPods builds don't define. canImport(React) keeps upstream behavior unchanged.
  • packages/agentic/native-core/macos/components/Callout/paper/: with a bridging header, the generated FRNNativeCore-Swift.h also declares internal Swift classes, including @interface RCTCalloutManager : RCTViewManager. FRNCalloutManager.m imported that header only so its focusWindow/blurWindow bodies could call FRNCalloutView. Against its RCT_EXTERN_MODULE(RCTCalloutManager, RCTViewManager), it fails with duplicate interface definition for class 'RCTCalloutManager'.
    • CalloutManager.swift: implement focusWindow/blurWindow, still dispatching the view lookup to the main queue. The nonisolated(unsafe) local keeps the closure clean under stricter Swift concurrency checking.
    • FRNCalloutManager.m: declare both with RCT_EXTERN_METHOD and drop the Swift header import. JS command names, selectors, and arguments are unchanged. RCTCalloutComponentView.mm (Fabric) still imports the generated header.
  • packages/experimental/AppearanceAdditions/ios/FRNAppearanceAdditions.m, packages/experimental/NativeDatePicker/ios/FRNDatePickerManager.m, and packages/agentic/native-core/macos/components/FocusZone/shared/RCTFocusZone.m: import RCTUIManager.h, RCTBridgeModule.h, and RCTI18nUtil.h as <React/...> rather than by bare name. They were the only FURN sources still including React Native headers by bare name. FocusZone's "RCTi18nUtil.h" also differed in case from React Native's RCTI18nUtil.h, so it only resolved through a case-insensitive header map. The <React/...> form is canonical, is already used by the rest of these files, and works with CocoaPods.
  • packages/agentic/native-core/macos/components/FocusZone/shared/RCTFocusZone.m: also mark the unused index and stop parameters of the indexOfObjectPassingTest: block in nextViewToFocusInHierarchyForAction: as __unused. Office's macOS build treats -Wunused-parameter as an error and carries this change as a local patch. No behavior change. Folded in from fix(native-core): silence unused block parameter warnings in macOS FocusZone #4365.
  • tests/native-ownership.test.mjs: only the Fabric adapter imports the generated header. Paper commands are declared with RCT_EXTERN_METHOD and implemented in Swift.
  • README.md: document that rule for future Paper managers.
  • Patch changesets for native-core, experimental-avatar, experimental-native-date-picker, and experimental-appearance-additions.

Independent of #4363 (the FRNCallout alias). The two merge cleanly in either order, to the same tree. With both applied, the alias's RCT_EXTERN_MODULE(FRNCalloutManager, RCTCalloutManager) also avoids a second duplicate interface in bridging-header builds.

Verification

  • In Office's build, the equivalent Swift and Callout changes on the older FURN snapshot (before the native-core move) build and link all FURN macOS targets in Word, and compile the iOS Avatar and DatePicker targets.
  • Local bridging-header reproduction with React Native macOS 0.81.9 headers (swiftc -import-objc-header with FRNCalloutManager.h, then clang -fsyntax-only FRNCalloutManager.m):
    • main: fails with duplicate interface definition for class 'RCTCalloutManager'.
    • This branch: passes in Swift 5 mode, and with the RegionBasedIsolation, InferSendableFromCaptures, and GlobalConcurrency upcoming features plus -warnings-as-errors. Under -strict-concurrency=complete, the new methods add no diagnostics; capturing self directly would warn at both call sites.
    • Merged with fix(native-core): restore macOS Paper FRNCallout manager alias #4363: passes.
  • Header imports, compiled with clang -fsyntax-only against React Native headers exposed only as <React/...>:
    • iOS (React Native 0.81.6, simulator SDK, FluentUI enums stubbed for the date picker): main fails with 'RCTUIManager.h' file not found and 'RCTBridgeModule.h' file not found. This branch compiles both files.
    • macOS FocusZone (React Native macOS 0.81.9, -Werror=nonportable-include-path): main fails with 'RCTi18nUtil.h' file not found. This branch compiles. Spelling it <React/RCTi18nUtil.h> fails the case check.
    • Checking every quoted include in FURN's iOS and macOS sources against React Native's header names found no other bare React Native includes. All 20 distinct <React/...> includes match the on-disk case.
  • FocusZone unused parameters: compiled the indexOfObjectPassingTest: block from RCTFocusZone.m in a stub function with xcrun clang -fsyntax-only -fobjc-arc -Wall -Wextra -Wunused-parameter -Werror (Xcode 26.4.1). main fails with unused parameter 'index' and unused parameter 'stop'. This branch passes. The same change and changeset passed Check for Changesets in fix(native-core): silence unused block parameter warnings in macOS FocusZone #4365.
  • yarn workspace @fluentui-react-native/native-core lint: passed.
  • yarn workspace @fluentui-react-native/native-core test (build, stories typecheck, node tests, Jest): passed. Ownership tests: 6/6, and 7/7 when merged with fix(native-core): restore macOS Paper FRNCallout manager alias #4363.
  • yarn oxfmt --check on the changed README and test: passed. yarn change:check: passed.
  • swiftc -parse on the three iOS Swift files: passed.
  • Yarn commands were run through the repo's pinned .yarn/releases/yarn-4.18.0.cjs.
  • CI passed all 17 checks on the first commit (af7eae7), including the iOS PR, macOS PR, and macOS Storybook PR native builds.
  • Not run locally: CocoaPods app builds (CocoaPods isn't installed on this machine) or a Paper runtime check of focusWindow/blurWindow.
Before After
Bridging-header builds fail: FRNCalloutManager.m hits duplicate interface definition for class 'RCTCalloutManager', and iOS import React requires a React module The same sources build with a bridging header and with the CocoaPods React module
FRNAppearanceAdditions.m, FRNDatePickerManager.m, and RCTFocusZone.m need hosts to export React Native headers by bare name, case-insensitively for RCTi18nUtil.h All three include React Native headers only as <React/...>, with the exact header case
RCTFocusZone.m fails to compile when -Wunused-parameter is an error (unused index and stop block parameters) Both block parameters are marked __unused, so it compiles cleanly

Pull request checklist

This PR has considered (when applicable):

  • Automated Tests
  • Documentation and examples
  • Keyboard Accessibility
  • Voiceover
  • Internationalization and Right-to-left Layouts

Hosts can compile FURN's Swift sources against React Native through an
Objective-C bridging header instead of a `React` clang module.

- Import `React` in the Avatar and NativeDatePicker iOS Swift sources only
  when the module is available.
- Implement the macOS Paper Callout `focusWindow` and `blurWindow` commands
  in Swift and declare them with `RCT_EXTERN_METHOD`, so FRNCalloutManager.m
  no longer imports the generated FRNNativeCore-Swift.h. With a bridging
  header, that generated header also declares the internal RCTCalloutManager
  class, which duplicates the `RCT_EXTERN_MODULE` interface.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ea215348-486c-45ef-8205-2a81a1dd75a7
@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Bundle size report

Tree-shaken, minified production esbuild bundles with React and React Native runtimes externalized.

Scenario Modules-Mac (Δ) Modules-Win (Δ) Size-Mac (Δ) Size-Win (Δ)
components-button 62 (+0) 63 (+0) 49.94k (+0b) 49.87k (+0b)
components-catalog 159 (+0) 160 (+0) 155.26k (+0b) 155.19k (+0b)
design-theme-state 33 (+0) 34 (+0) 30.11k (+0b) 30.03k (+0b)
design-themed-root 37 (+0) 38 (+0) 32.07k (+0b) 32.00k (+0b)
design-color-lib 6 (+0) 6 (+0) 6.33k (+0b) 6.33k (+0b)
design-theme-state-legacy-source 34 (+0) 35 (+0) 31.77k (+0b) 31.69k (+0b)
design-legacy-theme-from-flex 57 (+0) 58 (+0) 97.72k (+0b) 97.59k (+0b)

The job is advisory: size changes are reported but do not fail the pull request. Bundle or analysis errors still fail.


View bundle-size workflow run

…rces

FRNAppearanceAdditions.m and FRNDatePickerManager.m included RCTUIManager.h
and RCTBridgeModule.h by bare name, which only resolves in hosts that also
expose React Native headers without the React/ prefix. Use the canonical
<React/...> form, which the rest of these files and CocoaPods already use.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ea215348-486c-45ef-8205-2a81a1dd75a7
…ocusZone

The macOS FocusZone implementation included "RCTi18nUtil.h" by bare name and
with different capitalization from React Native's React/Modules/RCTI18nUtil.h,
so it only resolved through a case-insensitive bare-name header map. Use the
canonical <React/RCTI18nUtil.h> form.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ea215348-486c-45ef-8205-2a81a1dd75a7
@Saadnajmi
Saad Najmi (Saadnajmi) marked this pull request as ready for review October 13, 2026 22:47
…cusZone

Mark the unused `index` and `stop` parameters of the
indexOfObjectPassingTest: block in RCTFocusZone.m as `__unused` so the file
compiles cleanly when -Wunused-parameter is treated as an error.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d7aea072-fc8e-4a8d-890a-81311025130d
@Saadnajmi
Saad Najmi (Saadnajmi) merged commit f550cfb into microsoft:main Oct 14, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants