UITextView stretches horizontally but not vertically inside UIScrollView - ios

My task is very simple - just display vertically scrolling text inside custom UIView.
I have an UIScrollView with some container with one UITextView in it. Container is needed to add more items later. I use IB(xib file) to add autolayout constraints like shown below:
But everything I see is:
And it's scrolling horizontally :-(
I've tried setting contentSize of UIScrollView:
At initWithCoder, didMoveToSuperview, willMoveToSuperview = no effect
At custom method, called from main viewcontroller = no scrolling at all, and I see the same picture.
Thanks a lot!
Edit: Text View's and Container View's intristic size are set to "placeholder". I understand that I should limit width of ContainerView, but it should work with all screen sizes and orientations, so setting width in code, IMO, worse than setting constraints.

Yeah! I've found the solution!
If someone facing the same problem:
You should add left and right constraints not to UIScrollView, but to superview of UIScrollView (to super-super-view).
Unfortunately, this cannot be done in IB, but you can do it within code:
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.view
attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual
toItem:self.containerView attribute:NSLayoutAttributeLeft
multiplier:1.0 constant:-30]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.view
attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual
toItem:self.containerView attribute:NSLayoutAttributeRight
multiplier:1.0 constant:30]];
Where self.containerView is view inside UIScrolllView and self.view is view outside UIScrollView
So:
You set vertical constraints to UIScrollView and horizontal constraints to top-level view.

Related

Set a UIView width twice another view using autolayout

In my screen I have two view that are horizontally near to each other. I want the width of first view be twice of the width of second view.
I man for example, if right view has width=200 the second one show by with=100.
As I search and look in auto-layout, it has options for alignments and spaces between views. Do it has option for defining such relationships too?
You can do this programmatically by adding manual constraints that work with autolayout. I'm sure using InterfaceBuilder is also an option.
UIView *firstView;
UIView *secondView;
[firstView addConstraint:[NSLayoutConstraint constraintWithItem:secondView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:firstView
attribute:NSLayoutAttributeWidth
multiplier:2.0
constant:0]];
Note the multiplier there is 2.0 which is where it forces the width to be double.

Constraints not working with a UITextView

