75

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.

enter image description here

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.

Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
Alex Wulff
  • 2,039
  • 3
  • 18
  • 29
  • Are you using UINavigationBar as header over there, or its custom View? – Mrunal Dec 23 '14 at 15:01
  • @Mrunal I'm not sure what you mean, but it you're asking about the color it is a normal navigationbar from a push segue, just colored in the appdelegate. – Alex Wulff Dec 23 '14 at 15:07
  • 2
    are you setting the text in viewDidLoad? do a dispatch_async on main queue will solve the problem. – Charlie Wu Aug 19 '15 at 23:41

19 Answers19

168

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
}
dimpiax
  • 12,093
  • 5
  • 62
  • 45
David Cruz
  • 2,995
  • 3
  • 28
  • 41
  • 9
    Works great! I still don't understand why we have to do this. Why doesn't it auto start at 0,0? – mikemike396 Feb 27 '15 at 14:41
  • 6
    I think that because it's meant to be a user input area, when you type text, your cursor is located at the end of the text, and if you leave the screen, you want to continue typing from where you left when you come back. – user2700551 Jul 02 '15 at 14:24
  • 2
    I find `scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:NO` works better as it avoids an initial rendering caused by the implicit animation. – devios1 Sep 21 '15 at 21:15
  • This is definitely not working for me. The answer provided by @zeeple is working but if we did use the answer the textview scrolls up on any action performed on the view. Would appreciate some help. – mshrestha Jun 16 '16 at 05:53
  • Hi Manjul, I am not seeing the same behavior in my implementation. I can touch in the view, wiggle it some, etc and it does not auto-scroll to the top. – zeeple Jun 16 '16 at 15:14
  • Only the alternative solution gets the job done, the first one is not working... Why is that? – Mr. Xcoder Sep 07 '16 at 21:18
  • Thank you it's working for me :) but why this issue occur can you please explain. – Chandni Sep 27 '17 at 08:45
54

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 :)

dimpiax
  • 12,093
  • 5
  • 62
  • 45
zeeple
  • 5,509
  • 12
  • 43
  • 71
  • 2
    Maybe because I am using nibs, this is the only one that worked for me. – Rodrigo Pinto Dec 15 '15 at 17:49
  • 3
    @RodrigoPedroso Using storyboard and this was the only one that worked for me as well – Patrick Dec 17 '15 at 17:13
  • 1
    Same here, this is the only solution that worked for me. My text view is in a nib, for the record. Don't know if that changes anything. – Form Dec 23 '15 at 14:30
  • I'm using storyboard. Could this be related to iOS version? – Miguel Ribeiro Jan 21 '16 at 22:23
  • I tried it in `viewDidLayoutSubviews()` and I saw the textView scrolling around when the ViewController loaded, despite animation set to false. I resolved it by putting `setContentOffset` in `viewWillAppear` w/ animation set to false. – Adrian Feb 16 '16 at 00:57
  • This also worked for me (where the accepted answer did not) when using storyboards. – Craig Otis Apr 10 '16 at 13:24
  • CGPointZero is unavailable in newer version of swift use textView.setContentOffset(CGPoint(x: 0, y: 0), animated: false) – nivritgupta Sep 15 '17 at 19:13
14

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

Nick89
  • 2,948
  • 10
  • 31
  • 50
13

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.

keshav vishwkarma
  • 1,832
  • 13
  • 20
  • I do not see where the Adjust Scroll View Insets exists within the Attributes Inspector. – zeeple Oct 31 '15 at 02:59
  • @zeeple It's a ViewController property that will display below the title of the ViewController in Attributes Inspector after selecting ViewController in storyboard. Please check properly I hope you will find it. – keshav vishwkarma Nov 24 '15 at 18:39
8

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
    }
}
CodeOverRide
  • 4,431
  • 43
  • 36
  • Wow. I can't believe that was my issue. What is the benefit to having to do it like this? – Sami Jan 10 '17 at 03:40
6

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.

Mrunal
  • 13,982
  • 6
  • 52
  • 96
  • 1
    No luck on this one either... the text view still is scrolled all the way to the bottom. Could it be having something to do with the automaticallyAdjustsScrollViewInsets property? Because I have the textview placed beneath the navigationbar with this enabled, so the navigation bar does not eclipse the textview. Thanks for the answer. – Alex Wulff Dec 23 '14 at 15:32
  • Try commenting automaticallyAdjustsScrollViewInsets line and check. – Mrunal Dec 23 '14 at 15:40
5

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))
}
Brian Ogden
  • 18,439
  • 10
  • 97
  • 176
Kevin
  • 711
  • 7
  • 19
5

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:

  1. textView.scrollEnabled = false //then set text textView.scrollEnabled = true
  2. textView.scrollRangeToVisible(NSMakeRange(0, 0))
  3. textView.setContentOffset(CGPointZero, animated: false)

And there are two places you can put those codes in:

  1. viewDidLoad()
  2. 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.

outcast
  • 548
  • 1
  • 6
  • 20
  • setting the textview scrollEnabled to false and then true is what worked for me. And I didn't have to put it into viewDidLoad or ViewDidLayoutSubviews. I put the scrollEnabled = false right before we set the text and then I put the scrollEnable = true in a completion handler for an animation that makes the texView appear – Anna Harrison Oct 31 '19 at 17:01
4

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!

Community
  • 1
  • 1
ipe
  • 164
  • 3
  • 7
4

Put this one line of code in ViewDidLoad

self.automaticallyAdjustsScrollViewInsets = false
Murat Yasar
  • 994
  • 9
  • 24
3

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.

Gary Riches
  • 2,847
  • 1
  • 22
  • 19
  • I tried placing `[self.scrollView setContentOffset:CGPointMake(0, -self.scrollView.contentInset.top) animated:YES];` in multiple places: after where I set the code programmatically, in the viewDidLoad, and in the viewWillAppear, but no lucK. I was thinking it had something to do with my navigation bar? – Alex Wulff Dec 23 '14 at 14:52
2

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))
J.Bury
  • 21
  • 5
2

This worked for me:

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

}
bpolat
  • 3,879
  • 20
  • 26
2

This worked for me with Xcode 8.3.3:

-(void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];
    [self.txtQuestion scrollRangeToVisible:NSMakeRange(0, 0)];
}
Obsidian Age
  • 41,205
  • 10
  • 48
  • 71
dave
  • 21
  • 1
1

I translated zeeple's answer to MonoTouch/Xamarin (C#).

public override void ViewDidLayoutSubviews()
{
    base.ViewDidLayoutSubviews();
    myForm.SetContentOffset(new CoreGraphics.CGPoint(0,0), animated: false);
}
kcborys
  • 316
  • 1
  • 11
0

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.

Leon
  • 3,614
  • 1
  • 33
  • 46
0

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];
}
Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
0

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)
Amirca
  • 143
  • 1
  • 8
-1

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.

kelsheikh
  • 1,278
  • 3
  • 17
  • 37