I would like to add a textfield and send button that sticks to the bottom of uitableview similar to a chat app. I have come across comments on embedding a UITableView as a container view within a UIViewController.
However, they seem to lack an example on how to achieve this. More specifically details including where to add a textfield/button and move textfield up when keyboard appears, etc. Thanks!
follow the steps using the storyboard
1) drag a uiviewcontroller from the object library.
2) drag and drop the textfield and button and place it at the position you want
3) drag and drop a container view.
4) delete the default uiviewcontroller comes with the container view
5) drag a uitableviewcontroller and make a segue and the segue should be embedsegue.
and for keyboard handling you can go with IQKeyboardManager library https://github.com/hackiftekhar/IQKeyboardManager
A very simple chat model code, you can take a look:
#import "ViewController.h"
#interface ViewController ()
#property UIView* containerView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_containerView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
UITableView* tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height-30)];
UITextField* tfInput = [[UITextField alloc] initWithFrame:CGRectMake(0, tableView.frame.size.height, [UIScreen mainScreen].bounds.size.width-50, 30)];
tfInput.backgroundColor = [UIColor grayColor];
UIButton* btnSend = [[UIButton alloc] initWithFrame:CGRectMake(tfInput.frame.size.width, tfInput.frame.origin.y, 50, 30)];
[btnSend setTitle:#"Send" forState:UIControlStateNormal];
[btnSend setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
[btnSend addTarget:self action:#selector(btnClicked) forControlEvents:UIControlEventTouchUpInside];
[_containerView addSubview:tableView];
[_containerView addSubview:tfInput];
[_containerView addSubview:btnSend];
[self.view addSubview:_containerView];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)keyboardWillShow:(NSNotification *)notification {
NSDictionary *userInfo = [notification userInfo];
NSValue* aValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
float keyboardHeight = [aValue CGRectValue].size.height;
//Resize the container
_containerView.frame = CGRectMake(0, - keyboardHeight, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
}
-(void)btnClicked{
[self.view endEditing:YES];
}
- (void)keyboardWillHide:(NSNotification *)notification {
_containerView.frame = [UIScreen mainScreen].bounds;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
This is a screenshot for using storyboard:
Related
I tried to show a keyboard on after loading screen like this:
-(void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UITextField *tf = [[UITextField alloc] initWithFrame:CGRectMake(10, 200, 300, 40)];
tf.borderStyle = UITextBorderStyleRoundedRect;
tf.text = #"test";
[self.view addSubview:tf];
if([tf canBecomeFirstResponder]){
[tf becomeFirstResponder]; // surely this line is called
}
}
This code works on ios 8,9,10 but not 11. I'm not sure why the keyboard isn't show automatically on ios 11 while text field is focusing (has cursor).
And in this case, keyboard's notification isn't called:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
- (void) keyboardWillShow:(NSNotification *)note {
NSDictionary *userInfo = [note userInfo];
CGSize kbSize = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
DLog(#"Keyboard Height: %f Width: %f", kbSize.height, kbSize.width);
}
I even try this:
[tf performSelector:#selector(becomeFirstResponder) withObject:nil afterDelay:0];
but still not work.
I have to click on text field to bring up the keyboard.
Is there anything update from Apple which I don't know?
Update: it looks like there is something wrong with my project or all my view controllers because I can't make the keyboard showing on all screens. But when I create new project with above code, it can work well.
Here is one of the problem:
As you can see, I have to click on the textfield to show the keyboard, and from the second time, it can work properly. And this issue only happended on ios 11 (both simulator & device)
Here is the code for search field above:
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 215, 30)];
textField.backgroundColor = [UIColor clearColor];
textField.placeholder = #"Enter text to search";
textField.borderStyle = UITextBorderStyleLine;
textField.layer.masksToBounds=YES;
textField.layer.borderColor=[[UIColor lightGrayColor]CGColor];
textField.layer.borderWidth= 1.0f;
textField.returnKeyType = UIReturnKeySearch;
textField.delegate = self;
textField.clearButtonMode = UITextFieldViewModeAlways;
[UIView transitionWithView:self.navigationController.navigationBar
duration:0.55f
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
self.navigationItem.titleView = textField;
} completion:^(BOOL finished) {
[textField becomeFirstResponder];
}];
I wonder is there anything cause conflict keyboard?
When the TextField is not yet being drawn on the screen, then becomeFirstResponder() will not work. For example when it was hidden and never drawn. Then you need to call becomeFirstResponder() after it has been drawn. Maybe this will help:
DispatchQueue.main.async {
tf.becomeFirstResponder()
}
I have created a new project and tried your code. It works normally on both simulator and device iOS11. Keyboard is showed and notification is called. This is the code i have tried.
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UITextField* tf = [[UITextField alloc] initWithFrame:CGRectMake(10, 200, 300, 40)];
tf.borderStyle = UITextBorderStyleRoundedRect;
tf.text = #"test";
[self.view addSubview:tf];
if ([tf canBecomeFirstResponder]) {
[tf becomeFirstResponder]; // surely this line is called
}
}
- (void)keyboardWillShow:(NSNotification*)note {
NSDictionary* userInfo = [note userInfo];
CGSize kbSize = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
NSLog(#"Keyboard Height: %f Width: %f", kbSize.height, kbSize.width);
}
#end
You should check DLog method. I think keyboard's notification is called but nothing is logged because of DLog method.
Try this one. I feel textField object is getting null in completion block and your calling becomeFirstResponder on that.
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 215, 30)];
textField.backgroundColor = [UIColor clearColor];
textField.placeholder = #"Enter text to search";
textField.borderStyle = UITextBorderStyleLine;
textField.layer.masksToBounds=YES;
textField.layer.borderColor=[[UIColor lightGrayColor]CGColor];
textField.layer.borderWidth= 1.0f;
textField.returnKeyType = UIReturnKeySearch;
textField.delegate = self;
textField.clearButtonMode = UITextFieldViewModeAlways;
[UIView transitionWithView:self.navigationController.navigationBar
duration:0.55f
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
self.navigationItem.titleView = textField;
} completion:^(BOOL finished) {
[((UITextField *)self.navigationItem.titleView) becomeFirstResponder];
}];
try this;
- (BOOL)textFieldShouldReturn:(UITextField *)theTextField {
[theTextField resignFirstResponder];
return NO;
}
- (void)viewWillAppear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)keyboardWillShow:(NSNotification *)notification {
CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
}
- (void)keyboardWillHide:(NSNotification *)notification {
}
In my case the problem was, that I had 2 UIWindow objects and the one I used to add UI elements was not set as key window, and keyboard window had lower level, so keyboard was covered by my other window. I just had to call makeKeyAndVisible() on the second window. john07's comment helped me figure this out. For further reference check: https://developer.apple.com/library/archive/qa/qa1813/_index.html
It's really weird, because this issue can be fixed after showing a UIAlertView
So when I try to add a UIAlertView with loading indicator (and auto dimiss after few seconds) like this before showing keyboard with above code, it can work well.
Don't know the root cause of my project, but it works.
And it only happens on ios 11.
I basically just need to change tabs when the user taps a button on my custom UIView
This is my UIView implementation
#implementation CustomMenuView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
UIButton *searchButton = [[UIButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 135.0, 40.0)];
searchButton.backgroundColor = [UIColor clearColor];
[searchButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
searchButton.titleLabel.font = [UIFont systemFontOfSize:15];
[searchButton setTitle:#"Search" forState:UIControlStateNormal];
searchButton.tag = 0;
[searchButton addTarget:self action:#selector(menuItemTapped:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:searchButton];
}
return self;
}
-(void)menuItemTapped:(UIButton*)sender{
self.tabBarController.selectedIndex = sender.tag;
}
And my ViewController class:
UIView *menuView;
- (void)viewDidLoad
{
[super viewDidLoad];
CGFloat height = [UIScreen mainScreen].bounds.size.height;
menuView = [[CustomMenuView alloc]initWithFrame:CGRectMake(0, 60, 135, height)];
[self.view addSubview:menuView];
}
This crashes because the UIView does not have a reference to the tabBarController. How do I call a method on my custom view's parent or what is the best approach to solve this?
To the best of my knowledge, you could use the delegate pattern here. So you create a protocol named CustomMenuViewDelegate and declare a weak property on CustomMenuView of this type. When the menuItemTapped: method is called you call a method on the CustomMenuViewDelegate property. You can make your ViewController conform to the delegate protocol and set is as the delegate in the viewDidLoad method.
I ended up deciding to use local notifications like this:
On my custom UIView
-(void)menuItemTapped:(UIButton*)sender{
NSDictionary* userInfo = #{#"tab": #(sender.tag)};
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:#"MenuItemSelected" object:self userInfo:userInfo];
UIViewController class
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveMenuItemSelectedNotification:)
name:#"MenuItemSelected"
object:nil];
}
-(void) receiveMenuItemSelectedNotification:(NSNotification*)notification
{
if ([notification.name isEqualToString:#"MenuItemSelected"])
{
NSDictionary* userInfo = notification.userInfo;
NSNumber* tab = (NSNumber*)userInfo[#"tab"];
NSLog (#"Successfully received test notification! %i", tab.intValue);
self.tabBarController.selectedIndex = tab.intValue;
}
}
- (void)viewDidUnload
{
[super viewDidUnload];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"MenuItemSelected" object:nil];
}
We do have an input bar at the bottom of the screen and want to keep the keyboard up when switching the UIViewController. The keyboard get dismissed automatically and i couldn't find anything to prevent from this.
How do we prevent the keyboard from being dismissed when we change the UIViewController with a UIStoryboardSegue?
There is a property called disablesAutomaticKeyboardDismissal that you need to set to false to prevent this from happening.
There is a way to keep a keyboard on:
- (void)viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(coverKey) name:UIKeyboardDidShowNotification object:nil];
[super viewDidLoad];
}
- (void)coverKey {
CGRect r = [[UIScreen mainScreen] bounds];
UIWindow *myWindow = [[UIWindow alloc] initWithFrame:CGRectMake(r.size.width - 50 , r.size.height - 50, 50, 50)];
[myWindow setBackgroundColor:[UIColor clearColor]];
[super.view addSubview:myWindow];
[myWindow makeKeyAndVisible];
}
For an iPhone app with users entering their data into text boxes,
I need the page to scroll both when the text boxes are selected for user to input and when keyboard has not been opened for user to review what they entered.
When using the Interface Builder and overlaying the scroll view, I can't get it to stay or save so that the page is actually scrollable.
I would also prefer to do this programmatically. Other solutions like this have not worked when inserted into the .m file.
#property (weak, nonatomic) IBOutlet UIScrollView *topScrollView;
#synthesize topScrollView;
[topScrollView setFrame:CGRectMake(320, 0, 320, 65)];
[topScrollView setContentSize:CGSizeMake(500, 100)];
[topScrollView setBackgroundColor:[UIColor greenColor]];
[topScrollView setScrollEnabled:YES];
[topScrollView setShowsHorizontalScrollIndicator:YES];
[topScrollView setShowsVerticalScrollIndicator:NO];
[[self view] addSubview:topScrollView];
You can place a UIView with all your textfields on top of the main view and set NSNotifications for the keyboard and move the view around accordingly.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardDidShowWithNotification:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardDidHideWithNotification:) name:UIKeyboardWillHideNotification object:nil];
#pragma mark NSNotifications
- (void)keyboardDidShowWithNotification:(NSNotification *)aNotification
{
const int movementDistance = ([UIScreen mainScreen].bounds.size.height == 568 ? 155 : 180);
[UIView animateWithDuration:0.3
delay:0
options:UIViewAnimationCurveEaseOut|UIViewAnimationOptionBeginFromCurrentState
animations:^{
CGPoint adjust = CGPointMake(0, -movementDistance);
CGPoint newCenter = CGPointMake(self.loginContainer.center.x+adjust.x, self.loginContainer.center.y+adjust.y);
[self.loginContainer setCenter:newCenter];
}
completion:nil];
}
- (void)keyboardDidHideWithNotification:(NSNotification *)aNotification
{
[UIView animateWithDuration:0.3
delay:0
options:UIViewAnimationCurveEaseOut|UIViewAnimationOptionBeginFromCurrentState
animations:^{
self.loginContainer.frame = CGRectMake(0, 0, self.loginContainer.frame.size.width, self.loginContainer.frame.size.height);
}
completion:nil];
}
I have been trying to incorporate a UIView/Toolbar above my keyboard but have had no luck. When I added a toolbar it was scrambled so thus I need to put it into a UIView but the UIView does not want to appear above the keyboard. Code Below:
My Header:
#property (nonatomic, Strong) IBOutlet UITextView *textView;
#property (nonatomic, strong) IBOutlet UIToolbar *TitleBar;
#property (nonatomic, weak) IBOutlet UIView *AddView;
The ViewDidLoad:
- (void)viewDidLoad
{
// observe keyboard hide and show notifications to resize the text view appropriately
/*[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
*/
if ([self respondsToSelector:#selector(setNeedsStatusBarAppearanceUpdate)]) {
// iOS 7
[self performSelector:#selector(setNeedsStatusBarAppearanceUpdate)];
} else {
// iOS 6
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide];
}
self.attributionTitle.delegate = self;
self.attribution.delegate = self;
textView.scrollEnabled = YES;
// quoteText.layer.borderColor = [UIColor blackColor].CGColor;
// quoteText.layer.borderWidth = 1.0f;
// textView.delegate = self; // code or in IB
[textView becomeFirstResponder];
[super viewDidLoad];
// Do any additional setup after loading the view.
}
The textViewDidBeginEditing:
-(void)textViewDidBeginEditing:(UITextView *)textView
{
self.textView.inputAccessoryView = self.AddView;
}
Here is to show the UIView is connected:
I added the textView.inputAccessoryView = AddView;to the ViewDidLoadthen deleted the view from my storyboard and remade it. Lastly I added the UIView to the bottom black bar.
Adding the inputAccessoryView in textViewDidBeginEditing is probably too late. The input accessory view should be set before that, e.g., in the viewDidLoad method.
Try something like:
-(void)viewDidLoad{
[super viewDidLoad];
UIView
myTextField.inputAccessoryView = [self accessoryViewWithPreviousEnabled:NO nextEnabled:YES];
// more stuff as required...
}
And a method for creating a previous/next button (you'll need to provide your own images for the buttons and implements the previousAccessoryViewButtonTapped: and previousAccessoryViewButtonTapped: methods). It takes two BOOL parameters to indicate if the previous and/or next buttons should be enabled.
#pragma mark - Accessory view methods
-(UIView *)accessoryViewWithPreviousEnabled:(BOOL)previousEnabled nextEnabled:(BOOL)nextEnabled{
previousButton = [UIButton buttonWithType:UIButtonTypeCustom];
previousButton.frame = CGRectMake(10, 2, 60, 30);
[previousButton setImage:[UIImage imageNamed:PREVIOUS_BUTTON] forState:UIControlStateNormal];
previousButton.enabled = previousEnabled;
[previousButton addTarget:self action:#selector(previousAccessoryViewButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
nextButton = [UIButton buttonWithType:UIButtonTypeCustom];
nextButton.frame = CGRectMake(80, 2, 60, 30);
[nextButton setImage:[UIImage imageNamed:NEXT_BUTTON] forState:UIControlStateNormal];
nextButton.enabled = nextEnabled;
[nextButton addTarget:self action:#selector(nextAccessoryViewButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
UIView *transparentBlackView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1024, 34)];
transparentBlackView.backgroundColor = [UIColor colorWithRed:0.f green:0.f blue:0.f alpha:0.6f];
UIView *accessoryView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1024, 34)];
[accessoryView addSubview:transparentBlackView];
[accessoryView addSubview:previousButton];
[accessoryView addSubview:nextButton];
return accessoryView;
}
Note this method is hard coded for an iPad in landscape orientation. You need to change it for an iPhone.
The problem is that your self.AddView is already in your interface (because you put it there, in the storyboard). It can't be in two places at once.