I'm building a dictionary app, and one of the features is that you can "drill down" into a definition. You look up a word, there's a word in the definition you don't understand, so you tap that word and the app shows you the definition for that word, if available. This is pretty standard for iOS dictionary apps on the App Store, if anyone's familiar.
One feature is that I am going to have a bar that shows each word as you drill down, so you get a sort of "chain" of words across this horizontal bar. The idea is you can tap on the words in the chain to navigate forwards and backwards through your chain of words.
To represent this chain I am using a UICollectionView, with a subclassed UICollectionViewController. Since it's essentially a "grid" with a single row, this made sense to me. I'm using a storyboard, so I simply embedded my custom controller into the main view, pass events back and forth between the master controller and this custom collection controller. Pretty simple.
Now, let's say the user taps many different words, so you get the "chain" of UICollectionViewCells growing across the bar from left to right until you hit the edge. Since UICollectionView is based on/has a UIScrollView in it, this shouldn't be a problem. My concept is that, if the number of items to be displayed exceeds the width of the view, the CollectionView would expand the contentView and place items off the right edge of the view port, and I could then programmatically scroll the view to show the newly added item.
Here's the code that I use to do this (edited). It's a method that I call in my subclassed UICollectionViewController:
- (void)reloadDataWithAnimation
{
[self.collectionView performBatchUpdates:^{
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
} completion:^(BOOL finished) {
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:model.data.count - 1 inSection:0]
atScrollPosition:UICollectionViewScrollPositionNone animated:YES];
}
}];
}
This is more or less what I'd normally do with a UITableView: tell it to reload its data after I have changed the model. It didn't quite work at first until I found a question on SO that covered the use of performBatchUpdates, and I had trouble doing the scrollToItemAtIndexPath until I realized it needed to go into the completion block.
Now, here's the funky part: everything works fine before the items fill up the whole view. The first time an item would go beyond the boundaries, the CollectionView places the item below the last item in the row. This looks really strange because the view is only one row tall, so you can kinda half-see the item parked below the last one in the row, mostly obscured. No scrolling occurs.
At this point, if you scroll the CollectionView back and forth, the item reconfigures itself and moves into the correct position. Or, if you add another item, they align correctly and the view scrolls as intended. Successive additions work just fine. However, if you scroll back to the left and then add another item, it briefly appears underneath the last currently visible item in the viewport, then the view scrolls off to the far right and as it scrolls the misplaced item jumps up into its proper position.
Although I began posting this question as I could find nothing on Stack Overflow, in the course of writing my question, the Similar Question sidebar updated and I found similar questions, and eventually an answer.
Similar question: UICollectionView cell layout is out of the collection view's bounds
Apparently this issue is known to Apple.
Here is the solution linked to in the above question: https://stackoverflow.com/a/13389461/700471
The code that Nick Snyder posted solved the problem for me perfectly.
Edit: Additionally, in the end I subclassed UICollectionViewLayout, bypassing FlowLayout, and made my own layout with this tutorial.
Related
I have a UITableView which has a variable amount of sections and every section has a variable amount of rows. Every section contains a section header, a section footer and the rows for that section. They are shown and displayed inside a container. Everything up until now works as expected. To demonstrate what my UITableView currently looks like:
I am now trying to implement the possibility to swipe left on the entire section, so that the header of the section, the rows inside this section and the footer of this section move to the left and display another UIView. I am capable of swiping the cells separately, but enabling this feature on the entire sections have caused headaches for the past two days. This is what I would like to achieve:
I have thought about implementing UIGestureRecognizers but I am afraid they might conflict with my UITableView. Also, I started implementing it, but I would not know how to move solely the section on which was swiped to the left.
Then I thought I could maybe implement a UIScrollView as container for each section. How I would accomplish this is still a mystery to me, but it seems like a possible solution if this could be done.
Furthermore I am out of ideas and stuck on how this should be done. It's something I have not found on the Internet so far - at least no working examples of some kind so I have zero inspiration on how to achieve the effect.
I therefor would like to ask if somebody has an idea of how this could work and what I have to keep in mind when implementing this. Every clue pointing me towards a working solution is gratefully appreciated!
EDIT: I have already seen the possible duplicate this afternoon. However, the suggestion there shows touchesBegan() on the header, which does not work in my case as I need the entire section to be "draggable".
I think one way you can implement this is with a vertical stackview containing views that contains a tableview and the trash icon. When a user swipes left on the tableview header, it will show the hidden trash icon in the view. I would think that each tableview only has one section so it will be easier to keep track of which "section" the user has swiped.
I have currently managed to arrange a similar solution. I take the rectangle of the section with rect(forSection:int), add a UIPanGestureRecognizer in which I add a UIView on top of the UITableView if touches began, I calculate the location of the finger and let the cudtom UIView follow. When a certain point (100 from left edge of the UITableView) is reached, the section gets removed with deleteSections(indexSet:with:).
This works. It does the job, but it adds an overlay to the section rather than pushing it to the left.
Therefor I am asking of someone knows if there is any way of setting the offset for one specific section or for an area of a UIView, so I can offset the rectangle of the section. I have been able to setContentOffset on the entire UITableView but this is not the desired result.
If there is no way to do this, I would consider keeping the solution I have now or maybe implement a snapshot feature which takes a screenshot and crops the rect of the frame, adding this UIImage to the custom view to simulate the section. But that would be tricky. Any ideas for this idea are also welcome.
I am experimenting with the UITableView, but animations are not occurring as I would expect.
Given a list of cells, I want to delete the top-most cell (the item at IndexPathsForVisibleRows[0]), and have all the rows beneath it, animate upwards into their new position.
However, it appears that any time IndexPathsForVisibleRows[0] (or any invisible cell ABOVE the first visible one for that matter) is touched (moved, deleted, etc), the UITableView refuses to animate the results of the transaction.
I have included 2 gifs to demonstrate the issue.
The first GIF demonstrates the desired behavior. I am removing the cell at IndexPathsForVisibleRows[1] (the second on-screen cell).
Notice how all cells below animate correctly into position.
The second GIF demonstrates what happens when removing the top-most cell (IndexPathsForVisibleRows[0]). Note how all the cells below move immediately into the new position, without animation.
(note: the entire table change is wrapped in a BeginUpdates/EndUpdates block).
Am I missing something which causes the 2 scenarios to behave differently, or I have I just stumbled upon a UITableView bug/limitation?
After much investigation, including several new code projects distilling the UITableView down to the various different factors that could be contributing to this behavior, I have found the simple answer.
Don't use the UITableView
I, like many others, have relied heavily on the UITableView in many applications, due to its simplicity and performance, however, if you really want it to behave in fluid, sensible ways, it just cannot deliver.
There are some inherent bugs in it's behavior which don't appear to bet getting addressed by Apple - and not surprisingly, when there is a much better alternative already in the SDK.
Enter the UICollectionView.
I took all my backend logic for and adapted it to supply a UICollectionView instead (all the work took about half an hour), and lo and behold, everything just worked as originally intended.
So, all my code was correct, the UITableView was just getting in the way.
This is how it now looks. Notice have all deletion scenarios animate correctly, no jankiness.
I'm not the first to have found UITableView lacking:
https://pspdfkit.com/blog/2017/the-case-for-deprecating-uitableview/
Additional benefits to UICollectionView:
customizable layouts
easily customizable cell animations
update transaction does not halt currently running scroll animations (this one is great, if you have a background thread refreshing data, you won't get a sudden 'jerk' when rows get rearranged)
I am new to iOS development and unable to identify how Google/YouTube built this view in the YouTubeTV app. Is this built using an UITableView?
Essentially, the top row is selectable (Pre-animation). As you scroll up, the top row gets pushed up and out of view (Mid-animation), while the second row fades and grows into, and replaces, the top row). I've included screenshots of the animation in-progress. Thanks for the info and assistance.
This would be done with a UITableView or a UICollectionView. What you would do is enable paging on the Table/Collection view, so that it only ever displays entire cells in the visible area of the view. You can then manipulate the height use the heightForCellAtIndexPath: function - as an example of how it could be done on a tableview.
There is actually a really good example on github - typically we try to give more full answers on SO, but in this case, this could be relevant to you just starting out. Not affiliated in anyway, but it's a really good example.
https://github.com/aslanyanhaik/youtube-iOS
This is a very rare occurring bug from hell,
I have an infinite scroll controller that displays products, 2 in each row. Rarely, something affects the controller and causes items to vanish, when I tap the empty area where the item should be, it works as expected and directs the user to the item details controller. When I back out back to the list, sometimes the cell shows its content, and others get hidden.
Sometimes it just a couple of items missing, sometimes there are so many missing items that makes the list appear empty, like only 1 or 2 cells are visible per screen height.
An even stranger situation is, when I scroll really fast to the end and stretch the screen really fast out of the visible area, and there are no more items to load, the visible items can jump from left to right.
Please see these two videos.
https://www.dropbox.com/s/jibflcouz1ena8n/missingProductImages.mov?dl=0
https://www.dropbox.com/s/uz13fzorypnp38t/again.mov?dl=0
I could send code but I didn't want to clutter this place with full length code, let me know if you want to see a specific section of the code please. Maybe someone could have an idea of what might be going on by looking at the vids.
What's probably going on here is a state problem with your collection view cells. The code assigning model values to the cell's views would be of use here. But absent any actual information, the first thing to review would be the cell's prepareForReuse implementation:
Does it call super?
Does it clear out all current values?
Does it cancel any pending asynchronous operations?
Are fetches and cancellations correctly matched?
Next, check if there's any essential configuration in the cell's init/initWithCoder methods -- those are only called on first creation, not on reuse.
Those are the normal pitfalls of UICollectionView cell handling. If they don't suggest the problem, please post the cell code.
It looks like your cells are not being reused correctly.
Can you check that you have set the same reuseIdentifier for your cell in Interface Builder that you are assigning in your code?
Update: Attached image to show where to set the identifier in the storyboard/xib
Update: added layout solution
Another problem could be due to the layout bounds of your collectionViewCell. When you load your cells they bounds are not calculated until they have been added to your collectionView and rendered. This can cause the elements in your cell to layout with the wrong values. This happens commonly with async image frameworks as they cache a resized version of the image for performance. When you cell loads, the cached image is loaded at the wrong the wrong size.
Add the following code to your collectionViewCell:
- (void)setBounds:(CGRect)bounds {
[super setBounds:bounds];
self.contentView.frame = bounds;
[self setHighlighted:NO];
}
I guess this question is more of a best practice question than a problem solving question.
I would like to have a page on my app that has a UITableView at the bottom of it and some buttons/text above the UITableView but instead of just the UITableView scrolling, I would like the whole page to scroll.
I have been searching around and some people say to put the UITableView inside of a UIScrollView and disable scrolling on it and recalculate the height so the table view is as tall as all of it's rows.
Then I have read some other people say just to put the buttons/text in a Table Row Header and just have that scroll with the whole table view.
Which is the better practice and are either of them frowned upon?
Thanks!
Open the main storyboard and on the bottom right hand side you should see a list of view controllers, buttons, gestures etc.. In that list there should be a controller called "page control" that opts for the page-scroll you are looking for as well as the continuous one which you are trying to get rid of, you can just insertt this in to your basic view controller (via drag and drop). As for the button responsible for the segue (turning the page) you can find that in the list too. I can't explain how to program the button to turn the page step by step as I am typing this on my phone at work right now. If you want I can edit this later in more detail