I am trying, programmatically, to add subviews to my view. The process goes as follows:
1) Fetch an array of medias from the web
2) If it is a picture, build a UIImageView and add it to the bottom of the view. If it is text, build a UITextView and add it to the bottom of the view
3) Repeat step 2 until the end of the array
The problems:
How do I create the views if I don't know what will be the height of them? It seems that to add a view I need to specify its frame.
How do I add each view to the bottom of the already existing views?
Thanks in advance,
Tiago
If you don't know the size of the view, call the sizeToFit and layoutIfNeeded method after adding the view. Here's the code:
Objective-C :
[superView addSubview: view];
[view sizeToFit];
[view layoutIfNeeded];
Swift :
superView.addSubview(view)
view.sizeToFit()
view.layoutIfNeeded()
You can always create the view with a zero-sized frame (CGRectZero) and set its frame later once you know it.
I think you can use a UITableView to implement this feature. The tableView is normally used for displaying 'N' number of items. You can create two custom cells,
1 for displaying an Image
2nd for displaying a textview.
And then based on the item type of the Array you can reuse these custom cells.
I'm assuming you are looking to initialize and then add the view as a subview. If so, you can do the following:
UIView *yourView = [[UIView alloc] initWithFrame: CGRectZero];
//Note - CGRectZero is equivalent to CGRectMake(0,0,0,0)
[superView addSubview: yourView];
Now when you are ready to resize the view, you can do so as follows:
[yourView setFrame: CGRectMake(newXOrigin, newYOrigin, newWidth, newHeight)];
or alternatively
[yourView sizeToFit];
So I've figured an answer to both my questions:
How do I create the views if I don't know what will be the height of them?
After getting the content from web, call .layoutIfNeeded() to lay out the view. You are now able to use the contentSize of the view to draw it. See example below.
How do I add each view to the bottom of the already existing views?
2 ways:
1) Use a UITableView, easier and more reliable
2) Have a variable to hold the bottom of the stack of views, and update it every time you add a new view. Use this variable to decide where to add a new view.
Here's the code with 2):
var yAxis: CGFloat = 0
var textContent: String = "" {
didSet{
var textView = UITextView()
textView.text = textContent
textView.layoutIfNeeded()
textView.frame.size = CGSizeMake(UIScreen.mainScreen().bounds.width, textView.contentSize.height)
textView.frame.origin.y = yAxis
yAxis += textView.contentSize.height
view.addSubview(textView)
}
}
Thanks to #Dhruv Ramani for the hint.
Related
I need to implement the ability to display a label perfectly centered on screen with nothing else visible, but this needs to be done in a UITableView. The setup is a UISpiltViewController that has a UITableViewController for the detail view controller, and when no item is selected on the left I want to display a message stating that on the right, and when the user selects an item that label should instantly disappear and reveal the table. (Just like the Mail app.)
I already have this set up and it's working ok, but for some reason it's not always staying centered on screen, and it isn't a very good solution - there are some minor oddities for example you can partially see the top of the table while the rotation is occurring. I am just creating a UILabel, setting its frame to fill the visible area, then setting the table's tableHeaderView to that label, and finally disabling the ability to scroll the table. And upon rotation, the frame has to be updated to fill the visible area again. That's where the oddities occur, because it's not updating until after the rotation completes.
My question is, what is a better approach to implement this behavior? Is there some way I can prevent having to update the frame after rotation, would it be possible to use Auto Layout for the tableHeaderView?
//Setting the tableHeaderView
self.tableView.tableHeaderView = self.label;
//Creating the UILabel
- (UILabel *)label {
_label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.tableView.bounds.size.width,
self.tableView.bounds.size.height
- self.navigationController.navigationBar.frame.size.height
- self.tabBarController.tabBar.frame.size.height
- MIN([UIApplication sharedApplication].statusBarFrame.size.height,
[UIApplication sharedApplication].statusBarFrame.size.width))];
_label.text = #"Nothing Selected";
_label.textAlignment = NSTextAlignmentCenter;
_label.backgroundColor = self.tableView.backgroundColor;
return _label;
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
if (_label) {
_label.frame = CGRectMake(0, 0, self.tableView.bounds.size.width,
self.tableView.bounds.size.height
- self.navigationController.navigationBar.frame.size.height
- self.tabBarController.tabBar.frame.size.height
- MIN([UIApplication sharedApplication].statusBarFrame.size.height,
[UIApplication sharedApplication].statusBarFrame.size.width));
self.tableView.tableHeaderView = nil;
self.tableView.tableHeaderView = _label;
}
}
Is your detail controller a UITableViewController? If so, that makes things harder since any subviews you add (your label) become part of the table. It would be easier if you use a UIViewController, and alternately hide the label or table view when you need to. The label can be any size, and use centerX and centerY constraints to keep it centered. If you do it that way, you won't have to do anything on rotation.
You don't have to set the label as the headerView of your UITableView.
You can simply add the label to self.view. Even if you are running inside a UITableViewController, each UIViewController always has a view property.
Add the label to self.view and toggle the visibility of the label and the tableView as needed.This is much easier, than trying to fiddle the label in the tableview - hierarchy ;)
EDIT
As pointed out by #rdelmar, the UITableViewController indeed does not have a separate view which contains the tableview, but rather uses the tableview as it's view directly. -.-
Sorry. I did assume the two views were separated.
All I can say is follow #rdelmar s advice, and stop using UITableViewController. It forces you to do things the default way, and if you want to customized it you WILL have a bad time :(
EDIT 2
Ok so you have 2 options:
1) Do the following:
[_label setAutoResizingMask:(UIViewAutoResizingFlexibleWidth | UIViewAutoResizingFlexibleHeight)];
in your label getter. That way, you don't have to care about screen rotation anymore. The label will always fit its parents bounds.
2) Use UIViewController and treat the label as a sibling of the tableView. <- Better approach but requires more refactoring from your current state.
As pointed out by Cabus, the solution is to set the autoresizingMask of the label to UIViewAutoResizingFlexibleWidth | UIViewAutoResizingFlexibleHeight. That way, the label will always fit its parent. This can be done while using the label for the tableHeaderView, and there's no longer a need for detecting orientation changes.
I want to create a scrollView having many sub views as shown in image.
All views having a labels & image within it.
Number of views add in scrollView are dynamic.
And data of that views is also dynamic.
So that I can't make a static view in program and use it for display.
I want to make scrollview's subview like TableView with custom cells.
Like make a object of that TableViewCell and use it.
Can I use ViewController for that?
If i understood you question true, you need something like dynamic content of scrollView. So you need an array to control how many cell you will put into scrollView and create label, imageView or whatever you need.
For example like that;
//You will need to clean your scrollView Content in everytime
for(UIView *view in [yourScrollView subviews]){
[view removeFromSuperview];
}
for(int i=0;i!=[yourArray count];i++)
{
labels[i]=[[UILabel alloc]init];
//anyInteger is about your views place.
views[i]=[[UIView alloc]initWithFrame:CGRectMake(21, i*anyInteger, 300, 50)];
views[i].backgroundColor=[UIColor colorWithRed:0.1 green:0.2 blue:1.8 alpha:0.0];
[views[i] addSubview:labels[i]];
[yourScrollView addSubview:views[i]];
}
These codes will help you about insert objects in yourScrollView. I didnt test this yet but i guess it will give you an idea.
I've been messing around and trying to make my own messaging application (for practice).
I'm adding a custom UIView to a standard UIScrollView based on an IBAction.
Each time the user presses a button, I do the following (in order):
Enumerate the subviews in the scrollView and increase the value of an
integer. I use this int to set the y-position of the new custom
view
Create a custom UIView with a frame size that varies only in width. The view has a UILabel on top of it that has the text.
Create a new CGSize that I use to set the contentSize of the
UIScrollView. The new CGSize is the width of the scrollView + the
height of the custom view + some padding.
I then set the contentSize of the scrollView.
I then add the custom view as a subview of the scrollView.
I created a timer that adds a new message/subview every two seconds to test multiple messages and the issue I am seeing is that once the subviews are added outside the viewable area of the UIScrollView, they stop being added. They are only added once I bring the messages back into the viewable area, and even so, they are added overlaying at times.
Now, I am aware of a few things: I need to add the subviews on a separate thread since they won't be added while the user is scrolling. I know that I will need to "clean up" subviews as they build up since my app will keep eating memory as more subviews are added. I am also aware that I should reposition the UIScrollView as messages are added since it doesn't make sense to add them outside of the viewable area. Finally, I don't think enumerating the subviews is very elegant... so I will change that later... nor is the temp UILabel I make... Now for some code:
- (void)pushMyMessage:(NSString *)message
{
UILabel *tempLabel = [[UILabel alloc]init];
tempLabel.text = message;
[tempLabel sizeToFit];
//adding the new message as a subview below any previous messages
if (self.messageView.subviews.count > 0)
{
int yIndex = 0;
for (SDMessageView *view in self.messageView.subviews)
{
//increase the y value for the frame of the next message
yIndex += view.frame.size.height;
//add a little padding
yIndex += 5;
}
SDMessageView *newMessage = [[SDMessageView alloc]initWithFrame:CGRectMake(20, yIndex, tempLabel.frame.size.width + 8, tempLabel.frame.size.height) setMessage:message];
CGSize newSize = CGSizeMake(self.messageView.frame.size.width, self.messageView.contentSize.height + newMessage.frame.size.height + 30);
[self.messageView setContentSize:newSize];
[self.messageView addSubview:newMessage];
}
else
{
//initial message
SDMessageView *newMessage = [[SDMessageView alloc]initWithFrame:CGRectMake(20, 20, tempLabel.frame.size.width + 8, tempLabel.frame.size.height) setMessage:message];
CGSize newSize = CGSizeMake(self.messageView.frame.size.width, self.messageView.contentSize.height + newMessage.frame.size.height + 30);
[self.messageView setContentSize:newSize];
[self.messageView addSubview:newMessage];
}
}
Any ideas on why the subviews are only added while in view? Also, any ideas on how I can do this efficiently?
Thanks for any help in advance!
Few things I noticed while reading your post:
You should not make any UI interface changes (like adding subviews) on secondary thread, this should be done on main thread.
Why don't you use UITableView for displaying messages? If you use table view, you don't have to calculate all the frames, content sizes and etc., just need to give correct height to the cell, and this would efficient way of solving what you are trying to achieve.
I didn't really understand, why your subviews are not added to the hidden area of the scroll view, in my practice, I didn't notice such a behaviour of the scroll view (I used it a lot), there should be some other issue with your code (are you adding subviews on secondary thread?).
Hope this was helpful, Good Luck!
I have a UIScrollView which contains a UIView and a UITableView. My goal is to adjust the height of the UIScrollView to allow me to scroll the contents of the UIScrollView to a specific point.
Here is my view: It has a UIView up top and a UITableView down below.
When I scroll, I want the UIView to stop at a specific point like so:
The tableView would be able to continue scrolling, but the UIView would be locked in place until the user scrolled up and brought the UIView back to its original state.
A prime example of what I am trying to do is the AppStore.app on iOS 6. When you view the details of the app, the filter bar for Details, Reviews and Related moves to the top of the screen and stops. I hope this all made sense.
Thanks
I ended up going with a simpler approach. can't believe I didn't see this before. I created two views, one for the UITableView's tableHeaderView and one for the viewForHeaderInSection. The view I wanted to remain visible at all times is placed in the viewForHeaderInSection method and the other view is placed in the tableHeaderView property. This is a much simpler approach, I think than using a scrollview. The only issue I have run into with this approach is all my UIView animations in these two views no longer animate.
Here is my code.
[self.tableView setTableHeaderView:self.headerView];
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
return self.tableViewHeader;
}
add yourself as a UIScrollViewDelegate to the UITableView and implement the - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView so that if your views are in their starter positions they do this:
- your UITableView animates its size to the second state:
[UIView animateWithDuration:.1f animations:^{
CGRect theFrame = myView.frame;
theFrame.size.height += floatOfIncreasedHeight;
myView.frame = theFrame;
}];
- your UIView animates its vertical movement
[UIView animateWithDuration:3 delay:0 options:UIViewAnimationOptionCurveLinear animations:^(void){
view.center = CGPointMake(view.center.x , view.center.y + floatOfVerticalMovement);
}completion:^(BOOL Finished){
view.center = CGPointMake(view.center.x , view.center.y - floatOfVerticalMovement);]
Finally always in the delegate implement – scrollViewDidScrollToTop: so that you know can animate back to the initial state (using the same techniques reversed).
UPDATE:
since your views are inside a scroll view, there is a simpler way if you are ok with the table view being partly out of bounds in your starter position (i.e. instead of changing size it just scrolls into view):
make the scroll view frame size as big as your final tableview + your initial (entire) view and place it at 0,0 (so its final part will be hidden outside of the screen)
scrollview.frame = CGRectMake(0,0,tableview.frame.size.width,tableview.frame.size.height + view.frame.size.height);
you make the container scrollview contents as big as the entire table view + the entire view + the amount of the view that you want out of the way when scrolling the table view.
scrollview.contentSize = CGSizeMake(scrollview.frame.size.width, tableview.frame.size.height + view.frame.size.height + floatOfViewHeightIWantOutOfTheWay);
you place the view one after the other in the scrollview leaving all the additional empty space after the table view
view.frame = CGRectMake(0,0,view.frame.size.width, view.frame.size.height);
tableview.frame = CGRectMake(0,view.frame.size.height, tableview.frame.size.width, tableview.frame.size.height);
now it should just work because since iOS 3 nested scrolling is supported
You can easily achieve this by setting the content size of the scrollView correctly and keep the height of the UITableView smaller than your viewcontroller's height, so that it fits the bottom part of the top UIView and the UITableView...
Another scenario is to split the top View in 2 parts.
The part that will scroll away and the part that will be visible.
Then set the part that will scroll away as the entire UITableView header and the part that will remain visible as the header view for the first table section.
So then you can achieve this with a single UITableView, without having to use a UIScrollView
What you're looking for is something like what Game Center happens to do with it's header which can actually be modelled with a table header, a custom section header view, and some very clever calculations that never actually involve messing with the frame and bounds of the table.
First, the easy part: faking a sticky view. That "view that's always present when scrolling the table" implemented as a section header. By making the number of sections in the table 1, and implementing -headerViewForSection:, it's possible to seamlessly make the view scroll with the tableview all for free (API-wise that is):
- (UITableViewHeaderFooterView *)headerViewForSection:(NSInteger)section {
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0,0,320,50)];
label.text = #"Info that was always present when scrolling the UITableView";
label.textAlignment = UITextAlignmentCenter;
label.backgroundColor = [UIColor colorWithRed:0.243 green:0.250 blue:0.253 alpha:1.000];
label.textColor = UIColor.whiteColor;
return label;
}
Finally, the hard part: KVO. When the table scrolls, we have to keep the header up there sticky with regards to the top of the view's frame, which means that you can KVO contentOffset, and use the resultant change in value to approximate the frame that the view should stick to with a little MIN() magic. Assuming your header is 44 pixels tall, the code below calculates the appropriate frame value:
CGPoint offset = [contentOffsetChange CGPointValue];
[self.tableView layoutSubviews];
self.tableView.tableHeaderView.frame = CGRectMake(0,MIN(0,offset.y),CGRectGetWidth(self.scrollView.frame),44);
If the above is infeasible, SMHeadedList actually has a fairly great, and little known, example of how complicated it can be to implement a "double tableview". That implementation has the added benefit of allowing the "header" tableview to scroll with the "main" tableview.
For future visitors, I've implemented a much simpler version, albeit one that accomplishes the goal with Reactive Cocoa, and a little bit of a different outcome. Even so, I believe it may be relevant.
What if you break the UIView into the top and bottom. The bottom will be the info.
Set UITableView.tableHeaderView = topView in viewDidLoad
and the return bottomView as Section Header in delegate method to make it float:
(UITableViewHeaderFooterView *)headerViewForSection:(NSInteger)section
{
return bottomView;
}
Just using the UITableView can solve with your problem. it is not need to use another scroll view.
set your view as the header view of UITableView. Then add your present view to the header view.
complete - (void)scrollViewDidScroll:(UIScrollView *)scrollView; . Tn the function to check the contentoffset of scroll view, and set the present view's frame.
I wrote a code with a UILabel with the name of a meteorological station. Later, I added the code to put a UITableView with a grid view like this website explains http://www.recursiveawesome.com/blog/2011/04/06/creating-a-grid-view-in-ios/
The problem is that now the Table view is shown in all screen and the label can't be seen.
How do I make to put the elements in this order?
UILabel
UITableView
Thanks!
1 With a nib:
If you use a nib you can simply size / layout your label and table view so that they are positioned as desired. A UITableView can be made any size and take up any portion of the screen.
2 Without a nib
Create / alloc / initialize your label and table, and then add them to the view:
[self.view addSubview:myTableView];
[self.view addSubview:myLabel];
The magic step to this is that you need to set the frame of both your label and table view. Thats something that is really custom so I cant help you with that without more direction however it may look something like this:
// the number 10 is used for padding purposes
CGSize labelWidth = CGSizeMake(self.view.frame.size.width-20, 1000.0f);
CGSize textSize = [myLable.text sizeWithFont:myLable.font constrainedToSize:labelWidth lineBreakMode:UILineBreakModeWordWrap];
myLable.frame = CGRectMake(10, 10, labelWidth.width, textSize.height);
myTableView.frame = CGRectMake(0, myLable.frame.origin.y+10, self.view.frame.size.width, self.view.frame.size.height - (myLable.frame.origin.y+10));
Please note that calculating frames can be done MANY ways. Also you will probably have to recalculate the frames manually for rotations.
Hope this helps Good Luck!
Depending on the answer to my questions in the comments, there are several answers.
If you want the label to always be on top whether or not the tableview is scrolled, make sure you are using an instance of UIViewController, then create your two views. Set the frame appropriately and then add the label view and the table view as subviews to the main view.
If you want the label to scroll away, that's even easier. Your UIViewController can remain a subclass of UITableViewController. UITableView has a property called tableHeaderView. myTableView.tableHeaderView = labelView;
You could to like Rachel said
[self.view addSubview:myTableView];
[self.view addSubview:myLabel];
or after placing it
[self.view bringSubviewToFront:myTableView]