Важно: Актуально для айфонов с челкой
Люди запоминают вещи лучше, когда они связаны с
Получив задачу на добавление плашки приложения как в Telegram, сразу возникают два вопроса
Flow работы:
Момент скрина или записи видео должен появляться на месте знаменитой челки айфона
Решение, которое сразу мне пришло в голову заключается в показывании, до момента создания скриншота, watermark на месте челки айфона, который положен вверху иерархии вьюх, а после создании скрыть его. Этот был ровно наполовину верный путь и дальше я представлю свои рассуждения в рамках решение данной продуктовой задачи
Чтобы поместить водяной знак поверх всех (UIView, UIViewController и тд) логично, что необходимо работать с UIWindow (UIWindow - Фон для пользовательского интерфейса вашего приложения и объект, который отправляет события в ваши представления. Так говорит документация Apple). Таким образом нам необходимо создать второй UIWindow, в котором рутовым контроллером нам необходимо поставить Наш Вотермарк контроллер.
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let watermarkWindow = UIWindow(frame: UIScreen.main.bounds)
watermarkWindow.rootViewController = WatermarkLogoViewController()
watermarkWindow.makeKeyAndVisible()
...
}
Первый вопрос закрыт, перейдем к второму
Чтобы поймать момент скрина или записи экрана сразу приходит на ум UIApplication.userDidTakeScreenshotNotification все было бы хорошо, но в таком случае мы узнаем, что произошел скриншот непосредственно после его создания, что нас не устраивает, потому что мы же хотим показывать вотермарк до момента создания скриншота и позже его скрывать. И я на какое то время вошел в ступор, но потом придумал альтернативное решение, попрежнему с позиционированием водяного знака вверху иерархии.
Все так же создаем UIWindow и делаем рутового контроллеру view.background = .clear, но не опираемся на показывании и скрытии водяного до момента скрина и после, теперь мы будем его показывать почти всегда(кроме момента когда мы выходим из приложения в мультизадачный режим). И как бы все хорошо, теперь наш водяной знак всегда виден как на скрине, так и в режиме записи экрана, но возникает новая проблема(Созданный нами UIWindow(WatermarkWindow) не будет пропускать тачи в главный UIWindow). Поэтому нам нужно переопределить hitest у UIWindow и UIView и не забыть про тот случай когда мы выходим в мультизадачность(в этом нам поможет NotificationCenter.default.addObserver(self, selector: selector, name: notification, object: object))
final class HitThroughWindow: UIWindow {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
return view == self ? nil : view
}
}
final class HitThroughView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return nil
}
}
final class WatermarkLogoViewController: UIViewControlelr {
private var watermarkView: WatermarkView?
//MARK: ViewController
override func loadView() {
super.loadView()
self.view = HitThroughView()
self.watermarkView = WatermarkView()
view.backgroundColor = .clear
view.addSubview(clipsView ?? UIView())
subscribe(to: UIApplication.willResignActiveNotification, with: #selector(handleApplicationWillResignActiveNotification(_:)))
subscribe(to: UIApplication.didBecomeActiveNotification, with: #selector(handleApplicationDidBecomeActiveNotification(_:)))
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
watermarkView?.frame = .init(x: 0, y: 0, width: width, height: height)
watermarkView?.center = .init(x: view.frame.midX, y: 0)
}
//MARK: Actions
@objc
private func handleApplicationWillResignActiveNotification(_: Notification) {
watermarkView?.isHidden = true
}
@objc
private func handleApplicationDidBecomeActiveNotification(_: Notification) {
watermarkView?.isHidden = false
}
}
extension WatermarkLogoViewController {
final func subscribe(to notification: NSNotification.Name, with selector: Selector, and object: Any? = nil) {
NotificationCenter.default.addObserver(self, selector: selector, name: notification, object: object)
}
final func unsubscribe(to notification: NSNotification.Name, and object: Any? = nil) {
NotificationCenter.default.removeObserver(self, name: notification, object: object)
}
}
Либо, можно просто выставить zPosition большой у вашей NotchView и все будет намного проще)