| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import ObjectiveC |
| import UIKit |
| |
| /// Extends UIView to notify via KVO when it moved to a new window. |
| extension UIView { |
| /// MARK: Public |
| |
| /// Whether all instances of UIView will notify key-value observers for their `window` property. |
| /// |
| /// Default is `false`. |
| @objc public static var cr_supportsWindowObserving: Bool { |
| get { |
| return swizzled |
| } |
| set { |
| if swizzled != newValue { |
| // Swap implementations. |
| guard |
| let willMove1 = class_getInstanceMethod(UIView.self, #selector(willMove(toWindow:))), |
| let willMove2 = class_getInstanceMethod(UIView.self, #selector(cr_willMove(toWindow:))), |
| let didMove1 = class_getInstanceMethod(UIView.self, #selector(didMoveToWindow)), |
| let didMove2 = class_getInstanceMethod(UIView.self, #selector(cr_didMoveToWindow)), |
| // UITextField's original implementations don't call the super class implementations, |
| // which breaks the interposition at the UIView level. Handle UITextField explicitly. |
| let textFieldWillMove1 = class_getInstanceMethod( |
| UITextField.self, #selector(willMove(toWindow:))), |
| let textFieldWillMove2 = class_getInstanceMethod( |
| UITextField.self, #selector(cr_willMove(toWindow:))), |
| let textFieldDidMove1 = class_getInstanceMethod( |
| UITextField.self, #selector(didMoveToWindow)), |
| let textFieldDidMove2 = class_getInstanceMethod( |
| UITextField.self, #selector(cr_didMoveToWindow)) |
| else { |
| // If it failed here, don't change the `swizzled` state. |
| return |
| } |
| method_exchangeImplementations(willMove1, willMove2) |
| method_exchangeImplementations(didMove1, didMove2) |
| method_exchangeImplementations(textFieldWillMove1, textFieldWillMove2) |
| method_exchangeImplementations(textFieldDidMove1, textFieldDidMove2) |
| // Change the `swizzled` state. |
| swizzled = newValue |
| } |
| } |
| } |
| |
| /// Signals that the `window` key is supported via Manual Change Notification. |
| /// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueObserving/Articles/KVOCompliance.html#//apple_ref/doc/uid/20002178-SW3 |
| @objc class func automaticallyNotifiesObserversOfWindow() -> Bool { |
| return false |
| } |
| |
| /// MARK: Private |
| |
| /// Whether the original and alternative implementations have been swapped. |
| private static var swizzled = false |
| } |
| |
| extension UIView { |
| /// Adds a call to the KVO `willChangeValue(forKey:)` method. |
| @objc fileprivate func cr_willMove(toWindow newWindow: UIWindow?) { |
| cr_willMove(toWindow: newWindow) |
| willChangeValue(forKey: "window") |
| } |
| |
| /// Adds a call to the KVO `didChangeValue(forKey:)` method. |
| @objc fileprivate func cr_didMoveToWindow() { |
| cr_didMoveToWindow() |
| didChangeValue(forKey: "window") |
| } |
| } |
| |
| extension UITextField { |
| /// Adds a call to the KVO `willChangeValue(forKey:)` method. |
| /// |
| /// This version is necessary, as UITextField's original implementation doesn't call its |
| /// superclass implementation, hence escapes the interposition at the UIView level. |
| @objc override fileprivate func cr_willMove(toWindow newWindow: UIWindow?) { |
| cr_willMove(toWindow: newWindow) |
| willChangeValue(forKey: "window") |
| } |
| |
| /// Adds a call to the KVO `didChangeValue(forKey:)` method. |
| /// |
| /// This version is necessary, as UITextField's original implementation doesn't call its |
| /// superclass implementation, hence escapes the interposition at the UIView level. |
| @objc override fileprivate func cr_didMoveToWindow() { |
| cr_didMoveToWindow() |
| didChangeValue(forKey: "window") |
| } |
| } |