import ExpoModulesCore import UIKit public final class PaseoWindowLayoutModule: Module { public func definition() -> ModuleDefinition { Name("PaseoWindowLayout") View(WindowLayoutView.self) { Events("onGeometryChange") } } } final class WindowLayoutView: ExpoView { let onGeometryChange = EventDispatcher() private var updateScheduled = false private var previousPayload: [String: Any]? private var divisions: [CGRect] = [] private var barGuide: UILayoutGuide? private var barEdge = "none" private var measuredBounds = CGRect.zero required init(appContext: AppContext? = nil) { super.init(appContext: appContext) isUserInteractionEnabled = false #if compiler(>=6.4) if #available(iOS 27.1, *) { registerForTraitChanges(UITraitCollection.systemTraitsAffectingVerticalBarEdge) { (view: WindowLayoutView, _) in view.setNeedsUpdateConstraints() view.scheduleUpdate() } } #endif } override func didMoveToWindow() { super.didMoveToWindow() previousPayload = nil setNeedsUpdateConstraints() scheduleUpdate() } override func safeAreaInsetsDidChange() { super.safeAreaInsetsDidChange() setNeedsUpdateConstraints() scheduleUpdate() } override func layoutSubviews() { super.layoutSubviews() if bounds != measuredBounds { measuredBounds = bounds setNeedsUpdateConstraints() } scheduleUpdate() } override func updateConstraints() { #if compiler(>=6.4) if #available(iOS 27.1, *) { // UIKit tracks these reads and invalidates constraints when regions change. divisions = reservedRegions(kind: .division).compactMap { $0.isActive ? $0.frame : nil } let edge: String switch traitCollection.verticalBarEdge { case .leading: edge = "leading" case .trailing: edge = "trailing" default: edge = "none" } if edge != barEdge { barEdge = edge barGuide = edge == "none" ? nil : layoutGuide( for: .bar(onEdge: edge == "trailing" ? .right : .left, extent: 44) ) } } #endif super.updateConstraints() scheduleUpdate() } private func scheduleUpdate() { guard !updateScheduled else { return } updateScheduled = true DispatchQueue.main.async { [weak self] in guard let self else { return } self.updateScheduled = false self.publish() } } private func publish() { guard window != nil, bounds.width > 0, bounds.height > 0 else { return } let bar = barGuide?.layoutFrame var barPayload: Any = NSNull() if let bar, bar.width > 0, bar.height > 0 { barPayload = rect(bar) } let payload: [String: Any] = [ "bounds": rect(bounds), "verticalBarEdge": barEdge, "verticalBar": barPayload, "divisions": divisions.map(rect) ] guard !NSDictionary(dictionary: payload).isEqual(to: previousPayload ?? [:]) else { return } previousPayload = payload onGeometryChange(payload) } private func rect(_ frame: CGRect) -> [String: CGFloat] { ["x": frame.minX, "y": frame.minY, "width": frame.width, "height": frame.height] } }