UIImage as accessory view in UITableViewCell not showing - objective-c

Within my app, I bring up a UITableView when prompting the user to select an item. Based on whether the item is in stock, out of stock, on order, etc., I want to display an image on the right-hand side of the UITableViewCell.
I have tried using the accessory view button, but it isn't showing up on the UITableViewCell. I have tried changing the UITableViewCellStyle to UITableViewCellStyleValue2 (the style with the detail closure button), and have also tried setting the accessory type of the cell directly.
static NSString *CellIdentifier = #"MyCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
//cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:CellIdentifier];
}
[cell.textLabel setText:#"Testing"];
[cell.detailTextLabel setText:#""];
//cell.accessoryType = UITableViewCellAccessoryNone;
//cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
UIImageView *imageView = [[UIImageView alloc] initWithImage:[myImageFilePath stretchableImageWithLeftCapWidth:0 topCapHeight:1.0]];
[imageView setContentMode: UIViewContentModeScaleAspectFit];
[imageView setFrame:CGRectMake(0, 5, cell.frame.size.height - 10, cell.frame.size.height - 5)];
cell.accessoryView = imageView;
return cell;
When the app runs, the UITableViewCells appear with the "Testing" text, but there is no image on the right-hand side of the cells.
Is there another method that needs to be overridden in order to implement a custom UIImage for the detail diclosure button?
The "button" is more of a status indicator, because I don't want the tap of the button to do anything different than tapping anywhere else on the UITableViewCell does (just select the row).
Some additional info:
If I remove
[imageView setFrame:CGRectMake(0, 5, cell.frame.size.height - 10, cell.frame.size.height - 5)];
then the image shows up in the UITableViewCell, but it is too big and does not fit on a single row, so it overlaps with other rows' accessory views.
I was using the setFrame so that it would resize the image to fit with the UIViewContentModeScaleAspectFit so that it would fit on the row, but it seems to prevent the image from appearing at all.
Note that in this example cell.frame.size.height = 44.
If I change the frame to:
[imageView setFrame:CGRectMake(0.0, 0.0, 120, 44)];
then the image appears on the row.
However, since it is 120 pixels wide, it is cutting off the text with the trailing ... earlier that needed. The image could fit about 3 times before the trailing ..., with the width being 120 and height being 44.
How can I get the trailing ... to get closer to the image?
If I reduce the 120 to a lower value, the image moves to the right and eventually runs off the cell.

The width of the UITableView was 804. Updating it to 768 allowed me to use the original code that was posted.

Your question is very similar to this one here:
Using a custom image for a UITableViewCell's accessoryView and having it respond to UITableViewDelegate
You can ignore the "have it respond" bit unless you need to. But, as you can see there, the rect is square for the accessoryView property, so an image that is 120x44 is probably not going to fit correctly, ever.
I would rethink your approach to this problem, and create a custom UITableViewCell subclass that has the necessary items you require (sounds like a UILabel and a UIImageView). This way, you can easily control how large the label is versus how large the image is, how the label will wrap lines (if it wraps at all or maybe truncates) and so on.

Another way is just subclass the UITableviewCell and set frame's in "layoutSubviews" and also make sure to pass correct row height for each cell.
By subclassing you will get full flexibility.
hope this helps

I guess there's an issue with your CGRect X and Y, in this case here 0 and 5. Maybe trying to set them dinamically can fix this. Let me know

Related

How to programmatically position and size subviews into cell's contentView

I would like to get the cell frame at tableView:cellForRowAtIndexPath: in order to position and size the views that I want to add to UITableViewCell's contentView. But there self.frame is always (0, 0, 320, 44).
I know you can get the right frame in layoutSubviews (thanks to this answer), but if I add the subviews there it would be done every time the cell is reused, not only once like in the "official" example at Programmatically Adding Subviews to a Cell’s Content View.
In that example they add some views using hardcoded frames like:
mainLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 220.0, 15.0)];
I guess that example is outdated, since they should use constraints, or at least calculate the size of subviews using the actual cell frame (which may be impossible to get at that point indeed).
Note: this reminds me of the view holder design pattern used in Android.
This should return the bounding frame.
CGRect cellFrame = cell.bounds;
Then you can use it like
cellFrame.size.width;
cellFrame.size.height;
cellFrame.origin.x;
cellFrame.origin.y;
etc... Though origin.x and origin.y should be 0 each.
Maybe you should calculate cell height in a UITableViewCell subclass and use
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
in your delegate to find the correct height.
I use two solutions:
1) When I set the frames explicitly
I fake the frame:
#define SCREEN_WIDTH ([UIScreen mainScreen].bounds.size.width)
// Use whatever height you like for your cell
// Should be the same value you return in tableView:heightForRowAtIndexPath:
CGFloat cellHeight = XXX;
CGRect frame = CGRectMake(0, 0, SCREEN_WIDTH, cellHeight);
 2) When I use autolayout
