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
Related
Before asking this question I have googled a lot and not able to find a suitable answer.
I have a tableView with Three sections and n number of columns. The no. of rows in each section is also not fixed. The last two columns contains a UITextField and initially the value in each textfield is 0.00. So after entering the Value in each row if I close that section the textField value is revert back to 0.00. Can anybody tell me how to save this textField value.
Here is my code.
I'm using a custom Cell GenericTableViewCell and self.columnHeaderArray determines how many columns in tableview. columnHeaderArray Value contains the colum details like the column type, value etc...
in cellForRowAtIndexPath:
GenericTableViewCell *cell = nil;
if (cell == nil)
{
cell = [[GenericTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"withColumns:self.columnHeaderArray];
for (GenericTableColumn *theColumn in self.columnHeaderArray)
{
[self processDataForCell:cell forColumn:theColumn atIndexPath:indexPath];
}
return cell;
}
}
processDataForCell is a method where I updated the column with values and textfield. In this method I check the column type and insert values into each column.
The code for that is.
switch (column.columnType)
{
case textField:
{
UITextField *lblTextField = [[UITextField alloc] initWithFrame:CGRectMake(0, 4, label.frame.size.width - 10, label.frame.size.height - 6)];
lblTextField.delegate = self;
lblTextField.text = theValue; // setting the textField value iniatally 0.00
lblTextField.borderStyle = UITextBorderStyleRoundedRect;
lblTextField.layer.borderWidth = 1.0;
lblTextField.layer.borderColor = [[UIColor blackColor] CGColor];
[label addSubview:lblTextField];
break;
}
}
Any help would be greatly appreciated.
EDIT: I solved the issue thanks to #Ayazmon and #naturalnOva for their comments and leading me to solve this.
The Code:
In my .h file I declared a NSMutableDictionary named textFieldValues like
#property (nonatomic, strong) NSMutableDictionary <NSString *, NSString *> *textFieldValues;
And in viewDidLoad I instantiate this Dictionary as
self.textFieldValues = [NSMutableDictionary new];
In processDataForCell method I replaced my code with
switch (column.columnType)
{
case textField:
{
UITextField *lblTextField = [[UITextField alloc] initWithFrame:CGRectMake(0, 4, label.frame.size.width - 10, label.frame.size.height - 6)];
lblTextField.delegate = self;
lblTextField.tag = indexPath.row;
if ([self.textFieldValues count] != 0) {
// pass in the indexPath from cellForRow
NSString *key = [NSString stringWithFormat: #"%ld-%ld", (long)indexPath.section, (long)indexPath.row];
lblTextField.text = self.textFieldValues[key];
} else {
lblTextField.text = theValue; // For setting the textField value iniatally 0.00
}
lblTextField.borderStyle = UITextBorderStyleRoundedRect;
lblTextField.layer.borderWidth = 1.0;
lblTextField.layer.borderColor = [[UIColor blackColor] CGColor];
[label addSubview:lblTextField];
break;
}
}
And for me UITextField delegate method textFieldShouldEndEditing(_:) is not being called. So I used textFieldShouldReturn method.
-(BOOL) textFieldShouldReturn:(UITextField *)textField
{
NSString *key = [NSString stringWithFormat: #"%ld-%ld", (long)indexPath.section, (long)indexPath.row];
self.textFieldValues[key] = textField.text;
return self.textFieldValues[key];
}
Thanku Guys
As you scroll your cell's off view they are deallocated. Each time the cell scrolls back into view it's re-initialized. If you want to retain the value of the textfield I'd save it's contents when you can and hold onto that value.
I have done this before by holding on to a dictionary of Strings that contain the values for my indexPaths. Each time the textfield resigns as first responder I then store the value of the field in my dictionary.
You should register the UITextField's delegate in your view controller and override textFieldDidEndEditing(_:) where you can access the value of the textfield.
Example:
Create a property for the field values:
#property (nonatomic, strong) NSMutableDictionary *textFieldValues;
Override the UITextFieldDelegate method:
-(void) textFieldDidEndEditing: (UITextField *)textField
{
NSString *key = [NSString stringWithFormat: #"%ld-%ld", (long)indexPath.section, (long)indexPath.row];
self.textFieldValues[key] = textField.text;
}
Using your above code, assign a tag to the textfield and assign the fields contents using the dictionary:
UITextField *lblTextField = [[UITextField alloc] initWithFrame:CGRectMake(0, 4, label.frame.size.width - 10, label.frame.size.height - 6)];
lblTextField.delegate = self;
lblTextField.tag = indexPath.row;
// pass in the indexPath from cellForRow
NSString *key = [NSString stringWithFormat: #"%ld-%ld", (long)indexPath.section, (long)indexPath.row];
lblTextField.text = self.textFieldValues[key];
lblTextField.borderStyle = UITextBorderStyleRoundedRect;
lblTextField.layer.borderWidth = 1.0;
lblTextField.layer.borderColor = [[UIColor blackColor] CGColor];
[label addSubview:lblTextField];
Hope that helps.
cellForRowAtIndexPath is called whenever you try to display a cell in UITableView and in your code you create a completely new cell for the given indexPath every time. So when you set the value to the lblTextField and then displaying it again it gets resetted. My advice is restore the value then in processDataForCell method set the restored value to lblTextField that way the value will have consistency.
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.
I have a custom uiview where i have a setter and a getter when the uiview is dynamically created i set this value like this:
for(NSDictionary *dictCategory in arrCategoryList)
{
NSString *strCategoryId = [dictCategory objectForKey:#"CategoryId"];
NSString *strCategoryName = [dictCategory objectForKey:#"Name"];
NSLog(#"%# : %#",strCategoryId,strCategoryName);
UIViewMenuItem *linkMenu = [[UIViewMenuItem alloc] init];
[linkMenu setFrame:CGRectMake(10, i+1, 300, 35)];
[linkMenu setId:strCategoryId]; //here i set the value in the custom uiview
linkMenu.layer.zPosition = 7;
[viewSlide3 addSubview:linkMenu];
[linkMenu setBackgroundColor:[UIColor blueColor]];
linkMenu.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:.9];
UITapGestureRecognizer *singleFingerTap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(handleSingleTap:)];
[linkMenu addGestureRecognizer:singleFingerTap];
UILabel *labelMenu = [[UILabel alloc] init];
[labelMenu setFrame:CGRectMake(20, 0, 300, 35)];
[labelMenu setFont:[UIFont systemFontOfSize:16]];
[labelMenu setTextColor:[UIColor whiteColor]];
[linkMenu addSubview:labelMenu];
[labelMenu setText:strCategoryName];
i = i + 35 + 1;
}
Now when i tap on the custom uiview i want to get back the value from the custom uiview so I'm doing this:
- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer {
CGPoint location = [recognizer locationInView:[recognizer.view superview]];
CGPoint touchPoint=[recognizer locationInView:[recognizer.view superview]];
UIViewMenuItem *tempView = (UIViewMenuItem *)recognizer.view;
NSNumber *tag = [NSNumber numberWithInt:tempView.tag];
NSString *idCat = [tempView getCatId];
NSLog(#"TAG %#",idCat);
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString: [NSString stringWithFormat:#"http://localhost:8888/MAMP/WHFC/SubCategories.php?categoryid=%d", idCat]]];
int i = 0;
NSError *e;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&e];
NSArray *arrCategoryList = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&e];
UIViewController *viewController = [[UIViewController alloc] init];
viewController.view.backgroundColor = [UIColor whiteColor];
UIView *uiView = [[UIView alloc] init];
[uiView setFrame:CGRectMake(0, 480, 320, 480)];
[uiView setBackgroundColor:[UIColor grayColor]];
viewController.view = uiView;
UITableView *uiTableView = [[UITableView alloc] init];
[uiTableView setFrame:CGRectMake(0, 0, 320, 480)];
[uiView addSubview:uiTableView];
[self presentViewController:viewController animated:YES completion:nil];
//Do stuff here...
}
But i keep get the same value "13" from NSString *idCat = [tempView getCatId];
This is the custom UIView class:
#import <UIKit/UIKit.h>
#interface UIViewMenuItem : UIView
- (void) setId: (NSString *) cId;
- (NSString *) getCatId;
#end
NSString *catId;
#import "UIViewMenuItem.h"
#implementation UIViewMenuItem
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void) setId: (NSString *) cId;
{
catId = cId;
//If possible, set things up for the new word
}
- (NSString *) getCatId{
return catId;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
There is nothing wrong, in principle, with using a hidden (not visible from outside the class) instance variable cId and providing getters and setters.
But yours isn't an instance variable. It is declared somewhere between the interface and the implementation. It's a global variable or static. (I am not 100% positive about the global, but I think it is. I ran into a linker issue - dublicate object - once when I did the same mistake and did it in two classes using the same variable name. However, it is not that important whether it is global or just static. Being static is bad enough.)
This means at least that all the instances of UIViewMenuItem share the same variable (!!!).
First:
Move it to somewhere between #implementation and its #end.
Then it should work as expected.
Then:
Be lazy and solve it the objective-c-way. Get rid of the variable and get rid of the getter and setter.
If you are happy with the variable being public (it's accessible though getter and setter anyway) then just add a #property (nonatomic, retain) NSString *cid; to the interface. Unless you've got an older compiler then that is basically it. The compiler will add a getter and a setter (getCId and cId) automatically.
The compiler will add an iVar _cId that you would not use yourself in most cases. I am not 100% positive whether the name of the iVar will be cId or _cId when you use the comfortable way. If you care then you can conrol the name of the iVar by adding an #synthesize statment to the implementation and define the iVar name as you like. You could add much more customization (again declaring the iVar yourself, providing custom getters and setters) but there is no need for that when all you need from the property is what you have shown in your question and its code examples.
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 put Marquee Label in UITableView cell but with costomized like label text is different color
I am using MarqueeLabel Classes and I am able to display that Marquee Label on UITableViewCell and it is perfectly work.
I also tried for NSAttributedString but MarqueeLabel Does not support different color of label text
If anybody has answer then please give me
Thanks.
Here is my code
[cell.contentView addSubview:[self createMarqueeLabelWithIndex:indexPath.row]];
[cell.textLabel setTextColor:[UIColor redColor] range:NSMakeRange(4, 3)];
-(MarqueeLabel *)createMarqueeLabelWithIndex:(int)index
{
MarqueeLabel *continuousLabel2 = [[MarqueeLabel alloc] initWithFrame:CGRectMake(10,0,300,30) rate:50.0f andFadeLength:10.0f];
continuousLabel2.marqueeType = MLContinuous;
continuousLabel2.continuousMarqueeSeparator = #"";
continuousLabel2.animationCurve = UIViewAnimationOptionCurveLinear;
continuousLabel2.numberOfLines = 1;
continuousLabel2.opaque = NO;
continuousLabel2.enabled = YES;
continuousLabel2.shadowOffset = CGSizeMake(0.0, -1.0);
continuousLabel2.textAlignment = UITextAlignmentLeft;
continuousLabel2.backgroundColor = [UIColor clearColor];
continuousLabel2.font = [UIFont fontWithName:#"Helvetica-Bold" size:17.000];
NSString *strText = [[arrTicker objectAtIndex:index] objectForKey:#"text"];
NSString *strTime = [[arrTicker objectAtIndex:index] objectForKey:#"time"];
NSString *strUser = [[arrTicker objectAtIndex:index] objectForKey:#"userid"];
NSString *strTemp = [NSString stringWithFormat:#"%# %# %# ",strText,strTime,strUser];
continuousLabel2.text = [NSString stringWithFormat:#"%#",strTemp];
return continuousLabel2;
}
in your cellforrowatindexpath create uilabel and assign your marque label than uilabel's text set to attributed string and than convert uilabel to marque label and than add sub view to your cell.
Hope this will be helpful for you.
Thanks.
Not the best answer but rather a dirty hack.
After quick investigation I've just added [self setTextColor:[super textColor]]; to forwardPropertiesToSubLabel
- (void)forwardPropertiesToSubLabel {
// Since we're a UILabel, we actually do implement all of UILabel's properties.
// We don't care about these values, we just want to forward them on to our sublabel.
NSArray *properties = #[#"baselineAdjustment", #"enabled", #"font", #"highlighted", #"highlightedTextColor", #"minimumFontSize", #"shadowColor", #"shadowOffset", #"textAlignment", #"textColor", #"userInteractionEnabled", #"text", #"adjustsFontSizeToFitWidth", #"lineBreakMode", #"numberOfLines", #"backgroundColor"];
for (NSString *property in properties) {
id val = [super valueForKey:property];
[self.subLabel setValue:val forKey:property];
}
[self setText:[super text]];
[self setFont:[super font]];
[self setTextColor:[super textColor]];
}
Now I can change label color through storyboard editor
NB: As you can see I use plain text. For attributed text hack does not work.