Managing vertical scrolling in a UIWebView - ios

I try to get a content editable UIWebView with a "normal" scrolling operation, so that when the text cursor is going to be hidden by the keyboard, the UIWebView scrolls to prevent that. I read the "Managing the Keyboard" document from iOS developer library and got no result, using the scrollView property of my UIWebView. I also found numerous tricks on the web, using ScrollView properties/methods or javascript commands but cannot obtain a normal scrolling operation, like any NSTextView does on MacOS for example. Do you know any solution to this problem?
[EDITED] Got the solution, and created a method fired by a NSTimer. To get the caret Y position, one should get the selection of the active element, and insert a dummy node. Then getting the node.offsetTop property gives the caretY. Do not forget the remove the node...

Ok, here is the way Apple does it in their examples (I no longer remember what project name that was):
1.You register for keyboard notifications(the keyboard send a notification every time it shows up or it gets hidden):
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
2.Get the keyboard's size (you are interested in the height) and scroll if that point is not contained in the view:
- (void)keyboardWasShown:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
// If active text field is hidden by keyboard, scroll it so it's visible
CGRect aRect = self.view.frame;
aRect.size.height -= kbSize.height;
CGPoint point;
point = CGPointMake(0, activeTextField.frame.origin.y + activeTextField.frame.size.height);
if (!CGRectContainsPoint(aRect, point ))
{
CGPoint scrollPoint = CGPointMake(0.0, activeTextField.frame.origin.y+activeTextField.frame.size.height-kbSize.height);
[scrollView setContentOffset:scrollPoint animated:YES];
}
Don't forget do reverse things when hiding keyboard and remove the observers if you push to another view controller.
This example is used with text fields on a UIScrollView but I'm sure you can easily adapt it.
Personally if I also manage the web side, I prefer to create the webpages 320px wide, set my webView sizeToFit, add it on top of a UIScrollView and manage the scrolling from there. For textFields, I simply get the container for the selected textField using javascript and get it's coordinates.

Related

Is there any possible ways/delegates to identify the state of input accessory view of a UITextView's keyboard

In my native iOS app, I have a screen that contains a simple textview. I need to adjust the size/frame of the text view when keyboard appears. I've succeeded it with UIKeyboardDidShowNotification as below:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myKeyBoardIsOnScreen:) name:UIKeyboardDidShowNotification object:nil];
And setting the frame on:
- (void)myKeyBoardIsOnScreen:(NSNotification*)notification {
NSDictionary* keyboardInfo = [notification userInfo];
NSValue* keyboardFrameBegin = [keyboardInfo valueForKey:UIKeyboardFrameBeginUserInfoKey];
CGRect keyboardFrameBeginRect = [keyboardFrameBegin CGRectValue];
self.textView.frame = CGRectMake(self.textView.frame.origin.x, self.textView.frame.origin.y, self.textView.frame.size.width, self.view.frame.size.height-keyboardFrameBeginRect.size.height-self.textView.frame.origin.y);
}
Problem: This looks ok for the first moment. But later I realised that the frame of the keyboard is with the height of its accessory view included. So when I hide the accessory view by dragging it down, the textview appears to be broken.
Hence can anyone suggest me any possible ways/delegates to identify the state of input accessory view of a textview's keyboard (like: Input accessory view is shown/hidden,etc.)
NB: I need the accessory view. Hence I don't need to remove it.
Register as an observer for the UIKeyboardDidChangeFrameNotification to update your view's frame.
Apple Docs

center UIActivityIndicator when keyboard is active

I have a UITableView with a search bar and a search result display controller:
[self.searchDisplayController.searchResultsTableView addSubview:searchIndicator];
searchIndicator.center = CGPointMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0);
The code above places the indicator at the center of the screen. However I want to place the indicator at the center of the frame excluding the keyboard. How can I do this?
The indicator is defined like this:
#property (retain, nonatomic) IBOutlet UIActivityIndicatorView *searchIndicator;
#SaurabhPrajapati has the right idea in his comment. You'll need to subscribe to one of the keyboard willShow/didShow notifications (UIKeyboardDidShowNotification or
UIKeyboardWillShowNotification) and when you get a keyboard notification, collect information about the keyboard height from the Keyboard Notification User Info Keys (Search in the Xcode help system on that string for more information.) Save the keyboard height to an instance variable, and then when you get ready to display your activity indicator, use the keyboard height to adjust the position of the indicator as outlined in Saurabh's comment.
add this in viewDidLoad
//KeyBoard Helpers
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyBoardWillShow:) name:UIKeyboardWillShowNotification object:nil];
Then implement this method
-(void)keyBoardWillShow:(NSNotification*)notification
{
NSDictionary *userInfo = [notification userInfo];
CGRect keyboardFrame = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGFloat keyBoardHeight = keyboardFrame.size.height;
searchIndicator.center = CGPointMake(self.view.frame.size.width / 2.0, self.view.frame.size.height - keyBoardHeight/ 2.0);
}
You could just add a constraint to the top of your UIActivityIndicatorView and inside the to notifications of your keyboard. When keyboard will be shown, you shrink it by the keyboard height, if keyboard will be hide, you add the keyboard height again to the top constraint.

