UICollectionView fails to honour zPosition and zIndex - iOS 11 only - uicollectionview

I try to find a workaround for this radar: http://www.openradar.me/34308893
Currently in my - (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes I'm doing the following:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_MSEC * 50), dispatch_get_main_queue(), ^{
self.layer.zPosition = 0;
});
But no matter how many NSEC_PER_MSEC I enter it doesn't work quite right, the scrolling indicator sometimes hides behind the headers, especially when ne headers appear. Is there another/better approach?

I had a similar problem where a header view in a UICollectionView was getting rendered above another view that had it's zIndex set > 0 in a custom UICollectionViewFlowLayout.
The answer for me was to set the header view layer's zPosition in willDisplaySupplementaryView:
override public func collectionView(_ collectionView: UICollectionView,
willDisplaySupplementaryView view: UICollectionReusableView,
forElementKind elementKind: String,
at indexPath: IndexPath) {
if elementKind == UICollectionView.elementKindSectionHeader && type(of: view) == CollectionViewSectionHeaderView.self {
view.layer.zPosition = -1
}
}
iOS11 doesn't seem to honor z-indexes in UICollectionViewFlowLayout subclasses.

Related

UICollectionViewCell doesn't fit on screen

Does anyone know why this would happen or how to fix it? I am trying to make a simple collection view but for some reason, the cells have a little bit of an offset on the x-axis.
It's supposed to look like this
But it comes out like this
Here's what I have
func collectionView(_ collectionView:UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let screenWidth = UIScreen.main.bounds.width
return CGSize(width: screenWidth, height:135.0)
}
Okay well to solve the issue I just created a brand new view controller in the Storyboard, It was a pain but I honestly could not figure out what was wrong. I think that there must have just been a glitch somewhere in the constraints maybe, but even after I deleted all the constraints I still had the problem. Maybe I'm just the big dumb.

Full width NSCollectionViewFlowLayout with NSCollectionView

I have a NSCollectionViewFlowLayout which contains the following:
- (NSSize) itemSize
{
return CGSizeMake(self.collectionView.bounds.size.width, kItemHeight);
} // End of itemSize
- (CGFloat) minimumLineSpacing
{
return 0;
} // End of minimumLineSpacing
- (CGFloat) minimumInteritemSpacing
{
return 0;
} // End of minimumInteritemSpacing
I've tried to ways to make the layout responsive (set the width whenever resized). I've tried adding the following:
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(onWindowDidResize:)
name: NSWindowDidResizeNotification
object: nil];
- (void) onWindowDidResize: (NSNotification*) notification
{
[connectionsListView.collectionViewLayout invalidateLayout];
} // End of windowDidResize:
And this works fine if I expand the collection view (resize larger). But if I attempt to collapse the view (resize smaller), I get the following exceptions:
The behavior of the UICollectionViewFlowLayout is not defined because:
The item width must be less than the width of the UICollectionView minus the section insets left and right values, minus the content insets left and right values.
The relevant UICollectionViewFlowLayout instance is <TestListLayout: 0x106f70f90>, and it is attached to <NSCollectionView: 0x106f76480>.
Any suggestions on how I can resolve this?
NOTE1: This is macOS and not iOS (even though the error message states UICollectionViewFlowLayout).
NOTE2: Even though I receive the warning/error the layout width works, but I would like to figure out the underlying issue.
I had the same problem but in my case I resized view. #Giles solution didn't work for me until I changed invalidation context
class MyCollectionViewFlowLayout: NSCollectionViewFlowLayout {
override func shouldInvalidateLayout(forBoundsChange newBounds: NSRect) -> Bool {
return true
}
override func invalidationContext(forBoundsChange newBounds: NSRect) -> NSCollectionViewLayoutInvalidationContext {
let context = super.invalidationContext(forBoundsChange: newBounds) as! NSCollectionViewFlowLayoutInvalidationContext
context.invalidateFlowLayoutDelegateMetrics = true
return context
}
}
Hope it helps someone as it took me couple of evenings to find solution
This is because you are using the didResize event, which is too late. Your items are too wide at the moment the window starts to shrink. Try using:
func shouldInvalidateLayout(forBoundsChange newBounds: NSRect) -> Bool {
return true
}
...when overriding flow layout, to get everything recalculated.
The source code I posted in the question works fine as of macOS 10.14 with no issues. I added the following to my window which displays the collection view.
// Only Mojave and after is resizable. Before that, a full sized collection view caused issues as listed
// at https://stackoverflow.com/questions/48567326/full-width-nscollectionviewflowlayout-with-nscollectionview
if(#available(macOS 10.14, *))
{
self.window.styleMask |= NSWindowStyleMaskResizable;
} // End of macOS 10.14+

How can I horizontally center rows in CollectionView with sections

So I know this topic has been tackled in the past, still, I have try'd applying all previous suggestions but non have actually solved my issue.
I have a collectionView that contains a dynamic amount of sections and items and I need to constraint all rows to be horizontally centered.
I have try'd to apply a pod called KTCenterFlowLayout but with no success since apparently it doesn't work well with a multi section collectionView.
Is there anyway to center collectionView rows in a multi sectional CollectionView in swift4?
'func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let screenWidth = (UIScreen.main.bounds.size.width-4) / 3
return CGSize(width:screenWidth , height: screenWidth);
}'
Write it down that in your View Controller and set that you whatever thing that you want..enter image description here

