My project is design a View with numerous label, image, textField without storyboard or Nib. Do I need manually alloc and add every single thing to the view? I think this is very overkill but I have no idea how to do it any other way. Example:
UILabel *firstLabel =[UILabel alloc] init];
firstLabel.frame = (0,x,20,10) ;
firstLabel.text = ...;
firstLabel.font = ...;
firstLabel.textColor = ...;
.................
[self.view addSubView:firstLabel];
UILabel *secondLabel =[UIlabel alloc] init];
secondLabel.frame = (0,y,20,10);
secondLabel.text = ...;
secondLabel.font = ...;
secondLabel.textColor = ...;
.................
[self.view addSubView:secondLabel];
UILabel *thirdLabel =[UIlabel alloc] init];
thirdLabel.frame = (0,z,20,10);
thirdLabel.text = ...;
thirdLabel.font = ...;
thirdLabel.textColor = ...;
.................
[self.view addSubView:thirdLabel];
Should I put all of them in viewDidLoad or loadView or init method?
Or I just need make a method for CreatLabel and use it again and again? How to do it?
If I understand you correctly you ask how to apply DRY (Don't repeat yourself) to this code.
«Two of more — use a for» Edsger W. Dijkstra
or
«Two of more — use an enumeration» vikingosegundo
- (void)viewDidLoad { // or loadView, see Rob's answer
[super viewDidLoad];
NSArray *properties= #[#{#"color": [UIColor orangeColor], #"text": #"Ceterum censeo"},
#{#"color": [UIColor cyanColor], #"text": #"Carthaginem esse"},
#{#"color": [UIColor purpleColor], #"text": #"delendam"}];
[properties enumerateObjectsUsingBlock:^(NSDictionary *properties, NSUInteger idx, BOOL *stop) {
UILabel *l = [[UILabel alloc] initWithFrame:CGRectMake(0, (idx + 1) * 20, 200, 20)];
l.backgroundColor = properties[#"color"];
l.text = properties[#"text"];
l.font= [UIFont italicSystemFontOfSize:12];
[self.view addSubview:l];
}];
}
Ok, why do I prefer this block-based enumeration here?
Because it has an mutation guard and gives me the correct index for free that I need for the frame.
A C for-loop for (int idx = 0; idx < [properties count]; ++idx) gives me the correct index but I must include an extra statement to get the object NSDictionary *property = properties[idx]; and as it has no mutation guard: it could be changed during iteration which might lead to bad things.
A fast enumeration for(NSDictionary *property in properties) has such a mutation guard and is even slightly faster enumerating than the block enumeration. But it has the big disadvantage that if I need the index, I must call NSUIndex idx = [properties indexForObject:property]; causing a quadratic runtime performance instead of a linear: bye bye, speed advantage. And even worse: if an array contains identical objects it will only find the first one repeatedly — a good chance of creating false data.
Depending on the amount of the code, it might be useful to move this into a helper method — but this is more about taste.
As your question in the end is about readability, I want to share another matter of taste: I like to encapsulate object creation into a distinct scope:
Either by using an implicit block
UILabel *label = ^{
UILabel *l =[[UILabel alloc] initWithFrame:CGRectMake(0, (idx + 1) * 20, 200, 20)];
l.backgroundColor = properties[#"color"];
l.text = properties[#"text"];
l.font= [UIFont italicSystemFontOfSize:12];
return l;
}();
[self.view addSubview:label];
or a statement expression
UILabel *label = ({
UILabel *l =[[UILabel alloc] initWithFrame:CGRectMake(0, (idx + 1) * 20, 200, 20)];
l.backgroundColor = properties[#"color"];
l.text = properties[#"text"];
l.font= [UIFont italicSystemFontOfSize:12];
l;
});
[self.view addSubview:label];
If you're programmatically creating view from scratch, you'd do that in loadView. (See Creating a View Programmatically.) If, however, you have NIB/storyboard for the top level view and you are merely adding subviews, then you could do that in viewDidLoad.
Regarding the creation of a bunch of labels, yes, you could use subroutine. Or, assuming there is a regular pattern that dictates where these are positioned, you might even use a for loop in which you increment the y coordinate or build the constraints. (You can save references to these views in an array or use tag values to keep track of them.) But however you do it, yes, you'd want to minimize the amount of repeated code you write (simplifying life from a maintenance perspective, if nothing else).
It is okay to put in ViewDidLoad() but use custom method if there are large number of labels.
Related
I have some object with 18 properties (number of properties in that object is static and do not change over the time). Each property stores some information. I need to create a UILabels in cycle (for, foreach, etc.) and set current object.property to current label. How I can do that?
You may use the code like that:
id object; // your object
NSArray *properties = #[ #"prop1", #"prop2", ... ]; // your properties
[properties enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, idx * 30, 0, 30)]; // or set your custom frame
label.text = [NSString stringWithFormat:#"%#", [object valueForKey:obj]];
[label sizeToFit];
[self.view addSubview:label]; // add to some superview
}];
You also can get all of your properties using Objective-C Runtime without having prepopulated properties array, please refer here to SO
Notice that if some of your properties are scalar types this code will crash. So you probably need some type check here
Have been searching alot for this, but I think I'm doing something stupidly wrong. I have a lot of labels on my second screen, and they all have the same properties. That's why I create them via a method.
In ViewDidLoad I do this:
[self screenTwoLabelMaker:firstNameLabel withFrame:CGRectMake(30, 200, 200, 40) withText:#"First Name"];
That method is this one:
- (UILabel *)screenTwoLabelMaker:(UILabel *)sender withFrame:(CGRect)frame withText:(NSString *)text
{
sender = [[UILabel alloc] init];
sender.text = text;
sender.frame = frame;
sender.font = [UIFont systemFontOfSize:labelFontSize];
sender.textColor = [UIColor grayColor];
[self.scrollView addSubview:sender];
return sender;
}
Then I do this:
NSLog(#"firstNameLabel x: %f y:%f w:%f h:%f", firstNameLabel.frame.origin.x, firstNameLabel.frame.origin.y, firstNameLabel.frame.size.width, firstNameLabel.frame.size.height);
But the result is this:
MyApp[9608:60b] firstNameLabel x: 0.000000 y:0.000000 w:0.000000 h:0.000000
The weird thing is that the label is being put on the screen on the right place with the right text. So everything should be sunny and warm, no? Well, I also call a similar method for buttons, where there is a tag included. That tag is never active on the button (nil), so further programming for that button is killing me. For now I create them all by hand. Do you guys know what I'm doing wrong?
Your method returns your new label, so you need to assign it to the variable that you want to hold the reference. You don't need to pass in the variable to the method as the first thing you do is overwrite it. Use the following code -
firstNameLabel = [self screenTwoMakeLabelwithFrame:CGRectMake(30, 200, 200, 40) text:#"First Name"];
- (UILabel *)screenTwoMakeLabelwithFrame:(CGRect)frame text:(NSString *)text
{
UILabel newLabel = [[UILabel alloc] init];
newLabel.text = text;
newLabel.frame = frame;
newLabel.font = [UIFont systemFontOfSize:labelFontSize];
newLabel.textColor = [UIColor grayColor];
[self.scrollView addSubview:newLabel];
return newLabel;
}
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
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?
I want to make a tutorial screen and have put a scroll view and a Page Controller. I want to put image so that the user can swipe through the tutorial screen on first boot. However I can put labels like this
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
float defWidth = _scrollView.frame.size.width;
float defHeight = _scrollView.frame.size.height;
_scrollView.pagingEnabled = YES;
_scrollView.contentSize = CGSizeMake(defWidth * _pageControl.numberOfPages, _scrollView.frame.size.height);
_scrollView.showsHorizontalScrollIndicator = NO;
_scrollView.showsVerticalScrollIndicator = NO;
_scrollView.scrollsToTop = NO;
_scrollView.bounces = NO;
_scrollView.delegate = self;
UIColor *bgColor[] = {[UIColor orangeColor],[UIColor brownColor],[UIColor grayColor],[UIColor darkGrayColor],[UIColor blackColor]};
for (int i = 0; i < _pageControl.numberOfPages; i++) {
UILabel *label = [[UILabel alloc] init];
float x = (defWidth * (float)i);
label.frame = CGRectMake(x, 0.0f, defWidth, defHeight);
label.backgroundColor = bgColor[i];
label.textColor = [UIColor whiteColor];
label.font = [UIFont fontWithName:#"AppleGothic" size:20];
NSLog(#"%d,%f", i, x);
label.text = [NSString stringWithFormat:#"hogehoge%d", (i + 1)];
NSLog(#"%#", label.text);
[_scrollView addSubview:label];
}
}
But I cannot put images instead of labels. How can I put images in the scroll view so that it will be a slide show of few pictures?
to create a sliding image show so the user can swipe through the the images or images of the tutorials as you say, i find ray's tutorial very helpful. it also comes with the project sample code that has four different ways of sliding images. here is the link for that tutorial which is going to help you understand how it can be done.
http://www.raywenderlich.com/10518/how-to-use-uiscrollview-to-scroll-and-zoom-content
in adition if you want to just have a slide show there is a great control done by kirualex that is a nifty control and you can download it here:
https://github.com/kirualex/KASlideShow
i could have done a run down of these methods but it would be very lengthy and i think by going through the tutorial and looking at the codes you will understand this subject much better.