I have an app where I add a new item to a table view by having the user tap an edit button which shows a textfield cell at the bottom of the table, similar to the built in Notifications app. I need to adjust the table when the keyboard is shown so that it is not obstructed when their are many rows in the table. I am doing this by subscribing to the notification for when the keyboard shows:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector (keyboardDidShow:)
name: UIKeyboardDidShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector (keyboardDidHide:)
name: UIKeyboardDidHideNotification
object:nil];
}
...
...
-(void) keyboardDidShow: (NSNotification *)notif
{
// If keyboard is visible, return
if (self.keyboardVisible)
{
return;
}
// Get the size of the keyboard.
NSDictionary* info = [notif userInfo];
NSValue* aValue = [info objectForKey:UIKeyboardFrameBeginUserInfoKey];
CGSize keyboardSize = [aValue CGRectValue].size;
// Adjust the table view by the keyboards height.
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0);
NSIndexPath *path = [NSIndexPath indexPathForRow:self.newsFeeds.count inSection:0];
[self.tableView scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionTop animated:YES];
self.keyboardVisible = YES;
}
However, the table that I let a user add a row to can also be tapped and a new view is pushed on to the app. This view also has a text view and when the user taps in it and the keyboard shows the first viewcontroller still gets the notification, which causes a crash.
How can I either ignore the notification or get it to not fire when a new view is pushed?
You could add the class as an observer in viewDidAppear and remove it in viewWillDisappear.
Related
I have a Tableview in inside a viewcontroller. I have added following code to get keyboard notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleKeyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleKeyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
And on keyboard show i am scrolling my table to bottom.
- (void)handleKeyboardWillShow:(NSNotification *)notification
{
[self scrollToBottomAnimated:YES];
}
But i have a textview in my view controller as well. So when i click on textview the handleKeyboardWillShow method is called as well resulting unnecessary scrolling my tableview which i do not need if textview is clicked.
Can some one please help me figure out how to detect from which sender handleKeyboardWillShow is called.
Thanks
You can do it by checking who is first responder.
- (void)handleKeyboardWillShow:(NSNotification *)notification
{
if ([textFieldForScrolling isFirstResponder]) {
[self scrollToBottomAnimated:YES];
} else {
NSLog(#"Is a different text input");
}
}
Let me know if you need more explanation.
I would register for keyboardWillChange - which covers both showing and hiding. You can get the keyboard rect and adjust your content offset based on the keyboard's location. Note: You can animate the change in content offset - I just didn't do it in this answer.
In your textfield delegate methods willBeginEditing and didEndEditing, you can set the state variable called currentTextField.
-(void)keyboardWillChange:(NSNotification *)notification {
keyboardRect = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
keyboardRect = [self.view convertRect:keyboardRect fromView:nil];
CGPoint currentFieldPoint = [currentTextField convertPoint:currentTextField.frame.origin toView:self.view];
if(keyboardRect.origin.y < currentFieldPoint.y + currentTextField.frame.size.height){
//move stuff here
[[self tableView] setContentOffset:CGPointMake(0, [self tableView].contentOffset.y + offsetValue)];
}
}
I have view to type message as shown below.
Now, when I type message, the keyboard appears and the box should move just above keyboard as shown in figure below.
my problem
They keyboard animation and view animation occur at different time. Keyboard appears first and then view appears. Even if i tried to set animation time to any, they occur at different time.
How should I solve my problem?
Please, suggest me way to solve it so that keyboard and view animates to show as if they are of same view. Both animation should occur at exact time so that they look like same view appeared at a time.
what i tried
my view did load has following code
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification object:nil];
now keyboard show function look like
- (void)keyboardWillShow:(NSNotification *)note{
NSDictionary* keyboardInfo = [note userInfo];
CGFloat duration = [[keyboardInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
[UIView animateWithDuration:duration animations:
^{
//chat_typingView is name for typing view
chat_typingView.frame = CGRectMake(chat_typingView.frame.origin.x,
238,
chat_typingView.frame.size.width,
chat_typingView.frame.size.height);
}
maybe you should check if your chat_typinfView is First responder before do the animation and disable Autolayout (very important).
if ([chat_typingView isFirstResponder]) {
// Do the animation
}
PS is recommendable to subscribe to the notification on viewWillApper instead of viewDidLoad
I have a similar setup in one of my apps and I do the following:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardShowed:)
name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardHidden:)
name:UIKeyboardWillHideNotification object:nil];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
/*** FOR AUTOLAYOUT MODIFICATIONS & ADDITIONS # RUNTIME ***/
self.defaultViewFrame = self.myView.frame
}
- (void) keyboardShowed:(NSNotification*)notification {
//GET KEYBOARD FRAME
CGRect keyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
//CONVERT KEYBOARD FRAME TO MATCH THE OUR COORDINATE SYSTEM (FOR UPSIDEDOWN ROTATION)
CGRect convertedFrame = [self.view convertRect:keyboardFrame fromView:self.view.window];
//..... do something with the convertedFrame (in your case convertedFrame.origin.y)
}
- (void) keyboardHidden:(NSNotification*)notification {
//RESTORE ORIGINAL STATE
[UIView transitionWithView:self.view
duration:.3f
options:UIViewAnimationOptionCurveLinear
animations:^{
self.myView.frame = self.defaultViewFrame;
}
completion:nil];
}
I'm trying to set up tableview to scroll when a text field that would be hidden behind a keyboard is selected. I've tried multiple methods that I've found around including using keyboard notifications, textFieldDid/ShouldBegin/EndEditing etc, but none of them seem to work every time.
Here's a screenshot of what I'm working with:
I have two issues:
First, I'm using a date picker in place of a keyboard for my bottom text field (off screen in the screenshot but you can get the idea). Since this isn't technically the keyboard, the methods I've used for setting the keyboard offset aren't working for this text field. I'm sure I can get the height of the date picker and adjust accordingly if that is the currently selected item, but I was wondering if there was an easier way of incorporating this into the keyboard methods.
Second, when more cells are added, the offsets become incorrect. The way this view is set up is a table view divided into sections. When the user taps the "Add further support" button, it inserts a row into the support section. It seems like the height change that happens because of this is not being registered when I try to set the table scroll offset. Is there a way I can get the height to register properly?
Here's some relevant code
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
self.activeField = textField;
[self setOffsetForKeyboard];
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
if (self.activeField == self.dateTextField) {
[self datePickerValueChanged:nil];
}
self.activeField = nil;
}
- (void)registerForKeyboardNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
}
- (void)keyboardWillShow:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
self.keyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
[self setOffsetForKeyboard];
}
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
[self.myTable setContentOffset:CGPointMake(0.0, -(self.navigationController.navigationBar.frame.size.height + kStatusBarHeight))animated:YES];
}
- (void)setOffsetForKeyboard{
CGPoint location =[self.activeField.superview convertPoint:self.activeField.frame.origin toView:nil];
if (location.y > self.view.frame.size.height - self.keyboardSize.height-kKeyboardOffset) {
[self.myTable setContentOffset:CGPointMake(0.0, location.y-self.keyboardSize.height-kKeyboardOffset) animated:YES];
}
}
This is your solution , you will love it->
https://github.com/michaeltyson/TPKeyboardAvoiding
So I ended up just subclassing UITableViewController which has this functionality built in instead of UIViewController which is what I was using before. Works like a charm!
I'm using Storyboards and iOS 6.1, I'm initiating a view like this:
PropertiesViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"Properties"];
PropertiesViewController has a 60px height viewn an including some labels and more. I want to add these views to another ScrollView as subview. Here what I'm doing:
controller.leftLabel.text = #"Test";
controller.background.image = [UIImage imageNamed: #"someimage.png"];
controller.view.frame = CGRectMake(0, currentHeight, controller.view.frame.size.width, cellHeight);
[self.scrollView addSubview: controller.view];
leftLabel is a UILabel and backgorund is a UIImageView.The problem is none of view's elements are not updating outside of PropertiesViewController. addSubview: is just adding as how its created, not allow to configure.
I've already checked NSNotificationCenter approach, if I'm not wrong it's not about updating instances. And I've also already added a method to receiver class to update labels inside of PropertiesViewController. That even did not worked.
What am I doing wrong?
Note: You may ask that why just I'm not using a TableView. There has to be more dynamic resources and their besides are not clear. There is also one thing that TableView is not possible with ScrollView, in some cases I've never use a TableView so scrollview will manage scroll.
Any help would be great.
EDIT:
According to Kalpesh's answer here what I did:
In my sender view controller:
PropertiesViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"Properties"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"update" object:#"Test string"];
controller.view.frame = CGRectMake(0, currentHeight, controller.view.frame.size.width, cellHeight);
// not sure how but I can change frame successfully.
[self.scrollView addSubview: controller.view];
Here is receiver:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"Called"); // Working fine
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(changetext:) name:#"update" object:nil];
//even if set some object, nothing changed
}
- (void) changetext:(NSNotification *)notification {
NSLog(#"Received"); // Not working
leftLabel.text = [notification object];
}
Try this, You have to send notification from other viewcontroller when you want to change label of text
..
[[NSNotificationCenter defaultCenter] postNotificationName:#"changetext" object:stringobj];
in mainviewcontroller
-(void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(changetext:) name:#"changetext" object:nil];
}
- (void) changetext:(NSNotification *)notification
{
NSString * text =[notification object];
lbl.text=text;
}
I have a Tableview where the user can enter values into a textField as one of the custom cells
Apple have some documentation about how to adjust view content by repositioning the view clear of the keyboard's vertical dimension ( Here ) but it relies upon one placing that view into a UIScrollView. I cant do this with a tableview.
I could redesign the app so that the entry gets done in a separate detail view using the usual navigation controller, but i'd rather the user not have to perform an extra touch ( and be ferried off into yet another screen ) if possible. I like the idea of doing the deed "right where we are"
so my workaround to have a few extra tableview cells at the bottom containing a %20 or so, normal usage shouldn't register the oddity, as they are only focussed on what is visible.
I'd have to store the spaces in my datasource array and then sort descending, but that's OK
the question is, is this good practice? and even more possibly, could it be against Apple's HIG sufficient for refusal?
UITableView is a subclass of UIScrollView, so should be able to adjust the content and scroll view insets just like in the example you linked.
The way I've solved this issue is to subclass UITableView. Here's what I've done:
// AOTableView.h file
typedef enum
{
AOKeyboardStateUnknown = 0,
AOKeyboardStateShowing,
AOKeyboardStateHidden
} AOKeyboardState;
#import <UIKit/UIKit.h>
#import "AOKeyboardState.h"
#interface AOTableView : UITableView
#property (nonatomic) BOOL observeKeyboardNotifications;
#property (nonatomic) AOKeyboardState keyboardState;
#end
// AOTableView.m file
#import "AOTableView.h"
#interface AOTableView(Private)
#property (nonatomic) CGRect frame0;
- (void)setup;
- (void)keyboardWillShow:(NSNotification *)notification;
- (void)keyboardWillHide:(NSNotification *)notification;
#end
#implementation AOTableView
#pragma mark - Object lifecycle
- (void)awakeFromNib
{
[self setup];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
[self setup];
}
return self;
}
- (void)setup
{
self.contentSize = self.frame.size;
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_keyboardState = AOKeyboardStateUnknown;
_frame0 = self.frame;
_observeKeyboardNotifications = NO;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Custom setters
- (void)setObserveKeyboardNotifications:(BOOL)observeKeyboardNotifications
{
if (_observeKeyboardNotifications == observeKeyboardNotifications)
return;
_observeKeyboardNotifications = observeKeyboardNotifications;
if (_observeKeyboardNotifications)
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
else
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
}
#pragma mark - UIKeyboard Notifications
- (void)keyboardWillShow:(NSNotification *)notification
{
if (self.keyboardState == AOKeyboardStateShowing)
return;
self.frame0 = self.frame;
self.keyboardState = AOKeyboardStateShowing;
NSDictionary* info = [notification userInfo];
CGRect keyboardFrame = CGRectZero;
[[info objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardFrame];
CGRect frame = self.frame0;
frame.size.height = CGRectGetMinY(keyboardFrame) - CGRectGetMinY(frame);
self.frame = frame;
[self scrollToRowAtIndexPath:self.indexPathForSelectedRow atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
[self deselectRowAtIndexPath:self.indexPathForSelectedRow animated:NO];
}
- (void)keyboardWillHide:(NSNotification *)notification
{
if (self.keyboardState == AOKeyboardStateHidden)
return;
self.keyboardState = AOKeyboardStateHidden;
self.frame = self.frame0;
}
#end
After creation (or loading the view from an IBOutlet), you call this method to tell the class to start listening for keyboard notifications:
[tableViewInstance setObserveKeyboardNotifications:YES];
Whenever a user clicks on a cell, it becomes the self.indexPathForSelectedRow cell... so its scrolled to by the AOTableView instance automatically.
For this to work, though, I've had to turn off userInteraction on the UITextField within the cell (otherwise, the device can get confused about if the user is clicking on the cell or on the text field). Instead, when a user selects a cell that has a text field, I tell the text field to the become first responder, like this:
[cell.textField becomeFirstResponder];
I hope this helps.
You don't need the extra cells or anything fancy.
Since your text fields are inside the table view cells, you can use the following:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
UITableViewCell *cell = (UITableViewCell *)textField.superview.superview;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
return YES;
}
This means that the keyboard will scroll appropriately each time a text field becomes first responder. This takes advantage of the table view being a scroll view subclass.
Note that this assumes:
Your (table) view controller is the text fields' delegate.
Your text field is a subview of the cell's content view, not the cell itself.
If the text field is a subview of the cell, the first line of the method above should reference only one superview (i.e., textField.superview).