Минимизированный call-бар: UIKit additionalSafeAreaInsets для сдвига навбара, Telegram-style градиент и UI-рефакторинг
This commit is contained in:
69
Rosetta/DesignSystem/Components/CallBarSafeAreaBridge.swift
Normal file
69
Rosetta/DesignSystem/Components/CallBarSafeAreaBridge.swift
Normal file
@@ -0,0 +1,69 @@
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Call Bar Safe Area Bridge
|
||||
|
||||
/// UIKit bridge that pushes NavigationStack's nav bar down by setting
|
||||
/// `additionalSafeAreaInsets.top` on every UINavigationController in the window.
|
||||
///
|
||||
/// `UIViewController.navigationController` walks UP the parent chain — but the
|
||||
/// bridge VC is a SIBLING of NavigationStack's UINavigationController, not a child.
|
||||
/// So we walk DOWN from the window's root VC to find all navigation controllers.
|
||||
struct CallBarSafeAreaBridge: UIViewRepresentable {
|
||||
let topInset: CGFloat
|
||||
|
||||
func makeUIView(context: Context) -> CallBarInsetView {
|
||||
let view = CallBarInsetView()
|
||||
view.isHidden = true
|
||||
view.isUserInteractionEnabled = false
|
||||
return view
|
||||
}
|
||||
|
||||
func updateUIView(_ view: CallBarInsetView, context: Context) {
|
||||
view.topInset = topInset
|
||||
view.applyInset()
|
||||
}
|
||||
}
|
||||
|
||||
/// Invisible UIView that finds all UINavigationControllers in the window
|
||||
/// and sets `additionalSafeAreaInsets.top` on each.
|
||||
final class CallBarInsetView: UIView {
|
||||
var topInset: CGFloat = 0
|
||||
private var lastAppliedInset: CGFloat = -1
|
||||
|
||||
override func didMoveToWindow() {
|
||||
super.didMoveToWindow()
|
||||
lastAppliedInset = -1 // Force re-apply
|
||||
applyInset()
|
||||
}
|
||||
|
||||
func applyInset() {
|
||||
guard topInset != lastAppliedInset else { return }
|
||||
guard let window, let rootVC = window.rootViewController else { return }
|
||||
Self.apply(inset: topInset, in: rootVC)
|
||||
lastAppliedInset = topInset
|
||||
}
|
||||
|
||||
/// Recursively walk child view controllers (NOT presentedViewController)
|
||||
/// and set additionalSafeAreaInsets.top on every UINavigationController.
|
||||
private static func apply(inset: CGFloat, in vc: UIViewController) {
|
||||
if let nav = vc as? UINavigationController,
|
||||
nav.additionalSafeAreaInsets.top != inset {
|
||||
nav.additionalSafeAreaInsets.top = inset
|
||||
}
|
||||
for child in vc.children {
|
||||
apply(inset: inset, in: child)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
/// Push NavigationStack's nav bar down by `inset` points.
|
||||
/// Uses UIKit `additionalSafeAreaInsets` — the only reliable mechanism.
|
||||
func callBarSafeAreaInset(_ inset: CGFloat) -> some View {
|
||||
background(
|
||||
CallBarSafeAreaBridge(topInset: inset)
|
||||
.frame(width: 0, height: 0)
|
||||
.allowsHitTesting(false)
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user