I'm building a new app and wish to have a "Welcome walkthrough" at the beginning wherein I have a storyboard with a series of images presented in a UIPageViewController. I have it loading the images and all of that just fine, however the images are resized whenever they go beyond being the "previous" or "next" ViewController. I am using Swift to develop.
Here is a video of the issue: http://youtu.be/dXcjjT-8Bk0
I have tried all of the different View Modes (Aspect fit, aspect fill, redraw etc.) and they all behave the same.
I am using Auto-Layout + Size Classes as I wish to simplify the development for different screen sizes. The current constraints I have that make the UIImage appear at the right size are:
Align Centre X to Superview
Top Space to Top Layout Guide
Bottom Space to Bottom Layout Guide + Equals: 50
I am currently using Aspect Fit which gives me the correct image (after they have done their 'resizing behaviour'.
Can anyone guide me further as to how to fix this?
From your video, I noticed that your UIImageView is always "resized" at the top, not at the bottom. This is most certainly because of your autolayout constraint you call "Top Space to Top Layout Guide". While your UIImageView's view controller is being transitioned through your scrolling page view controller, it doesn't know where the top layout guide is, so its topLayoutGuide.length is 0. Only after the animation completes does the view controller get a positive value for topLayoutGuide.length. Yes, the page view controller should be a bit smarter than this, but it's not.
You can either stop using the top layout guide and make an autolayout constraint relative to the top of its superview. Or you can continue to use the top layout guide but account for when it's length is 0. You can do this by making an outlet for your storyboard's NSLayoutConstraint and overriding viewWillLayoutSubviews() in your ViewController containing your UIImageViews:
#IBOutlet weak var topSpaceToTLG: NSLayoutConstraint!
var parentTLGlength: CGFloat = 20
override func viewWillLayoutSubviews() {
if self.topLayoutGuide.length == 0 {
// Lengthen the autolayout constraint to where we know the
// top layout guide will be when the transition completes
topSpaceToTLG.constant = parentTLGlength
} else {
topSpaceToTLG.constant = 0
}
}
This will always put the top of your UIImageView at the top layout guide, assuming that the status bar is always 20 points. Before laying out subviews, it will check to see if the top layout guide length is 0 or not and adjusts your autolayout constraint accordingly. After the transition animation completes, layout is triggered again, and the top layout guide length will be the expected value, so the constraint constant can go back to 0. Even better than hardcoding the value is to pass in the parent view controller's exact length during initialization, accounting for any possible changes to the top layout guide like adding a navigation bar.
From the video I think you could solve this by preventing the UIPageViewController from extending under the top bars.
In xcode you can do this using the attribute inspector for the page view controller by deselecting Extend Edges Under Top Bars.
This should prevent it paging in under the status bar I think helping to avoid the switch you see.
I figured out my problem was that when the view controller began animating the Top and Bottom Layout Guides had no height. The right and left margins didn't either. When the view finished animating they all received a height or width and my view resized itself.
I fixed this problem on my project by adding vertical constraints between my objects and their super view instead of to the Top/Bottom Layout Guide. I also had to change my horizontal constraints to ignore the side margins.
The last issue I came across is that I had to account for the status bar myself. It may or may not be there or it could be a double bar, like when you are using Maps.
Related
I'm using a mix of Xamarin and XCode Interface Builder to build my UI.
As you can see in the screen shots the PlaceholderView at the top can have different content. The problem I'm having is trying to keep the Submit button at the bottom of the ContentView.
I'm using the ScrollView with ContentView approach and setting constraints in IB.
In ViewDidLoad() I load the contents for the PlaceholderView and then I set the height constraint of the PlaceholderView programmatically.
public override void ViewDidLoad()
{
onlineSuspectDetails = OnlineSuspectDetailsView.Create();
onlineSuspectDetails.BackgroundColor = UIColor.Gray;
SuspectDetailsPlaceholderView.AddSubview(onlineSuspectDetails);
SuspectDetailsPlaceholderView.HeightAnchor.ConstraintEqualTo(onlineSuspectDetails.HeightAnchor, 1).Active = true;
}
Now of course I had to set a Top and Bottom constraint for the Submit Button so it's working for one type
but I can't see a way to change it depending on height of the PlaceholderView in order to keep the Submit Button at the bottom.
If I could access the Bottom constraint I can calculate the new Top constraint but I can't find a way to access the Bottom constraint. How can I do this?
Are there any alternative suggestions to how I can solve this problem?
Hmmm... a bit tricky...
There may be better ways to do it, but here is one approach:
Orange = main view background
Pale Yellow = scroll view background
Gray = UIView... "label / field pairs" container; label/field pairs are in a standard UIStackView
Cyan = UIView... "details" container
Dark Green = button
Red = UIView... this is the tricky part... is a "Shim" to hold the button at the bottom
View constraints are inset by 8 so we can see them easier than if they're taking up the full screen/view.
Gray view and Details view constraints for positions / sizes are straight-forward (looks like you have no problem with that aspect).
In this method, we use a "Shim" view, and some greater-than-or-equal-to constraints to manage the Button's position.
The Shim is pinned leading and top to Zero, and its Height constraint is set to >= -30 relative to the scroll view height. Its bottom constraint is also set to >= 8 relative to the bottom of the Details view.
This tells auto-layout to put the bottom of the Shim no more than 30-pts from the bottom of the scroll view AND at least 8-pts below the bottom of the Details view.
Then the top of the Submit button is constrained to the bottom of the Shim view.
One "quirk" that I've found when working with scroll views in Interface Builder - it can be really tough (maybe impossible?) to get IB to be happy with the necessary constraints. It will show conflicts, but if you follow IB's "fixes" the desired layout then fails.
I don't actually work with IB / Storyboards, so I just focus on avoiding auto-layout / constraint conflicts at runtime.
This is probably easier to understand by seeing the actual file, so I put this up as a GitHub repo: https://github.com/DonMag/SWMoreScrollViewStuff
How are you actually adding the button to the main view? I have done something like this before by having a master UIView for everything except the button. And then I just put the button below the view and applied auto layout to everything (should just be 0,0,0,0 on the view and button). This way your button is always at the front of your view and you can do everything else in the contained view!
I have the most basic set up possible. See pic 1:
Believe it or not this is my first project using AutoLayout, I have created everything prior programatically. This basic set up is literally a UIWebView with 1 custom UIView positioned at the bottom. Previously I was using a tool bar that handled everything for me and had no issues with constraints whatsoever. However, the tool bar created discrepancies for event handling when adding a UILongPressGesture to the subview of the UIBarButtonItem so I decided to convert the tool bar to a UIView (Even inserting a UIView into a tool bar, it naturally converts to a button item) for easier handling. But run-time, the view gets pushed off screen by half of the UIView size (48px) See Pic 2. Then when I add buttons, it just gets worse:
I have reviewed the documents and the support HERE with no results. I've spent about 24 hours in total researching it and learned a lot, so my efforts aren't in vein. I KNOW by 'Adding Missing Constraints', the constraints are just recommendations based on the current set up, they aren't concrete in all cases, so I did try to create my own with control drag after reviewing the documents but my set up concluded with no different results, and exponentially more sloppy. So I decided to include the populated constraints by Xcode below :
UIWebView Constraints
Custom UIView (toolBar) Constraints
Any solid starting point recommendations? Does Intrinsic Size have anything to do with it?
EDIT : WORKING CONSTRAINTS I didn't realize you could simply omit a constraint. It seems the culprit was adding one to the top layout guide.
Just for answerer #Matt :
Constant 0 result : there are small gaps at edges
-16 for leading space/trailing space results as a true toolbar emulation in my case with no outstanding warnings or issues. Thanks
Let's talk about the view at the bottom of your interface and how you would use auto layout to position and size it the way a toolbar would be positioned and sized.
To use auto layout, you need to supply sufficient info to determine both position and size. This view is a subview of the view controller's main view. The main view will be resized depending on the screen, so we want to use auto layout to resize the subview ("toolbar") as well. This is what auto layout is for!
So constrain subview leading edge to the leading edge of the superview, and constrain subview trailing edge to the trailing edge of the superview, both with a constant of 0. Now the right and left edges match the superview!
That takes care of horizontal position and size.
Now let's talk about vertical position. The position should be the bottom. So constrain subview bottom edge to the bottom layout guide of the view controller, again with a constant of 0. Now the bottom of the view is at the bottom!
The only thing we don't know is the top of the subview. This, in our situation, is the same as knowing its height. So give the subview a height constraint, set its constant to a reasonable value like 40, and you're done.
Finally somehow i managed to create the login view using auto layout (Still not sure, if its correct or not). everything looks good in different devices as well as in both the orientations also.
What i want to achieve now is when user clicks on any textfield my gray view scrolls up and when tapping on the return key on the keyboard it should go down to its initial position.
After doing a lot of googling, I came to know is I have to give top spacing margin related to the parent view (self.view) and set change the constraints programmatically like below:
self.topSpaceConstraint.constant = 50 (Set Priority to 999 in my code)
self.view.setNeedsUpdateConstraints()
But when i am doing so, my container view (Gray view) squeezes. I am not able to figure it out why, I want a fixed height constraint (Set Priority to 1000 in my code) along with vertical and horizontal center. Please suggest how to scroll the container view with proper auto layout constraints.
I want to use the Top Layout Guide in the UIScrollView through Auto Layout. Without the UIScrollView Auto Layout works well with Top Layout Guide.
But when I embed the UIButton in UIScrollView, it doesn't.
I know that is because UIScrollView is not the same hierarchy level with Top Layout Guide. But I think there may be a good solution to resolve this issue.
You are right to be confused. It is a bit counterintuitive but the top and bottom layout guides are irrelevant to configuring a UIScrollView so that its scrollable content will underlap the translucent navigation bar, which is the effect you are trying to achieve.
what to do
Given the view hierarchy you've shown in the second picture, this is what you need to do on iOS8:
Configure the view controller so that "Extend Edges Under Top Bar" is checked (in code, use edgesForExtendedLayout). This will ensure that the view controller's lays out its root view so that it underlaps the nav bar.
Configure the scroll view constraints so that the scroll view's top edge has a zero offset from the top edge of its superview, not zero space from the top layout guide. This will ensure that the collection view fills the root view and thus also underlaps the nav bar, which is necessary for the scroll view's content to be able to scroll under the nav bar. (IB might fight you on this. See the footnote below.)
So now how do you make sure that the scroll view has any idea where the nav bar is, so that (for instance) it doesnt't always position its content under the nav bar? The answer has nothing to do with layout guides. In the view controller, check the box "Adjusts Scroll View Insets" (or in code, automaticallyAdjustsScrollViewInsets). This will cause the view controller to automatically adjust the scroll view's contentInset property so that the scroll view positions its content appropriately.
This will work.
what's going on
So why is this the answer? And why is it so confusing?
Frankly, it's easy to get confused because the top and bottom layout guides are prominently presented to us as elements that convey layout information about translucent overlaid elements. However, they are not the only "translucency-aware" layout mechanism. They are directly relevant only for positioning of "normal" subviews, i.e., not the view controller's root view, and not content within a UIScrollView.
Content within a scroll view (or a subclass like UICollectionView and UITableView) will always be positioned in a more complicated way involving the scroll the view itself, affected by properties like contentInset, contentOffset, etc.. (Really, if scroll view layout were a straightforward thing, why would Apple have dedicated WWDC sessions to scroll view layout for the last four years running?!)
To summarize, as the steps above indicate, the three distinct translucency-aware mechanisms for managing layout are as follows:
Extends Edges determines if the view controller positions its root view so that it underlaps the nav bar.
Layout Guides provide a metric that tells where the "main" content area is, taking translucent bars into account. You can use these with Auto Layout to position normal views so they don't underlap. Or you can access the numerical values in code.
Scroll View Insets are the right way to ensure that a scroll view's content can underlap but doesn't always underlap. The automaticallyAdjustsScrollViewInsets property on the view controller can do this for you automatically in simple cases. (Presumably, this property just causes the view controller to update the scroll view's contentInset based on the same values it exposes via the layout guides. So if you needed to manage the insets yourself, that's how you would do it.)
fighting IB's layout guide mania
A footnote on "Fighting with IB":
Unfortunately, Interface Builder might fight you when you try to constrain the scroll view edge to its superview's edge. If you do a ctrl-drag from the scroll view to the superview, when it pops up the menu of possible constraints to add between those views, it might try to get you to constrain the scroll view against the view controller's layout guides. This is because IB mindlessly prefers layout guides to superview edges, when the superview is the root view. But when you're working with a scrollview, this is the wrong thing.
Why? For instance, suppose you accept a constraint against the layout guide. Then you will end up with a top constraint on your scrollview that constrains it to topLayoutGuide-64.0. That -64.0 is a hard-coded value compensating for the exact height of a nav bar. So what happens when one fine day the nav bar does not equal 64pt? Or when you simply turn off the nav bar entirely? Or want to re-use this scene in a context without a nav bar? Answer: then you get a broken layout.
So how do you force IB to add a constraint from the scroll view to its superview's edge, as opposed to the layout guide? As far as I can tell, the answer is that you can't add that constraint correctly in IB by doing a ctrl-drag between views.
Instead, you need to select the view, and then use the "Pin" control at the bottom of the canvas. This is the one that looks like a capital H with a box in its middle. In the top section of the Pin popup dialog, the section with the little diagram showing superview space constraints, you can use the dropdown controls next to the text fields to configure if the space constraint binds a layout guide or a superview. This is shown below:
Github link to demo projects: https://github.com/algal/ScrollViewUnderlapDemo
While algal's answer seems to have worked prior to iOS 9.0 it is unnecessarily complicated and broken beyond iOS 9.0. The easier way that also works beyond iOS 9.0 and requires no interaction with auto layout is to simply do the following:
Ensure Adjust Scroll View Insets is checked for the ViewController in Interface Builder (or set automaticallyAdjustsScrollViewInsets to true programmatically).
Set the class of the ViewController's root View to UIScrollView in Interface Builder (or replace it (self.view) manually with an UIScrollView in code).
While adding spacing constraints, Xcode does not show items which have negative distance with your view. It seems you added a vertical space constraint between UIScrollView and UIView. Delete that constraint, move your scroll view to below Top Layout Guide and add a new vertical space constraint between UIScrollView and Top Layout Guide.
I can't figure out how to correctly position subviews in a Navigation Controller.
I am trying to position a view, table and another view.
If I turn off AutoLayout than top view and table are ok but my bottom view is pushed off the screen.
With autolayout I get both the table and bottom view in the wrong place:
I try to set frame in viewDidLoad as follows (calendarPicker is position at the top below navigation bar), I want the configPanel to be on the bottom, I hide bottom bar on Push.
self.eventsTable.frame = CGRectMake(0, CGRectGetMaxY(self.calendarPicker.frame),
self.eventsTable.bounds.size.width,
self.view.bounds.size.height - self.calendarPicker.bounds.size.height
);
self.configPanel.frame = CGRectMake(0, self.view.bounds.size.height,
self.configPanel.bounds.size.width,
self.view.bounds.size.height - self.configPanel.bounds.size.height
);
Should I rely on autolayout? How should I make my constraints?
I believe my problems arise due to autosizing of the table mostly, but given that I am setting its frame size why would it not change? I do not want to remove auto layout since it is used on other views designed in the storyboard and from what I understand it applies to all?
Is there something that I am missing that needs to be done on top of setting the frames of individual controls?
EDIT:
I think my biggest problem is autolayout and inability to size table appropriately. If I add constraint to the bottom view to be 0 from bottom of the view, it will originally appear correctly. However consequently when resizing table and top view, the table will push bottom view down sizing itself to occupy all available space.
I need to force UITableView to be no more than height between the bottom of the top view and lower view, but still not sure how to do this.
Somewhat closer
Removing code for frame change of the table fixes the issue of the bottom view being pushed off. However in this case top view overlapps table when it changes size at the same time not being drawn correctly:
Uncheck autolayout and then set frames of all three subviews.
Or
If you want constraints then you can use NSLayoutConstraint class to add constraint to your subviews.
I recommend against switching off autolayout, especially because you can't do it on a view by view basis. Autolayout is a must if you tend to do i18n or want to make sure your app does well in different screen resolutions/orientation.
Try This!
disable use Autolayout in storyboard. Place your three views on view controller(view1, view2, view3). Next go to size inspector and use autosizing masks for all three views. Check this, which will help you
http://www.raywenderlich.com/50317/beginning-auto-layout-tutorial-in-ios-7-part-1