UITableViewCell drawRect() Override Makes Device Really Slow (CPU Usage Spike) - swift

That's pretty much my issue. I want to create rounded plain cells in a table view. I override drawRect(frame: CGRect) in a TableViewCell class, but when the cell is shown, the device (simulator too) freezes for about 1 second and the debugger shows a spike in CPU usage to 75%.
Not that it might be of any use, but here's my code:
override func drawRect(rect: CGRect) {
frame.origin.x = 10
frame.size.width -= 20
}
So is there any easier/MUCH more efficient/"legal"(somebody said overriding drawRect is considered "hacky") way of simply making every cell narrower than the screen?

You're not really allowed to use drawRect to modify a view's frame. drawRect just gives you an opportunity to draw within the given frame.
The actual frame of a UITableViewCell is managed by the UITableView through it's width and the heightForRowAtIndexPath: method. Also, the table view is very efficient at caching and reusing cells. Unless you are displaying 1000's of cell on the screen, you won't need to worry about the efficiency of the drawing.
I think you should be adding a subview to the cell's contentView, and setting it's frame as inset to the cell. This can be done using autolayout in a custom cell xib, or manaully in cellForRowAtIndexPath:.

Related

UITableViewCell with background image and dynamic height

I have a tableView that present a list of Books, each of the table cells includes "Book name" and "Book description". The cell's height is determined by the length of the book description, so cells have different heights.
Every cell also has a background image that of course starches according to the cell's height.
I'm drawing the background image in the cell drawRect as follow:
- (void)drawRect:(CGRect)rect
{
UIImage *bgImage = [UIImage imageNamed:#"cell_BG.png"];
bgImage = [bgImage stretchableImageWithLeftCapWidth:60.0 topCapHeight:60.0];
[bgImage drawInRect:rect];
}
This code works, the problem is the scrolling performance, it's not smooth as I would like it to be.
I noticed that the main problem is the changing height, this seems to trigger a drawRect call for all cells, including the reusable cells.
(When I tried to set the same height for all cells the scrolling performance improved drastically, but I must use dynamic height...)
Is there a better approach to do this so the table scrolling will improve?
You probably don't want to override drawRect for a UITableViewCell's view since drawRect is pretty darn expensive.
Try placing a UIImageView on top of your prototype cell and setting its image (you can do so programatically or just drag in interface builder). You can then set the image view's frame size to match the dynamic height of its respective UITableViewCell. UIImageView is optimized for images so this approach should run smoother.
You can set the image and frame size of your imageView in the cellforRowAtIndexPath method in you UITableViewController.
UPDATE:
You'll probably also want to set the content mode to scale and fit your image.
From UIView class reference:
"contentMode - Provides layout behavior for the view’s content, as opposed to the frame of the view. This property also affects how the content is scaled to fit the view and whether it is cached or redrawn."
For example redraw calls drawRect anytime the view's frame changes.
It can be set programatically:
UIView* view = [[UIView alloc] initWithFrame:CGRectMake...etc];
view.contentMode = UIViewContentModeRedraw; // for example
or just by selecting the view in interface builder and setting the content mode attribute in the attributes inspector. I think your looking for UIContentModeScaleToFit or something like that. Also if using interface builder check the struts and springs of the UIImageView in the size inspector.

iPhone UIScrollView how to properly zoom the view? Currently it's height is not adjusted properly

I have a UITableView within a UIScrollView. It took me quite a lot of work to make it work.
The tableView is 640x350, I use the scroll view to scroll from one end of the cell to the next.
The scroll view is 320x350.
The scroll view's content size is 640x350
I'm running into this problem:
if I set scrollView's minimum zoom scale to 0.5, the tableview's width now fills the screen, but it's height is only half the screen. I would like the tableview to show more rows when I zoom out to 0.5.
First of all I would like to understand if this is the correct behavior, or the result of my tableView's content size and frame manipulations. The tableview has all springs and struts set in interface builder and should fill the frame available. This is my first attempt at zooming in months, and I don't remember how it works with zooming.
Can someone help me understand where and what do I need to adjust?
As far as I understand, I need to put the code into scrollViewDidZoom: that will manipulate the tableView's frame and content size.
PS. I"m returning the tableview from the viewForZooming: method of UIScrollView
What you are trying to achieve is pretty hard.
Solution 1 This solution uses the exact setup you have (UITableView inside UIScrollView).
You say that when you set the zoomScale to 0.5, you want your table view to fill the scrollView vertically. At 0.5, your table view must be 640x700 in order to fill the UIScrollView as you wish. For this to happen, on scrollViewDidZoom: you must resize the frame of the table view to 640x700
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
// No matter what the zoomScale is, the tableView will be zoomed accordingly
// Only zoom the height of the table
tableView.frame = CGRectMake(0, 0, 640, 350 / zoomScale);
// Also, update the contentSize
scrollView.contentSize = CGSizeMake(640, 350 / zoomScale);
}
If you run the code above for zoomScale = 0.5 you will get a frame size of 640x700.
This only changes the frame of the table and doesn't change the heights of the cells. This means that as you zoom out, you will also see more cells in the tableview.
Solution 2 Use only UITableView
UITableView is a subclass of UIScrollView. This means it has the ability to zoom and scroll around.
Start with a UITableView with the size that you want on the screen. Then, after the content is loaded modify the contentSize and make it wider than your frame width. This should enable horizontal scrolling.
However, UITableViewCells have their frame set automatically to the width of the tableview frame. You can bypass this by using a custom UITableViewCell, with clipsToBounds=false. Inside it you will insert a UIView with the frame set to the width&height you desire and with no autoresizingMask. When the tableview will resize UITableViewCell frame, this will not affect your inner UIView.

