UICollectionView & AVPlayer Performance - ios

I have a UICollectionView whose cells each have an AVPlayerLayer, all playing a video at the same time. About 9 cells fit on the screen at a time and it's pretty laggy so I need ways to boost the performance to achieve smooth scroll. There's a few things I've tried already:
1) Each cell has only one instance of AVPlayer. It doesn't get created, instead, player.replaceCurrentItemWithPlayer is called when the video url changes.
2) Because I'm using ReactiveCocoa, it's trivial to skip repeat urls to avoid playing the same video twice.
What else can I do to speed up the scroll and performance?

First I want to say it's a bit crazy to see several players in action at the same time: it's a heavy task of rendering anyway.
As far as I know, simply scaling/re-framing the video to smaller size doesn't make it less content to render: if you have a 100x100 picture to render and you render it in a frame of 10x10, it still consumes the same amount of memory as the original picture would consume; same thing goes for videos. So, try to make your video assets having similar resolution as the frame where you would present them.

Store each cell in an NSCache or NSMapTable using the NSIndexPath as the key for each; then, whenever you need a cell, call one directly from the cache or map table.
Obviously, you'll have to create all the cells at once, but you'll get the same scrolling performance as you do with nothing in the cells at all.
Need sample code?
Here's my latest iteration of a perfectly smooth-scrolling collection view with real-time video previews (up to 16 at a time):
https://youtu.be/7QlaO7WxjGg
It even uses a cover flow custom layout and "reflection" view that mirrors the video preview perfectly. The source code is here:
http://www.mediafire.com/download/ivecygnlhqxwynr/VideoWallCollectionView.zip

Unfortunately, UIKit will hit bottlenecks and drop frames when put under pressure for this use case. If you can muster refactoring code to use Facebook's AsyncDisplayKit / then this would be your best bet.
For objective-c / this project here is a good reference.
https://github.com/TextureGroup/Texture/tree/master/examples/ASDKTube
- (ASCellNode *)tableNode:(ASTableNode *)tableNode nodeForRowAtIndexPath:(NSIndexPath *)indexPath
{
VideoModel *videoObject = [_videoFeedData objectAtIndex:indexPath.row];
VideoContentCell *cellNode = [[VideoContentCell alloc] initWithVideoObject:videoObject];
return cellNode;
}
there is swift code available if you dig deep enough in github.

Related

UICollectionView weird animation (ghost cell, flash) on reload data

Hello as you can see in the video link below when I reload the data, for a frame something happens and some ghost cells appear and disappear, it's like a weird animation... Is it because of using reusable cells? and if it is, is it preventable cause that's not so pretty and it seems to cause a bit of frame drop with use of CATransition animation (not shown in the video). Any help would be appreciated.
Google Drive Link
Video Youtube Link
* EDIT *
Seems the video is not processed by google drive, sorry you gotta download it first to watch it.
Added Youtube link for convenience
Ok I Found the answer myself thanks to all the unnecessary downvotes:
The first solution was to use
collectionView.reloadItems(at: collectionView.indexPathsForVisibleItems)
but that still caused some problem and flicker stuff.
Some other said to use layoutIfNeeded() after that, yet that did not help.
The Solution I Found was to use func reloadSections(_ sections: IndexSet)
so when I use collectionView.reloadSections(IndexSet(integer:0)) (in my case it is the first section) The animation will happen smooth and perfect.

iOS Photo Gallery Memory Leak / Crash when using Push Segues

I've created a small photo gallery which is presenting a new view controller with a larger version of the photo and some additional text when it is clicked:
The problem is - after going through a handful of images - the application crashes due to overuse of memory. I attempted to resolve this by compressing the images in order to leave a smaller memory footprint, but the issue remains and I'm not sure what else I can do to resolve this issue.
Also - there is almost no code to doing this since I'm using storyboard's push segues as well as the built in navigation item to go back between viewControllers.
P.S.
If you feel source code is necessary to provide insight in this instance - it can be found here:
https://www.dropbox.com/s/q1qq8pq4tzv8wyo/EXAMPLE%20BUILD.zip?dl=0
To resolve this issue you have to use this trick; Put a "placeHolder" image in your cell's imageview in "StoryBoard". Don't load the images all at once in your "ViewController", load them one by one by running a loop or in your "cellForRowAtIndexPath()" method and add a delay in each iteration (Load first image then add a delay, load second image and add a delay, then for third one and so on up to the last image).
If you want to know how to add a delay then check this link:
NSTimer - how to delay in Swift
To resolve this issue I simply resized the images - I noticed I accidentally used a gigantic (6000 x 4000) image and even though I compressed the images iOS had to crunch pretty hard to resize them into the view... thereby causing the memory leak and subsequent crash.
Resizing them to 600x400 did the trick.

