https://github.com/nst/iOS-Runtime-Headers/blob/master/PrivateFrameworks/UIKitCore.framework/_UIPortalView.h - репликация view без копирования и снапшотов
Сталкивались с ситуацией, когда необходимо показать содержимое одной view в другом месте экрана? Например, живая миниатюра контента (PiP), отражение, размытый backdrop или копия view для hero-transition.
Вариантов обычно два:
1. Дублировать объект - создать полную копию с вложенной иерархией, синхронизировать состояние, управлять жизненным циклом.
2. Рендерить snapshot каждый кадр через drawHierarchy или snapshotView.
Оба варианта жизнеспособны, но первый даёт усложнение, а второй нагрузку на CPU.
У Apple есть приватное API _UIPortalView, которое решает эту задачу на уровне системного композитора.
В iOS 26 _UIPortalView стал частью
https://x.com/SebJVidal/status/1932184648567947553 - приватного компонента, который реализует Liquid Glass.
Как это работает?
_UIPortalView это UIView, чей backing layer https://aditya.vaidyam.me/blog/2018/02/18/ CAPortalLayer хранит ссылку (sourceLayerRenderId) на source layer в render tree и говорит render server композитить содержимое source в позиции портала. _UIPortalView добавляет UIKit-уровень: sourceView вместо sourceLayerRenderId, matchesPosition, matchesAlpha и другие свойства.
Как получить доступ?
Класс приватный - доступ через runtime:
Class cls = NSClassFromString(@"_UIPortalView");
UIView *portal = [[cls alloc] init];
Доступные свойства
sourceView — vew-источник, контент которого отображает портал
matchesPosition — привязка к позиции source в координатах окна
matchesTransform — наследует transform от source
matchesAlpha — наследует alpha от source
hidesSourceView — скрывает source, но порталы его отображают
allowsHitTesting — разрешает hit testing на портале
allowsBackdropGroups — поддержка CABackdropLayer групп (blur/vibrancy)
forwardsClientHitTestingToSourceView — пробрасывает тачи с портала на source
Как работает matchesPosition?
• matchesPosition = true - портал показывает фрагмент source, совпадающий по позиции в координатах окна. Как отверстие в стене, за которой спрятан source (именно так работает эффект https://github.com/TelegramMessenger/Telegram-iOS/blob/master/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift у баблов сообщений в Telegram - каждый бабл показывает свой кусок одного общего градиента)
• matchesPosition = false - портал показывает source от (0, 0). Управлять видимой областью можно через bounds.origin портала (сдвиг viewport) или через frame.origin портала внутри clipped-контейнера.
Перформанс
Поскольку рендеринг происходит в render server, создание порталов практически не увеличивает нагрузку на CPU.
Но есть нюансы при большом количестве порталов - GPU накладывает свои ограничения, но об этом расскажу отдельно.
Когда попробовал порталировать AVPlayerLayer, столкнулся с тем, что в портале отображался застывший кадр - портал обновлялся только при изменении layer-свойства в source subtree.
Причина: AVPlayerLayer.contents обновляется через IOSurface swap. CAPortalLayer подписан на изменения layer tree (transform, opacity, sublayers), а не на IOSurface content changes внутри source subtree.
Workaround - infinite анимация на source sublayer, которая поднимает dirty flag каждый кадр.
Спасибо
https://t.me/eleev за пояснение.
В следующих статьях расскажу подробней про ограничения, производительность и хитрые способы применения.