How do you center the content in a scrollview? - ios

I know this should be a very easy problem to solve, but I don´t seem to get the desired result.
I have horizontally scrolling UIScroll view with a width of 320. The width of its content is 467. I also have vertically scrolling UIScroll view with a hight of 310. The height of its content is 467 as well.
After searching for answers on the internet, the closest I have come to a solution is this:
CGFloat newContentOffsetX = (ScrollView.contentSize.width/2) - (ScrollView.bounds.size.width/2);
ScrollView.contentOffset = CGPointMake(newContentOffsetX, 0);
The code is getting errors, maybe because it was written in 2013 . And I know this code would only center the content horizontally.
Help is very much appreciated!

I usually set the contentOffset inside viewDidLayoutSubviews, similar to the code snippet below.
override func viewDidLayoutSubviews() {
// Center the ScrollView content
let centerOffsetX = (scrollView.contentSize.width - scrollView.frame.size.width) / 2
let centerOffsetY = (scrollView.contentSize.height - scrollView.frame.size.height) / 2
let centerPoint = CGPoint(x: centerOffsetX, y: centerOffsetY)
self.scrollView.setContentOffset(centerPoint, animated: true)
}

this code I use in my apps
func scrollViewDidZoom(scrollView: UIScrollView) {
let offsetX = scrollView.bounds.size.width > scrollView.contentSize.width ? (scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5 : 0.0;
let offsetY = scrollView.bounds.size.height > scrollView.contentSize.height ? (scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5 : 0.0;
_imageView.center = CGPointMake(scrollView.contentSize.width / 2 + offsetX, scrollView.contentSize.height / 2 + offsetY);
}

Related

Change UICollectionView's bounce start/end points

Normally UICollectionView starts to bounce when scrolled past it's contentSize, ie when contentOffset < 0 or contentOffset > contentSize.width for horizontal orientation.
Is it possible to change this behavior so the bounce effect starts when scrolled past let's say 10th item (when contentOffset < itemSize.Width * 10 or contentOffset > contentSize.width - (itemSize.Width * 10))?
UPDATE 1:
#OverD - thanks for pointing me towards the right direction.
I ended up with some work in scrollViewWillEndDragging and adjusting targetContentOffset when necessary.
The problem I'm still facing is that the bounce animation is not smooth like the original bounce when reaching the contenSize end.
Any ideas what's missing? Code snippet below:
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let numberOfBouncingCells = 10
let extraSectionWidth = (collectionViewFlowLayout.itemSize.width + collectionViewFlowLayout.minimumLineSpacing) * numberOfBouncingCells
let startXOffset = extraSectionWidth
let endXOffset = collectionView.contentSize.width - 2 * extraSectionWidth
let yOffset = collectionView.contentOffset.y
if targetContentOffset.pointee.x < startXOffset {
targetContentOffset.pointee = CGPoint(x: startXOffset, y: yOffset)
} else if targetContentOffset.pointee.x > endXOffset {
targetContentOffset.pointee = CGPoint(x: endXOffset, y: yOffset)
}
}
UPDATE 2 (for answer):
See answer below, I ditched the scrollViewWillEndDragging approach in favor of simply changing collectionView.contentInset [.left, .right]
I've answered my own question eventually.
I only had to set collectionView.contentInset [.left, .right] to achieve expected behavior.
var collectionViewFlowLayout: UICollectionViewFlowLayout {
return collectionView.collectionViewLayout as! UICollectionViewFlowLayout
}
let numberOfBouncingCells = 10
let extraBouncingWidth = (collectionViewFlowLayout.itemSize.width + collectionViewFlowLayout.minimumLineSpacing) * numberOfBouncingCells
collectionView.contentInset.left = -extraBouncingWidth
collectionView.contentInset.right = -extraBouncingWidth

How to force UITextView text to be centered vertically and horizontally when screen loads?

I've been trying unsuccessfully to vertically center my text in my UITextView as soon as my app starts up. No matter what I've tried the text just appears at the top of the text view like it normally would. Here is a photo.
The only time the text centers correctly is when I type it, this was done by using an observer. Here is the code I'm using to center my text in viewDidLoad, I've tried 2 methods, neither worked.
extension UITextView {
func centerText() {
self.textAlignment = .center
let fittingSize = CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude)
let size = sizeThatFits(fittingSize)
let topOffset = (bounds.size.height - size.height * zoomScale) / 2
let positiveTopOffset = max(1, topOffset)
contentOffset.y = -positiveTopOffset
}
func alignTextVerticallyInContainer() {
var topCorrect = (self.bounds.size.height - self.contentSize.height * self.zoomScale) / 2
topCorrect = topCorrect < 0.0 ? 0.0 : topCorrect;
self.contentInset.top = topCorrect
}
}
I would call these methods in my viewDidLoad like this: self.goalTextView.centerText() or self.goalTextView.alignTextVerticallyInContainer(). So now that you've seen what I've tried so far, anyone have any idea how to force the text in the textview to be centered vertically on startup? Any help would be appreciated.

