In an iOS 8 app, I'm trying to create a screen that has a "parallax header", i.e., an image header that grows as you pull down. I would like to do this using only constraints in Interface Builder, if possible.
Here is a nice guide by Pete Hare on how to do such a thing, and I've also had good help at looking at this example project by Bill Carson. However, contrary to these projects, this is not the header to a scrolling area that's taller than the screen, like a Table View; it's just one page. And for some reason, I can't get things to work in my app. I find Scroll Views in Interface Builder rather confusing to begin with. Could anyone walk me through the steps?
Why, certainly! First we'll set up a view controller with a scroll view.
Create an empty View Controller. Give its initial View the Xcode Specific Label Root View so we can tell things apart.
Add a Scroll View. Resize it to fill the view controller, and add constraints for Leading, Trailing, Top and Bottom to equal the superview's corresponding edges. (I do this by control-dragging from Scroll View to View in the Document Outline, hold down the Option and Shift key and then select all four edges, and then Add Constraints.)
Enable Bounce Vertically in the Scroll View's Attribute Inspector.
Add a simple View to the Scroll View and pin its edges to its superview -- the Scroll View -- in the exact same way as we did with the Scroll View to Root View (although this time we don't need to hold Option key when adding constraints). Give the new view the Xcode Specific Label Scrolling Content.
Xcode is not happy, it says Scroll View is missing constraints: "needs constraints for X position or width" and "needs constraints for Y position or height". Let it automatically add missing constraints, it will add constraints for the center of Scrolling Content to the center of Scroll View, in X and Y directions.
By setting a background color to the Scrolling Content, we can now run and confirm that the scroll view with vertical bounce is working as intended. Nice. Now, let's add the header.
Add a nice header image to the project assets, and drag an Image View to the Scrolling Content. Label it Header Image. Select your image asset as the Header Image's image. Drag the corners of the Image View so that it is aligned to the top, left and right edges of your view. Now let's go through the constraints to set on the Image View.
We want the top edge to be fixed to the top of the screen, regardless of how the user is scrolling. So we need it to be pinned to something outside of the Scroll View. You may try fixing it to the Root View's top edge, but unfortunately, that does not work for some reason. What does work is to pin it to the Top Layout Guide. You'll do this easiest by control-dragging in the Document Outline, between the Header Image and the Top Layout Guide and accpet the suggested constant.
The bottom edge of the Header Image needs to be pinned to something inside the Scroll View. Ultimately, we'd like it to be set to a fixed distance from the top of the Scrolling Content -- but Interface Builder won't let us do that. You can only set it to some distance from the bottom of the Scrolling Content, which is not very practical since you will then have to take the height of the device into account. What we instead do is to add another view directly beneath the Header Image, pin that view's top edge to some distance from the Scrolling Content's top edge, and then pin the Header Image's bottom edge to our new view's top edge. This could be a regular View that holds the rest of your user interface below the header. But for this example (and to demonstrate a later point in this guide), we'll use a label. Add a label directly beneath Header Image and give it three constraints: pin it's top edge to the Header Image's bottom edge, it's top edge also to the Scrolling Content's top edge, and it's center X to the Scrolling Content's center X. The distance between the label's top edge and Scrolling Content's top edge will be the height of the image in the non-dragged state. This unfortunately needs to be set to a constant in the storyboard file -- we'll have to update it programmatically. More on that later.
The last part is easy: pin the leading and trailing edges of the Header Image to the leading and trailing edges of the Scrolling Content.
Now, all our constraints are in place! If you run the app, you can see how it's working correctly constraintwise, but the image isn't scaling the way we expect it to. A couple of last tweaks before we're done.
In the Attribute Inspector, under View, set the Mode of the Image View to "Aspect Fill". That gives it the correct parallaxy behavior.
To set the height of the Header Image correctly, we need a little bit of code. First, make an outlet from the constraint between the label's top edge and the Scrolling Content's top edge -- the one we set to a fixed value -- to your View Controller's source file. Call it imageHeightConstraint. Also add an outlet from the Image View called headerImageView. A good place to update the constraint programmatically is in the viewWillLayoutSubviews View Controller delegate method. Here is some code in Objective C:
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
CGSize imageSize = self.headerImageView.image.size;
CGFloat heightForWidth = imageSize.height / imageSize.width;
CGRect screenBounds = [UIScreen mainScreen].bounds;
CGFloat screenWidth = CGRectGetWidth(screenBounds);
self.imageHeightConstraint.constant = screenWidth * heightForWidth;
}
Finally, you may notice that when scrolling up, the label -- or whatever content is underneath the header view -- is getting covered by it. This is solved by checking Clip Supbviews on the Image View's Attribute Inspector.
Whew!
(Note: I began writing this as a question and then kept writing while I solved it for myself. I guess it would do well with a little text pruning and some images, but maybe it will help someone...)
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 a weird behavior when I use scrollView.
As you can see in the picture bellow the button called "Back" has a bottom space constraint with value 0. But we can see is not even near to the bottom of the scroll view.
I already set the scrollview to have equals width and height of the superView and top,bottom,trailing and leading space with 0 value.
How can I fix this? I want the Back button still inside of the scrollview and put it at the bottom of the view.
Update
I have the code here https://github.com/rchampa/NDParallaxIntroView and the xib is called PageB.xib
Ricardo: you've added two constraint regarding manage y position of Validate Code button where your top constraint stop to move Validate Code button towards the bottom. So please remove top constraint of Validate Code only add bottom constraint.
you need to manage all constraint like your scrollview content view height will be equal to height. Please increase some top constraint from top.
So, if I understand correctly, the problem you are running into is that your back button is unable to be positioned based on the bottom of the scroll view.
A scroll has two sets of constraints in a storyboard, one that defines the size of the scroll view, and the other that defines the content size. The top, left, bottom, right to superview constraints on the scroll view will define the size.
The part that is causing your problem is that the content size is defined by the subviews inside the scroll view. So the button cannot be placed relative to the bottom of the scroll view since the scroll view doesn't know how large its content size is. All of the subviews of a scroll view must be placed in relation to each other.
So, create constraints for your logo, text fields, and all the buttons in relation to each other. Then create constraints from the outer most subviews to the scroll view.
In your example, you would constraints from the logo to the text field, 1st text field to 2nd text field, then 2nd text flied to the label, label to the "Send email..." button, and finally "Send email..." to the "Validate Code". For the "Back" button, it would need a left align with the text fields and then a center align with the "Validate Code" button. Create a top constraint from the logo to the scroll view. Then have leading and trailing constraints from one of the text fields to the scroll view. Finally, create a bottom constraint from either the "Back" or "Validate Code" buttons to the scroll view. You will also need some alignment constraints (logo center to the text field is an example of one). After that is all setup, your view will be fully defined for the content size of the scroll view.
If I understand you, my new question is: how can I make the subviews
make the height of scroll fit 100% screen device height? Is there a
way to define weights like Android? I don't know how achieve this
since the canvas is 600*600 which is different to every device.
Response would be too long for a comment:
So you wouldn't use a scroll view for that. A scroll view is specifically for containing content that will not fit on the screen (so the user can scroll to reach the new content).
The simplest solution is to add a regular view that has TopLeftBottomRight 0 distance constraints (so it is the max height and width of the screen) and setup the view like you have here to position the bottom in the bottom left all the time.
There is also a weight system, I can explain that if you want, but it wouldn't be required thus far.
I feel like your next response will be something like "what if I want it to scroll when its too small for the current screen?". The only way I know of doing that in encapsulating all your interface into a single UIView, and changing its sized in the viewDidLoad/viewDidAppear based on the size of the scroll view. Something like this:
func viewDidLoad() {
super.viewDidLoad()
containerViewHeightConstraint.constant = scrollView.frame.size.height
containerViewWidthConstraint.constant = scrollView.frame.size.width
}
I have a UIScrollView that fills the width of the device and it contains several UIViews laid out horizontally. The views all have the same width, so on the iPhone one has to scroll to see all the views, but on the iPad all of the views are visible.
My question is how can I perfectly horizontally center the views on screen? When the available space is large enough to display all views, they need to be centered but when it's not large enough to display all views they can be laid out as they are now, simply left to right.
The interface has been set up entirely in Interface Builder. The scroll view is set to fill the entire device width - leading and trailing to superview. The first view in the scroll view has leading set to its superview so it's stuck to the far left. The last button has its trailing set to the superview - to the far right of the scroll view, to define the scrollable content area. Each view in the middle is laid out relative to the view to the left of it - leading to the previous button.
Here's a graphical representation of the current layout:
Here's a graphical representation of what I'd like to obtain:
Additional info:
The scroll view doesn't have to always fill the device width, as it is actually completely transparent. When all views are visible I want to disable scrolling. This will need to be adaptive, such that upon rotating the device it can change the layout if needed, because on iPhone in landscape all the views should be visible but in portrait they won't be.
You can try setting the contentInset property of the scroll view, which has a type of UIEdgeInsets. This property simply provides the scroll view with a certain amount of "padding", adding to the scrollable area.
You can determine how much extra space you have at the end (right-hand side) of your view, divide it by 2, and use that value to provide equal-width padding on each side.
Another purely autolayout-based approach could be:
Constrain your middle view to be horizontally centered in the superview.
Constrain each view relative to the middle view in a chain
I tried to create a custom freefrom view with interface builder.
I have a UIView which is containing a UIImageView and UILabel.
I set the constraints to adjusting the Label according to the Image etc and a margin to the superview.
Now I want the superview to exactly fit the subviews. But when I press Editor -> Size To Fit Content, the superview is not resized completely and the constrains are in conflict. What there appears is a implicit constraint ("iPhone Portrait Screen Height"), which can not be altered/deleted. See Screenshot below:
While not ideal, depending on how your XIB is structured and how you are referencing the view, you could create a "scratch pad" view and place your custom view inside of that just to see how everything will layout. That is if you're setting the view to an outlet defined in the XIB's File Owner, you can just connect it to the view you've created inside the "scratch pad".
For example, I created a container view with a square and a switch inside. The top example complains of conflicting constraints and doesn't layout correctly, while the bottom example shows the same thing centered in a throw-away view. Again, not ideal, but it's working for me.
Edit: I just realized that some size class constraints may be lost once the "scratch pad" view is discarded so that is another wrinkle to consider. Still looking into this.
Just to make it simple forget the label for the time being. You have already added the width and height constraint of the image view, now instead of using sizetofit content, try adding leading, trailing, top and bottom constraints between the image view and it's parent view. With that your parent view should fit the image view.
The same technique (constraints to parent view) can be used to get it work with the image view and label together.
I noticed that when I physically dragged the superview's width and height to satisfy the constraints, the red constraint errors went away along with the "iPhone Portrait Screen Width" constraint. In other words, my constraints dictated that my subview be bottom aligned with its super view. If I dragged the superview's bottom edge up to align flush with the subview, the errors went away. Still seems buggy and unintuitive.
I have trouble implementing scrollview with auto layout:/
Here is simple "Hello World" project with that problem: http://www.sendspace.com/file/cg96by
But the problem… I need to create scrollview, but also I need to use auto layout. So I created pure single view application, added (like I saw in some tutorials) Scrollview (the same size as main view), added constraints (all zeros) and then add Container View (the same size as scroll view), add constraints and.. It's didn't work:/ It doesn't scroll. Here is the screen of that situation:
Now, when I delete two constraints named: Center X and Y Alignment (marked on the screen), it works perfectly, but Xcode5 doesn't quite like it :/ and shows some errors like on the next screen below.
Why does he want width and heigh 0 ?!
Actually ContainerView doesn't matter. I've also tried with image view. When you select "Add missing constraints" it also add Center X and Y Alignment. And scroll doesn't work. When you delete them, scroll works but you have this warring:
Ok, I find manually adding the autolayout constraints to be quite confusing, using the "Add New Constraints" button at the bottom really comes in handy, try this, after you get each view where you want it use that way of adding layout constraints for each view (ie scrollView, containerView, UIImageView) I did this, and heres the example, select each constraint as I have so that the top, left, bottom, and right edges are set, this should do exactly what you want.
You will probably want to erase all your current constraints and then implement them this way.
heres the layout like you want http://cl.ly/image/463k2043401L
and heres the example adding the constraints: http://cl.ly/image/472l2V0l3N1L
To keep it dynamic so that you can rotate it, you will want to do the above for all view BUT the container view... then If you control drag from container to scrollview with no constraints, you can add all of them that it suggests except for the height and width at the bottom, that satisfies any missing constraints and should do the trick, It will center the container inside of the scrollview and keep it pinned to the top, bottom, left and right, and it should dynamically change with your scrollview content size.
http://cl.ly/image/1o3k1e452W0g
Sorry for the confusion, hope this helps!
I had a similar problem and i found relative simple solution from within Interface Builder using pure Autolayout without any code.
For proof-of-concept at first remove any constraint in View Controller to if see this works.
This is sample layout:
View (main view of my UIViewController)
Scroll View (UIScrollView)
Container View (UIView)
Content View (e.g. UIImageView)
A. Scroll View width/height should be smaller that Container View width/height
B. Container View should have some determinated width/height (may be explicit width/height )
C. Do Control-drag Container View to Scroll View and add only:
Leading Space to Container
Trailing Space to Container
D. Check out those two constraints and set "constant" value for both to 0
E. Run app and