I am writing an app for the first time and I need it to pull images from a database and list them for the user to click on. My apologies ahead of time if this is hard to follow. The problem I am having is I need to put multiple images in one cell (Or as mentioned in the title simply have multiple cells per row), and I need each image to direct to its own unique detail page. So two questions: Is it possible to put multiple cells in one row (based off of the screen width)? and Is there an exception that I can write to stop looping when an Array goes out of bounds?
The images I am pulling all have the same dimensions (360x125) so I grab the width and height of the container based off of the orientation and adjust their size according to how many I can fit in a cell.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Array the images are stored in.
Companies *item = _feedItems[indexPath.row];
// I grab the screen size and adjust if the screen is in landscape
CGRect result = [UIScreen mainScreen].bounds;
CGFloat width = CGRectGetWidth(result);
CGFloat height = CGRectGetHeight(result);
UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;
if(UIInterfaceOrientationIsPortrait(interfaceOrientation)){
result.size = CGSizeMake(width, height);
} else if(UIInterfaceOrientationIsLandscape(interfaceOrientation)) {
result.size = CGSizeMake(height, width);
}
// if the width is less than 480 I fit one image and set the cells height,
// else I set the height to that if there were two images.
if(result.size.width <= 480) {
return (item.imageName.size.height / item.imageName.size.width) * CGRectGetWidth(self.listTableView.bounds);
}
if(result.size.width > 480) {
return (item.imageName.size.height / item.imageName.size.width) * CGRectGetWidth(self.listTableView.bounds) / 2;
}
return (item.imageName.size.height / item.imageName.size.width) * CGRectGetWidth(self.listTableView.bounds);
}
If the width is greater than 480 then I put two images instead of one. This may increase to three images per line later, I have not got around to testing this on the iPad yet.
Then I draw the images into the cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIndentifier = #"BasicCell";
UITableViewCell *myCell = [tableView dequeueReusableCellWithIdentifier:cellIndentifier];
myCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIndentifier];
CGRect result = [UIScreen mainScreen].bounds;
CGFloat width = CGRectGetWidth(result);
CGFloat height = CGRectGetHeight(result);
UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;
if(UIInterfaceOrientationIsPortrait(interfaceOrientation)){
result.size = CGSizeMake(width, height);
} else if(UIInterfaceOrientationIsLandscape(interfaceOrientation)) {
result.size = CGSizeMake(height, width);
}
UIImageView *myImageView;
UIImageView *myImageView2;
if(result.size.width <= 480) {
Companies *item = _feedItems[indexPath.row];
myImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,CGRectGetWidth(self.listTableView.bounds), (item.imageName.size.height / item.imageName.size.width) * CGRectGetWidth(self.listTableView.bounds))];
myImageView.tag = 1;
myImageView.image = item.imageName;
[myCell addSubview:myImageView];
}
if(result.size.width > 480) {
Companies *item = _feedItems[indexPath.row * 2];
Companies *item2 = _feedItems[(indexPath.row * 2) + 1];
myImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,CGRectGetWidth(self.listTableView.bounds) / 2, (item.imageName.size.height / item.imageName.size.width) / 2 * CGRectGetWidth(self.listTableView.bounds))];
myImageView2 = [[UIImageView alloc] initWithFrame:CGRectMake(CGRectGetWidth(self.listTableView.bounds) / 2,0,CGRectGetWidth(self.listTableView.bounds) / 2, (item.imageName.size.height / item.imageName.size.width) / 2 * CGRectGetWidth(self.listTableView.bounds))];
myImageView.tag = 1;
myImageView2.tag = 2;
myImageView.image = item.imageName;
myImageView2.image = item2.imageName;
[myCell addSubview:myImageView];
[myCell addSubview:myImageView2];
}
return myCell;
}
This is the code I have so far, and it will work normally when the orientation is portrait. However when the orientation is set to landscape I get an expected error of NSRangeException index beyond bounds. Because of this
Companies *item = _feedItems[indexPath.row * 2];
Companies *item2 = _feedItems[(indexPath.row * 2) + 1];
I want to pull both the image and the image that will come after it, to put in the same cell (Sorry if that is confusing). This is why I use y = 2x and y = 2x + 1. The problem is when x exceeds the highest value for y. So if the Array has 8 images [0 - 7], it will loop through the cell draw process 8 times, if I draw two at once it will draw the 8th image on the 3rd loop: 0[0,1] 1[2,3] 2[4,5] 3[6,7] The error will come up on the 4th loop: 4[8,9] index 8 beyond bounds 7. Is there an exception I can write to avoid this? Or am I using an incorrect thought process that should be handled differently?
How can I make it so that once I do put two images in the same row, each one will have their own on-click event handler and redirect to their own unique page (right now the call is on the table cell view, so that when I click either image in the same row, the page will redirect to the same one).
My restrictions are: I need to pull the data as I am from a mysql database through a php page. The number of images that will be pulled will be random. The images are 360 x 125 so I cannot stretch them too much, and thus need to have multiple ones on the same row should the user flip to landscape view or use a larger tablet or phone. Is there a better way to do this?
To prevent out of bounds error, just do a check on item count like this:
if (_feedItems.count < (indexPath.row*2+1)){
Companies *item2 = _feedItems[(indexPath.row * 2) + 1];
// rest of set up for second image
}
For detecting image press events, add a tap gesture recognizer to each image.
myImageView.userInteractionEnabled = YES; // This is important!
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(onImageTap:)];
[tap setNumberOfTouchesRequired:1];
[tap setNumberOfTapsRequired:1];
[tap setDelegate:self];
[myImageView addGestureRecognizer:tap];
In onImageTap method:
-(void)onImageTap:(UITapGestureRecognizer *)recognizer
{
int tag = recognizer.view.tag;
// rest of code based on image tag
}
Once you get this working and are comfortable with table views, you should look into custom table view cells by subclassing UITableViewCell and also understand table view cell reuse.
If you are feeling adventurous, I would recommend checking out UICollectionView as it will let you display two items in a row and treat each item as a different cell. You will have to manage the spacing between cells if you want strictly two items per row.
Related
I have collection view with custom flow layout and i wonder why i have extra space like that (right side):
It may hard to notice here, though, but you may see gray gap between right side and cell.
Here is how i override standard collection view flow layout to remove horizontals gaps:
#implementation CalendarFlowLayout
- (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *answer = [super layoutAttributesForElementsInRect:rect];
NSLog(#"layout blck passed");
for(int i = 1; i < [answer count]; ++i) {
UICollectionViewLayoutAttributes *currentLayoutAttributes = answer[i];
UICollectionViewLayoutAttributes *prevLayoutAttributes = answer[i - 1];
NSInteger maximumSpacing = 0;
NSInteger origin = CGRectGetMaxX(prevLayoutAttributes.frame);
if(origin + maximumSpacing + currentLayoutAttributes.frame.size.width < self.collectionViewContentSize.width) {
CGRect frame = currentLayoutAttributes.frame;
frame.origin.x = origin + maximumSpacing;
currentLayoutAttributes.frame = frame;
}
}
return answer;
}
#end
Cell size calculated as follow:
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
return CGSizeMake(SCREEN_WIDTH/7, (SCREEN_HEIGHT-NAVBAR_HEIGHT-STATUS_BAR_HEIGHT)/6);
}
Those macros above look like that:
#define SCREEN_WIDTH [[UIScreen mainScreen] bounds].size.width
It has been tested and it return correct output.
Also, if i set width to SCREEN_WIDTH/3, there is will be no gap.
How to remove it? Why is that happen?
375 / 3
= 125
375 / 7
= 53.57142857142857
You can't really light a half a pixel, so it is probably rounding down, leaving you with a gap. Make sure your size is a multiple of your subview size (or add a pixel to the rightmost object if it's not).
The problem is that the width of the cell (eg. SCREEN_WIDTH/3) is not a divisor of SCREEN_WIDTH. Correct divisors of SCREEN_WIDTH (375, as you said in comments) are 3, 5, 25 and 125 (125*3=375).
But As there are different screen size among all iOS devices, I think you should manage the problem differently. For example, you should choose a specific cell width and try to center the collection view in its container, so the extra-space will always be divided on both left and right side of the collection view.
I'm building a table that shows an entry for a user. One of the cells shows a set of "tags" downloaded from the server. I am currently building a set of UILabels and manually adding them to a view contained in the cell. While this works, the cell does not dynamically resize after adding the tags. The tags overlap the cell beneath it and I can't figure out how to manually update the height of the cell.
In cellForRowAtIndexPath:
JournalTagsCell *cell = [tableView dequeueReusableCellWithIdentifier:#"JournalTagsCell" forIndexPath:indexPath];
//Check if we have any tags to show
if(self.journalObject.journalEntryTags != nil){
cell.placeholderLabel.hidden = YES;
cell.tagsView = [self updateTagsView:cell.tagsView];
}
return cell;
The following is my method for actually creating each tag, laying them out and adding them to the view:
- (void)updateTagsView:(UIView*)viewToUpdate{
NSArray *items = self.journalObject.journalEntryTags;
//Clean up the view first
NSArray *viewsToRemove = [viewToUpdate subviews];
for (UIView *v in viewsToRemove) {
[v removeFromSuperview];
}
float x = 10;
float y = 10;
for (int i = 0; i < items.count; i++) {
CGRect textRect = [items[i] boundingRectWithSize:CGSizeMake(self.view.frame.size.width - 20, 1000)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:[UIFont tagCopy]}
context:nil];
CGSize size = textRect.size;
if (x+size.width > (self.view.frame.size.width-20)) {
y += size.height + 10;
x = 10;
}
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(x, y, size.width, size.height)];
lbl.userInteractionEnabled = YES;
[lbl setText:[NSString stringWithFormat:#" %# ", items[i]]];
[lbl setFont:[UIFont tagCopy]];
[lbl sizeToFit];
[lbl setTextColor:[UIColor colorWithRed:0.145 green:0.392 blue:0.576 alpha:1.000]];
[lbl setBackgroundColor:[UIColor colorWithRed:0.804 green:0.871 blue:0.914 alpha:1.000]];
lbl.layer.borderWidth = 1;
lbl.layer.borderColor = [UIColor colorWithRed:0.145 green:0.392 blue:0.576 alpha:1.000].CGColor;
lbl.layer.cornerRadius = 5;
[lbl.layer setMasksToBounds:YES];
[viewToUpdate addSubview:lbl];
UITapGestureRecognizer *tapGesture =
[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(userClickedOnTag:)];
[lbl addGestureRecognizer:tapGesture];
x += size.width + 10;
if (x > (self.view.frame.size.width-20)) {
y += size.height + 10;
x = 10;
}
if (i == items.count-1) {
y+= size.height + 20;
}
}
[viewToUpdate setFrame:CGRectMake(0, 0, self.view.frame.size.width, y)];
}
However, I don't know how to manually update the height for this cell based on the size of this view. I don't want/need to manually calculate the height of every single cell, just this one which is why I'm not currently using heightForRowAtIndexPath but I don't know how to update the height of this one cell. Obviously I can calculate the height I need for this cell if necessary as I'm already setting up the view frame that holds the tags, but short of having to go through every single cell and manually calculate each ones height, I'm stumped.
I think you will have to use heightForRowAtIndexPath. I'm not sure you completely understand what's happening. Or maybe I have it wrong.. Either way, how I understand it: The total height of the "cell" will always be presented. You never set the "height" of the cell, it will automatically show the entire content. heightForRowAtIndexPath is not a way to tell the cell how tall it should be, but rather how much space the tableView should reserve for that particular cell. If you pass a height too short, it will still present the entire cell, but the next cell will start too soon. It also works the other way around, if you pass a bigger number than necessary, it will look like the cells are bigger, even though the cells aren't. It's just the tableView's representation.
If you are using iOS 8 you can use UITableViewAutomaticDimension.You can check out this example
self.tableView.rowHeight = UITableViewAutomaticDimension;
You can take a look also on this video : What's New in Table and Collection Views in the 2014 WWDC.
You can solve the issue with a variable for that row's height. In viewDidLoad() store default cell height to the variable. then while you calculate the height for the view store the view's height to the variable and to reload that cell use below method for the tableview by passing single cell's indexpath in array.
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
It will update cell at particular index by calling all lifecycle method of a cell.
Make sure you return default value for all other cell in heightForRowAtIndexPath() except the cell with tagsView with calculated height cell.
-(CGFloat)tableView:(UITableView *)myTableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexpath.row == TAGSVIEW_ROWINDEX)
return calculated_height_value;
return default_row_height;
}
If the Cell with tagsView is not predefined with rowIndex, you can also store that rowIndex in one integer variable in cellForRowAtIndexPath().
Lately I'm pulling my hair out because of this. I'm still a rookie iOS Developer therefore a simple solution would be appreciated.
I have a UIPageViewController with three views, the middle view is a UITableView which i populate with data programmatically and try to make a layout like a grid. When the UITableView has too many data the information gets outside of the screen.
I've tried to disable auto layout and add the UITableView to a UIScrollView, enabling the UITableView to scroll horizontal. I've managed to do that but somehow the interaction is messy and most of the times when trying to horizontal scroll the table it catches the UIPageViewController interaction. Also the vertical scroll of the table does not respond well too.
Is there a known solution for this situation?
After testing a few things I ended up with a acceptable solution using autolayout.
So first I added a UIScrollView and added the UITableView inside the UIScrollView.
I've enabled the Paging and disabled the Bounce Horizontally in the UIScrollView.
In the UITableView I've basically disabled everything relative to bouncing paging and scrolling.
Since the data is being populated programmatically to be some sort of a grid I have to know the total width of the longest row for that I've created a few methods to calculate the maximum width and arrange the labels.
I also created constraints for the table Width and Height which I calculate and update after all the calculations are done.
-(void) ArrangeTable
{
for (int i= 0; i < [arrayTable count]; i++) {
for (int j=0; j< [[arrayTable objectAtIndex:i ] count]; j++) {
UILabel * nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 8.0, 95.0, 30.0)];
[nameLabel setText:[NSString stringWithFormat:#"%#",[[arrayTable objectAtIndex:i] objectAtIndex:j]]];
[nameLabel sizeToFit];
float widthIs = [nameLabel.text
boundingRectWithSize:nameLabel.frame.size
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{ NSFontAttributeName:nameLabel.font }
context:nil].size.width;
[self alignLabels:j currentWidth:[NSNumber numberWithFloat: widthIs]];
}
}
}
-(void) alignLabels:(int)colNumber currentWidth:(NSNumber*)labelWidth
{
if(colNumber >= [arrayLabelWidth count])
{
//Insert without position
[arrayLabelWidth addObject:labelWidth];
}else if ([labelWidth floatValue] > [[arrayLabelWidth objectAtIndex:colNumber] floatValue] )
{
[arrayLabelWidth replaceObjectAtIndex:colNumber withObject:labelWidth];
}
}
-(void) setMaxRowWidth
{
float widthTV =[[arrayLabelWidth valueForKeyPath:#"#sum.self"] floatValue] + ([arrayLabelWidth count]) * 10;
CGRect screenRect = [[UIScreen mainScreen] bounds];
float heightTV = [self tableViewHeight];
if(widthTV > screenRect.size.width)
{
tvWidth.constant = widthTV;
//svWidth.constant = valueTV;
}else{
if (UIDeviceOrientationIsPortrait(self.interfaceOrientation)){
//DO Portrait
tvWidth.constant = screenRect.size.width;
}else{
//DO Landscape
float calc = screenRect.size.width - self.view.frame.size.width;
tvWidth.constant = screenRect.size.width - calc;
}
}
tvHeight.constant = heightTV;
}
- (CGFloat)tableViewHeight
{
[tablePod layoutIfNeeded];
return [tablePod contentSize].height;
}
I have big problem with my iPad application which I am working on. I will explain you what is a problem.
I have list of products. I have table view to show that products. I try to load 3 images (products) in one cell in landscape mode and 2 in portrait mode. For that I use this code in my controller class
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"cell"];
[cell setBackgroundColor:[UIColor clearColor]];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0ul);
dispatch_async(queue, ^{
for(int i = 0; i < self.cols; i++){
int index = (indexPath.row * self.cols) + i;
if([self.productList count] > 0 && index < [self.productList count]){
Product *tmpProduct = nil;
if([self.productList isKindOfClass:[NSMutableSet class]]){
tmpProduct = (Product *)[[NSArray arrayWithArray:[((NSSet *)self.productList) allObjects]] objectAtIndex:index];
}else{
tmpProduct = (Product *)[((NSArray *)self.productList) objectAtIndex:index];
}
int x = i * self.itemWidth + (50 * (i + 1));
int y = 0;
int imageWidth = self.itemWidth;
int imageHeight = self.itemHeight - 45;
UIView *awesomeView = [[UIView alloc] initWithFrame:CGRectMake(x, y, imageWidth, self.itemHeight)];
awesomeView.backgroundColor = [UIColor clearColor];
awesomeView.userInteractionEnabled = YES;
awesomeView.tag = 700;
UIImage *image;
if([tmpProduct getImageFilePath]){
image = [UIImage imageWithContentsOfFile:[tmpProduct getImageFilePath]];
}else{
image = [UIImage imageNamed:#"no-image.png"];
}
dispatch_sync(dispatch_get_main_queue(), ^{
UIImageView *productImage = [[UIImageView alloc] initWithImage: image];
CGRect frameRect = productImage.frame;
frameRect.size.width = imageWidth;
frameRect.size.height = imageHeight;
productImage.frame = frameRect;
productImage.contentMode = UIViewContentModeScaleAspectFill;
productImage.tag = index;
productImage.backgroundColor = [UIColor grayColor];
productImage.contentMode = UIViewContentModeScaleAspectFit;
[awesomeView addSubview:productImage];
[cell.contentView addSubview:awesomeView];
[cell setNeedsLayout];
});
}
}
});
return cell;
}
You can see that when cell want to show and "cellForRowAtIndexPath" method is called I initiate background queue. Start "for" for 3 or 2 images in one cell. Get product for that cell based on row. Calculating image width and height and position. Initiate one helper view. Than load image. If image exists in product object I load product image if not I loaddefault image (no-image). Place that image view in my helper view. After that I load my helper view to cell in main queue. (I use that helper view "awesomeView" because I have more thing to display, some labels, i removed that to simplified this question).
Here is youtube video which I have recorded to see which problem i have. You can see that when I scroll up or down it is very lagy. It freeze for part of second every time when script load next cell. YOUTUBE Video: http://youtu.be/hE3KI0SVrPk
Can some one help me I do not know where I am wrong.
I tried couple of combinations with queues and also I tried without queues and result is almost the same.
Thanks in advanced.
The proper way to do this is as follows:
create some mutable collection to hold images
when you find you need an image, and its not in the collection, do (somewhat) as you are doing - get the image in the background
keep an index or some id of what you are currently fetching, so you don't do it more than once.
when you get the image, then dispatch_async to a method in your view controller
the receiving method receives the image, and examines all the visible cells in the table (there's a method for that), to see if the image applies to any of them. If so it updates that cell. If not it puts the image into the mutable collection.
In my case I had a known productID I could use as a key to use with a mutable dictionary. I would when first fetching an image add an entry of [NSNull null] for the object of the key - that made it easy to test for whether or not I already was fetching the image. Once I got the image, the image replaced the nsnull placeholder.
I have created a sort of GRID using uitableView. For that I have taken various labels for showing grid type line I am using line image. So by considering Grid my tableview is having around 88 columns.
My issue is when I scroll it down, I am getting jerky effect. Its performance s very poor. I am creating around 108 label and each row is having 88 labels and 86 image views.
What step do I need to follow to improve scrolling performance???
I was using clearColor for label background. But later on I have removed those background colors.
Having read your problem again, I am thinking you are scrolling horizontally and vertically with an incredibly wide tableview. If this is the case then you need to switch to UIScrollView and attach each item to this view. UIScrollView will only load the views that are visible and provide the kind of scroll performance you desire.
It will be important to avoid this:
// Using ARC, therefore no release on the UILabel
- (void) viewDidLoad {
self.scrollView.backgroundColor = [UIColor whiteColor];
UIFont *font = [UIFont systemFontOfSize:24];
CGSize size = [#"10000:10000" sizeWithFont:font];
_scrollView.contentSize = CGSizeMake(10000 * size.width, 10000 * size.height);
for (int y = 0; y < 10000; y++) {
for (int x = 0; x < 10000; x++) {
NSLog(#"Loading: %d, %d", x, y);
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(x * size.width, y * size.height, size.width, size.height)];
label.font = font;
label.textAlignment = UITextAlignmentRight;
label.text = [NSString stringWithFormat:#"%d:%d", x, y];
[_scrollView addSubview:label];
}
}
}
While this will eventually load it will take ages while it loads up all of these labels and it will consume a ton of memory. You want to lazy load this view just like a TableView. I will write up an example this evening.
You need to give the table cell a reuse identifier. Otherwise, each time you create a completely new cell and consume more and more memory.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *const kReuseIdentifer = #"ReuseIdentifer";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kReuseIdentifier] autorelease];
}
return cell;
}