I am trying to make a view controller which deals with the user login. Since I needed the view controller to be scrollable, contain a separate view (for the login), and contain a background, I decided to go with the route of making a tableviewcontroller, subclassing it, and than adding in the necessary views. I subclassed UITableViewController and added this code into the viewdidload()
UIImageView *tempImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"TableViewControllerBlurred.png"]];
[tempImageView setFrame:self.tableView.frame];
self.tableView.backgroundView = tempImageView;
[tempImageView release];
This successfully added my background image to the controller and at this point, the view controller looked like: http://imgur.com/ST4H8uf as it was supposed to.
Moving on, I began working with static cells, dropped in a view into one of the cells and began to design the sign in screen. At this point, my storyboard looked like: http://imgur.com/n6GKeGq&ST4H8uf but the problem comes about when I run the project.
When I run the project, I keep getting the same background screen as seen in the first image without any of the new static cells or views. All and any help is much appreciated as to what may be the cause of this problem. Thank you.
CellForRowAtIndexPath Code:
*/
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:<##"reuseIdentifier"#> forIndexPath:indexPath];
// Configure the cell...
return cell;
}
*/
If what you want is a UITableView with just static cells, then learn to use UIScrollView with a UIViewController.
#interface vc : UIViewController
#property (nonatomic, strong) UIScrollView *scrollView;
#end
#implementation vc
- (id)init // or whatever initializer you are using to make your view controller
{
self = [super init];
if (self) {
_scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0,0,320,568)];
[_scrollView setContentSize:CGSizeMake(320,568)]; // equals one screen
[_scrollView setContentSize:CGSizeMake(320,568*2)]; // equals two screens, etc
// contentSize property determines how much you can scroll inside the UIScrollView view if that makes any sense to you.
[self.view addSubview:_scrollView]
// one way of adding a background
UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"imageName"]];
[self.view addSubview:backgroundImageView];
[_scrollView addSubview:[self newStaticCellAtPosition:CGRectMake(0,0,320,45)]];
[_scrollView addSubview:[self newStaticCellAtPosition:CGRectMake(0,45,320,45)]];
// add subviews, you can even use UITableViewCell if you want.
// I'd use simple UIView's and draw separators and whatnot myself if I were you.
}
return self;
}
- (UIView *)newStaticCellAtPosition:(CGRect)position
{
UIView *staticCell = [[UIView alloc] initWithFrame:position];
[staticCell setBackgroundColor:[UIColor redColor]];
return staticCell;
}
#end
For other properties you should check out UIScrollView documentation. Remember UITableView inherits from UIScrollView so if it's easy to pick and choose what you want.
first check datasource and delegate of tableview has to be set.
You might be geeting a problem beacuse of that.
Never use a UITableViewController! In almost every case I have come across it is much much easier to use a UIViewController and add a table view. You simply cannot get at the backgroundView of a UITableViewController and have it scroll properly. I realize that you can only make a "static" table view with a UITableViewController but its simple enough to mimic the exact same behaviour with a regular table view and you don't have to deal with the headache of not being able to add views behind the table (like a background image).
Related
I have UITableViewCell that contains UIView (lets call it CPView) which is created while cellForRowAtIndexPath is called. CPView is just a plain coloured view and for every cell its width is different (that's why needed to create in cellForRowAtIndexPath).
Problem is
1)The CPView 's colour gets darker every time cell loads (May be due to every time that cell creates the same view so overlapping effect).
2) The cell overlaps / inherits other cell's CPView (we can see this because of light and dark colour of two CPView).
How can I prevent cell to recreate if it already exist or creation of this CPView again?
Edit
- (void)configureCell:(CreditDebitCell *)cell atIndexPath:(NSIndexPath *)indexPath {
//other code
UIView * CPView;
if (CPView){
CPView =nil;
}
else
{
CPView = [[UIView alloc] initWithFrame:CGRectMake(cell.bounds.origin.x, cell.bounds.origin.y, cell.frame.size.width*[self.percentArray[indexPath.row] floatValue] ,cell.frame.size.height )];
[CPView setClipsToBounds:YES];
[CPView setBackgroundColor:[UIColor colorWithRed:107/255.0 green:15/255.0 blue:47/255.0 alpha:0.5]];
[cell addSubview: CPView];
}
}
The issue here is reuse of the cells - and therefore you get multiple views added to your cell view.
You can:
-remove subview
-check if subview exists and do/don't do anything.
You can check if the subview is there by going through subviews:
for (UIView *v in cell.contentView.subview) {
if ([v isKindOfClass:[CPView class]]) {
// remove or flag that it exists
}
}
But I think that you should handle this in your cell - not your view controller that implements table view delegate. Better tell cell to use some view/hide some view based on some kind of logic then to do that inside cellForRowAtIndexPath
According to your i question(without cellforRowAtIndexpath) i can assume that you should check every time something like in cellForRowAtIndexPath
if(cpView){
cpView = nil;
}
// alloc again with required size for particular row.
Make a subclass of your UITableViewCell and make a property of it that will reference your CPView. This will now let you have a better control whether your subclassed cell does / doesn't have any CPView that needs to be added.
I've been reading online tutorials on UICollectionView with different layouts. Also looked at a lot of SO Questions on the subject. But it seems what I am looking might be something more simple but I am stuck on how to go forward.
The Goal
I have a UIViewController that is embedded in a UINavigation controller. I am displaying data in a UITableView which includes:1 UIImageView and three UILabels in each cell. The data is fetched from a server and all works nicely.
I then wanted to have a UIButton that, when tapped, would kick off a cool animation that shows the cells transition into a nice grid view.
It suddenly dawned on me that I needed to use a UICollectionView to change between these two cells and ditch the UITableView completely. Tapping the button again, would switch back to the last state (Grid or UITableView style)
The grid cell needs to loose one label - but keep the image.
The problem
I have spent the last two days reading up on UICollectionView and UICollectionViewFlowLayout. I think I could use a Apple's pre-made UICollectionViewFlowLayout and just tweak it a little.
I don't know if I need two custom cells or one cell that changes shape between the two views and how the animations must work.
I'm not looking for the exact code to do this - I just need to know which direction I need to go in and if I need to use two custom cells - and how do I change between the two with animation and not reloading all the data again.
Appreciate any input.
Thanks all.
I finally found a solution that was acceptable to my need. If anyone ever has similar needs - this is how you use two different custom UICollectionViewCell's and how to change between the two different cells / layouts.
First thing is create the customCells in IB - creating the xib
files.
Then set the up as you need
Since my requirement needed the standard flow layout provided by the class UICollectionViewFlowLayout - I just needed to create two custom layouts and tweak them to my needs.
Create two (or more if needed) classes that subclass UICollectionViewFlowLayout
In the implementation - setup the layout as needed. Since I am subclassing the pre-made UICollectionViewFlowLayOut and all I need to do is tweak it - the implementation is pretty simple.
So - for the table view layout I did this:
tableViewFlowLayOut.m
-(id)init
{
self = [super init];
if (self){
self.itemSize = CGSizeMake(320, 80);
self.minimumLineSpacing = 0.1f;
}
return self;
}
This sets each cells width and height to the values I needed. self.minimumLineSpacing sets the spacing between the cells. (Spacing between the cell above / below )
Then for the grid layout:
gridFlowLayOut.m
-(id)init
{
self = [super init];
if (self){
self.itemSize = CGSizeMake(159, 200);
self.minimumInteritemSpacing = 0.1f;
self.minimumLineSpacing = 0.1f;
}
return self;
}
Same as before - however, this time I needed spacing between my cells right edge -
self.minimumInteritemSpacing = 0.1f'
takes care of that.
Right - now to put it all together - in the viewController that has the UICollectionView
viewController.m
// Import the new layouts needed.
#import "GridFlowLayOut.h"
#import "TableViewFlowLayOut.m"
//Create the properties
#property (strong, nonatomic) TableViewFlowLayOut *tableViewLayout;
#property (strong, nonatomic) GridFlowLayOut *grideLayout;
-(void)viewDidLow
{
//Register the two custom collection view cells you created earlier. Make sure you set the correct reuse identifier here.
[self.tradeFeedCollectionView registerNib:[UINib nibWithNibName:#"TableViewCell" bundle:nil] forCellWithReuseIdentifier:#"TableItemCell"];
[self.tradeFeedCollectionView registerNib:[UINib nibWithNibName:#"GridViewCell" bundle:nil] forCellWithReuseIdentifier:#"GridItemCell"];
}
-(void)viewWillAppear
{
//Create the layout objects
self.grideLayout = [[GridFlowLayOut alloc]init];
self.tableViewLayout = [[TableViewFlowLayOut alloc]init];
//Set the first layout to what it should be
[self.tradeFeedCollectionView setCollectionViewLayout:self.tableViewLayout];
}
Right - now to change between the layouts with some animation. This is actually very easy to do and only needs a few lines of code -
I called this code in a button method in viewController.m
-(void)changeViewLayoutButtonPressed
{
//BOOl value to switch between layouts
self.changeLayout = !self.changeLayout;
if (self.changeLayout){
[self.tradeFeedCollectionView setCollectionViewLayout:self.grideLayout animated:YES];
}
else {
[self.tradeFeedCollectionView setCollectionViewLayout:self.tableViewLayout animated:YES];
}
}
And lastly in cellForItemAtIndexPath
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{ static NSString *tableCellIdentifier = #"TableItemCell";
static NSString *gridCellIdentifier = #"GridItemCell";
//BOOL used to detect which layout is active
if (self.gridLayoutActive == NO){
CustomCollectionCellClass *tableItemCell = [collectionView dequeueReusableCellWithReuseIdentifier:tableCellIdentifier forIndexPath:indexPath];
//Setup the cell
}
return tableItemCell;
}else
{
CustomCollectionCellClass *gridItemCell= [collectionView dequeueReusableCellWithReuseIdentifier:gridCellIdentifier forIndexPath:indexPath];
//Setup the cell
}
return gridItemCell;
}
return nil;
}
Of course you will need to conform to the other UICollectionView delegates and setup the remaining stuff.
This actually took me a while to figure out. I really hope it helps others out there.
If anyone wants a demo project - I'll happily create one and upload to GitHub.
For anyone new to UICollectionViews I highly recommend reading Apple's programming guide on the subject - it was this document which lead me to this solution.
Reference:
https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/Introduction/Introduction.html
I'm having an issue,
I have a simple UICollectionView with a static 200 cells that load images from Flickr.
my CellForItemAtIndexPath looks like this:
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"FlickrCell" forIndexPath:indexPath];
cell.backgroundColor = [self generateRandomUIColor];
if(![[cell.subviews objectAtIndex:0] isKindOfClass:[PFImageView class]])
{
NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];
PFImageView *imageView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell];
[cell addSubview:imageView];
}
return cell;
}
PFImageView is a subclass of UIImageView that loads a Flickr photo URL on a background thread and then updates it's own image on the main thread - this works fine.
The logic is really simple - I create a cell if there isn't one dequeueable.
If the cell (which I'm expecting to be dequeued and already have a PFImageView) doesn't have a PFImageView, I alloc and init an imageView for the cell and add it as a subview of the cell.
Thus I expect if the cell has been dequeued it should already have a PFImageView as a subview and as we should not get into the if statement to create a new imageView and kick off a new photo download request
Instead what I see is that the cells at the top and bottom of the UICollectionView that 'go off screen' momentarily - when they come back on screen they are not being reused and seemingly a new cell is created and the picture refreshed.
1) How can I achieve a static image once the cell has been created (i.e. not refreshing when the cell goes slightly off screen.
2) Why are the cells not being reused?
Many thanks for your time.
John
UICollectionView will reuse cells for maximum efficiency. It does not guarantee any particular reuse or population strategies. Anecdotally, it seems to place and remove cells based on integer power of two regions — e.g. on a non-retina iPad it might divide your scroll area up into regions of 1024x1024 and then populate and depopulate each of those regions as they transition into and out of the visible area. However you should not predicate any expectations on its exact behaviour.
In addition, your use of collection view cells is incorrect. See the documentation. A cell explicitly has at least two subviews — backgroundView and contentView. So if you add a subview it will be at index 2 at the absolute least and, in reality, the index will be undefined. In any case you should add subviews to contentView, not to the cell itself.
The most normal way of doing what you're doing would be to create a custom UICollectionView subclass that inherently has a PFImageView within it.
I see several potential issues:
You are looking specifically at index 0 of the cell for the child class that you are adding. The UICollectionViewCell may have other views as children, so you can't just assume that the only (or first) child is the one you added.
I don't see that you are calling registerClass:forCellWithReuseIdentifier: or registerNib:forCellWithReuseIdentifier:, one of which is required for proper use of dequeue (https://developer.apple.com/library/ios/documentation/uikit/reference/UICollectionViewCell_class/Reference/Reference.html).
You are only setting the URL of the PFImageView in the case that you have to construct the PFImageView. The idea with dequeuing reusable views is that you will only construct a small subset of the views needed, and the UITableView will recycle them as they move offscreen. You need to reset the value for the indexPath that is being requested, even when you don't construct the new content.
If your case is as simple as you describe, you can probably get away with adding your PFImageView to the contentView property of your dequeued UICollectionView.
In your controller:
// solve problem 2
[self.collectionView registerClass:[UICollectionViewCell class] forReuseIdentifer:#"FlickrCell"];
In collectionView:cellForItemAtIndexPath
UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"FlickrCell" forIndexPath:indexPath];
cell.backgroundColor = [self generateRandomUIColor];
// solve problem 1 by looking in the contentView for your subview (and looping instead of assuming at 0)
PFImageView *pfImageView = nil;
for (UIView *subview in cell.contentView.subviews)
{
if ([subview isKindOfClass:[PFImageView class]])
{
pfImageView = (PFImageView *)subview;
break;
}
}
NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];
if (pfImageView == nil)
{
// No PFImageView, create one
// note the use of contentView!
pfImageView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.contentView.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell.contentView];
[cell.contentView addSubview:pfImageView];
}
else
{
// Already have recycled view.
// need to reset the url for the pfImageView. (Problem 3)
// not sure what PFImageView looks like so this is an e.g. I'd probably remove the
// URL loading from the ctr above and instead have a function that loads the
// image. Then, you could do this outside of the if, regardless of whether you had
// to alloc the child view or not.
[pfImageView loadImageWithUrl:staticPhotoURL];
// if you really only have 200 static images, you might consider caching all of them
}
return cell;
For less simple cases (e.g. where I want to visually lay out the cell, or where I have multiple children in the content), I typically customize my UICollectionViewCell's using Interface Builder.
Create a subclass of UICollectionViewCell in the project (In your case, call it PFImageCell).
Add an IBOutlet property to that subclass for the view I want to change in initialization (In your case, a UIImageView).
#property (nonatomic, assign) IBOutlet UIImageView *image;
In Interface Builder, create a prototype cell for the UITableView.
In the properties sheet for that prototype cell, identify the UICollectionViewCell subclass as the class.
Give the prototype cell an identifier (the reuse identifier) in the property sheet.
Add the view child in interface builder to the prototype cell (here, a UIImageView).
Use IB to map the IBOutlet property to the added UIImageView
Then, on dequeue in cellForRowAtIndexPath, cast the dequeued result to the subclass (PFImageCell) and set the value of the IBOutlet property instance. Here, you'd load the proper image for your UIImageView.
I am not sure if the cell is being re-used or not. It may be being reused but the subview may not be there. My suggestion would be to create a PFImageViewCollectionViewCell Class (sub class of UICollectionViewCell) and register it as the CollectionView Cell and try. That's how I do and would do if I need a subview inside a cell.
Try adding a tag on this particular UIImageView
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static int photoViewTag = 54353532;
UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"FlickrCell" forIndexPath:indexPath];
cell.backgroundColor = [self generateRandomUIColor];
PFImageView *photoView = [cell.contentView viewWithTag:photoViewTag];
// Create a view
//
if (!photoView) {
photoView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell];
imageView.tag = photoViewTag;
[cell.contentView addSubview:imageView];
}
// Update the current view
//
else {
NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];
photoView.imageURL = staticPhotoURL;
}
return cell;
}
I would really recommend to create your own UICollectionViewCell subclass though.
EDIT: Also, note that I used the contentView property instead of adding it directly to the cell.
I've made header using this post: Table Header Views in StoryBoards
But i am unable to make header stick (fix) to the top of the screen while scrolling.
How can that be achieved?
P.S. Table style is plain, and when i tired to overload viewForHeaderInSection and returned outlet for header view, it just got worse.
Thanks in advance!
Here's the code which I believe makes the header view stick to the table top (that is not it's default behavior):
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGRect rect = self.tableHeaderView.frame;
rect.origin.y = MIN(0, self.tableView.contentOffset.y);
self.tableHeaderView.frame = rect;
}
An alternative is to have header view for the first section. You can't use any subview at viewForHeaderInSection, you have to return there the view which is not yet at views hierarchy. The advantage of the method is that the section header view is designed to be at the table top, the drawback is that you might want to have headers for sections in the feature, it will break the solution.
I think it's better for you to use a UIViewController, instead of a UITableViewController in the storyboard, to achieve this. Just put a header in the top of the view, in the storyboard, and put a UITableView under the header.
I was able to do it (at least in iOS 6) like this, don't know why this thing works:)
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if(self.headerManualView == nil) {
//allocate the view if it doesn't exist yet
headerManualView = [[UIView alloc] init];
//create the button
UITextField *txtField = [[UITextField alloc] initWithFrame:CGRectMake(10, 3, 250, 44)];
//the button should be as big as a table view cell
txtField.borderStyle = UITextBorderStyleRoundedRect;
//set action of the button
//[txtField addTarget:self action:#selector(removeAction:) forControlEvents:UIControlEventTouchUpInside];
//add the button to the view
[headerManualView addSubview:txtField];
}
//return the view for the footer
return headerManualView;
}
In a UITableViewController I am instantiating UITableViewCells where some cells are highlighted by an accessoryView. For me, this works:
// works for me
UIImageView *favoriteImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"icon"]];
[cell setAccessoryView:favoriteImageView];
It seems wasteful to me to instantiate the same view repeatedly for multiple cells; however when I attempt to re-use the same view as the accessoryView of multiple cells my app fails in a miserable way (completely black screen, no views presented) I haven't been able to debug. Whether I declare favoriteImageView as a static inside the method such as
// doesn't work for me
static UIImageView *favoriteImageView = nil;
if (!favoriteImageView)
favoriteImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"icon"]];
[cell setAccessoryView:favoriteImageView];
Or declare it as an ivar and define it in init such that I wind up with:
// doesn't work for me
[cell setAccessoryView:[self favoriteImageView]];
In these two not-working cases, when one cell has its accessoryView set, it displays properly. As soon as I mark a second row such that the accessoryView would be set to reference the same view, the whole thing hangs up.
What are the requirements for constructing a UIView and/or configuring a UITableViewCell in such a way that the same UIView may be referenced as the accessoryView of multiple UITableViewCells?
UIImageView extends from UIView. And a UIView can't be in two or more places at the same time.
Therefor, if you try to display an UIImageView in two or more cells at the same time, it won't work. You need an UIImageView for each cell on screen.
I suggest that for each cell you create the UIImageView. The tableviewcells will be reused alongside with their accessoryview, so I wouldn't worry too much about performance or memoryproblems.