How to reuse/recycle custom element like uitableviewcell does?

When using UITableView, we can reuse its cells using [[ UITableViewCell alloc] initWithStyle: reuseIdentifier:] and [uiTableViewInstance dequeueReusableCellWithIdentifier:] methods. This helps keep memory in check for huge tables as only a few cells are there in the view at a given instant.
I want to create a UIScrollView that has many subviews. Inserting all the subviews takes up a lot of memory and initial time that I want to avoid. Does Apple API provides ways to reuse such custom components (a UIView or a subclass of it here) just like cell views using a identifier?
I will create one if there is no API, but have some doubts regarding this. For example, for every new subview, I am setting its frame position, after the previos views. How should I go about updating the frame for each subview while recycling? Should I delete and reload the content of every subview as it gets recycled? Should I do all these calculations in another thread to avoid jerky scrolling? In all, I would like to have a smooth scrolling experience like in UITableView with all the reusing stuff.
Here is a sample of code that I have written so far:
int numberOfPages = 0;
int pageWidth = 100;
int pageHeight = 100
UIScrollView *myScrollView = //allocate and initialize a scrollview
//set its size to 100 by 100 (width equal to pageWidth)
//set paging enabled for myScrollView
Adding subviews to it from a method, that is called multiple times
- (void) appendSubViewToScrollView {
UIView *view = //allocate and initialize a view and dump data in it.
CGRect rect = view.frame;
rect.size.height = pageHeight;
rect.size.width = pageWidth;
rect.origin = CGPointMake(pageHeight * numberOfPages, 0);
view.frame = rect;
[myScrollView addSubview:view];
numberOfPages++;
[scrollView setContentSize:CGSizeMake(pageHeight * numberOfPages, pageWidth)];
[view release];
}
Edit:
Some insight into how tableview and its cells achieve this behind the scenes would be useful.
Yes, you should restore each subview content each time, exactly as in the table view. The advantage of recycling subviews is in memory saving for view storage, and time saving for view allocation, but of course content data management is up to you.
So the standard recycling approach requires you to use a number of cells which is equal to the number of views visible at the same time on screen + the number of extra cells you may get when starting scrolling.
Let's say for example you're showing 5 full views at a time (scroll view stable) and then while scrolling you will need one extra view which is partially shown, so at the end you need 5+1=6 views. This is in theory, it is recommended to use 2 more views.
So you need to write two pools: one called "visibleViews" which is made of all views added as subviews to the scrollview, and another one called "availableViews" which is made of all views available for re-use.
Then you create all these views and add them to the scroll view (yes: you need to adjust their frame according to their position in the scrollview, and yes, you need to setup the content again).
Finally you need to track the scroll view movement by setting a delegate. The purpose of this tracking is to calculate which of the visible views is no more visible, then remove it from the visible pool and move to the usable pool. Besides the delegate must understand when a new cell is going to appear but it is still not visible, then getting it from the available pool (or alloc/init it if the pool is empty) and adding to both the visible pool and as subview of the scrollview.
Of course if you want to increase performance you can place more subviews in the scroll view in order to avoid to move cells exactly when they start appearing on screen, that's why I recommended to use a couple of extra views at the sides of the scroll view.
There is a great video from WWDC 2010 (you can access it if you're a registered developer) about usage of scroll views in iOS: it explains this technique.
The Apple's PhotoScroller example code in the XCode documentation does essentially what is stated in the WWDC video and explains this technique.

is there any way to change default frame sizes of an imageView, textLabel and detailTextLabel of cell?

I am trying to change frame sizes of an imageView. textLabel and detailTextLabel of UITableViewCell property on inside the UITableView. I tried with change the frame size, resizing mask, resizing subviews and so on, but there is no use. Is there any way to change default frame size? and I am using UITableViewCellStyleSubtitle.
Note: My images took their own image size.
I think is possible with a UITableViewDelegate method:
– tableView:willDisplayCell:forRowAtIndexPath:
you can set frame sizes, position, etc. Basically any layout should have effect there.
or you can subclass UITableViewCell and override layoutSubviews: method (don't forget to call [super layoutSubviews]; first)
Actually I am doing the second approach and works excelent.
Hope this helps ;)
In my opinion, the right way is to redesigned the cell.
2 possibilities:
Programmatically in tableView:cellForRowAtIndexPath: (see iOS guide)
with a custom UITableCellView (see iOS guide)
And for the cell height: tableView:heightForRowAtIndexPath: of the delegate

Subclassing UIScrollView for drawing w/o views

I'm contemplating subclassing UIScrollView (the way UITextView does) to draw a fairly large amount of text (formatted in ways that NSTextView can't).
So far the view won't actually scroll. I'm setting contentSize, and when I drag, I see the scroll indicator. But nothing changes (and I don't get a drawRect: message).
An alternate approach is to use a child view, and I've done this. The view can be over 5000 pixels high, however, and I'm a bit concerned about performance on an actual device.
(The other approach, be like UITableView, would be a huge pain -- I'm "porting" Mac Cocoa code, and a collection of views would be a huge architecture change.)
I've done some searching, but haven't found anyone who is using UIScrollView to do the drawing. Has anyone done this and know of any pitfalls?
All that UIScrollView does when the user scrolls is update its bounds rectangle, which causes the scroll views subviews to move. You could try setting the scroll view's contentMode to UIViewContentModeRedraw. From the docs:
UIViewContentModeRedraw
Redisplays the view when the bounds change by invoking the setNeedsDisplay method.