I have two questions related to UITableViews.
1) The first one is, what is the gap at the top of the UITableView? My app always starts with the top cell not flush with the top of the tableview (as shown in the second image), it starts about one cell lower, i.e. where that gap is in the interface builder. I can't find where that is coming from, or why.
2) I can't seem to resize the uitableview programmatically, I'm trying to reduce the height after a popup appears. I've shown an example of it not working in the second picture.
Here is (an example of) what I am trying at the moment:
- (void)viewDidLoad
{
[super viewDidLoad];
self.table_view.delegate = self;
CGRect tableBounds = self.table_view.bounds;
tableBounds.size.height -= 100;
self.table_view.bounds = tableBounds;
CGRect tableFrame = self.table_view.frame;
tableBounds.size.height -= 100;
self.table_view.frame = tableFrame;
}
Thanks!
UITableView Selected:
Simulation:
In your xib (or storyboard) put your UITableView at position (0,0). ( the same position as the navigation bar).
The first image shows that your table view has problems even in Interface Builder. It looks as if you've set the top inset incorrectly; check your edge insets.
The reason your resizing code is not working is probably that it is too early (viewDidLoad); put it in viewDidAppear: and see if that works, and if it does, try moving it back to viewWillAppear: so the user doesn't see the resizing. If it doesn't work, it might be because you're using auto layout; you can't manually alter the frame of something whose frame is dictated by auto layout. (Your resizing code is also silly; you want to set the frame, not the bounds.) But it might also be because you're using a UITableViewController in a UINavigationController; if you do that, the table view is under the navigation controller's direct control and its size is not up to you.
Related
I'm loading a view from a storyboard programatically, but there's an issue with my subview heights (I'm using Autoresize Subviews etc).
When the view loads, the main UIView appears to be the correct size, but the subviews that depend on the constraints to dynamically calculate their final height don't seem to be resizing from the sizes they were set at in interface builder.
Loading the view like so:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
ApplicationRecordingViewController* recordingController = (ApplicationRecordingViewController*)[storyboard instantiateViewControllerWithIdentifier:#"recordView2"];
[self presentViewController:recordingController animated:NO completion:nil];
Orange area's height is from top of main view to top of grey area. Grey area is fixed height
The strange thing is, if I set the simulated size in interface builder as the correct view for my phone, the sizes work out. I guess the initial heights for subviews are 'baked' into the view, but then (usually) resized when the view actually loads?
Note: The orange area should be completely filled by the camera view I'm loading into it.
Incorrect height
Correct height if I change simulated layout
Would love to know what I'm doing wrong!
Edit 1: Constraints
Camera is the orange bit. The Camera bottom constraint is currently set as the bottom of the view + enough room for the gray bar. Have also tried setting the bottom to match the top of the gray bar!
Link to Storyboard with Scene
I know you're all better than me, but I experienced the same in my last project. My Camera View inside a UIView is not in full screen or resizing and fitting the whole UIView, and I also posted it on my daily blog.
Implement viewDidLayoutSubviews -- Inside that method, layout again your video preview layer.Do not alloc init anything inside that method, just re-layout your views. After that, your video preview will fill the height and width of its parent, or whatever you've been targetting.
The issue also has something to do with the hierarchy of the constraints and views.
You already got the answer.
'if I set the simulated size in interface builder as the correct view for my phone, the sizes work out.'
You may call some functions to get height in viewDidLoad or viewWillAppear.
Change Code like this
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
CGFloat height = [self getHeight];
}
or
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CGFloat height = [self getHeight];
}
As i can see in your storyboard constrain you have set bottom space to bottom layout guide 150 . i think this is the issue . if you give fixed height to bottom view then no need to set constant as from bottom layout guide --> 150 .
Your old constrain :- >
Just set bottom space of orangeview from greenview = 0.
Check new constrain ,
Here is your storyboard link ,
Storyboard Download link
Here is your output,
After investigating the layout a little more, I found the camera view (orange) seemed to be the correct size, but the live camera preview wasn't.
Solution was to move the camera library (SCRecorder)'s init method into viewDidAppear (was in viewDidLoad before).
My thinking is iOS doesn't autolayout the camera preview view in the same way as normal UIViews, etc.
#interface ApplicationRecordingViewController () {
SCRecorder *_recorder;
}
#end
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
_recorder.previewView = _previewView;
}
Perhaps you can try to get rid of these two hook.
I have the following layout in my view controller. I want to be able to scroll vertically with the header scrolling off the view and the UISegmentedControl sticking to the top of the view, beyond that the remaining scroll should be handled by the Collection View.
However I'm a bit confused as to what is the best approach to implemented this layout.
I tried a few implementations with mixed results:
UIScrollView with UICollectionView as subviews: UIScrollView as the parent view with the header, segmented control and collection views as child controls. The problem with this approach is that the nested scrolling does not seem to work correctly. To be able to scroll the UIScrollView the tap needs to be outside the CollectionView area otherwise only the CollectionView scrolls and the header and segmented control don't move.
Header and Segmented Control in Header cell: I tried another approach by using a single CollectionView. I added the header and Segmented Control as subviews of a single Header cell of the collection view. When the segmented control value was changed, I switch the data source property of the CollectionView to achieve the 3 views required for the collection view. Visually everything works perfectly. The only problem here is the race condition when switching quickly between first,second and third tabs. I load the data from a web service, if the web service takes time and is still loading the data and I quickly switch the tabs then I run into bugs where the data returned is for a different collection view than what is currently selected, a lot of out of order sync issues.
Update constant value for Autolayout Constraint: Another approach I tried is to change the constant value of the auto layout constraint applied to "Header" view. Then I added a gesture to the view controller's view to track the scroll, as the user scrolls vertically I adjust the constant of the auto layout constraint so that the "header" cell pops out of view. Again this doesn't seem to work that smoothly, but I suppose I can tweak it, but it seems sort of a hack.
Is there a better way to implement this layout?
#2 seems like a good solution — the scrolling gestures will be most consistent with what users expect, since it's all a single scroll view. (I agree that #3 sounds like a hack.) You can make the header "sticky" with some custom layout attributes.
The only problem here is the race condition when switching quickly between first, second and third tabs.
This is a common problem with asynchronous loading when views are being switched out (especially when you are loading data into individual cells, which are being reused as you scroll). It is important that upon receiving the data you always check whether the receiver is still expecting it; i.e., you should check the segmented control value before changing the backing data source. You could also:
Use separate data source objects for the different segments, having each one manage its own data fetching so they can't get mixed up.
Cancel the outstanding requests, if you can, when quickly switching tabs, to avoid unnecessary network requests.
Cache data to avoid re-fetching every time you switch tabs.
I think you want the same functionality that pinterest profile page have. To implement such functionality at easy way, you need to do following things.
Step 1 : Add UIView as tableHeaderView those who showing off while scrolling up.
self.tableHeaderView = yourView
Step 2 : Add UISegmentControl in section header view.
- (UITableViewHeaderFooterView *)headerViewForSection:(NSInteger)section{
return your_segmentcontrolView;
}
Step 3 : Add UICollectionView into first row of first section.
By implementing following way, you can got your desire functionality.
Hope this help you.
An alternative approach you could consider:
Use a UITableView to contain your UI
Create a UITableView, and set your header as the UITableView's headerView.
Use a sectionHeader to contain the segmentedControl.
Place your collectionView inside of a single UITableViewCell. Or alternatively, you may be able to use the UITableView's footerView to contain the gridView.
By using the sectionHeader, this should allow the header to scroll out of view, but then the sectionHeader will stick below the navigationBar or top of the contentView until another section comes into view (and in your case you will only have one section.)
Add Header View, Body View (Holding Segment View & Collection View) into scroll view.
Initially set userInteractionEnabled property to "NO" for collection view.
Track the insect of scroll view always.
If the y-coordinate of the scrolled insect is more than the height of header view, then set userInteractionEnabled property to "YES" so that thereafter collection view can be scrolled.
If user scroll outside the scroll view and try to bring the header view down, i.e Scroll view y-coordinate insect is less than the height of header view, then immediately change the user iteration mode of collection view and allow user to scroll the scroll view till the top.
Rather than implementing this by hand, you could use a library/cocoapod to set this up for you. This one looks like a pretty good fit: https://github.com/iosengineer/BMFloatingHeaderCollectionViewLayout
Plus, the code is open-source, so you can always modify as needed.
All I can say is that you need to subclass UIView and make it a delegate of UIGestureRecognizerDelegate and UICollectionViewDelegate, then in your UIView subclass, do the following, I can't give out anymore information on this because the code, although owned by myself, is proprietary to the point of probably enraging quite a few organizations that I've used this for, so here's the secret sauce:
CGPoint contentOffset = [scrollView contentOffset];
CGFloat newHeight = [_headerView maxHeight] - contentOffset.y;
CGRect frame = [_headerView frame];
if (newHeight > [_headerView maxHeight]) {
frame.origin.y = contentOffset.y;
frame.size.height = [_headerView maxHeight];
[_headerView setFrame:frame];
} else if (newHeight < [_headerView minHeight]) {
frame.origin.y = contentOffset.y;
frame.size.height = [_headerView minHeight];
[_headerView setFrame:frame];
} else {
frame.origin.y = contentOffset.y;
frame.size.height = newHeight;
[_headerView setFrame:frame];
}
if ([_delegate respondsToSelector:#selector(scrollViewDidScroll:)]) {
return [_delegate scrollViewDidScroll:scrollView];
}
You must subclass another UIView that is defined as the header for this custom UiCollectionView. Then, you must declare the UIView custom header view inside the custom subview of the UIView/UICollectionView delegate, and then set the header of that custom subview inside the UICollctionViewdelegate. You should then pull in this compounded subclass of UIView/UIcollectionView into your UIViewController. Oh yes, and in your layoutSubViews, make sure you do the height calculations that are passed through a double layered subclass. So, you will have the following files:
UIVew this is the delegate of UICollectionView and what I mentioned before
UIView this is a UISCrollViewDelegate and this is the header view
UIViewController that pulls in the subclassed UIView in number 1
UIView subclass of number 1 that pulls in number 2 and sets it as its header
In the number 4 part, make sure you do something like this:
- (CGFloat)maxHeight
{
if (SCREEN_WIDTH == 414)
{
return 260;
}else if (SCREEN_WIDTH == 375)
{
return 325;
}else
{
return 290;
}
}
- (CGFloat)minHeight
{
if (SCREEN_WIDTH == 414)
{
return 90;
}else if (SCREEN_WIDTH == 375)
{
return 325;
}else
{
return 290;
}
}
This will then pass through to the UIView subclass that is a compounded subclass as I already explained. The idea is to capture the maxHeight of you header in the subclass of this header UIView (number 2 above), and then pass this into the main UIView subclass that intercepts these values in the scrollViewDidScroll.
Last tidbit of information, make sure you set up your layoutSubviews in all methods to intercept scroll events. For example in number 1 above, the layoutsubviews method is this:
- (void)layoutSubviews
{
CGRect frame = [_headerView frame];
frame.size.width = [self frame].size.width;
[_headerView setFrame:frame];
[super layoutSubviews];
}
This is all I can give you, I wish I could post more, but this should give you an idea of how it's done in production environments for the big time apps you see out in the wild.
One more thing to note. When you start going down the road of intense implementations like this, don't be surprised to learn that, for example, a single view controller in an app that works with methods like I've explained will have anywhere from 30-40 custom subclasses that are either subclasses in their own right or compounded subclasses or subclasses of my own subclasses or my own subclasses. I'm telling you this so you get an idea of how much code is required to get this right, not to scare you, but to let you know that it might take a while to get right, and to not kick yourself in the butt if it takes awhile to make work. Good luck!!
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 have tableview and several controls (text fields, buttons and labels) beneath it.
When keyboard shows up I can either reduce parent view height or slide it up so my controls are accessible.
That part works fine.
However I want to adjust tableview also, so I tried reducing its height (if I reduce the height of the parent view) or moving down the origin.y coordinate (if I slide parent view up).
Neither worked, i.e. tableView would not change to the new frame, it stays the same. Tableview only resizes if I do not manipulate parent view, i.e. if I adjust tableView frame alone.
Here is the methods for that:
-(void)resizeTbl:(int)pnts{
CGRect tblframe = self.myTable.frame;
//tblframe.origin.y+=pnts;
tblframe.size.height-=pnts;
self.myTable.frame =tblframe;}
-(void )keyboardWillShow:(NSNotification *)notif{
int pnts=160;
CGRect frame = self.view.frame;
//frame.origin.y-=pnts;
frame.size.height-=pnts;
self.view.frame = frame;
[self resizeTbl:pnts];}
I probably could move all the controls up one by one and then table resize would work but I think there should be an easier way. Any advice would be greatly appreciated.
Update/workaround:
Since table resize works alone just fine, I added one more observer - keyboardDidShow, and moved resize myTable from keyboardWilShow into there to take care of tableview after keyboard is up.
-(void)keyboardDidShow:(NSNotification *)notif{
[self resizeTbl:160];}
So all the views come up as they are supposed to now.
However, when focus moves from one textbox to another with the keyboard already up, tableView resizes to its original frame by itself and I cannot catch when and how that does it. And then, when keyboard goes down tableView expands beyond its original frame, because I must have
-(void)keyboardDidHide:(NSNotification *)notif{
[self resizeTbl:-160];}
However, when focus changes again tableView shrinks back to its normal frame and everything looks just fine. If I could somehow prevent those unwanted tableview resizes that mess things up.
If someone could makes sense out all of this I would be very appreciative.
Update:
I got it. Autolayout was messing me up. I turned it off like that and my logic now works, no glitches.
What is the autoResizingMask of the view set to?
Try this when you initialize the table view.
self.myTable.autoresizingMask = UIViewAutoresizingFlexibleHeight;
This will cause the table view to be resized with its parent view.
You can't change self.view.frame. You should have a container view above self.view that holds self.myTable and all other controls you want. self.myTable and other controls should have the correct autoresizingMask.
When keyboard is shown, you resize the container view and all other views will respect their autoresizingMasks.
-(void )keyboardWillShow:(NSNotification *)notif{
int pnts=160;
CGRect rect = self.view.bounds;
rect.size.height-=pnts;
self.containerView.frame = rect;
// the following should not be needed if self.myTableView.autoresizingMask
// is at least UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleTopMargin
// but you can try adding this
self.myTableView.frame = self.containerView.bounds;
}
I have a UIView that has two child elements: a UIScrollView on the upper half (which contains two UILabels), and a UITableView at the bottom. This is basically a dictionary and the purpose of the scroll view is to display the word and definition, and the table view for displaying the related words. Not all words in my dictionary have a related words array associated to them, so I hide the UITableView when that array is empty.
However, I can't get the UIScrollView to fill the entire parent view when the UITableView is hidden. Here's what I've tried so far:
- (void)updateUIWithWord:(NSString *)theWord
andDefinition:(NSString *)theDefinition
andRelatedWordsArray:(NSArray *)theRelatedWordsArray {
self.navigationItem.title = theWord;
self.word.text = theWord;
self.definition.text = theDefinition;
self.relatedWordsArray = theRelatedWordsArray;
if (![relatedWordsArray count]) {
relatedWordsTableView.hidden = YES;
// set the UITableView's width and height to 0 just to be sure
// I feel this isn't needed though
CGRect relatedWordsTableViewFrame;
relatedWordsTableViewFrame.size = CGSizeMake(0, 0);
relatedWordsTableView.frame = relatedWordsTableViewFrame;
// then make the scroll view occupy the remaining height;
// that is, the self.view's actual height
CGRect scrollViewFrame;
scrollViewFrame.origin = CGPointMake(0, 0);
scrollViewFrame.size = CGSizeMake(self.view.frame.size.width, self.view.frame.size.height);
scrollView.frame = scrollViewFrame;
}
}
Simply put, this doesn't work. For any word that has no related words and a very long definition, the scroll view simply occupies the same amount of height even with the table view gone. Help?
ADD: I tried fixing the constraints in the UIScrollView to make it relative to the top of the UITableView instead of having a fixed height, but that doesn't seem possible.
You have an "if" and an "else". Only one of those is going to execute. So when the "if" part runs and relatedWordsTableView.hidden is set to YES, the table view is hidden but nothing else happens. The "else" part isn't running so nothing is happening.
Approached the problem in a different way. I made the UIScrollView occupy the whole screen and put the UITableView inside it, below my two labels, with scrollling disabled. Now I can just hide and show it.