AVPlayer.play() in UITableViewCell briefly blocks UI

I’m trying add inline videos to my UITableViewCells a la Instagram, Twitter, Vine…etc. I’m testing out the UI with a local video file using AVPlayerController and a custom cell (see sample code below). I wait for the status of the AVPlayer to be ReadyToPlay and then play the video.
The issue is that when scrolling on the tableView the UI freezes for a fraction of section whenever a video cell is loaded which makes the app seem clunky. This effect is made worse when there are multiple video cells in a row. Any thoughts and help would be greatly appreciated
TableView Code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("videoCell", forIndexPath: indexPath) as! CustomCell
//Set up video player if cell doesn't already have one
if(cell.videoPlayerController == nil){
cell.videoPlayerController = AVPlayerViewController()
cell.videoPlayerController.view.frame.size.width = cell.mediaView.frame.width
cell.videoPlayerController.view.frame.size.height = cell.mediaView.frame.height
cell.videoPlayerController.view.center = cell.mediaView.center
cell.mediaView.addSubview(cell.videoPlayerController.view)
}
//Remove old observers on cell.videoPlayer if they exist
if(cell.videoObserverSet){
cell.videoPlayer.removeObserver(cell, forKeyPath: "status")
}
let localVideoUrl: NSURL = NSBundle.mainBundle().URLForResource("videoTest", withExtension: "m4v")!
cell.videoPlayer = AVPlayer(URL:localVideoUrl)
cell.videoPlayer.addObserver(cell, forKeyPath:"status", options:NSKeyValueObservingOptions.New, context = nil)
cell.videoObserverSet = true
cell.videoPlayerController.player = cell.videoPlayer
return cell
}
Custom Cell Observer Code:
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if(keyPath=="status"){
print("status changed on cell video!");
if(self.videoPlayer.status == AVPlayerStatus.ReadyToPlay){
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.videoPlayer.play()
})
}
}
}
I also tried loading an AVAsset version of the video using the loadValuesAsynchronouslyForKeys(["playable"]) but that didn't help with the UI block either.
What can I do to get the silky smooth video playback and scrolling of Instagram?
Another Update
Highly recommend using Facebook's Async Display Kit ASVideoNode as a drop in replacement for AVPlayer. You'll get the silky smooth Instagram tableview performance while loading and playing videos. I think AVPlayer runs a few processes on the main thread and there's no way to get around them cause it's happening at a relatively low level.
Update: Solved
I wasn't able to directly tackle the issue, but I used to some tricks and assumptions and to get the performance I wanted.
The assumption was that there's no way to avoid the video taking a certain amount of time to load on the main thread (unless you're instagram and you possess some AsyncDisplayKit magic, but I didn't want to make that jump), so instead of trying to remove the UI block, try to hide it instead.
I hid the UIBlock by implementing a simple isScrolling check on on my tableView
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if(self.isScrolling){
if(!decelerate){
self.isScrolling = false
self.tableView.reloadData()
}
}
}
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
if(self.isScrolling){
self.isScrolling = false
self.tableView.reloadData()
}
}
func scrollViewWillBeginDragging(scrollView: UIScrollView) {
self.isScrolling = true
}
And then just made sure not to load the video cells when tableView is scrolling or a video is already playing, and reload the the tableView when the scrolling stops.
if(!isScrolling && cell.videoPlayer == nil){
//Load video cell here
}
But make sure to remove the videoPlayer, and stop listening to replay notifications once the videoCell is not on the screen anymore.
func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if(tableView.indexPathsForVisibleRows?.indexOf(indexPath) == nil){
if (cell.videoPlayer != nil && murmur.video_url != nil){
if(cell.videoPlayer.rate != 0){
cell.videoPlayer.removeObserver(cell, forKeyPath: "status")
cell.videoPlayer = nil;
cell.videoPlayerController.view.alpha = 0;
}
}
}
}
With these changes I was able to achieve a smooth UI with multiple (2) video cells on the screen at a time. Hope this helps and let me know if you have any specific questions or if what I described wasn't clear.

