I am using a scroll view that contains a couple of text field. When the text field "begins editing", I perform 3 operations:
I change the scroll view content inset (so that the whole view becomes apparent above the keyboard). I only do that if it's not fixed already to this content inset.
I also change the scroll indicator inset to match the one in 1.
Finally, I change the scroll view content offset to some specific value.
However, a strange thing happens. Once I tap the text field, the scroll view animates to a content offset larger than the specific value, then immediately back to that specific value.
I suspected that both step 1 and 2 are the reasons for that so I excluded them and everything works just fine. However, this left me with part of the scroll view hidden below the keyboard.
EDIT: Here is the code I use (called when the text field starts editing):
UIScrollView *scrollView = (UIScrollView *) self.view;
if (scrollView.contentInset.bottom != C_SCROLL_VIEW_CONTENT_INSET_BOTTOM) {
[scrollView setContentInset:UIEdgeInsetsMake(C_ORIGIN_ZERO,
C_ORIGIN_ZERO,
C_SCROLL_VIEW_CONTENT_INSET_BOTTOM,
C_ORIGIN_ZERO)];
[scrollView setScrollIndicatorInsets:UIEdgeInsetsMake(C_ORIGIN_ZERO,
C_ORIGIN_ZERO,
C_SCROLL_VIEW_CONTENT_INSET_BOTTOM,
C_ORIGIN_ZERO)];
}
if (textField.tag == C_TAG_BUTTON) {
[scrollView setContentOffset:CGPointMake(C_ORIGIN_ZERO, C_ORIGIN_SHIFT_SCROLL_VIEW_FOR_CURRENT_Y)
animated:YES];
return;
}
Assuming that C_SCROLL_VIEW_CONTENT_INSET_BOTTOM is your constant, I think that value is wrong (too big).
Related
So I'm pretty puzzled right now because my UIScrollView is acting pretty weirdly.
Basically, I have a UIScrollView that is about twice the height of an iPhone 6 that I am using to display some graphs using iOS Charts and so I've set it to be able to scroll and bounce vertically, but neither scroll nor bounce horizontally. All of the graphs, and some additional UITextFields and UILabels are embedded on a separate "body view" that is aligned with the frame of the UIScrollView as seems to be common practice. Thus, the hierarchy of my views looks like this:
This worked well until I noticed today that when I press a specific UITextField on this UIScrollView, which triggers a UIPickerView, all of the sudden my scroll view starts to allow horizontal bouncing. This behavior does not occur for the two other UITextField's on the body view when they are tapped.
I've checked all of the code that is being triggered by tapping on the affected text field, and nothing is directly editing the frames or bounds of any UI objects. In fact, the only function called when the text field is tapped on is the textFieldDidBeginEditing. I've attached the code for this function below, but I am fairly certain it is not the problem.
My next suspicion was that the UIPickerView popping up has been messing with the dimensions of my scroll view and/or it's embedded view. I'm not quite sure if this is possible/probable, but this whole thing has left me pretty stumped.
Here's my code:
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == overallTimeframeTextField {
...
// Not the problematic text field
} else if textField == subjectTimeframeTextField {
...
// Also not the problematic text field
} else { // Affected text field
// Set the text of the text field
if textField.text == "" {
// This is executed in this scenario
textField.text = subjectPickerData[0]
} else {
...
}
}
}
Here is a short GIF outlining my issue. You can see me scrolling down the page, where I am not able to bounce horizontally, and then once I tag on the text field, all of the sudden the scroll view allows the bounces.
GIF
I'm pretty lost with this issue, so any help would be appreciated.
Edit
To clarify, I explicitly declare my scrollView's content size to be equal the desired height and the width of the screen that the user is on. I then set the bodyView's width to equal the same value. This is done in viewDidAppear with the following code:
// Fit the content to the screen
scrollView.contentSize = CGSize(width: UIScreen.main.bounds.width, height: 1200)
bodyView.frame.size.width = UIScreen.main.bounds.width
I also have constraints which force the scrollview and body view to both have the same width as the UIViewController's default child view (the parent of the scroll view in my hierarchy).
One interesting thing that I've noticed is that when I print the width of my scroll view and my body view when the views load, I receive the following output for iPhone 6:
385.0
385.0
This is correct as that is the width of an iPhone 6. However, when I tap on the text field, and then print the same values, I get this output:
385.0
384.0
So for some reason, my body view is one point smaller than my scroll view. I've tried setting the body view's width to be equal to the scroll view when I tap on the text field, like I do in the viewDidAppear function, but this had no effect.
In terms of the UIPickerView, I initialize a pickerview with my class instance variables like so:
var subjectPickerView = UIPickerView()
I then assign this picker view to be the input view for the text field in viewDidLoad:
textField.inputView = subjectPickerView
So I'm not sure if this makes the picker view a subview of the scroll view, but it's just replacing the keyboard in this scenario.
Thanks to #AchmadJP's comment, I tried explicitly creating an equal widths constraint between my scroll view and my body view. This seems to have solved the issue.
The reason I had not done this previously was that the body view's leading space, trailing space, top space and bottom space were constrained to be the same as those of the scroll view. Theoretically, this should have meant that the widths were equal at all times, but apparently, that is not the case.
For anyone else with the same problem, you can see this answer for the solution.
I'm building a view that's very similar to the messages app - I have a subview at the bottom of the page with a UITextView in it and as the user types and reaches the end of the line the text view as well as the view containing it should expand upward.
The way I have it working is that in the textViewDidChange: method I call my layout function, and that does
CGFloat textViewWidth = 200;
CGFloat textViewHeight = [self.textView sizeThatFits:CGSizeMake(textViewWidth, 2000)].height;
[self resizeParentWithTextViewSize:CGSizeMake(textViewWidth, textViewHeight)];
// And then the resize parent method eventually calls
textView.frame = CGRectMake(10, 10, textViewWidth, textViewHeight);
The problem is that when typing at the end of line and the view expands, I end up with an arbitrary contentOffset.y of something like 10.5 on the text view so the text is all shifted up to the top of the view. Weirdly, it's alternating on every other line, so expanding the first time leaves the y content offset shifted up, then at the next line it's close to zero, then back to 10.5 on the next line, etc. (not sure if that's helpful or just a strange artifact of my values). I can set it back to zero afterwards but it looks terrible because there's a brief flash where the text has the offset value and then it gets shifted back to the middle.
I've read that it's usually better to use content insets for scroll views rather than changing the frame, but I don't get how to do that because I do need to change the frame size as well.
How can I resize the UITextView without this happening? I think I can get by with setting the text view not to be scrollable and that fixes the issue, but I'd like to understand what's going on.
The problem is that UITextView's scroll animation and your frame setting action were happened at the same time.
UITextView internally scrolls the texts you currently typing to visible when typed one more character at the end of the line or typed the new line character. But the scroll animation does not need because you are expanding the textview. Unfortunately we can't control textview's internal scroll action so the text scrolls to the top of the expanded textview weirdly. And that weird scroll makes unnecessary bottom padding too.
You can avoid this weird action very simply with overriding UITextView's setContentOffset:animated: like this.
Objective-C
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated {
[super setContentOffset:contentOffset animated:NO];
}
Swift
override func setContentOffset(_ contentOffset: CGPoint, animated: Bool) {
super.setContentOffset(contentOffset, animated: false)
}
This code avoids the auto sizing UITextView's unnecessary scroll animations and you can expand the size of the text view freely.
Setting textView.scrollable = NO lets me resize the text view without any strange offsets, that's the only way I've been able to figure out. And I guess it's not too much of a limitation for common scenarios, if you want the text view to be scrollable you probably don't need to resize it on the fly since the user can scroll around as the content changes.
I confronted the same issue: changing the UITextView's frame to fit its content had a side effect on the scroll position being wrong. The UITextView scrolled even when the contentSize was fitting the bounds.
I ended up with setting scrollEnabled to true and with rolling the content offset back if the UITextView is not actually scrollable
override var contentOffset: CGPoint {
didSet {
if iOS8 {
// here the contentOffset may become non zero whereas it shouldn't be
if !isContentScrollable && contentOffset.y > 0 {
contentOffset.y = 0
}
}
}
}
var isContentScrollable: Bool {
let result = ceil(contentSize.height) > ceil(height)
return result
}
Actually, I faced the same issue and found that actually this happens only when UITextView has any Autolayout constraints. If you will try to use UITextView without applying any Constraint then this will not happen and will work fine. but, as you apply Auto layout constraints it automatically scrolls to bottom. To deal with this I just add method
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
self.textView.contentOffset = CGPointZero;
}
I have tableview and several controls (text fields, buttons and labels) beneath it.
When keyboard shows up I can either reduce parent view height or slide it up so my controls are accessible.
That part works fine.
However I want to adjust tableview also, so I tried reducing its height (if I reduce the height of the parent view) or moving down the origin.y coordinate (if I slide parent view up).
Neither worked, i.e. tableView would not change to the new frame, it stays the same. Tableview only resizes if I do not manipulate parent view, i.e. if I adjust tableView frame alone.
Here is the methods for that:
-(void)resizeTbl:(int)pnts{
CGRect tblframe = self.myTable.frame;
//tblframe.origin.y+=pnts;
tblframe.size.height-=pnts;
self.myTable.frame =tblframe;}
-(void )keyboardWillShow:(NSNotification *)notif{
int pnts=160;
CGRect frame = self.view.frame;
//frame.origin.y-=pnts;
frame.size.height-=pnts;
self.view.frame = frame;
[self resizeTbl:pnts];}
I probably could move all the controls up one by one and then table resize would work but I think there should be an easier way. Any advice would be greatly appreciated.
Update/workaround:
Since table resize works alone just fine, I added one more observer - keyboardDidShow, and moved resize myTable from keyboardWilShow into there to take care of tableview after keyboard is up.
-(void)keyboardDidShow:(NSNotification *)notif{
[self resizeTbl:160];}
So all the views come up as they are supposed to now.
However, when focus moves from one textbox to another with the keyboard already up, tableView resizes to its original frame by itself and I cannot catch when and how that does it. And then, when keyboard goes down tableView expands beyond its original frame, because I must have
-(void)keyboardDidHide:(NSNotification *)notif{
[self resizeTbl:-160];}
However, when focus changes again tableView shrinks back to its normal frame and everything looks just fine. If I could somehow prevent those unwanted tableview resizes that mess things up.
If someone could makes sense out all of this I would be very appreciative.
Update:
I got it. Autolayout was messing me up. I turned it off like that and my logic now works, no glitches.
What is the autoResizingMask of the view set to?
Try this when you initialize the table view.
self.myTable.autoresizingMask = UIViewAutoresizingFlexibleHeight;
This will cause the table view to be resized with its parent view.
You can't change self.view.frame. You should have a container view above self.view that holds self.myTable and all other controls you want. self.myTable and other controls should have the correct autoresizingMask.
When keyboard is shown, you resize the container view and all other views will respect their autoresizingMasks.
-(void )keyboardWillShow:(NSNotification *)notif{
int pnts=160;
CGRect rect = self.view.bounds;
rect.size.height-=pnts;
self.containerView.frame = rect;
// the following should not be needed if self.myTableView.autoresizingMask
// is at least UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleTopMargin
// but you can try adding this
self.myTableView.frame = self.containerView.bounds;
}
I have a UIView that has two child elements: a UIScrollView on the upper half (which contains two UILabels), and a UITableView at the bottom. This is basically a dictionary and the purpose of the scroll view is to display the word and definition, and the table view for displaying the related words. Not all words in my dictionary have a related words array associated to them, so I hide the UITableView when that array is empty.
However, I can't get the UIScrollView to fill the entire parent view when the UITableView is hidden. Here's what I've tried so far:
- (void)updateUIWithWord:(NSString *)theWord
andDefinition:(NSString *)theDefinition
andRelatedWordsArray:(NSArray *)theRelatedWordsArray {
self.navigationItem.title = theWord;
self.word.text = theWord;
self.definition.text = theDefinition;
self.relatedWordsArray = theRelatedWordsArray;
if (![relatedWordsArray count]) {
relatedWordsTableView.hidden = YES;
// set the UITableView's width and height to 0 just to be sure
// I feel this isn't needed though
CGRect relatedWordsTableViewFrame;
relatedWordsTableViewFrame.size = CGSizeMake(0, 0);
relatedWordsTableView.frame = relatedWordsTableViewFrame;
// then make the scroll view occupy the remaining height;
// that is, the self.view's actual height
CGRect scrollViewFrame;
scrollViewFrame.origin = CGPointMake(0, 0);
scrollViewFrame.size = CGSizeMake(self.view.frame.size.width, self.view.frame.size.height);
scrollView.frame = scrollViewFrame;
}
}
Simply put, this doesn't work. For any word that has no related words and a very long definition, the scroll view simply occupies the same amount of height even with the table view gone. Help?
ADD: I tried fixing the constraints in the UIScrollView to make it relative to the top of the UITableView instead of having a fixed height, but that doesn't seem possible.
You have an "if" and an "else". Only one of those is going to execute. So when the "if" part runs and relatedWordsTableView.hidden is set to YES, the table view is hidden but nothing else happens. The "else" part isn't running so nothing is happening.
Approached the problem in a different way. I made the UIScrollView occupy the whole screen and put the UITableView inside it, below my two labels, with scrollling disabled. Now I can just hide and show it.
I would like to increase the keyboard height use in your code by 30. I have a toolbar over my keyboard. I play around with the code but I couldn't make it work. the toolbar over keyboard hides the last cell. I changed this line of code:
if ( offset != -1 ) {
[self setContentOffset:CGPointMake(self.contentOffset.x, offset+30) animated:YES]; // this one
}
I works but the rest of the cell don't get center anymore when clicking on it.
Where in your code is the height of the keyboard?
For that, first you have take scroll view. And add table view inside the scroll view.
After that, you have set scroll view height accordingly.
Like Below:
[scrlPage setFrame:CGRectMake(scrlPage.frame.origin.x, scrlPage.frame.origin.y, scrlPage.frame.size.width, 361.0-150.0)];
[scrlPage setContentSize:CGSizeMake(scrlPage.frame.size.width, 361.0)];
[scrlPage setContentOffset:CGPointMake(0.0, 50.0)];
Hope you will get it.
Though let me know in case of any difficulty.