I noticed UITextFields don't always deinit when closing its VC (or using removeFromSuperview()), but only if it has become first responder at least once.
Actually, if you have a VC with multiple UITextFields, let one become first responder, then close the VC, ALL UITextFields won't deinit! Then if you activate another UITextField in another VC they will deinit after all. I believe it happens more often when textContentType isn't nil.
To check yourself just subclass UITextField and check the deinit calls in the console:
class TestTextField: UITextField {
deinit {
print("deinitted textfield \(self.placeholder ?? "") \(self.text ?? "")")
}
}
Post
Replies
Boosts
Views
Activity
iOS 17 still has the same issue but at least now there is a way to avoid it:
The leak will clear once you set isSelectable to false. So I'd advice to do this before removeFromSuperview() or in VC's deinit.
myTextView.isSelectable = false
myTextView.removeFromSuperview()
//in ViewController
deinit {
myTextView.isSelectable = false
}
Or even better is to just subclass UITextView and override removeFromSuperview():
//in UITextView subclass
override func removeFromSuperview() {
let wasSelectable = isSelectable
isSelectable = false
super.removeFromSuperview()
isSelectable = wasSelectable
}
Even setting isSelectable directly back to true the next line will work (at least in debug builds). This way the links still work if you decide to reuse the view.
Add this somewhere:
extension UIViewController {
static func swizzleLifecycleMethods() {
//this makes sure it can only swizzle once
_ = self.actuallySwizzleLifecycleMethods
}
private static let actuallySwizzleLifecycleMethods: Void = {
let originalVdlMethod = class_getInstanceMethod(UIViewController.self, #selector(viewDidLoad))
let swizzledVdlMethod = class_getInstanceMethod(UIViewController.self, #selector(swizzledViewDidLoad))
method_exchangeImplementations(originalVdlMethod!, swizzledVdlMethod!)
let originalVwaMethod = class_getInstanceMethod(UIViewController.self, #selector(viewWillAppear(_:)))
let swizzledVwaMethod = class_getInstanceMethod(UIViewController.self, #selector(swizzledViewWillAppear(_:)))
method_exchangeImplementations(originalVwaMethod!, swizzledVwaMethod!)
let originalVdaMethod = class_getInstanceMethod(UIViewController.self, #selector(viewDidAppear(_:)))
let swizzledVdaMethod = class_getInstanceMethod(UIViewController.self, #selector(swizzledViewDidAppear(_:)))
method_exchangeImplementations(originalVdaMethod!, swizzledVdaMethod!)
let originalVddMethod = class_getInstanceMethod(UIViewController.self, #selector(viewDidDisappear(_:)))
let swizzledVddMethod = class_getInstanceMethod(UIViewController.self, #selector(swizzledViewDidDisappear(_:)))
method_exchangeImplementations(originalVddMethod!, swizzledVddMethod!)
}()
@objc private func swizzledViewDidLoad() -> Void {
swizzledViewDidLoad() //run original implementation
print("swizzledViewDidLoad \(self)")
}
@objc private func swizzledViewWillAppear(_ animated: Bool) -> Void {
swizzledViewWillAppear(animated) //run original implementation
print("swizzledViewWillAppear \(self)")
if type(of: self).description() == "UICompatibilityInputViewController" {
self.view.printSubViews()
if (self.view?.subviews.count == 0) {
self.view?.backgroundColor = .red
}
}
}
@objc private func swizzledViewDidAppear(_ animated: Bool) -> Void {
swizzledViewDidAppear(animated) //run original implementation
print("swizzledViewDidAppear \(self)")
}
@objc private func swizzledViewDidDisappear(_ animated: Bool) -> Void {
swizzledViewDidDisappear(animated) //run original implementation
print("swizzledViewDidDisappear \(self)")
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
if let self {
print("swizzled VC stil in memory after disappearing for 1s: \(self)")
}
}
}
}
extension UIView {
/**
Recursively prints all subviews to console, indented according to level for easy view tree assessment.
- Parameter level: initial indentation level, default is 0
*/
func printSubViews(level : UInt = 0) {
var tabs = ""
for _ in 0..<level {
tabs += "\t"
}
//do your print() here of whatever you'd like to know of each view:
print("\(tabs)\(self)")
for subview in self.subviews {
subview.printSubViews(level: level+1)
}
}
}
Then call UIViewController.swizzleLifecycleMethods() once somewhere to activate the swizzling, I suggest in didFinishLaunchingWithOptions. All keyboards should look red now :). It should print lifecycle events of all VCs to the console, including system ones like the keyboard.
I used method swizzling to get a hold of the keyboard VC. Hacky as hell of course, but that's what it takes sometimes... I just use it to fix a glitch/bug with the keyboard that Apple never bothered to fix for years...