UIScrollView xcode 8 swift 3 - ios

This is my first question and I am brand new to coding.
I'm creating an app and one of the pages has a form for a user to fill out with two labels, two textfields, a switch and a "send" button.
For smaller screen sizes, some elements were disappearing, so I used a stackview, but when the keyboard comes up, it blocks the textfield which will be annoying to the user, I'm sure.
I've seen a few tutorials about adding scrollviews, but setting the height so everything fits nicely for smallest devices leaves an uncomfortably large blank area at the bottom of larger screens.
Is there a way to enable the scroll view only when it is needed? Is this what "dynamic" is for?

If your issue is with UI formatting on different sized screens, auto layout is a built in feature in xcode that addresses this. By adding constraints to your views, autolayout will do its best to resize your UI elements so that what you see in storyboard is what you get across all devices. Using this technique you may be able to avoid a scrollview altogether. Try selecting all your views, then go to Editor -> Resolve Auto Layout Issues -> Reset to Suggested Constraints, and see if that works.

Seems that you have two questions. First one is "For smaller screen sizes, some elements were disappearing". That one is answered by #Dalton Sweeney. Use Auto Layout then Reset to Suggested Constraints
I am going to answer your second question, what you are supposed to do when the keyboard covers some UIElemnts.
You don't need to change view controller, you should dynamically change view's frame when the keyboard hides/appears. I made a demo in GitHub, check the full version if you want:https://github.com/EricZhang90/IOS_Demo/tree/master/KeyboadDemo
The main workflow is following:
1) create two notifications to detect the keyboard is about to hide/appear:
- (void)viewDidLoad {
[super viewDidLoad];
NSNotificationCenter *ctr = [NSNotificationCenter defaultCenter];
[ctr addObserver:self
selector:#selector(moveKeyboardInResponseToWillShowNotification:)
name:UIKeyboardWillShowNotification
object:nil];
[ctr addObserver:self
selector:#selector(moveKeyboardInResponseToWillHideNotification:)
name:UIKeyboardWillHideNotification
object:nil];
}
2) adjust view's frame
The method takes two parameters: the first is keyboard information sent from notification center. You can get it from the method in first step. The second parameter indicates this adjustment is about to reduce or expand frame. Its value is 0 when the keyboard is about to hide, and is frame of keyboard when the keyboard is about to appear.
-(void)adjustView: (NSDictionary *)info :(CGRect)rect{
// get duration of keyboard appears/hides
CGFloat duration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
// get keyboard's curve
UIViewAnimationCurve curve = [[info objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];
[self.view layoutSubviews];
// Start to adjust frame:
// Animate:
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:duration];
[UIView setAnimationCurve:curve];
[UIView setAnimationBeginsFromCurrentState:YES];
// self.buttonLayoutConstraint is the layout constraint between bottom of view and bottom of screen(super view).
// This is the most important part.
self.buttonLayoutConstraint.constant = rect.size.height;
[self.view layoutSubviews];
[UIView commitAnimations];
}

This is what ended up working for me in terms of moving the scrollview up and out of the way when the textView was selected and activated the keyboard.
func textViewDidBeginEditing(_ textView: UITextView) {
scrollView.setContentOffset(CGPoint(x: 0, y: 50), animated: true)
}
**The 50 value was just what worked for my layout...that number may be different for you.
I also put in:
func textViewDidEndEditing(_ textView: UITextView) {
scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: true)
}
...so that the scrollview returned to normal once the user is done editing.

Related

UITextView disappears behind keyboard after typing a while

