UITextView starts at Bottom or Middle of the text - ios

I'll get right to it. I have a UItextView placed in my view that when needs to scroll to see all the text (when a lot of text is present in the textView) the textView starts in the middle of the text sometimes and the bottom of the text other times.
Editing is not enabled on the textView. I need a way to force the textView to start at the top, every time. I saw some questions somewhat like this where other people used a content offset, but I do not really know how that works or if it would even be applicable here.
Thanks for your help.

That did the trick for me!
Objective C:
[self.textView scrollRangeToVisible:NSMakeRange(0, 0)];
Swift:
self.textView.scrollRangeToVisible(NSMakeRange(0, 0))
Swift 2 (Alternate Solution)
Add this override method to your ViewController
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
textView.setContentOffset(CGPointZero, animated: false)
}
Swift 3 & 4 (syntax edit)
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
textView.contentOffset = .zero
}

All of the answers above did not work for me. However, the secret turns out to be to implement your solution within an override of viewDidLayoutSubviews, as in:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
welcomeText.contentOffset = .zero
}
HTH :)

In Swift 2
You can use this to make the textView start from the top:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
myTextView.setContentOffset(CGPointZero, animated: false)
}
Confirmed working in Xcode 7.2 with Swift 2

Try this below code -
if ( [self respondsToSelector:#selector(setAutomaticallyAdjustsScrollViewInsets:)]){
self.automaticallyAdjustsScrollViewInsets = NO;
}
Or you can also set this property by StoryBoard -
Select ViewController then select attributes inspector now unchecked Adjust Scroll View Insets.

For Swift >2.2, I had issues with iOS 8 and iOS 9 using above methods as there are no single answer that works so here is what I did to make it work for both.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if #available(iOS 9.0, *) {
textView.scrollEnabled = false
}
self.textView.scrollRangeToVisible(NSMakeRange(0, 0))
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if #available(iOS 9.0, *) {
textView.scrollEnabled = true
}
}

Update your UINavigationBar's translucent property to NO:
self.navigationController.navigationBar.translucent = NO;
This will fix the view from being framed underneath the navigation bar and status bar.
If you have to show and hide the navigation bar, then use below code in your viewDidLoad
if ([self respondsToSelector:#selector(edgesForExtendedLayout)])
self.edgesForExtendedLayout = UIRectEdgeNone; // iOS 7 specific
Hope this helps.

Xcode 7.2 7c68; IOS 9.1
My ViewController which contains UITextView is complicated, and changed a lot during the project (IDE version changed maybe 2~3 times too).
I've tried all above solutions, if you encounter the same issue, be PATIENT.
There are three possible 'secret codes' to solve:
textView.scrollEnabled = false
//then set text
textView.scrollEnabled = true
textView.scrollRangeToVisible(NSMakeRange(0, 0))
textView.setContentOffset(CGPointZero, animated: false)
And there are two places you can put those codes in:
viewDidLoad()
viewDidLayoutSubviews()
Combine them, you'll get 3*2=6 solutions, the correct combination depends on how complicated you ViewController is (Believe me, after delete just a view above textView, I need to find a new combination).
And I found that:
When put 'secret codes' in viewDidLayoutSubviews(), but textView.text = someStrings in viewDidLoad(), the content in textView will 'shake' sometimes. So, put them in the same place.
Last word: try ALL combinations, this is how I solve this stupid bug more than three times during two months.

With a lot of testing, i found must add below in viewWillLayoutSubviews() function to make sure the UITextView show up from the very beginning:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
textViewTerms.scrollRangeToVisible(NSMakeRange(0, 0))
}

UITextView scrolling seems to be a problem to a lot of people. Gathering from the answers here around (especially this) and the Apple Developer documentation, using some of my own wit, here is a solution that works for me. You can modify the code to suit your needs.
My use case is as follows: the same UITextView is used for different purposes, displaying varying content in different circumstances. What I want is that when the content changes, the old scroll position is restored, or at times, scrolled to the end. I don't want too much animation when this is done. Especially I don't want the view to animate like all the text was new. This solution first restores the old scroll position without animation, then scrolls to the end animated, if so desired.
What you need to do (or should I say can do) is extend UITextView as follows:
extension UITextView {
func setText(text: String, storedOffset: CGPoint, scrollToEnd: Bool) {
self.text = text
let delayInSeconds = 0.001
let popTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(delayInSeconds * Double(NSEC_PER_SEC)))
dispatch_after(popTime, dispatch_get_main_queue(), {
self.setContentOffset(storedOffset, animated: false)
if scrollToEnd && !text.isEmpty {
let popTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(delayInSeconds * Double(NSEC_PER_SEC)))
dispatch_after(popTime, dispatch_get_main_queue(), {
self.scrollRangeToVisible(NSMakeRange(text.lengthOfBytesUsingEncoding(NSUTF8StringEncoding) - 1, 0))
})
}
})
}
}
What this does is it updates the text, then uses a stored value of the UITextView.contentOffset property (or anything you pass as a parameter), and sets the offset of the view accordingly. If desired, after this, it scrolls to the end of the new, potentially changed content.
I'm new to iOS programming and I don't know why it works so well it does, if someone has some information on this it would be nice to know. Also the approach may not be perfect so I'm open to improvement ideas as well.
And of course thanks to NixonsBack for posting the answer behind the link above.
My first post :), cheers!

