I have custom and very simple UITableViewCell.
I just want to init my table view by using new technique in ios 6, i.e. registering class and after deque cell with identifier.
So i have UITableViewController, inside the tableview i have one prototype cell , here is the image of it.
at first i forgot to register the class in viewDidLoad method inside the Table View controller and it was working fine. here is the code.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:#"customCellIdentifier" forIndexPath:indexPath];
return cell;
}
but in this case
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
is not called during the cell init process.
and the after i put the register class functionality inside the viewdidload.
here is the code
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerClass:[CustomCell class] forCellReuseIdentifier:#"customCellIdentifier"];
}
after it the - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier starts invoking but in this case the cells are absolutely empty when i'm trying to debug , inside the initwithstyle method all the outlets are nil.
whats going on? what i'm doing wrong?
If you have defined the cell as prototype cell for the table view in your nib/storyboard file, then you don't have to register it, that is done automatically. But in that case, initWithCoder: is called for cells instantiated from the nib/storyboard.
Related
I have a basic table view cell that is all SQLite3 database generated.
When I click on the Disclosure Indicator in a cell to move to a View Controller so I can edit the contents of the previous cell, how can I just pass the row# or the id# over?
Is there a cute way of doing this in iOS6?
EH
You can use didSelectRowAtIndexPath method of UITableView delegate for your purpose
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
/* your other stuff */
NSLog ( #\"You selected row: %d\", indexPath.row ); // Gives you row#
}
To pass row# to another DetailViewController,
Add following line in DetailViewController.h file
#property (readwrite) int selectedRow;
And synthsis it in DetailViewController.m file
#synthesize selectedRow;
Now you can use selectedRow by creating object of DetailViewController like,
[detailViewController setSelectedRow:indexPath.row];
This question already has answers here:
UIRefreshControl - beginRefreshing not working when UITableViewController is inside UINavigationController
(15 answers)
Closed 9 years ago.
I am not able to see the loading spinner when calling beginRefreshing
[self.refreshControl beginRefreshing];
My UITableViewController subclass uses a UIRefreshControl
// refresh
UIRefreshControl * refreshControl = [UIRefreshControl new];
[refreshControl addTarget:self action:#selector(refreshTableView) forControlEvents:UIControlEventValueChanged];
self.refreshControl = refreshControl;
It is working perfectly with user interaction (when the user drops the table down), then the spinner is visible.
But when i call beginRefreshing on viewDidLoad, I don't see the spinner (only when i drag the table down).
Notes:
self.refreshControl reference is right
reloadData or endRefreshing is not called immediately after beginRefreshing, but there is a long time delay (loading data through network), so I am not canceling the beginRefreshing.
Edit :
This only happens when the contentOffset property of the tableView is 0 and i call [self.refreshControl beginRefreshing]. Bug? Feauture?
It looks like a bug to me, because it only occures when the contentOffset property of the tableView is 0
I fixed that with the following code (method for the UITableViewController) :
- (void)beginRefreshingTableView {
[self.refreshControl beginRefreshing];
if (self.tableView.contentOffset.y == 0) {
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^(void){
self.tableView.contentOffset = CGPointMake(0, -self.refreshControl.frame.size.height);
} completion:^(BOOL finished){
}];
}
}
Your fix looks good, But I don't think this as a bug.
When beginRefreshing method is called manually,
When there is no row / cell available it makes sense for refresh control appearing automatically.
But when there are some cells available, and when we call begin refresh manually (A scenario where we refresh periodically based on timer) then It should not animate / change the content offset as it will distract the user if he is seeing / reading content in some visible cell.
I have some data that is fetched in another thread that updates a UICollectionView's header. However, I've not found an efficient way of reloading a supplementary view such as a header or footer.
I can call collectionView reloadSections:, but this reloads the entire section which is unnecessary. collectionView reloadItemsAtIndexPaths: only seems to target cells (not supplementary views). And calling setNeedsDisplay on the header itself doesn't appear to work either. Am I missing something?
You can also use (the lazy way)
collectionView.collectionViewLayout.invalidateLayout() // swift
[[_collectionView collectionViewLayout] invalidateLayout] // objc
More complex would be to provide a context
collectionView.collectionViewLayout.invalidateLayout(with: context) // swift
[[_collectionView collectionViewLayout] invalidateLayoutWithContext:context] // objc
You can then make a or configure the context yourself to inform about what should be updated see: UICollectionViewLayoutInvalidationContext
It has a function in there that you can override:
invalidateSupplementaryElements(ofKind:at:) // swift
Another option is (if you have already loaded the correct header/footer/supplementary view) and you only want to update the view with the new data than you can use one of the following functions to retrieve it:
supplementaryView(forElementKind:at:) // get specific one
visibleSupplementaryViews(ofKind:) // all visible ones
Same goes for visible cells with visibleCells. The advantage of just getting the view and not reloading a view entirely is that the cells retains it state. This is espically nice with table view cells when they use swipe to delete/edit/etc since that state is lost after reloading the cell.
If you feel fanatic you can of course also write some extensions to retrieve only cells/supplementary views of a given kind using generics
if let view = supplementaryView(forType: MySupplementaryView.self, at: indexPath) {
configure(view, at indexPath)
}
this assumes that you have a function that registers/dequeues views in example with their class name. I made a post about this here
I just ran into the same problem, and I ended up looking up the view using its tag to edit a label:
UICollectionReusableView *footer = (UICollectionReusableView*)[self.collectionView viewWithTag:999];
UILabel *footerLabel = (UILabel*)[footer viewWithTag:100];
Like you said it is unnecessary to reload an entire section, which cancels out any animation on cells as well. My solution isn't ideal, but it's easy enough.
Swift 3/4/5 version:
collectionView.collectionViewLayout.invalidateLayout()
Caution!
If you change the number of collectionView items at the same time (for example you show the footer only if all cells were loaded), it will crash. You need to reload the data first, to make sure that the number of items is the same before and after invalidateLayout():
collectionView.reloadData()
collectionView.collectionViewLayout.invalidateLayout()
I got the same problem. I tried #BobVorks's answer and it is working fine, if only the cell was reused else it won't. So, I tried finding a more cleaner way to achieve this and I came up reloading the whole UICollectionView after the performBatchUpdate (completion block) and it is working great. It reloads the Collection Without any cancellation of animation in the insertItemsAtIndexPath. Actually I personally up voted recent 2 answers cause i find it working but in my case, this is the cleanest way to do it.
[self.collectionView performBatchUpdates:^{
// perform indexpaths insertion
} completion:^(BOOL finished) {
[self.collectionView reloadData];
}];
[UIView performWithoutAnimation:^{
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:4]];
}];
[UIView performWithoutAnimation:^{
[self.collectionView reloadData];
}];
Here are two ways you could do it.
1.
Create a mutable model to back the data that will eventually be available. Use KVO in inherited class of UICollectionReusableView to observe the changes and update the header view with the new data as it comes available.
[model addObserver:headerView
forKeyPath:#"path_To_Header_Data_I_care_about"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:NULL];
then implement listener method in header view
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
2.
add notification listener to the view and post a notification when the data has successfully come available. Downside is that this is application wide and not a clean design.
// place in shared header file
#define HEADER_DATA_AVAILABLE #"Header Data Available Notification Name"
// object can contain userData property which could hole data needed.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(headerDataAvailable:) name:HEADER_DATA_AVAILABLE object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:HEADER_DATA_AVAILABLE object:nil];
let headerView = collectionView.visibleSupplementaryViews(ofKind: UICollectionView.elementKindSectionHeader)[0] as! UICollectionReusableView
I've used above method to get current header, and successfully updated subviews on it.
Here's what I did to update only the section headers that are currently loaded in memory:
Add a weakToStrong NSMapTable. When you create a header, add the header as the weakly held key, with the indexPath object. If we reuse the header we'll update the indexPath.
When you need to update the headers, you can now enumerate the objects/keys from the NSMapTable as needed.
#interface YourCVController ()
#property (nonatomic, strong) NSMapTable *sectionHeaders;
#end
#implementation YourCVContoller
- (void)viewDidLoad {
[super viewDidLoad];
// This will weakly hold on to the KEYS and strongly hold on to the OBJECTS
// keys == HeaderView, object == indexPath
self.sectionHeaders = [NSMapTable weakToStrongObjectsMapTable];
}
// Creating a Header. Shove it into our map so we can update on the fly
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
PresentationSectionHeader *header = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:#"presentationHeader" forIndexPath:indexPath];
// Shove data into header here
...
// Use header as our weak key. If it goes away we don't care about it
// Set indexPath as object so we can easily find our indexPath if we need it
[self.sectionHeaders setObject:indexPath forKey:header];
return header;
}
// Update Received, need to update our headers
- (void) updateHeaders {
NSEnumerator *enumerator = self.sectionHeaders.keyEnumerator;
PresentationSectionHeader *header = nil;
while ((header = enumerator.nextObject)) {
// Update the header as needed here
NSIndexPath *indexPath = [self.sectionHeaders objectForKey:header];
}
}
#end
This question is very old but a simple way to do it is to just set a delay that covers the time your view is animating and disabling the animation while you update the view...usually a delete or insert takes about .35 seconds so just do:
delay(0.35){
UIView.performWithoutAnimation{
self.collectionView.reloadSections(NSIndexSet(index: 1))
}
My problem arose when frame sizes for the supplementary views changed upon invalidating the layout. It appeared that the supplementary views were not refreshing. It turns out they were, but I was building the UICollectionReusableView objects programmatically, and I was not removing the old UILabel subviews. So when the collection view dequeued each header view, the UILabels would pile up, causing erratic appearance.
The solution was to build each UICollectionReusableView completely inside the viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath method, starting by a) removing all subviews from the dequeued cell, then b) getting the frame size from the item's layout attributes to allow adding the new subviews.
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
yourClass *header = (yourClass *)[collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:#"identifier" forIndexPath:indexPath];
[[header viewWithTag:1] removeFromSuperview]; // remove additional subviews as required
UICollectionViewLayoutAttributes *attributes = [collectionView layoutAttributesForSupplementaryElementOfKind:kind atIndexPath:indexPath];
CGRect frame = attributes.frame;
UILabel *label = [[UILabel alloc] initWithFrame: // CGRectMake based on header frame
label.tag = 1;
[header addSubview:label];
// configure label
return header;
}
I have got a Perfect solution:
let footerView = self.collectionView.visibleSupplementaryViews(ofKind: UICollectionView.elementKindSectionFooter)
Now you can access all subview of footerView by using:
footerView[0].subviews[0]
If you have label in your footerView then :
let label: UILabel = footerView[0].subviews[0] as? UILabel ?? UILabel()
Final Step:
label.text = "Successfully Updated Footer."
if let footerView = collectionView.subviews.first(where: {$0 is LoadingFooterCell}) as? LoadingFooterCell {
footerView.isLoading = .loading
}
Has anyone seen a decent answer to this problem?
initialLayoutAttributesForAppearingItemAtIndexPath seems to be being called for all visible cells, not just the cell being inserted. According to Apple's own docs:
For moved items, the collection view uses the standard methods to retrieve the item’s updated layout attributes. For items being inserted or deleted, the collection view calls some different methods, which you should override to provide the appropriate layout information
Which doesn't sound like what is happening... the other cells aren't being inserted, they are being moved, but it's calling initialLayoutAttributesForAppearingItemAtIndexPath for the ones being moved too.
I have seen work arounds using prepareForCollectionViewUpdates: to trace which indexPaths are being updated and only changing those, but this seems a bit odd that it's going agains their own docs. Has anyone else found a better way around this?
I found this blog post by Mark Pospesel to be helpful.
The author also fixed WWDC CircleLayout sample and posted it on Github.
Methods of interest:
- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems
{
// Keep track of insert and delete index paths
[super prepareForCollectionViewUpdates:updateItems];
self.deleteIndexPaths = [NSMutableArray array];
self.insertIndexPaths = [NSMutableArray array];
for (UICollectionViewUpdateItem *update in updateItems)
{
if (update.updateAction == UICollectionUpdateActionDelete)
{
[self.deleteIndexPaths addObject:update.indexPathBeforeUpdate];
}
else if (update.updateAction == UICollectionUpdateActionInsert)
{
[self.insertIndexPaths addObject:update.indexPathAfterUpdate];
}
}
}
- (void)finalizeCollectionViewUpdates
{
[super finalizeCollectionViewUpdates];
// release the insert and delete index paths
self.deleteIndexPaths = nil;
self.insertIndexPaths = nil;
}
// Note: name of method changed
// Also this gets called for all visible cells (not just the inserted ones) and
// even gets called when deleting cells!
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
// Must call super
UICollectionViewLayoutAttributes *attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];
if ([self.insertIndexPaths containsObject:itemIndexPath])
{
// only change attributes on inserted cells
if (!attributes)
attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
// Configure attributes ...
attributes.alpha = 0.0;
attributes.center = CGPointMake(_center.x, _center.y);
}
return attributes;
}
// Note: name of method changed
// Also this gets called for all visible cells (not just the deleted ones) and
// even gets called when inserting cells!
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
// So far, calling super hasn't been strictly necessary here, but leaving it in
// for good measure
UICollectionViewLayoutAttributes *attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath];
if ([self.deleteIndexPaths containsObject:itemIndexPath])
{
// only change attributes on deleted cells
if (!attributes)
attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
// Configure attributes ...
attributes.alpha = 0.0;
attributes.center = CGPointMake(_center.x, _center.y);
attributes.transform3D = CATransform3DMakeScale(0.1, 0.1, 1.0);
}
return attributes;
}
You're not alone. The UICollectionViewLayout header file comments make things a little clearer.
For each element on screen before the invalidation,
finalLayoutAttributesForDisappearingXXX will be called and an
animation setup from what is on screen to those final attributes.
For each element on screen after the invalidation,
initialLayoutAttributesForAppearingXXX will be called an an animation
setup from those initial attributes to what ends up on screen.
Basically finalLayoutAttributesForDisappearingItemAtIndexPath is called for each item on screen before the animation block starts, and initialLayoutAttributesForAppearingItemAtIndexPath is called for each item after the animation block ends. It's up to you to cache the array of UICollectionViewUpdateItem objects sent in prepareForCollectionViewUpdates so you know how to setup the initial and final attributes. In my case I cached the previous layout rectangles in prepareLayout so I knew the correct initial positions to use.
One thing that stumped me for a while is you should use super's implementation of initialLayoutAttributesForAppearingItemAtIndexPath and modify the attributes it returns. I was just calling layoutAttributesForItemAtIndexPath in my implementation, and animations weren't working because the layout positions were different.
If you've subclassed UICollectionViewFlowLayout, you can call the super implementation. Once you've got the default initial layout, you can check for an .alpha of 0. If alpha is anything other than 0, the cell is being moved, if it's 0 it's being inserted.
Bit of a hack, I know, but it works 👍.
Swift 2.0 implementation follows:
override func initialLayoutAttributesForAppearingItemAtIndexPath(itemIndexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
guard let attributes = super.initialLayoutAttributesForAppearingItemAtIndexPath(itemIndexPath) where attributes.alpha == 0 else {
return nil
}
// modify attributes for insertion here
return attributes
}
Make sure you're using new method signature in Swift 3. Autocorrection doesn't work for this method:
func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes?
I am setting up routing to a TTTableViewController as follows:
[map from:#"myurl://filter/(initWithName:)"
toViewController:[FilterViewController class]];
and then, in another view controller I set up a mutable dictionary to pass through as my query:
// Set up dictionary and populate a field
NSMutableDictionary *filterDictionary;
filterDictionary = [[NSMutableDictionary alloc] init];
[filterDictionary setObject:#"test entry" forKey:#"test key"];
// Attach a query to the URL and open it
TTURLAction *theAction = [[TTURLAction actionWithURLPath:#"myurl://filter/search"]
applyQuery:filterDictionary];
[[TTNavigator navigator] openURLAction:theAction];
Finally, in the filter view controller, I can correctly access the values:
in .h:
#property (nonatomic, retain) NSMutableDictionary *filterDictionary;
in .m:
- (id)initWithName:(NSString *)filterName query:(NSMutableDictionary *)filters {
if (self = [self init]) {
self.filterDictionary = filters;
NSLog(#"Filter Dictionary assigned : %#", self.filterDictionary);
}
return self;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
if (filterDictionary)
[filterDictionary setObject:textField.text forKey:#"searchAddress"];
[textField resignFirstResponder];
return YES;
}
The dictionary is correctly mutable and I can add values etc. without problem. However, when my filterViewController is dismissed, I assumed the changed dictionary would be reflected in the parent - it was passed by reference correctly.
Am I missing something? Is my dictionary in the filterVC actually a copy due to a base class of Three20 somewhere?
I'm running into a similar issue. I suspect we may need to pass in a delegate (via that query), along with your dictionary as a separate object. Then have the parent honor a protocol defined in this new VC, wherein you can now pass back that dictionary at the proper time.
TTNavigator also has viewControllerForURL:query: (among others) for obtaining a VC without opening it, but perhaps passing in the query and having the VC "do the right thing" is enough, plus I think - accent on think - the idea is to start using URL Actions and not just URLs (in the Three20 sense).