Preloading mathjax into a collection iOS Swift

(Please excuse the amount of text)
I'm trying to render a bunch of math questions in an app. Say there's 30 questions, and each question has about 7-8 fields (instructions, question, answer choices, and review).
I am able to do this successfully when I load them into containers with MathJax rendering (I also use a callback in the JS to make sure the content is loaded before being viewed).
The thing is, the process takes a few seconds each time, and I'd like to preload the entire set of questions, and the views, so that after the initial load, question loading will be instantaneous.
I think using a collection would be the best way to do this, and I've been trying two methods with no luck:
load the collection with all the content as cells/items, then jump to the cell (I was facing the issue that only the viewed cell would load, and then I figured if they did all load, I would face the issue that I am facing now with the next option)
load all the content into an invisible subview, then load the subview into a cell when it is selected (may offer more control over how things are loaded)
In either case, I run into the same issue (and this even happened in the initial one-by-one loading with multiple containers, where if I jumped around too quickly, the app would crash).
When loading a bunch of subviews, each with Mathjax, the app gets overwhelmed and crashes (I believe due to memory or cpu, it says "lost connection to iPad"). This doesn't seem to happen if there is a lot of Mathjax within one view. I think it's because the processes don't wait for each other to finish before beginning, and the app can't handle running multiple Mathjax rendering at the same time.
I think a possible solution is to make sure that the loop that loads the view does not reloop until the Mathjax is done rendering, but cannot figure out a way to make the loop wait (once it initiates the loading it keeps incrementing).
Hopefully someone can provide a good idea for:
how to structure the preloading mechanism in general
or
how to specifically make the loop wait for the view to render before relooping (I have a process that recognizes when the Mathjax has been fully rendered, but I can't figure out how to make the loop wait for it. This process is currently used to simply unhide the view once it's done).
for var i = 0; i < objectsKeyValues.count; i++ {
let iInput:String = cDParentVC.string1 + cDContentAbstraction.objectsKeyValues[i]["instructions"]! + cDParentVC.string2
let qInput:String = cDParentVC.string1 + cDContentAbstraction.objectsKeyValues[i]["question"]! + cDParentVC.string2
... for a few more of these
let iView = WKWebView.init(frame: CGRectMake(5,5,300,35), configuration: config)
...same
iView.loadHTMLString(iInput, baseURL: NSBundle.mainBundle().bundleURL)
..same
viewDictArray.append(["iView":iView,"qView":qView,"a1View":a1View,"a2View":a2View,"a3View":a3View,"a4View":a4View,"a5View":a5View,"rView":rView,"pView":pView])
cDLargeContent.invisiView.addSubview(viewDictArray[i]["iView"]!)
...same
}
I suspect NSOperations or GCD could also provide the necessary controls, but I am pretty green with programming and haven't been able to implement those concepts successfully, let alone verify if they would solve this issue.

UICollectionView received memory warning while scrolling

