I have a UIScrollView with a 6 textfields in it and a button inside of it. There is not enough content in the scrollView to make it scroll.
But when the keyboard shows, I would like the scrollview to scroll so the user doesn't have to dismiss the keyboard in order to select another textfield that is hidden by the keyboard.
I am using iOS7 and have autolayout enabled.
Any suggestions?
I am using storyboards and the only code I have is the following.
reg.h file
interface registerViewController : UIViewController <UITextFieldDelegate, UIScrollViewDelegate>
In order to make a scrollview scrollable, the content size must be larger than the scrollview's frame so the scrollview has something to scroll to. Use setContentSize to adjust the content size:
[scrollview setContentSize:CGSizeMake(width, height)];
In this case, you should adjust the size to view.frame.width, view.frame.height + keyboard_height, then adjust the content offset once the keyboard appears:
[scrollview setContentOffset:CGPointMake(0, 0 - keyboard_height)];
If for some screwy, autolayout-related reason this still doesn't make the view scrollable, implement this setContentSize function in viewDidLayoutSubviews in order to override the autolayout:
- (void)viewDidLayoutSubviews {
[scrollview setContentSize:CGSizeMake(width, height)];
}
EDIT: To reset the scrollview after dismissing the keyboard, reset the scrollview content size to the scrollview's frame and the offset to zero:
[scrollview setContentSize:CGSizeMake(scrollview.frame.size.width, scrollview.frame.size.height)];
[scrollview setContentOffset:CGPointZero];
P.S. To animate the content offset, use:
[scrollview setContentOffset:offsetSize animated:YES];
There is a contentInset property of UIScrollViews, you can set the contentInset to make additional space at the bottom to allow for scrolling without changing contentSize.
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, 100, 0.0);
scrollView.contentInset = contentInsets;
above code adds 100 points inset at the bottom.
By the way, there is an official document about this matter. It explains everything you should do. You can find it here. You can find what you are looking for under the section 'Moving Content That Is Located Under the Keyboard'
Try
Create Scroll view
Add View to the scroll view (In my case i added view as mainView).
Set ScrollView autoresizing.
Set MainView autoresizing.
To set the Scroll content Size equal to the view created add below line
Add the below line
-(void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.scrollView.contentSize = self.mainView.frame.size;
}
Related
I have a UIViewController (MasterViewController) with a UIScrollView, that has a header view on top, and a container below.
When I embed any childViewController (e.g. the EmbeddedViewController), the contentSize of the UIScrollView obviously needs to be adjusted. In order to adjust the size, I need to wait until the embedded view is layouted correctly and then set
scrollView.contentSize.height = embeddedViewController.tableView.contentSize.height + HeaderView.frame.height;
When (in the MasterViewController) can I update the scrollView.contentSize after embedding any embeddedViewController?
I thought I could call
[embeddedViewController layoutIfNeeded];
... right before I embed it into the masterView, but somehow the scrollView contentSize is not set correctly. Do you have any ideas or a best practice for dynamic length containerViews in UIScrollViews?
give scrollview contentsize in viewDidLayoutSubviews method
-(void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
scrollView.contentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width,350);
}
I have a UITableView with a UIView on top. I want the UIView to stick to the top as the tableView cells scroll over it.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
if (self.tableView.contentOffset.y > 0) {
CGRect newframe = self.publicTopView.frame;
newframe.origin.y = -self.tableView.contentOffset.y;
self.publicTopView.frame = newframe;
NSLog(#"After: %f", self.publicTopView.frame.origin.y);
}
}
You need to set your table view header view to the view you want on top.
Add this code to you viewDidLoad
self.tableView.tableHeaderView = self.publicTopView
I'm not certain what you're trying to accomplish, but I have a guess at what is wrong. As you scroll your contentOffset will continue to change and let's say your tableView has a content size height of 1500, then your contentOffset will eventually be larger than the height of your view controllers view. Now see that you are putting that contentOffset into the origin.y of your publicTopView. So your publicTopView could possibly be moving too much, even offscreen depending on how large your tableview's content size is.
Challenge time!
Imagine we have 2 content views:
UIView with dynamically height content (expandable UITextView) = RED
UIView as a footer = BLUE
This content is inside a UIScrollView = GEEN
How should I structure and handle the constraints with auto-layout to archive all the following cases?
I am thinking next basic structure to start with:
- UIScrollView (with always bounce vertically)
- UIView - Container
- UIView - DynamicHeightContent
- UIView - Sticky Footer
Keyboard handling should be done by code watching notifications UIKeyboardWillShowNotification and UIKeyboardWillHideNotification. We can chose to set the keyboard's end frame height to Container UIView bottom pin constraint or to the UIScrollView bottom contentInset.
Now, the tricky part is the sticky footer.
How we make sure the sticky footer UIView stays at the bottom if there is more screen available than the whole Container View?
How do we know the available screen space when the keyboard is shown/hidden? we'll surely need it.
Is is it right this structure I purpose?
Thank you.
When the text content of the UITextView is relatively short, the content view's subviews (i.e., the text view and footer) will not be able to dictate the size of their content view through constraints. That's because when the text content is short, the content view's size will need to be determined by the scroll view's size.
Update: The latter paragraph is untrue. You could install a fixed-height constraint either on the content view itself or somewhere in the content view's view hierarchy. The fixed-height constraint's constant could be set in code to reflect the height of the scroll view. The latter paragraph also reflects a fallacy in thinking. In a pure Auto Layout approach, the content view's subviews don't need to dictate the scroll view's contentSize; instead, it's the content view itself that ultimately must dictate the contentSize.
Regardless, I decided to go with Apple's so-called "mixed approach" for using Auto Layout with UIScrollView (see Apple's Technical Note: https://developer.apple.com/library/ios/technotes/tn2154/_index.html)
Some iOS technical writers, like Erica Sadun, prefer using the mixed approach in pretty much all situations ("iOS Auto Layout Demystified", 2nd Ed.).
In the mixed approach, the content view's frame and the scroll view's content size are explicitly set in code.
Here's the GitHub repo I created for this challenge: https://github.com/bilobatum/StickyFooterAutoLayoutChallenge. It's a working solution complete with animation of layout changes. It works on different sized devices. For simplicity, I disabled rotation to landscape.
For those who don't want to download and run the GitHub project, I have included some highlights below (for the complete implementation, you'll have to look at the GitHub project):
The content view is orange, the text view is gray, and the sticky footer is blue. The text is visible behind the status bar while scrolling. I don't actually like that, but it's fine for a demo.
The only view instantiated in storyboard is the scroll view, which is full-screen (i.e., underlaps status bar).
For testing purposes, I attached a double tap gesture recognizer to the blue footer for the purpose of dismissing the keyboard.
- (void)viewDidLoad
{
[super viewDidLoad];
self.scrollView.alwaysBounceVertical = YES;
[self.scrollView addSubview:self.contentView];
[self.contentView addSubview:self.textView];
[self.contentView addSubview:self.stickyFooterView];
[self configureConstraintsForContentViewSubviews];
// Apple's mixed (a.k.a. hybrid) approach to laying out a scroll view with Auto Layout: explicitly set content view's frame and scroll view's contentSize (see Apple's Technical Note TN2154: https://developer.apple.com/library/ios/technotes/tn2154/_index.html)
CGFloat textViewHeight = [self calculateHeightForTextViewWithString:self.textView.text];
CGFloat contentViewHeight = [self calculateHeightForContentViewWithTextViewHeight:textViewHeight];
// scroll view is fullscreen in storyboard; i.e., it's final on-screen geometries will be the same as the view controller's main view; unfortunately, the scroll view's final on-screen geometries are not available in viewDidLoad
CGSize scrollViewSize = self.view.bounds.size;
if (contentViewHeight < scrollViewSize.height) {
self.contentView.frame = CGRectMake(0, 0, scrollViewSize.width, scrollViewSize.height);
} else {
self.contentView.frame = CGRectMake(0, 0, scrollViewSize.width, contentViewHeight);
}
self.scrollView.contentSize = self.contentView.bounds.size;
}
- (void)configureConstraintsForContentViewSubviews
{
assert(_textView && _stickyFooterView); // for debugging
// note: there is no constraint between the subviews along the vertical axis; the amount of vertical space between the subviews is determined by the content view's height
NSString *format = #"H:|-(space)-[textView]-(space)-|";
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:#{#"space": #(SIDE_MARGIN)} views:#{#"textView": _textView}]];
format = #"H:|-(space)-[footer]-(space)-|";
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:#{#"space": #(SIDE_MARGIN)} views:#{#"footer": _stickyFooterView}]];
format = #"V:|-(space)-[textView]";
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:#{#"space": #(TOP_MARGIN)} views:#{#"textView": _textView}]];
format = #"V:[footer(height)]-(space)-|";
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:#{#"space": #(BOTTOM_MARGIN), #"height": #(FOOTER_HEIGHT)} views:#{#"footer": _stickyFooterView}]];
// a UITextView does not have an intrinsic content size; will need to install an explicit height constraint based on the size of the text; when the text is modified, this height constraint's constant will need to be updated
CGFloat textViewHeight = [self calculateHeightForTextViewWithString:self.textView.text];
self.textViewHeightConstraint = [NSLayoutConstraint constraintWithItem:self.textView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0f constant:textViewHeight];
[self.textView addConstraint:self.textViewHeightConstraint];
}
- (void)keyboardUp:(NSNotification *)notification
{
// when the keyboard appears, extraneous vertical space between the subviews is eliminated–if necessary; i.e., vertical space between the subviews is reduced to the minimum if this space is not already at the minimum
NSDictionary *info = [notification userInfo];
CGRect keyboardRect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
keyboardRect = [self.view convertRect:keyboardRect fromView:nil];
double duration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
CGFloat contentViewHeight = [self calculateHeightForContentViewWithTextViewHeight:self.textView.bounds.size.height];
CGSize scrollViewSize = self.scrollView.bounds.size;
[UIView animateWithDuration:duration animations:^{
self.contentView.frame = CGRectMake(0, 0, scrollViewSize.width, contentViewHeight);
self.scrollView.contentSize = self.contentView.bounds.size;
UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, keyboardRect.size.height, 0);
self.scrollView.contentInset = insets;
self.scrollView.scrollIndicatorInsets = insets;
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
[self scrollToCaret];
}];
}
Although the Auto Layout component of this demo app took some time, I spent almost as much time on scrolling issues related to a UITextView being nested inside of a UIScrollView.
Instead of using a UIScrollView you would very likely be better off with a UITableView. It also might be better to not using auto-layout. At least, I've found it better to not use it for these sorts of manipulations.
Look into the following:
UITextView textViewDidChange
Change the size of the text view using sizeThatFits (limiting width and using FLT_MAX for height). Change the frame, not the contentSize.
Call UITableView beginUpdates/endUpdates to update the table view
Scroll to the cursor
UIKeyboardWillShowNotification notification
On NSNotification that comes through, you can call userInfo (a Dictionary), and the key UIKeyboardFrameBeginUserInfoKey. Reduce the frame of the table view based on the height of the size of the keyboard.
Scroll to cursor again (since the layouts will have all changed)
UIKeyboardWillHideNotification notification
The same as the show notification, just opposite (increasing the table view height)
To have the footer view stick to the bottom, you could add an intermediate cell to the table view, and have it change size depending on the size of the text and whether the keyboard is visible.
The above will definitely require some extra manipulation on your part - I don't fully understand all of your cases, but it should definitely get you started.
If I understand whole task, my solution is put "red" and "blue" views to one container view, and in the moment when you know size of dynamic content (red) you can calculate size of container and set scrollView content size.
Later, on keyboard events you can adjust white space between content and footer views
I have a UITextView within a UIScrollView in one of my controllers.
The problem is that when you enter text and get to the bottom of the scrollView, the last line of text (the one you're typing) actually extends beyond the bottom of the scroll view and is hidden.
Any help would be much appreciated.
scrollView.frame = CGRectMake(15.0f, 20.0f+15.0f, self.view.frame.size.width-15.0f*2.0f, self.view.frame.size.height-keyboardBar.frame.size.height-keyboardBounds.size.height-20.0f-15.0f*3.0f);
postTextView.frame = CGRectMake(0.0f, 0.0f, scrollView.frame.size.width, scrollView.frame.size.height);
[self.view addSubview:scrollView];
[scrollView addSubview:postTextView];
`
You need to set the content size of the scroll view so that it knows how to scroll its subviews:
[scrollView setContentSize:postTextView.frame.size];
The frame of the scroll view is just the space it takes up by itself on self.view. The content size is the size that all of the scroll view's subviews will take up inside of the scroll view.
I have a UIView like iPhone's Springboard. I have created it using a UIScrollView and UIButtons. I want to disable horizontal scrolling on said scrollview. I want only vertical scrolling. How do I accomplish this?
You have to set the contentSize property of the UIScrollView. For example, if your UIScrollView is 320 pixels wide (the width of the screen), then you could do this:
CGSize scrollableSize = CGSizeMake(320, myScrollableHeight);
[myScrollView setContentSize:scrollableSize];
The UIScrollView will then only scroll vertically, because it can already display everything horizontally.
UPDATED: (After #EranMarom pointed out on his comment)
You can stop horizontal scrolling or vertical scrolling in the ScrollViewDelegate Method.
Here it is how,
Stops Horizontal Scrolling:
If you want to scroll horizontally, then you need to increase the contentOffset.x. Preventing that stops the scrollview scroll in horizontal direction.
- (void)scrollViewDidScroll:(UIScrollView *)sender {
sender.contentOffset.x = 0.0
}
Stops Vertical Scrolling:
If you want to scroll vertically, then you need to increase the contentOffset.y. Preventing that stops the scrollview scroll in vertical direction.
- (void)scrollViewDidScroll:(UIScrollView *)sender {
sender.contentOffset.y = 0.0
}
Above code prevents the changes in x and y of a scrollview contentOffset and it leads to stop the scrolling in scrollViewDidScroll: method.
since iOS7 use
self.automaticallyAdjustsScrollViewInsets = NO;
//and create you page scroller with 3 pages
self.pageView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[self.pageView setContentSize:CGSizeMake(self.view.frame.size.width*3, self.view.frame.size.height)];
[self.pageView setShowsVerticalScrollIndicator:NO];
[self.pageView setPagingEnabled:YES];
[self.view addSubview:self.pageView];
Swift solution
Create two outlets, one for your view and one for your scroll view:
#IBOutlet weak var myView: UIView!
#IBOutlet weak var scrollView: UIScrollView!
Then in your viewDidLayoutSubviews you can add the following code:
let scrollSize = CGSize(width: myView.frame.size.width,
height: myView.frame.size.height)
scrollView.contentSize = scrollSize
What we've done is collected the height and width of the view and set the scrollViews content size to match it. This will stop your scrollview from scrolling horizontally.
More Thoughts:
CGSizeMake takes a width & height using CGFloats. You may need to use your UIScrollViews existing height for the second parameter. Which would look like this:
let scrollSize = CGSize(width: myView.frame.size.width,
height: scrollView.contentSize.height)
In my case, with Swift 4.2 you can use:
Disable vertical scroll:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollView.contentOffset.y = 0.0
}
Disable horizontal scroll:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollView.contentOffset.x = 0.0
}
In my case the width of the contentView was greater than the width of UIScrollView and that was the reason for unwanted horizontal scrolling. I solved it by setting the width of contentView equal to width of UIScrollView.
Hope it helps someone
You can select the view, then under Attributes Inspector uncheck User Interaction Enabled .
Introduced in iOS 11 is a new property on UIScrollView
var contentLayoutGuide: UILayoutGuide
The documentation states that you:
Use this layout guide when you want to create Auto Layout constraints related to the content area of a scroll view.
Along with any other Autolayout constraints that you might be adding you will want to constrain the widthAnchor of the UIScrollView's contentLayoutGuide to be the same size as the "frame". You can use the frameLayoutGuide (also introduced in iOS 11) or any external width (such as your superView's.)
example:
NSLayoutConstraint.activate([
scrollView.contentLayoutGuide.widthAnchor.constraint(equalTo: self.widthAnchor)
])
Documentation:
https://developer.apple.com/documentation/uikit/uiscrollview/2865870-contentlayoutguide
#Gulfam Khan's answer is the correct one, I am adding imagery to help the concept get more visibility.
When we set the contentView to have equal width's with the Scroll view, if the multiplier is even slightly greater than 1:1, then we will get horizontal scrolling.
Here is what it produces:
If you do not want horizontal scrolling, you most likely do not have horizontal content that exceeds the width of the superview.
Therefore if you ensure the contentView width does not exceed the width of the scroll view, that will automatically resolve the problem as UIKit recognizes there is no horizontal content to scroll to. Like so:
Now you should only see vertical:
I had the tableview contentInset set in viewDidLoad (as below) that what causing the horizontal scrolling
self.tableView.contentInset = UIEdgeInsetsMake(0, 30, 0, 0);
Check if there are any tableview contentInset set for different reasons and disable it
I struggled with this for some time trying unsuccessfully the various suggestions in this and other threads.
However, in another thread (not sure where) someone suggested that using a negative constraint on the UIScrollView worked for him.
So I tried various combinations of constraints with inconsistent results. What eventually worked for me was to add leading and trailing constraints of -32 to the scrollview and add an (invisible) textview with a width of 320 (and centered).
Try This:
CGSize scrollSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, scrollHeight);
[scrollView setContentSize: scrollSize];
Disable horizontal scrolling by overriding contentOffset property in subclass.
override var contentOffset: CGPoint {
get {
return super.contentOffset
}
set {
super.contentOffset = CGPoint(x: 0, y: newValue.y)
}
}
Once I did it replacing the UIScrollView with a UITableView with only 1 cell, it worked fine.
Use this single line.
self.automaticallyAdjustsScrollViewInsets = NO;