I have a View controller, in which the view has two image views and two text views. I turned off auto layout, and I programmatically set the distance between the first text view and the first image view by using this code:
The following code is in the viewDidLoad method of my custom view controller class. I have set the autoresizing mask to no in both cases, so I have no idea why the code doesn't work.
(tf2_logo is the image view and itemName is the text view)
self.tf2_logo.translatesAutoresizingMaskIntoConstraints = NO;
[self.backpackBackground addConstraint:[NSLayoutConstraint constraintWithItem:self.itemName attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.tf2_logo attribute:NSLayoutAttributeTop multiplier:1.0 constant:-1.0]];
[self.backpackBackground addConstraint:[NSLayoutConstraint constraintWithItem:self.tf2_logo attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.backpackBackground attribute:NSLayoutAttributeLeft multiplier:1.0 constant:17]];
Now I want to do the same thing with my other text view, basically I wanted to keep the distance between the itemName text view and the text view at a certain distance. I used this code:
(tf2 is my other text view)
self.tf2.translatesAutoresizingMaskIntoConstraints = NO;
[self.backpackBackground addConstraint:[NSLayoutConstraint constraintWithItem:self.itemName attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.tf2 attribute:NSLayoutAttributeTop multiplier:1.0 constant:-3.0]];
[self.backpackBackground addConstraint:[NSLayoutConstraint constraintWithItem:self.tf2 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.tf2_logo attribute:NSLayoutAttributeRight multiplier:1.0 constant:20]];
After implementing this code, the tf2 text view doesn't even show up in the view controller. What is the problem?
EDIT: You can download the whole project here: https://www.dropbox.com/sh/u820u2ndyrncuz8/P4atI-9CAx
EDIT#2:
You mentioned that you turned off auto layout, because UITextView has that little gap on top in iOS7. To remove the gap, try this:
self.tf1.textContainerInset = UIEdgeInsetsZero;
When you log the original value of the textContainerInset it shows: {8, 0, 8, 0} . The two 8's are responsible for the gap (one at the top). The line above sets all values to zero and the content is nicely aligned to the top of the frame.
(EDIT#1: Completely changed the answer)
I assume you primarily want to have a flexible height of the imageName UITextView. First I suggest to use auto layout. You can set constraints in Xcode according to the following image:
The red lines are the constraints. The green line is special: It shall be a height constraint and you create an outlet for it in the view controller. (Open the document outline view, locate the height constraint in the tree and control-drag it to the code.)
Then in the viewDidLoad method:
CGSize size = [self.tf1 sizeThatFits:self.tf1.frame.size];
self.tf1Height.constant = size.height;
The height of the "lore ipsum" field now adjusts to its content.
Have you tried using frames instead of constraints? If your not using autolayout I think frames might be easier to read/implement.
sample:
// tf2 will be placed at (0,0) in superview and have width of 100 and height of 20
tf2.frame = CGRectMake(0, 0, 100, 20);
you can play around with different values to get your layout as desired.

Resetting a fixed width and height in AutoLayout

I'm creating a custom UIView called CTCycleClock with a subview called CTCycleSlider. It reacts to a gesture so it can rotate on one axis (like looking from above upon a roulette table).
To achieve this, the main view CTCycleClock creates two constraints on the CTCycleSlider subview that center it on X and Y.
Example:
Furthermore, the CTCycleSlider subview creates two constraints on itself that set a specific width and height. This is necessary because otherwise upon rotation, the disk will make itself larger.
This works nicely and correctly. But when the superview has a bigger size (for instance on iPad), I don't know how to tell AutoLayout that the subview has a new fixed width and height equal to the superview.
This is how I set constraints in the superview:
NSLayoutConstraint *centerX = [NSLayoutConstraint
constraintWithItem:subview
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterX
multiplier:1.f constant:0.f];
NSLayoutConstraint *centerY = [NSLayoutConstraint
constraintWithItem:subview
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterY
multiplier:1.f constant:0.f];
[self addConstraint:centerX];
[self addConstraint:centerY];
This is how I set constraints in the subview, where self.widthAndHeight is currently hardcoded to 320 on iPhone and 450 on iPad:
NSLayoutConstraint *w = [NSLayoutConstraint
constraintWithItem:self
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:self.widthAndHeight];
NSLayoutConstraint *h = [NSLayoutConstraint
constraintWithItem:self
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:self.widthAndHeight];
[self addConstraint:w];
[self addConstraint:h];
So my question is: how can I make a subview first hug the superview frame with a certain margin, but also set its width and height fixed?
EDIT: some clarifications as to why I need the constraint that sets width/height fixed.
When I won't set the width/height fixed, and the user touch-rotates the wheel, you get the following result:
In the above image, I've set constraints on the subview that set top/lead/width/height to the superview. That works great when the user hasn't rotated the wheel subview yet, but when they do, the autolayout constraints force the rectangular UIView smaller so it completely fits in the superview.
Thus the question remains: how can I create constraints that initially resize the subview correctly to the superview, but then set a fixed width/height so upon rotation, it stays the same size?
...how can I make a subview first hug the superview frame with a
certain margin, but also set its width and height fixed?
I don't understand your question. If you make your image view hug the superview with a fixed margin (on all sides) then the size of the image view is dictated by the superview.
You could pin the image view on 2 sides (e.g. top and left) and specify a size. Then the distance to the other 2 sides would vary based on the size of the superview. Or you could center it in the superview and fix the size, and then ALL The margins would vary based on the size of the superview.

can I set constraints directly to superview in a custom view?

A pretty simple question I reckon:
one UIViewController
one custom UIView
The controller only does:
-(void)loadView{
[super loadView];
self.sideMenu = [[sideMenuView alloc]init];
[self.view addSubview:self.sideMenu];
}
and in the UIView I would like to do something like:
self.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.superview attribute:NSLayoutAttributeLeading multiplier:1 constant:100];
[self.superview addConstraint:constraint];
So that when I create the UIView in the controller its constraints is already set in relation to the controller.
I have tried and nothing crashes but the UIView gets realy weird x and y coords
Maby I need to update the constraints? Or maby this isnt at all possible?
I'm not sure what ui behavior you are exactly looking for since it appears that you are trying to tie the leading space of your view to the leading space of it's superview. Being the leading space, the space on the left of the view, could it be that you are looking for the more common "stick my left side 100 pixels from my parents left border"? Anyway, in either case, I would connect an outlet from the controller to the custom view (i.e. myCustomView below) and then build the constraint in the UIViewController and not the UIView by overriding:
- (void)updateViewConstraints {
[super updateViewConstraints];
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:myCustomView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:myCustomView.superview
attribute:NSLayoutAttributeLeading
multiplier:1
constant:100];
[myCustomView addConstraint:constraint];
}
Apple has an interesting page with a table showing the various runtime entry points for autolayout at this address:
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/Articles/runtime.html#//apple_ref/doc/uid/TP40010853-CH6-SW1
You might consider adding other constraints as well. Auto layout has the the tendency to exploit any freedom you leave unchecked in the worst possible way ;-)
So leading edge is not enough.
You need enough constraints to satisfy vertical and horizontal layout.
In one direction you need at least
one edge & width (or hight)
Or
Two edges ( implicit width or height )
Or
A horizontal (or vertical) center based constraint and an explicit width ( or height respectively)
The thing about width and height is that they can also be determined by intrinsic content size.
Add constraints after adding the view to the superview.
A bit late but PureLayout is pretty handy https://github.com/smileyborg/PureLayout