I just add my views and constraints to self.contentView.
[someView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.contentView addSubview:someView];
[self.contentView addConstraints:someConstraints];
But check this answer just in case.

How to consistently draw NSAttributedString in UITextView in UITableViewCell

I am having trouble getting consistent results from UITextViews in a UITableViewCell using NSAttributedStrings.
Inside - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
headerText = [[UITextView alloc]initWithFrame:CGRectZero];
[headerText setUserInteractionEnabled:NO];
headerText.tag = HEADER_TEXT;
[cell.contentView addSubview:headerText];
} else {
headerText = (UITextView*)[cell.contentView viewWithTag:HEADER_TEXT];
}
//...setting up attributed strings
[headerText setAttributedText:headerString];
CGSize headerSize = [headerText sizeThatFits:CGSizeMake(246, CGFLOAT_MAX)];
headerText.frame = CGRectMake(45, 8, headerSize.width, headerSize.height);
Results:
As you can see, the first two appear to draw the text in a way that I would expect/want. In the last two the UITextView sizeThatFits method returns a much larger size then is required to draw the text and the text becomes centered in the frame rather then tight to the top of the frame. This is an issue because I want to be able to layout other views based on the uitextview frame height.
After Scrolling out of frame and back in:
Now it gets even stranger, when the cells are reused, and the attributed string is set again. the uitextview draws the text in an inconsistent way.
Even Setting the contentInsets to
headerText.contentInset = UIEdgeInsetsMake(-8, -8, -8, -8);
Does not provide any sort of consistent results:
And After Scrolling with contentinset set:
Are there other attributes on UITextView that would allow me to get the behavior that I need?
When setting the attributed string of a UITextView that previously had a different attributed string, you must always set all of the UITextView's string-related properties to nil first, e.g.:
self.tv.text = nil;
self.tv.font = nil;
self.tv.textColor = nil;
self.tv.textAlignment = NSTextAlignmentLeft;
self.tv.attributedText = s2;
Otherwise, as you have discovered, old features of the previous attributed string still hang around and affect the new attributed string.
In general, though, I have to say I don't see why you're using UITextView at all. If you don't need the user to be able to edit these attributed strings, use UILabel or else just draw the attributed string directly for the most accurate possible rendering. NSAttributedString gives you all the power you need to measure the size and draw within that size.

Customizing tableView: Corner radius, decreased width and shadow

