UITableView expander animation - objective-c

I would like to create a kind of expander (like in .net) animation. I have a UITableView filled with an array. When I touch a row I would like to show a UIView (or a SubView i guess) that would expand under the row cell.
Is it possible to achieve this? I've looked at most of the animation UIView setAnimation Transition but none of them would fit my needs.
Thanks for your help!
Oh by the way, I am running Xcode 4.3.3 on Mac os x 10.7.4. Targeted device for now is iPhone 5.1

I haven't seen the .net expander but you can achieve this effect with UITableViews. You can put the View you would like to expand in a cell in a separate section (lets call it section B). You then would hide the cell by setting numberOfRowsInSection: to return 0 for section B.
when you want to show the View, you would set a flag or boolean to make numberOfRowsInSection: return 1 for section B and then call reloadSections:withRowAnimation: to reload section B. Note if you set the row animation to UITableViewRowAnimationTop you would achieve the expander effect..
EDIT:
for hiding the cells in the section you can do it this way:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 2) {//assume 2 is section B
if (showExpandedView) //ShowExpandedView is a BOOL you would use to
return 1; //to trigger the expanded view.
else
return 0;
}
...
}
Hope this helps

Related

Set margin between UITableViewCell using Objective C

Is there a best way to set margin between UITableViewCell, i have one image and a label in table cell, i know this question can be marked as possible duplicate but i have searched a lot and the solution found almost everywhere was ;
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return yourArry.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 1;
}
but problem is that when i tried the above code, the function cellForRowAtIndexPath returned 0 every time and ultimately the whole table was showing the same value in each cell. As i am new to IOS developing, i am unable to resolve this issue, any suggestion will be highly appreciated.
Resolve it, i just set the margin bottom (reset the constraints in AutoLayout) between the imageView and cell container like 10.5, now the imageView is not getting attached with the next cell.

Resizing UICollectionViewCell After Data Is Loaded Using invalidateLayout

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 !

UITableView setContentOffset but don't scroll tableView?

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)

Preload cells of uitableview

I acknowledge that UITableview load dynamically a cell when user scrolls. I wonder if there is a way to preload all cells in order not to load each one while scrolling. I need to preload 10 cells. Is this possible?
You can initialize table view cells, put them into an array precomputedCells and in the data source delegate method tableView:cellForRowAtIndexPath: return the precomputed cells from the array instead of calling dequeueReusableCellWithIdentifier:. (Similar to what you would do in a static table view.)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [self.precomputedCells objectAtIndex:indexPath.row];
}
It might also be useful to have a look at the
WWDC 2012 Session 211 "Building Concurrent User Interfaces on iOS"
where it is shown how to fill the contents of table view cells in background threads while keeping the user interface responsive.
I was looking for a solution to the original question and I thought I'd share my solution.
In my case, I only needed to preload the next cell (I won't go into the reason why, but there was a good reason).
It seems the UITableView renders as many cells as will fit into the UITableView frame assigned to it.
Therefore, I oversized the UITableView frame by the height of 1 extra cell, pushing the oversized region offscreen (or it could be into a clipped UIView if needed). Of course, this would now mean that when I scrolled the table view, the last cell wouldn't be visible (because the UITableView frame is bigger than it's superview). Therefore I added an additional UIView to the tableFooterView of the height of a cell. This means that when the table is scrolled to the bottom, the last cells sits nicely at the bottom of it's superview, while the added tableFooterView remains offscreen.
This can of course be applied to any number of cells. It should even be possible to apply it to preload ALL cells if needed by oversizing the UITableView frame to the contentSize iOS originally calculates, then adding a tableFooterView of the same size.
Hopefully this helps someone else with the same problem.
As suggested by Mark I also changed the height of my UITableView temporarily so that the table view creates enough reusable cells. Then I reset the height of my table view so that it stops creating reusable cells while scrolling.
To accomplish that I create a helper bool which is set to false by default:
var didPreloadCells = false
It is set to true when my table view first reloaded data and therefore created the first reusable cells.
resultsHandler.doSearch { (resultDict, error) -> Void in
[...]
self.tableView.reloadData()
self.didPreloadCells = true
[...]
}
The real trick happens in my viewDidLayoutSubviews Method. Here I set the frame of my table view depending on my boolean. If the reusable cells were not created yet I increase the frame of the table view. In the other case I set the normal frame
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.tableView.frame = self.view.bounds
if !didPreloadCells
{
self.tableView.frame.size.height += ResultCellHeight
}
}
With the help of that the table view creates more initial reusable cells than normal and the scrolling is smooth and fluent because no additional cells need to be created.
Change the dequeueReusableCellWithIdentifier when you are reusing the table view otherwise it will load the old data
Solution for autoresizing cells. Change 'estimatedRowHeight' to lower value
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 32; //Actual is 64
}
Actual estimated height is 64. 32 is used to add more cells for reuse to avoid lagging when scrolling begins

Make cells appear and disappear in TableView

I making an app with a table view and a data source (core data). In this table i group several tasks ordered by date, and i have this segmented control.
I want the table to only load the tasks later or equal than today's date, when the user taps the second segment i want to show all tasks, if he taps the first segment the table must only show the later dates tasks again.
The problem is:
1 - I'm using fetchedResultsController associate with a indexPath to get the managed object.
2 - I use the insertRowsAtIndexPaths:withRowAnimation: and deleteRowsAtIndexPaths:withRowAnimation: methods to make the cells appear and disappear. And this mess with my indexPaths, if i want to go to the detail view of an specific row it is associate with a different indexPath, after delete the rows.
This problem was fixed by a method i did, but i still have other problems of indexPaths and cells, and it seems to me that is gone be me messy to each problem a fix.
There is a simple way to do that?
I tried just to hide the cells instead of delete, it works just fine, but in the place of the hidden cells was a blank space, if there is a way to hide these cells and make the non-hidden cells occupy the blank space i think that will be the simplest way.
Anyone can help me?
set the height of the cell to 0 when it hides, and set the height back to the original value when it appears.
TableViewController.h
#interface TableViewController{
CGFloat cellHeight;
}
TableViewController.m
- (void)cellHeightChange{
//if you need hide the cell then
cellHeight = 0;
cellNeedHide.hidden = YES;
//if you need hide the cell then
cellHeight = 44; // 44 is an example
cellNeedHide.hidden = NO;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
{
switch (section) {
// for example section 0 , row 0 is the cell you wanna hide.
case 0:
switch (row) {
case 0:
return cellHeight;
}
}
}
When the user taps on a segment execute a new fetch request on your managed object to give you an appropriate array (either an array of all dates, or the greater/equal dates). Then use reloadData on the tableView using this new array in the datasource.
or
Give the cell's you wish to hide a height of 0?