I'm working on an iPad app and at some points, I need to show a popover with options for a user to pick from. For this, I use a UITableView in a UIPopoverController. The problem is that, on an iPad (not on the simulator), when scrolling the tableview, I get a sort of "double vision" effect, where it appears like two sets of of the list exist. One that is stationary, and one that scrolls up and down.
I construct the popover like this:
self.fClassTypeList = [[NSMutableArray alloc] init];
[self.fClassTypeList removeAllObjects];
NSUInteger stringLength = 0;
(populate self.fClassTypeList, and set stringLength to the size of the longest entry)
[self setContentSizeForViewInPopover:CGSizeMake(stringLength * 15.0f, [self.fClassTypeList count] * 30)];
CGFloat tableBorderLeft = 5;
CGFloat tableBorderRight = 5;
CGRect viewFrame = self.view.frame;
viewFrame.size.width -= tableBorderLeft + tableBorderRight; // reduce the width of the table
self.fListOfItems = [[UITableView alloc] initWithFrame:viewFrame style:UITableViewStylePlain];
self.fListOfItems.delegate = self;
self.fListOfItems.dataSource = self;
[self.view addSubview:self.fListOfItems];
I put in the viewDidLayoutSubviews(…) part of the view controller, maybe I should put it somewhere else? I am not sure why this happens on the actual machine, but not the simulator.
-viewDidLayoutSubviews is a weird place to put allocations because that method can be called multiple times. So as far as your main issue goes, I believe you should move your allocations into the -init method, and move your layout code into your -viewWillAppear method.
- (id)init
{
self = [super init];
if (self)
{
self.fClassTypeList = [[NSMutableArray alloc] init];
self.fListOfItems = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self.fListOfItems.delegate = self;
self.fListOfItems.dataSource = self;
[self.view addSubview:self.fListOfItems];
}
return self;
}
- (void )viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSUInteger stringLength = 0;
CGFloat tableBorderLeft = 5;
CGFloat tableBorderRight = 5;
CGRect viewFrame = self.view.frame;
viewFrame.size.width -= tableBorderLeft + tableBorderRight; // reduce the width of the table
self.fListOfItems.frame = viewFrame;
[self setContentSizeForViewInPopover:CGSizeMake(stringLength * 15.0f, [self.fClassTypeList count] * 30)];
}
This promotes better memory management.
As an added bonus, I would recommend you refactor the
[self setContentSizeForViewInPopover:CGSizeMake(stringLength * 15.0f, [self.fClassTypeList count] * 30)]; method into a setter method of fClassTypeList. Even better is to simply call -viewWillAppear: in that same setter method instead. This will promote good scalability as you (or someone else) continues to build upon this code later on.
It's a little confusing to see what exactly you're trying to accomplish in this code because it's so hardcoded so let me know if I'm missing the mark you're looking for (w/ an explanation why) and I'll make an edit.
Cheers
Related
I'm a beginner and still trying to figure out how to read this.
There are 3 custom views and at the start I allocate the first one.
And then the second / deallocate the first and then the third / deallocate the second.
I do empty/nil all arrays right before deallocating each view so from what I see, all memory retained each time I allocated views, should be released whenever I deallocate/nil them but in the graph it keeps increasing, I don't see anything being released at all.
Is that supposed to look like that? I'm nil-ing delegates, arrays, dictionaries etc everything.
-(void)firstTOsecond {
[self.first removeFromSuperview];
self.first.delegate = nil;
self.first = nil;
self.second = [[Second alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[self.view addSubview:self.second];
self.second.delegate = self;}
-(void)secondTOthird {
[self.second removeFromSuperview];
self.second.delegate = nil;
self.second = nil;
self.third = [[Third alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[self.view addSubview:self.third];
self.third.delegate = self;}
EDIT
In First.m / Second.m / Third.m
-(instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.buttonStartFrame = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"buttonStartFrame"]];
[self addSubview:self.buttonStartFrame];
self.buttonStartFrame.userInteractionEnabled = YES;
self.buttonStartButton = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"buttonStartDefault"]];
[self.buttonStartFrame addSubview:self.buttonStartButton];
self.buttonStartButton.userInteractionEnabled = YES;
self.labelStart = [[UILabel alloc]init];
[self.buttonStartButton addSubview:self.labelStart];
self.labelStart.textColor = [UIColor darkGrayColor];
self.labelStart.text = #"Start";
}
return self;}
the memory leak could be that you are continuously creating new view elements when they could be updated instead .. you can create helper methods to avoid allocating new views, and updating the one's on the main thread instead
- (void)limitedUpdateTextColorView:(NSDictionary *)somethingCache {
self.labelStart.textColor = [somethingCache valueForKey:#"bar"];
self.labelStart.text = [somethingCache valueForKey:#"foo"];
}
I'm trying to make my View Controllers leaner by abstracting common UI away into UIView subclasses and just instantiating them in my VCs, much like I would create HTML templates in web dev.
Here is my VC:
- (void)addEmptyView {
self.emptyHomeView = [[PLOTEmptyHomeView alloc] initWithFrame:CGRectMake(0, 10, self.view.bounds.size.width, self.view.bounds.size.height - 113) andUser:self.userModel.user];
[self.view addSubview:self.emptyHomeView];
}
And the UIView I subclass and instantiate:
- (id)initWithFrame:(CGRect)frame andUser:(NSDictionary *)user {
self = [super initWithFrame:frame];
if(self){
self.user = user;
[self drawUI];
}
return self;
}
- (void)drawUI {
self.arrowUp = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"arrow-up"]];
CGRect arrowUpFrame = self.arrowUp.frame;
arrowUpFrame.origin.x = self.bounds.size.width - 60;
arrowUpFrame.size.width = 33.75;
arrowUpFrame.size.height = 33;
self.arrowUp.frame = arrowUpFrame;
[self addSubview:self.arrowUp];
self.welcome = [[UILabel alloc] initWithFrame:CGRectMake(0, 167.5, self.bounds.size.width, 22.5)];
NSArray *nameParts = [self.user[#"name"] componentsSeparatedByString:#" "];
self.welcome.text = [NSString stringWithFormat:#"Hey %#", nameParts[0]];
self.welcome.font = [UIFont fontWithName:#"Avenir-Light" size:24];
self.welcome.textColor = [UIColor plotPlaceholderGrey];
self.welcome.textAlignment = NSTextAlignmentCenter;
[self addSubview:self.welcome];
}
Now this code all works fine, but for my own understanding, am I abusing the init method? I'm also aware of layoutSubviews & drawRect in UIView but i'm not sure if I should be using them in the above scenario?
Any pointers are appreciated...
Your not abusing of init method, but you have to declare all your ui objects and all their properties without define the frame and in the layoutsubviews, you define the frame of all your object. It's my way to declare an custom view.
- (id)initWithFrame:(CGRect)frame andUser:(NSDictionary *)user {
self = [super initWithFrame:frame];
if(self){
self.user = user;
self.arrowUp = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"arrow-up"]];
[self addSubview:self.arrowUp];
self.welcome = [[UILabel alloc] init];
NSArray *nameParts = [self.user[#"name"] componentsSeparatedByString:#" "];
self.welcome.text = [NSString stringWithFormat:#"Hey %#", nameParts[0]];
self.welcome.font = [UIFont fontWithName:#"Avenir-Light" size:24];
self.welcome.textColor = [UIColor plotPlaceholderGrey];
self.welcome.textAlignment = NSTextAlignmentCenter;
[self addSubview:self.welcome];
}
return self;
}
- (void)layoutSubviews {
CGRect arrowUpFrame = self.arrowUp.frame;
arrowUpFrame.origin.x = self.bounds.size.width - 60;
arrowUpFrame.size.width = 33.75;
arrowUpFrame.size.height = 33;
self.arrowUp.frame = arrowUpFrame;
self.welcome.frame =CGRectMake(0, 167.5, self.bounds.size.width, 22.5)];
}
Hope it will help you.
Optimally, you can instantiate and add subviews in your method as shown, but do actual layout involving frames in layoutSubviews. The code you have will work as is, but if your initialize would be from a nib or something that doesn't provide the core frame (or autolayout was involved) you may not truly know the proper bounds/frame at init time. Often layout code is dependent on other factors that may change so layoutSubviews is a more proper location.
I am sublassing either an UIImageView or UIView for generating simple color tiles with digits. If I am using UIImageView, I use initWithImage method. If I use UIView, the method initWithFrame is being used.
Either red square image or programmatically generated red view is used for initialization.
The problem can be seen on two screenshots: when initWithImage is being used - everything works fine. If initWithFrame method is being used, I am ending with multiple white views without any information created in even order with normal views. All the screenshots and code attached.
This how it looks when initializing using initWithImage:
- (id)initWithImage:(UIImage *)image {
self = [super initWithImage:image];
if (self) {
//Some non-important, label-related stuff.
[self addSubview:self.numberLabel];
}
return self;
}
And this is how it looks with initWithFrame:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.redView = [self redViewWithFrame:frame];
[self addSubview:self.redView];
//label-related stuff
}
return self;
}
- (UIView *)redViewWithFrame:(CGRect)frame {
UIView *view = [[UIView alloc] initWithFrame:frame];
view.backgroundColor = [UIColor redColor];
view.alpha = 1;
return view;
}
And the for-loop, calling these initializers from another class (UIScrollView subclass). The label value is being set after the view had been initialized.
- (void)setNumberOfBlocks:(NSInteger)numberOfBlocks {
_blocks = [[NSMutableArray alloc] init];
CGSize contentSize;
contentSize.width = BLOCK_WIDTH * (numberOfBlocks);
contentSize.height = BLOCK_HEIGHT;
self.contentSize = contentSize;
for (int i = 0; i < numberOfBlocks; i++) {
CGFloat totalWidth = BLOCK_WIDTH * i;
CGRect frame = CGRectMake(0, 0, BLOCK_WIDTH, BLOCK_HEIGHT);
frame.origin.x = totalWidth;
BlockView *view = [[BlockView alloc] initWithImage:[UIImage imageNamed:#"block.png"]];
OR!!!
BlockView *view = [[BlockView alloc] initWithFrame:frame];
view.frame = frame;
NSString *number = [NSString stringWithFormat:#"%ld", (long)i + 1];
view.numberLabel.text = number;
[self addSubview:view];
[_blocks addObject:view];
}
}
Googling gave me the impression that this is very common problem, but I haven't found any solution how to beat this. Moreover, I still do not understand, why numbers are in totally right order, the only problem is view position.
The problem is that you're setting the frame of the red view to the superview's frame instead of its bounds. Since the red view is a subview of the BlockView, its frame needs to be relative to its superview, which you get with bounds (its not clear why you even need the red view, as opposed to setting the background color of the block view to red).
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.redView = [self redViewWithFrame:self.bounds];
[self addSubview:self.redView];
//label-related stuff
}
return self;
}
I think you should set the frame origin to (0,0) in redViewWithFrame in ordre to hâve superposed views
So I'm subclassing SChartCrosshairMultiValueTooltip to implement a custom multi-value (OHLC) tooltip. I can get the OHLC data from the chart OK. But I'm having difficulty understanding how to present the data in the way the Shinobi frameworks intend me to.
Specifically, I do not understand how to use the labels property. I first assumed that it would be pre-populated with labels generated by the superclass calling keyValueDisplayPairsForDataPoint:onSeries:withXAxis:withYAxis:, but that is not correct as the debugger reveals that the labels array is empty.
So I tried calling that method and manually adding UILabels to the labels array in my overridden - (void)setDataPoint:(id<SChartData>)dataPoint fromSeries:(SChartSeries *)series fromChart:(ShinobiChart *)chart method, but nothing is shown. So then I tried adding them as a subviews to self.view. That works, but because I've just added them myself they are not styled according to the tooltip, and anyway that just feels wrong since there's virtually no point subclassing if I have to do everything myself. I also have to ensure [self layoutContents] is not called for this to work, which feels double-wrong.
The code that works as described above looks like this:-
- (void)setDataPoint:(id<SChartData>)dataPoint fromSeries:(SChartSeries *)series fromChart:(ShinobiChart *)chart
{
SChartMultiYDataPoint *dp = (SChartMultiYDataPoint *)dataPoint;
NSDictionary *dict = [self keyValueDisplayPairsForDataPoint:dp onSeries:series withXAxis:chart.xAxis withYAxis:chart.yAxis];
for (UILabel *label in self.labels)
{
[label removeFromSuperview];
}
[self.labels removeAllObjects];
CGFloat y = 0;
const CGFloat kHeight = 25.0f;
for (id key in [dict allKeys])
{
UILabel *keyLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, y, 80, kHeight)];
keyLabel.text = key;
UILabel *valueLabel = [[UILabel alloc] initWithFrame:CGRectMake(80, y, 80, kHeight)];
valueLabel.text = dict[key];
[self.labels addObject:keyLabel];
[self.labels addObject:valueLabel];
y += kHeight;
[self addSubview:keyLabel];
[self addSubview:valueLabel];
}
}
- (void)setPosition:(struct SChartPoint)pos onCanvas:(SChartCanvas *)canvas
{
// [self layoutContents]; // do NOT call this
self.frame = CGRectMake(0, 0, 160, 100);
}
Does anyone know a better way?
first take a look on this picture from localScope app :
i have 2 (simple?) questions :
how can i paginate my icons like this?
how can i detect witch icon is " selected "
thank you.
Answer to the first question: You have to make your scroll view as big as the page size, enable its pagingEnabled property, then somehow make it to display elements and respond to touches outside of its bounds. See this code and these links:
#interface SmallPagedScrollView: UIScrollView <UIScrollViewDelegate> {
UIEdgeInsets responseInsets;
NSMutableArray *items;
}
#implementation SmallPagedScrollView
#synthesize responseInsets;
- (id)init
{
if ((self = [super initWithFrame:CGRectMake(x, y, w, h)]))
{
self.backgroundColor = [UIColor clearColor];
self.pagingEnabled = YES;
self.showsHorizontalScrollIndicator = NO;
self.showsVerticalScrollIndicator = NO;
self.clipsToBounds = NO;
CGFloat hInset = 3 * self.width / 2;
self.responseInsets = UIEdgeInsetsMake(0.0f, hInset, 0.0f, hInset);
self.delegate = self;
items = [[NSMutableArray alloc] init];
}
return self;
}
- (void)dealloc
{
[items release];
[super dealloc];
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
CGPoint parentLocation = [self convertPoint:point toView:self.superview];
CGRect responseRect = self.frame;
responseRect.origin.x -= self.responseInsets.left;
responseRect.origin.y -= self.responseInsets.top;
responseRect.size.width += self.responseInsets.left + self.responseInsets.right;
responseRect.size.height += self.responseInsets.top + self.responseInsets.bottom;
return CGRectContainsPoint(responseRect, parentLocation);
}
See also Paging UIScrollView in increments smaller than frame size (Split's answer)
Answer to the second question: you can calculate the selected page using this formula:
int selectedIndex = (scrollView.contentOffset + scrollView.size.width / 2) / scrollView.size.width;
Well one clean & memory efficient approach is to have a UINavigationController & UIToolBar like so -
When the user taps on any button in the UIToolBar invoke that particular viewController by popping and pushing them.
I hope its clear that the look and feel can be achieved close to what you are showing in the image, I am talking about the functionality.