iOS Autolayout how to stretch out subview in superview - ios

As you can see on the screenshot here is gray UIView which I use as a container. In the code I allocate a UINavigationController and add it's view as a subview to the gray container view. The UINavigationController has RootViewController (you can see the cut off table view on the left side - it is my root view controller of UINavigationController).
My question is how to pin all sides to the superview bounds. Because right now I just can see a part of the RootViewController view.
In the storyboard I have set all constraints as for gray container view as for RootViewController view of UINavigationController. If I normally PushViewController instead of adding as a subview it shows without cut off issue.
Here is a code I have in UIViewController that contains gray container view:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
KNCouponsViewController *couponsViewControllerByList = [KNInstantiateHelper instantiateViewControllerWithIdentifier:#"KNCouponsViewController"];
self.theNavigationController = [[UINavigationController alloc] initWithRootViewController:couponsViewControllerByList];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.theContainerView addSubview:self.theNavigationController.view];
}
Here is constraints for the gray container view
Here is constraints for the view of root view controller which has cut off table:
Seems like a simple solution is to set UINavigationController view the same width and hight as the gray container has:
- (void)viewDidLoad
{
[super viewDidLoad];
CGRect rect = self.theContainerView.frame;
rect.origin.x = 0;
rect.origin.y = 0;
[self.theNavigationController.view setFrame:rect];
[self.theContainerView addSubview:self.theNavigationController.view];
}
Then it works correct. But I think if we will not modify a frame only then it will work?

there are two ways.
1 way is to call addSubView code in viewDidLayoutSubview method
2nd way is to use following code for settings frame
[self.theContainerView addSubview:self.theNavigationController.view];
[self.theNavigationController.view setFrame: self.theContainerView.frame];
Note: If you use first way then add your code in
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self.theContainerView addSubview:self.theNavigationController.view];
[self.containerView layoutIfNeeded];
});

Related

Issue with presenting an external UIView in a UIViewController: outlets don't show up

I have a UIViewController that has its UI elements setup in storyboard, and things are showing up fine. Now I created a new UIView in separate xib, .h and .m files, I'll call it "overlay"; then I present that overlay which should cover everything below, so that only the overlay can be seen:
// in controller.m
OverlayView *overlayView = [[OverlayView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
overlayView.layer.zPosition = 1000;
[self.view addSubview:overlayView];
The overlay appears in a weird position (the top of it is some 50px away from the bottom of the navigation bar). What's more, apart from the background of the overlay, no elements in the overlay can be seen. It's just a blank red canvas. I have double checked that the elemenets' alpha values are 1, they are set to be not hidden, and they are also set explicitly in the initWithFrame of the overlay:
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor redColor]; // red canvas shows up
self.title.textColor = [UIColor blackColor]; // title can't be seen; why not?
self.body.textColor = [UIColor blackColor]; // body can't be seen; why not?
}
return self;
}
The reason I don't directly put overlay in the controller's xib and then simply change it's hidden property is that this overlay is to be used by multiple controllers, and I'd like to re-use it, thus putting it as a separate view.
What am I doing wrong?
The parent view (view controller's view) is responsable to set the child view's frame. If you use Auto Layout you need to use setTranslateAutoresizingMaskIntoConstraints to NO and add the desired constraints in code. Keep in mind that the child's frame will be set after viewDidLayoutSubviews is called.
If you don't use Auto Layout just set the frame using setFrame method in viewWillAppear.
It turns out I had to load the nib from within the view's own initWithFrame.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
id mainView;
if (self) {
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:#"MyOverlayView" owner:self options:nil];
mainView = [subviewArray objectAtIndex:0];
}
return mainView;
}
- (void)awakeFromNib {
[super awakeFromNib];
}
The code above fixed it.

How to access properties from view after it loaded