I set scroll view offset to show text field hidden by keyboard. If the user scrolls while keyboard is show, scroll view snaps back down

As the title says, I have a UITextField inside a UIScrollView. When the keyboard is shown, I adjust the contentOffset of the scroll view so that the text field is hidden. The issue is if the text field is at the bottom of the scroll view. When the keyboard pops up, the scroll view adjusts as needed. But, if the user touches and scrolls the area above the keyboard, then the scroll view snaps back down. Intuitively, this makes sense because I've programatically over-scrolled the scroll view, but from a user perspective it is not nice.
What can I do about this? One thing I've thought of is to move the entire scroll view frame instead of setting the content offset. I don't know how to do this. I have the desired change in offset stored in a CGFloat. Can someone help?
You need to change the contentInset. The contentOffset is the current scroll position so when the user scrolls it gets reset.
An example of this can be found here: https://stackoverflow.com/a/16806736/78496
One thing you could do is listen to UIKeyboardWillShowNotification and UIKeyboardWillHideNotification system notifications to know when to modify the contentInset of your UIScrollView. You could do this at the viewWillAppear:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
Don't forget to remove yourself as an observer too,
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
When the keyboard will show or hide you can adjust the contentInset given the keyboard's height.
- (void)keyboardWillShow:(NSNotification *)notification {
CGRect keyboardEndFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
UIEdgeInsets scrollInsetWithKeyboard = UIEdgeInsetsMake(0, 0, 0, -keyboardEndFrame.height, 0)
self.scrollView.contentInset = scrollInsetWithKeyboard; // If you have a custom inset maybe now would be a good idea to save it so you can restore it later
}
- (void)keyboardWillHide:(NSNotification *)notification {
self.scrollView.contentInset = UIEdgeInsetsZero; // Or to whatever inset you had before
}
When those two methods are fired you could also animate the contentOffset if you'd like.
You should use this library : https://github.com/hackiftekhar/IQKeyboardManager
It is really awesome, you have only to add this lib in your project and it will manage all your textfields. You have zero line of code to do to implement this lib, it is automatic. I use it in all my project and it works fine everywhere (for textfield in a cell, tableview, scrollview...)

Objective-C: detect keyboard size/origin *before* it opens?

