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 +12 -0
- package/android/build.gradle +2 -2
- package/android/src/compose/expo/modules/kotlin/views/ExpoComposeView.kt +2 -0
- package/ios/Core/DynamicTypes/DynamicSwiftUIViewType.swift +10 -0
- package/ios/Core/Views/SwiftUI/AnyChild.swift +7 -0
- package/ios/Core/Views/SwiftUI/ExpoSwiftUI.swift +8 -0
- package/ios/Core/Views/SwiftUI/SwiftUIHostingView.swift +3 -1
- package/ios/Core/Views/SwiftUI/SwiftUIViewDefinition.swift +9 -0
- package/ios/Core/Views/SwiftUI/SwiftUIViewHost.swift +5 -44
- package/ios/Core/Views/SwiftUI/SwiftUIViewProps.swift +5 -0
- package/ios/Core/Views/SwiftUI/SwiftUIVirtualView.swift +21 -0
- package/ios/Core/Views/SwiftUI/SwiftUIVirtualViewObjC.mm +8 -0
- package/package.json +2 -2
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
|
package/android/build.gradle
CHANGED
|
@@ -29,7 +29,7 @@ if (shouldIncludeCompose) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
group = 'host.exp.exponent'
|
|
32
|
-
version = '55.0.
|
|
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.
|
|
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
|
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
|
|
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.
|
|
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": "
|
|
69
|
+
"gitHead": "bcdd2c239f8a92cdf5140e35cde768352630acd6"
|
|
70
70
|
}
|