How to restrict shifting of a pinch zoomed image to the image borders?

I set up a scrollView in storyboard with a single imageView using auto layout as shown here.
In addition I enabled pinch zooming for this imageView as described in the docs.
The imageView has 4 constraints of 0 to the sides of the scollView, and it has equal width and height to the main view, which has the scrollView as subview.
The imageView has aspectFit scaling mode, so in the unzoomed state, the image will extend fully from left to right, or from top to bottom (or both).
In the following example, it extends from top to bottom, leaving some background visible left and right:
I can pinch zoom the image so that it is larger than the screen:
I can shift the zoomed image up or down, but the movement stops before the background becomes visible on top or bottom. This is want I want.
However, I can shift the zoomed image left or right so that the background can be seen:
How can I avoid shifting the image so far (here, to the right). Like with top or bottom, it should stop moving at the border, so that no background becomes visible.
Not ideal solution but I hope it will help:
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
[self adjustScrollViewInsets];
}
- (void)adjustScrollViewInsets {
CGFloat imageWidth = self.imageView.image.size.width;
CGFloat imageHeight = self.imageView.image.size.height;
CGFloat aspect = imageWidth / imageHeight;
CGSize imageViewSize = self.imageView.frame.size;
if (imageViewSize.width / aspect <= imageViewSize.height) {
[self adjustVerticalInsetsWithImageHeight:(imageViewSize.width / aspect)];
} else {
[self adjustHorizontalInsetsWithImageWidth:(imageViewSize.height * aspect)];
}
}
- (void)adjustHorizontalInsetsWithImageWidth:(CGFloat)width {
CGFloat horizontalInset = (self.scrollView.contentSize.width - width) / 2;
if (width < self.scrollView.frame.size.width) {
horizontalInset = horizontalInset - (self.scrollView.frame.size.width - width) / 2;
}
[self.scrollView setContentInset:UIEdgeInsetsMake(0, -horizontalInset, 0, -horizontalInset)];
}
- (void)adjustVerticalInsetsWithImageHeight:(CGFloat)height {
CGFloat verticalInset = (self.scrollView.contentSize.height - height) / 2;
if (height < self.scrollView.frame.size.height) {
verticalInset = verticalInset - (self.scrollView.frame.size.height - height) / 2;
}
[self.scrollView setContentInset:UIEdgeInsetsMake(-verticalInset, 0, -verticalInset, 0)];
}
If you have troubles, check out this repo.
I was having the same problem and implemented in Swift 4.0 as given below and it worked for me.
Swift 4.0:
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
adjustScrollViewInsets()
}
func adjustScrollViewInsets() {
let imageWidth = self.imageView.image?.size.width
let imageHeight = self.imageView.image?.size.height
let aspect = imageWidth! / imageHeight!
let imageViewSize = self.imageView.frame.size
if imageViewSize.width / aspect <= imageViewSize.height {
adjustVerticalInsetsWithImageHeight(height: imageViewSize.width / 2)
} else {
adjustHorizontalInsetsWithImageWidth(width: imageViewSize.height / 2)
}
}
func adjustHorizontalInsetsWithImageWidth(width: CGFloat) {
var horizontalInset = (self.scrollView.contentSize.width - width) / 2
if width < self.scrollView.contentSize.width {
horizontalInset = horizontalInset - (self.scrollView.frame.size.width - width) / 2
}
self.scrollView.contentInset = UIEdgeInsetsMake(0, -horizontalInset, 0, -horizontalInset)
}
func adjustVerticalInsetsWithImageHeight(height: CGFloat) {
var verticalInset = (self.scrollView.contentSize.height - height) / 2
if height < self.scrollView.frame.size.height {
verticalInset = verticalInset - (self.scrollView.frame.size.height - height) / 2
}
self.scrollView.contentInset = UIEdgeInsetsMake(-verticalInset, 0, -verticalInset, 0)
}

