UITextView's textContainer renders at the wrong frame when constraints are changed











up vote
0
down vote

favorite












All I want is to resize the UITextView whenever you type/paste on it. It should be scrollable because I don't want the UITextView to fill up the screen. It could be easily achievable with these few lines of codes.



@IBOutlet weak var mainTextView: UITextView!
@IBOutlet weak var mainTextViewHeightConstraint: NSLayoutConstraint!

override func viewDidLoad() {
super.viewDidLoad()
self.mainTextView.delegate = self
}

// This method is used for calculating what would be the UITextView's height
func heightOf(text: String, for textView: UITextView) -> CGFloat {
let nsstring: NSString = text as NSString
let boundingSize = nsstring.boundingRect(
with: CGSize(width: textView.frame.size.width, height: .greatestFiniteMagnitude),
options: .usesLineFragmentOrigin,
attributes: [.font: textView.font!],
context: nil).size
return ceil(boundingSize.height + textView.textContainerInset.top + textView.textContainerInset.bottom)
}

// The lines inside will change the UITextView's height
func textViewDidChange(_ textView: UITextView) {
let newString: String = textView.text ?? ""
let minHeight = self.heightOf(text: "", for: textView) // 1 line height
let maxHeight = self.heightOf(text: "nnnnn", for: textView) // 6 lines height
let contentHeight = self.heightOf(text: newString, for: textView) // height of the new string
self.mainTextViewHeightConstraint.constant = min(max(minHeight, contentHeight), maxHeight)
}


This works perfectly well, except when the you paste a multi-line text.





To solve the pasting issue, various SO questions suggested that I either create a subclass of UITextView to override the paste(_:) method or I use the textView(_: shouldChangeTextIn....) delegate method. After a few experiments with both the best answer was to use the shouldChangeTextIn method which resulted my code into this



// I replaced `textViewDidChange(_ textView: UITextView)` with this
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
let newString: String = ((textView.text ?? "") as NSString).replacingCharacters(in: range, with: text)
let minHeight = self.heightOf(text: "", for: textView)
let maxHeight = self.heightOf(text: "nnnnn", for: textView)
let contentHeight = self.heightOf(text: newString, for: textView)
self.mainTextViewHeightConstraint.constant = min(max(minHeight, contentHeight), maxHeight)
return true
}




