uiscrollview's unusual contentsize property - ios

I am quite new to iOS application development.
I was working with UIScrollView and found very strange behavior. Hope somebody could explain this to me.
I tried the two methods and found the outputs different.
1). UIScrollView is added to my view in the Interface Builder, and a view (UIView) is also added to the scrollview earlier. I set the view's bounds manually in the IB, and set the scrollview's content size in the class file.
Observation : The scrollview doesn't scroll with the setcontentsize, rather takes some unusual content size, independent of anything else, even its own bounds.
2). The same UIScrollView is again added in the Interface Builder, but the view is added this time programmatically.
Observation : This time everything works out quite good.
I don't understand what could have gone wrong.
Can anyone explain and elaborate

I'm not sure exactly what your problem is, but if you want to be able to add views to a UIScrollView in IB, create a UIScrollView subclass and do something like this:
- (void)awakeFromNib
{
[super awakeFromNib];
CGRect bounds = CGRectZero;
for (UIView* view in self.subviews)
{
bounds = CGRectUnion(bounds, view.frame);
}
[self setContentSize:bounds.size];
}

Related

Button width and height is disabled in horizontal stackview

Im learning iOS development right now, XCode doesnt allow me to edit width and height of buttons which are in stack view:
In the Storyboard I create a new button of size 30 x 30 with a custom image and then make more 5 copies of that button. Then I embed them after selecting all of them in a Stack View. Now a disaster happens, the buttons are resized to god knows what size and they appear huge and when I try to go to size inspector to resize those buttons I see that "Width" and "Height" fields are disabled.
I tried few suggestions on stackoverflow and selected the stack view and change the distribution of stack view to "Fill Equally" but still the buttons size is being changed. I dont want this to happen. I want a fixed size buttons in a horizontal stack view and putting them in stack view should not change the size or shape of buttons like this. Can anyone please tell me how do I fix this problem?
Please help.
Sometime Interface Builder is not easy to handle because it is a running layout system at design-time / IB_DESIGNABLE. You make changes, IB gets triggered to 'think', changes parameters, layouts again, you see it does not fit and you change again.
It can be easier to fix UIStackView's constrains to your outer layout before dropping content that will be arranged by taking intrinsicContentSize of the subviews into its calculation. Even worse, if the stackview does not have complete constrains already and you drop something in as being arranged, it will take the default size as intrinsicContentSize of the dropped view and change the stackview spacing as it should. This is no surprise but it can be frustrating as convenience is disturbing your workflow here.
The docs tell you should not change intrinsicContentSize because it is not meant to be animated, it will even disturb animations and layout or even break constrains. Well, you can not set intrinsicContentSize, it is read-only. As thats for good reasons they could have written that while UIView's are instanced they can have supportive variables which have to be set before laying out which allows you to make pre-calculations.
While in code this can be tricky also, you can subclass UIView to make arranged subview instances more supportive to your needs.
There is UIView's invalidateIntrinsicContentSize that triggers the layout to take changed intrinsicContentSize into the next layout cycle. You still cant set intrinsicContentSize, but thats not needed when you would have a class designed like shown below.
// IntrinsicView.h
#import UIKit
IB_DESIGNABLE
#interface IntrinsicView : UIView
-(instancetype)initWithFrame:(CGRect)rect;
#property IBInspectable CGFloat intrinsicHeight;
#property IBInspectable CGFloat intrinsicWidth;
#end
// IntrinsicView.m
#import "IntrinsicView.h"
#implementation IntrinsicView {
CGFloat _intrinsicHeight;
CGFloat _intrinsicWidth;
}
- (instancetype)initWithFrame:(CGRect)frame {
_intrinsicHeight = frame.size.height;
_intrinsicWidth = frame.size.width;
if ( !(self = [super initWithFrame:frame]) ) return nil;
// your stuff here..
return self;
}
-(CGSize)intrinsicContentSize {
return CGSizeMake(_intrinsicWidth, _intrinsicHeight);
}
-(void)prepareForInterfaceBuilder {
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, _intrinsicWidth,_intrinsicHeight);
}
#end
Now this gives you control of the behaviour when UIStackView will layout.
Let's look at instancing of your UIStackView.
#import "IntrinsicView.h"
- (void)viewDidLoad {
[super viewDidLoad];
UIStackView *column = [[UIStackView alloc] initWithFrame:self.view.frame];
column.spacing = 2;
column.alignment = UIStackViewAlignmentFill;
column.axis = UILayoutConstraintAxisVertical; //Up-Down
column.distribution = UIStackViewDistributionFillEqually;
CGFloat quadratur = 30.0;
for (int row=0; row<5; row++) {
IntrinsicView *supportiveView = [[IntrinsicView alloc] initWithFrame:CGRectMake(0, 0, quadratur, quadratur)];
// supportiveView stuff here..
[column addArrangedSubview:supportiveView];
}
[self.view addSubview:column];
}
Don't forget IntrinsicView's intrinsicContentSize is set before instancing is complete, so this example takes frame size at initWithFrame as intended and stores that size to be used when intrinsicContentSize is asked. Having that still needs that UIStackView is large enough to layout nicely but you forced the arranged subviews to that intrinsic size. Btw. the example is arranged up..down.
You can use the IntrinsicView in Interface Builder, just change the views inside UIStackView to the above written class. IB will automatically update the designable API and serve you propertys you can set up. This still needs the StackView to have at least width and height set and also constrains if needed. But it takes away the impression your width and height of arranged views would have any effect other than expected, because IntrinicViews height + width is inactive in IB then.
Just to show you how much this improves your possibilities in IB, see image

