NSAutoLayout: How to add element dynamically - cocoa-touch

I need to add an undefined number of NSButton to an NSView in code. The problem is that I can' t use constraintsWithVisualFormat: because i don' t know the name of the NSButton and also the number of button that I have. Anyone have a solution? Thanks!

When using constraintsWithVisualFormat:, you need to know the names of the variables that point to your NSButtons only if you use NSDictionaryOfVariableBindings to create the dictionary of views. You could just as easily build your own dictionary using whatever keys you like.
If your buttons are stored in an array, you can iterate through them and create constraints between each of them:
for ( int i = 1 ; i < buttonArray.count ; i++ ) {
NSDictionary* views = #{ #"buttonOne":buttonArray[i-1] , #"buttonTwo":buttonArray[i] } ;
NSArray* constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"[buttonOne]-[buttonTwo]" options:0 metrics:nil views:views] ;
// Use the constraints.
}

Related

Changing UIImageView picture which name stored in a string

Hi there i have image views in my application to change pictures in them. but their name like imageview1, imageview2, and so.. i create a string which like:
`("imageview%i", number)`
so my string is imageview1 for example. and i need to change
self."**mystring**".image = [uiimage ...]
I looked key-value coding but i couldn't get it exactly. i searched the forum and i can't get anything either. what could i do to resolve this. i think i must do an array with my uiimageviews inside of it. than compare their name with my string (i didn't know how can i get property names as nsstring). then return that image view. Please help.
If you want to do that in runtime, either hack your way using selectors or use tags on your UIImageViews. They are not really good programming concepts, but they work. Examples:
-(void)usingSelectors{
for(int i=0; i < 4; i++){
NSString* propName = [NSString stringWithFormat:#"imageview%d", i];
UIImageView* imageView = [self performSelector:NSSelectorFromString(propName)];
[imageView setImage:/*your image*/];
}
}
-(void)usingTags{
for(int i=0; i < 4; i++){
UIImageView* imageView = [self.view viewWithTag:i];
[imageView setImage:/*your image*/];
}
}
If you want to do that in compile time you'll have to use preprocessor macros, but I don't think that's your case.

What is the most efficient way to move NSArray objects to UITextFields?

I have some code where there may or may not be objects in the Array... this is the code I am dealing with:
oServices1.text = CustomServicesArray[0];
oServices2.text = CustomServicesArray[1];
oServices3.text = CustomServicesArray[2];
oServices4.text = CustomServicesArray[3];
oServices5.text = CustomServicesArray[4];
oServices6.text = CustomServicesArray[5];
oServices7.text = CustomServicesArray[6];
oServices8.text = CustomServicesArray[7];
oServices9.text = CustomServicesArray[8];
oServices10.text = CustomServicesArray[9];
oServices11.text = CustomServicesArray[10];
oServices12.text = CustomServicesArray[11];
oServices13.text = CustomServicesArray[12];
oServices14.text = CustomServicesArray[13];
oServices15.text = CustomServicesArray[14];
oServices16.text = CustomServicesArray[15];
oServices17.text = CustomServicesArray[16];
oServices18.text = CustomServicesArray[17];
oServices19.text = CustomServicesArray[18];
oServices20.text = CustomServicesArray[19];
oServices21.text = CustomServicesArray[20];
oServices22.text = CustomServicesArray[21];
oServices23.text = CustomServicesArray[22];
Rather than check each and every array object for nil, is there a way I can take the oServices*xx*.text UIFields and put them into some kind of array so I can just use a loop?
Are you aware of reflexivity? With KVC you could save up much code and time:
for(int i=1; i<=23; i++) {
NSString* key= [NSString stringWithFormat: #"oServices%d"i];
// Remember that variables should start with a lowercase letter
[[self valueForKey: key] setText: customServicesArray[i-1] ];
}
But if you don't want to bind all these variables in your storyboard/xib file (even this may be too much), just set the tag of each text field in the order that you want (from 1), so that you can get them back using viewWithTag:
// From the UIViewController
for(int i=1; i<=23; i++) { // Consider defining a constant instead of 23
[[self.view viewWithTag: i] setText: customServicesArray[i-1] ];
}
I consider this last solution better because you avoid binding so many variables.
You can use an OutletCollection to hold oServices and loop on that. Note however that outlet collections are not sorted so you would need to sort them beforehand (on the tag criteria, or location for example).
For ordering see this question.
Set the tag property of the UITextFields to their corresponding ordinal in the array. The default value of tag is 0, so you may need to set the tag property to ordinal + 1 if there are other views in the parent view of your UITextFields. On the parent view of your text fields, you can use the viewWithTag: method to retrieve the appropriate UITextField.

Equally distribute spacing using Auto Layout visual format string

Is it possible to equally distribute left and right space for b in #"|-[a(5)]-[b(8)]-[c(5)]-|" using visual format strings?
No.
However, you can do it with visual format and a manual constraint creation. Change your VFL string to:
#"|-[a(5)]->=0-[b(8)]->=0-[c(5)]-|"
This says that you're not too concerned about the actual size of the spaces between a and b, and b and c.
Now, create a constraint pinning the center of b to the center of the superview using constraintWithItem:... (I'm typing this on a phone so forgive me for not spelling out the whole method).
This, coupled with your flexible spacing, will give even spaces to the left and right of b.
Apple's Auto Layout Guide suggests using "spacer views". Here's the solution for laying out your three views with equal spacing horizontally:
// create views dictionary
NSMutableDictionary *viewsDictionary = [NSMutableDictionary dictionary];
[viewsDictionary addEntriesFromDictionary:NSDictionaryOfVariableBindings(viewA, viewB, viewC)];
// create 4 spacer views
for (int i = 0; i < 4; i++) {
UIView *spacerView = [[UIView alloc] init];
spacerView.hidden = YES;
[self addSubview:spacerView];
[viewsDictionary setObject:spacerView
forKey:[NSString stringWithFormat:#"spacer%d", i + 1]];
}
// disable translatesAutoresizingMaskIntoConstraints in views for auto layout
[viewsDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop)
{
[obj setTranslatesAutoresizingMaskIntoConstraints:NO];
}];
// add constraints
[superview addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:
#"|[spacer1(>=0)][viewA][spacer2(==spacer1)][viewB][spacer3(==spacer1)][viewC][spacer4(==spacer1)]|"
options:kNilOptions
metrics:nil
views:viewsDictionary]];
Note that spacer1's width is set to be more than 0. Subsequent spacer views are set to have equal widths with spacer1.

Using a variable inside a variable objective-C

EDIT: {
No longer need help!
I used tags and a bunch of loops to reference them at anytime.
I never knew you could store so many images in one UIImageView!
}
I have an application that deals with a a lot of images and what i want to do is use an integer inside the variable name so i don't have to write code for each image. Ex:
- (void)addIconClicked {
if (icons < 28) {
icons += 1;
}
if (icons == 2) {
UIImageView * iconImage2 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"icon1.png"]];
//modify image view like setting the frame, setting the title, etc... (not important)
}
else if (icons == 3) {
// iconImage 3 set up
}
this continues all the way up to iconImage26!
so i was wondering if i could use the integer "icons" as part of the variable name so i don't have to run the code 26 different times!!
Ex:
- (void)addIconClicked {
if (icons < 28) {
icons += 1;
}
/*some how insert the "icons" int where (icons) is. Like NSString uses
stringWithFormat ([NSString stringWithFormat:#"%i", icons])*/
UIImageView * iconImage(icons) = [[UIImageView alloc] initWithImage:[UIImage imageNamed:[NSString stringWithFormat:#"icon%i.png", icons]]];
}
-----edit----- (to clarify what i want)
my problem is that i want to make an unlimited amount of image views but if i use the same variable more than once, it would show up on the view fine, but it would be released and i can't edit it anymore.
Ex:
- (void)addIconClicked {
if (icons < 28) {
icons += 1;
}
NSString * iconName = [NSString stringWithFormat:#"icon%i.png", icons];
//if i ran this 100 times, 100 images would show up, but i can no longer edit any of them except the newest.
iconImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:iconName]];
}
so i need to know how to make an unlimited amount of images that i can still edit individually.
i thought i might be able to use the integer's value inside a variable name when i create it, but i guess i can't:(
so if anyone knows, please explain!!
so to sum it up...
my exact problem is that i want to create an unlimited number of UIImageView's using a different image each time.
You think i could just use 1 variable for all of the images (which would show up) but then i can't edit them at all because they are released.
I just need a way to create an unlimited amount of Global UIImageViews that i can edit and access at any time.
The best way to do this is with an array.
NSMutableArray* allIcons = [NSMutableArray array]; //First, you'll need this empty array.
//Then, when you create your icon...
NSString* iconName = [NSString stringWithFormat:#"icon%lx.png",icons];
UIImageView * newIcon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:iconName]];
[allIcons addObject:newIcon]; //This adds the icon at the next index, starting at 0.
Then, when you use your icons, instead of icon1, icon2, and so on, use [allIcons objectAtIndex:0], [allIcons objectAtIndex:1], and so on.
This is a pretty common concept--you should consider checking out a few beginning Cocoa tutorials; you might find it in use.
You can use an array for storing UIImageView references. Or you can set their tags and find them later using viewWithTag.
EDIT: Ok I am home now and I can explain:
Use tagging:
UIImageView * anImageView= [[UIImageView alloc] initWithImage:[UIImage imageNamed:[NSString stringWithFormat#"icon%i.png",icons]]];
anImageView.tag=icons;
[parentView addSubview: anImageView];
UIImageView *theOneYouWant = (UIImageView)[parentView viewWithTag:someTag];
or use a c-array of UIImageView pointers to store references if you don't mind scope:
UIImageView * imageViews[28]; // put this into class interface
imageViews[icons]= [[UIImageView alloc] initWithImage:[UIImage imageNamed:[NSString stringWithFormat#"icon%i.png",icons]]];

UISegmentedControl method to set Segment Index?

I'm using a UISegmentedControl in a view to select a specific client. This is then setting an NSString property in my data model from the unique segment title as and when the view is closed. All works exactly as I had hoped. When I reload the view depending on what is stored in the model, I am then setting the UISegmentedControl with the following code in viewDidLoad. i.e. it reads the string property from the model, converts it to an index and selects the correct segment to reflect which client is stored in the model.
if ([self.itemToEdit.client isEqualToString:#"John"]) {
myIndex = 3;
} else if ([self.itemToEdit.client isEqualToString:#"David"]) {
myIndex = 2;
} else if ([self.itemToEdit.client isEqualToString:#"Paul"]) {
myIndex = 1;
} else if ([self.itemToEdit.client isEqualToString:#"Stephen"]) {
myIndex = 0;
}
self.reportEditorClient.selectedSegmentIndex = myIndex;
All works as planned, it's just that it seems quite clunky. I have scoured the documentation to see if there is a UISegmentedControl method that will do this but cannot find anything. Is there a better approach, or am I on the right lines here?
Put this name-to-index mapping in a dictionary, then this chain of if-else pretty much becomes a one liner.
Code formatting wise, you could use an array of names
NSArray *names = [NSArray arrayWithObjects:#"John", #"David", #"Paul", #"Stephen", nil];
then
self.reportEditorClient.selectedSegmentIndex = [names indexOfObject:self.itemToEdit.client];
(Though it's only slightly neater)