I have a custom UITableViewCell that extends UITableViewCell and implements MFMailComposeViewControllerDelegate. (i.e. UITableViewCell<MFMailComposeViewControllerDelegate>).
When a button is clicked in the custom table cell, I present a mailController with presentModalViewController. The user can then type the email or cancel, everything works.
But when didReceiveMemoryWarning is called while the mailController is present, and then when the mailController closes, the app crashes. I get this error:
"-[CustomCell respondsToSelector:]: message sent to deallocated instance 0xf4988b0"
Now I'm pretty sure its because the table view that owns the tablecell has been deallocated hence the cell has been deallocated but does anyone have any suggestions to go about fixing this? I mean I guess I could switch the MailCompose delegate to the table cell's table view's view controller but I'd rather not. I'd rather keep it in the table cell. Any ideas?
You should not be using a table view cell as the MFMailComposeViewControllerDelegate. Because of the nature of cells and their reuse, it's hard to pin a particular instance to be a delegate, especially a delegate of a view controller on top of the view controller the cell is being shown, and on this particular case, the memory warning might be causing a flush of the cache of cells.
The MFMailComposeViewControllerDelegate should be the delegate of the table view where the cell is. It's also a better MVC pattern.
Related
I have an NSSplitView showing two NSTableView instances. I need to detect which table view has become "active" (of focused), which means the one that the user has clicked. I need to know that because each table view acts as a source list for another view that shows the content of the selected row(s). This other view is shared for both tables.
I could do it by subclassing NSTableView and reacting to mouseDown: or another method but it I'd rather avoid subclassing just for that. I also don't want to track any NSWindow event just to know if the user has clicked one of the tables (I'd rather subclass NSTableView).
Currently, I use the delegate method tableViewSelectionDidChange:, but this method is, obviously, only called when the selected row changes. I need to know that a table becomes active even if the selected row hasn't changed.
Observing the clickedRow property of the table views doesn't appear to work. If may not be KVO compliant.
Any ideas?
For those interested, the most convenient solution I found was to take advantage of the fact that NSTableView is a subclass of NSControl. So just like NSButton it can send action messages when clicked (upon mouse up).
For each tableView, I wired its "action" to the same ibaction selector of my controller object in interface builder.
The controller identifies the sender and acts accordingly.
No need to subclass NSTableView.
I'm literally going nuts.
I have a service app that opens a pretty simple NSPanel that consists of an NSTableView, a label, and three NSButton controls.
That's it.
The table view is view-based, and has four different NSTableCellView rows defined in IB.
The table code is pretty straight forward; my NSWindowController subclass is both the data source and the delegate. And for the most part, everything works perfectly:
the window opens
the expected data source methods (numberOfRowsInTableView: and
tableView:objectValueForTableColumn:row:) get called
the table gets populated with its views and section separators via
tableView:isGroupRow: and tableView:viewForTableColumn:row:
empty selections are disabled, so the delegate immediately receives a
tableView:shouldSelectRow: message, and the table automatically
selects the first row
the table appears exactly as it should and the first row is selected
Everything looks perfect, but I can't select a different rows. Nothing I click on in the table changes the selection.
I have tried:
subclassing NSTableCellView and verified that it is receiving a hitTest: call
tried "hacking" the table cell view hitTest: so it always return nil
tried various combination of "refuses first responder", "enabled", "editable" properties on the control view inside the table cell view
tried deleting all of the control views. so the table cell views were empty
tried implementing tableView:selectionIndexesForProposedSelection: instead of tableView:shouldSelectRow:
changed the NSPanel into a regular NSWindow
Nothing seems to make any difference. Nothing I click on in the table changes the selection, and my table view delegate never receives another tableView:shouldSelectRow: call.
Note that all of the other (NSButton) controls in the window work just fine. I can click on any of them.
Update #1
Per the comment, I tried changing the product to a plain-old .app, but it makes no difference.
My next step was to fiddle some more with the delegate methods and have narrowed down the problem to this:
If I implement tableView:shouldSelectRow:, the table calls my delegate method twice (because I have have "empty selection" turned off, the table must initially determine which row select as a default, so I get two calls, one for row 0 (NO) and a second for row 1 (YES)). However, if I click in the table, I never receive another tableView:shouldSelectRow:.
If I remove the implementation of tableView:shouldSelectRow:, table selection magically starts to work. (Except for the fact that you can select group rows, which is worng.)
Also tried implementing tableView:selectionIndexesForProposedSelection: instead of tableView:shouldSelectRow:; same behavior
So it appears if I implement any of the delegate methods to determine which rows are selectable, I can't select any rows. sigh
Update #2
I punted and refactored the app so there is now a new view controller object dedicated to managing the table view (rather than overloading the window controller). All data model and delegate methods were moved to the new view controller.
Also (thinking there might be something weird with the table view in IB), I deleted the table view from the NIB and recreated it and all of its connections.
Unfortunately, neither of these changes made any difference. If the tableView:shouldSelectRow: is implemented, the table is unusable. Delete the method, and it works again.
Update #3
And it just get weirder: thinking I could "hack" the row selection problem, I implemented the table view delegate methods tableViewSelectionIsChanging: and tableViewSelectionDidChange:. Neither get called. Even if I remove tableView:shouldSelectRow:, allowing the table selection to work, no tableViewSelectionIsChanging: or tableViewSelectionDidChange: is ever received.
Yet, if I add observers for NSTableViewSelectionIsChangingNotification and NSTableViewSelectionDidChangeNotification, those are received.
Also note that the NSViewController subclass that is the table view's delegate and data source explicitly conforms to <NSTableViewDelegate> and <NSTableViewDataSource>, so there's shouldn't be any reason why the table view should be confused as to what delegate methods are implemented.
Yikes! Now this is embarrassing.
So the problem was a weak reference(s) to the window controller.
Here's what was happening: the window controller was being created, loaded, and the window was presented. During the initial presentation and display, everything worked (tableView:shouldSelectRow: et. al.) because the window and view controllers existed. But in some future event loop, ARC destroyed the window and view controllers, leaving only the window on the screen with a table view, and the weak references to its delegate and data source objects were now nil.
Solution was to fix the window controller management so it keeps a strong reference to the window controller until the window closes.
Sometimes it's the simplest things that trip you up...
Struggling to find where fault is with my code. On first view load, everything works and loads fine as it should, but when i revisit that view, it seems that the first two cells are empty. I logged the dictionary (dict) in viewWillAppear: and it logs the data fine, so error has to be in cellForRow method. Take a look at my method, and see where i'm going wrong, the third cell populate third piece of data, so i'm totally stumped, but the first two cells are completely blank, no data.
http://pastebin.com/Va84MG5g
First of all, why are you doing all of that insane UITableViewCell customization inside of your tableView:cellForRowAtIndexPath: method? Create a custom UITableViewCell subclass and do the set up there.
In the class's initWithStyle: method, add all of your subviews with a frame of CGRectZero, because at initialization, the table view cell doesn't know how big it is. You can set text alignments, colors, etc. here as well. Then, in layoutSubviews, go ahead and set all the frames. Override prepareForReuse and set things like your UIImageViews to nil. This will help with performance for reused cells.
As for why you're not seeing your data in your first two cells, my initial thought is that it has something to do with the way you're setting up your cells for reuse. You're asking your tableView to dequeue a regular UITableViewCell and only creating all of these subviews if the returned cell is nil. So what happens when it returns a UITableViewCell? You skip the part where you alloc/init all these subviews, and so you're basically adding nothing to the cell. I feel if you create a custom subclass and ask your UITableView to dequeue that instead, you'll get the result you're looking for.
NOTE: If you're targeting at least iOS 5, you can create your UITableViewCell's layout in a nib and register the nib with the table view. Doing so will guarantee that you always get a dequeued cell, and you never have to do your if (cell == nil) check. If you're targeting iOS 6, you can register a UITableViewCell subclass.
Background:
I have a UICollectionViewController that shows items in one of two modes, which the user can toggle between. Each mode uses a different class of UICollectionViewCell. Let's call these modes "list view" and "grid view".
When I switch modes, I call .reloadData on the UICollectionView, which redraws the collection view using the correct cell classes. Everything works fine here.
Now: Inside the UICollectionViewCell subclass for one type of cells, I want to be notified when the collection view that contains it switches modes. Visually, a cell which was on-screen vanishes; the collection view is drawn fully with the other type of cell. When switching back, the cell is re-displayed.
Question:
How can I be notified when a UICollectionViewCell is "removed" (i.e., no longer shown; I'm not sure what's happening under the hood yet) from its parent collection view?
Notes:
prepareForReuse is not called on the cell when the collection view's updateData causes the cell to no longer be included.
willTransitionFromLayout:toLayout: (an empty layout?) is not called.
Overriding didMoveToSuperview is of no help; it is not called.
Observing .hidden or .alpha on the cell does not work.
The cell's dealloc is not called; it sticks around in the reuse queue.
Something in the cell must be changing that I can observe or hook into, what is it?
Update: UICollectionViewDelegate has this method, which from the documentation seems like it does what I am asking:
collectionView:didEndDisplayingCell:forItemAtIndexPath:
Original answer:
I got this working as desired by having the UICollectionViewController manually notify visible cells of impending doom with this method when I'm about to toggle and call reloadData:
- (void)notifyCellsWillBeHidden {
for (UICollectionViewCell *cell in self.collectionView.visibleCells) {
if ([cell respondsToSelector:#selector(willBeRemovedFromCollectionView)]) {
[cell performSelector:#selector(willBeRemovedFromCollectionView)];
}
}
}
These cells can then do what they need to do if they implement the above method.
Calling prepareForReuse may not a good idea because it will be called again before the cells are re-displayed, if this is a problem.
I'm still curious whether there is a way for a cell to receive notification that it is going to be made non-visible without an explicit call.
Must I always resignFirstResponder for a UITextView? Or, will this happen automatically when its view controller disappears?
I'm asking because I'm having an issue similar to iPhone Objective-C: Keyboard won't hide with resignFirstResponder, sometimes, where the keyboard stays up even when the nav controller pushes and pops other view controllers. The keyboard works, and when I hit done, it unfocuses the UITextView (i.e., the cursor disappears), but the keyboard stays up.
I never found out why this is happening, but maybe it's due to not doing resignFirstResponder before pushing another view controller, but I thought it was optional?
At a total guess, the UITextView has a reference to the view controller (as its delegate) but does not retain it. When you go to the next screen, the controller is dealloced and then the UITextView (which has perhaps been retained by something else) tries to call back to the dealloced controller and crashes. When you call resignFirstResponder, you reverse the order this happens, and therefore no crash.
The way round this to add a textView.delegate = nil call in your view controller's dealloc method - obviously put it before you release the text view.
The contract between a UITextView and it's delegate says that the delegate will send -resignFirstResponder when the text view is done editing. This informs the framework that the view is done editing, fires the events relating to that (willEndEditing and didEndEditing), and allows other parts of the responder hierarchy to react accordingly. Failing to do so might work, but it's not following the contract (that's all a protocol is) it agreed to.
I don't think you have to because the Xcode Sample UICatalog UITextField doesn't call resignFirstResponder before the TextViewController is popped.
The reason the keyboard got stuck for me is that I was having the same view controller present two view controllers modally at the same time, one after the other. UIKit didn't like that.
Calling resignFirstResponder makes sure that the text property contains the actual text shown in the control.
Depending on the state this is not always necessary, but if your controls have resigned first responder, you know that you're working with valid data.