Put this one line of code in ViewDidLoad
self.automaticallyAdjustsScrollViewInsets = false

The following code should give you effect you want.
[self.scrollView setContentOffset:CGPointMake(0, -self.scrollView.contentInset.top) animated:YES];
You'll need to replace "self.scrollView" with the name of your scroll view. You should put this code in after you've set the text of the scroll view.

This worked for me:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
textView.scrollRectToVisible(CGRect(origin: CGPointZero, size: CGSizeMake(1.0, 1.0)), animated: false)
}

This worked for me with Xcode 8.3.3:
-(void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
[self.txtQuestion scrollRangeToVisible:NSMakeRange(0, 0)];
}

Create an outlet for your UITextView in the ViewController.swift file. In the ViewDidLoad section put the following:
Swift:
self.textView.contentOffset.y = 0
I have tried:
self.textView.scrollRangeToVisible(NSMakeRange(0, 0))

I translated zeeple's answer to MonoTouch/Xamarin (C#).
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
myForm.SetContentOffset(new CoreGraphics.CGPoint(0,0), animated: false);
}

I had to implement two answers here to get my view working as I want:
From Juan David Cruz Serrano:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
textView.setContentOffset(CGPoint.zero, animated: false)
}
And from Murat Yasar:
automaticallyAdjustsScrollViewInsets = false
This gave a UITextView that loads with the scroll at the very top and where the insets are not changed once scrolling starts. Very strange that this is not the default behaviour.

To force the textView to start at the top every time, use the following code:
Swift 4.2:
override func viewDidLayoutSubviews() {
textView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
}
Objective-C:
- (void)viewDidLayoutSubviews {
[self.yourTextView setContentOffset:CGPointZero animated:NO];
}

Swift 4.2 & Swift 5
set content offset both before and after setting the text. (on the main Thread)
let animation = false //or whatever you want
self.mainTextView.setContentOffset(.zero, animated: animation)
self.mainTextView.attributedText = YOUR_ATTRIBUTED_TEXT
self.mainTextView.setContentOffset(.zero, animated: animation)

In my case I was loading a textView in a Custom tableview cell. Below is what I did to make sure the text in a textview loads at the top of the text in my textview in my custom cell.
1.) In storyboard, set the textview ScrollEnabled = false by unchecking the button.
2.) You set the isScrollEnabled to true on the textview after the view loads. I set mine in a small delay like below:
override func awakeFromNib() {
super.awakeFromNib()
let when = DispatchTime.now() + 1
DispatchQueue.main.asyncAfter(deadline: when){
self.textView.isScrollEnabled = true
}
}
Regardless, if you are in my situation or not, try setting scrollEnabled to false and then when the view loads, set scrollEnabled to true.

Related

SLComposeServiceViewController height issue

