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
63 changes: 54 additions & 9 deletions ios/Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,65 @@ 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()
propagateSafeArea()
}

override func viewSafeAreaInsetsDidChange() {
super.viewSafeAreaInsetsDidChange()
propagateSafeArea()
}

/// Re-applies safe area insets from a stable UIKit source, since SwiftUI's
/// .ignoresSafeArea() causes embedded UIKit views to report .zero.
private func propagateSafeArea() {
let insets = nearestNonZeroSafeAreaInsets() ?? view.window?.safeAreaInsets ?? .zero
if abs(additionalSafeAreaInsets.top - insets.top) > 0.5
|| abs(additionalSafeAreaInsets.left - insets.left) > 0.5
|| abs(additionalSafeAreaInsets.bottom - insets.bottom) > 0.5
|| abs(additionalSafeAreaInsets.right - insets.right) > 0.5 {
additionalSafeAreaInsets = insets
}
}

private func nearestNonZeroSafeAreaInsets() -> UIEdgeInsets? {
var current = view.superview
while let candidate = current {
let insets = candidate.safeAreaInsets
if insets.top > 0 || insets.left > 0 || insets.bottom > 0 || insets.right > 0 {
return insets
}
current = candidate.superview
}
return nil
}
}

extension Collection {
Expand Down Expand Up @@ -71,4 +117,3 @@ extension UIHostingController {
}
}
}

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
Loading