expo-modules-core 55.0.13 → 55.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -10,10 +10,22 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 55.0.15 — 2026-03-11
14
+
15
+ ### 💡 Others
16
+
17
+ - [iOS] Add `ViewWrapper` protocol and `AnyContentViewProvider` for type-erased access to wrapped SwiftUI views. ([#43669](https://github.com/expo/expo/pull/43669) by [@nishan](https://github.com/intergalacticspacehighway))
18
+ - [iOS] Make RNHostView SwiftUI view ([#43570](https://github.com/expo/expo/pull/43570) by [@nishan](https://github.com/intergalacticspacehighway))
19
+
20
+ ## 55.0.14 — 2026-03-05
21
+
22
+ _This version does not introduce any user-facing changes._
23
+
13
24
  ## 55.0.13 — 2026-02-26
14
25
 
15
26
  ### 🐛 Bug fixes
16
27
 
28
+ - [Android] Fixed Compose view clipping that caused Material ripple effects to be cut off (e.g., Switch thumb ripple). ([#43656](https://github.com/expo/expo/pull/43656) by [@vonovak](https://github.com/vonovak))
17
29
  - [iOS] Fix memory leak due to retain cycle in SwiftUI views. ([#43468](https://github.com/expo/expo/pull/43468) by [@nishan](https://github.com/intergalacticspacehighway))
18
30
 
19
31
  ## 55.0.12 — 2026-02-25
@@ -29,7 +29,7 @@ if (shouldIncludeCompose) {
29
29
  }
30
30
 
31
31
  group = 'host.exp.exponent'
32
- version = '55.0.13'
32
+ version = '55.0.15'
33
33
 
34
34
  def isExpoModulesCoreTests = {
35
35
  Gradle gradle = getGradle()
@@ -96,7 +96,7 @@ android {
96
96
  defaultConfig {
97
97
  consumerProguardFiles 'proguard-rules.pro'
98
98
  versionCode 1
99
- versionName "55.0.13"
99
+ versionName "55.0.15"
100
100
  buildConfigField "String", "EXPO_MODULES_CORE_VERSION", "\"${versionName}\""
101
101
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true"
102
102
 
@@ -149,6 +149,8 @@ abstract class ExpoComposeView<T : ComposeProps>(
149
149
 
150
150
  init {
151
151
  if (withHostingView) {
152
+ clipChildren = false
153
+ clipToPadding = false
152
154
  addComposeView()
153
155
  } else {
154
156
  this.visibility = GONE
@@ -34,9 +34,19 @@ internal struct DynamicSwiftUIViewType<ViewType: ExpoSwiftUIView>: AnyDynamicTyp
34
34
  guard Thread.isMainThread else {
35
35
  throw NonMainThreadException()
36
36
  }
37
+ // Direct match, the virtual view's content type matches exactly.
38
+ // e.g. View(SlotView.self)
37
39
  if let view = appContext.findView(withTag: viewTag, ofType: ExpoSwiftUI.SwiftUIVirtualView<ViewType.Props, ViewType>.self) {
38
40
  return view.contentView
39
41
  }
42
+ // For wrapper types
43
+ // e.g. ExpoUIView(SecureFieldView.self)
44
+ if let provider = appContext.findView(withTag: viewTag, ofType: ExpoSwiftUI.ViewWrapper.self),
45
+ let innerView = provider.getWrappedView() as? ViewType {
46
+ return innerView
47
+ }
48
+ // For views using WithHostingView protocol.
49
+ // e.g. View(HostView.self) where HostView conforms to WithHostingView
40
50
  guard let view = appContext.findView(withTag: viewTag, ofType: AnyExpoSwiftUIHostingView.self) else {
41
51
  throw Exceptions.SwiftUIViewNotFound((tag: viewTag, type: innerType.self))
42
52
  }
@@ -12,6 +12,9 @@ extension ExpoSwiftUI {
12
12
  var childView: ChildViewType { get }
13
13
 
14
14
  var id: ObjectIdentifier { get }
15
+
16
+ // The underlying UIKit view, if this child wraps one. Returns `nil` for pure SwiftUI child.
17
+ var uiView: UIView? { get }
15
18
  }
16
19
  }
17
20
 
@@ -23,4 +26,8 @@ public extension ExpoSwiftUI.AnyChild where Self == ChildViewType {
23
26
  var id: ObjectIdentifier {
24
27
  fatalError("Expected override by derived SwiftUIVirtualView or UIViewHost")
25
28
  }
29
+
30
+ var uiView: UIView? {
31
+ nil
32
+ }
26
33
  }
@@ -18,4 +18,12 @@ extension ExpoSwiftUI {
18
18
  public protocol FocusableView {
19
19
  func forceResignFirstResponder()
20
20
  }
21
+
22
+ /**
23
+ Protocol for wrapper views (e.g., UIBaseView) that wrap an inner view.
24
+ Used by DynamicSwiftUIViewType to resolve the underlying view through wrapper layers.
25
+ */
26
+ public protocol ViewWrapper {
27
+ func getWrappedView() -> Any
28
+ }
21
29
  }
@@ -70,7 +70,7 @@ extension ExpoSwiftUI {
70
70
  */
71
71
  init(viewType: ContentView.Type, props: Props, appContext: AppContext) {
72
72
  self.contentView = ContentView(props: props)
73
- let rootView = AnyView(contentView.environmentObject(shadowNodeProxy))
73
+ let rootView = AnyView(contentView)
74
74
  self.props = props
75
75
  let controller = UIHostingController(rootView: rootView)
76
76
 
@@ -89,6 +89,8 @@ extension ExpoSwiftUI {
89
89
  self?.setStyleSize(width, height: height)
90
90
  }
91
91
 
92
+ props.shadowNodeProxy = shadowNodeProxy
93
+
92
94
  shadowNodeProxy.objectWillChange.send()
93
95
 
94
96
  #if os(iOS) || os(tvOS)
@@ -88,6 +88,15 @@ extension ExpoSwiftUI {
88
88
  self.init(viewType, elements: [nameDefinitionElement])
89
89
  }
90
90
 
91
+ /**
92
+ Used by wrapper patterns (e.g., `ExpoUIView`)
93
+ */
94
+ public convenience init(_ viewType: ViewType.Type, name: String, elements: [AnyViewDefinitionElement]) {
95
+ var allElements: [AnyViewDefinitionElement] = [ViewNameDefinition(name: name)]
96
+ allElements.append(contentsOf: elements)
97
+ self.init(viewType, elements: allElements)
98
+ }
99
+
91
100
  public override func createView(appContext: AppContext) -> AppleView? {
92
101
  // It's assumed that this function is called only from the main thread.
93
102
  // In the ideal scenario it would be marked as `@MainActor`, but then `ViewModuleWrapper`
@@ -45,62 +45,23 @@ extension ExpoSwiftUI {
45
45
 
46
46
  class Coordinator {
47
47
  var originalAutoresizingMask: UIView.AutoresizingMask = []
48
+ init() {}
48
49
  }
49
50
 
50
51
  // MARK: - AnyChild implementations
51
52
 
52
53
  var childView: some SwiftUI.View {
53
- ViewSizeWrapper(viewHost: self)
54
+ self
54
55
  }
55
56
 
56
57
  var id: ObjectIdentifier {
57
58
  ObjectIdentifier(view)
58
59
  }
59
- }
60
-
61
- public protocol RNHostViewProtocol {
62
- var matchContents: Bool { get set }
63
- }
64
- }
65
-
66
- // ViewSizeWrapper attaches an observer to the view's bounds and updates the frame modifier of the view host.
67
- // This allows us to respect RN layout styling in SwiftUI realm
68
- // .e.g. <View style={{ width: 100, height: 100 }} />
69
- private struct ViewSizeWrapper: View {
70
- let viewHost: ExpoSwiftUI.UIViewHost
71
- @StateObject private var viewSizeModel: ViewSizeModel
72
-
73
- init(viewHost: ExpoSwiftUI.UIViewHost) {
74
- self.viewHost = viewHost
75
- _viewSizeModel = StateObject(wrappedValue: ViewSizeModel(viewHost: viewHost))
76
- }
77
60
 
78
- var body: some View {
79
- if let rnHostView = viewHost.view as? ExpoSwiftUI.RNHostViewProtocol, rnHostView.matchContents {
80
- viewHost
81
- .frame(width: viewSizeModel.viewFrame.width, height: viewSizeModel.viewFrame.height)
82
- } else {
83
- viewHost
84
- }
85
- }
86
- }
87
-
88
- @MainActor
89
- private class ViewSizeModel: ObservableObject {
90
- @Published var viewFrame: CGSize
91
- private var observer: NSKeyValueObservation?
92
-
93
- init(viewHost: ExpoSwiftUI.UIViewHost) {
94
- let view = viewHost.view
95
- self.viewFrame = view.bounds.size
96
- observer = view.observe(\.bounds) { [weak self] view, _ in
97
- MainActor.assumeIsolated {
98
- self?.viewFrame = view.bounds.size
99
- }
61
+ var uiView: UIView? {
62
+ view
100
63
  }
101
64
  }
102
65
 
103
- deinit {
104
- observer?.invalidate()
105
- }
106
66
  }
67
+
@@ -34,6 +34,11 @@ extension ExpoSwiftUI {
34
34
  */
35
35
  public var children: [any AnyChild]?
36
36
 
37
+ /**
38
+ Proxy for controlling the shadow node (Yoga layout) of the view.
39
+ */
40
+ public internal(set) var shadowNodeProxy: ShadowNodeProxy = ShadowNodeProxy()
41
+
37
42
  public internal(set) weak var appContext: AppContext?
38
43
 
39
44
  /**
@@ -8,6 +8,8 @@ extension ExpoSwiftUI {
8
8
  This class is the Swift component of SwiftUIVirtualView, as referenced in ExpoFabricView.swift.
9
9
  */
10
10
  final class SwiftUIVirtualView<Props: ViewProps, ContentView: View<Props>>: SwiftUIVirtualViewObjC, ExpoSwiftUIView {
11
+ var uiView: UIView?
12
+
11
13
  /**
12
14
  A weak reference to the app context associated with this view.
13
15
  The app context is injected into the class after the context is initialized.
@@ -39,6 +41,14 @@ extension ExpoSwiftUI {
39
41
  self.viewDefinition = viewDefinition
40
42
  self.appContext = appContext
41
43
  super.init()
44
+
45
+ props.shadowNodeProxy.setViewSize = { [weak self] size in
46
+ self?.setViewSize(size)
47
+ }
48
+ props.shadowNodeProxy.setStyleSize = { [weak self] width, height in
49
+ self?.setStyleSize(width, height: height)
50
+ }
51
+
42
52
  installEventDispatchers()
43
53
  }
44
54
 
@@ -180,3 +190,14 @@ extension ExpoSwiftUI {
180
190
  }
181
191
  }
182
192
  }
193
+
194
+ // MARK: - ViewWrapper
195
+
196
+ extension ExpoSwiftUI.SwiftUIVirtualView: @MainActor ExpoSwiftUI.ViewWrapper {
197
+ func getWrappedView() -> Any {
198
+ if let wrapper = contentView as? ExpoSwiftUI.ViewWrapper {
199
+ return wrapper.getWrappedView()
200
+ }
201
+ return contentView
202
+ }
203
+ }
@@ -354,7 +354,11 @@ static std::unordered_map<std::string, expo::ExpoViewComponentDescriptor::Flavor
354
354
  - (void)setShadowNodeSize:(float)width height:(float)height
355
355
  {
356
356
  if (_state) {
357
+ #if REACT_NATIVE_TARGET_VERSION >= 82
358
+ _state->updateState(expo::ExpoViewState(width, height), EventQueue::UpdateMode::unstable_Immediate);
359
+ #else
357
360
  _state->updateState(expo::ExpoViewState(width, height));
361
+ #endif
358
362
  }
359
363
  }
360
364
 
@@ -363,7 +367,11 @@ static std::unordered_map<std::string, expo::ExpoViewComponentDescriptor::Flavor
363
367
  if (_state) {
364
368
  float widthValue = width ? [width floatValue] : std::numeric_limits<float>::quiet_NaN();
365
369
  float heightValue = height ? [height floatValue] : std::numeric_limits<float>::quiet_NaN();
370
+ #if REACT_NATIVE_TARGET_VERSION >= 82
371
+ _state->updateState(expo::ExpoViewState::withStyleDimensions(widthValue, heightValue), EventQueue::UpdateMode::unstable_Immediate);
372
+ #else
366
373
  _state->updateState(expo::ExpoViewState::withStyleDimensions(widthValue, heightValue));
374
+ #endif
367
375
  }
368
376
  }
369
377
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-core",
3
- "version": "55.0.13",
3
+ "version": "55.0.15",
4
4
  "description": "The core of Expo Modules architecture",
5
5
  "main": "src/index.ts",
6
6
  "types": "build/index.d.ts",
@@ -66,5 +66,5 @@
66
66
  "@testing-library/react-native": "^13.3.0",
67
67
  "expo-module-scripts": "^55.0.2"
68
68
  },
69
- "gitHead": "413b0450434d3e456eb391eca792ee9ac1e1efec"
69
+ "gitHead": "bcdd2c239f8a92cdf5140e35cde768352630acd6"
70
70
  }