When a subclass of UIInputView is created programmatically, a memory leak occurs. This issue can be easily reproduced even with a very simple sample project, so I’m submitting this report along with a minimal reproducible example.
When a custom view subclassing UIInputView is instantiated in code, _InputViewContent retains the custom view, and the custom view also holds a reference back to _InputViewContent, creating a strong reference cycle that prevents deallocation.
The issue consistently occurs and has been confirmed on Xcode 16.4, 26.0, and 26.1.
As a workaround, initializing the view via Storyboard allows it to be properly deallocated from memory. (Please refer to the CustomInputView in the attached sample code.)
import UIKit
final class LeakInputView: UIInputView {
deinit {
print("LeakInputView deinit") // not called
}
}
final class CustomInputView: UIInputView {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect, inputViewStyle: UIInputView.Style) {
super.init(frame: frame, inputViewStyle: inputViewStyle)
}
deinit {
print("CustomInputView deinit") // called
}
}
extension CustomInputView {
static func loadFromNib() -> CustomInputView {
let nib = UINib(nibName: "CustomInputView", bundle: nil)
guard let view = nib.instantiate(withOwner: nil, options: nil).first as? CustomInputView else {
fatalError("Failed to load CustomInputView from nib.")
}
return view
}
}
final class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
LeakInputView()
LeakInputView()
LeakInputView()
CustomInputView.loadFromNib()
CustomInputView.loadFromNib()
CustomInputView.loadFromNib()
DispatchQueue.main.async {
print("Next runloop tick")
}
}
}