My Share extension's view height seems to be rendered smaller than needed to accommodate the SLComposeSheetConfigurationItem. as you can see in attachment, it seems to be cut off from the bottom ("to: test#email.com") and I have to scroll it to see it all. Any idea why it might be happening. I am not customizing it in any way.
SLComposeServiceViewController stored SLComposeSheetConfigurationItem in tablView, so you need to access tableView by travelling on subView and call scrollToRow for bottom position will fix your issue.
Solution 1:
Add following code in ShareViewController.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let slSheet = self.children.first as? UINavigationController,
let tblView = slSheet.children.first?.view.subviews.first as? UITableView
{
// Scroll tablView to bottom
tblView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .bottom, animated: true)
}
}
Solution 2:
Change the row height will fix your issue, But make sure it is exactly height value which you want, I have used as per my requirements. Default value is 44.
tblView.rowHeight = 35
tblView.separatorStyle = .none
This seems to be an issue with iOS 13. The sheet's height is controlled by the system. Setting the preferredContentSize property doesn't have any effect. I'm using the following workaround:
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 13.0, *) {
_ = NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidShowNotification, object: nil, queue: .main) { (_) in
if let layoutContainerView = self.view.subviews.last {
layoutContainerView.frame.size.height += 10
}
}
}
}
The last subview of self.view is an instance of UILayoutContainerView, which is a private UIKit class. This class determines the size of the share dialog, but doesn't have any constraints attached to it. Therefore I have to set the frame property directly. I'm using the keyboardDidShowNotification callback because the frame is updated by the system when the keyboard appears.
If anyone comes up with a better solution, please let me know.

Scroll TextView to the top

I have a Text View (UITextView) which displays a long text that is set on runtime like so:
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
if something {
textView.text = "(very long text here)"
}
textView.contentOffset = CGPoint.zero // doesn't work
}
Unfortunately, when the Text View is displayed, the text is not scrolled to the top but somewhere in the middle.
I'm thinking, either setting the contentOffset is the wrong way of doing it or I am doing it at the wrong time (maybe the text gets changed after setting contentOffset?).
I have tried a lot, I even contacted Apple Code Level Support. They couldn't help me, really (which surprised the hell out of me) – can you?
I'd very much appreciate it. Thank you.
I had a very similar issue, especially when using splitview and testing on the iPhoneX, I resolved this by incorporating this bit of code in my ViewController when I needed the textView to scroll to the top:
textView.setContentOffset(.zero, animated: false)
textView.layoutIfNeeded()
If you wish to scroll to the top of the textView upon loading your ViewController:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// Can add an if statement HERE to limit when you wish to scroll to top
textView.setContentOffset(.zero, animated: false)
}
You may need to write more information/code because if you try this piece of code in clean project with only one VC with UITextView, you'll see that it's actually working. If you're really using some condition (if something) this might be the issue. What is this something in your real code?
You need to use viewDidLayoutSubviews() so that it set the scrollview to the top.
override func viewDidLayoutSubviews() {
textView.setContentOffset(.zero, animated: false)
}

UITextView is not scrolled to top when loaded

