Where can I set contentOffset to the last item in UICollectionView before the view appears?





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{ height:90px;width:728px;box-sizing:border-box;
}







4















I am scrolling multiple horizontal collectionViews to the last (Far right) item using:



 timelineCollectionView.scrollToMaxContentOffset(animated: false)
postsCollectionView.scrollToMaxContentOffset(animated: false)


this works great, except I can't figure out where to put them.



Various places I've tried:



viewWillAppear - it doesn't scroll as though the cells aren't fully loaded yet.



viewDidAppear - it does scroll perfectly. Awesome! Except now you can see it scroll even when you put animated: false. The collectionView loads at the far left for a split second before updating to the far right



viewDidLayoutSubviews - this works perfectly - timing and everything! However, it gets called many times. If I could determine which subview was just laid out, perhaps I could scroll it in there but Im not sure how.



Is there a better option? How can I do this?



Also these are the functions I am using to set content offset:



extension UIScrollView {

var minContentOffset: CGPoint {
return CGPoint(
x: -contentInset.left,
y: -contentInset.top)
}

var maxContentOffset: CGPoint {
return CGPoint(
x: contentSize.width - bounds.width + contentInset.right,
y: contentSize.height - bounds.height + contentInset.bottom)
}

func scrollToMinContentOffset(animated: Bool) {
setContentOffset(minContentOffset, animated: animated)
}

func scrollToMaxContentOffset(animated: Bool) {
setContentOffset(maxContentOffset, animated: animated)
}
}