This is what I want to do:
As you can see i want to:
Decrease the width of the tableView (I want more margin on the sides than the grouped tableView provides)
Corner radius (bigger radius than the default for grouped tableView)
Drop shadow around the table and a special shadow beneath the last cell
You can do this by "drawing" the backgroundView of the cells yourself.
I'd recommend getting an image to use as the background (if the cells are all the same height).
You'll need three images.
A "top" image with the top corners rounded.
A "bottom" image with the bottom corners rounded and the drop shadow how you want it.
And a "middle" image with no rounded corners.
If the cells don't have any texture or gradient within them then you can use stretchable images to reduce the memory footprint of them.
Then I would subclass the UITableViewCell and override the backgroundView to add a UIImageView. I'd also provide an accessor method to change the type (top, middle, bottom) of the cell.
Each cell can then have three placeHolder properties of a UIImage (topImage, bottomImage and middleImage). When the type of the cell is changed these can be accessed (use lazy instantiation to make sure they are only loaded once and only when needed) and then set the backgroundVIew image to be the required image.
Something like this...
In the UITableViewCell subclass define a type enum...
typedef enum {
CellTypeTop,
CellTypeMiddle,
CellTypeBottom
} cellType;
Then a property for the type...
#property (nonatomic) cellType cellType
Then in the .m ...
Define some more internal properties...
#property UIImageView *bgImageView;
#property UIImage *topImage;
#property UIImage *middleImage;
#property UIImage *bottomImage;
Then add the imageView (only once)...
- (void)awakeFromNib //or in the init depends how you are initialising the cell
{
self.bgImageView = [[UIImageView alloc] initWithFrame:blah];
[self.backgroundView addSubView:self.bgImageView];
}
Now when the type is changed...
- (void)setCellType:(cellType)cellType
{
switch(cellType) {
case CellTypeTop:
self.bgImageView.image = self.topImage;
break;
case CellTypeMiddle:
self.bgImageView.image = self.middleImage;
break;
case CellTypeBottom:
self.bgImageView.image = self.bottomImage;
break;
}
}
Finally a lazy instantiation of the images...
- (UIImage *)topImage
{
if (_topImage == nil) {
_topImage = [UIImage imageNamed:#"topImage"];
//alternatively...
_topImage = [[UIImage imageNamed:#"topImage"] stretchableImageWith...
}
return _topImage;
}
Now repeat these for the other images.
This will be more performant (by a long way) than using a CALayer alternative and, especially if using the stretchable images, will have a very small memory footprint.
Several other users have said that this is not good for performance, memory, design, whatever, but it really is the best way to get the best performance for UserExperience than CALayers. Yes, it will use more memory than CALayers but only marginally and it will get to a limit as there are only a few dequeueable cells created.
A couple of links explaining performance issues when using CALayers in scrollViews...
http://www.quora.com/iOS-Development/What-is-the-best-way-to-optimize-the-performance-of-a-non-paging-but-view-recycling-UIScrollView-involving-loading-potentially-caching-and-displaying-bundled-images
Bad performance on scroll view loaded with 3 view controllers, drawn with CALayer
::EDIT:: Edit to answer Michael's question.
In the storyboard create a UITableViewController (rename the Class in the inspector so that it matches your subclass UITableViewController - I'll call it MyTableViewController).
Create a subclass of UITableViewCell (I'll call mine MyTableViewCell) in the code (i.e. the .h and .m).
Add the above code to do with properties and types and imageViews to your MyTableViewCell.h file.
In the storyboard select the cell in the TableViewController and rename the class to MyTableViewCell. Also set the reuse identifier on it.
In the MyTableViewController code you will need a function like this...
-(UITableViewCell*)tableView:(UITabelView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
MyTableViewCell *cell = [tableView dequeueCellWithReuseIdentifier:#"Cell"];
cell.cellType = CellTypeTop; //or whichever it needs to be
cell.textLabel.text = #"Blah";
return cell;
}
Oh, another thing, in the storyboard you will be able to layout your cell how you want it to look and link up all the labels and imageviews etc... Make sure you add IBOutlet to the UIImageView so that you can link it up in the storyboard.
make sure you have #import <QuartzCore/QuartzCore.h> imported, then you can start accessing the layers of the UITableView like.
UITableView *yourTable = [[UITableView alloc] initWithStyle:UITableViewStyleGrouped];
[[yourTable layer] setCornerRadius:10.0f];
[[yourTable layer] setShadowColor:[[UIColor blackColor] CGColor]];
[[yourTable layer] setShadowOffset:CGSizeMake([CALayer ShadowOffSetWidthWithFloat:10.0f], [CALayer ShadowOffSetWidthWithFloat:10.0f])];
[[yourTable layer] setShadowOpacity:[CALayer ShadowOpacity:1]];
[[yourTable layer] setMasksToBounds:NO];
UIBezierPath *path = [UIBezierPAth bezierPathWithRect:yourTable.bounds];
[[yourTable layer] setShadowPath:[path CGPath]];
This will add shadow affect to your table view with the shadow not masked to the bounds of the UITableView, at setCornerRadius you can set the corners of the table to whatever you want. You can also set the frame of the UITableView by doing
[yourTable setFrame:CGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height)];
EDIT
As another user has tried to point out that CALayer is very slow, this is not the case
CALayer was introduced to help performance issues around animation. Please read documentation. Loading an image straight in may seem like a good idea but in the long run will take up more memory. Please this question about memory allocation for images. As you can see it may seem faster, but it takes up 2.25 MByte of memory per image which after loading each image so many times your app will start to become slow.

iOS - UITableViewCell's image go ontop of table upon selection?

I have 2 tables in one view. Table A lists a bunch of users. Table B lists a users objects. When a row is selected in Table A, Table B is reloaded with the objects that belong to that user.
So when a user selects a row in Table A, the image in the background of the cell changes to the highlighted version of the image.
Here is the normal version of the background image:
Here is the highlighted version of the background image:
As you can see, the highlighted version has a small arrow on the right of it. This arrow is beyond the width of the table cell the table itself. When the row is selected, the image changes as it should, but the image is sized down to fit the whole image into the cell.
What I would like to happen is the image goes outside of the table, or on top of the table for that selected row.
One possible solution I thought was to center the table on the selected row and then overlay that image, but if the user was to try to scroll through the table, the image would need to move and that would be a big pain.
So what I would like to know is it is possible to extend the cell's size beyond the table one it is selected?
Thanks in advance!
EDIT:
The following does not work, just in case anyone was going to try:
[cell setFrame:CGRectMake(cell.frame.origin.x, cell.frame.origin.y, cell.frame.size.width+20, cell.frame.size.height)];
Setting a views clipsToBounds property to NO will allow the view to draw outside of its own frame.
In your UITableViewController subclass:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do what you normally do here, if anything then add...
self.view.clipsToBounds = NO;
}
Doing this has a side effect where you will see full cells be created at the bottom or top of the tableview instead of them scrolling partially into view. Either put the table view into another view that has clipsToBounds set to YES, align the edges of the table view with the edges of the screen, or have views covering over the bottom and top (like a UIToolbar and UINavigationBar normally would).
To get the UITableViewCell's selectedBackgroundView to extend past the table view's frame create a subclass of UITableViewCell and override the layoutSubviews method.
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
self.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"YQGyZ"]];
self.selectedBackgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"CQXYh"]];
self.textLabel.backgroundColor = [UIColor clearColor];
self.detailTextLabel.backgroundColor = [UIColor clearColor];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGRect frame = self.selectedBackgroundView.frame;
frame.size.width += 13; // where 13 is the size of the arrow overhang from the same images
self.selectedBackgroundView.frame = frame;
// You can also change the location of the default labels (set their background colors to clear to see the background under them)
self.textLabel.frame = CGRectMake(70, 0, 148, 30);
self.detailTextLabel.frame = CGRectMake(70, 30, 148, 30);
}
Good luck.
I would recommend modifying the image resource of the unselected cell background such that it is the same width as the selected background, but just has a transparent rectangle on the side.