When I have text that does not fill the UITextView, it is scrolled to the top working as intended. When there is more text than will fit on screen, the UITextView is scrolled to the middle of the text, rather than the top.
Here are some potentially relevant details:
In viewDidLoad to give some padding on top and bottom of UITextView:
self.mainTextView.textContainerInset = UIEdgeInsetsMake(90, 0, 70, 0);
The UITextView uses auto layout to anchor it 20px from top, bottom and each side of the screen (done in IB) to allow for different screen sizes and orientations.
I can still scroll it with my finger once its loaded.
EDIT
I found that removing the auto layout constraints and then fixing the width only seems to fix the issue, but only for that screen width.
add the following function to your view controller class...
Swift 3
override func viewDidLayoutSubviews() {
self.mainTextView.setContentOffset(.zero, animated: false)
}
Swift 2.1
override func viewDidLayoutSubviews() {
self.mainTextView.setContentOffset(CGPointZero, animated: false)
}
Objective C
- (void)viewDidLayoutSubviews {
[self.mainTextView setContentOffset:CGPointZero animated:NO];
}
UITextView is a subclass of UIScrollView, so you can use its methods. If all you want to do is ensure that it's scrolled to the top, then wherever the text is added try:
[self.mainTextView setContentOffset:CGPointZero animated:NO];
EDIT: AutoLayout with any kind of scrollview gets wonky fast. That setting a fixed width solves it isn't surprising. If it doesn't work in -viewDidLayoutSubviews then that is odd. Setting a layout constraint manually may work. First create the constraints in IB:
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *textViewWidthConstraint;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *textViewHeightConstraint;
then in the ViewController
-(void)updateViewConstraints {
self.textViewWidthConstraint.constant = self.view.frame.size.width - 40.0f;
self.textViewHeightConstraint.constant = self.view.frame.size.height - 40.0f;
[super updateViewConstraints];
}
May still be necessary to setContentOffset in -viewDidLayoutSubviews.
(Another method would be to create a layout constraint for "'equal' widths" and "'equal' heights" between the textView and its superView, with a constant of "-40". It's only 'equal' if the constant is zero, otherwise it adjusts by the constant. But because you can only add this constraint to a view that constraints both views, you can't do this in IB.)
You may ask yourself, if I have to do this, what's the point of AutoLayout? I've studied AutoLayout in depth, and that is an excellent question.
Swift
self.textView.scrollRangeToVisible(NSMakeRange(0, 0))
Objective-C
[self.textView scrollRangeToVisible:(NSMakeRange(0, 0))];
i had same issue! Reset to suggested constrains and just put (y offset)
#IBOutlet weak var textContent: UITextView!
override func viewDidLoad() {
textContent.scrollsToTop = true
var contentHeight = textContent.contentSize.height
var offSet = textContent.contentOffset.x
var contentOffset = contentHeight - offSet
textContent.contentOffset = CGPointMake(0, -contentOffset)
}
For iOS9 and later the textview even on viewWillAppear: is coming with CGRect(0,0,1000,1000). In order for this to work you have to call in viewWillAppear:
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
// * Your code here
After that the textview will have correct CGRect data and you can perform any scrolling operation you may need.
The problem with putting code in viewDidLayoutSubviews and viewWillLayoutSubviews is that these methods are called a lot (during device rotation, resizing views etc ...). If you're reading something from text view, and you rotate the device, you expect that the part of the content you're viewing stays on screen. You do not expect that it scrolls back to top.
Instead of scrolling the content to top, try to keep text view's scrollEnabled property set to NO (false), and turn it back on in viewDidAppear.
If you don't wanna mess with constraints:
override func updateViewConstraints() {
super.updateViewConstraints()
}
override func viewDidLayoutSubviews() {
self.textLabel.setContentOffset(CGPointZero, animated: false)
}
This is an interesting bug. In our project, this is only occurring on devices with an iPhone 5-size screen. It appears that the textview contentOffset changes at some point during the view controller lifecycle. In viewDidLoad and viewWillAppear the textview's contentOffset is 0,0, and by viewDidAppear it's changed. You can see it happening in viewWillLayoutSubviews. Constraints appear to be set up correctly.
This will ensure you don't call a scrolling method unless it's needed:
if textView.contentOffset.y > 0 {
textView.contentOffset = CGPoint(x: 0, y: 0)
// Or use scrollRectToVisible, scrollRangeToVisible, etc.
}
Swift
override func viewDidLoad() {
super.viewDidLoad()
textView.isScrollEnabled = false
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
textView.isScrollEnabled = true
}
For me this works in a different way, I tried all things mentioned above but none of the worked in func viewWillAppear(_ animated: Bool). Which eventually makes textView scrolled up, and in func viewDidAppear(_ animated: Bool) it would scroll after screen appeared.
Below worked for me but got some constraint related issue with keyboard up and down.
override func viewDidLayoutSubviews() {
self.textView.setContentOffset(.zero, animated: false)
}
Below worked as expectation:
override func viewDidLoad() {
super.viewDidLoad()
self.textView.scrollsToTop = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.view.layoutIfNeeded()
self.textView.setContentOffset(.zero, animated: false)
}
David Rectors answer in Objective C:
#import "TopTextView.h"
#implementation TopTextView
bool scrolled = NO;
- (void) layoutSubviews
{
[super layoutSubviews];
if (!scrolled) {
[self setContentOffset:CGPointMake(0, 0) animated:NO];
scrolled = YES;
}
}
#end
It seems like a terrible idea to handle this issue in code in the view controller because: A. The view controller isn't making any mistake or doing anything wrong, and B, if you have more than one view controller with a wrongly scrolled text view, you end up with redundant code. The solution should be to write code that exists in the text view class. My solution works with Interface Builder where I simply select a custom class for the UITextView and use this class:
import Foundation
import UIKit
class TopTextView: UITextView {
var scrolled = false
override func layoutSubviews() {
super.layoutSubviews()
if scrolled { return }
setContentOffset(.zero, animated: false)
scrolled = true
}
}
This worked for me. I happen to have a view controller with a child view with a UITextView as a child of that view, not with a UITextView as the child of the view controller. I don't know how well this works if the text view is under top or bottom bars but since no edge insets are touched, this should work.
In my case I had to do it like this:
textView.setContentOffset(CGPoint(x: 0, y: -self.textView.adjustedContentInset.top), animated: false)
because the texview was underneath the navigation bar and had an adjusted inset

