I use a UITextView in a subclass of UIViewController which I've named FacebookPostViewController. So the purpose of the text view is to allow the user to review the content of a prepared post, possibly make changes to it, and then decide if the text from the text view should be posted to Facebook, or not.
I've set the returnKeyType property of the UITextView to UIReturnKeySend, because I want to use it to actually post to Facebook. Canceling is possible via a back button in the NavBar.
So the FacebookPostViewController is delegate of the UITextView, and in the textView:shouldChangeTextInRange:replacementText: delegate method, I check for a newline insertion, and then call my sendTextToFacebook method:
- (void)sendTextToFacebook;
{
// prepare sending
NSMutableDictionary* sendParameters = [NSMutableDictionary dictionaryWithObjectsAndKeys:self.textView.text, @"message", nil];
// request sending to own feed
[[FacebookController sharedFacebookController].facebook requestWithGraphPath:@"me/feed" andParams:sendParameters andHttpMethod:@"POST" andDelegate:self];
}
As you can see, I specify self as the FBRequestDelegate, so I also implement
- (void)request:(FBRequest *)request didFailWithError:(NSError *)error;
- (void)request:(FBRequest *)request didLoad:(id)result;
where I give the user feedback if posting was successfull or not.
However, I've figured that it takes some time until the Facebook server replies, so I want to have the complete UI inactive until one of the two delegate methods is called. Therefore, I've implemented the following method:
- (void)disableUserInterface;
{
self.textView.editable = NO;
self.loginButton.enabled = NO;
self.logoutButton.enabled = NO;
[self.navigationItem setHidesBackButton:YES animated:YES];
[self.spinner startAnimating];
self.spinner.hidden = NO;
}
My problem is that calling self.textView.editable = NO will hide the keyboard, which looks stupid. If I remove that line, the keyboard won't disappear, but the user can change the textView content while the text is sent to Facebook. However, as posting is already initiated. these changes won't actually appear in Facebook.
I thought I could simply implement another UITextViewDelegate method:
- (BOOL)textViewShouldEndEditing:(UITextView *)textView
{
return NO;
}
The UI result is then exactly how I'd like to have it. However, this will lead to a EXC_BAD_ACCESS crash later when I've popped the FacebookPostViewController and then push another view to the UINavigationController. Ther error then is
*** -[UITextView isKindOfClass:]: message sent to deallocated instance 0x20c650
So, has anybody an idea how I can solve my problem to make the UITextView uneditable, but prevent the keyboard from disappearing?!?
Okay, thanks to the comment from David H above, I've figured out a solution. But here are the most important insights:
When I pop my FacebookPostViewController from the UINavigationController, the UITextView is asked automatically by the system to resign being the first responder.
You should never let textViewShouldEndEditing: always return NO, because this will always prevent the keyboard to hide, which will then lead to the crash described above, as soon as the UITextView is deallocated.
The solution is to prevent the keyboard from disappearing only in case we are still waiting for a response from the Facebook server. Here are a couple of lines of codes that I've added, which do exactly this:
@interface FacebookPostViewController ()
// ...
@property (nonatomic, assign, getter = isWaitingForFBResponse) BOOL waitingForFBResponse;
// ...
@end
@implementation FacebookPostViewController
// ...
@synthesize waitingForFBResponse = _waitingForFBResponse;
- (id)init;
{
self = [super init];
if (self) {
// set default values
self.waitingForFBResponse = NO;
// ...
}
return self;
}
#pragma mark -
#pragma mark UITextViewDelegate methods
- (BOOL)textViewShouldEndEditing:(UITextView *)textView
{
if ([self isWaitingForFBResponse]) {
return NO;
} else {
return YES;
}
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
// Did user press 'return' (Send) key?
if([text isEqualToString:@"\n"]) {
// enter waiting state
self.waitingForFBResponse = YES;
// disable the user interface
[self disableUserInterface];
FacebookController *fbController = [FacebookController sharedFacebookController];
if ([fbController.facebook isSessionValid] == YES) {
// Post to Facebook!
[self sendTextToFacebook];
} else {
// we need to login first
[self loginToFacebook];
// remember that user wants to post to Facebook
self.sendTextAfterFBLogin = YES;
}
// Don't allow textView to insert a LF into the text property
return NO;
}
// allow all other edits
return YES;
}
#pragma mark -
#pragma mark FBRequestDelegate methods
- (void)request:(FBRequest *)request didFailWithError:(NSError *)error;
{
// request failed, so display error status overlay.
[[FeedbackController sharedFeedbackController] displayStatusOverlayWithString:NSLocalizedString(@"StatusMessage_Failure", nil)];
// leave waiting state
self.waitingForFBResponse = NO;
// enable UI again
[self enableUserInterface];
}
- (void)request:(FBRequest *)request didLoad:(id)result;
{
// request succeeded, give user appropriate feedback.
[[FeedbackController sharedFeedbackController] displayStatusOverlayWithString:NSLocalizedString(@"StatusMessage_Sent", nil)];
// leave waiting state
self.waitingForFBResponse = NO;
// pop the view, as the user accomplished his goal.
[self.navigationController popViewControllerAnimated:YES];
}
@end