iOS - viewForFooterInSection sticking to bottom of UITableView

I have a UITableView with 3 sections. Each of them have a footer that I've added using viewForFooterInSection. The problem I'm having is that when I scroll the tableview down, the footer sticks to the bottom of the screen, and doesn't scroll like the rest of the cells. Does anyone know how to make it so the footer almost acts like a cell, and scrolls along with the rest of the table? Thanks!
I actually figured it out. Probably isn't the smartest way to do it, but I changed my UITableView style to grouped, and that fixed it. I had to tweak the TableView a bit so the cells would look the same as they did non-grouped (clear background, no separators), but it worked fine. The footer no longer sticks to the bottom of the TableView.
Footers are supposed to stick. The trick is to add an extra cell to each section and render the footer there. If you need more help, add a comment, but it should be pretty straightforward.
EDITS:
Q: Alright. I'm using Core Data and NSFetchedResultsController, to populate the TableView. Would that make it more tricky to accomplish this?
A: Not at all. Override
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
To add an extra cell in each section, and in
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
test if the indexPath.row > than your fetchedResultsController's rows for that section. If true, add in your cell that shows the footer information.
One way around this is if you set the footer as one of the cells as the last cell in the scroll view (could be done but setting it as the last item in the array that you set the uitable from)
Adding an extra cell, making it invisible, and rendering your view there is not an advisable way of adding a footer. Doing it properly is pretty straight-forward:
- (UIView*)tableView:(UITableView*)tableView viewForFooterInSection:(NSInteger)section {
NSString* sectionFooter = [self tableView:tableView titleForFooterInSection:section];
UIView* view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, yourWidth, yourHeight)]; //create a view- the width should usually be the width of the screen
UILabel* label = [[UILabel alloc] initWithFrame:someFrame];
label.backgroundColor = [UIColor blueColor];
label.textColor = [UIColor whiteColor];
label.text = sectionFooter;
[view addSubview:label];
return view;
}
You will also have to implement tableView: titleForFooterInSection: if you want to add text like I have here.
I did had similar problem when I found out that my code did not work with Landscape mode, and only worked in Portrait mode. In my old code, when in Landscape the tableview can not scroll lower than visible view and it bounced back to top row when scroll (not letting me see the lower rows). All I have to change is to make sure that the height set to 44 as default cell height. So my footer is basically another cell with clearColor. Note my app uses 'AutoLayout'
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
CGRect screenBounds = [UIScreen mainScreen].bounds;
CGFloat width = screenBounds.size.width;
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, 44)];
view.backgroundColor = [UIColor clearColor];
UILabel *label = [[UILabel alloc] initWithFrame:view.frame];
label.text = #"Your Text";
[view addSubview:label];
return view;
}