iOS. UITextView. Text jumping after UIKeyboardDidHideNotification

I have editable UITextView and keyboard dismiss mode is interactive. Also my controller is listening two notifications: UIKeyboardWillShowNotification, UIKeyboardWillHideNotification.
func keyboardWillShow(notification: NSNotification) {
if let userInfo = notification.userInfo {
var insets = self.textView.contentInset;
let rect = userInfo[UIKeyboardFrameEndUserInfoKey]?.CGRectValue() ?? CGRectZero
insets.bottom = (rect.size.height - (CGRectGetHeight(self.view.frame) - CGRectGetMaxY(self.textView.frame)))
self.textView.contentInset = insets
self.textView.scrollIndicatorInsets = insets
}
}
func keyboardWillHide(notification: NSNotification) {
self.textView.contentInset = UIEdgeInsetsZero
self.textView.scrollIndicatorInsets = UIEdgeInsetsZero
}
This stuff works great, if text in UITextView doesn't contain any empty lines. If it do, contentOffset jumps to another, random place.
I'm not sure if this is a bug in iOS 7+, or I am doing something wrong.
If it's not a bug, how to get this going fluently without the jumping behaviour?
Thanks for your help.
I had been battling this exact same problem, when I would dismiss the keyboard the UITextView's content offset would jump back to {0, 0}. Interestingly, I only got this behavior on the device, but not in the simulator.
I originally tried to solve it by overriding UITextView's contentOffset method and having it just ignore {0, 0} values, and that was semi effective, until the content got too long, in which case it would just jump to a random offset, and set the same value 3 times (so it would set content offset to {0, 3605}, {0, 3605}, and {0, 3605} all in rapid succession).
After a long time spent looking for a solution, it turned out to be rather simple:
textview.layoutManager.allowsNonContiguousLayout = NO;
As discussed in this blog post. Hope that helps :)
I had 100% exactly the same problem as you and I also asked a question about it but no one could get it right. (I am the one who up voted and favourited your question!!)
I eventually did a workaround after 4 days of frustration. Just put the UITextView inside a UITableView (You don't need to put it inside a UITableViewCell, just drag to the UITableView then it's ok). Make your UITextView unscrollable.
The following method will make UITextView expand and update the UITableView every time it is changed. (Don't forget to connect UITextView's delegate)
func textViewDidChange(textView: UITextView) {
// Change textView height
self.textView.sizeToFit()
UIView.setAnimationsEnabled(false)
self.tableView.beginUpdates()
self.tableView.endUpdates()
UIView.setAnimationsEnabled(true)
}
The following method will make UITableView autoscroll to the cursor when UITextView becomes active.
func textViewDidBeginEditing(textView: UITextView) {
// Delay the following line so that it works properly
let delay = 0.005 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue()) {
var rect = self.textView.caretRectForPosition(self.textView.selectedTextRange?.end)
var changedRect = CGRectMake(rect.origin.x, rect.origin.y, rect.width, rect.height+3)
self.tableView.scrollRectToVisible(changedRect, animated: true)
}
}
You also need to change the UITableView contentInset and scrollIndicatorInsets in your keyboardWillShow and keyboardWillHide methods, depending on your screen layout.

UITableViewWrapperView and UITableView size differs with autolayout

