UIView Hell. Hiding one subview hides them all - iphone

I am apparently in some swirling UIView hell zone at the moment where up is down sibling is parent and my brain is completely fried.
Here's the deal. Really, really simple. I have a container view with N leaf node sibling subviews. No tricks here, dead simple. I do the following:
// occludedPageSet is the set of view tags corresponding to views that are off screen and // thus fully occluded. This was determined geometrically.
for (NSNumber *n in occludedPageSet) {
// Point to a view corresponding to this tage
UIView *v = [self.containerView viewWithTag:[n integerValue]];
// Hide this view
if (v.hidden == NO) {
NSLog(#"View %d is occluded. Hide it.", [n integerValue]);
v.hidden = YES;
} // if (v.hidden == NO)
} // for (occludedPageSet)
Pretty tame stuff. Unfortunately ALL sibling views vanish! What the?!? How is this possible?
Do I need a [retain]/[release] for v here. I'm stumped.
Baffled,
Doug

Am I missing something about the problem here? It's only natural that if you hide a view, any view it holds as a subview would be hidden as well. After all, you can't see the container view...
If you put ten things in a box and make the box invisible, wouldn't you expect that to mean you couldn't see the things in the box? Similarly an invisibility cloak would be of little use if only the cloak were invisible and not the person beneath...
If you need some things visible and some not, work on the specific items and not the container.

Apparently, all of your views are included in occludedPageSet, or all of your tags are the same n.
NSNumber *n in occludedPageSet
Or, one of the v views is the parent of the rest, so when you hide it, you hide them all.

Make sure self.containerView's tag is something completely different from any of the children's tags. Calling viewWithTag will return the receiver if it is the given tag, which will in turn hide all of your views. Either step through the iteration or print out the address that v points to so that you know you're occluding what you should be occluding.

Related

How to tell UICollectionView to preload a larger range of cells?

I have a UICollectionView which shows images retrieved from the web. They are downloaded asynchronous.
When user scrolls fast, they see placeholders until the cell loads. It seems UICollectionView only loads what is visible.
Is there a way to say "collection view, load 20 cells more above and below" so chance is higher that it loaded more cells while user was looking at content without scrolling?
The idea is to have the VC recognize when a remote load might be required and start it. The only tricky part is keeping the right state so you don't trigger too much.
Let's say your collection is vertical, the condition you want to know about is when:
BOOL topLoad = scrollView.contentOffset.y < M * scrollView.bounds.size.height
or when
BOOL bottomLoad = scrollView.contentOffset.y > scrollView.contentSize.height - M * scrollView.bounds.size.height
in other words, when we are M "pages" from the edge of the content. In practice though, this condition will be over-triggered, like when you're first loading, or if you're testing it on scrollViewDidScroll, you don't want to generate web requests for every pixel of user scrolling.
Getting it right, therefore, requires additional state in the view controller. The vc can have a pair of BOOLs, like topLoadEnabled, bottomLoadEnabled, that are NO until the view is ready. Then, scroll delegate code looks like this:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// compute topLoad and bottomLoad conditions
if (topLoad && self.topLoadEnabled) [self startTopLoad];
similarly for bottom. The load code looks like this, abstractly:
self.topLoadEnabled = NO; // don't trigger more loading until we're done
[self.model getMoreTopStuff:^(NSArray *newStuff, NSError *error) {
// do view inserts, e.g. tableView.beginUpdates
self.topLoadEnabled = YES;
}];
Same idea for bottom. We expect the model to fetch for itself (maybe the model has image urls) and cache the result (then the model has images). As the datasource for the view, the view controller gets called upon to configure view cells. I can just naively ask the model for images. The model should answer either fetched images or placeholders.
Hope that makes sense.
In my opinion you are making the wrong assumption: cells are just views so you shouldn't treat them as model objects. UICollectionView and UITableView are very efficient because they constantly recycle cells so you should think in therms of pre loading content in the business side of things. Create interactor or viewmodel objects and populate your data source with those, then you'll be able to ask those objects to preload images, if you still wish to do so.
A BOOL flag seldom is the answer. I'd rather go for estimating a reasonable page size and fetching images as needed from the cellForItemAtIndePath method.

UIView Content Clipping Not Working/UIView Resize Not Working

I ran into an issue where clipping of subviews is not working consistently (or potentially, the UIView resize isn't working). Here's the scenario:
I have a UIView (section above the line). The actual size is larger to accommodate search bar. I basically resize it on viewDidLoad.
When clicking on the text field, UIView expands and show a search bar
After search, I collapse the UIView, but found that the UIView is not collapsed in certain case (see first image). If I were to not hide the search bar, it will remain also.
Is there any reason why this issue occurs? I'm still trying to debug and see if there is anything that could have caused this issue (as something may reset to original size). It definitely does seem like everything is resized correctly since even the result table is moved correctly also. Something else must have triggered it after the expected resize. Any pointer is appreciated. I've only done iOS development for 5 days so I'm still not aware of a lot of things.
- (void)showAddressField
{
CGRect headerFrame = self.searchHeaderView.frame;
headerFrame.size.height = headerHeight + adjustmentSize;
self.searchHeaderView.frame = headerFrame;
CGRect tableFrame = resultTableView.frame;
tableFrame.size.height = tableHeight - adjustmentSize;
tableFrame.origin.y = headerFrame.size.height + statusBarHeight;
resultTableView.frame = tableFrame;
[self renderHeaderBorder:headerFrame.size.height - 1];
}
- (void)hideAddressField
{
CGRect headerFrame = self.searchHeaderView.frame;
headerFrame.size.height = headerHeight;
self.searchHeaderView.frame = headerFrame;
CGRect tableFrame = resultTableView.frame;
tableFrame.size.height = tableHeight;
tableFrame.origin.y = headerHeight + statusBarHeight;
resultTableView.frame = tableFrame;
[self renderHeaderBorder:headerHeight - 1];
}
EDITED
SearchHeaderView is just a UIView that is a subview of the main view. It's not a table header. I found that if I put a search bar in the table header, it behaves very unpredictably so I have a UIView containing the search portion and have a UITableView right below it.
Sorry, since I only have just over a week to get a rather massive app out from scratch, I didn't have time to wait. I already changed my approach a little bit, but I will still award the points even after the bounty has expired. I'm trying to understand everything to do with layout since that's pretty much the only thing that I can't quite figure out with iOS app development.
If the search bar is in the table view header, try reassigning the header view to the table view:
[tableView setTableHeaderView:self.searchHeaderView];
If it is a section header, be sure to update the value for the delegate's
– tableView:heightForHeaderInSection:
– tableView:viewForHeaderInSection:
Otherwise, please post more code on how the searchHeaderView is initialized and added to the table.
Hope this helps!
Firstly, frames are already relative to the enclosing window. You should not be taking into account the height of the status bar (assuming that refers to the 20px area at the top of the screen).
I have found in the past that you may need to implement "layoutSubviews" in your view controller and compute the frames of the views there. It depends on your resizing mask / if you auto-layout enabled.
As the others have stated, more code would be helpful... or at least the relevant portions of the nib/xib.

Pushing a view controller after scrolling is complete

When a user adds an item to my list, I want to scroll to the new row, highlight it, and select it (which will push a new controller). The key part is waiting for the scroll animation to complete before pushing the new controller.
In this answer, I learned how to use the animation delegate to wait until the scroll is complete.
However, if the insertion row is already on scree, the table view will not scroll and the method will not fire.
How can I wait to push the new controller until the end of the scroll, and deal with the case where no scroll will be initiated - and how might I tell the difference between each case?
The easiest way to check whether a given row is visible in your table view is something like this:
if (!CGRectContainsRect([self.tableView bounds], [self.tableView rectForRowAtIndexPath:indexPath])
{
// the row is partially outside the table view’s boundaries and needs to be scrolled for full visibility
}
else
{
// the row is within the boundaries and does not need to be scrolled
}
Try creating a method to see if scrolling is needed. If no scrolling is needed, call the push right away, otherwise wait for the delegate call and push.
- (BOOL)isSrollingingNeededForIndexPath:(NSIndexPath *)indexPath {
NSArray *visibleIndices = [self.tableView indexPathsForVisibleRows];
for (NSIndexPath *visibleIndexPath in visibleIndices)
if ([indexPath compare:visibleIndexPath] == NSOrderedSame)
return NO;
return YES;
}
Edit: Good point. Since indexPathsForVisibleRows is used for data rendering.
You could do essentially the same thing with indexPathsForRowsInRect where you use the content.offset.y and the tableview.frame.size.height to determine your "visible rect".
Then to account for partially visible rows at the top and bottom you could add rowHeight-1 to the top of the rect and subtract rowHeight - 1 from the bottom of the rect. Code shouldn't be too gnarly if you have static height rows. If you have varying height rows it would still work, but it would be a bit more involved.
All said though, it seems like a lot of code for something which you'd think would have a simple answer.

Large UICollectionViewCell's disappearing with custom layout

I subclassed UICollectionViewLayout. So far, so good.
However, I have some cells that are wider than the screen, and sometimes these disappear while scrolling. When you scroll some more, they magically reappear where they should be. I can show you my code, however I think there's nothing wrong with it since it works in 90% of the cases.. However, really large cells (more than two times the screen size) disappear sometimes.
NSMutableArray* attributes = [NSMutableArray array];
for (int section=0; section < [[self collectionView] numberOfSections]; section++) {
[attributes addObject:[self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]];
for (int row=0; row < [[self collectionView] numberOfItemsInSection:section]; row++) {
NSIndexPath* indexPath = [NSIndexPath indexPathForItem:row inSection:section];
[attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
}
}
return attributes;
I've also these posts: UICollectionView's cell disappearing and Large cells in a UICollectionView getting removed while the cell is still displayed
However, a solution isn't mentioned. Can anybody help me with this? Could it be the problem is on Apple's side? And if it is, is there anything I can do to solve it myself?
Sorry to tell you: I'm pretty sure this a real bug in UICollectionView; I've run into this exact same thing a few weeks ago. I've made a small program to demonstrate the bug and filed a RADAR with Apple, but haven't heard back on whether or if they plan on fixing it. The best workaround I can think of (Warning: I haven't yet implemented this workaround) is to notice that the layout attributes are very far off the edges of the screen, intentionally change the bounds of the cell attributes so it only slightly goes off screen, and possibly store that offset (the amount that you trimmed off of what the real bounds should be) as an additional custom field in the layoutAttributes, so your cell knows to draw it's content correctly. Which feels like a gross hack, but it ought to work.
Are you sure you return the right attribute object for this cell when method -(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect is called and when the rect is just a part of your cell ?

Using tags in iphone programming--can someone explain?

I've seen people using tags in iphone programming, like inside labels or tableview cells:
name.tag = kNameTag
Can someone explain with an example how these tags might be used? I gather that it's so that you can refer to a ui element later? Like if you programmatically use a for loop to create an array of UIButtons onto the iphone screen, do you assign tags to each button within the for loop or something?
Thanks!
The example you've included in your question is one of the common ones.
You can instantiate buttons (or other UI elements) in a loop, assigning a incremental tag to each. When an IBAction is invoked by one of those buttons, you can ask the sender for it's tag, which tells you exactly which button triggered the request.
for( int i = 0; i < 10; i++ ) {
UIButton * button = [[UIButton alloc] init...];
button.tag = i;
}
IBAction:
- (IBAction)doSomethingFromButtonTap:(id)sender {
NSLog(#"Button pressed: %d", [sender tag]);
}
They're also widely used to find specific subviews within a parent view. UIView provides a viewWithTag:(NSInteger)tag method. This is useful when building custom views without subclassing (or situations where you don't want to hold references to subviews, but know the tag).
Tags are integers. You assign them to a view using UIView.tag. You then use -[UIView viewWithTag:] to search the view hierarchy for the view.
UIKit doesn't use tags (I think), so they're free for you to use as necessary. However, tags are global to your app, so they're not an ideal replacement for IBOutlet (but it's often more convenient when you have a lot of views).
Avoid using 0 as a tag, since it's the default tag — [v viewWithTag:0] is unlikely to return the view you're looking for.