Storyboard:
Constraints:
Result:
I'm trying to understand autolayout and how I can use it inside a container.
I got a default ViewController that was made for me when I opened the Storyboard. I put a View Container inside there. And then I added a loose (not connected to anything) ViewController. I want the content inside the new ViewController to be put in the container.
So the added ViewController which will be put inside the Container consists of three labels where I am using autolayout.
Clicking on the black bar for the Container View Controller in the Storyboard I go to Identity Inspector and set the Custom Class to "ContainerViewController". Then I set the Storyboard ID to "ChildController" for the loose View Controller.
Then I override viewDidLoad in ContainerViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
UIViewController *child = [self.storyboard instantiateViewControllerWithIdentifier:#"ChildController"];
[self addChildViewController:child];
[self.view addSubview:child.view];
}
Why won't the constraints for the autolayout work when put in a container? I hoped they would, so I could take this further to a UIPageViewController.
EDIT:
So it seems to be the frame size. I need to do something like this before adding it as sub view:
child.view.frame = CGRectMake(0, 0, 265, 370);
Now I created an outlet for the Container in ViewController.h.
#property (weak, nonatomic) IBOutlet UIView *container;
But.. From ContainerViewController, how can I ask presenting view controller (ViewController) of this property when it's not set yet?
ViewController *parent = (ViewController*)self.presentingViewController;
UIView *container = parent.container;
NSLog(#"ParentView Container Width: %f, Height: %f", container.frame.size.width, container.frame.size.height);
It just gave me zero width and height, because the view hasn't loaded yet. Later on when it loads, I get the actual values..
NSLog(#"View Controller Container. Width: %f, Height: %f", self.container.frame.size.width, self.container.frame.size.height);
The question: How can I access properties of presentingViewController when they are ready/loaded?
when adding a child view controller you should set its view frame like this
UIViewController *child = [self.storyboard instantiateViewControllerWithIdentifier:#"ChildController"];
[self addChildViewController:child];
[self.view addSubview:child.view];
//set the view frame
child.view.frame = self.view.bounds;
So the child view controller will resize its view frame and layout contraints will do their homework ;)
EDIT
put the code in -(void)viewDidLayoutSubviews like this:
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
UIViewController *child = [self.storyboard instantiateViewControllerWithIdentifier:#"ChildController"];
[self addChildViewController:child];
[self.view addSubview:child.view];
//set the view frame
child.view.frame = self.view.bounds;
}
Just try to set frame in viewWillLayoutSubviews for view Controller

automaticallyAdjustsScrollViewInsets not working

I've created an extremely simple demo app to test the functionality of automaticallyAdjustsScrollViewInsets, but the last cell of the tableView is covered by my tab bar.
My AppDelegate code:
UITabBarController *tabControl = [[UITabBarController alloc] init];
tabControl.tabBar.translucent = YES;
testViewController *test = [[testViewController alloc] init];
[tabControl setViewControllers:#[test]];
[self.window setRootViewController:tabControl];
My testViewController (subclass of UITableViewController) Code:
- (void)viewDidLoad
{
[super viewDidLoad];
self.automaticallyAdjustsScrollViewInsets = YES;
self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
self.tableView.dataSource = self;
self.tableView.scrollIndicatorInsets = self.tableView.contentInset;
//[self.view addSubview:self.tableView];
// Do any additional setup after loading the view.
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 20;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#""];
cell.textLabel.text = #"test";
return cell;
}
Is this a bug in iOS 7? If not, what did I do wrong?
I think that automaticallyAdjustsScrollViewInsets only works when your controllers view is a UIScrollView (a table view is one).
You're problem seems to be that your controller's view is a regular UIView and your UITableView is just a subview, so you'll have to either:
Make the table view the "root" view.
Adjust insets manually:
UIEdgeInsets insets = UIEdgeInsetsMake(controller.topLayoutGuide.length,
0.0,
controller.bottomLayoutGuide.length,
0.0);
scrollView.contentInset = insets;
Edit:
Seems like the SDK is capable of adjusting some scroll views despite not being the controller's root view.
So far It works with UIScrollView's and UIWebView's scrollView when they are the subview at index 0.
Anyway this may change in future iOS releases, so you're safer adjusting insets yourself.
For automaticallyAdjustsScrollViewInsets to work, your view controller must be directly on a UINavigationController's stack, i.e. not as a child view controller within another view controller.
If it is a child view controller of another view controller which is on the navigation stack, you can instead set automaticallyAdjustsScrollViewInsets = NO on the parent. Alternatively you can do this:
self.parentViewController.automaticallyAdjustsScrollViewInsets = NO;
I just solved this issue with iOS 11 and swift 4, my current problem was that iOS11 has a new property to validate the insets when a ScrollView does exist, that one is contentInsetAdjustmentBehavior which is a ScrollView's property and the default property is automatic so my code was:
if #available(iOS 11, *) {
myScroll.contentInsetAdjustmentBehavior = .never
} else {
self.automaticallyAdjustsScrollViewInsets = false
}
I hope this solve your problems too...
I have this hierarchy:
custom navigationcontroller contains custom tabbarcontroller
custom tabbarcontroller contains several controllers
these controllers contains subviews and one of them contains a subclass of uiscrollview.
I had to set automaticallyAdjustsScrollViewInsets to NO
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.automaticallyAdjustsScrollViewInsets = NO;
in the custom tabbarcontroller. Other controllers in the hierarchy do not have any impact on the nested scroll view's behavior.
I was having the same issue, a Table View with unwanted top padding.
All answers say to fix by setting automaticallyAdjustsScrollViewInsets = NO, but that was not eliminating the padding for me.
Similar to the other answers here, these directions need to be tweaked slightly if you're using a non-standard view hierarchy.
I had a UIViewController with an embedded UITableViewController. It was not working to set automaticallyAdjustsScrollViewInsets on the Table View Controller.
Instead, I set automaticallyAdjustsScrollViewInsets = NO on the parent UIViewController that was embedding my Table View Controller. That successfully eliminated the padding on the Table View.

Work with iOS 7 top layout guide

I'm designing a view controller in my storyboard. It contains a navigation bar at the top and a content view. I've set the constraints as
Navigation bar's top space to top layout guide = 0
Content view fills the remaining space (leading and trailing space to superview = 0 ; bottom space to superview = 0, top space to navigation bar =0)
Because I want to display the status bar, I'm using the top layout guide to make the navigation bar displayed just under it. It works with a standard UINavigationBar.
But I have custom navigation bar : I've created my own class inheriting UINavigationBar.
The problem is : when specifying this custom class for the navigation bar in the Identity Inspector of the interface builder, the bar is not at the right place when running the app.
Is there anything to add or modify in initWitFrame or initWithCoder, or something else to do, so my custom navigation bar will follow the constraints of my storyboard.
EDIT - source added
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// additional setup
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:(NSCoder *)aDecoder];
if (self) {
// same setup than initWithFrame:
}
return self;
}
- (void)drawRect:(CGRect)rect
{
//[self setFrame:rect]; navigation bar is not at the right place if uncommented
}
Why are you calling [self setFrame:rect];? frame is correctly set up at this point, you shouldn't change it. Also, rect is in an internal coordinate system of the navigation bar.

