@momo-kits/native-kits 0.167.1-duo.12 → 0.167.1-duo.13
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.
|
@@ -219,16 +219,20 @@ public func isIPhoneDuoDevice(windowSize: CGSize? = nil) -> Bool {
|
|
|
219
219
|
/// System occlusion regions (iOS 27.1+) in window coordinates: the iPhone Duo camera, and the
|
|
220
220
|
/// status bar cluster as it grows for a call or a Live Activity. Empty before iOS 27.1.
|
|
221
221
|
enum ReservedRegions {
|
|
222
|
-
// last report of
|
|
223
|
-
static var current: [CGRect] = []
|
|
222
|
+
// last report of a probe, so `ScreenLayout.current` includes it
|
|
223
|
+
private(set) static var current: [CGRect] = []
|
|
224
|
+
static let didChange = Notification.Name("MoMoUIKitsReservedRegionsDidChange")
|
|
225
|
+
|
|
226
|
+
fileprivate static func publish(_ regions: [CGRect]) {
|
|
227
|
+
guard regions != current else { return }
|
|
228
|
+
current = regions
|
|
229
|
+
NotificationCenter.default.post(name: didChange, object: nil)
|
|
230
|
+
}
|
|
224
231
|
}
|
|
225
232
|
|
|
226
233
|
/// Invisible probe: reading the regions in layoutSubviews makes UIKit run it again when they
|
|
227
234
|
/// change, so nothing polls. Port of RN `@momo-kits/reserved-regions`.
|
|
228
235
|
private final class ReservedRegionsProbe: UIView {
|
|
229
|
-
var onChange: (() -> Void)?
|
|
230
|
-
private var last: [CGRect]?
|
|
231
|
-
|
|
232
236
|
override init(frame: CGRect) {
|
|
233
237
|
super.init(frame: frame)
|
|
234
238
|
isUserInteractionEnabled = false
|
|
@@ -238,12 +242,19 @@ private final class ReservedRegionsProbe: UIView {
|
|
|
238
242
|
|
|
239
243
|
override func layoutSubviews() {
|
|
240
244
|
super.layoutSubviews()
|
|
245
|
+
// read even when not publishing, so UIKit keeps re-running this when they change
|
|
241
246
|
let regions = occlusionRegions()
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
247
|
+
// every screen has a probe but the regions are window-wide: one sliding in on a push or
|
|
248
|
+
// a present, or off the window, would overwrite them with a partial read
|
|
249
|
+
guard spansWindow else { return }
|
|
245
250
|
// layout pass: publish on the next turn instead of mutating SwiftUI state mid-update
|
|
246
|
-
DispatchQueue.main.async {
|
|
251
|
+
DispatchQueue.main.async { ReservedRegions.publish(regions) }
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private var spansWindow: Bool {
|
|
255
|
+
guard let window else { return false }
|
|
256
|
+
let frame = convert(bounds, to: nil)
|
|
257
|
+
return abs(frame.minX) < 1 && abs(frame.maxX - window.bounds.width) < 1 && abs(frame.minY) < 1
|
|
247
258
|
}
|
|
248
259
|
|
|
249
260
|
override func didMoveToWindow() {
|
|
@@ -265,17 +276,9 @@ private final class ReservedRegionsProbe: UIView {
|
|
|
265
276
|
}
|
|
266
277
|
|
|
267
278
|
private struct ReservedRegionsProbeView: UIViewRepresentable {
|
|
268
|
-
|
|
279
|
+
func makeUIView(context: Context) -> ReservedRegionsProbe { ReservedRegionsProbe() }
|
|
269
280
|
|
|
270
|
-
func
|
|
271
|
-
let probe = ReservedRegionsProbe()
|
|
272
|
-
probe.onChange = onChange
|
|
273
|
-
return probe
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
func updateUIView(_ probe: ReservedRegionsProbe, context: Context) {
|
|
277
|
-
probe.onChange = onChange
|
|
278
|
-
}
|
|
281
|
+
func updateUIView(_ probe: ReservedRegionsProbe, context: Context) {}
|
|
279
282
|
}
|
|
280
283
|
|
|
281
284
|
// MARK: - Environment
|
|
@@ -307,7 +310,11 @@ private struct ScreenLayoutReader: ViewModifier {
|
|
|
307
310
|
.onChange(of: proxy.safeAreaInsets) { _ in refresh() }
|
|
308
311
|
}
|
|
309
312
|
)
|
|
310
|
-
.background(ReservedRegionsProbeView
|
|
313
|
+
.background(ReservedRegionsProbeView().ignoresSafeArea())
|
|
314
|
+
// any screen's probe may report, so every reader follows the shared value
|
|
315
|
+
.onReceive(NotificationCenter.default.publisher(for: ReservedRegions.didChange)) { _ in
|
|
316
|
+
refresh()
|
|
317
|
+
}
|
|
311
318
|
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
|
312
319
|
refresh()
|
|
313
320
|
}
|
|
@@ -291,8 +291,9 @@ struct BottomSheetView<Content: View>: View {
|
|
|
291
291
|
}
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
-
/// Compose's `heightIn(max:)
|
|
295
|
-
///
|
|
294
|
+
/// Compose's `heightIn(max:)`. `.frame(maxHeight:)` grows to the cap whenever the proposal is
|
|
295
|
+
/// larger, leaving a gap above the header; this sizes the sheet to its content and only caps it
|
|
296
|
+
/// when taller.
|
|
296
297
|
@available(iOS 16.0, *)
|
|
297
298
|
private struct CapHeight: ViewModifier {
|
|
298
299
|
let maxHeight: CGFloat
|
|
@@ -307,8 +308,11 @@ private struct CapHeightLayout: Layout {
|
|
|
307
308
|
let maxHeight: CGFloat
|
|
308
309
|
|
|
309
310
|
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
|
|
310
|
-
let
|
|
311
|
-
|
|
311
|
+
guard let subview = subviews.first else { return .zero }
|
|
312
|
+
let cap = min(maxHeight, proposal.height ?? .infinity)
|
|
313
|
+
// ideal first: a scroll view would otherwise fill whatever height it is offered
|
|
314
|
+
let ideal = subview.sizeThatFits(ProposedViewSize(width: proposal.width, height: nil))
|
|
315
|
+
return subview.sizeThatFits(ProposedViewSize(width: proposal.width, height: min(ideal.height, cap)))
|
|
312
316
|
}
|
|
313
317
|
|
|
314
318
|
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
|
package/ios/native-kits.podspec
CHANGED