The content exceeds the ViewController view, How could i show my lengthy contents in screen by scrolling?

I am having more lines of contents which exceeds the ViewController height, I want to show it in same screen by scrolling. Is there any way to achieve that?
Use a UIScrollView and put your contents in it.
You can use UIScrollView or UITableView for the same.
Add all views as subviews of one super view say containerView. Add containerView as sub-view of UIScrollView. Do not forget set appropriate content size of UIScrollViiew. You can do it by proper autolayout or programatically as
CGRect contentRect = CGRectZero;
for (UIView *view in self.scrollView.subviews) {
contentRect = CGRectUnion(contentRect, view.frame);
}
self.scrollView.contentSize = contentRect.size;
You can also use UITableViewController with static cells. You can add view in static cells and you are good to go. UITableViewController itself manages scrolling.
Hope it helps. Happy Coding!!
You can use a UIScrollView (As #luckyShubhra and #chedabo suggested) , you can setup the UI correctly in Storyboard in this case and have the contentsize of the UIScrollView set to the size of the viewController contained in the UIScrollView...
Then also the UITableView (As #luckyShubhra suggested)... if your data is setup to deal with as a list of NSStrings or object, this can work well...
Then if the content is a NSString, and you would use a UILabel, I would suggest changing it to a UITextView... This will make it possible to scroll as well. If the contents is not to be edited just change the Selectable and Editable booleans to false...
UIScrollView or even UITextView can help that too. if it is HTML, you may consider of rendering it with UIWebView.
just make sure the control height is lesser than the view controller, then it will render the contents in a scrollable manner.

UIScrollview: What is setting the contentSize back to 0?

I have a UIScrollView, and in viewDidAppear I set the contentSize:
- (void)viewDidAppear:(BOOL)animated{
CGSize scrollContentSize = CGSizeMake(320, 9200);
self.scrollView.contentSize = scrollContentSize;
}
This code is definitely running.
However, the view doesn't scroll. I wired up a button to log the contentSize, and it returns 0,0. If I get the button to set contentSize again it works fine.
I'm not referencing scrollView anywhere else in my code, what could be setting the contentSize back to 0, and is there any way I can stop it from doing so, or run my setup later in the process of setting up the view?
if you used the Autolayout then you have to make constraints for subviews in it. Then Autolayout will calculate its content size automatically.
But when I set contentsize through button action it works? How?
When the view relayout the scrollview again it will be calculated based on the constraints. So it won't work when u rotate the device/relayout the view.
I don't want to use the autolayout?Now what?
Then you are good to set contentsize.
One more thing, it is best practice to call super implementation on few lifecyle methods.
so do it so.
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
// Your code here
}
Here is the Good starting point
Excellent tutorial

translatesAutoresizingMaskIntoConstraints and UILabel

I have created this simple custom UIView
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
internalView = [UIView new];
[self addSubview:internalView];
internalView.backgroundColor = [UIColor whiteColor];
sublabel = [UILabel new];
sublabel.text = #"test";
sublabel.backgroundColor = [[UIColor yellowColor] colorWithAlphaComponent:0.4];
sublabel.textAlignment = NSTextAlignmentCenter;
sublabel.translatesAutoresizingMaskIntoConstraints = NO;
[internalView addSubview:sublabel];
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
[internalView setFrame:self.bounds];
[sublabel setFrame:internalView.bounds];
}
#end
The view is used in a xib where are defined 4 constraints:
The constrains are related to the top, trailing, leading and the height.
The views hierarchy of the custom view is self -> internalView -> sublabel
The layoutSubviews is doing a simple thing like setting the frame of each subview with the bounds of the superview.
I created the code above just to point out a strange behaviour i found in translatesAutoresizingMaskIntoConstraints.
(The yellow view is the label)
If the value of the property is YES the results is what I expect from the code in the layoutSubviews method
If it is NO, with the same layoutSubviews i got:
The documentation of the translatesAutoresizingMaskIntoConstraints says:
If this is value is YES, the view’s superview looks at the view’s
autoresizing mask, produces constraints that implement it, and adds
those constraints to itself (the superview).
In both cases the autoresizingMask is set to UIViewAutoresizingNone and there are no constraints in the constraints property of the label.
Why with the translatesAutoresizingMaskIntoConstraints=NO the frame that i set in the layoutSubviews is not what i see on screen?
Edit 2:
I expect, given the exposed scenario, to have the same results with translatesAutoresizingMaskIntoConstraints set to YES or NO.
Edit:
I tried to swizzle the sizeToFit method to check if it is called. It's not.
This is happen in iOS6/7
Update 08/08/14
After further investigation i discovered that there is a way to change the frame of the label without having autolayout playing around.
I discovered that after the first pass of layout (when is called the layoutSubviews) with the translatesAutoresizingMaskIntoConstraints=NO autolayout adds constraints for the hugging/compression of the UILabel. The point is that for every view that implements intrinsicContentSize returning something different from UIViewNoIntrinsicMetric the autolayout adds specific constrains. That is the reason behind the resizing of the label.
So the first thing that i did is to reimplement a subclass of the UILabel to override the
intrinsicContentSize.
After that, following the suggestions in this really good article http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html, I tried to turn of autolayout completely for the subviews involved removing [super layoutSubviews].
The goal for me was to avoid that autolayout could act on views where a was trying to apply animated transformations. So if you have the same needs i hope this can help you.
This comes more from intuition of having used it than actual study, but...
If you set translatesAutoresizingMaskIntoConstraints to YES, the system will create constraints to enforce the frame you defined for your view.
If it is set to NO, no constraints will be set for the view, and as you did not set them yourself either, the view will be resized according to default behaviours. In this case it seems to resize to the minimum content size because of the "contentHugging" values.
Bottom line is, from my understanding, when auto-layout is active all views need constraints to be properly placed. You either set that property to YES, or set the constraints yourself. If you don't do either, results will be a bit unpredictable.
The UIViewAutoresizingNone mask is still a valid mask, with fixed dimensions (no resizing) and it will be translated to constraints. Views can coexist without setting constraints, when you set that option to YES.
You are interpreting UIViewAutoresizingNone as meaning "no mask" when it really means "mask with no resizing". Sorry to disagree with you, but I think that this is the expected behaviour :)

UIScrollView is not scrolling the content of another subview

Hi everyone. I dragged a UIScrollView to my storyboard, then I added a UIView on top of that UIScrollView. This UIView loads another subview dynamically depending on what the server throws to this app.
The problem is the UIScrollView is not scrolling the content, even though I made the loaded UIView bigger than the UIScrollView. I'm not invoking [myScroller addSubView:myUiView]. Am I doing it right? Thanks in advance!
For every time you change the content try calling
//removeFromSuperview the UIView was set as content view
self.myScroller.contentSize = self.myUiView.frame.size;
[myScroller addSubView:myUiView];
Don't forget to link both views on Interface Builder
PS: In this apporach the subView must not be inside the scrollView on IB
You must set the content size of the scroll view.
Add this in your view controller.
- (void) viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
myScroller.contentSize = CGSizeMake(CGRectGetMaxX(myUiView.frame), CGRectGetMaxY(myUiView.frame));
}

Resources