I wanted to create a simple slideshow of images using an UIScrollView and I noticed that, using the new AutoLayout mode, the operation became way more complicated compared to iOS5. I wasn't able to find any SIMPLE and SHORT example/tutorial for accomplishing it. There was a lot of material about "pure" and "hybrid" approaches,but honestly nothing really worked for me. Maybe the material is not clear enough, maybe I'm not good enough..who knows. Anyway, I thought it could be useful sharing my finding and the consequent snippet of code which is currently doing the job for me. Here we go:
- (void)setupSlideshow {
NSInteger nPhotos = [self.profilePhotos count];
UIScrollView *scrollView;
UIImageView *imageView;
NSDictionary *viewsDictionary;
// Create the scroll view and the image view.
scrollView = self.slideShow;
CGFloat sWidth = scrollView.frame.size.width;
CGFloat sHeight = scrollView.frame.size.height;
UIView *container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, nPhotos * sWidth, sHeight)];
scrollView.pagingEnabled = YES;
CGFloat cx = 0;
for (int i = 0; i < nPhotos; i++) {
imageView = [[UIImageView alloc] init];
// Add an image to the image view.
[imageView setImage:[UIImage imageNamed:self.profilePhotos[i]]];
CGRect imFrame = imageView.frame;
imFrame.origin.x = cx;
imFrame.origin.y = 0;
imFrame.size.width = sWidth;
imFrame.size.height = sHeight;
imageView.frame = imFrame;
[container addSubview:imageView];
cx += sWidth;
}
container.translatesAutoresizingMaskIntoConstraints = NO;
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[scrollView addSubview:container];
// Set the constraints for the scroll view and the image view.
viewsDictionary = NSDictionaryOfVariableBindings(scrollView, container);
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"H:|-0-[container(%f)]-0-|",cx]
options:0
metrics: 0
views:viewsDictionary]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[container]-0-|" options:0 metrics: 0 views:viewsDictionary]];
}
The scrollView has been instantiated in the StoryBoard.
As you can notice, I adopted an approach based on a UIView container for the pictures I want to use which has been added to a scrollview. I have then added some constraints using the Visual Constraints Format notation. I tried to match the width of the container to the scroller's one using this:
[NSString stringWithFormat:#"H:|-0-[container(==scrollView)]-0-|"]
but it doesn't work (to be precise it doesn't scroll but sticks to the first image), so I opted for a dynamic approach using a simple string format.
As I said, it does the job, but I'm sure I can make it way more elegant. Besides that, is it possible to fix the problem relative the dynamic width?
I hope to receive some useful feedback, in order to refine this example. I will then setup a GitHub for making it publicly available for all of those who spent 3 days banging their head against a wall...like I did.
I think I have a fundamental issue with these new constraints.
Imagine you put 5 labels all going from top to bottom in a UIView. The top label is equally spaced between the top of the view and the next label underneath it. Then each label is also equally spaced.
What I would like to happen is that when the UIView height is made less or more the space between each label changes equally?
What should I be aiming for. Should I make the 3rd label vertically centred in the view and then add constraints to the other labels from the centre or should 1 and 2 be constrained to the top of the view, 3 centre and 4 and 5 to the bottom?
Thanks in advance for all your suggestions.
Edit:
After the below answer was added I have played around with the constraints in interface builder and whilst I am not 100% sure I have answered it I think I have it working.
Between all labels I have a certain distance. But I also have a certain minimum distance like following apples HIG I think it is 15 between labels. So mine are at 30 and I set a constraint of greater than or equal to 15. Now iOS does all the moving around from 30 to 15 for me when the superview changes.
I originally did this with buttons, and the only way I could make this work so that it would automatically adjust to the view frame size (on rotation) was to add labels (with no text, so they're invisible) between each button, and between the containing view and the first and last button. The buttons all had an intrinsic size and the labels did not, so they were free to change their heights to fill the space. I modified the code to use all labels, with the "b" labels being the ones with text.
-(void)viewDidLoad {
[super viewDidLoad];
NSMutableDictionary *viewsDict = [NSMutableDictionary dictionary];
for (int i=1; i<5; i++) {
UILabel *b = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 150, 44)];
b.text = #"This is my label";
[b setTranslatesAutoresizingMaskIntoConstraints:NO];
[viewsDict setObject:b forKey:[NSString stringWithFormat:#"b%d",i]];
}
for (int i=1; i<6; i++) { // these are the spacer labels
UILabel *l = [[UILabel alloc ]init];
[l setTranslatesAutoresizingMaskIntoConstraints:NO];
[viewsDict setObject:l forKey:[NSString stringWithFormat:#"l%d",i]];
}
for (id obj in viewsDict.allKeys)
[self.view addSubview:viewsDict[obj]];
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[l1][b1][l2(==l1)][b2][l3(==l1)][b3][l4(==l1)][b4][l5(==l1)]|"
options:NSLayoutFormatAlignAllLeading
metrics:nil
views:viewsDict];
NSArray *constraints2 = [NSLayoutConstraint constraintsWithVisualFormat:#"|-[b1]"
options:0
metrics:nil
views:viewsDict];
[self.view addConstraints:constraints];
[self.view addConstraints:constraints2];
}
This will produce evenly spaced labels with the standard space to the left edge of the view.
I am looking to create a representation of the sidebar shown in Keynote for iPad. How can I do this for iOS? I would imagine that a UITableView is needed, but not quite sure on the saving of the current screen?
Already seen one example project , but wasn't too much help
use this link I think this will helpful for you.first try with this code.
You will try this code add one scrollview on nib and set its contentsize on viewdidload
CGFloat x = 20;
for (int i = 0;i<[playerScores count];i++) {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(x, 10, 60, 60)];
view.backgroundColor=[UIColor blueColor];
NSString *text = [NSString stringWithFormat:[playerScores objectAtIndex:i]];
[view setText:text];
[yardsScrollView addSubview:view];
[myLabel release];
x = x+125;
}
PlayersScore is a NSMutable array which have many text like how much view You want.
then also check this.
https://github.com/bjhomer/HSImageSidebarView
I have two little problems with my UIScrollView.
I managed to create scroll view, which is similar to the picker view except that it's horizontal.
The problem n.1 is - How do I get the number which the user tapped?
The problem n.2 is - How do I make the scrollview to go round and round - never-ending?
A question - Is it possible to make some "selection indicator"?
Here is my code:
numberArray = [NSArray arrayWithObjects:#"1", #"2", #"3", #"4", #"5",
#"6", #"7", #"8", #"9", #"10", #"11", #"12", #"13", #"14", #"15",
#"16", #"17", #"18", #"19", #"20", #"21", #"22", #"23", #"24", #"25",
#"26", #"27", #"28", #"29", #"30", #"31", #"32", #"33", #"34", #"35",
#"36", #"37", #"38", #"39", #"40", #"41", #"42", #"43", #"44", #"45",
#"46", #"47", #"48", #"49", #"50", #"51", #"52", #"53", #"54", #"55",
#"56", #"57", #"58", #"59", #"60", #"61", #"62", #"63", #"64", #"65",
#"66", #"67", #"68", #"69", #"70", #"71", #"72", #"73", #"74", #"75",
#"76", #"77", #"78", #"79", #"80", #"81", #"82", #"83", #"84", #"85",
#"86", #"87", #"88", #"89", #"90", #"91", #"92", #"93", #"94", #"95",
#"96", #"97", #"98", #"99", #"100", nil];
masterDividerView = [[UIView alloc] initWithFrame:CGRectMake(0, 480, 320, 44)];
morePeople = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 320, 53)];
morePeople.backgroundColor = [UIColor scrollViewTexturedBackgroundColor];
morePeople.delegate = self;
[morePeople setBackgroundColor:[UIColor whiteColor]];
[morePeople setCanCancelContentTouches:NO];
morePeople.showsHorizontalScrollIndicator = NO;
morePeople.showsVerticalScrollIndicator = NO;
morePeople.clipsToBounds = NO;
morePeople.scrollEnabled = YES;
morePeople.pagingEnabled = NO;
NSUInteger nimages = 0;
NSInteger tot=0;
CGFloat cx = 0;
for (; ; nimages++) {
NSString *label = [numberArray objectAtIndex:nimages];
if (tot==99) {
break;
}
if (99==nimages) {
nimages=0;
}
UILabel *labelView = [[UILabel alloc] init];
labelView.text = label;
labelView.lineBreakMode = UILineBreakModeWordWrap;
labelView.numberOfLines = 0;
[labelView sizeToFit];
labelView.backgroundColor = [UIColor scrollViewTexturedBackgroundColor];
CGRect rect = labelView.frame;
rect.size.height = 53;
rect.size.width = 40;
rect.origin.x = cx;
rect.origin.y = 0;
labelView.frame = rect;
[morePeople addSubview:labelView];
cx += labelView.frame.size.width+5;
tot++;
}
self.pageControl.numberOfPages = nimages;
[morePeople setContentSize:CGSizeMake(cx, [morePeople bounds].size.height)];
[masterDividerView addSubview:morePeople];
[self.view addSubview:masterDividerView];
If anybody knows a good solution to this, I would be very happy!!!! :))
Your question is too wide to make a detailed answer, but yes, everything of what you're talking about can be solved in a quite easy way.
The problem n.1 is - How do I get the number which the user tapped?
If you mean to know what element is chosen by your UIScrollView at the moment, then it's not a problem: UIScrollView always knows its position (contentOffset), which gives you the easy possibility to define, which object is chosen (which object you're working with) now.
If you mean to know when the user tapped by his finger one of the elements of your "picker view" (UIScrollView), then I would say that the answer to this depends on how you actually represent your data (like if you see one or several elements of your scrollView at a time on your screen). But in any case you can easily solve this by using UITapGestureRecognizer. Smth like:
// add gesture recognizers to the scroll view
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
[singleTap setNumberOfTapsRequired:1];
[self.yourScrollView addGestureRecognizer:singleTap];
[singleTap release];
And then to scroll it programmatically in your selector you do smth like:
- (void)handleSingleTap:(UIGestureRecognizer *)gestureRecognizer
{
CGPoint pointOfTouch = [gestureRecognizer locationInView:self.view];
if ( (pointOfTouch.x > self.rightArrowMin) && (pointOfTouch.x < self.rightArrowMax) ) {
[scrollView setContentOffset:CGPointMake(lastOffset + self.lengthOfLabel, 0) animated:YES];
} else if ( (pointOfTouch.x > self.leftArrowMin) && (pointOfTouch.x < self.leftArrowMax) ) {
[scrollView setContentOffset:CGPointMake(lastOffset - self.lengthOfLabel, 0) animated:YES];
}
}
The problem n.2 is - How do I make the scrollview to go round and round - never-ending?
It can be solved if you know the algorithm of calculating the next/previous or random element in the sequence of elements of your scrollView.
Basically, I solved this same thing for myself in one of my projects (free app "IJCAI11" in the appStore, there you can see at the work of a datePicker in the details of any chosen conference's day: there is applied the algorithm of infinite scrolling view, the limits I applied later only from design point of view).
The scheme I use there is simple, though perhaps a bit weird (before reading ahead, note that in my case I see only one element of scrollView on the screen, when the scrollView is in nonscrolling state):
I create the UIScrollView with 3 (in case you see more than 1 element at a time this can be like 5, 7, ..., 2N+1 according to your very case) labels;
Make the second (central) label active.
Assign proper value to all 3 (2N+1) labels. On this step your second (central) label will contain data of (will show) the default value.
When user scrolls one step left/right (to the position M_pos), you do it in a standard way, since your neighboring to central labels contain correct data (M).
Then you take this new active value (M) and apply it for your central label (this is gonna be behind the scene, therefore the user wont see this on the screen), then you recalculate ALL the other labels properly ( ..., M-1, M, M+1, ...), including your M_pos position(!).
With "animated:NO" mode you shift your scrollView to your central position. In this case user doesn't see anything, when in fact his scrollView was moved from position M_pos to the central position. All neighboring labels are already recalculated and you are ready to repeat the point 4 any number of times you wish, making a strong feeling for user, that he works with the scrollView with a huge number of elements (whereas in practice your just work with 3 (2N+1) labels, changing their content after every movement).
A question - Is it possible to make some "selection indicator"?
If I understand this question correctly, then just consider the View/Subview thing and in particular the function addSubview of View class ;)
Hope this helps!
I'm not sure if I understood your question right, but did you already think of using a NSTimer to repeat events with a certain speed or during a certain period of time? (Pointing at your "round and round thing".)
I'm using the following code in my root view controller class implementation:
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *segmentTextContent = [NSArray arrayWithObjects:
NSLocalizedString(#"button1", #""),
NSLocalizedString(#"button2", #""),
NSLocalizedString(#"button3", #""), nil];
UISegmentedControl *segmentedControl = [[UISegmentedControl alloc] nitWithItems:segmentTextContent];
segmentedControl.selectedSegmentIndex = 0;
segmentedControl.autoresizingMask = UIViewAutoresizingFlexibleWidth;
segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
segmentedControl.frame = CGRectMake(0, 0, 400, 30);
self.navigationItem.titleView = segmentedControl;
[segmentedControl release];
}
The segmented control is not centered correctly, it is shifted a few pixels to the right. The funny thing is, it is not right-aligned either. It's as if there is some left padding preventing it from being centered. Another funny thing is, if I draw this segmented control in interface builder, it centers perfectly, so this is only a programmatic issue.
I've tried this code in a brand new empty app and get the same results, so I know it's not something else in my app that is causing it.
Sorry, I wanted to post a screen shot, but I don't have enough reputation points ;-)
Any Ideas?
There is no left button, I've even tried doing:
self.navigationItem.leftBarButtonItem = nil;
self.navigationItem.backBarButtonItem = nil;
As I said, it works in IB, just not in code. And it also fails in a brand new project - you can try it for yourself, and see...
I seemed to have found the fix. The 400 specified for the width was causing the problem. I specified 300, and now it is centered correctly. I supposed ideally I should be grabbing some global screen width value instead of hard-coding 300.