How is this done? I'm looking for iOS7/8 solutions. keyboardWillShow is not satisfactory because I need to resize a view based on the keyboard height before the keyboard actually shows.
keyboardWillShow is fired before the keyboard is shown. If that's not satisfactory for you, then you'll need to be smart about the keyboard size.
If the keyboard has never been shown on screen in your app before, you can make an educated guess by first checking for the device type and orientation and then having a quick lookup table of the default keyboard sizes. This will cover you 99% of the time.
In the event that the user has a custom keyboard in use that is not a standard size, you can use the keyboard size from keyboardWillShow, store it and the orientation (NSUserDefaults would work well here) and then reference the stored value the next time you need the size.
This wouldn't cover your needs every time because you wouldn't know which keyboard is going to be pulled up until keyboardWillShow is called. For example, you could replace the inputView on two different UITextField's with your own custom views; those views could be different sizes. You wouldn't know which one was going to be shown until keyboardWillShow would be called.
EDIT
There is another possibility...if you know the view that you want to show the keyboard for explicitly.
I added this to viewDidLoad:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShowFirstTimeNotification:)
name:UIKeyboardWillShowNotification
object:nil];
[self.view addSubview:self.textField];
[self.textField becomeFirstResponder];
Then, add a method for handling that notification. This method should only be called once and then inside of it remove the notification so it's never called again.
- (void)keyboardWillShowFirstTimeNotification:(NSNotification*)notification {
NSDictionary* keyboardInfo = [notification userInfo];
NSValue* keyboardFrameBegin = [keyboardInfo valueForKey:UIKeyboardFrameBeginUserInfoKey];
CGRect keyboardFrameBeginRect = [keyboardFrameBegin CGRectValue];
NSLog(#"keyboardFrameBeginRectHeight: %f", keyboardFrameBeginRect.size.height);
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow)
name:UIKeyboardWillShowNotification
object:nil];
[self.textField resignFirstResponder];
}
This will log the keyboard height without ever showing it on screen.
If you wanted to extend this further, you could subclass UITextField and UITextView to have properties for keyboard height for different orientations and then you could store that value directly in the text fields and text views. Then, you'd be able to have multiple input view sizes and know what they will be prior to showing them.
Currently the time for the keyboard to show is 0.3 seconds, but Apple may change that at any time. This is also true for the keyboard size. The default keyboard in portait mode is 216px in height and in landscape it is 162px, but that may also change at any time. If (for any reason) you need to find out the keyboard size you can do that pretty easily.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
// Read the userInfo for the key UIKeyboardFrameBeginUserInfoKey
-(void)keyboardWillShow:(NSNotification*)notification {
NSDictionary* keyboardInfo = [notification userInfo];
NSValue* keyboardFrameBegin = [keyboardInfo valueForKey:UIKeyboardFrameBeginUserInfoKey];
CGRect keyboardFrameBeginRect = [keyboardFrameBegin CGRectValue];
NSLog(#"%#", NSStringFromCGRect(keyboardFrameBeginRect));
}

iOS view move down automatically after keyboard disappear

I had a iPad project, inside one of its view controllers, there are two TextFields inside a panel view (Views are build in Storyboard). What I would like to achieve is when any of those textfield become first responder (i.e. Keyboard Appears), the panel view will move up and if keyboard disappear it will move down to origin position.
Once I test move up, I found the view will automatically move back to origin position after keyboard disappear, but I didn't write any code to do that.
Code to move up:
- (void)moveUpTextFields {
if ([self.emailTextField isFirstResponder] || [self.passwordTextField isFirstResponder]) {
CGRect frame = self.textFieldPanel.frame;
frame.origin.y -= 50;
self.textFieldPanel.frame = frame;
}
}
So I would like to figure out how does that happen, and how should I achieve my goal (i.e. Move up if keyboard appear and back to original position)?
UPDATE:
To achieve my goals, it should use Keyboard Notification. Register these notifications in viewWillAppear
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
I did this in another project long time ago, just didn't remember it at the first place :(
You have to listen to the keyboard events in the Notification Center, and lucky you, this is a functionality almost every app needs, so check this :)
https://github.com/aceontech/KBKeyboardHandler
It does exactly what you need but I prefer if you would understand how it behaves (registering for system notifications).
It's also available through cocoapods if you want
pod 'KBKeyboardHandler'
OK, so the weird behaviour is because when Keyboard Appear or Disappear, it will relayout view controller's sub views and trigger -(void)viewDidLayoutSubviews and reposition view back to StoryBoard's position. If the view is created by code then this won't be a issue.
In addition to add notifications for KeyBoard, we also need to reposition the view in viewDidLayoutSubviews since the notification triggered before layout subviews.
- (void)keyboardWillShow:(NSNotification *)notification {
CGSize keyboardSize = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
CGFloat y = self.view.frame.size.height - keyboardSize.height - self.textFieldPanel.frame.size.height - 5;
CGRect frame = self.textFieldPanel.frame;
panelViewYOffset = frame.origin.y - y;
frame.origin.y -= panelViewYOffset;
// Since it will layout subviews in viewDidLayout, why we set frame here?
// I tried to move this line, turns out the panel will move up but the animation is gone
// So this line will have a nice liner animation curve but I don't why, may be someone can explain it.
self.textFieldPanel.frame = frame;
}
- (void)keyboardWillHide:(NSNotification *)notification {
CGRect frame = self.textFieldPanel.frame;
frame.origin.y += panelViewYOffset;
self.textFieldPanel.frame = frame;
panelViewYOffset = 0;
}
In viewDidLayoutSubviews:
- (void)viewDidLayoutSubviews {
CGRect frame = self.textFieldPanel.frame;
frame.origin.y -= panelViewYOffset;
self.textFieldPanel.frame = frame;
}
Like I mentioned, this approach only works for view from StoryBoard (Interface Builder), if self.textFieldPanel is created by code, then the code in viewDidLayoutSubviews should be removed.

Resources