I have a Storyboard setup with a dialog box that utilizes a secondary UIView (yellow in the screenshot.) This is done by dragging a UIView from the objects library up in between First Responder and Exit on the Storyboard.
I can't figure out how to add basic constraints to this secondary UIView relative to the superview. Right click and dragging won't let me apply constraints. Is this possible? Do I need to handle all Autolayout / constraints programmatically?
I just need to add constraints from leading / trailing. Height will be fixed, but may adjust accordingly depending on screen size.
This has been figured out. You can not use right-click & drag constraints in IB on these views. Why?? Because they're not a part of the view hierarchy yet! (silly me..)
In my above example with the yellow secondary view, here's how you would apply auto layout to it using VFL (Visual Format Language).
In ViewDidLoad, you first want to turn off the views auto resizing mask:
self.yellowSecondaryView.translatesAutoresizingMaskIntoConstraints = false
Now find where you're adding the subview. Let's say you use a button action to trigger it... Inside that action, you first add the view (1), and then the constraints (2):
// 1
view.addSubview(yellowSecondaryView)
// Create a string representation of the view to use it with VFL.
let viewsDictionary: [String: UIView] = ["yellowSecondaryView": yellowSecondaryView]
//2
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-50-[yellowSecondaryView]-50-|", options: [], metrics: nil, views: viewsDictionary)) // Width of yellow view will be full width, minus 50 points from the LEFT and the RIGHT of the superview (|)
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[yellowSecondaryView(250)]", options: [], metrics: nil, views: viewsDictionary)) // Set a static height of 250 for the yellow view
view.addConstraint(NSLayoutConstraint(item: yellowSecondaryView, attribute: .centerY, relatedBy: NSLayoutRelation(rawValue: 0)!, toItem: view, attribute: .centerY, multiplier: 1, constant: 0)) // Center the view vertically in the superview. (credit to #rdelmar in this thread: http://stackoverflow.com/questions/21494039/using-autolayout-in-ib-how-do-i-position-a-uiview-in-the-center-of-the-screen-pr/21494707#21494707)
Hope this helps somebody create a much more manageable and organized storyboard! :)
I'm currently working with a UITableViewController which contains some UITableViewCells subclasses.
When layoutSubviews is called on these UITableViewCells, I change some of the cells' subviews' scales and positions depending on the width and height of the contentView. (The UITableViewCells have some subviews, and I change their scales and positions)
A good example is toggling edit mode, since it shortens the contentView by a bit.
When I do this, the scales and positions of my UITableViewCell's subview do animate
func toggleEdit() {
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(0.35)
self.tableView.beginUpdates()
self.tableView.editing = !self.tableView.editing
self.tableView.endUpdates()
UIView.commitAnimations()
}
When I do this, ONLY the positions animate, the scales change immediately which looks really ugly:
func toggleEdit() {
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(0.35)
self.tableView.editing = !self.tableView.editing
UIView.commitAnimations()
}
I kind of found this out by accident, so I'm wondering now what kind of magic begin/end tableViewUpdate does, and how I can control myself in any scenario which properties should animate and which shouldn't.
The docs state
When you call endUpdates, UITableView animates the operations simultaneously
You could try changing things that you don't want animated in a performWithoutAnimation(_:) block
UIView.performWithoutAnimation {
// things you don't want to animate
}
In swift how can I set the width of the scroll view to be the width of the screen size?
I tried setting right leading and trailing bounds, but my aspectscalefill image keeps stretching the entire frame.
I am trying to force the view to be the width of the screen and allow vertical scrolling only.
If you want to make a scroll view with auto layout try this. No code at all so you will have to drag things out of the library to the right of Xcode.
In your view controller drag and place the scroll view size it to whatever you want but it looks like you want to make it the size of the screen. Pin all edges to the edges of the view controller. Pin trailing, leading, top, and bottom.
Now, instead of placing your items in the scroll view, place another view in the scroll view. With this new view you will place all of your items. You will most likely have to move the view up or down to place them all and resize the view. You can then place whatever constraints you want on your items. When all items are in their place, set the frame of the new view back to x = 0 and y = 0.
You will then place constraints as follows. Select the new view and pin to top, bottom, trailing, and leading and then center in container. This will make a constraint that is vertical with some negative number. In the storyboard outline select this constraint and set it to zero.
You will now be able to scroll vertically. Let me know if you have any questions.
I created a single view application, and added a UIScrollView in the storyboard with four constraints (top, left, right, bottom). And I add the following code in the viewDidLoad(). Everything works just fine. My image is only scrolling vertically, but not horizontally. No additional settings are needed.
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
// Just as an example, I make a size with same with of the view
// and twice the height of the view.
let size = CGSizeMake(
self.view.frame.size.width,
self.view.frame.size.height * 2);
// Load an image and make a image view
let image = UIImage(named: "im.jpg")!
let imageView = UIImageView(image: image)
imageView.frame = CGRect(
origin: CGPoint(x: 0, y: 0),
size: size);
imageView.contentMode = UIViewContentMode.ScaleToFill;
// Add the image view to scroll view
scrollView.addSubview(imageView)
scrollView.contentSize = size;
}
Your problem i caused by the fact that you add your elements to the scrollView from the storyBoard.
In the storyBoard you need:
1) Add the scrollView, if you need to be in the width of the screen then set it's trailing and leading spaces to superView to 0
2) uncheck the "allow horizontal scrolling" in the attribute inspector
In your code
1) set the scrollView's contentSize to what ever size you want it to be.
1.1) in your code you need to set the size of the contentView like so
scroller.contentSize = CGSizeMake(your width, your height);
This is a very important step, the scrollView will not be able to scroll if you don't set the size. Think about the scrollView as the window and the contentView as the view. If the contentView if the same size as the scrollView then the scrollView can't scroll anywhere (all of the contentView fits into the scrollView) in order to create the scrolling you need to make the contentView bigger then the scrollView itself
2) start adding your elements to the scrollView's contentView by calling
[scroller addSubView:<your view>];
this will add your views to the scrollView's contentView and will be
I have the following layout (see below), which for most circumstances works just fine. The user is able to scroll within the blue UIScrollView (which for all intents and purposes is a UITableView, but this question generalises this), and then when they've reached the end of this scroll view, they can start scrolling again (they have to take their finger off, and on again, because the inner scroll view rubberbands otherwise), and the 'super' scroll view starts scrolling, revealing the rest of the image.
It's the whole (they have to take their finger off, and on again, because the inner scroll view rubberbands otherwise) that I don't want. Ideally, once the contained UIScrollView reaches the end of its content, I want the superview to take over scrolling straight away, so that the inner scroll view doesn't rubberband.
The same goes when the user is scrolling back up; when the red scrollview reaches the top of it's content, I want the inner blue scroll view to start scrolling up straight away, instead of the red scroll view rubberbanding at the top.
Any idea how? I am able to determine when the scroll views have reached the ends of their content, but I'm not sure how to apply this knowledge to achieve the effect I'm after. Thanks.
// Inner (blue) scroll view bounds checking
if (scrollView.contentOffset.y + scrollView.frame.size.height > scrollView.contentSize.height) { ... }
// Outer (red) scroll view bounds checking
if (scrollView.contentOffset.y < 0) { ... }
Yeah. Ive got a nifty trick for you.
Instead of having your red outlined view a scrollview make it a normal UIView that fills the screen. In that view lay out your scroll view (table view) and image view as they are in your illustration.
Place a scrollview that fills the bounds of the root view (i.e. also fills the screen) above all the other scrollview and image views. Set the content size of this view to be the total content height of all the views you want to scroll through. In otherwords there is an invisible scrollview sitting on top of all your other views and its content size height is inner scrollview (tableview) content size height + image view size height.
The heierarchy should look like this:
Then this scrollview on top that you have made with the really tall content size make its delegate be your view controller. Implement scrollViewDidScroll and we'll work some magic.
Scrollviews basically scroll by adjusting the bounds origin with funky formulas for momentum and stuff. So in our scrollviewDidScroll method we will simply adjust the bounds of the underlying views:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
//the scroll view underneath (in the container view) will have a max content offset equal to the content height
//but minus the bounds height
CGFloat maxYOffsetForUnderScrollView = self.underScrollView.contentSize.height - self.underScrollView.bounds.size.height;
CGRect scrolledBoundsForContainerView = self.view.bounds;
if (scrollView.contentOffset.y <= maxYOffsetForUnderScrollView) {
//in this scenario we are still within the content for the underScrollView
//so we make sure the container view is scrolled to the top and set the offset for the contained scrollview
self.containerView.bounds = scrolledBoundsForContainerView;
self.underScrollview.contentOffset = scrollView.contentOffset;
return;
}
//in this scenario we have scrolled throug the entirety of the contained scrollview
//set its offset to the max and change the bounds of the container view to scroll everything else.
self.underScrollView.contentOffset = CGPointMake(0, maxYOffsetForUnderScrollView);
scrolledBoundsForContainerView.origin.y = scrollView.contentOffset.y - maxYOffsetForUnderScrollView;
self.containerView.bounds = scrolledBoundsForContainerView;
}
You will find that as scrollViewDidScroll is called every frame of animation that this faux scrolling of the contained views looks really natural.
But wait! I hear you say. That scroll view on top now intercepts ALL touches, and the views underneath it need to be touched as well. I have an interesting solution for that as well.
Set the scrollview on top to be off screen somewhere (i.e. set its frame off screen, but still the same size.) and then in your viewDidLoad method you add the scrollview's panGestureRecogniser to the main view. This will mean that you get all the iOS natural scrolling momentum and stuff without actually having the view on the screen. The contained scroll view will now probably go juddery as its pan gesture recognizer will get called as well (they work differently to UIEvent handling) so you will need to remove it.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self.view addGestureRecognizer:self.scrollview.panGestureRecognizer];
[self.underScrollview removeGestureRecognizer:self.underScrollView.panGestureRecognizer];
//further code to set up content sizes and stuff
}
I had fun making this so heres a link to the sample project on github:
https://github.com/joelparsons/multipleScrollers
EDIT:
To show the scrollbar for the top scrollview when its off the screen no matter where you put it you can set the scrollIndicatorInsets to an inset created like this:
CGPoint scrollviewOrigin = self.scrollview.frame.origin;
self.scrollview.scrollIndicatorInsets = UIEdgeInsetsMake(-scrollviewOrigin.y,0,scrollviewOrigin.y,scrollviewOrigin.x);
*caveat that the scrollview still has to be the right height but I'm sure you get the idea.
And then to make the bar draw outside the scrollview's visible bounds you have to turn off clips to bounds
self.scrollview.clipsToBounds = NO;
OMG. jackslash, you saved my life.
In my case, I need to use three depth of scroll views.
Parent Scroll View
that has a scroll view as one of the children
Child Scroll View: shown as 'Explanation Scroll View / Review Table View / Information Scroll View' in the below image)
Hidden Scroll View
which is distribute own content offset to 'Parent Scroll View' and 'Child Scroll View'
which has content size of whole flatten contents
Screenshot of view hierarchy
After setting view hierarchy, I just need to sync whole flatten content size and distribute content offset properly.
Observable.combineLatest(
overviewStackView.rx.observe(CGRect.self, #keyPath(UIStackView.frame)).unwrap(),
explanationScrollView.rx.observe(CGSize.self, #keyPath(UIScrollView.contentSize)).unwrap()
)
.subscribe(onNext: { [weak self] overviewStackViewFrame, explanationScrollViewContentSize in
guard let self = self else { return }
let totalContentHeight = overviewStackViewFrame.height + self.segmentedControl.frame.height + explanationScrollViewContentSize.height
self.hiddenScrollView.contentSize.height = totalContentHeight
})
.disposed(by: disposeBag)
hiddenScrollView.rx.didScroll
.subscribe(onNext: { [weak self] _ in
guard let self = self else { return }
let currentHiddenScrollViewOffsetY = self.hiddenScrollView.contentOffset.y
let parentScrollViewMaxOffsetY = self.overviewStackView.frame.height
let expectedChildScrollViewOffsetY = max(currentHiddenScrollViewOffsetY - parentScrollViewMaxOffsetY, 0)
self.parentScrollView.contentOffset.y = min(parentScrollViewMaxOffsetY, currentHiddenScrollViewOffsetY)
self.explanationScrollView.contentOffset.y = expectedChildScrollViewOffsetY
})
.disposed(by: disposeBag)
I put a UIScrollView in my nib's view, and linked it to a an IBOutlet property.
Now, when I do this in my viewDidLoad method, it seems to have no effect on the contentSize:
self.sv.backgroundColor = [UIColor yellowColor]; // this works
CGSize size = CGSizeMake(1000.0, 1000.0);
[self.sv setContentSize:size]; // this does not
It behaves as if the contentSize was the same as the frame. What's going on?
This started working when I turned off AutoLayout. Why?
I had the same problem. Auto Layout for UIScrollView is messed up.
Work around: Put everything in the UIScrollView into another UIView, and put that UIView as the only child of the UIScrollView. Then you can use Auto Layout.
If things near the end is messed up (the end of whichever direction your UIScrollView scrolls), change the constraint at the end to have the lowest possible priority.
I tried viewWillLayoutSubviews to update scrollView's contentSize, it worked for me.
- (void)viewDidLayoutSubviews
{
[self.bgScrollView setContentSize:CGSizeMake(320, self.view.frame.size.height* 1.5)];
}
Apple Doc
-(void)viewDidLayoutSubviews
Called to notify the view controller that its view has just laid out its subviews.
Discussion
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.
Your view controller can override this method to make changes after the view lays out its subviews. The default implementation of this method does nothing.
The easiest/cleanest way is to set contentSize at viewDidAppear so you negate the effects of autolayout. This doesn't involve adding random views. However relying on load order for an implementation to work may not be the best idea.
Use this code. ScrollView setContentSize should be called async in main thread.
Swift:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
DispatchQueue.main.async {
var contentRect = CGRect.zero
for view in self.scrollView.subviews {
contentRect = contentRect.union(view.frame)
}
self.scrollView.contentSize = contentRect.size
}
}
Objective C:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
dispatch_async(dispatch_get_main_queue(), ^ {
CGRect contentRect = CGRectZero;
for(UIView *view in scrollView.subviews)
contentRect = CGRectUnion(contentRect,view.frame);
scrollView.contentSize = contentRect.size;
});
}
There are two problems here. (1) viewDidLoad is too soon; you have to wait until after layout has taken place. (2) If you want to use autolayout with a scrollview that comes from a nib, then either you must use constraints to completely describe the size of the contentSize (and then you don't set the contentSize in code at all), or, if you want to set it in code, you must prevent the constraints on the scrollview's subviews from dictating the contentSize. It sounds like you would like to do the latter. To do so, you need a UIView that acts as the sole top-level subview of the scrollview, and in code you must set it to not use autolayout, enabling its autoresizingMask and removing its other external constraints. I show an example of how to do that, here:
https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/ch20p573scrollViewAutoLayout/ch20p573scrollViewAutoLayout/ViewController.m
But notice also the next example, which shows how to use constraints completely, instead of contentSize.
A SUPER easy way to use AutoLayout with UIScrollViews inside Interface Builder:
Step 1: Create a UIScrollView
Step 2: Create a UIView that is a child of your scroll view like so:
-UIScrollView
---UIView
-----Your other content
(We'll call this one contentView).
Step 3: In the size inspector, give this view a height and width (say, 320x700).
Step 4 (using AutoLayout): Create unambiguous constraints from your contentView to its superview (the UIScrollView): connect the 4 edges (top, leading, trailing, bottom), then give it a defined width and height that you want it to scroll too.
For example: If your scroll view spans the entire screen, you could give your content view a width of [device width] and a height of 600; it will then set the content size of the UIScrollView to match.
OR:
Step 4 (not using AutoLayout): Connect both of these new controls to your view controller using IB (ctrl+drag from each control to your view controller's .h #implementation). Let's assume each is called scrollView and contentView, respectively. It should look like this:
#interface YourViewController : UIViewController
#property (strong, nonatomic) IBOutlet UIScrollView *scrollView;
#property (strong, nonatomic) IBOutlet UIView *contentView;
#end
Step 5 (not using AutoLayout): In the view controller's .h file add (actually, override) the following method:
-(void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.scrollView.contentSize = self.contentView.frame.size;
}
You can use this lines of code into your *.m file's
- (void)viewDidLoad{
[scroll setContentSize:CGSizeMake(320, 800)] ;
[scroll setScrollEnabled:TRUE];
[scroll setShowsVerticalScrollIndicator:NO];
[scroll setShowsHorizontalScrollIndicator:YES];
}
for this you need to take an IBOutlet property of UIScrollView into your *.h file this way:
IBOutlet UIScrollView *scroll;
And connect this from Storyboard.
Or,
You can use this method into your *.m file:
-(void)viewDidLayoutSubviews
{
[scroll setContentSize:CGSizeMake(320, self.view.frame.size.height* 1.5)];
// this will pick height automatically from device's height and multiply it with 1.5
}
This both solution works for me in xcode-5, xcode-6, xcode-6.1, xcode-6.2
Setting the contentSize in viewDidAppear is critical.
But I also had a variation of what worked in the 3.5 inch screen, and the 4 inch screen. The 4 inch screen worked, the older one does not. Both iOS 7. Bizarre is an understatement!
I could never get auto layout based on constraints to work. Since my view was already a subclass UIScrollView I solved it by overriding setContentView: and ignoring auto layouts zero height setContentSize: message.
#interface MyView : UIScrollView {}
#end
#implementation MyView
- (void)setContentSize:(CGSize)aSize {
if (aSize.height > 0)
[super setContentSize:aSize];
}
#end
I used to do set up the uiscrollview programmatically UNTIL I watched the following wonderful tutorial, step by step how to get uiscrollview and uiview to work: https://www.youtube.com/watch?v=PgeNPRBrB18
After watching the video you will start liking Interface Builder I am sure.
Vote up
Still not scrolling when dynamic height of labels exceeds view height.
I did what yuf's answer marked as correct above said to do (I added a content view to my scrollview and set the constraints leading, trailing, top bottom, and equal widths from the content view to the scroll view.) but still my view was not scrolling when the internal controls height exceeded the height of the scrollview.
Inside my content view I have an image and 3 labels below it. Each label adjusts their own height dependant on how much text is in them (they are set to word-wrap and numberoflines = 0 to achieve this).
The problem I had was my content view's height was not adjusting with the dynamic height of the labels when they exceeded the height of the scroll view/main view.
To fix this I worded out I needed to set the Bottom Space to Container constraint between my bottom label and the contentview and gave it a value of 40 (chosen arbitrarily to give it a nice margin at the bottom). This now means that my contentview adjusts its height so that there is a space between the bottom of the last label and itself and it scrolls perfectly!
Yay!
Try this out...
add all constraints like you do for UIView (See screenShot of my ViewControler in Storyboard)
Now trick begins. select your last object and select its bottom constraint. (See above screenShot, Instagram button's Bottom Constraint(Yellow line)) and Change the Constant in Size Inspector like in bellow screenshot.
i require Constant=8 but you can change as per your requirements.
this Constant is the Space between That Orange Button's Bottom and the scrollView.
EDIT
Make Sure about your view's hierarchy .
0) ViewController.view (optional)
1) UIScrollView
2) UIView (Rename as "contentView")
3) UIView (this view is your content that will make scrollView scroll)
I finally worked out my own solution to this problem because in my case I couldn't use the view controller's life cycle. Create your own scroll view subclass and use it instead of UIScrollView. This even worked for a scroll view inside a collection view cell.
class MyScrollView:UIScrollView {
var myContentSize:CGSize = CGSize.zero // you must set this yourself
override func layoutSubviews() {
super.layoutSubviews()
contentSize = myContentSize
}
}
My MyScrollView was defined in the nib with a tag of 90. If so this is a good way to set content size in the code in the parent view.
let scrollView = viewWithTag(90) as! MyScrollView
scrollView.myContentSize = ...
If you are using AutoLayout a really easy way to set the contentSize of a UIScrollView is just to add something like this:
CGFloat contentWidth = YOUR_CONTENT_WIDTH;
NSLayoutConstraint *constraintWidth =
[NSLayoutConstraint constraintWithItem:self.scrollView
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView
attribute:NSLayoutAttributeLeading
multiplier:1
constant:contentWidth];
[self.scrollView addConstraint:constraintWidth];
I got Autolayout to work for paginated scroll views whose pages occupy the full-width of the screen. The pages automatically resize according to the scroll view's size. I haven't tested this for lesser-width scroll views but do comment away if it works--I beleieve it should. Targeted for iOS 9, wrote code in Swift 2, used a mix of IB's and custom code in awakeFromNib.
Steps:
Define a full-screen scroll view.
Inside the scroll view, add a UIView (I called mine contentView) whose top, trailing, bottom, and leading edges to the scroll view are all zero; the height is equal to the scroll view's; but the width is the scroll view's width times the number of pages. If you're doing this visually, you will see your content view extend beyond your scroll view in Inteface Builder.
For every "page" inside the contentView, add Autolayout rules to put them side-by-side each other, but most importantly, give them each a constraint so that their widths are equal to the scroll view's, not the content view's.
Sample code below. embedChildViewController is just my convenience method for adding child VCs--do look at setupLayoutRulesForPages. I have exactly two pages so the function is too simple, but you can expand it to your needs.
In my view controller:
override func loadView() {
self.view = self.customView
}
override func viewDidLoad() {
super.viewDidLoad()
self.embedChildViewController(self.addExpenseVC, toView: self.customView.contentView, fillSuperview: false)
self.embedChildViewController(self.addCategoryVC, toView: self.customView.contentView, fillSuperview: false)
self.customView.setupLayoutRulesForPages(self.addExpenseVC.view, secondPage: self.addCategoryVC.view)
}
My custom view:
class __AMVCView: UIView {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var contentView: UIView!
#IBOutlet weak var pageControl: UIPageControl!
override func awakeFromNib() {
super.awakeFromNib()
self.scrollView.pagingEnabled = true
self.scrollView.bounces = true
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.showsVerticalScrollIndicator = false
self.pageControl.numberOfPages = 2
self.contentView.backgroundColor = UIColor.blueColor()
self.scrollView.backgroundColor = UIColor.clearColor()
self.backgroundColor = UIColor.blackColor()
}
func setupLayoutRulesForPages(firstPage: UIView, secondPage: UIView) {
guard self.contentView.subviews.contains(firstPage) && self.contentView.subviews.contains(secondPage)
else {
return
}
let rules = [
"H:|-0-[firstPage]-0-[secondPage]-0-|",
"V:|-0-[firstPage]-0-|",
"V:|-0-[secondPage]-0-|"
]
let views = [
"firstPage" : firstPage,
"secondPage" : secondPage
]
let constraints = NSLayoutConstraint.constraintsWithVisualFormatArray(rules, metrics: nil, views: views)
UIView.disableAutoresizingMasksInViews(firstPage, secondPage)
self.addConstraints(constraints)
// Add the width Autolayout rules to the pages.
let widthConstraint = NSLayoutConstraint(item: firstPage, attribute: .Width, relatedBy: .Equal, toItem: self.scrollView, attribute: .Width, multiplier: 1, constant: 0)
self.addConstraint(widthConstraint)
}
}