How to vertical align (center) the content of UITableView?

I want to center the content of my UITableView that contains headerView and footerView created at storyboard, and UITableViewCells. How can I achieve this?
Here is what I'm trying to implement to solve my problem but this does not work.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
CGFloat height = self.tableView.frameHeight - self.navigationController.navigationBar.frameHeight - [UIApplication sharedApplication].statusBarFrame.size.height - (self.rowCount * self.rowHeight);
self.tableView.tableHeaderView.frameHeight = height / 2.0;
}
So I subtracted the height of the navigationBar & statusBar and cells' height to the tableView's height to get the height of the empty area.
Now that I get the height if the empty area, I divided it to 2 for the footer and header's view.
In the viewWillAppear and in the didRotateFromInterfaceOrientation functions :
CGFloat headerHeight = (self.view.frame.size.height - (ROW_HEIGHT * [self.tableView numberOfRowsInSection:0]))) / 2;
self.tableView.contentInset = UIEdgeInsetsMake(headerHeight, 0, -headerHeight, 0);
This will solve your problem.
EDIT
Call the updateTableViewContentInset function in the viewWillLayoutSubviews and after each reloadData :
Ojective-C
- (void)updateTableViewContentInset {
CGFloat viewHeight = self.view.frame.size.height;
CGFloat tableViewContentHeight = self.tableView.contentSize.height;
CGFloat marginHeight = (viewHeight - tableViewContentHeight) / 2.0;
self.tableView.contentInset = UIEdgeInsetsMake(marginHeight, 0, -marginHeight, 0);
}
Swift 4
func updateTableViewContentInset() {
let viewHeight: CGFloat = view.frame.size.height
let tableViewContentHeight: CGFloat = tableView.contentSize.height
let marginHeight: CGFloat = (viewHeight - tableViewContentHeight) / 2.0
self.tableView.contentInset = UIEdgeInsets(top: marginHeight, left: 0, bottom: -marginHeight, right: 0)
}
Override viewDidLayoutSubviews and compare the tableView's frame with its contentSize to set its contentInset. In Swift 4 :
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let tableViewHeight = self.tableView.frame.height
let contentHeight = self.tableView.contentSize.height
let centeringInset = (tableViewHeight - contentHeight) / 2.0
let topInset = max(centeringInset, 0.0)
self.tableView.contentInset = UIEdgeInsets(top: topInset, left: 0.0, bottom: 0.0, right: 0.0)
}
This works well with self-sizing table view cells 👌
Use the contentSize property of UITableView, that how you don't have to take the cell height or count into account.
- (void) updateTableInsetsForHeight: (CGFloat) height
{
CGFloat tableViewInset = MAX((height - self.tableView.contentSize.height) / 2.0, 0.f);
self.tableView.contentInset = UIEdgeInsetsMake(tableViewInset, 0, -tableViewInset, 0);
}
Update the contentInsets after reloading tableView-data, and when the containing view of the tableView becomes visible (viewWillAppear:) and when it changes size (viewWillTransitionToSize:withTransitionCoordinator:).
- (void) viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[self updateTableInsetsForHeight: size.height];
}
Swift 3 based on Pipiks answer
let headerHeight: CGFloat = (view.frame.size.height - CGFloat(Int(tableView.rowHeight) * tableView.numberOfRows(inSection: 0))) / 2
tableView.contentInset = UIEdgeInsetsMake(headerHeight, 0, -headerHeight, 0)
Add a UIView at the top (UIView should be parent of your TableView) of your TableView from storyboard. After that set its constraints to fill the view. Now, set the size of your TableView and set its constraints to center both vertically and horizontally your top
UIView. This will solve your problem.
I found out what causing my computation wrong. The value of self.tableView.bounds.size.height is different from the actual height in viewWillAppear. I used self.view.bounds.size.height instead.
for SWIFT 3 :
var headerHeight: CGFloat = (tableView.frame.size.height - CGFloat(Int(tableView.rowHeight) * tableView.numberOfRows(inSection: 0))) / 2
if headerHeight > 0 {
tableView.contentInset = UIEdgeInsetsMake(headerHeight, 0, 0/*-headerHeight*/, 0)
} else {
headerHeight = 0
tableView.contentInset = UIEdgeInsetsMake(headerHeight, 0, 0/*-headerHeight*/, 0)
}
RESULT :
Set frame of your UITableView like this
CGFloat width = self.view.frame.size.width;
CGFloat height = self.view.frame.size.height;
CGFloat tableHeight = 200.0; //your table height
self.tableView.frame = CGRectMake(0,(height-tableHeight)/2,width,tableHeight);
or try this

