UIViewController with inputAccessoryView is not deallocated
Asked Answered
M

2

9

I have simple subclass of UIViewController (code below). If I attach inputAccessoryView, my viewcontroller is never deallocated. If I do not set inputAccessoryView in viewDidLoad, dealloc is called as expected.

Am I missing something?

@interface IMTestViewController : UIViewController

@property (nonatomic, strong) UIView *messageInputView;
@property(nonatomic, readwrite, strong) UIView *inputAccessoryView;

@end

@implementation IMTestViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)dealloc
{

}

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.inputAccessoryView = self.messageInputView;
}

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

- (UIView *)messageInputView
{
    if (_messageInputView == nil)
    {
        _messageInputView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 45)];
        _messageInputView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
    }
    return _messageInputView;
}
@end

I have ran out of ideas. Thank you.

Mendelssohn answered 6/7, 2014 at 12:57 Comment(6)
Do you want this inputAccessoryView to be added to the keyboard that appears? If so, you're doing this incorrectly. inputAccessoryView is a property on UITextField, not UIViewController. I'm not sure, why doing it this way would cause your controller to not be deallocated, but if you change the name to something other than inputAccessoryView, it is deallocated properly. So, it seems using this property name incorrectly is confusing the system somehow.Harrod
please add the code that used for setting self.messageInputView and code used for presenting IMTestViewControllerTractile
@rdelmar, inputAccessoryView is a property of UIResponder, which is a superclass of UIViewController.Mendelssohn
@Rafeek, this is all the code that I have. I created a sample project for this. Code in appDelegate: UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:[UIViewController new]]; [nav pushViewController:[IMChatIAVViewController new] animated:NO]; self.window.rootViewController = nav;Mendelssohn
I'm at the same point as you. If you found a solution, let me know.Stylography
@GuilhermeSprint just use self.view as First Responder instead of self. Yes, you need to subclass UIView or UITableView and implement some methods.Ultun
I
7

Unfortunately for me @rdelmar's answer didn't work. After some time spent trying to solve it I found this article: http://derpturkey.com/uitextfield-docked-like-ios-messenger/

My goal is to have the input accessory view visible even if the keyboard is not, exactly like in all IM apps. I previously subclassed my UIViewController custom class to allow it to become first responder and returned my custom subview as inputAccessoryView. This was preventing the view controller from being dealloced. Now I subclass the controller's view to achieve the same thing as recommended in the link above, everything seems to work fine.

EDIT: after some more testing I can confirm the custom UIView is dealloced just fine.

EDIT 2: only downside is that you can't make the keyboard appear in viewWillAppear, the inputAccessoryView is not already added to the view hierarchy and can't become first responder.

Interrelation answered 7/10, 2014 at 20:46 Comment(4)
Wow, great idea to make the view a responder, not view controller itself. And it solves deallocation problem. Thank you!Mendelssohn
This is a great find, I've been looking for a solution to this for some time.Momentary
On iOS > 7 the UIViewController subclassing works fine, but the memory leak of the inputAccessoryView mentioned in the article still occurs :(Freewheel
Yes, sorry if my answer is not very clear. I used to return the view controller, but I now subclass its view instead to allow it to become first responderInterrelation
F
2

This question is rather old, but I came across it in 2019 when trying to use an inputAccessoryView in iOS 12.

The deallocation problem still exists today and the first solution proposed in the article mentioned in dvkch's answer does not work either. The second solution in the article (involving animations) is just too much work and does not work well when the user dismisses the keyboard interactively via a UIScrollView with scrollView.keyboardDismissMode = .interactive.

The best approach I could come up with is just setting the first responder UITextField or UITextView inputAccessoryView to nil on viewDidDisappear. That gets rid of the memory leak entirely and does not seem to have any side-effects or downsides.

So here's a full Swift 4.2 example:

class MyViewController: UIViewController {
    /// You could also add your text field or text view programmatically, 
    /// but let's say it's coming from a .xib for now...
    @IBOutlet private weak var myTextField: UITextField!

    /// This is required for the inputAccessoryView to work.
    override internal var canBecomeFirstResponder: Bool {
        return true
    }

    /// Here's a nice empty red view that will be used as an
    /// input accessory.
    private lazy var accessoryView: UIView = {
        let accessoryView = UIView()
        accessoryView.backgroundColor = UIColor.red
        accessoryView.frame.size = CGSize(
            width: view.frame.size.width,
            height: 45
        )

        return accessoryView
    } ()

    override var inputAccessoryView: UIView? {
        return accessoryView
    }

    /// This is required to avoid leaking the `inputAccessoryView`
    /// when the keyboard is open and the `UIViewController`
    /// is deallocated.
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        myTextField.inputAccessoryView = nil
    }
}
Freewheel answered 9/3, 2019 at 12:33 Comment(2)
I think this would be the simplest, but maybe do this in did disappear because willDisappear can be called when using the interactive Pop gesture of UINavigationController, which can be cancelled. Also a viewController can disappear when presenting another one so maybe check for presentedViewController == nilInterrelation
@dkvch woops!! You are absolutely right about did disappear! I thought that's what I had typed :) I'll fix it right now. Thanks! Regarding the controller disappearance, I don't think it matters, since inputAccessoryView will be called again. You just have to make sure that the view is not deallocated in the meantime (in this example it's not - as there's a strong reference to it in the private lazy var).Freewheel

© 2022 - 2024 — McMap. All rights reserved.