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;
}
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
add a comment |
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
add a comment |
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
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
ios swift uicollectionview
edited Dec 28 '18 at 19:39
BigBoy1337
asked Dec 28 '18 at 1:49
BigBoy1337BigBoy1337
1,06673675
1,06673675
add a comment |
add a comment |
4 Answers
4
active
oldest
votes
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.
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 ofscrollToItem
, please try try withincompletion
orDispatchQueue.main.async
closure to justscrollToMaxContentOffset(animated: false)
?
– AamirR
Jan 1 at 12:52
add a comment |
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)
}
}
add a comment |
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.
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
|
show 1 more comment
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.
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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.
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 ofscrollToItem
, please try try withincompletion
orDispatchQueue.main.async
closure to justscrollToMaxContentOffset(animated: false)
?
– AamirR
Jan 1 at 12:52
add a comment |
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.
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 ofscrollToItem
, please try try withincompletion
orDispatchQueue.main.async
closure to justscrollToMaxContentOffset(animated: false)
?
– AamirR
Jan 1 at 12:52
add a comment |
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.
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.
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 ofscrollToItem
, please try try withincompletion
orDispatchQueue.main.async
closure to justscrollToMaxContentOffset(animated: false)
?
– AamirR
Jan 1 at 12:52
add a comment |
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 ofscrollToItem
, please try try withincompletion
orDispatchQueue.main.async
closure to justscrollToMaxContentOffset(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
add a comment |
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)
}
}
add a comment |
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)
}
}
add a comment |
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)
}
}
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)
}
}
edited Jan 1 at 11:40
answered Jan 1 at 11:34
Bhavesh.iosDevBhavesh.iosDev
592215
592215
add a comment |
add a comment |
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.
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
|
show 1 more comment
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.
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
|
show 1 more comment
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.
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.
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
|
show 1 more comment
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
|
show 1 more comment
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.
add a comment |
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.
add a comment |
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.
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.
edited Jan 11 at 23:33
answered Jan 11 at 20:25
BigBoy1337BigBoy1337
1,06673675
1,06673675
add a comment |
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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