Now what should happen is that when the you paste text the UITextView should look like this. (The pasted string is "AnAnAnAnA" or 5 lines of just A's)



enter image description here



However it becomes like this



enter image description here



As you can see from the screenshots, the textContainer's frame is at the wrong position. Now the weird thing about this is that it seems to only happen when you're pasting at an empty UITextView.



I've tried setting and resetting the UITextView's frame.size, I've tried manually setting the NSTextContainer's size, and some other hacky solutions but I just can't seem to fix it. How can I solve this?



Note:



OS Support: iOS 8.xx - iOS 12.xx
Xcode: v10.10
Swift: 3 and 4 (I tried on both)



PS: yes I have already been on other SO questions such ones listed on the bottom and a few others




  • TextContainer isn't resizing while changing bounds of UITextView

  • UITextView's text going beyond bounds

  • UITextView stange animation glitch on paste action (iOS11)

  • How to calculate TextView height base on text










share|improve this question




























    up vote
    0
    down vote

    favorite












    All I want is to resize the UITextView whenever you type/paste on it. It should be scrollable because I don't want the UITextView to fill up the screen. It could be easily achievable with these few lines of codes.



    @IBOutlet weak var mainTextView: UITextView!
    @IBOutlet weak var mainTextViewHeightConstraint: NSLayoutConstraint!

    override func viewDidLoad() {
    super.viewDidLoad()
    self.mainTextView.delegate = self
    }

    // This method is used for calculating what would be the UITextView's height
    func heightOf(text: String, for textView: UITextView) -> CGFloat {
    let nsstring: NSString = text as NSString
    let boundingSize = nsstring.boundingRect(
    with: CGSize(width: textView.frame.size.width, height: .greatestFiniteMagnitude),
    options: .usesLineFragmentOrigin,
    attributes: [.font: textView.font!],
    context: nil).size
    return ceil(boundingSize.height + textView.textContainerInset.top + textView.textContainerInset.bottom)
    }

    // The lines inside will change the UITextView's height
    func textViewDidChange(_ textView: UITextView) {
    let newString: String = textView.text ?? ""
    let minHeight = self.heightOf(text: "", for: textView) // 1 line height
    let maxHeight = self.heightOf(text: "nnnnn", for: textView) // 6 lines height
    let contentHeight = self.heightOf(text: newString, for: textView) // height of the new string
    self.mainTextViewHeightConstraint.constant = min(max(minHeight, contentHeight), maxHeight)
    }


    This works perfectly well, except when the you paste a multi-line text.





    To solve the pasting issue, various SO questions suggested that I either create a subclass of UITextView to override the paste(_:) method or I use the textView(_: shouldChangeTextIn....) delegate method. After a few experiments with both the best answer was to use the shouldChangeTextIn method which resulted my code into this



    // I replaced `textViewDidChange(_ textView: UITextView)` with this
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    let newString: String = ((textView.text ?? "") as NSString).replacingCharacters(in: range, with: text)
    let minHeight = self.heightOf(text: "", for: textView)
    let maxHeight = self.heightOf(text: "nnnnn", for: textView)
    let contentHeight = self.heightOf(text: newString, for: textView)
    self.mainTextViewHeightConstraint.constant = min(max(minHeight, contentHeight), maxHeight)
    return true
    }




    Now what should happen is that when the you paste text the UITextView should look like this. (The pasted string is "AnAnAnAnA" or 5 lines of just A's)



    enter image description here



    However it becomes like this



    enter image description here



    As you can see from the screenshots, the textContainer's frame is at the wrong position. Now the weird thing about this is that it seems to only happen when you're pasting at an empty UITextView.



    I've tried setting and resetting the UITextView's frame.size, I've tried manually setting the NSTextContainer's size, and some other hacky solutions but I just can't seem to fix it. How can I solve this?



    Note:



    OS Support: iOS 8.xx - iOS 12.xx
    Xcode: v10.10
    Swift: 3 and 4 (I tried on both)



    PS: yes I have already been on other SO questions such ones listed on the bottom and a few others




    • TextContainer isn't resizing while changing bounds of UITextView

    • UITextView's text going beyond bounds

    • UITextView stange animation glitch on paste action (iOS11)

    • How to calculate TextView height base on text










    share|improve this question


























      up vote
      0
      down vote

      favorite









      up vote
      0
      down vote

      favorite











      All I want is to resize the UITextView whenever you type/paste on it. It should be scrollable because I don't want the UITextView to fill up the screen. It could be easily achievable with these few lines of codes.



      @IBOutlet weak var mainTextView: UITextView!
      @IBOutlet weak var mainTextViewHeightConstraint: NSLayoutConstraint!

      override func viewDidLoad() {
      super.viewDidLoad()
      self.mainTextView.delegate = self
      }

      // This method is used for calculating what would be the UITextView's height
      func heightOf(text: String, for textView: UITextView) -> CGFloat {
      let nsstring: NSString = text as NSString
      let boundingSize = nsstring.boundingRect(
      with: CGSize(width: textView.frame.size.width, height: .greatestFiniteMagnitude),
      options: .usesLineFragmentOrigin,
      attributes: [.font: textView.font!],
      context: nil).size
      return ceil(boundingSize.height + textView.textContainerInset.top + textView.textContainerInset.bottom)
      }

      // The lines inside will change the UITextView's height
      func textViewDidChange(_ textView: UITextView) {
      let newString: String = textView.text ?? ""
      let minHeight = self.heightOf(text: "", for: textView) // 1 line height
      let maxHeight = self.heightOf(text: "nnnnn", for: textView) // 6 lines height
      let contentHeight = self.heightOf(text: newString, for: textView) // height of the new string
      self.mainTextViewHeightConstraint.constant = min(max(minHeight, contentHeight), maxHeight)
      }


      This works perfectly well, except when the you paste a multi-line text.





      To solve the pasting issue, various SO questions suggested that I either create a subclass of UITextView to override the paste(_:) method or I use the textView(_: shouldChangeTextIn....) delegate method. After a few experiments with both the best answer was to use the shouldChangeTextIn method which resulted my code into this



      // I replaced `textViewDidChange(_ textView: UITextView)` with this
      func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
      let newString: String = ((textView.text ?? "") as NSString).replacingCharacters(in: range, with: text)
      let minHeight = self.heightOf(text: "", for: textView)
      let maxHeight = self.heightOf(text: "nnnnn", for: textView)
      let contentHeight = self.heightOf(text: newString, for: textView)
      self.mainTextViewHeightConstraint.constant = min(max(minHeight, contentHeight), maxHeight)
      return true
      }




      Now what should happen is that when the you paste text the UITextView should look like this. (The pasted string is "AnAnAnAnA" or 5 lines of just A's)



      enter image description here



      However it becomes like this



      enter image description here



      As you can see from the screenshots, the textContainer's frame is at the wrong position. Now the weird thing about this is that it seems to only happen when you're pasting at an empty UITextView.



      I've tried setting and resetting the UITextView's frame.size, I've tried manually setting the NSTextContainer's size, and some other hacky solutions but I just can't seem to fix it. How can I solve this?



      Note:



      OS Support: iOS 8.xx - iOS 12.xx
      Xcode: v10.10
      Swift: 3 and 4 (I tried on both)



      PS: yes I have already been on other SO questions such ones listed on the bottom and a few others




      • TextContainer isn't resizing while changing bounds of UITextView

      • UITextView's text going beyond bounds

      • UITextView stange animation glitch on paste action (iOS11)

      • How to calculate TextView height base on text










      share|improve this question















      All I want is to resize the UITextView whenever you type/paste on it. It should be scrollable because I don't want the UITextView to fill up the screen. It could be easily achievable with these few lines of codes.



      @IBOutlet weak var mainTextView: UITextView!
      @IBOutlet weak var mainTextViewHeightConstraint: NSLayoutConstraint!

      override func viewDidLoad() {
      super.viewDidLoad()
      self.mainTextView.delegate = self
      }

      // This method is used for calculating what would be the UITextView's height
      func heightOf(text: String, for textView: UITextView) -> CGFloat {
      let nsstring: NSString = text as NSString
      let boundingSize = nsstring.boundingRect(
      with: CGSize(width: textView.frame.size.width, height: .greatestFiniteMagnitude),
      options: .usesLineFragmentOrigin,
      attributes: [.font: textView.font!],
      context: nil).size
      return ceil(boundingSize.height + textView.textContainerInset.top + textView.textContainerInset.bottom)
      }

      // The lines inside will change the UITextView's height
      func textViewDidChange(_ textView: UITextView) {
      let newString: String = textView.text ?? ""
      let minHeight = self.heightOf(text: "", for: textView) // 1 line height
      let maxHeight = self.heightOf(text: "nnnnn", for: textView) // 6 lines height
      let contentHeight = self.heightOf(text: newString, for: textView) // height of the new string
      self.mainTextViewHeightConstraint.constant = min(max(minHeight, contentHeight), maxHeight)
      }


      This works perfectly well, except when the you paste a multi-line text.





      To solve the pasting issue, various SO questions suggested that I either create a subclass of UITextView to override the paste(_:) method or I use the textView(_: shouldChangeTextIn....) delegate method. After a few experiments with both the best answer was to use the shouldChangeTextIn method which resulted my code into this



      // I replaced `textViewDidChange(_ textView: UITextView)` with this
      func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
      let newString: String = ((textView.text ?? "") as NSString).replacingCharacters(in: range, with: text)
      let minHeight = self.heightOf(text: "", for: textView)
      let maxHeight = self.heightOf(text: "nnnnn", for: textView)
      let contentHeight = self.heightOf(text: newString, for: textView)
      self.mainTextViewHeightConstraint.constant = min(max(minHeight, contentHeight), maxHeight)
      return true
      }




      Now what should happen is that when the you paste text the UITextView should look like this. (The pasted string is "AnAnAnAnA" or 5 lines of just A's)



      enter image description here



      However it becomes like this



      enter image description here



      As you can see from the screenshots, the textContainer's frame is at the wrong position. Now the weird thing about this is that it seems to only happen when you're pasting at an empty UITextView.



      I've tried setting and resetting the UITextView's frame.size, I've tried manually setting the NSTextContainer's size, and some other hacky solutions but I just can't seem to fix it. How can I solve this?



      Note:



      OS Support: iOS 8.xx - iOS 12.xx
      Xcode: v10.10
      Swift: 3 and 4 (I tried on both)



      PS: yes I have already been on other SO questions such ones listed on the bottom and a few others




      • TextContainer isn't resizing while changing bounds of UITextView

      • UITextView's text going beyond bounds

      • UITextView stange animation glitch on paste action (iOS11)

      • How to calculate TextView height base on text







      ios swift uitextview






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Nov 9 at 2:30

























      asked Nov 8 at 11:05









      Zonily Jame

      1,4921925




      1,4921925
























          1 Answer
          1






          active

          oldest

          votes

















          up vote
          0
          down vote













          You can ignore all the code for calculating height and use the set the textview attribute scrolling Enabled = false
          the textview will adjust it self to fit with the text content
          image1



          then change the code as you want



          @IBOutlet weak var textView: UITextView!

          override func viewDidLoad() {
          super.viewDidLoad()
          self.textView.text = "AnAnAnAnA"
          }


          The result will be like this
          image2






          share|improve this answer





















          • Nope, that doesn't solve my problem, let me edit my question so it can be clearer.
            – Zonily Jame
            Nov 9 at 2:25











          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














           

          draft saved


          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53206469%2fuitextviews-textcontainer-renders-at-the-wrong-frame-when-constraints-are-chang%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes








          up vote
          0
          down vote













          You can ignore all the code for calculating height and use the set the textview attribute scrolling Enabled = false
          the textview will adjust it self to fit with the text content
          image1



          then change the code as you want



          @IBOutlet weak var textView: UITextView!

          override func viewDidLoad() {
          super.viewDidLoad()
          self.textView.text = "AnAnAnAnA"
          }


          The result will be like this
          image2






          share|improve this answer





















          • Nope, that doesn't solve my problem, let me edit my question so it can be clearer.
            – Zonily Jame
            Nov 9 at 2:25















          up vote
          0
          down vote













          You can ignore all the code for calculating height and use the set the textview attribute scrolling Enabled = false
          the textview will adjust it self to fit with the text content
          image1



          then change the code as you want



          @IBOutlet weak var textView: UITextView!

          override func viewDidLoad() {
          super.viewDidLoad()
          self.textView.text = "AnAnAnAnA"
          }


          The result will be like this
          image2






          share|improve this answer





















          • Nope, that doesn't solve my problem, let me edit my question so it can be clearer.
            – Zonily Jame
            Nov 9 at 2:25













          up vote
          0
          down vote










          up vote
          0
          down vote









          You can ignore all the code for calculating height and use the set the textview attribute scrolling Enabled = false
          the textview will adjust it self to fit with the text content
          image1



          then change the code as you want



          @IBOutlet weak var textView: UITextView!

          override func viewDidLoad() {
          super.viewDidLoad()
          self.textView.text = "AnAnAnAnA"
          }


          The result will be like this
          image2






          share|improve this answer












          You can ignore all the code for calculating height and use the set the textview attribute scrolling Enabled = false
          the textview will adjust it self to fit with the text content
          image1



          then change the code as you want



          @IBOutlet weak var textView: UITextView!

          override func viewDidLoad() {
          super.viewDidLoad()
          self.textView.text = "AnAnAnAnA"
          }


          The result will be like this
          image2







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Nov 8 at 14:10









          Hussam

          11




          11












          • Nope, that doesn't solve my problem, let me edit my question so it can be clearer.
            – Zonily Jame
            Nov 9 at 2:25


















          • Nope, that doesn't solve my problem, let me edit my question so it can be clearer.
            – Zonily Jame
            Nov 9 at 2:25
















          Nope, that doesn't solve my problem, let me edit my question so it can be clearer.
          – Zonily Jame
          Nov 9 at 2:25




          Nope, that doesn't solve my problem, let me edit my question so it can be clearer.
          – Zonily Jame
          Nov 9 at 2:25


















           

          draft saved


          draft discarded



















































           


          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53206469%2fuitextviews-textcontainer-renders-at-the-wrong-frame-when-constraints-are-chang%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Schultheiß

          Verwaltungsgliederung Dänemarks

          Liste der Kulturdenkmale in Wilsdruff