subviews are overlapping inside the scrollview - ios

My original question is posted here, but I wanted to open new question to explain in better in briefly way about my issue.
I try to draw 3 subviews on a scrollview and make that scroll horizontally. The content size is Ok, I can scroll up to three times (width of 3 subviews) but the problem is that all of the subviews are drawn over each other so obviously I can see only the last subview.
The subview is loaded from a xib file. Here is the relevant code:
startX = 0;//static int startX ;
for (App_Table *_appTable in sorted) {
NSArray *arr = [[NSBundle mainBundle] loadNibNamed:#"AppTableView" owner:self options:nil];
AppTableView *aView = [arr objectAtIndex:0];
aView.frame = CGRectMake(startX, 0, aView.bounds.size.width, aView.bounds.size.height);
aView.backgroundColor = [UIColor redColor];
[self.scrollView addSubview:aView];
self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width+aView.frame.size.width
,self.scrollView.contentSize.height);
startX = startX + aView.frame.size.width;
AppTableView *_appTableView = (AppTableView *) [[self.scrollView subviews] lastObject];
_appTableView.txtADDRESS.text = _appTable.address;//Fill in the form, no need to write all the form fields code because it's the same way.
}
As you can see, the X origin is incremented by the width of the view, I debugged to confirm, however the subviews are overlapping one above the other, and finally just one subview (the last one) is visible. I checked that by setting the background to red color, and only one red background subview is loaded.
If you can help with and want to post your answer in the original question for which I make a bounty, feel free.
Thanx.

Related

Layout in UIScrollView

I am new to UIScrollView, and they are driving me crazy.
I am trying to create a screen that has a title and some descriptive text at the top, and then a scroll view (with paging enabled) in the bottom two thirds of the screen. This scroll view will contain 1-3 images. Ideally, I'd like the first image to appear centered in the initial scroll view window, and then enable the user to view the other images by scrolling/paging horizontally, again ideally with each image centered in its 'page'.
The code below loads the images for 'item' from the Internet, creates a UIImageView for each, and inserts them into the scroll view. According to the frame calculations, I would expect the first image to be up against the left side of the scrollview, and then the other images to the right, with no space between them. But instead, the first image is shifted to the right, about half-way across the screen, and there are equally large spaces between each image.
I have turned off paging to simplify matters, and I have tried experimenting with tweaking the image frame values, trying to understand what's happening, but nothing works as I expect it to.
I am guessing that autolayout is messing with me. Can someone confirm that, and maybe give me some hints on how to get this scrollview under control?
Thanks in advance.
self.imageScrollView.contentSize = CGSizeMake(self.imageScrollView.frame.size.width * (imageFieldNames.count),
self.imageScrollView.frame.size.height);
self.imageScrollView.pagingEnabled = NO;
CGFloat xPos = 0.0;
for (NSString *field in imageFieldNames) {
NSString *name = [self.item valueForKey:field];
if (name.length > 0) {
UIImageView *iv = [[UIImageView alloc] init];
iv.contentMode = UIViewContentModeScaleAspectFit;
[self loadImage:iv withFile:name];
iv.frame = CGRectMake(xPos, 0.0,
self.imageScrollView.frame.size.width,
self.imageScrollView.frame.size.height);
[self.imageScrollView addSubview:iv];
xPos += self.imageScrollView.frame.size.width;
}
}

Autolayout Constraints on empty UIView do not work as expected

