In viewDidLoad of my child view controller (UITableViewController) I'm getting the contentSize.Height after calling layoutIfNeeded. Than I set the preferredContentSize with these values. In viewDidLoad of my container (which holds this child) I also set the preferredContentSize based on the child's preferredContentSize. This works on iOS 8 but not on iOS 7.
I know that the viewDidLoad of the child view controller is called after the viewDidLoad of the container.
How do I get the table view size of the child or how can I force that the child view has layout its subviews before the container has?
You can try sending the layout request after all views have loaded:
// In the container
-(void) viewDidLoad {
// ...
[self.view performSelector:#selector(setNeedsLayout) withObject:nil afterDelay:0];
}
I ended up with the following:
In the child view controller I created a separate method which takes the values for the table view. Afterwards I set the preferred content size.
public void setDocumentList(List<string> documentList){
this.documentList = documentList;
this.TableView.ReloadData ();
// adjust size for popover
float height = TableView.ContentSize.Height;
if (this.View.Bounds.Height > 0) {
// limit the max size of the popover
height = Math.Min (this.View.Bounds.Height, height);
}
this.PreferredContentSize = new SizeF (320f, height);
}
In my container I have a similar function, but here I only set the preferred content size.
public void setDocumentList(List<string> documentList){
documentListController.setDocumentList (documentList);
this.PreferredContentSize = documentListController.PreferredContentSize;
// some autolayout constraints ...
}
This kind of works. I get the correct values, but the values of the popover are not correct in the beginning. If someone is wondering this is the C# language. I'm still open for better solutions.
You can load the tableView of the child by calling viewDidLoad of the child in the parent, and forcing the tableview to layout.
// Parent.m
[Child view];
[self layoutParentViewsOrDoSomething];
// Child.m
-(void)viewDidLoad{
[super viewDidLoad];
[self layoutSubviews];
// or [tableView reloadData]...
}
Related
I am having a (UIView and UICollectionView in a UIScrollView) as 1stVC. I have to scroll both at the same time for which I already unable the collection view scrolling. So first time When I launch that screen I am able to scroll the whole view, But when I push to (next ViewController) 2ndVC and then press Back Button to 1stVC my scroll view is not preforming it got Freeze.
Tried this Method:-
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
dispatch_async(dispatch_get_main_queue(), ^ {
CGRect contentRect = CGRectZero;
for(UIView *view in self.scrollView.subviews)
contentRect = CGRectUnion(contentRect,view.frame);
self.scrollView.contentSize = contentRect.size;
});
}
Tried this Method:-
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
[_scrollView setContentSize:CGSizeMake(_scrollView.frame.size.width, _scrollView.frame.size.height)];
}
And For Getting Dynamic Height of the Collection view I have Done This
CGFloat height = _collectionCompass.contentSize.height;
Which I Have provided to ScollView ContentSize.
_scrollView.contentSize = CGSizeMake(_scrollView.frame.size.width, height);
Please help me.
What I really suggest it's to remove scroll view and do all stuff in collection view, since with scroll view you're missing some of benefits (e.g. cells reusing). Also collection view in scroll view it's a bad practice.
From Apple style guide:
Don’t place a scroll view inside of another scroll view. Doing so creates an unpredictable interface that’s difficult to control.
If your UI as list, you can add view as header of collection view or as another cell. With this approach you can remove the code from viewDidLayoutSubviews
When using auto layout, the view's size is unknown when it is initialised, this brings a problem to me.
When using UIImageView, I wrote a category that can load image from my own CDN by setting the image URL to UIImageView, my CDN stores one image with different sizes so that difference devices can load the size it really needs.
I want to make my UIImageView be able to load the URL for the resolution it needs, but when my UIImageView get the URL, the size of it is not yet determined by auto layout.
So is there a way for UIView to know that the layout process for it has finished for the first time?
there is a method for UIView.You could override it.
-(void)layoutSubviews {
CGRect bounds =self.bounds;
//build your imageView's frame here
self.imageView=imageViewFrame.
}
In Swift 5.X
override func layoutSubviews() {
super.layoutSubviews()
let myFrame = = self.bounds
}
If you have other complex items in the custom view, don't forget to call super if you override layoutSubviews()...
Sadly, there is no straightforward way that a UIView can be notified that its constraints have been set. You can try a bunch of different things though,
Implement layoutSubviews function of a UIView, this is called whenever UIView's layout is changed.
Implement viewDidLayoutSubviews of the UIViewController that has it inside it. This function is called when all the layouts have been set. At this point you can your category function.
here's a test, I only use autolayout and I only use custom subclassed subviews. I do all auto layout in the initializer of the subclass:
This is for a "login button" that has no frame but is then set with autolayout:
-(void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
if (self.contentView.loginButton.frame.size.height) {
NSLog(#"viewDidLayoutSubviews");
}
}
-(void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
if (self.contentView.loginButton.frame.size.height) {
NSLog(#"viewWillLayoutSubviews");
}
}
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.contentView.loginButton.frame.size.height) {
NSLog(#"didAppear");
}
}
-(void)viewWillAppear:(BOOL)animated {
if (self.contentView.loginButton.frame.size.height) {
NSLog(#"viewWillAppear");
}
}
-(void)viewDidLoad {
[super viewDidLoad];
if (self.contentView.loginButton.frame.size.height) {
NSLog(#"viewDidLoad");
}
}
Here's the output. So, this means that my view finally has a frame when the
2015-08-25 01:28:27.789 [67502:1183631] viewDidLayoutSubviews
2015-08-25 01:28:27.790 [67502:1183631] viewWillLayoutSubviews
2015-08-25 01:28:27.790 [67502:1183631] viewDidLayoutSubviews
2015-08-25 01:28:28.007 [67502:1183631] didAppear
This means that the first time the login button has a frame is in the viewDidLayoutSubviews, this will look weird becuase this is the first pass of main view's subviews. There's no frame in ViewWillAppear although the view of the viewcontroller itself is already set before the login button. The entire UIView subclass's main view is also set when the viewcontroler's view is set, this happens before the login button as well. So, the subviews of the view are set after the parent view is set.
The point is this: if you plop the imageview information pull in the viewDidLayoutSubviews then you have a frame to work with, unless you set this UIImageView frame to the view of the ViewController by type casting then you will have the UIImageView's frame set in the viewDidLoad. Good luck!
Using Storyboards and Autolayout, I have a UIViewController with a UIScrollView as the main view. I have several container views embedded in the scroll view. Some of those embedded container views contain UITableViews, each having cells of different heights. I'll need the tableView's height to be large enough to show all cells at once, as scrolling will be disabled on the tableView.
In the main UIViewController, container view's height has to be defined in order for the scroll view to work properly. This is problematic because there's no way for me to know how large my tableView will be once all it's cells of varying heights are finished rendering. How can I adjust my container view's height at runtime to fit my non-scrolling UITableView?
So far, I've done the following:
// in embedded UITableViewController
//
- (void)viewDidLoad {
// force layout early so I can determine my table's height
[self.tableView layoutIfNeeded];
if (self.detailsDelegate) {
[self.detailsTableDelegate didDetermineHeightForDetailsTableView:self.tableView];
}
}
// in my main UIViewController
// I have an IBOutlet to a height constraint set up on my container view
// this initial height constraint is just temporary, and will be overridden
// once this delegate method is called
- (void)didDetermineHeightForDetailsTableView:(UITableView *)tableView
{
self.detailsContainerHeightConstraint.constant = tableView.contentSize.height;
}
This is working fine and I was pleased with the results. However, I have one or two more container views to add, which will have non-scrolling tableViews, and I'd hate to have to create a new delegate protocol for each container view. I don't think I can make the protocol I have generic.
Any ideas?
Here's what I ended up doing:
// In my embedded UITableViewController:
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 60.0;
// via storyboards, this viewController has been embeded in a containerView, which is
// in a scrollView, which demands a height constraint. some rows from our static tableView
// might not display (for lack of data), so we need to send our table's height. we'll force
// layout early so we can get our size, and then pass it up to our delegate so it can set
// the containerView's heightConstraint.
[self.tableView layoutIfNeeded];
self.sizeForEmbeddingInContainerView = self.tableView.contentSize;
}
// in another embedded view controller:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.sizeForEmbeddingInContainerView = self.tableView.contentSize;
}
// then, in the parent view controller, I do this:
// 1) ensure each container view in the storyboard has an outlet to a height constraint
// 2) add this:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.placeDetailsContainerHeightConstraint.constant = self.placeDetailsTableViewController.sizeForEmbeddingInContainerView.height;
self.secondaryExperiencesContainerHeightConstraint.constant = self.secondaryExperiencesViewController.sizeForEmbeddingInContainerView.height;
}
I haven't done this yet, but it'd probably be best to create a Protocol with a property of CGSize sizeForEmbeddingInContainerView that each child view controller can adopt.
Here's what worked for me perfectly.
- (void)updateSizeBasedOnChildViews {
// Set height of container to match embedded tableview
CGRect containerFrame = self.cardTableContainer.frame;
containerFrame.size.height = [[[self.cardTableContainer subviews] lastObject]contentSize].height;
self.cardTableContainer.frame = containerFrame;
// Set content height of scrollview according to container
CGRect scrollFrame = self.cardTabScrollView.frame;
scrollFrame.size.height = containerFrame.origin.y + containerFrame.size.height;
// + height of any other subviews below the container
self.cardTabScrollView.contentSize = scrollFrame.size;
}
I have an iOS app in which I need to know when a new view is completely visible on-screen; that is, when Autolayout has finished its calculations and the view has finished drawing.
ViewDidAppear seems to fire well before the view is completely visible. If I turn off Autolayout, the timing seems to line up as far as human perception goes, but I need to use Autolayout in this project (so this isn't a solution...just a test).
Is there any method that fires when Autolayout is done calculating? Or another method that fires when the view is ACTUALLY visible (since ViewDidAppear doesn't work for this)?
Thanks!
The following can be used to avoid multiple calls:
- (void) didFinishAutoLayout {
// Do some stuff here.
NSLog(#"didFinishAutoLayout");
}
and
- (void) viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:#selector(didFinishAutoLayout)
object:nil];
[self performSelector:#selector(didFinishAutoLayout) withObject:nil
afterDelay:0];
}
I'm using viewDidLayoutSubviews for this. Apple's documentation says, "Called to notify the view controller that its view has just laid out its subviews."
If you watched 2018's WWDC about "High-Performance AutoLayout", you would know the answer to this question.
Technically, there is no such API method that will be called when autolayout has completed your view's layout. But when autolayout has completed the calculations, your view's setBounds and setCenter will be called so that your view gets its size and position.
After this, your view's layoutSubviews will be called. So, layoutSubviews can, to some degree, be thought of as the method that fires after autolayout has done calculations.
As to view controller's viewDidLayoutSubviews, this is a bit complicated. The documentation says:
When the bounds change for a view controller's view, the view adjusts the positions of its subviews and then the system calls this method. However, this method being called does not indicate that the individual layouts of the view's subviews have been adjusted. Each subview is responsible for adjusting its own layout.
So when viewDidLayoutSubviews called on a view controller, only the view controller'view 's first-level subviews are guaranteed to be laid out correctly.
What it worked in my case was request layout after changed a constraint value:
self.cnsTableviewHeight.constant = 50;
[self layoutIfNeeded];
Later on override layoutSubviews method:
- (void) layoutSubviews { //This method when auto layout engine finishes
}
You can call setNeedsLayout also instead of layoutIfNeeded
I guess implementing viewDidLayoutSubviews is the correct way but I used an animation just to write the completion callback inside the same method.
someConstraint.constant = 100; // the change
// Animate just to make sure the constraint change is fully applied
[UIView animateWithDuration:0.1f animations:^{
[self.view setNeedsLayout];
} completion:^(BOOL finished) {
// Here do whatever you need to do after constraint change
}];
You might face this problem not just with UIViewControllers but also UIViews. If you have a subview and want to know if AutoLayout has updated it's bounds, here is the Swift 5 implementation,
var viewBounds: CGFloat = 0.0
var autoLayoutHasCompleted: Bool = false
override func awakeFromNib() {
super.awakeFromNib()
// someSubView is the name of a view you want to check has changed
viewBounds = someSubView.bounds.width
}
override func layoutSubviews() {
if viewBounds != someSubView.bounds.width && !autoLayoutHasCompleted {
// Place your code here
autoLayoutHasCompleted = true
}
}
I have a custom view:
-(id)initWithFrame:(CGRect)frame
{
CGRect frameRect = CGRectMake(0, NAVIGATION_BAR_HEIGHT , frame.size.width, 4 * ROW_HEIGHT + NAVIGATION_BAR_HEIGHT + MESSAGE_BODY_PADDING);
self = [super initWithFrame:frameRect];
if (self) {
_selectionViewWidth = &frame.size.width;
[self initView];
}
return self;
}
-(void)initView
{
CGRect sectionSize = CGRectMake(0, 0 , *(_selectionViewWidth), ROW_HEIGHT * 4);
_selectionView = [[UIView alloc] initWithFrame:sectionSize];
[_selectionView setBackgroundColor:[UIColor clearColor]];
That I use in a View Controller the next way:
_mailAttributesView = [[MailAttributesView alloc]initWithFrame:self.view.frame];
_mailAttributesView.delegate = self;
[self.view addSubview:_mailAttributesView];
So when orientation changes from P to L I have the next problem:
What's the best way to get orientation change callback and redraw my custom view?
You likely need to override your UIView layoutSubviews method and proceed to manually layout your subviews (looks like to/from/cc/subject controls) there.
Or, you could better configure your subview spring/struts (or autolayout constraints) for automatic layout. You could do this in code or via a nib or storyboard.
EDIT: additional info since you seem not to be getting layoutSubviews on orientation change.
My guess is that the viewcontroller-view isn't resizing/repositioning your MailAttributes view either.
It's also not clear when/where you add your MailAttributesView to the veiwcontroller view. If you're doing it in viewDidLoad your viewcontroller view may or may not have a valid frame size (depending if it was loaded from a nib or not). It's best not to depend on the viewcontroller-view frame for layout purposes in viewDidLoad.
Rather, layout any viewcontroller-view subviews in viewWillLayoutSubviews. There your viewcontroller-view frame will be set.
Others may point out that you can set your autoresizingFlags in viewDidLoad for any subviews, but there are gotcha's with this. Primarily if the parent view has zero size, and your subviews are to be inset but have springs/struts defined to glue them to the parent view edges.
The best solution overall IMO is to setup autolayout constraints for everything contained in your viewcontroller view, on down.