I am building a chat. Everything seem to be quite ok but I bumped into sort of 'buggy' problem.
i got UIViewController with UITextView bar for entering message and UITableView.
They are in this constraint: "V:|-(64)-[chatTable][sendMessageBar]-(keyboard)-|".
When the keyboard is not out - the constant of this constraint is 0. and after keyboard is out - i increase the constant to keyboard height.
when the keyboard is not out:
self.table.contentSize = (375.0,78.5)
self.table.bounds = (0.0,-490.0,375.0,568.5)
self.table.frame = (0.0,64.0,375.0,568.5)
self.table.subviews[0].frame (UITableViewWrapperView) = (0.0,0.0,375.0,568.5)
self.table.subviews[0].frame (UITableViewWrapperView) = (0.0,0.0,375.0,568.5)
and when the keyboard comes out:
self.table.contentSize = (375.0,78.5)
self.table.bounds = (0.0,-274.0,375.0,352.5
self.table.frame = (0.0,64.0,375.0,352.5)
self.table.subviews[0].frame (UITableViewWrapperView) = (0.0,-137.5,375.0,137.5)
self.table.subviews[0].frame (UITableViewWrapperView) = (0.0,0.0,375.0,137.5)
So the UITableViewWrapperView, after I increase constraints constant, differs in size to its superview - UITableView. Is there a way to fix this ? I would assume that UITableViewWrapperView would change its frame and bounds according to UITableView but it does not.
Any ideas where is the problem or how could I work around it ?
ADDING:
After some more research - it seems that it happens somewhere between viewWillLayoutSubviews and viewDidLayoutSubviews. It is kinda weird tho:
override func viewWillLayoutSubviews() {
println("WrapperView Frame :991: \(self.table.subviews[0].frame)") \\ WrapperView Frame :991: (0.0,0.0,375.0,568.5)
super.viewWillLayoutSubviews()
println("WrapperView Frame :992: \(self.table.subviews[0].frame)") \\ WrapperView Frame :992: (0.0,0.0,375.0,568.5)
}
override func viewDidLayoutSubviews() {
println("WrapperView Frame :6: \(self.table.subviews[0].frame)") \\ WrapperView Frame :6: (0.0,-137.5,375.0,137.5)
super.viewDidLayoutSubviews()
println(">> viewDidLayoutSubviews")
}
So it seems that something happens there that messes up the UITableViewWrapperView
The following fixed it for me:
func fixTableViewInsets() {
let zContentInsets = UIEdgeInsetsZero
tableView.contentInset = zContentInsets
tableView.scrollIndicatorInsets = zContentInsets
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
fixTableViewInsets()
}
I discovered that at viewWillAppear() that the insets were all 0. But at viewDidAppear(), they had been modified to apparently offset for navigation bar, etc. This makes the UITableViewWrapperView different from the UITableView.
I changed the insets in its own routine so that it was easier to experiment with calling it from different places. The viewWillLayoutSubviews() let it get changed before being presented - placing the change in viewDidAppear() caused the table to jerk.
I ran into this today and while the fix suggested by #anorskdev works nicely, it seems that the root cause of the issue is the automaticallyAdjustsScrollViewInsets property of UIViewController, which is true by default. I turned it off in my storyboard and the problem went away. Look for the "Adjust Scroll View Insets" checkbox in the View Controller inspector and make sure it's unchecked.
It seems that it is a bug (fighting with this bug took all day for me)
Finally this workaround helped:
for (UIView *subview in tableView.subviews)
{
if ([NSStringFromClass([subview class]) isEqualToString:#"UITableViewWrapperView"])
{
subview.frame = CGRectMake(0, 0, tableView.bounds.size.width, tableView.bounds.size.height);
}
}
After small investigation I have found this solution with setting all the safeAreaInsets and layoutMargins on the UITableView to zero:
Swift 4 snipset:
class CustomTableView: UITableView {
override var safeAreaInsets: UIEdgeInsets {
get {
return .zero
}
}
override var layoutMargins: UIEdgeInsets {
get {
return .zero
}
set {
super.layoutMargins = .zero
}
}
}
The main problem is safeAreaInsets introduced in tvOS 11.0 - the UITableViewWrapperView just took the properties from the parent view (UITableView) and renders the content with safeAreaInsets.
I was facing the same issue on tvOS 11.3, and neither of suggestions related with zero insets or scroll disable did the job, except looping through tableView's subviews and setting the UITableViewWrapperView's frame to the tableView's frame.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
for view in tableView.subviews {
if String(describing: type(of: view)) == "UITableViewWrapperView" {
view.frame = CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: tableView.bounds.size.height)
}
}
}
In iOS 11 UITableViewWrapperView has gone, so this problem may occur only on later iOS versions. I faced it on iOS10 when I pushed custom UIViewController in UINavigationController stack.
So, the solution is to override property automaticallyAdjustsScrollViewInsets in custom view controller like this:
override var automaticallyAdjustsScrollViewInsets: Bool {
get {
return false
}
set {
}
}
Objective C version of this answer given by anorskdev
- (void) viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
[tableView setContentInset:UIEdgeInsetsZero];
[tableView setScrollIndicatorInsets:UIEdgeInsetsZero];
}
edit: Turning off automaticallyAdjustsScrollViewInsets on the hosting ViewController, as suggested by Steve Roy in this answer, also worked and is the one I went with, as it seems cleaner to disable the behaviour rather than correcting it afterwards.

Resources