I am developing an App with an UICollectionView in one ViewController.
This CollectionView it's a gallery with 3 images in each row.
I get the images from the server in groups of 30 pictures, first url and code, and when the cell is going to be displayed in cellForItemAtIndexPath I use SDWebImage Library to download those pictures asynchronously. Every 30 pictures I call again the web service and I get 30 pictures more sending the request with limit and offset. This could happen 100 times, there are profiles with 3000 pictures.
Well, my problem comes one I launch the app in my iPhone 4, after some fast scrolling I start getting Received Memory Warnings and after some warnings the app crashes. When I make the same test in the Simulator, nothing bad happens.
Each time I download 30 pictures I add the result array to the NSMutableArray *data property which handles the CollectionView data and reload the collectionView. I have tried to use Instruments with allocations, but it is very difficult for me to understand what is happening.
This is the code I use to create the cells
-(UICollectionViewCell *)collectionView:(LZProfileImagesCollectionView *)aCollectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
if(aCollectionView == self.imagesCollectionView){
if([self.data count] - 12 == indexPath.row){
self.photoOffset += 30;
[self loadUserData];
}
LZProfileCollectionViewCell * cell = (LZProfileCollectionViewCell *)[imagesCollectionView dequeueReusableCellWithReuseIdentifier:ImageCollectionCellIdentifier forIndexPath:indexPath];
Image *image =(self.data)[indexPath.row];
[cell configureCellWithImage:image];
return cell;
}
return nil;
}
And in the LZProfileCollectionViewCell cell I have this one:
-(void)configureCellWithImage:(Image *)image
{
[self setNeedsDisplay];
// Imagen
NSString *imageUrl = [kBaseURL stringByAppendingString:image.imageStringUrl];
[self.pictureImg setImageWithURL:[NSURL URLWithString:imageUrl] placeholderImage:[UIImage imageNamed:kDefaultLoadingImage]];
[self.pictureImg setContentMode:UIViewContentModeScaleAspectFit];
}
I have taken setImageWithURL from SDWebImage
After 3 minutes I get this snapshot in the Instruments(Statistics)
Thank you in advance
Well, you are caching every image in an array, so once you cache so many images you are eventually going to run out of memory. It doesn't happen on the simulator because the simulator has as much memory as the computer running it, so it is unlikely to ever run out of memory, a real device however will run out very fast. I'd handle the memory error yourself and empty the array, to free up new space for new images. Or better yet create your own caching mechanism to hold and clear images that haven't been in view for a while, either way you have to have some way to dump images after you hit a certain threshold.
In the method – didReceiveMemoryWarning you should free some memory of the array where you are stored the images. Maybe the first images that you are not displaying stored it a second array like an auxiliar array.
This is probably happening because a lot of UIImage objects end up in memory. You could instead cache this on disk. AFNetworking has a nice UIImageView category that does all of this automatically.
If you're not saving the Images in your dataSource array, then they're probably being retained somewhere else, which is a bug.
Also, make sure you're dequeuing cells and not creating new ones.
I've experienced similar situation so I'm replying on this issue though the reason seems a little different from yours.
I have a horizontal UICollectionView inside UITableViewCell. When I scroll UICollectionView right and left rapidly, the entire UI(even a home button) stops and after a while the app crashes with the message "Message from debugger: Terminated due to memory issue" on the console even without didReceiveMemoryWarning call.
I tested it with a UICollectionViewCell without any UI components but no changes. XCode Profile tool says it's actually keep allocating memory during the UI's stopped until it crashes even without any UI components in it.
The only suspicious point was that I was setting the width of a UICollectionViewCell's subview(custom UIView pinned to all superview edges) with AppDelegate.window.size.width * 0.35 to determine UICollectionViewCell's width and it was a float value like 130.25
When I hardcode this value to 130, all the crashes are gone. I think something was happening under the hood with this vague value of 130.25 and it's determining the width as 130 or 130.25 occasionally.
So I'm using CGFloat(Int(AppDelegate.window.size.width * 0.35)) now and everything's fine.

Suspend redrawing while changing multiple children

I have a UIViewController that after asynchronously loading some data needs to resize some child views (Some UITableViews, and some UIScrollViews). This works just fine in the simulator, but on an actual device, it hangs for a long time (as much as 30 seconds in one case). I think the problem is that it wants to recalculate everything after each changed to the child view and I would like to be able to tell it to defer any recalculations until I've resized everything, but I'm drawing a blank on finding a way to do that. Is there a mechanism to do this?
So what I'm doing is something like this (I'm using C# with Xamarin but input in Obj-C would also be appreciated!):
// Need to suspend layout here...
MyTable.Frame = new RectangleF(...);
SomeOtherTable.Frame = new RectangleF(...);
ScrollView.ContentSize = new SizeF(...);
AnotherScrollView.ContentSize = new SizeF(...);
AnotherScrollView.Frame = new RectangleF(...);
// Ok - now you can redraw!
Update: Jacob's suggestion of disabling animation didn't seem to help, but I think I've isolated the problem to the table resizing. They seem to have the biggest impact on performance, but I'm not sure why, or how to mitigate the problem. I may follow up with a separate question on that.

Resources