We're trying to support iOS 9 for a while yet... That's the only place this is happening...
I have a UITextView that occupies the entire view, which occupies the entire screen. I register to receive UIKeyboardWillShowNotification and UIKeyboardWillHideNotification. I get those just fine and adjust the height of my UITextView so that it is above the keyboard:
- (void) keyboardWillShow:(NSNotification *)aNotification
{
CGRect keyboardRect = [[[aNotification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect bounds = self.view.bounds;
bounds.size.height = bounds.size.height - keyboardRect.size.height;
self.textView.frame = bounds;
}
Again, this works great. Now I type a few lines. For a while, the bottom line of text is kept above the keyboard automatically as I add more lines of text. But eventually, usually within just a few lines (maybe 4-5), the next line will go under the keyboard and I can continue typing lines under the keyboard. Eventually the lines I can still see above the keyboard will start scrolling up as I hit the bottom of the screen with the lines I'm typing behind the keyboard.
I am not getting the UIKeyboardWillHideNotification during this process, but it is almost behaving like I am -- that is, the size of my UITextView appears to return to its original size before the keyboard was shown.
All of this works fine in iOS 11. That is, as I type, the text scrolls up so the bottom line of text never goes behind the keyboard. It's just iOS 9. Anybody remember a known issue back in those days, and a work-around?
My best guess is that you're mixing Autolayout w/ Autoresizing and it seems to be picking the intended one in later iOS's but not in iOS 9.
A quick sample using your code + auto layout constraints in the storyboard resulted in what I believe to be the behavior you're seeing in older iOS's (iOS 9.3 Simulator shown below):
If I roll with a pure auto layout approach (i.e. IBOutlet the textView's bottom constraint and adjust the constant for that programmatically in keyboardWillShow) then it results in the correct behavior on iOS 9 (no setFrame call):
#property (strong, nullable) IBOutlet NSLayoutConstraint *textViewBottomConstraint;
...
- (void)keyboardWillShow:(NSNotification *)aNotification {
CGRect keyboardRect = [[[aNotification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
self.textViewBottomConstraint.constant = -keyboardRect.size.height;
}

TextField attach to keyboard while editing - how to make the ScrollView appear same as before editing?

I created new Xcode single view project. In interface builder, I added a UIScrollView to cover complete view. Over this scrollview, I added a UITextField. The resulting UI looked like this:
Note that the scrollview does not scroll at this point as the content takes only the size of view and not greater than it.
Now for bringing UITextField on top of keyboard while editing, I followed the way described by Apple on Managing The Keyboard page. After doing this, it gave me expected behavior which brought the text field right above keyboard on editing begin as shown in following screenshot:
Now, after calling [textfield endEditing:YES], the keyboard hides itself, but the textfield does not return to its original place. It return to the place just little above its original place and now the scroll view becomes scrollable as if little height was added to it:
Notice the scroll bar in the above screenshot!
I want help in bring back the original behavior of the view after editing ends (when keyboard hides) i.e. the textField to return to exact same place and scroll should be happening as it was not happening before editing begin.
Project URL: - https://github.com/therohansanap/keyboard.git
You need adjust scrollview contentOffset textFieldDidBeginEditing and textFieldDidEndEditing.
or
One controller is available for scrollview auto scroll.
https://github.com/simonbs/BSKeyboardControls
I think the official way specified by Apple here is easiest and best way to keep this functionality working.
You can do something similar without Using Keyboard notifications as well. As you may know that we have TextField delegate methods , we can use those to set scrollView contentOffset and acquire the same behaviour
- (void)textFieldDidBeginEditing:(UITextField *)textField{
scrollView.contentOffset = CGPointMake(0, textField.center.y-80); // you can change 80 to whatever which fits your needs
}
the above method sets contentOffset Value of scroll view and your textFiled moves up, while the textField resignFirstResponder the below delegate method gets called, where you can set back the contentOffset value
- (void)textFieldDidEndEditing:(UITextField *)textField{
scrollView.contentOffset = CGPointMake(0,-80);
}
Note: you need to make every text field in your view to have their delegate as your UIViewController instance. You also need need your UIViewController to adopt UITextFieldDelegate
Took a look at your code, you dont need to change the content insets etc when trying to position the scroll view. You just need to modify the content offset property.
Here is the modified code :
#interface ViewController () {
UITextField *activeField;
CGPoint scrollViewOldPosition;
}
Modify the keyboardWasShow as follows :
// Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWasShown:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
CGFloat someSpaceBetweenKeyBoardAndField = 20.0;
scrollViewOldPosition = self.scrollView.contentOffset;
self.scrollView.contentOffset = CGPointMake(0, kbSize.height - (self.view.frame.size.height - activeField.frame.origin.y - activeField.frame.size.height) + someSpaceBetweenKeyBoardAndField);
}
Keyboard will be hidden method :
// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
self.scrollView.contentOffset = scrollViewOldPosition;
}
Not the best code ever but it have some more features u can use, anything with _ is global variable
//Handle notification when keyboard appear
- (void)keyboardOnScreen:(NSNotification *)notification
{
if (_isKeyboardShow) {
return; //If keyboard is showing then return
}
_keyboardHeight = [[notification.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
[self animateTextFieldUp: YES];
_isKeyboardShow = YES;
}
//Handle notification when keyboard hide
- (void)keyboardOffScreen:(NSNotification *)notification
{
if(!_isKeyboardShow) return;
[self animateTextFieldUp: NO];
_isKeyboardShow = NO;//Missed this line
}
//Push view up with animation when keyboard show
- (void) animateTextFieldUp: (BOOL) up
{
UITextField *textfield = [UIResponder currentFirstResponder];
CGPoint windowPoint = [textfield convertPoint:textfield.bounds.origin toView:self.view];
int movementDistance;
CGPoint point = [_mainScrollView contentOffset];
//Push up only when blocked by keyboard
if (windowPoint.y + textfield.frame.size.height >= self.view.frame.size.height - _keyboardHeight) {
movementDistance = windowPoint.y - (self.view.frame.size.height - _keyboardHeight) + textfield.frame.size.height + 10;
_oldMovementDistance = movementDistance;
int movement = (up ? -movementDistance : movementDistance);
[_mainScrollView setContentOffset:CGPointMake(0, point.y - movement) animated:YES];
}
else { //Push view down the same amount
int movement = (up ? -movementDistance : _oldMovementDistance);
[_mainScrollView setContentOffset:CGPointMake(0, point.y - movement) animated:YES];
_oldMovementDistance = 0;
}
}
move UITextField and UITextView out of the way of the keyboard during editing:
For non-UITableViewControllers, drop the TPKeyboardAvoidingScrollView.m and TPKeyboardAvoidingScrollView.h source files into your project, pop a UIScrollView into your view controller's xib or storyboard, set the scroll view's class to TPKeyboardAvoidingScrollView, and put all your controls within that scroll view. You can also create it programmatically, without using a xib - just use the TPKeyboardAvoidingScrollView as your top-level view.
For use with UITableViewController classes, drop TPKeyboardAvoidingTableView.m and TPKeyboardAvoidingTableView.h into your project, and make your UITableView a TPKeyboardAvoidingTableView in the xib. If you're not using a xib with your controller, I know of no easy way to make its UITableView a custom class: The path of least resistance is to create a xib for it.
You can get reference from here.
Hope this helps.

How can I make a table view appear when a button is pressed/tapped?

I am trying to make a UITableView appear when a user taps on a button and disappear when the button is re-tapped.
I implemented the following code but nothing seems to appear when I tap the button.
- (IBAction)dropDown:(UIButton *)sender
{
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.6];
CGAffineTransform transfrom = CGAffineTransformMakeTranslation(0, 200);
self.markersTableView.transform = transfrom;
self.markersTableView.alpha = self.markersTableView.alpha * (-1) + 1;
[UIView commitAnimations];
}
What may potentially be the issue?
EDIT:
I was able to make the UITableView appear and disappear by adding self.markersTableView.hidden = YES; in viewDidLoad() and self.markersTableView.hidden = NO; in the IBAction method.
However, the table view disappears when I initially tap on the button as shown in the screenshot:
The fading of the rows is an indication it is moving down the screen, and then it disappears.
It only reappears when I re-tap on the UIButton the 2nd time.
Any clues?
Don't use a transform on your table view. That will make it LOOK like it's in a different position, but it won't respond to taps correctly.
Instead, use the newer UIView block-based animation methods and change the view's center.
The code might look like this (if you're NOT using auto-layout)
[UIView AnimateWithDuration: .2
animations: ^
{
CGPoint center = self.markersTableView.center;
center.y += 200;
self.markersTableView.center = center;
}
];
If you're using auto-layout, though, that won't work and you will need to animate the view's position using constraints.
From memory, here's an outline of how to do auto-layout based animations: You would create a vertical position constraint on your table view and connect the constraint to an IBOutlet in your view controller. In your animation code you change the constant of the constraint (the vertical position) and then in the animation block, send the view a needsLayout message.

How to Use Autolayout to Animate Resizing a UITextView's Bottom Constraint When Content Has Been Scrolled

Background
I'm working on a quick and dirty notes app purely to try to understand autolayout. As such I am looking for an autolayout-specific solution to this problem.
I am quite sure that my terminology and understanding of this subject may be incorrect in places so if I misphrase or omit information through ignorance that would otherwise be helpful I am very happy to update this question with better specifics.
Short Problem Summary
This app is a simple note app. On the detail view of the note, there are two text input views, a UITextField, and a UITextView.
The goal is to use autolayout to animate a change of height to the UITextView when it is being edited (making room for the keyboard), and then animate the UITextView back to it's original size when editing is finished.
The animation code I have in place works, however when the UITextView is scrolled near to the bottom of the text the animation from "editing" size to "non-editing" size displays incorrectly durring the animation. (The final result of the animation, however is correct.)
I'm open to alternate "correct" ways of doing this if there's a common pattern for the solution. I am, however, looking for an autolayout solution which, I believe, means avoiding modifying the view's frame directly. (Could be wrong on that.)
Details and Code
A short video of the problem is available here:
http://pile.cliffpruitt.com/m/constraint_problem.mp4
This is the code performing the animation:
// self.bodyFieldConstraintBottom refers to an outlet referencing the UITextView's bottom constraint
// This animation occurrs when the text view is tapped
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
{
[self enterEditingMode];
[UIView animateWithDuration:2.35
animations:^{
NSLayoutConstraint *bottom_constraint = self.bodyFieldConstraintBottom;
bottom_constraint.constant = 216;
[self.view layoutIfNeeded];
}];
return YES;
}
// This animation occurrs when editing ends and the text field size is restored
- (BOOL)textViewShouldEndEditing:(UITextView *)textView
{
[self exitEditingMode];
[UIView animateWithDuration:2.35
animations:^{
NSLayoutConstraint *bottom_constraint = self.bodyFieldConstraintBottom;
bottom_constraint.constant = 20;
[self.view layoutIfNeeded];
}];
return YES;
}
Full project source (in all it's messy glory) can be downloaded here:
http://pile.cliffpruitt.com/dl/LittleNotebooks.zip
Additional Comments
My understanding of cocoa terminology isn't the best so I'm having a hard time making google searches and docs searches effective. My best guess about the problem (based on observing the animation at a slow speed) is that it is related to a scroll offset somehow because unless the text is scrolled past a certain point, the problem does not manifest itself.
I have read quite a few SO question/answers including:
Resizing an UITextView when the keyboard pops up with auto layout
How to resize UITextView on iOS when a keyboard appears?
UIScrollView animation of height and contentOffset "jumps" content from bottom
The problem is that these answers either do not work ([self.bodyField setContentInset:UIEdgeInsetsMake(0, 0, 216, 0)]; seems to have no effect) or appear to rely on setting the frame of the UIText view which I believe is not supposed to be done when using autolayout.
Final Side Note
I've been at this off and on for about 4 days so my understanding and recollection of all I've read and tried is actually a little less clear than when I'd started. I hope I'm explaining this well enough to convey the issue.
EDIT:
I've noticed that this code actually gets somewhat close to the desired result:
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
{
[self enterEditingMode];
[UIView animateWithDuration:2.35
animations:^{
[self.bodyField setContentInset:UIEdgeInsetsMake(0, 0, 216, 0)];
[self.view layoutIfNeeded];
}];
return YES;
}
- (BOOL)textViewShouldEndEditing:(UITextView *)textView
{
[self exitEditingMode];
[UIView animateWithDuration:2.35
animations:^{
[self.bodyField setContentInset:UIEdgeInsetsMake(0, 0, 0, 0)];
[self.view layoutIfNeeded];
}];
return YES;
}
The problem with this version is that the scroll indicator scrolls down past the visible area of the text content, meaning it gets "lost" behind the keybaord. Also, it does not help me understand the correct way to animate a UITextView (UIScrollView ?) bottom constraint.
The issue looks weird and I am really not sure whats the main issue but I found out that for the best results you should call [self.view setNeedsUpdateConstraints]; before animating view.
My example code to animate view when keyboard appears:
-(void)keyboardWillShow:(NSNotification *)notification {
CGSize kbSize = [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
//BH: iOS7 is messed up
CGFloat keyboardHeight = kbSize.width;
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"8.0")) {
keyboardHeight = kbSize.height;
}
self.centerYConstraint.constant = keyboardHeight;
[self.view setNeedsUpdateConstraints];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:[notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
[UIView setAnimationCurve:[notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]];
[UIView setAnimationBeginsFromCurrentState:YES];
[self.view layoutIfNeeded];
[UIView commitAnimations];
}
I am using commit animations to animate view with same animationCurve as iOS is animating keyboard so view is moving 1 to 1 accordingly to keyboard. Also, please notice if statement for iOS8 vs iOS7 where Apple finally fixed window sizing.

How to stick a button that is normally below a UIWebView above the keyboard when it opens?

I would like to add a button and some other views below a UIWebView. When the keyboard opens, these views should smoothly animate up while the keyboard rises. The UIWebView should resize smoothly. I currently have the following:
No keyboard:
Keyboard:
It looks like it's working fine, but in reality, this is what it looks like in the middle of resizing:
The UIWebView's bottom jumps up immediately, the keyboard resizes smoothly, and the bottom views lag a little bit behind.
I have the following code:
- (void)registerForKeyboardNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleKeyboardWillChangeFrameNotification:)
name:UIKeyboardWillChangeFrameNotification object:nil];
}
- (void)handleKeyboardWillChangeFrameNotification:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
NSTimeInterval animationDuration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
if (!self.is_keyboard_open) {
self.bottom_constraint_constant = self.bottomConstraint.constant;
self.bottomConstraint.constant = kbSize.height + self.bottomConstraint.constant;
} else {
self.bottomConstraint.constant = self.bottom_constraint_constant;
}
[UIView animateWithDuration:animationDuration animations:^{
[self.view layoutIfNeeded];
}];
self.is_keyboard_open = !self.is_keyboard_open;
}
The bottom constraint is the constraint between the text and the bottom of the screen.
Another issue I have is that when the keyboard is toggled more than once, the UIWebView does not resize correctly when it is opened, causing a partial render, as well as a large gray box at the bottom (which I can scroll up and down along with rest of the content, which would fit normally if this gray box was not present), like so:
What can I do? Thank you for your help!
You should animate the constraint change. You have all the information necessary in the notification user info (UIKeyboardAnimationCurveUserInfoKey, UIKeyboardAnimationDurationUserInfoKey).
Your second issue is a problem with UIWebView, I think. I've had this issue too. In iOS7, Apple seems to attempt setting a content inset for the webview scroll view, but sometimes it fails miserably. I ended up doing nasty tricks to prevent the webview from doing those.
Addendum
How to use the animation curve
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
// ... do stuff here
[UIView commitAnimations];
Webview tricks
Our webview use is even more complex because we use contentEditable=YES to edit HTML content. What we did was taking full control of the internal webview subview.
Normally, the view hierarchy of a UIWebView is:
UIWebView
Private UIScrollView subclass
Private view where the actual content is drawn
We ended up taking the private view and putting it inside our own scroll view which we can manage by ourselves. You need to update the content size as the private view grows or shrinks. We used key-value observation on the frame property to listen to changes to the size. To have the correct content inset, we used the keyboard frame to determine how much the bottom content inset is.
I must postface this, and say that ideally, we should not do this. It's error prone. From experience, we know it works from iOS5 to iOS7.1. I cannot promise it will work on iOS8. But since there are bugs which cannot be resolved otherwise, this is the best solution we found.

Resources