share|improve this question































    4















    I am scrolling multiple horizontal collectionViews to the last (Far right) item using:



     timelineCollectionView.scrollToMaxContentOffset(animated: false)
    postsCollectionView.scrollToMaxContentOffset(animated: false)


    this works great, except I can't figure out where to put them.



    Various places I've tried:



    viewWillAppear - it doesn't scroll as though the cells aren't fully loaded yet.



    viewDidAppear - it does scroll perfectly. Awesome! Except now you can see it scroll even when you put animated: false. The collectionView loads at the far left for a split second before updating to the far right



    viewDidLayoutSubviews - this works perfectly - timing and everything! However, it gets called many times. If I could determine which subview was just laid out, perhaps I could scroll it in there but Im not sure how.



    Is there a better option? How can I do this?



    Also these are the functions I am using to set content offset:



    extension UIScrollView {

    var minContentOffset: CGPoint {
    return CGPoint(
    x: -contentInset.left,
    y: -contentInset.top)
    }

    var maxContentOffset: CGPoint {
    return CGPoint(
    x: contentSize.width - bounds.width + contentInset.right,
    y: contentSize.height - bounds.height + contentInset.bottom)
    }

    func scrollToMinContentOffset(animated: Bool) {
    setContentOffset(minContentOffset, animated: animated)
    }

    func scrollToMaxContentOffset(animated: Bool) {
    setContentOffset(maxContentOffset, animated: animated)
    }
    }









    share|improve this question



























      4












      4








      4


      0






      I am scrolling multiple horizontal collectionViews to the last (Far right) item using:



       timelineCollectionView.scrollToMaxContentOffset(animated: false)
      postsCollectionView.scrollToMaxContentOffset(animated: false)


      this works great, except I can't figure out where to put them.



      Various places I've tried:



      viewWillAppear - it doesn't scroll as though the cells aren't fully loaded yet.



      viewDidAppear - it does scroll perfectly. Awesome! Except now you can see it scroll even when you put animated: false. The collectionView loads at the far left for a split second before updating to the far right



      viewDidLayoutSubviews - this works perfectly - timing and everything! However, it gets called many times. If I could determine which subview was just laid out, perhaps I could scroll it in there but Im not sure how.



      Is there a better option? How can I do this?



      Also these are the functions I am using to set content offset:



      extension UIScrollView {

      var minContentOffset: CGPoint {
      return CGPoint(
      x: -contentInset.left,
      y: -contentInset.top)
      }

      var maxContentOffset: CGPoint {
      return CGPoint(
      x: contentSize.width - bounds.width + contentInset.right,
      y: contentSize.height - bounds.height + contentInset.bottom)
      }

      func scrollToMinContentOffset(animated: Bool) {
      setContentOffset(minContentOffset, animated: animated)
      }

      func scrollToMaxContentOffset(animated: Bool) {
      setContentOffset(maxContentOffset, animated: animated)
      }
      }









      share|improve this question
















      I am scrolling multiple horizontal collectionViews to the last (Far right) item using:



       timelineCollectionView.scrollToMaxContentOffset(animated: false)
      postsCollectionView.scrollToMaxContentOffset(animated: false)


      this works great, except I can't figure out where to put them.



      Various places I've tried:



      viewWillAppear - it doesn't scroll as though the cells aren't fully loaded yet.



      viewDidAppear - it does scroll perfectly. Awesome! Except now you can see it scroll even when you put animated: false. The collectionView loads at the far left for a split second before updating to the far right



      viewDidLayoutSubviews - this works perfectly - timing and everything! However, it gets called many times. If I could determine which subview was just laid out, perhaps I could scroll it in there but Im not sure how.



      Is there a better option? How can I do this?



      Also these are the functions I am using to set content offset:



      extension UIScrollView {

      var minContentOffset: CGPoint {
      return CGPoint(
      x: -contentInset.left,
      y: -contentInset.top)
      }

      var maxContentOffset: CGPoint {
      return CGPoint(
      x: contentSize.width - bounds.width + contentInset.right,
      y: contentSize.height - bounds.height + contentInset.bottom)
      }

      func scrollToMinContentOffset(animated: Bool) {
      setContentOffset(minContentOffset, animated: animated)
      }

      func scrollToMaxContentOffset(animated: Bool) {
      setContentOffset(maxContentOffset, animated: animated)
      }
      }






      ios swift uicollectionview






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Dec 28 '18 at 19:39







      BigBoy1337

















      asked Dec 28 '18 at 1:49









      BigBoy1337BigBoy1337

      1,06673675




      1,06673675
























          4 Answers
          4






          active

          oldest

          votes


















          1















          Is there a better option?




          Yes there is, and that would be UICollectionView.scrollToItem(at:at:animated:)



          Next is to wait until cells are dequeued so we can call UICollectionView.scrollToItem(at:at:animated:), and there are multiple ways you can do this.



          You can call UICollectionView.reloadData within animateWithDuration:animations:completion it works both in viewDidLoad or viewWillAppear, scrolling is seemless too, like so:



          func scrollToItem(at index: Int) {
          UIView.animate(withDuration: 0.0, animations: { [weak self] in
          self?.collectionView.reloadData()
          }, completion: { [weak self] (finished) in
          if let count = self?.dataSource?.count, index < count {
          let indexPath = IndexPath(item: index, section: 0)
          self!.collectionView.scrollToItem(at: indexPath, at: UICollectionView.ScrollPosition.right, animated: false)
          }
          })
          }


          Another way is to dispatch a code block to main queue after UICollectionView.reloadData, like so:



          func scrollToItem(at index: Int) {
          self.collectionView.reloadData()
          DispatchQueue.main.async { [weak self] in
          // this will be executed after cells dequeuing
          if let count = self?.dataSource?.count, index < count {
          let indexPath = IndexPath(item: index, section: 0)
          self!.collectionView.scrollToItem(at: indexPath, at: UICollectionView.ScrollPosition.right, animated: false)
          }
          }
          }


          I have also created a gist where I am using a horizontal UICollectionView, just create a new xcode project and replace ViewController.swift with the gist source code.






          share|improve this answer


























          • I need to use setContentOffset rather than scrollToItem because I am using a custom colletionView layout that changes the content size and screws up the width (stackoverflow.com/questions/53770783/…)

            – BigBoy1337
            Dec 31 '18 at 22:06











          • Instead of scrollToItem, please try try within completion or DispatchQueue.main.async closure to just scrollToMaxContentOffset(animated: false)?

            – AamirR
            Jan 1 at 12:52





















          0














          you can use performBatchUpdate in viewdidLoad.



          timelineCollectionView.reloadData()
          timelineCollectionView.performBatchUpdates(nil, completion: {
          (result) in
          timelineCollectionView.scrollToMaxContentOffset(animated: false)
          })


          Second option is you can put observer for collection view ContentSize.



          override func viewDidLoad() {
          super.viewDidLoad()
          timelineCollectionView.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.old, context: nil)
          }

          override func viewWillDisappear(_ animated: Bool) {
          super.viewWillDisappear(animated)
          timelineCollectionView.removeObserver(self, forKeyPath: "contentSize")
          }

          override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
          if let observedObject = object as? UICollectionView, observedObject == timelineCollectionView {
          print("set ContentOffset here")
          timelineCollectionView.scrollToMaxContentOffset(animated: false)
          }
          }





          share|improve this answer

































            0














            As you need the content size and the bounds of your scrollView to compute your target content offset, you need your scrollView to be laid out before setting it.



            viewDidLayoutSubviews looks good to me too. It is called each time the view of your view controller has just laid out its subviews (only its subviews, not the subviews of its subviews). Just add a flag to make sure the change is done only once. The hack is pretty common when you handle rotations or trait collection changes.



            var firstTime = true

            override func viewDidLayoutSubviews() {
            super.viewDidLayoutSubviews()
            guard firstTime else { return }
            firstTime = false
            collectionView.scrollToMaxContentOffset(animated: false)
            }


            Is there a better option?




            • viewWillAppear is called too early. The bounds and the content size are wrong at this time.


            • viewDidAppear is called too late. Too bad, the layout is done though. It is called once the animation of the view controller presentation is done, so the content offset of your scroll view will be at zero during all the animation duration.


            • Hacking the completion blocks of UIView.animation or performBatchUpdates ?
              Well, it works but this is totally counterintuitive. I think this is not a proper solution.



            Besides, as specified in the documentation :




            completion: [...] If the duration of the animation is 0, this block is performed at the beginning of the next run loop cycle.




            You are just adding an extra useless cycle. viewDidLayoutSubviews is called just before the end of the current one.






            share|improve this answer


























            • the problem is that this gets called multiple times. So its called the first time when laying out a view other than the collectionView, sets it to false, and then when it gets called again for the collectionView, its already false

              – BigBoy1337
              Jan 7 at 3:14











            • What about keeping track of the collection view frame ? You set the content offset each time previousFrame != collectionView.frame.

              – GaétanZ
              Jan 7 at 8:01











            • hmm where would I do that?

              – BigBoy1337
              Jan 7 at 22:37











            • Instead of 'firstTime'. You store 'previousFrame = collectionView.frame' each time it changes and set the content offset at the same time.

              – GaétanZ
              Jan 7 at 23:07











            • but I don't want it to change every time the collectionView frame changes... Only the first time the view loads. In which function would "if previousFrame != collectionView.frame { scrollToMaxOffset... } go?

              – BigBoy1337
              Jan 7 at 23:21



















            0














            It seems the best place to do it is in viewWillAppear and its now working. Note that I had to add some calls:



            override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            let postCount = posts.fetchedObjects?.count
            let lastPostIndex = IndexPath(item: max(postCount! - 1,0), section: 0)
            postsCollectionView.setNeedsLayout()
            postsCollectionView.layoutIfNeeded()
            postsCollectionView.scrollToItem(at: lastPostIndex, at: .centeredHorizontally, animated: false) // you can also do setContentOffset here
            }


            The reason that my viewWillAppear wasn't working before is that it didn't know the contentSize of my collectionView and it thought the contentSize was 0 so scrollToItem and setContentOffset didn't move it anywhere. layoutIfNeeded() checks the contentOffset and now that its non-zero, it moves it the appropriate amount.



            Edit: In fact you can even put this in ViewDidLoad if you want so that it doesn't re-fire when you add and dismiss a supplementary view for instance. The key really is that layoutIfNeeded command which accesses the contentSize.






            share|improve this answer


























              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',
              autoActivateHeartbeat: false,
              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%2f53952825%2fwhere-can-i-set-contentoffset-to-the-last-item-in-uicollectionview-before-the-vi%23new-answer', 'question_page');
              }
              );

              Post as a guest















              Required, but never shown

























              4 Answers
              4






              active

              oldest

              votes








              4 Answers
              4






              active

              oldest

              votes









              active

              oldest

              votes






              active

              oldest

              votes









              1















              Is there a better option?




              Yes there is, and that would be UICollectionView.scrollToItem(at:at:animated:)



              Next is to wait until cells are dequeued so we can call UICollectionView.scrollToItem(at:at:animated:), and there are multiple ways you can do this.



              You can call UICollectionView.reloadData within animateWithDuration:animations:completion it works both in viewDidLoad or viewWillAppear, scrolling is seemless too, like so:



              func scrollToItem(at index: Int) {
              UIView.animate(withDuration: 0.0, animations: { [weak self] in
              self?.collectionView.reloadData()
              }, completion: { [weak self] (finished) in
              if let count = self?.dataSource?.count, index < count {
              let indexPath = IndexPath(item: index, section: 0)
              self!.collectionView.scrollToItem(at: indexPath, at: UICollectionView.ScrollPosition.right, animated: false)
              }
              })
              }


              Another way is to dispatch a code block to main queue after UICollectionView.reloadData, like so:



              func scrollToItem(at index: Int) {
              self.collectionView.reloadData()
              DispatchQueue.main.async { [weak self] in
              // this will be executed after cells dequeuing
              if let count = self?.dataSource?.count, index < count {
              let indexPath = IndexPath(item: index, section: 0)
              self!.collectionView.scrollToItem(at: indexPath, at: UICollectionView.ScrollPosition.right, animated: false)
              }
              }
              }


              I have also created a gist where I am using a horizontal UICollectionView, just create a new xcode project and replace ViewController.swift with the gist source code.






              share|improve this answer


























              • I need to use setContentOffset rather than scrollToItem because I am using a custom colletionView layout that changes the content size and screws up the width (stackoverflow.com/questions/53770783/…)

                – BigBoy1337
                Dec 31 '18 at 22:06











              • Instead of scrollToItem, please try try within completion or DispatchQueue.main.async closure to just scrollToMaxContentOffset(animated: false)?

                – AamirR
                Jan 1 at 12:52


















              1















              Is there a better option?




              Yes there is, and that would be UICollectionView.scrollToItem(at:at:animated:)



              Next is to wait until cells are dequeued so we can call UICollectionView.scrollToItem(at:at:animated:), and there are multiple ways you can do this.



              You can call UICollectionView.reloadData within animateWithDuration:animations:completion it works both in viewDidLoad or viewWillAppear, scrolling is seemless too, like so:



              func scrollToItem(at index: Int) {
              UIView.animate(withDuration: 0.0, animations: { [weak self] in
              self?.collectionView.reloadData()
              }, completion: { [weak self] (finished) in
              if let count = self?.dataSource?.count, index < count {
              let indexPath = IndexPath(item: index, section: 0)
              self!.collectionView.scrollToItem(at: indexPath, at: UICollectionView.ScrollPosition.right, animated: false)
              }
              })
              }


              Another way is to dispatch a code block to main queue after UICollectionView.reloadData, like so:



              func scrollToItem(at index: Int) {
              self.collectionView.reloadData()
              DispatchQueue.main.async { [weak self] in
              // this will be executed after cells dequeuing
              if let count = self?.dataSource?.count, index < count {
              let indexPath = IndexPath(item: index, section: 0)
              self!.collectionView.scrollToItem(at: indexPath, at: UICollectionView.ScrollPosition.right, animated: false)
              }
              }
              }


              I have also created a gist where I am using a horizontal UICollectionView, just create a new xcode project and replace ViewController.swift with the gist source code.






              share|improve this answer


























              • I need to use setContentOffset rather than scrollToItem because I am using a custom colletionView layout that changes the content size and screws up the width (stackoverflow.com/questions/53770783/…)

                – BigBoy1337
                Dec 31 '18 at 22:06











              • Instead of scrollToItem, please try try within completion or DispatchQueue.main.async closure to just scrollToMaxContentOffset(animated: false)?

                – AamirR
                Jan 1 at 12:52
















              1












              1








              1








              Is there a better option?




              Yes there is, and that would be UICollectionView.scrollToItem(at:at:animated:)



              Next is to wait until cells are dequeued so we can call UICollectionView.scrollToItem(at:at:animated:), and there are multiple ways you can do this.



              You can call UICollectionView.reloadData within animateWithDuration:animations:completion it works both in viewDidLoad or viewWillAppear, scrolling is seemless too, like so:



              func scrollToItem(at index: Int) {
              UIView.animate(withDuration: 0.0, animations: { [weak self] in
              self?.collectionView.reloadData()
              }, completion: { [weak self] (finished) in
              if let count = self?.dataSource?.count, index < count {
              let indexPath = IndexPath(item: index, section: 0)
              self!.collectionView.scrollToItem(at: indexPath, at: UICollectionView.ScrollPosition.right, animated: false)
              }
              })
              }


              Another way is to dispatch a code block to main queue after UICollectionView.reloadData, like so:



              func scrollToItem(at index: Int) {
              self.collectionView.reloadData()
              DispatchQueue.main.async { [weak self] in
              // this will be executed after cells dequeuing
              if let count = self?.dataSource?.count, index < count {
              let indexPath = IndexPath(item: index, section: 0)
              self!.collectionView.scrollToItem(at: indexPath, at: UICollectionView.ScrollPosition.right, animated: false)
              }
              }
              }


              I have also created a gist where I am using a horizontal UICollectionView, just create a new xcode project and replace ViewController.swift with the gist source code.






              share|improve this answer
















              Is there a better option?




              Yes there is, and that would be UICollectionView.scrollToItem(at:at:animated:)



              Next is to wait until cells are dequeued so we can call UICollectionView.scrollToItem(at:at:animated:), and there are multiple ways you can do this.



              You can call UICollectionView.reloadData within animateWithDuration:animations:completion it works both in viewDidLoad or viewWillAppear, scrolling is seemless too, like so:



              func scrollToItem(at index: Int) {
              UIView.animate(withDuration: 0.0, animations: { [weak self] in
              self?.collectionView.reloadData()
              }, completion: { [weak self] (finished) in
              if let count = self?.dataSource?.count, index < count {
              let indexPath = IndexPath(item: index, section: 0)
              self!.collectionView.scrollToItem(at: indexPath, at: UICollectionView.ScrollPosition.right, animated: false)
              }
              })
              }


              Another way is to dispatch a code block to main queue after UICollectionView.reloadData, like so:



              func scrollToItem(at index: Int) {
              self.collectionView.reloadData()
              DispatchQueue.main.async { [weak self] in
              // this will be executed after cells dequeuing
              if let count = self?.dataSource?.count, index < count {
              let indexPath = IndexPath(item: index, section: 0)
              self!.collectionView.scrollToItem(at: indexPath, at: UICollectionView.ScrollPosition.right, animated: false)
              }
              }
              }


              I have also created a gist where I am using a horizontal UICollectionView, just create a new xcode project and replace ViewController.swift with the gist source code.







              share|improve this answer














              share|improve this answer



              share|improve this answer








              edited Dec 31 '18 at 20:01

























              answered Dec 31 '18 at 19:12









              AamirRAamirR

              5,12312938




              5,12312938













              • I need to use setContentOffset rather than scrollToItem because I am using a custom colletionView layout that changes the content size and screws up the width (stackoverflow.com/questions/53770783/…)

                – BigBoy1337
                Dec 31 '18 at 22:06











              • Instead of scrollToItem, please try try within completion or DispatchQueue.main.async closure to just scrollToMaxContentOffset(animated: false)?

                – AamirR
                Jan 1 at 12:52





















              • I need to use setContentOffset rather than scrollToItem because I am using a custom colletionView layout that changes the content size and screws up the width (stackoverflow.com/questions/53770783/…)

                – BigBoy1337
                Dec 31 '18 at 22:06











              • Instead of scrollToItem, please try try within completion or DispatchQueue.main.async closure to just scrollToMaxContentOffset(animated: false)?

                – AamirR
                Jan 1 at 12:52



















              I need to use setContentOffset rather than scrollToItem because I am using a custom colletionView layout that changes the content size and screws up the width (stackoverflow.com/questions/53770783/…)

              – BigBoy1337
              Dec 31 '18 at 22:06





              I need to use setContentOffset rather than scrollToItem because I am using a custom colletionView layout that changes the content size and screws up the width (stackoverflow.com/questions/53770783/…)

              – BigBoy1337
              Dec 31 '18 at 22:06













              Instead of scrollToItem, please try try within completion or DispatchQueue.main.async closure to just scrollToMaxContentOffset(animated: false)?

              – AamirR
              Jan 1 at 12:52







              Instead of scrollToItem, please try try within completion or DispatchQueue.main.async closure to just scrollToMaxContentOffset(animated: false)?

              – AamirR
              Jan 1 at 12:52















              0














              you can use performBatchUpdate in viewdidLoad.



              timelineCollectionView.reloadData()
              timelineCollectionView.performBatchUpdates(nil, completion: {
              (result) in
              timelineCollectionView.scrollToMaxContentOffset(animated: false)
              })


              Second option is you can put observer for collection view ContentSize.



              override func viewDidLoad() {
              super.viewDidLoad()
              timelineCollectionView.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.old, context: nil)
              }

              override func viewWillDisappear(_ animated: Bool) {
              super.viewWillDisappear(animated)
              timelineCollectionView.removeObserver(self, forKeyPath: "contentSize")
              }

              override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
              if let observedObject = object as? UICollectionView, observedObject == timelineCollectionView {
              print("set ContentOffset here")
              timelineCollectionView.scrollToMaxContentOffset(animated: false)
              }
              }





              share|improve this answer






























                0














                you can use performBatchUpdate in viewdidLoad.



                timelineCollectionView.reloadData()
                timelineCollectionView.performBatchUpdates(nil, completion: {
                (result) in
                timelineCollectionView.scrollToMaxContentOffset(animated: false)
                })


                Second option is you can put observer for collection view ContentSize.



                override func viewDidLoad() {
                super.viewDidLoad()
                timelineCollectionView.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.old, context: nil)
                }

                override func viewWillDisappear(_ animated: Bool) {
                super.viewWillDisappear(animated)
                timelineCollectionView.removeObserver(self, forKeyPath: "contentSize")
                }

                override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
                if let observedObject = object as? UICollectionView, observedObject == timelineCollectionView {
                print("set ContentOffset here")
                timelineCollectionView.scrollToMaxContentOffset(animated: false)
                }
                }





                share|improve this answer




























                  0












                  0








                  0







                  you can use performBatchUpdate in viewdidLoad.



                  timelineCollectionView.reloadData()
                  timelineCollectionView.performBatchUpdates(nil, completion: {
                  (result) in
                  timelineCollectionView.scrollToMaxContentOffset(animated: false)
                  })


                  Second option is you can put observer for collection view ContentSize.



                  override func viewDidLoad() {
                  super.viewDidLoad()
                  timelineCollectionView.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.old, context: nil)
                  }

                  override func viewWillDisappear(_ animated: Bool) {
                  super.viewWillDisappear(animated)
                  timelineCollectionView.removeObserver(self, forKeyPath: "contentSize")
                  }

                  override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
                  if let observedObject = object as? UICollectionView, observedObject == timelineCollectionView {
                  print("set ContentOffset here")
                  timelineCollectionView.scrollToMaxContentOffset(animated: false)
                  }
                  }





                  share|improve this answer















                  you can use performBatchUpdate in viewdidLoad.



                  timelineCollectionView.reloadData()
                  timelineCollectionView.performBatchUpdates(nil, completion: {
                  (result) in
                  timelineCollectionView.scrollToMaxContentOffset(animated: false)
                  })


                  Second option is you can put observer for collection view ContentSize.



                  override func viewDidLoad() {
                  super.viewDidLoad()
                  timelineCollectionView.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.old, context: nil)
                  }

                  override func viewWillDisappear(_ animated: Bool) {
                  super.viewWillDisappear(animated)
                  timelineCollectionView.removeObserver(self, forKeyPath: "contentSize")
                  }

                  override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
                  if let observedObject = object as? UICollectionView, observedObject == timelineCollectionView {
                  print("set ContentOffset here")
                  timelineCollectionView.scrollToMaxContentOffset(animated: false)
                  }
                  }






                  share|improve this answer














                  share|improve this answer



                  share|improve this answer








                  edited Jan 1 at 11:40

























                  answered Jan 1 at 11:34









                  Bhavesh.iosDevBhavesh.iosDev

                  592215




                  592215























                      0














                      As you need the content size and the bounds of your scrollView to compute your target content offset, you need your scrollView to be laid out before setting it.



                      viewDidLayoutSubviews looks good to me too. It is called each time the view of your view controller has just laid out its subviews (only its subviews, not the subviews of its subviews). Just add a flag to make sure the change is done only once. The hack is pretty common when you handle rotations or trait collection changes.



                      var firstTime = true

                      override func viewDidLayoutSubviews() {
                      super.viewDidLayoutSubviews()
                      guard firstTime else { return }
                      firstTime = false
                      collectionView.scrollToMaxContentOffset(animated: false)
                      }


                      Is there a better option?




                      • viewWillAppear is called too early. The bounds and the content size are wrong at this time.


                      • viewDidAppear is called too late. Too bad, the layout is done though. It is called once the animation of the view controller presentation is done, so the content offset of your scroll view will be at zero during all the animation duration.


                      • Hacking the completion blocks of UIView.animation or performBatchUpdates ?
                        Well, it works but this is totally counterintuitive. I think this is not a proper solution.



                      Besides, as specified in the documentation :




                      completion: [...] If the duration of the animation is 0, this block is performed at the beginning of the next run loop cycle.




                      You are just adding an extra useless cycle. viewDidLayoutSubviews is called just before the end of the current one.






                      share|improve this answer


























                      • the problem is that this gets called multiple times. So its called the first time when laying out a view other than the collectionView, sets it to false, and then when it gets called again for the collectionView, its already false

                        – BigBoy1337
                        Jan 7 at 3:14











                      • What about keeping track of the collection view frame ? You set the content offset each time previousFrame != collectionView.frame.

                        – GaétanZ
                        Jan 7 at 8:01











                      • hmm where would I do that?

                        – BigBoy1337
                        Jan 7 at 22:37











                      • Instead of 'firstTime'. You store 'previousFrame = collectionView.frame' each time it changes and set the content offset at the same time.

                        – GaétanZ
                        Jan 7 at 23:07











                      • but I don't want it to change every time the collectionView frame changes... Only the first time the view loads. In which function would "if previousFrame != collectionView.frame { scrollToMaxOffset... } go?

                        – BigBoy1337
                        Jan 7 at 23:21
















                      0














                      As you need the content size and the bounds of your scrollView to compute your target content offset, you need your scrollView to be laid out before setting it.



                      viewDidLayoutSubviews looks good to me too. It is called each time the view of your view controller has just laid out its subviews (only its subviews, not the subviews of its subviews). Just add a flag to make sure the change is done only once. The hack is pretty common when you handle rotations or trait collection changes.



                      var firstTime = true

                      override func viewDidLayoutSubviews() {
                      super.viewDidLayoutSubviews()
                      guard firstTime else { return }
                      firstTime = false
                      collectionView.scrollToMaxContentOffset(animated: false)
                      }


                      Is there a better option?




                      • viewWillAppear is called too early. The bounds and the content size are wrong at this time.


                      • viewDidAppear is called too late. Too bad, the layout is done though. It is called once the animation of the view controller presentation is done, so the content offset of your scroll view will be at zero during all the animation duration.


                      • Hacking the completion blocks of UIView.animation or performBatchUpdates ?
                        Well, it works but this is totally counterintuitive. I think this is not a proper solution.



                      Besides, as specified in the documentation :




                      completion: [...] If the duration of the animation is 0, this block is performed at the beginning of the next run loop cycle.




                      You are just adding an extra useless cycle. viewDidLayoutSubviews is called just before the end of the current one.






                      share|improve this answer


























                      • the problem is that this gets called multiple times. So its called the first time when laying out a view other than the collectionView, sets it to false, and then when it gets called again for the collectionView, its already false

                        – BigBoy1337
                        Jan 7 at 3:14











                      • What about keeping track of the collection view frame ? You set the content offset each time previousFrame != collectionView.frame.

                        – GaétanZ
                        Jan 7 at 8:01











                      • hmm where would I do that?

                        – BigBoy1337
                        Jan 7 at 22:37











                      • Instead of 'firstTime'. You store 'previousFrame = collectionView.frame' each time it changes and set the content offset at the same time.

                        – GaétanZ
                        Jan 7 at 23:07











                      • but I don't want it to change every time the collectionView frame changes... Only the first time the view loads. In which function would "if previousFrame != collectionView.frame { scrollToMaxOffset... } go?

                        – BigBoy1337
                        Jan 7 at 23:21














                      0












                      0








                      0







                      As you need the content size and the bounds of your scrollView to compute your target content offset, you need your scrollView to be laid out before setting it.



                      viewDidLayoutSubviews looks good to me too. It is called each time the view of your view controller has just laid out its subviews (only its subviews, not the subviews of its subviews). Just add a flag to make sure the change is done only once. The hack is pretty common when you handle rotations or trait collection changes.



                      var firstTime = true

                      override func viewDidLayoutSubviews() {
                      super.viewDidLayoutSubviews()
                      guard firstTime else { return }
                      firstTime = false
                      collectionView.scrollToMaxContentOffset(animated: false)
                      }


                      Is there a better option?




                      • viewWillAppear is called too early. The bounds and the content size are wrong at this time.


                      • viewDidAppear is called too late. Too bad, the layout is done though. It is called once the animation of the view controller presentation is done, so the content offset of your scroll view will be at zero during all the animation duration.


                      • Hacking the completion blocks of UIView.animation or performBatchUpdates ?
                        Well, it works but this is totally counterintuitive. I think this is not a proper solution.



                      Besides, as specified in the documentation :




                      completion: [...] If the duration of the animation is 0, this block is performed at the beginning of the next run loop cycle.




                      You are just adding an extra useless cycle. viewDidLayoutSubviews is called just before the end of the current one.






                      share|improve this answer















                      As you need the content size and the bounds of your scrollView to compute your target content offset, you need your scrollView to be laid out before setting it.



                      viewDidLayoutSubviews looks good to me too. It is called each time the view of your view controller has just laid out its subviews (only its subviews, not the subviews of its subviews). Just add a flag to make sure the change is done only once. The hack is pretty common when you handle rotations or trait collection changes.



                      var firstTime = true

                      override func viewDidLayoutSubviews() {
                      super.viewDidLayoutSubviews()
                      guard firstTime else { return }
                      firstTime = false
                      collectionView.scrollToMaxContentOffset(animated: false)
                      }


                      Is there a better option?




                      • viewWillAppear is called too early. The bounds and the content size are wrong at this time.


                      • viewDidAppear is called too late. Too bad, the layout is done though. It is called once the animation of the view controller presentation is done, so the content offset of your scroll view will be at zero during all the animation duration.


                      • Hacking the completion blocks of UIView.animation or performBatchUpdates ?
                        Well, it works but this is totally counterintuitive. I think this is not a proper solution.



                      Besides, as specified in the documentation :




                      completion: [...] If the duration of the animation is 0, this block is performed at the beginning of the next run loop cycle.




                      You are just adding an extra useless cycle. viewDidLayoutSubviews is called just before the end of the current one.







                      share|improve this answer














                      share|improve this answer



                      share|improve this answer








                      edited Jan 2 at 15:18

























                      answered Jan 2 at 15:06









                      GaétanZGaétanZ

                      2,48911327




                      2,48911327













                      • the problem is that this gets called multiple times. So its called the first time when laying out a view other than the collectionView, sets it to false, and then when it gets called again for the collectionView, its already false

                        – BigBoy1337
                        Jan 7 at 3:14











                      • What about keeping track of the collection view frame ? You set the content offset each time previousFrame != collectionView.frame.

                        – GaétanZ
                        Jan 7 at 8:01











                      • hmm where would I do that?

                        – BigBoy1337
                        Jan 7 at 22:37











                      • Instead of 'firstTime'. You store 'previousFrame = collectionView.frame' each time it changes and set the content offset at the same time.

                        – GaétanZ
                        Jan 7 at 23:07











                      • but I don't want it to change every time the collectionView frame changes... Only the first time the view loads. In which function would "if previousFrame != collectionView.frame { scrollToMaxOffset... } go?

                        – BigBoy1337
                        Jan 7 at 23:21



















                      • the problem is that this gets called multiple times. So its called the first time when laying out a view other than the collectionView, sets it to false, and then when it gets called again for the collectionView, its already false

                        – BigBoy1337
                        Jan 7 at 3:14











                      • What about keeping track of the collection view frame ? You set the content offset each time previousFrame != collectionView.frame.

                        – GaétanZ
                        Jan 7 at 8:01











                      • hmm where would I do that?

                        – BigBoy1337
                        Jan 7 at 22:37











                      • Instead of 'firstTime'. You store 'previousFrame = collectionView.frame' each time it changes and set the content offset at the same time.

                        – GaétanZ
                        Jan 7 at 23:07











                      • but I don't want it to change every time the collectionView frame changes... Only the first time the view loads. In which function would "if previousFrame != collectionView.frame { scrollToMaxOffset... } go?

                        – BigBoy1337
                        Jan 7 at 23:21

















                      the problem is that this gets called multiple times. So its called the first time when laying out a view other than the collectionView, sets it to false, and then when it gets called again for the collectionView, its already false

                      – BigBoy1337
                      Jan 7 at 3:14





                      the problem is that this gets called multiple times. So its called the first time when laying out a view other than the collectionView, sets it to false, and then when it gets called again for the collectionView, its already false

                      – BigBoy1337
                      Jan 7 at 3:14













                      What about keeping track of the collection view frame ? You set the content offset each time previousFrame != collectionView.frame.

                      – GaétanZ
                      Jan 7 at 8:01





                      What about keeping track of the collection view frame ? You set the content offset each time previousFrame != collectionView.frame.

                      – GaétanZ
                      Jan 7 at 8:01













                      hmm where would I do that?

                      – BigBoy1337
                      Jan 7 at 22:37





                      hmm where would I do that?

                      – BigBoy1337
                      Jan 7 at 22:37













                      Instead of 'firstTime'. You store 'previousFrame = collectionView.frame' each time it changes and set the content offset at the same time.

                      – GaétanZ
                      Jan 7 at 23:07





                      Instead of 'firstTime'. You store 'previousFrame = collectionView.frame' each time it changes and set the content offset at the same time.

                      – GaétanZ
                      Jan 7 at 23:07













                      but I don't want it to change every time the collectionView frame changes... Only the first time the view loads. In which function would "if previousFrame != collectionView.frame { scrollToMaxOffset... } go?

                      – BigBoy1337
                      Jan 7 at 23:21





                      but I don't want it to change every time the collectionView frame changes... Only the first time the view loads. In which function would "if previousFrame != collectionView.frame { scrollToMaxOffset... } go?

                      – BigBoy1337
                      Jan 7 at 23:21











                      0














                      It seems the best place to do it is in viewWillAppear and its now working. Note that I had to add some calls:



                      override func viewWillAppear(_ animated: Bool) {
                      super.viewWillAppear(animated)
                      let postCount = posts.fetchedObjects?.count
                      let lastPostIndex = IndexPath(item: max(postCount! - 1,0), section: 0)
                      postsCollectionView.setNeedsLayout()
                      postsCollectionView.layoutIfNeeded()
                      postsCollectionView.scrollToItem(at: lastPostIndex, at: .centeredHorizontally, animated: false) // you can also do setContentOffset here
                      }


                      The reason that my viewWillAppear wasn't working before is that it didn't know the contentSize of my collectionView and it thought the contentSize was 0 so scrollToItem and setContentOffset didn't move it anywhere. layoutIfNeeded() checks the contentOffset and now that its non-zero, it moves it the appropriate amount.



                      Edit: In fact you can even put this in ViewDidLoad if you want so that it doesn't re-fire when you add and dismiss a supplementary view for instance. The key really is that layoutIfNeeded command which accesses the contentSize.






                      share|improve this answer






























                        0














                        It seems the best place to do it is in viewWillAppear and its now working. Note that I had to add some calls:



                        override func viewWillAppear(_ animated: Bool) {
                        super.viewWillAppear(animated)
                        let postCount = posts.fetchedObjects?.count
                        let lastPostIndex = IndexPath(item: max(postCount! - 1,0), section: 0)
                        postsCollectionView.setNeedsLayout()
                        postsCollectionView.layoutIfNeeded()
                        postsCollectionView.scrollToItem(at: lastPostIndex, at: .centeredHorizontally, animated: false) // you can also do setContentOffset here
                        }


                        The reason that my viewWillAppear wasn't working before is that it didn't know the contentSize of my collectionView and it thought the contentSize was 0 so scrollToItem and setContentOffset didn't move it anywhere. layoutIfNeeded() checks the contentOffset and now that its non-zero, it moves it the appropriate amount.



                        Edit: In fact you can even put this in ViewDidLoad if you want so that it doesn't re-fire when you add and dismiss a supplementary view for instance. The key really is that layoutIfNeeded command which accesses the contentSize.






                        share|improve this answer




























                          0












                          0








                          0







                          It seems the best place to do it is in viewWillAppear and its now working. Note that I had to add some calls:



                          override func viewWillAppear(_ animated: Bool) {
                          super.viewWillAppear(animated)
                          let postCount = posts.fetchedObjects?.count
                          let lastPostIndex = IndexPath(item: max(postCount! - 1,0), section: 0)
                          postsCollectionView.setNeedsLayout()
                          postsCollectionView.layoutIfNeeded()
                          postsCollectionView.scrollToItem(at: lastPostIndex, at: .centeredHorizontally, animated: false) // you can also do setContentOffset here
                          }


                          The reason that my viewWillAppear wasn't working before is that it didn't know the contentSize of my collectionView and it thought the contentSize was 0 so scrollToItem and setContentOffset didn't move it anywhere. layoutIfNeeded() checks the contentOffset and now that its non-zero, it moves it the appropriate amount.



                          Edit: In fact you can even put this in ViewDidLoad if you want so that it doesn't re-fire when you add and dismiss a supplementary view for instance. The key really is that layoutIfNeeded command which accesses the contentSize.






                          share|improve this answer















                          It seems the best place to do it is in viewWillAppear and its now working. Note that I had to add some calls:



                          override func viewWillAppear(_ animated: Bool) {
                          super.viewWillAppear(animated)
                          let postCount = posts.fetchedObjects?.count
                          let lastPostIndex = IndexPath(item: max(postCount! - 1,0), section: 0)
                          postsCollectionView.setNeedsLayout()
                          postsCollectionView.layoutIfNeeded()
                          postsCollectionView.scrollToItem(at: lastPostIndex, at: .centeredHorizontally, animated: false) // you can also do setContentOffset here
                          }


                          The reason that my viewWillAppear wasn't working before is that it didn't know the contentSize of my collectionView and it thought the contentSize was 0 so scrollToItem and setContentOffset didn't move it anywhere. layoutIfNeeded() checks the contentOffset and now that its non-zero, it moves it the appropriate amount.



                          Edit: In fact you can even put this in ViewDidLoad if you want so that it doesn't re-fire when you add and dismiss a supplementary view for instance. The key really is that layoutIfNeeded command which accesses the contentSize.







                          share|improve this answer














                          share|improve this answer



                          share|improve this answer








                          edited Jan 11 at 23:33

























                          answered Jan 11 at 20:25









                          BigBoy1337BigBoy1337

                          1,06673675




                          1,06673675






























                              draft saved

                              draft discarded




















































                              Thanks for contributing an answer to Stack Overflow!


                              • Please be sure to answer the question. Provide details and share your research!

                              But avoid



                              • Asking for help, clarification, or responding to other answers.

                              • Making statements based on opinion; back them up with references or personal experience.


                              To learn more, see our tips on writing great answers.




                              draft saved


                              draft discarded














                              StackExchange.ready(
                              function () {
                              StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53952825%2fwhere-can-i-set-contentoffset-to-the-last-item-in-uicollectionview-before-the-vi%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

                              Can a sorcerer learn a 5th-level spell early by creating spell slots using the Font of Magic feature?

                              ts Property 'filter' does not exist on type '{}'

                              mat-slide-toggle shouldn't change it's state when I click cancel in confirmation window