UITableViewCell awakeFromNib wrong frame size

I have a UITableViewCell with constraints so that the cells layout correctly on iPhone 6 and iPhone 6+.
The cell contents correctly resize correctly. However, I need to draw a sublayer gradient over one of the views inside. In order to do so, I'm using awakeFromNib to draw the sublayer.
AwakeFromNib is giving the size of the frame prior to it autoresizing and therefore the gradient subview isn't the right size.
If I use layoutSubviews, the first time it's called, the size is also wrong and the cell needs to be scrolled before it has the right size; so that method also isn't working
What method should I be using to get the right frame size in order to draw correctly?
This code worked for me, is written in Swift.
import UIKit
import QuartzCore
class DemoTableViewCell: UITableViewCell {
#IBOutlet weak var gradientView: UIView!
private let gl = CAGradientLayer()
override func awakeFromNib() {
super.awakeFromNib()
// set gradient
let colors: [AnyObject] = [
UIColor.clearColor().CGColor,
UIColor(white: 0, alpha: 0.8).CGColor
]
gl.colors = colors
gradientView.layer.insertSublayer(gl, atIndex: 0)
}
override func layoutSubviews() {
super.layoutSubviews()
// update gradient
gl.frame = gradientView.bounds
}
}
during awakeFromNib the frame layouts do not appear. On your custom Cell you can either do this
-(void)drawRect:(CGRect)rect
{
[super drawRect:rect];
// Enter Custom Code
}
Or you can override applyLayoutAttributes method like this
-(void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
[super applyLayoutAttributes:layoutAttributes];
// Enter custom Code here
}
applyLayoutAttributes is called before drawRect method but still has the size parameteres of rect.
#angshuk answer is perfect where if anyone wants to modify UI inside UITableViewCell. Like in my case I was trying to add a different layer on runtime to an UIView, placed inside UITableViewCell using storyboard/autolayout. Tried the same thing inside awakeFromNib() & layoutSubviews() also. Nothing worked.
Here is the same code using Swift. By the way I am using Xcode 7.3.1 with Swift 2.2.
import UIKit
class InviteMainCellClass: UITableViewCell {
#IBOutlet weak var shareCodeLblSuperView: UIView! //Custom view added using IB/Autolayout
override func awakeFromNib() {
super.awakeFromNib()
//shareCodeLblSuperView.addDashedBorderToView(self.shareCodeLblSuperView, viewBorderColor: UIColor.whiteColor(), borderWidth: 1.0) //Not worked
}
override func drawRect(rect: CGRect) {
super.drawRect(rect)
shareCodeLblSuperView.addDashedBorderToView(self.shareCodeLblSuperView, viewBorderColor: UIColor.whiteColor(), borderWidth: 1.0) //Worked
}
override func layoutSubviews() {
super.layoutSubviews()
//shareCodeLblSuperView.addDashedBorderToView(self.shareCodeLblSuperView, viewBorderColor: UIColor.whiteColor(), borderWidth: 1.0)//Not worked
}
Hope this helped. Thanks.