Matching subview's width to it's superview using autolayout

Goal:
Have a UIWebView be the same width as it's superview, which is a UIScrollView, using autolayout constraints.
Code
NSLayoutConstraint *makeWidthTheSameAsScrollView =[NSLayoutConstraint
constraintWithItem:self.questionWebView
attribute:NSLayoutAttributeWidth
relatedBy:0
toItem:self.masterScrollView
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0];
[self.view addConstraint:makeWidthTheSameAsScrollView];
NSLog(#"The width of questionWebView *AFTER* adding the constrain is: %f", self.questionWebView.frame.size.width);
NSLog(#"The width of scrollView *AFTER* adding the constrain is: %f", self.masterScrollView.frame.size.width);
Current Result
When I log the width of self.questionWebView (the UIWebView), it's width does not change when the autolayout constrain is applied.
Questions
Is this the correct approach?
What am I doing wrong?
p.s I know it is against Apple's recommendations to place a UIWebView in a UIScrollView, however I've turned off the ability to scroll the UIWebView using the property self.questionWebView.userInteractionEnabled = NO;. And currently using a UIWebView is my best strategy for displaying an HTML table.
Improving on Rob's answer, as requested.
As Rob already mentioned, UIScrollViews have peculiar behavior under Auto Layout.
What is of interest in this case is the fact that the scrollView total width is determined by using its subviews total width. So while the scrollView already asks the webView for its width, you're telling the webView to also ask the scrollView for its width. That's why it doesn't work. One is asking another, and no one knows the answer. You need another reference view to use as a constraint for the webView, and then the scrollView will also be able to successfully ask about its expected width.
An easy way this could be done: create another view, containerView, and add the scrollView as a subview to that. Then set the proper constraints for containerView. Let's say you wanted the scrollView centered on a viewController, with some padding on the edges. So do it for the containerView:
NSDictionary *dict = NSDictionaryOfVariableBindings(containerView);
[self.view addConstraints:[NSLayoutConstraints constraintsWithVisualFormat:#"H|-(100)-[containerView]-(100)-|" options:0 metrics:0 views:dict];
[self.view addConstraints:[NSLayoutConstraints constraintsWithVisualFormat:#"V|-(100)-[containerView]-(100)-|" options:0 metrics:0 views:dict];
Then you can proceed adding the webView as a subview to the scrollView and setting its width:
NSLayoutConstraint *makeWidthTheSameAsScrollView =[NSLayoutConstraint
constraintWithItem:self.questionWebView
attribute:NSLayoutAttributeWidth
relatedBy:0
toItem:containerView
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0];
[self.view addConstraint:makeWidthTheSameAsScrollView];
This would make the scrollview as large and tall as the webView, and they both would be placed as intended (with the constraints set on containerView).
Scrollviews are a bit strange in how they interact with auto layout. See TN2154 (UIScrollView and Autolayout).
See also UIScrollView doesn't use autolayout constraints.
In general, you need to get the width of the contained view some other way than "the current width of the scrollview" since in auto layout the scrollview's width (i.e. content width) is defined in terms of its content. Thus your current request is circular.

Resources