How can I create views with dynamic content in iOS? - ios

I' m encountered with a problem because of my lack of knowledge of iOS platform I think.I have a view controller User Profile. The main view has next structure: ScrollView which contains one subview and this UIView has 7 UIViews with UILabels, UItextViews, UIImageViews, etc. each of them displays specific info about user. The problem - data for this views is fetched from the server and it can be different. E.g I have a subview Education, it contains next info: name of the institute, degree, years of learning, etc. But one user can have several educations. So the size of this view can be dynamic. But i can't simply change the view size with view.frame = CGRectMake because under it I have a view job experience and etc. Or for example professional skills view: it can be one skill and it can be one hundred skills for one user -so I need to change the size of this view but under it I have another view so i need to move it and so on and so forth. So the question what is the correct way of dealing with this situation? I understood that I can't just change view frames with view.frame= CGRectMake() - because it is too much work and it is a stupid approach I think. I know there must be some more straightforward way for this very common problem, may be autolayout or something else? Anyway I hope for some help and advice of how can I make views with dynamic content.

I really don't know why you said "I can't just change view frames". Of course you can!
The approach I always take to this scenario (where your view's height is variable) is to declare a yOffset property and based on this I place my content at the right y position.
#property (nonatomic, assign) int yOffset;
Then inside the init method I initialize this property to either 0 or some predefined initial margin.
_yOffset = TOP_MARGIN;
Then consider the following scenario: a user profile with n number of skills. This is how you would use the yOffset property.
for (int i=0; i<n; ++i)
{
// place the skillView at the right 'y' position
SkillView *skillView = [[SkillView alloc] initWithFrame:CGRectMake(0,self.yOffset,300,50)];
// configure the skillView here
[self.view addSubview:skillView];
// Don't forget it has to be "+=" because you have to keep track of the height!!!
self.yOffset += skillView.frame.size.height + MARGIN_BETWEEN_SKILLS;
}
And then imagine the user profile has c number of Education entries; same thing:
for (int i=0; i<c; ++i)
{
// place the educationView at the right 'y' position
EducationView *educationView = [[EducationView alloc] initWithFrame:CGRectMake(0,self.yOffset,300,50)];
// configure the educationView here
[self.view educationView];
// Don't forget it has to be "+=" because you have to keep track of the height!!!
self.yOffset += educationView.frame.size.height + MARGIN_BETWEEN_EDUCATIONS;
}
Finally, when all your subviews have been added you have to change the frame of the containing view. (Because when you created it you couldn't know upfront how tall it was going to be)
CGRect viewFrame = self.view.frame;
viewFrame.size.height = self.yOffset + BOTTOM_MARGIN;
self.view.frame = viewFrame;
And that's it.
Hope this helps!

As you've stated, manually updating the frames is a lot of work and is the old way of doing things. The new better way as you've also said is to use auto layout. Describing how to use auto layout to position your views would require more specific information on how you want it to look but there are some awesome tutorials out there on the web! Here's one of many:
Ray Wenderlich autolayout in iOS 7 part 1

Related

Allocation in ARC

i am facing one problem i.e in ARC enable project if add buttons to scroll view by adjusting its y position,then allocation is showing that 50%. But in ARC it should not display right. That for loop may iterate 15000 times . And i got unbounded memory growth,i observed that progress bar getting lazy when the count is increasing.Example code is
static int i;
for(;i<15000;i++)
{
UIButton *but = [UIButton buttonWithType:UIButtonTypeCustom];
but.frame = CGRectMake(50, j, 60, 30);
j = j+30;
[_scroll addSubview:but];
}
here i am getting 50% allocation.
but.frame=CGRectMake(50, j, 60, 30);
As mentioned in the comments, the setup is not quite right for your context.
Your app slowed down because it was using so much memory due to the instantiation of 15000 UIButton objects.
You said that you need to have so many instances, because you are doing some sort of list for contacts. This is exactly the use case for UITableView or UITableViewController (it's the same UI control that Apple uses in its own preinstalled apps).
It allows you to reuse the cell objects (instances of class UITableViewCell) instead of creating a whole new object for every contact you want to display.
You will have to implement the UITableViewDelegate and UITableViewDataSource protocols for the setup to work. These are not too trivial at first sight, but you'll get acquainted to the underlying mechanism rather quickly once you start to take off. I'd recommend you to start with a simple tutorial and then move on to your particular case from there.
Do not reinvent the wheel !
As the previous speakers already pointed out you should use a UITableView.
But you do not have to reinvent the wheel. There are a lot of implementations out there which face exactly this problem.
Here you can find a good implementation which already uses a UITableView (1: Objective-C, 2:Swift).
SSMessagesViewController
Acani Chats
What it basically does is:
Use a custom UIView to display the "bubbles"
Use this custom UIView to display in a custom UITableViewCell
Use this custom UITableViewCell to be displayed in a UITableView which manages to get a high perfomance for your task

programmatically adding textview

Usually I do all of the creation of views in storyboard but now I need to add a view via code and although I understand the very basics of doing one, I need some help on how I go about setting up views but more than that I need to get them placed inside and existing view and also create multiple items. So I will have a parent UIScrollView and inside of that one I need to be able to create up to x additional blocks each block containing two lables and one textview. Something along these lines
I can use the storyboard to create the Parent ScrollView
ParentSCROLLVIEW
child_1_block
lable_1_1
label_1_2
textview_1
child_2_block
lable_2_1
label_2_2
textview_2
child_3_block
lable_3_1
label_3_2
textview_3
child_4_block
lable_4_1
label_4_2
textview_4
.
.
.
child_x_block
lable_x_1
label_x_2
textview_x
I am trying to create just a single one now but I am not sure of the syntax to place it inside of an existing scrollview currently it is going into the main view working on that right now. The other confusing this I have been thinking about is teh creation of a dynamic amount of the same type of objects are is the object name determined if it is going to be dynamic
UITextView *newTextView = [[UITextView alloc] initWithFrame:CGRectMake(10, 10, 300, size.height + 16)];
Currently I use this to create a new textview but I will need to do the same thing for multiple entities
such as *newTextView1, *newTextView2, *newTextView3, *newTextView4, ..., *newTextViewX
Am I able to construct a string with the appended number and then use that string as the name of the object I need to create...never did that before so I am not sure but I have a feeling I would see errors
I am hoping some one could show me some sample code or point me in the right direction or even suggest what the correct terms I can search for...anything would be helpful
Jeff
You can't give dynamic names to instances.
What you can do, is give them different tags, in case they subclassing from UIView (Like UITextView).
Run this in a loop:
UITextView *myTextView = [[UITextView alloc] initWithFrame....];
[self.view addSubview:myTextView];
myTextView.tag = loopIndexInt; // This is where you put your dynamic number.
Now, In order to retrieve a specific UITextView by it's tag, do:
UITextView *textView = [self.view viewWithTag:5]; // in order to get textView with tag number 5.
Anyway, it really depends on what you're trying to do.

Splitviewcontroller logic is not right, duplicate content

I am trying to port my iphone app to Ipad, the logic does not work with splitviewcontroller.
In viewDidLoad i call a function that updates the view and with setSearchResult (overriding the setters)
the problem is everytime when i select a row in the masterviewcontroller the contents of the detailviewcontroller get added to it instead of removing the old contents..
i think this gives me the problem:
CGRect framephoto = CGRectMake(0, 85, 320, 186);
carousel = [[iCarousel alloc] initWithFrame:framephoto];
[self.scrollview addSubview:carousel];
and this table (custom mg)
// the tables grid
CGSize tablesGridSize = IPAD_TABLES_GRID;
tablesGrid = [MGBox boxWithSize:tablesGridSize];
tablesGrid.contentLayoutMode = MGLayoutGridStyle;
[self.scrollview.boxes addObject:tablesGrid];
I think because everytime when i select a row and it gets to these codes it adds another one.
I am not a pro so maybe this question is stupid, but every tip is welcome..
Whats the best way to handle this ?
Unfortunately, the best way to handle this is to use the UINavigationController you should already have in use in your application. A easy solution for your problem is to just remove all subviews before you are adding new ones (maybe you have to safe the references to the iCarousel and the tablesGrid for this). A even better way is to just update the data of the views you already have allocated.

How can i auto generate text fields

I'm new to iOS and I'm busy with my first app. I have a question: In the code below is there any way to have my app auto generate a text field when a button is pressed without me having to set it up first?
For example in the code below would it be possible to get input from a text field and use it in place of "field1" to auto generate another textfield.
UITextField *field1 = [[UITextField alloc] initWithFrame:CGRectMake(10, 10, 100, 30)];
field1.placeholder = #"Textbox 1";
field1.borderStyle = UITextBorderStyleRoundedRect;
[self.view addSubview:field1];
x = 10;
w = 100;
h = 30;
y = y + 60;
UITextField *field1 = [[UITextField alloc] initWithFrame:CGRectMake(x, y, w, h)];
field1.placeholder = #"Textbox 1";
field1.borderStyle = UITextBorderStyleRoundedRect;
[self.view addSubview:field1];
OK i see it was my code. In CGRectMake i replaced the values with variables which i incremented now with every button press and its more text fields, before it was adding on on top of the other thats why it seemed like it wasnt adding new text fields.
Now 2 questions on the above,
can i access the textfields individualy using this method.
Is this considered best practice or does it just use a lot of memory.
General Answer
Going by your question and comments, it looks like you want to keep adding an arbitrary number of fields and manage them as you go (keep them around so you can access and manipulate them). You'll still need to write code to create a field and stash it somewhere.
You can create a -createAndStoreFieldWithName: method that's called by your action. Such a method would use something similar to the code you wrote above but would store it somewhere. I'd recommend an 'NSMutableDictionary' that lives permanently in your controller, since dictionaries provide named access to their content.
This way, if you want to create a field called "Foo", your -createAndStoreFieldWithName: method would check to see if an entry named Foo already exists in the fieldsDictionary dictionary and, if not, would create the field (and add it to its superview, positioned wherever you wish) in code and call -[fieldsDictionary setObject:newlyCreatedField forKey:fieldName] to store it. That way, you can always get the field by name by asking -[fieldsDictionary objectForKey:desiredFieldName].
If you later want to remove them, use the same "get the field by name" approach to access the field, remove it from its superview, and remove it from the dictionary so it can be disposed of properly.
Of course if multiple fields with the same name can exist (ie, more than one Foo field), you'll need to add a layer of abstraction. In this case, you can use a unique identifier (like a UUID) the user never sees. The thing to figure out (which is hard to specify without more detail from you) is how you'll match the identifier to the proper field (given the possibility of multiple Foo fields, for example).
Also, you could just add the fields to the superview and loop through its -subviews array to locate fields by their label but this is anti-pattern to proper MVC design. Your controller object (the one creating the labels and adding them to other views) should be keeping track of these fields as I mentioned above, not the superview. This lets your controller be the intelligent mediator, since it knows what the fields are for and will use them (or possibly hand them off to some other controller) as it needs.
Situation-Specific Answer
BUT - consider whether individual UITextFields are the right way to go. Perhaps a UITableView with added rows is the better choice here? In this case your controller just decides there's one more row and kicks the table view to update. When asked about the cell, just give it your desired label from some labels container (maybe just a straight NSMutableArray of labels maintained by your controller - it matches array index to row index). MUCH simpler.
More detail in your question will get you more specific answers. Again, though, edit your original question - don't keep appending comments.
I assume you have a buttonPressed: method? Your question is not very clear, so if you'd like to clarify, I'm sure we can provide you with a better answer. Assuming you have a textField named inputField as a property of your view controller, you could do something like this:
- (void)buttonPressed:(id)sender {
NSString *currentText = self.inputField.text;
UITextField *newTextField = [[UITextField alloc] initWithFrame:CGRectMake(10, 10, 100, 30)];
newTextField.placeholder = currentText;
newTextField.borderStyle = UITextBorderStyleRoundedRect;
[self.view addSubview:newTextField];
}
This would create a new UITextField upon button press with its placeholder text set to whatever was in your other UITextField. This is assuming you just want to do this once and that you only have a single new UITextField. If you can do this multiple times, but still just want a single UITextField updated with new text, newTextField should be a property, and you check if it is not nil and alloc init if it is. If it's not nil (it already exists), you need only update the text/placeholder property with the new text.
If every time the user enters text, you need a new UITextField in a new location, the solution would be a little different. Again, all of these answers (mine and others) are making assumptions about what you are trying to achieve.
UPDATE: Allow me to update this answer appropriately, as was previously pointed out. The long-and-short of it would be to use #JoshuaNozzi's approach, as it will achieve what you're trying to do. Just updating this for completeness.

How to record all of the user's touches in iPhone app

note: This is an expansion (and clarification) of a question I asked yesterday.
I am conducting a research project where I want to record all of the user's touches in the iPhone app. After the experiment, I will be able to download the data and process it in either Excel or (more likely) Matlab and determine how many times they clicked on certain buttons, when they clicked certain buttons, etc. To do this, I would need to know:
a) When they touched
b) Where they touched
c) Which view they touched
The first two are easy, but the third I am having trouble with. I know I can do this to get the reference to the UIView that was touched:
CGPoint locationPoint = [[touches anyObject] locationInView:self];
UIView* viewYouWishToObtain = [self hitTest:locationPoint withEvent:event];
However, that will just give me a pointer to the view, not the name of the view that was touched. I could assign each view a tag, but then every time I create a new view I would need to remember to tag it (or, alternatively, log the address of each view when initialized and log it when the view is touched). Subclassing UIView and adding an automatic tag isn't really an option since I'm creating other UIButtons and UISliders and would need to subclass those also, which doesn't seem like a very good solution.
Does anyone know of a clean, easy way to do this?
For "Which view they touched", what information do you need?
Perhaps you could use a category to add a method to UIView. This method would generate a string containing information about the view. Such as:
its type e.g. UIButton etc.
its size and position
the title of the view, if it has one (e.g. the button title)
the parent view type and title
other stuff e.g. is the view enabled, what state it is in. anything you like.
For example: "Type:UIButton Title:"Back" Rect:{3,5,40,25}" or some such string.
This is very clean and gives you quite a lot of information to be going with.
You could add a category to UIView which would then be inherited by all UIView descended objects, although I'm not sure its any more efficient than tagging. Since a category can override methods then you could override init methods for automatic tagging I suppose.
http://macdevelopertips.com/objective-c/objective-c-categories.html
I'm not sure what you mean by the "name" of the view. If you mean the view name in Interface Builder, I don't believe it includes that in the instantiated objects. You could use the Tag attribute which is included, but that's just a number and not a name.

Resources