I've got custom UITableViewCell class which keeps AVPlayerLayer inside one of its view (called videoView). Everytime I have to load a new cell (inside cellForRow... method) I'm checking if there's AVPlayerLayer and creating new one in case it wasn't made in the past:
_playerLayer = nil ;
for(CALayer * layer in _videoView.layer.sublayers)
if ([layer isKindOfClass:[AVPlayerLayer class]]) {
_playerLayer = (AVPlayerLayer*) layer;
}
if(!_playerLayer)
{
_playerLayer.player = nil ;
_playerLayer = [AVPlayerLayer layer];
[_videoView.layer addSublayer:_playerLayer];
}
Sometimes, I want to reload whole cell while video is playing (for example in order to update height or subtitle). cellForRow... method is called again, but that time I am getting new cell (is there way to force using old cell instead of getting new instance?). I want to move state of player/player layer from one view to another. I am doing it that way:
// create AVPlayerLayer
if(iAmPlayingButCellIsReloaded)
{
_playerLayer.player = playerWhichIsPlaying;
// time resets to 0 here
}
Mostly, it's working correctly, but there are cases when video restarts (moving to the beginning) after replacing cells. It happens every first reload and rarely during next iterations.
Is there fix for that? Should I wait to finish video, and then reload cell? Can I somehow force to use old cell while reloading row? Is there better way to copy AVPlayerLayer/AVPlayer from one place to another?
Related
This question has been asked a few times but none of the answers are detailed enough for me to understand why/how things work. For reference the other SO questions are:
How to update size of cells in UICollectionView after cell data is set?
Resize UICollectionView cells after their data has been set
Where to determine the height of a dynamically sized UICollectionViewCell?
I'm using MVC but to keep things simple lets say that I have a ViewController that in ViewWillAppear calls a web service to load some data. When the data has been loaded it calls
[self.collectionView reloadData]
The self.collectionView contains 1 UICollectionViewCell (let's call it DetailsCollectionViewCell).
When self.collectionView is being created it first calls sizeForItemAtIndexPath and then cellForItemAtIndexPath. This causes a problem for me because it's only during cellForItemAtIndexPath that I set the result of the web service to DetailsCollectionViewCell via:
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"detailsCell" forIndexPath:indexPath];
((DetailsCollectionViewCell*)cell).details = result;
DetailsCollectionViewCell has a setter for the property details that does some work that I need to happen first to know what the correct cell size should be.
Based on the linked questions above it seems like the only way to fire sizeForItemAtIndexPath after cellForItemAtIndexPath is to call
[self.collectionView.collectionViewLayout invalidateLayout];
But this where the other questions don't work for me because although it calls sizeForItemAtIndexPath and allows me to grab enough information from DetailsCollectionViewCell to set the correct height it doesn't update the UI until after the user scrolls the UICollectionView and my guess is that it has something to do with this line from the documentation
The actual layout update occurs during the next view layout update cycle.
However, i'm stumped on how to get around this. It almost feels like I need to create a static method on DetailsCollectionViewCell that I can pass the web service result to during the first sizeForItemAtIndexPath pass and then just cache that result. But i'm hoping there is a simple solution to having the UI automatically update instead.
Thanks,
p.s. - First SO question so hope i followed all the rules correctly.
Actually, from what I found, calling to invalidateLayout will cause calling sizeForItemAtIndexPath for all cells when dequeuing next cell (this is for iOS < 8.0, since 8.0 it will recalculate layout in next view layout update).
So the solution i came up with, is subclassing UICollectionView, and overriding layoutSubviews with something like this:
- (void)layoutSubviews
{
if ( self.shouldInvalidateCollectionViewLayout ) {
[self.collectionViewLayout invalidateLayout];
self.shouldInvalidateCollectionViewLayout = NO;
} else {
[super layoutSubviews];
}
}
and then calling setNeedsLayout in cellForItemAtIndexPath and setting shouldInvalidateCollectionViewLayout to YES. This worked for me in iOS >= 7.0. I also implemented estimated items size this way. Thx.
Here my case and solution.
My collectionView is in a scrollView and I want my collectionView and her cells to resize as I'm scrolling my scrollView.
So in my UIScrollView delegate method : scrollViewDidScroll :
[super scrollViewDidScroll:scrollView];
if(scrollView.contentOffset.y>0){
CGRect lc_frame = picturesCollectionView.frame;
lc_frame.origin.y=scrollView.contentOffset.y/2;
picturesCollectionView.frame = lc_frame;
}
else{
CGRect lc_frame = picturesCollectionView.frame;
lc_frame.origin.y=scrollView.contentOffset.y;
lc_frame.size.height=(3*(contentScrollView.frame.size.width/4))-scrollView.contentOffset.y;
picturesCollectionView.frame = lc_frame;
picturesCollectionViewFlowLayout.itemSize = CGSizeMake(picturesCollectionView.frame.size.width, picturesCollectionView.frame.size.height);
[picturesCollectionViewFlowLayout invalidateLayout];
}
I had to re set the collectionViewFlowLayout cell size then invalidate his layout.
Hope it helps !
I have looked at some sample LazyLoading to load the image whenever the cells are visible and the loading is complete. I noticed they do not call ReloadData How do I change the images without having to call ReloadData or reloading a specific row?
I figured that changing the image data at the address would do it, but it doesn't work.
I tried
for (NSIndexPath *indexPath in visiblePaths)
{
UIBubbleTableViewCell *aCell = (UIBubbleTableViewCell *)[self.bubbleTableView cellForRowAtIndexPath:indexPath];
NSBubbleData *cellData = [aCell data];
cellData.avatar = [self.profileImages objectForKey:[NSString stringWithFormat:#"%#", [cellData posterID]]];
}
However, this does not update the image. I have to call ReloadData after the for loop for it to load the image. If I do not call ReloadData it will not update until the cell goes off the screen again and comes back into view.
Looking at the source code for UIBubbleTableViewCell, it does not appear to handle updates to the data. However, it looks like the the data is re-applied anytime you set the cell's frame. So doing something like this might be a workaround (I don't use this library so I can't test it):
aCell.frame = aCell.frame;
I am using setContentOffset on a UITableView because I want to initially hide a search field that is my tableHeaderView.
[self.tableView setContentOffset:CGPointMake(0, 56)]; // No scroll please!
Each time I push a new viewController I want to hide the search bar with contentOffset. But when I pop a viewController that offset is no longer in effect for some reason and shows the search bar. Why is this?
you can try and implement it on the following
- (void)viewWillAppear:(BOOL)animated {
[self.tableView setContentOffset:CGPointMake(0, 56)];
}
that will put the table in the correct position before it is displayed on the screen, I am assuming you mean no animation while setting the position.
I am guessing that you want to stop the user being able to scroll to the very top of the screen. If so you can implement the following UITableView delegate (on iOS5 and above):
scrollViewWillEndDragging:withVelocity:targetContentOffset:
which allows you to modify the final target for a change in the contentOffset. In the implementation you do:
- (void)scrollViewWillEndDragging:(UIScrollView *)theScrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
if(targetContentOffset->y < 56) {
targetContentOffset->y=56;
}
}
If you are trying to preserve the value of something during an action that loses it, the natural solution is to hold onto it yourself ("Hold/Restore"):
"Hold": get content offset to a field or local variable. Apple doc
.. do whatever you want.
"Restore": set content offset to the value you got above.
(Sorry, I don't write Objective C code, so can't provide the exact code. An edit to add the code, would be welcome.)
In a different situation, it might be necessary to hold the row you were at, and then scroll back to that row:
(Adapted from: https://stackoverflow.com/a/34270078/199364)
(Swift)
1. Hold current row.
let holdIndexPath = tableView.indexPathForSelectedRow()
.. do whatever (perhaps ending with "reloadData").
Restore held row:
// The next line is to make sure the row object exists.
tableView.reloadRowsAtIndexPaths([holdIndexPath], withRowAnimation: .None)
tableView.scrollToRowAtIndexPath(holdIndexPath, atScrollPosition: atScrollPosition, animated: true)
I've been trying to implement an overlaying activity indicator for a UITableView by this tutorial - http://www.markbetz.net/2010/09/30/ios-diary-showing-an-activity-spinner-over-a-uitableview/
It might be a little old but it seems to work well apart from a little issue with the bounds to display the overlay in.
I try to get those bounds here:
-(void)showActivityView {
if (overlayController == nil) {
// This is where I get the wrong bounds
overlayController = [[ActivityOverlayController alloc] initWithFrame:self.tableView.bounds];
}
[self.tableView insertSubview:overlayController.view aboveSubview:self.tableView];
}
And this gets the bounds and displays my overlay perfectly if I call the method AFTER the table is loaded and filled but if I call it before it gets wrong bounds. I've tried getting the bounds of the tableView.superView but this just displays the overlay in the top left corner.
I understand this is because the UITableView doesn't contain any cells before loading and so doesn't have proper bounds yet but I don't know of a way to get these.
Wrong display:
Correct (but after loading table) display:
How about using the UITableView's "frame" property, rather than "bounds"? If the Table View is defined in a XIB, the frame should already be fine before loading cells into the table. So you would need to replace:
overlayController = [[ActivityOverlayController alloc] initWithFrame:self.tableView.bounds];
with:
overlayController = [[ActivityOverlayController alloc] initWithFrame:self.tableView.frame];
According to the example link you should do
-(void)showActivityView {
if (overlayController == nil) {
// This is where I get the wrong bounds
overlayController = [[ActivityOverlayController alloc] initWithFrame:self.tableView.superview.bounds];
}
[self.tableView.superview insertSubview:overlayController.view aboveSubview:self.tableView];
}
Why are you doing [self.tableView insertSubview:..] instead of [self.tableView.superview insertSubview:..]?
I'm writing a dynamic wizard application using cocoa/objective c on osx 10.6. The application sequences through a series of views gathering user input along the way. Each view that is displayed is provided by a loadable bundle. When the app starts up, a set of bundles are loaded and as the controller sequences through them, it asks each bundle for its view to display. I use the following to animate the transition between views
[[myContentView animator] replaceSubview:[oldView retain] with:newView];
This works fine most of the time. Every once in a while, a view is displayed and some of the subviews are not displayed. It may be a static text field, a checkbox, or even the entire set of subviews. If, for example, a checkbox is not displayed, I can still click where it should be and it then gets displayed.
I thought it might have something to do with the animation so I tried it like this
[myContentView replaceSubview:[oldView retain] with:newView];
with the same result. Any ideas on what's going on here? Thanks for any assistance.
I don't think is good to use this [oldView retain]. This retain do not make sense. The function replaceSubview will retain it if it is necessary.
Because it work "most of the time" I think it is a memory problem. You try to use a released thing. Test without it and see what happens.
I got the same problem, replaceSubview didn't work.
Finally i found something wrong with my code. so here are the rules :
Both subviews should be in MyContentview's subviews array.
OldView should be the topmost subview on MyContentView or replacesubview will not take place.
here is a simple function to perform replacesubview
- (void) replaceSubView:(NSView *)parentView:(NSView *)oldView:(NSView *)newView {
//Make sure every input pointer is not nill and oldView != newView
if (parentView && oldView && newView && oldView!=newView) {
//if newview is not present in parentview's subview array
//then add it to parentview's subview
if ([[parentView subviews] indexOfObject:newView] == NSNotFound) {
[parentView addSubview:newView];
}
//Note : Sometimes you should make sure that the oldview is the top
//subview in parentview. If not then the view won't change.
//you should change oldview with the top most view on parentview
//replace sub view here
[parentView replaceSubview:oldView with:newView];
}
}