So I have setup a UIView that contains a UIScrollView (and child content view) that has sub views that are series of UILabels and UIViews that grow and shrink depending on the content contained in them, all using AutoLayout from the Storyboard. This works when I have something like Label - Label - Label - View w/o any issues, however if I put an empty UIView in-between two labels and insert sub views on the UIView, I'm not seeing the results I'm expecting. I have the following layout in a storyboard:
...where the teal and blue views are labels that grow to infinite height and the orange view (optionsPanel) is an empty UIVIew that I later inject sub views into. The rest of the stuff on the window is UILabels and UISegment controls. Between each row of views I have a Vertical Space constraint with a constant of 8. This all worked beautifully until I had to put in the empty UIView and programmatically inject sub views. The code I would expect to work would be something like (optionsPanel is the orange colored UIView)...
optionsPanel.translatesAutoresizingMaskIntoConstraints = YES;
NSArray *options = [product objectForKey:#"options"];
lastTop = 10;
for(int i=0;i<options.count; i++) {
NSDictionary *option = [options objectAtIndex:i];
NSArray *values = [option objectForKey:#"values"];
if([self hasNoneValue:values] && values.count == 2) {
NSDictionary *value = [self notNoneValue:values];
M13Checkbox *optionCheck = [[M13Checkbox alloc] initWithTitle:[option objectForKey:#"name"]];
optionCheck.frame = CGRectMake(0, lastTop, 280, 25);
[optionsPanel addSubview:optionCheck];
lastTop += 25;
} else {}
}
...where the orange UIView would magically grow and everything would just get pushed around accordingly, however this is what I'm seeing:
...the orange UIView does not grow at all, and the other two top UIView have gone somewhere off the screen. So my next guess was to turn off the Autoresizing Mask using...
optionsPanel.translatesAutoresizingMaskIntoConstraints = NO;
...but I'm getting a result where everything appears to be working but the orange UIView (optionsPanel) has no height for whatever reason and looks like:
This is getting closer to what I would expect, so I thought I would force the height of the orange UIView using code like...
frame = optionsPanel.frame;
frame.size.height = lastTop;
optionsPanel.frame = frame;
...but this appears to have no affect on anything.
Purely guessing, I found that this code almost works, if I arbitrary set the optionPanel's origin to something much larger than the space that is needed....
optionsPanel.translatesAutoresizingMaskIntoConstraints = YES;
NSArray *options = [product objectForKey:#"options"];
lastTop = 10;
for(int i=0;i<options.count; i++) {
NSDictionary *option = [options objectAtIndex:i];
NSArray *values = [option objectForKey:#"values"];
if([self hasNoneValue:values] && values.count == 2) {
NSDictionary *value = [self notNoneValue:values];
M13Checkbox *optionCheck = [[M13Checkbox alloc] initWithTitle:[option objectForKey:#"name"]];
optionCheck.frame = CGRectMake(0, lastTop, 280, 25);
[optionsPanel addSubview:optionCheck];
lastTop += 25;
} else {}
}
lastTop += 10;
frame = optionsPanel.frame;
frame.size.height = lastTop;
frame.origin.y += 300; //some arbitrarily‎ large number
optionsPanel.frame = frame;
..which gives this result:
...but apparently the AutoLayout has decided that the name label needs to take up the extra space. Its an ugly approach but if I could figure out how much space I need then I could just push everything down, if I had to. What's the secret to having a dynamic UIView between two dynamically sized labels and everything just work???
As #Timothy says, you need to manually add constraints to the subviews of the orange view if you want it to resize based on its contents—views don’t do this by default.
In general, if you’re using autolayout in a window, you should never be manually setting the frame of any view. Autolayout overrides any frames you set the every time it’s called, so even if you manage to manually get it working for a second it’ll fail the next time anything triggers a layout.
For views created in code, it's perfectly fine to set their frames as long as their translatesAutoresizingMaskIntoConstraints property is YES (the default, by the way).
However, for a view instantiated in storyboard or a nib, you can not set its translatesAutoresizingMaskIntoConstraints to YES.

iOS: Autolayout causing UIScrollView to not scroll

I have set up a UIScrollView with which I want to display 12 images (only 8 fit on screen) laid out horizontally. In the following image you can see the problem I'm having (which makes my scroll view not scroll), my constraints and the UIScrollView which I have added on storyboard:
I have called the following method on -(void)viewDidLoad, where I "set up"my scrollview (itemList is my scroll view property and itemNames a array with the images'names):
- (void)setupHorizontalScrollView
{
self.itemList.delegate = self;
[self.itemList setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.itemList setBackgroundColor:[UIColor blackColor]];
[self.itemList setCanCancelContentTouches:NO];
self.itemList.indicatorStyle = UIScrollViewIndicatorStyleWhite;
self.itemList.clipsToBounds = NO;
self.itemList.scrollEnabled = YES;
self.itemList.pagingEnabled = NO;
NSInteger tot=0;
CGFloat cx = 0;
for (; ; tot++) {
if (tot==12) {
break;
}
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:[self.itemNames objectAtIndex:tot]]];
CGRect rect = imageView.frame;
rect.size.height = 40;
rect.size.width = 40;
rect.origin.x = cx;
rect.origin.y = 0;
imageView.frame = rect;
[self.itemList addSubview:imageView];
cx += imageView.frame.size.width;
}
[self.itemList setContentSize:CGSizeMake(cx, [self.itemList bounds].size.height)];
}
I have added the [self.itemList setTranslatesAutoresizingMaskIntoConstraints:NO]; because I saw this suggestion on other posts, but it doesn't work with or without it. The only way it works is if I uncheck use AutoLayout on the storyboard, but that moves the UIImageViewI use to look as a navigation bar to the bottom of the screen.
I don't know what to do anymore, any help is appreciated :)
Try to set your scrollView's Content size int "viewDidLayoutSubviews" method with keeping the autolayouts set.
-(void)viewDidLayoutSubviews
{
[self.itemList setContentSize:CGSizeMake(required_width, required_height)];
}
Two Solutions:
Create different constraints that can be satisfied simultaneously (you will have to edit). I think the problem is your bottom space and top space constraints are mutually exclusive. please remove one and try again. IF this is difficult for you, try adding another UIView to contain the UIScrollView to help manage your constraints, it might seem odd at first, but sometimes adding another view to contain your view actually makes it simpler at each level.
Turn off Autolayout, and change the autoresize masks of your UIImageView to be what you wish.
Insert: [scrollView setContentSize:CGSizeMake(x,y)]; in the following method:
-(void)viewWillAppear:(BOOL)animated

Scrolling UILabel like a marquee in a subview

I have a UILabel in the main view with text - "Very Very long text". The proper width to this would be 142, but i've shortened it to 55.
Basically I want to implement a marquee type scroll, so I wrote code to add it onto a subview and animate it within the bounds of that view.
CODE --
CGRect tempLblFrame = _lblLongText.frame;
UIView *lblView = [[UIView alloc] initWithFrame:tempLblFrame];
//Add label to UIView at 0,0 wrt to new UIView
tempLblFrame.origin.x = 0;
tempLblFrame.origin.y = 0;
[_lblLongText setFrame:tempLblFrame];
[_lblLongText removeFromSuperview];
[lblView addSubview:_lblLongText];
//SetClipToBounds so that if label moves out of bounds of its superview, it wont be displayed
[lblView setClipsToBounds:YES];
[lblView setBackgroundColor:[UIColor cyanColor]];
[self.view addSubview:lblView];
After this I get this output on the simulator -->
The problem occurs when i try the Animation with this code -
tempLblFrame.origin.x = -_lblLongText.intrinsicContentSize.width;
[UIView animateWithDuration:2.0 delay:1.0 options:UIViewAnimationOptionCurveLinear
animations:^{
[_lblLongText setFrame:tempLblFrame];
}
completion:^(BOOL finished) {
NSLog(#"completed");
}];
I was hoping I would see the entire "Very Very long text", rather only "Very..." scrolls from left to right.
To solve this I added one line of code --
//Add label to UIView at 0,0 wrt to new UIView
tempLblFrame.origin.x = 0;
tempLblFrame.origin.y = 0;
tempLblFrame.size.width = _lblLongText.intrinsicContentSize.width; //THIS LINE WAS ADDED
[_lblLongText setFrame:tempLblFrame];
[_lblLongText removeFromSuperview];
[lblView addSubview:_lblLongText];
I thought the full text will be set inside the newly added UIView and it would scroll properly. But running in the simulator gave me this --
And again, only "Very..." was scrolling from left to right.
What am I doing wrong? Please help!!
EDIT
Apparently the culprit was AutoLayout.
I have no clue why, but once I unchecked "Use Autolayout" for the view
in the XIB, everything started working as expected. Setting
tempLblFrame.origin.x = -_lblLongText.intrinsicContentSize.width; was
working properly and so was the scroll.
Any explanation on this!!?
This question is possibly Duplicate of.
Although there is nice code snippet written by Charles Powell for MarqueeLabel,
also take a look at This link.
I hope this will help you and will save your time by giving a desired output.
Make the UILabel the width (or longer) of the text and the UIView the scroll area you want to see. Then set the UIView's clipToBounds to YES (which you are doing). Then when you animate left to right you will only see the the text the width of the UIView, since it is cutting any extra subviews. Just make sure you scroll the entire length of the UILabel.
Right now you are setting the view and label's height and width to the same thing. This is why you are getting clipped text, not a clipped label.
You add In your view scrollview and add this label in your scroll view .Use this code
scroll.contentSize =CGSizeMake(100 *[clubArray count],20);
NSString *bname;
bname=#"";
for(int i = 0; i < [clubArray count]; i++)
{
bname = [NSString stringWithFormat:#"%# %# ,",bname,[[clubArray objectAtIndex:i] objectForKey:#"bottle_name"]];
[bname retain];
}
UILabel *lbl1 = [[UILabel alloc] init];
[lbl1 setFrame:CGRectMake(0,5,[clubArray count]*100,20)];
lbl1.backgroundColor=[UIColor clearColor];
lbl1.textColor=[UIColor whiteColor];
lbl1.userInteractionEnabled=YES;
[scroll addSubview:lbl1];
lbl1.text= bname;
This is implemented code.Thanks
Apparently the culprit was AutoLayout.
I have no clue why, but once I unchecked "Use Autolayout" for the view in the XIB, everything started working as expected. Setting tempLblFrame.origin.x = -_lblLongText.intrinsicContentSize.width; was working properly and so was the scroll.
Still, a better explanation for this would surely help!!
EDIT: Solution with AutoLayout -
//Make UIView for Label to sit in
CGRect tempLblFrame = _lblLongText.frame;
UIView *lblView = [[UIView alloc] initWithFrame:tempLblFrame];
//#CHANGE 1 Removing all constraints
[_lblLongText removeConstraints:_lblLongText.constraints];
//Add label to UIView at 0,0 wrt to new UIView
tempLblFrame.origin.x = 0;
tempLblFrame.origin.y = 0;
//Set Full length of Label so that complete text shows (else only truncated text will scroll)
tempLblFrame.size.width = _lblLongText.intrinsicContentSize.width;
//#CHANGE 2 setting fresh constraints using the frame which was manually set
[_lblLongText setTranslatesAutoresizingMaskIntoConstraints :YES];
[_lblLongText setFrame:tempLblFrame];
[_lblLongText removeFromSuperview];
[lblView addSubview:_lblLongText];

scrollview is showing only the last added subview

I am getting strange behavior with UIScrollView subviews, the idea is to create programmatically an instance of UIView with a customized nib file which is a form in my case, fill that form with data from a model class, and add it as subview for my UIScrollView. The problem is when I deal with more than one subview, the UIScrollView only keep the latest subview, so if I created three subviews, the scrollview will show only the third (the latest) subview. Although the page control is set to the coreect number of subviews (three).
The project is too long, so I will try to explain brievely my issue with the relevant code:
- (void)viewDidLoad {
NSArray *sorted = [appArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];//this array contains the data from model, I debugged that to make sure I got the exact data, no more no less :)
//Loop all NSManaged objects, for example let's say I have 3 model objects, don't worry about how I get data etc, because I debugged all and maked sure all data objects number are exact, etc.
for (App_Table *_appTable in sorted) {
//This looped 3 times as expected, I debugged that also and maked sure on each iteration I got the data I expected to have
//App_Table is a subclass of NSManagedObject, it's my model class and get its data from coredata file
[self addMoreView];//Call this method will create a new subview and add it as subview to the UIScrollView, it will also update the page control, update the content size property, etc.
AppTableView *_appTableView = (AppTableView *) [[self.scrollView subviews] lastObject];//Remember addMoreView method create a new instance of AppTableView and add it as subview for the UIScrollView property, then I get that subview to fill it with data here
_appTableView.txtADDRESS.text = _appTable.address;//Fill in the form, no need to write all the form fields code because it's the same way.
// Scroll To First Page...
self.pageControl.currentPage = 0;
[self.scrollView scrollRectToVisible:CGRectMake(0, 0, viewWidth, viewHeight) animated:YES];
self.scrollView.contentSize = CGSizeMake(viewWidth*noOfItems, viewHeight);//Set the content size to the sum of subviews width, I also debugged that to check it's correct
self.scrollView.delegate = self;
[super viewDidLoad];
}
Ok, so when I have three subviews, the scrollview will load with the width of three subviews as calculated with the line above:
self.scrollView.contentSize = CGSizeMake(viewWidth*noOfItems, viewHeight);//Set the content size to the sum of subviews width, I also debugged that to check it's correct
And I can scroll till 3 moves (which is the number of subviews) and the UIPageControl is also set to three dots, but only ONE subview is visible, only the latest one I can see, the two other subviews disappeared, the scroll view calculated content size for them but they are not visible. Any thoughts ? Thanx.
EDIT:
It's worth to note that the first time I edit the view, all goes fine, when I deal with 3 subviews, they are all visible, but when I go to another view and get back to this view, only the last subview is visible.
Also, I am working on an iPad project for that with a split view.
EDIT:
This is the code of the method which draw new subview for the UIScrollView
-(IBAction) addMoreView
{
NSArray *arr = [[NSBundle mainBundle] loadNibNamed:#"AppTableView" owner:self options:nil];
AppTableView *aView = [arr objectAtIndex:0];
[aView setFrame:CGRectMake(startX, 0, aView.bounds.size.width, aView.bounds.size.height)];
[self.scrollView addSubview:aView];
self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width+aView.frame.size.width
, self.scrollView.contentSize.height);
startX = startX + aView.frame.size.width;//Update the X position, first 0, then 600, 1200, and so on.
[self.scrollView scrollRectToVisible:aView.frame animated:YES];
NSLog(#"%f",self.scrollView.contentSize.width);
NSLog(#"%f",self.scrollView.contentSize.height);
}
Suppose I have 3 subviews, the method above will be called 3 times since it's put inside the for loop. So here is the result of NSLogs for the above method:
NSLog(#"%f",self.scrollView.contentSize.width);
NSLog(#"%f",self.scrollView.contentSize.height);
First iteration:
600.000000
0.000000
Second iteration:
1200.000000
0.000000
Third iteration:
1800.000000
0.000000
EDIT:
The command suggested by rob mayoff allows me to see why this happen:
| | | | <AppTableView: 0x1eef4700; frame = (0 18; 600 430); autoresize = LM+RM+TM+BM; tag = 990; layer = <CALayer: 0x1eef4790>>
| | | | <AppTableView: 0x1ee3f380; frame = (0 18; 600 430); autoresize = LM+RM+TM+BM; tag = 991; layer = <CALayer: 0x1ee3f410>>
| | | | <AppTableView: 0x1ee56910; frame = (0 18; 600 430); autoresize = LM+RM+TM+BM; tag = 992; layer = <CALayer: 0x1ee3f410>>
All Three subviews are drawn in the same frame, so they are above each other, but when I debug that with breakpoints in runtime, the x is changing, first time 0, then 600 then 1200 which make me think it's drawing correctly. How to fix that especially that the x value is being incremented correctly, so what's the problem and why they still drawing on the same x coordinate?
First things first, you should place the [super viewDidLoad] call at the top of your - (void)viewDidLoad method, not at the bottom.
Given that it's not clear enough what you are actually seeing and what you expect to see, providing a minimum working example of your problem as a downloadable project would help us to help you. If you just try to reproduce this in a separate view controller that does not interact with managed objects, but uses some statically provided data, others will be able to reproduce it themselves and debug it. Or you might just as well figure it out yourself in the process.
I am getting back a bit late, but finally I figure out the fix for my problem so thought it's good to share it to save someone else's time.
For my case, the scrollview is a custom view in a nib file. So by activating its Autosizing masks left and top, the bug was fixed.
This block of code in -addMoreView is the problem:
AppTableView *aView = [arr objectAtIndex:0];
[aView setFrame:CGRectMake(startX, 0, aView.bounds.size.width, aView.bounds.size.height)];
[self.scrollView addSubview:aView];
If startX never changes, you're adding all of these views at the same spot.
Surely your not setting the content size correctly for 3 items:
self.scrollView.contentSize.width+aView.frame.size.width
should be
self.scrollView.contentSize.width+ ( aView.frame.size.width * NUMBER_OF_SUBVIEWS)
I presume when you scroll horizontally on the scrollview, the content size only allows enough room for one subview correct ?
used a static variable which is reinitialized to 0 on load, then it gets incremented by the width of the subview to start drawing on new X position.
Try to set the autoresizingMask of aView to UIViewAutoresizingNone inside the addMoreView method.
I created a project that uses your code in the sister post, and its working just fine. I added several tests on the autoresizing masks as you can see below. I suggest you compare it to what you are doing. Weird things happen when the masks on EITHER the subviews or the scrollView are not set to being tied to the top/left.
My modified code:
NSArray *colors = #[ [UIColor redColor], [UIColor greenColor], [UIColor blueColor] ];
UIViewAutoresizing mask;
mask = [self.scrollView autoresizingMask];
//assert(!self.scrollView.autoresizesSubviews); // has no affect here
if((mask & (UIViewAutoresizingFlexibleRightMargin|UIViewAutoresizingFlexibleBottomMargin)) != mask) NSLog(#"scrollView MASK WRONG");
for (int i=0; i<3; ++i) {
NSArray *arr = [[NSBundle mainBundle] loadNibNamed:#"AppTableView" owner:self options:nil];
NSLog(#"scrollView frame: %#", NSStringFromCGRect(self.scrollView.frame));
if((mask & (UIViewAutoresizingFlexibleRightMargin|UIViewAutoresizingFlexibleBottomMargin)) != mask) NSLog(#"aView MASK WRONG");
AppTableView *aView = [arr objectAtIndex:0];
assert([aView isKindOfClass:[AppTableView class]]);
NSLog(#"orig aView frame: %#", NSStringFromCGRect(aView.frame));
UIViewAutoresizing mask = [aView autoresizingMask];
if((mask & (UIViewAutoresizingFlexibleRightMargin|UIViewAutoresizingFlexibleBottomMargin)) != mask) NSLog(#"aView MASK WRONG");
aView.frame = CGRectMake(startX, 0, aView.bounds.size.width, aView.bounds.size.height);
NSLog(#"changed aView frame: %#", NSStringFromCGRect(aView.frame));
aView.backgroundColor = colors[i];
[self.scrollView addSubview:aView];
self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width+aView.frame.size.width
,self.scrollView.contentSize.height);
startX = startX + aView.frame.size.width;
//AppTableView *_appTableView = (AppTableView *) [[self.scrollView subviews] lastObject];
//_appTableView.txtADDRESS.text = _appTable.address;//Fill in the form, no need to write all the form fields code because it's the same way.
}
NSLog(#"scrollView frame: %#", NSStringFromCGRect(self.scrollView.frame));
NSLog(#"scrollView contentOffset: %#", NSStringFromCGPoint(self.scrollView.contentOffset));

Resources