I have a UITableView that contains more than 20 rows, each UITableViewCell contains a UITextView which I can set different UIFont, alignment and colour. When I scroll down or up there is a choppy or lagging during the scrolling, when I remove the font and text colours and alignment everything become perfect. Did Apple changes the way of redrawing text in iOS 7? This did not happen with the previous iOS versions.
instruments shows that the time consumed at dequeueReusableCellWithIdentifier:forIndexPath:
-- UPDATE Add Code
if (unit.fontName != nil ) {
textView.font = [UIFont fontWithName:unit.fontName size:unit.fontSize.floatValue];
}
if (unit.fontColor) {
textView.textColor=[self convertString:unit.fontColor];
}
else {
textView.textColor=[UIColor colorWithRed:0 green:0 blue:0 alpha:1];
}
if ([unit.fontSize floatValue] !=0) {
textView.font = [UIFont fontWithName:textView.font.fontName size:[unit.fontSize floatValue]];
}
if (unit.textAlignment) {
switch ([summaryUnit.textAlignment intValue]) {
case 0:
textView.textAlignment = NSTextAlignmentLeft;
break;
case 1:
textView.textAlignment = NSTextAlignmentCenter;
break;
case 2:
textView.textAlignment = NSTextAlignmentRight;
break;
default:
break;
}
}
There are a lot of things going on behind the scenes in an UITableView, that might not make it obvious why it appears unresponsive.
As an example, you should not do tons of work in the tableView:heightForRowAtIndexPath: as the UITableView needs to ask for the height of every single row in your dataset, regardless of whether they are apparent on the screen or not. It does this in order to determine the content size.
It might not at all be your data or your cells that causes the problem. Complex cells with lots of subviews might render slow in the compositor. Especially if you have things like transparency.
Therefore I always put four basic rules down for myself whenever I am implementing a UITableView.
1) Always know what you need to tell the table view when you reload the data. Don't do lot's of stuff in the data source or delegate methods. Have things ready for when UITableView needs it.
2) Don't destroy and create views in the scroll cycle. By that, you should not create new UIView (or whatever) - as an example in your prepareForReuse methods. Just reset content of existing views and controls, and then reconfigure them whenever UITableView asks you to.
3) Don't have complex view hierarchies in your cells. Especially transparency and out-of-bounds content that needs to be rendered, can be cumbersome for the compositor to render at fast enough speeds.
4) Don't configure your cells in tableView:cellForRowAtIndexPath:. Just dequeue and return the cell needed. Always configure and setup your cells in the tableView:willDisplayCell:forRowAtIndexPath: delegate method.
That being said, there are some tricks that can help you. As I mentioned above, don't create and destroy views or controls in the cell's prepareForReuse method. Don't do heavyweight work at all. Just reset your views and go on. Which it might sound like you're doing, since it is the dequeueReusableCellWithIdentifier:forIndexPath: that causes your troubles.
Lastly if it is because of compositing (Instruments would show work in some Quartz-related methods) you could succeed by using the shouldRasterize and rasterizationScale of your cell's CALayer. This can sometimes speed up rendering of cells considerable.
Let me know if any of this stuff makes sense, and if it might have solved your problem.
EDIT:
PS. Yes. Apple did change the text-rendering in iOS 7. But why are you using UITextView?!? I don't know it that well, but I know it is much more heavyweight than UITextField or UILabel. Maybe you could consider using those instead. UITextView is usually for editing something that resembles documents (like the Notes app). I really think this might be what is causing your troubles.
Related
I'm implementing custom visual effects when the user selects a row in a UITableView. I've overridden UITableViewCell.setSelected(), but I call it in my subclass, it does some tweaking of the appearance of the content view before adding the selectedBackgroundView to the hierarchy, like setting opacity to false. I don't want the content view and it's subviews to be !opaque, so I thought I could avoid calling super.setSelected when overriding it, but then that breaks the select/deselect behavior.
Is there a way to avoid the default UI changes of UITableViewCell.setSelected() without breaking cell selection/deselection? It is surprising to me that built in UI changes for selecting/deselecting cells is so intertwined with the logic/state tracking to make selection/deselection work at all inside setSelected()
More generally, are there any debugging tips or tracing I can use to see what exactly goes on, in terms of what updates are happening to views, when calling something like UITableViewCell.setSelected(), so I can mimic it without calling the base class method (I understand this is likely to break with new versions of iOS)
Thank you,
Neal
Instead of using the cell selection method you can try using the highlight method.
Step 1: Make the table view cell selection to none
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
Step 2: Override the cell's set highlighted method to customize the cell selection
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{
if (highlighted)
{
}
else
{
}
}
HI, i set my uisegmentedcontrol's width manually, but when the width gets too small, the words becomes ...
Is that anyway that it won't behave in this way? Instead, i just want to show the text just like the picture shown below.
I'd suggest changing your design here and going for a different approach.
The design that you seem to want makes readability pretty much impossible.
Plus, what happens if I'm using your app and add another "Active Project". What happens if I have 10 active projects?
Take the fact that the UI does not work as a sign that you are using the wrong UI for the problem you are trying solve.
I'd suggest possibly just have the current project title here with a button to maybe present a list of projects to switch to... or something.
The text has been truncated. If you want it to fit your segment, you need to update the segment control size based on the text length. If you just want to get rid of truncation, you can use the following snippet. However, it's not recommended, as later Apple might change the UISegmentControl hierarchy.
for item in segmentedControl.subviews {
for subview in item.subviews {
if subview.isKind(of: UILabel.self) {
let _label = subview as! UILabel
_label.numberOfLines = 0
_label.lineBreakMode = .byWordWrapping
}
}
}
I made an infinite scrolling calendar in a UIScrollView, where each may can contain up to eight subviews. The function I'm calling inside scrollViewDidScroll() looks like this (simplified):
func addAndRemoveRow(scrollView: UIScrollView) {
if scrollView.contentOffset.y - scrollViewZeroOffsetY < -heightOfDay/2 { // Going back in time.
createRowAtBottom()
removeRowAtTop()
} else if scrollView.contentOffset.y - scrollViewZeroOffsetY > heightOfDay/2 { // Going forward in time.
createRowAtTop()
removeRowAtBottom()
}
}
Functionally, this works perfectly, and in the mode where each row contains a single day, it runs smoothly even on my iPhone 5 (both modes run fluidly on the simulator). However, in the mode where each row contains a week, it's pretty choppy.
It seems like overkill to call addAndRemoveRow() every time the scrollview is moved even a pixel. Is there a way to call it less frequently?
(Alternatively, is there a more optimized way of doing this? I tried using a UICollectionView and it doesn't run any more smoothly.)
I would be curious as to why a UICollectionView didn't help. I have had collectionView's with 100's of cells on the screen at the same time and it has run very smoothly even on 5's.
If you still have your implementation of the collectionView, some things I would check to see if it can help your performance would be:
1: Make sure that you are reusing cells and that your datasource isn't changing dynamically unless it needs too. Also make sure that if you are adding more subviews to a cell that it is done in awakeFromNib instead of when you configure it and reuse your changes if possible.
2: if there is a lag on startup, use estimated size assuming your using iOS8+ and a flow layout #property (nonatomic) CGSize estimatedItemSize NS_AVAILABLE_IOS(8_0);
3: If you'r not using a flow layout make sure you are only returning the correct number of layout attributes for - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect; You should only have to return the attributes for whatever is in the rect.
4: Try not invalidating the layout on bounds change to see if it helps performance.
If you are doing more dynamic things with the view this is a good read: http://www.raizlabs.com/2013/10/animating-items-uicollectionview-2/
This problem sometimes bothers me a lot. For example, if I just want to change the header text font size, I have to create the whole UIView. There is no direct instance method for UITableView like [tableView setHeaderTextFont], I think apple should really add this method.
However, this post did a good job, but it's not exactly the same with the original one. If you check the original one very careful, it has effect like 'inner glow' in photoshop, but with NSAttributedString, this can not be done (correct me if I'm wrong). It must be rendered by some private methods.
Also, the footer text style is the same.
So my question is: how to recreate the grouped tableview header/footer text style, exactly.
Thanks.
The effect you're talking about is just a 1px white shadow. It's there in the post you linked to:
label.shadowColor = [UIColor colorWithWhite:1.0 alpha:1];
label.shadowOffset = CGSizeMake(0, 1);
Are you implementing this and not seeing the effect?
I think I remember something about UI being updated on the main thread, could be the explanation, however my issue is that I have a UIScrollView with 1000+ images in it, and I wrote a routine that quickly will toggle the images visible in the view to show their real image, and when out of visible view, they switch to a placeholder image. All the offscreen images share the same placeholder, greatly reducing memory load.
I wrote two methods that toggle the next few tiles of the view on/off, and are kicked off if you have scrolled enough in a certain direction. The scrolling is detected via the UIScrollViewDelegate callback method scrollViewDidScroll.
This strategy works fine, but the scrollview scrolling is a little jittery on slow devices (iPad 1). I wanted to increase performance, so I thought I could change the code so that the two methods that update the scrollview contents were NSOperationInvocation, added to an NSOperationQueue.
However, when I do this, even with priority set to VeryHigh, the routines to not update the UI even though they seem to be called, and the queue does grow, when scrolling. The observed behavior is that the images retain their placeholder images and do not switch on.
Why doesn't this work?
Code for the NSOperationInvocation:
ImageThumbView *thumb = [boardThumbsArray objectAtIndex:lowestLoadedThumbTag - i];
[loadedThumbsArray addObject:thumb];
[thumb showThumbImage];
ImageThumbView is a UIButton Subclass
- (void)showThumbImage {
if (thumbImagePath != nil) {
thumbImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL fileURLWithPath:thumbImagePath]]];
} else {
NSLog(#"%#", #"Cannot show thumb image: thumbImagePath is nil");
}
[self setBackgroundImage:thumbImage forState:UIControlStateNormal];
[self setNeedsDisplay];
}
Look at Apple's "PhotoScroller" sample code. You should not be having 1000 views as subviews of scrollview. Recycle the ones that are no longer visible.
Also look at WWDC 2010 "Designing apps with scrollview" video. That explains the photoscroller sample code
Thanks