Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 49 additions & 8 deletions ios/Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,60 @@ import UIKit

/**
Helper used to render UIView inside of SwiftUI.
Uses UIViewControllerRepresentable to re-inject safe area insets
that SwiftUI's .ignoresSafeArea() strips from child UIKit views.
*/
struct RepresentableView: UIViewRepresentable {
struct RepresentableView: UIViewControllerRepresentable {
var view: UIView

// Adding a wrapper UIView to avoid SwiftUI directly managing React Native views.
// This fixes issues with incorrect layout rendering.
func makeUIView(context: Context) -> UIView {
let wrapper = UIView()
wrapper.addSubview(view)
return wrapper
func makeUIViewController(context: Context) -> PageChildViewController {
let viewController = PageChildViewController()
viewController.wrappedView = view
return viewController
}

func updateUIView(_ uiView: UIView, context: Context) {}
func updateUIViewController(_ uiViewController: PageChildViewController, context: Context) {}
}

class PageChildViewController: UIViewController {
var wrappedView: UIView?

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .clear
if let wrappedView {
view.addSubview(wrappedView)
}
}

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
propagateHostingSafeArea()
}

/// Traverses up to the _UIHostingView and re-applies its bottom safe area
/// as additionalSafeAreaInsets on this child VC, since .ignoresSafeArea()
/// causes UIKit to report safeAreaInsets = .zero for embedded views.
private func propagateHostingSafeArea() {
var current: UIView? = view.superview
while let v = current {
if String(describing: type(of: v)).contains("_UIHostingView") {
let hosting = v.safeAreaInsets
let delta = UIEdgeInsets(
top: hosting.top, left: hosting.left,
bottom: hosting.bottom, right: hosting.right
)
if abs(additionalSafeAreaInsets.top - delta.top) > 0.5
|| abs(additionalSafeAreaInsets.left - delta.left) > 0.5
|| abs(additionalSafeAreaInsets.bottom - delta.bottom) > 0.5
|| abs(additionalSafeAreaInsets.right - delta.right) > 0.5 {
additionalSafeAreaInsets = delta
}
return
}
current = v.superview
}
}
}

extension Collection {
Expand Down
1 change: 1 addition & 0 deletions ios/PagerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ struct PagerView: View {
.id(props.children.count)
.background(.clear)
.tabViewStyle(.page(indexDisplayMode: .never))
.ignoresSafeArea()
.environment(\.layoutDirection, props.layoutDirection.converted)
.introspect(.tabView(style: .page), on: .iOS(.v14...)) { collectionView in
self.collectionView = collectionView
Expand Down
12 changes: 1 addition & 11 deletions ios/PagerViewProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,22 +113,12 @@ import UIKit
return
}

// Only create the hosting controller once it can actually be attached to the
// React view-controller hierarchy. `reactViewController()` can be nil on the
// first `didMoveToWindow` pass (e.g. when the pager is mounted inside a screen
// that is still being attached). Previously `self.hostingController` was
// assigned before this check, so when `reactViewController()` was nil the
// SwiftUI host was never added/pinned (its view frame stayed `.zero`, leaving
// the page blank) while the early-return above prevented any later retry.
// Deferring keeps `hostingController` nil so a subsequent window/layout pass
// can attach it once the view controller is available.
guard let parentViewController = reactViewController() else {
return
}

let hostingController = UIHostingController(
rootView: PagerView(props: props, delegate: delegate),
ignoreSafeArea: true
rootView: PagerView(props: props, delegate: delegate)
)
self.hostingController = hostingController

Expand Down