So to solve this, I first tried adding code to scroll to the top right after I retrieve the content and call UICollectionView.reloadData(). Unfortunately this didn't turn out as I had hoped.
Since reloadData runs asynchronously, the jump to the top will be attempted before the new content is loaded into the collection. This causes a crash the first time it's called, since there will be no items in the view yet and therefore you can't scroll to the top item.
Ok, so we should probably be doing the scroll in UICollectionViewDelegate's collectionViewDidFinishLoading(UICollectionView) function, right? Well, unfortunately, that doesn't actually exist.
So, we need to detect when the content has finished loading, and only then scroll to the top.
Here's how I made it work:
1. I created an ("old school" Objective-C style) observer in my controller's viewDidLoad to see that the content had loaded.
...
var observerAdded : Bool = false
private var newsContext = 0
override func viewDidLoad() {
super.viewDidLoad()
...
if(!self.observerAdded) {
let opt = NSKeyValueObservingOptions([.New, .Old])
self.collectionView.addObserver(self,
forKeyPath: "contentSize",
options: opt,
context: &self.newsContext)
self.observerAdded = true
}
}
var observerAdded : Bool = false
private var newsContext = 0
override func viewDidLoad() {
super.viewDidLoad()
...
if(!self.observerAdded) {
let opt = NSKeyValueObservingOptions([.New, .Old])
self.collectionView.addObserver(self,
forKeyPath: "contentSize",
options: opt,
context: &self.newsContext)
self.observerAdded = true
}
}
Notes:
- The reason for observerAdded is that I don't want to add it more than once and I need to know that it should be removed.
- The reason for myContext is that I need to know this observer callback is for me and not something else iOS or other code is doing.
2. Then I made sure to unregister the observer during cleanup.
deinit {
if(self.observerAdded) {
collectionView.removeObserver(self,
forKeyPath: "contentSize",
context: &self.newsContext)
self.observerAdded = false
}
}
if(self.observerAdded) {
collectionView.removeObserver(self,
forKeyPath: "contentSize",
context: &self.newsContext)
self.observerAdded = false
}
}
3. Finally I added the observer callback and scroll to the top item.
override func observeValueForKeyPath(keyPath: String?,
ofObject object: AnyObject?,
change: [String : AnyObject]?,
context: UnsafeMutablePointer<void>) {
if(context == &self.newsContext) {
let old = change![NSKeyValueChangeOldKey]?.CGSizeValue()
let new = change![NSKeyValueChangeNewKey]?.CGSizeValue()
if(old != new && new?.height > 100) {
dispatch_async(dispatch_get_main_queue(), {
self.collectionView.scrollToItemAtIndexPath(NSIndexPath(forItem: 0, inSection: 0),
atScrollPosition: .Top,
animated: true)
})
}
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
ofObject object: AnyObject?,
change: [String : AnyObject]?,
context: UnsafeMutablePointer<void>) {
let old = change![NSKeyValueChangeOldKey]?.CGSizeValue()
let new = change![NSKeyValueChangeNewKey]?.CGSizeValue()
if(old != new && new?.height > 100) {
dispatch_async(dispatch_get_main_queue(), {
self.collectionView.scrollToItemAtIndexPath(NSIndexPath(forItem: 0, inSection: 0),
atScrollPosition: .Top,
animated: true)
})
}
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
No comments:
Post a Comment