UIScrollView child jumping after UINavigationController push and pop

So I have a UIScrollView on my iPad app with a single child view (which itself is parent to all the controls). The scrolling all works fine on it. Rotating works fine (the whole view fits in portrait, scrolls on landscape). Once pushing a new screen on the UINavigationController, and then coming back breaks it.
It looks as if the frame of the scrollview's child has moved up, relative to the scroll position, but the scrollview has remained at the bottom (the entire child view has shifted upwards).
I've tried fighting the Constraints in storyboard, literally for hours, and cannot work out what could be causing this.
I had the same problem with scroll view and auto layouts (iOS 6 - doesn't work, iOS 7 - works fine), of course this is not perfect solution, but seems like it works. Hope it will help you:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self performSelector:#selector(content) withObject:nil afterDelay:0.0];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
offset = self.scrollView.contentOffset;
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
self.scrollView.contentOffset = CGPointZero;
}
- (void)content
{
[self.scrollView setContentOffset:offset animated:NO];
}
Get the frame of the subview before it disappears then manually reset the frame of the subview every time the view appears in -(void)viewWillAppear:(BOOL)animated.
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
globalFrameVariable = subview.frame;
[subview removeFromSuperview];
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[subview setFrame:globalFrameVariable];
[scrollView addSubview:subview];
}
Here is a simple solution i found. (Assuming the parent view is meant to span the entire contentSize) Use this subclass of UIScrollView:
#interface BugFixScrollView : UIScrollView
#end
#implementation BugFixScrollView
-(void)layoutSubviews
{
[super layoutSubviews];
UIView *view=[self.subviews firstObject];
if(view)
{
CGRect rect=view.frame;
rect.origin=CGPointMake(0, 0);
view.frame=rect;
}
}
#end
It simply resets the origin every time auto-layout messes it up. this class can be used in InterfaceBuilder simply by changing the class name after placing the UIScrollView.

Resources