UIScrollView scroll to bottom programmatically

How can I make a UIScrollView scroll to the bottom within my code? Or in a more generic way, to any point of a subview?
You can use the UIScrollView's setContentOffset:animated: function to scroll to any part of the content view. Here's some code that would scroll to the bottom, assuming your scrollView is self.scrollView:
Objective-C:
CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.contentInset.bottom);
[self.scrollView setContentOffset:bottomOffset animated:YES];
Swift:
let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.height + scrollView.contentInset.bottom)
scrollView.setContentOffset(bottomOffset, animated: true)
Swift version of the accepted answer for easy copy pasting:
let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.size.height)
scrollView.setContentOffset(bottomOffset, animated: true)
Simplest Solution:
[scrollview scrollRectToVisible:CGRectMake(scrollview.contentSize.width - 1,scrollview.contentSize.height - 1, 1, 1) animated:YES];
A swifty implementation:
extension UIScrollView {
func scrollToBottom(animated: Bool) {
if self.contentSize.height < self.bounds.size.height { return }
let bottomOffset = CGPoint(x: 0, y: self.contentSize.height - self.bounds.size.height)
self.setContentOffset(bottomOffset, animated: animated)
}
}
use it:
yourScrollview.scrollToBottom(animated: true)
Just an enhancement to the existing answer.
CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.contentInset.bottom);
[self.scrollView setContentOffset:bottomOffset animated:YES];
It takes care of the bottom inset as well (in case you're using that to adjust your scroll view when the keyboard is visible)
Setting the content offset to the height of the content size is wrong: it scrolls the bottom of the content to the top of the scroll view, and thus out of sight.
The correct solution is to scroll the bottom of the content to the bottom of the scroll view, like this (sv is the UIScrollView):
CGSize csz = sv.contentSize;
CGSize bsz = sv.bounds.size;
if (sv.contentOffset.y + bsz.height > csz.height) {
[sv setContentOffset:CGPointMake(sv.contentOffset.x,
csz.height - bsz.height)
animated:YES];
}
A Swift 2.2 solution, taking contentInset into account
let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.size.height + scrollView.contentInset.bottom)
scrollView.setContentOffset(bottomOffset, animated: true)
This should be in an extension
extension UIScrollView {
func scrollToBottom() {
let bottomOffset = CGPoint(x: 0, y: contentSize.height - bounds.size.height + contentInset.bottom)
setContentOffset(bottomOffset, animated: true)
}
}
Note that you may want to check if bottomOffset.y > 0 before scroll
What if contentSize is lower than bounds?
For Swift it is:
scrollView.setContentOffset(CGPointMake(0, max(scrollView.contentSize.height - scrollView.bounds.size.height, 0) ), animated: true)
Scroll To Top
- CGPoint topOffset = CGPointMake(0, 0);
- [scrollView setContentOffset:topOffset animated:YES];
Scroll To Bottom
- CGPoint bottomOffset = CGPointMake(0, scrollView.contentSize.height - self.scrollView.bounds.size.height);
- [scrollView setContentOffset:bottomOffset animated:YES];
It looks like all of the answers here didn't take the safe area into consideration.
Since iOS 11, iPhone X had a safe area introduced. This may affect the scrollView's contentInset.
For iOS 11 and above, to properly scroll to the bottom with the content inset included. You should use adjustedContentInset instead of contentInset. Check this code:
Swift:
let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.height + scrollView.adjustedContentInset.bottom)
scrollView.setContentOffset(bottomOffset, animated: true)
Objective-C
CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.adjustedContentInset.bottom);
[self.scrollView setContentOffset:bottomOffset animated:YES];
Swift extension (this keeps the original contentOffset.x):
extension UIScrollView {
func scrollsToBottom(animated: Bool) {
let bottomOffset = CGPoint(x: contentOffset.x,
y: contentSize.height - bounds.height + adjustedContentInset.bottom)
setContentOffset(bottomOffset, animated: animated)
}
}
References:
adjustedContentInset
I also found another useful way of doing this in the case you are using a UITableview (which is a subclass of UIScrollView):
[(UITableView *)self.view scrollToRowAtIndexPath:scrollIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
Using UIScrollView's setContentOffset:animated: function to scroll to the bottom in Swift.
let bottomOffset : CGPoint = CGPointMake(0, scrollView.contentSize.height - scrollView.bounds.size.height + scrollView.contentInset.bottom)
scrollView.setContentOffset(bottomOffset, animated: true)
If you somehow change scrollView contentSize (ex. add something to stackView which is inside scrollView) you must call scrollView.layoutIfNeeded() before scrolling, otherwise it does nothing.
Example:
scrollView.layoutIfNeeded()
let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.size.height + scrollView.contentInset.bottom)
if(bottomOffset.y > 0) {
scrollView.setContentOffset(bottomOffset, animated: true)
}
With an (optional) footerView and contentInset, the solution is:
CGPoint bottomOffset = CGPointMake(0, _tableView.contentSize.height - tableView.frame.size.height + _tableView.contentInset.bottom);
if (bottomOffset.y > 0) [_tableView setContentOffset: bottomOffset animated: YES];
Swift:
You could use an extension like this:
extension UIScrollView {
func scrollsToBottom(animated: Bool) {
let bottomOffset = CGPoint(x: 0, y: contentSize.height - bounds.size.height)
setContentOffset(bottomOffset, animated: animated)
}
}
Use:
scrollView.scrollsToBottom(animated: true)
valdyr, hope this will help you:
CGPoint bottomOffset = CGPointMake(0, [textView contentSize].height - textView.frame.size.height);
if (bottomOffset.y > 0)
[textView setContentOffset: bottomOffset animated: YES];
Category to the rescue!
Add this to a shared utility header somewhere:
#interface UIScrollView (ScrollToBottom)
- (void)scrollToBottomAnimated:(BOOL)animated;
#end
And then to that utility implementation:
#implementation UIScrollView(ScrollToBottom)
- (void)scrollToBottomAnimated:(BOOL)animated
{
CGPoint bottomOffset = CGPointMake(0, self.contentSize.height - self.bounds.size.height);
[self setContentOffset:bottomOffset animated:animated];
}
#end
Then Implement it wherever you like, for instance:
[[myWebView scrollView] scrollToBottomAnimated:YES];
For Horizontal ScrollView
If you like me has a Horizontal ScrollView and want to scroll to end of it (in my case to most right of it), you need to change some parts of the accepted answer:
Objective-C
CGPoint rightOffset = CGPointMake(self.scrollView.contentSize.width - self.scrollView.bounds.size.width + self.scrollView.contentInset.right, 0 );
[self.scrollView setContentOffset:rightOffset animated:YES];
Swift
let rightOffset: CGPoint = CGPoint(x: self.scrollView.contentSize.width - self.scrollView.bounds.size.width + self.scrollView.contentInset.right, y: 0)
self.scrollView.setContentOffset(rightOffset, animated: true)
A good way to ensure the bottom of your content is visible is to use the formula:
contentOffsetY = MIN(0, contentHeight - boundsHeight)
This ensures the bottom edge of your content is always at or above the bottom edge of the view. The MIN(0, ...) is required because UITableView (and probably UIScrollView) ensures contentOffsetY >= 0 when the user tries to scroll by visibly snapping contentOffsetY = 0. This looks pretty weird to the user.
The code to implement this is:
UIScrollView scrollView = ...;
CGSize contentSize = scrollView.contentSize;
CGSize boundsSize = scrollView.bounds.size;
if (contentSize.height > boundsSize.height)
{
CGPoint contentOffset = scrollView.contentOffset;
contentOffset.y = contentSize.height - boundsSize.height;
[scrollView setContentOffset:contentOffset animated:YES];
}
If you don't need animation, this works:
[self.scrollView setContentOffset:CGPointMake(0, CGFLOAT_MAX) animated:NO];
While Matt solution seems correct to me you need to take in account also the collection view inset if there is one that has been set-up.
The adapted code will be:
CGSize csz = sv.contentSize;
CGSize bsz = sv.bounds.size;
NSInteger bottomInset = sv.contentInset.bottom;
if (sv.contentOffset.y + bsz.height + bottomInset > csz.height) {
[sv setContentOffset:CGPointMake(sv.contentOffset.x,
csz.height - bsz.height + bottomInset)
animated:YES];
}
In swift:
if self.mainScroll.contentSize.height > self.mainScroll.bounds.size.height {
let bottomOffset = CGPointMake(0, self.mainScroll.contentSize.height - self.mainScroll.bounds.size.height);
self.mainScroll.setContentOffset(bottomOffset, animated: true)
}
Solution to scroll to last item of a table View :
Swift 3 :
if self.items.count > 0 {
self.tableView.scrollToRow(at: IndexPath.init(row: self.items.count - 1, section: 0), at: UITableViewScrollPosition.bottom, animated: true)
}
Didn't work for me, when I tried to use it in UITableViewController on self.tableView (iOS 4.1), after adding footerView. It scrolls out of the borders, showing black screen.
Alternative solution:
CGFloat height = self.tableView.contentSize.height;
[self.tableView setTableFooterView: myFooterView];
[self.tableView reloadData];
CGFloat delta = self.tableView.contentSize.height - height;
CGPoint offset = [self.tableView contentOffset];
offset.y += delta;
[self.tableView setContentOffset: offset animated: YES];
CGFloat yOffset = scrollView.contentOffset.y;
CGFloat height = scrollView.frame.size.height;
CGFloat contentHeight = scrollView.contentSize.height;
CGFloat distance = (contentHeight - height) - yOffset;
if(distance < 0)
{
return ;
}
CGPoint offset = scrollView.contentOffset;
offset.y += distance;
[scrollView setContentOffset:offset animated:YES];
I found that contentSize doesn't really reflect the actual size of the text, so when trying to scroll to the bottom, it will be a little bit off. The best way to determine the actual content size is actually to use the NSLayoutManager's usedRectForTextContainer: method:
UITextView *textView;
CGSize textSize = [textView.layoutManager usedRectForTextContainer:textView.textContainer].size;
To determine how much text actually is shown in the UITextView, you can calculate it by subtracting the text container insets from the frame height.
UITextView *textView;
UIEdgeInsets textInsets = textView.textContainerInset;
CGFloat textViewHeight = textView.frame.size.height - textInsets.top - textInsets.bottom;
Then it becomes easy to scroll:
// if you want scroll animation, use contentOffset
UITextView *textView;
textView.contentOffset = CGPointMake(textView.contentOffset.x, textSize - textViewHeight);
// if you don't want scroll animation
CGRect scrollBounds = textView.bounds;
scrollBounds.origin = CGPointMake(textView.contentOffset.x, textSize - textViewHeight);
textView.bounds = scrollBounds;
Some numbers for reference on what the different sizes represent for an empty UITextView.
textView.frame.size = (width=246, height=50)
textSize = (width=10, height=16.701999999999998)
textView.contentSize = (width=246, height=33)
textView.textContainerInset = (top=8, left=0, bottom=8, right=0)
Extend UIScrollView to add a scrollToBottom method:
extension UIScrollView {
func scrollToBottom(animated:Bool) {
let offset = self.contentSize.height - self.visibleSize.height
if offset > self.contentOffset.y {
self.setContentOffset(CGPoint(x: 0, y: offset), animated: animated)
}
}
}
To scroll to the bottom end, we have to work with the target view maximum height.
import UIKit
extension UIScrollView {
func scrollToBottomOf(targetView: UIView, animated: Bool) {
setContentOffset(CGPoint(x:targetView.frame.minX, y:targetView.frame.maxY), animated: animated)
}
}
//func invocation example
optionScrollView.scrollToBottomOf(targetView: self.optionsStackView, animated: false)
As explained here
https://janeshswift.com/ios/swift/how-to-scroll-to-a-position-programmatically-in-uiscrollview/
We can create a custom UIScrollView extension as
extension UIScrollView {
func scrollToTop(animated: Bool = false) {
setContentOffset(CGPoint(x: contentOffset.x, y: -adjustedContentInset.top), animated: animated)
}
var bottomContentOffsetY: CGFloat {
max(contentSize.height - bounds.height + adjustedContentInset.bottom, -adjustedContentInset.top)
}
func scrollToBottom(animated: Bool = false) {
setContentOffset(CGPoint(x: contentOffset.x, y: bottomContentOffsetY), animated: animated)
}
func scrollToLeading(animated: Bool = false) {
setContentOffset(CGPoint(x: -adjustedContentInset.left, y: contentOffset.y), animated: animated)
}
var trailingContentOffsetX: CGFloat {
max(contentSize.width - bounds.width + adjustedContentInset.right, -adjustedContentInset.left)
}
func scrollToTrailing(animated: Bool = false) {
setContentOffset(CGPoint(x: trailingContentOffsetX, y: contentOffset.y), animated: animated)
}
func scrollViewToVisible(_ view: UIView, animated: Bool = false) {
scrollRectToVisible(convert(view.bounds, from: view), animated: true)
}
var isOnTop: Bool {
contentOffset.y <= -adjustedContentInset.top
}
var isOnBottom: Bool {
contentOffset.y >= bottomContentOffsetY
}
}
Use It as --
DispatchQueue.main.async {
self.itemsScrollView.scrollToBottom()
}
Xamarin.iOS version for UICollectionView of the accepted answer for ease in copying and pasting
var bottomOffset = new CGPoint (0, CollectionView.ContentSize.Height - CollectionView.Frame.Size.Height + CollectionView.ContentInset.Bottom);
CollectionView.SetContentOffset (bottomOffset, false);

Resources