I'm a beginner with iOS, so i'm just not sure what to research here. I have a UIScrollView with a few square subViews added. How can i make the subviews smaller as they scroll off screen and bigger as they approach the center of the screen?
#import "HorizontalScrollMenuViewController.h"
#import <UIKit/UIKit.h>
#define SUBVIEW_WIDTH_HEIGHT 280
#interface HorizontalScrollMenuViewController : UIViewController
#property (nonatomic, strong) IBOutlet UIScrollView *scrollView;
#end
#implementation HorizontalScrollMenuViewController
-(void)viewDidLoad{
[super viewDidLoad];
NSArray *colors = [NSArray arrayWithObjects:[UIColor greenColor],[UIColor redColor],[UIColor orangeColor],[UIColor blueColor],nil ];
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
CGFloat screenHeight = screenRect.size.height;
CGFloat originX = (screenWidth - SUBVIEW_WIDTH_HEIGHT)/2.0; // get margin to left and right of subview
CGFloat originY = ((screenHeight - SUBVIEW_WIDTH_HEIGHT)/2);
// add subviews of all activities
for (int i = 0; i < colors.count; i++){
CGRect frame = CGRectMake(0,0,SUBVIEW_WIDTH_HEIGHT,SUBVIEW_WIDTH_HEIGHT);
frame.origin.x = self.scrollView.frame.size.width * i + originX;
frame.origin.y = originY;
UIView *subView = [[UIView alloc] initWithFrame:frame];
[UIView setAnimationBeginsFromCurrentState: YES];
subView.layer.cornerRadius = 15;
subView.layer.masksToBounds = YES;
subView.backgroundColor = [colors objectAtIndex:i];
[self.scrollView addSubview:subView];
}
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width * colors.count, self.scrollView.frame.size.height);
}
#end
Here you can find a fully working example of what you're trying to accomplish. It only has
one subview because it's just to give you an idea of how can you accomplish it. Also, this example was tested on an iPad (iOS7) simulator.
The *.h file
#import <UIKit/UIKit.h>
// Remember to declare ourselves as the scroll view delegate
#interface TSViewController : UIViewController <UIScrollViewDelegate>
#property (nonatomic, strong) UIView *squareView;
#end
The *.m file
#import "TSViewController.h"
#implementation TSViewController
#synthesize squareView = _squareView;
- (void)viewDidLoad
{
[super viewDidLoad];
// Create and configure the scroll view (light gray)
UIScrollView *myScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(100, 100, 500, 500)];
CGRect contentSize = myScrollView.frame;
contentSize.size.height = contentSize.size.height + 400;
myScrollView.contentSize = contentSize.size;
myScrollView.userInteractionEnabled = YES;
// give the scroll view a gray color so it's easily identifiable
myScrollView.backgroundColor = [UIColor lightGrayColor];
// remember to set yourself as the delegate of the scroll view
myScrollView.delegate = self;
[self.view addSubview:myScrollView];
// Create and configure the square view (blue)
self.squareView = [[UIView alloc] initWithFrame:CGRectMake(200, 400, 60, 60)];
self.squareView.backgroundColor = [UIColor blueColor];
[myScrollView addSubview:self.squareView];
}
// Here is where all the work happens
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
// Get the difference between the contentOffset y position and the squareView y position
CGFloat y = self.squareView.frame.origin.y - scrollView.contentOffset.y;
// If the square has gone out of view, return
if (y <= 0) {
return;
}
// Modify the squareView's frame depending on it's current position
CGRect squareViewFrame = self.squareView.frame;
squareViewFrame.size.height = y + 5;
squareViewFrame.size.width = y + 5;
squareViewFrame.origin.x = (scrollView.contentSize.width - squareViewFrame.size.width) / 2.0;
self.squareView.frame = squareViewFrame;
}
#end
And here is a little explanation of what is going on:
A UIScrollView has several properties that allow you to configure it correctly. For example it has a frame (gray) which is inherited from UIView; with this property you specify the visible size of the scroll view. It also has the contentSize (red) which specifies the total size of the scroll view; in the image it's showed as the red area but this is only for illustration purposes as it will not be visible in the program. Imagine the scroll view's frame as the window that let's you see only a part of the bigger content the scroll view has.
When the user starts scrolling a gap appears between the top part of the contentSize and the top part of the frame. This gap is known as the contentOffset
Here is the reference to UIScrollView
Here is the reference to UIScrollViewDelegate
Hope this helps!
Assuming that you have the scrollView inside self.view, you can implement scrollViewDidScroll: in the scroll view delegate to find when it is scrolled.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
for (UIView *view in self.scrollView.subviews) {
CGRect frame = [view convertRect:view.frame toView:self.view]; // Contains the frame of view with respect to self.view
}
}
You can them use the frame to resize subviews as you want.
The answer starts with analyzing the UIScrollView Class Reference and it's delegate. In the delegate documentation see the responding to scrolling and dragging section. You should also review the sample code for each. You can create outlets to your subviews and change the subview properties within a uiview animation. These references will give you a good foundation in understanding where you can build the call to animate the subviews.
Here is a link to animating subviews. Additional examples can be found by Googling "uiview subview animation" (without the quotes). If you run into any major issues read the header files first and post some sample code for additional (more precise) help.
Other reference:
UIKit ScrollViews
Related
I am making an app where I need some images can be scrolled through using a UIPageControl. Sadly, I don't know how to use a UIPageControl, or a UIScrollView either. A link to a YouTube video or Xcode documentation by Apple would be much appreciated!
Thanks in advance!!
here is a code which will help you
CGSize size = [[UIScreen mainScreen] bounds].size;
CGFloat frameX = size.width;
CGFloat frameY = size.height-30; //padding for UIpageControl
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(offsetX, offsetY, frameX, frameY)];
scrollView.pagingEnabled = YES;
scrollView.backgroundColor = [UIColor clearColor];
scrollView.contentSize = CGSizeMake(frameX, frameY);
[self.view addSubview: scrollView];
// let say you have array of image in imageArray
for(int i = 0; i < [imageArray]; i++)
{
UIImage *image = [UIImage imageNamed:[imageArray objectAtIndex:i]];
imageView = [[UIImageView alloc] initWithImage:image];
imageView.frame = CGRectMake(frameX * i, 0.0, frameX, frameY);
[scrollView addSubview:imageView];
}
scrollView.contentSize = CGSizeMake(frameX*[imageArray count], frameY);
please declare the variables according to scope of use. i have declared all the variables locally so that might not get confuse and also add the UIPageControl at the bottom of your viewcontroller
Implement the delegate method of UIScrollView to the current Page indicator
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
float width = scrollView.frame.size.width;
float xPos = scrollView.contentOffset.x+10;
//Calculate the page we are on based on x coordinate position and width of scroll view
pageControl.currentPage = (int)xPos/width;
}
where pageControl is UIPageControl added on bottom your viewcontroller
ScrollView:
ScrollView is used to display the more number views/Objects. We can display those views by horizontal or vertical scrolling.
You can handle zooming, panning, scrolling etc.
You can go through some of these tutorials
Are there any good UIScrollView Tutorials on the net?
https://developer.apple.com/library/ios/documentation/windowsviews/conceptual/UIScrollView_pg/CreatingBasicScrollViews/CreatingBasicScrollViews.html
Example Code for Scrollview:
UIScrollView *scroll = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0,self.view.frame.size.width, self.view.frame.size.height)];
//This will create a scrollview of device screen frame
You can enable the scrolling by
scroll.scrollEnabled = YES;
Example for adding 3 views to scrollview
NSInteger numberOfViews = 3;
for (int i = 0; i < numberOfViews; i++) {
CGFloat xOrigin = i * self.view.frame.size.width;
UIView *awesomeView = [[UIView alloc] initWithFrame:CGRectMake(xOrigin, 0, self.view.frame.size.width, self.view.frame.size.height)];
awesomeView.backgroundColor = [UIColor colorWithRed:0.5/i green:0.5 blue:0.5 alpha:1];
[scroll addSubview:awesomeView];
[awesomeView release];
}
// 3 views added horizontally to the scrollview by using xOrigin.
The most important part in this for is to understand the xOrigin. This will place every UIView exactly where the previous UIView has stopped, in other words, each UIView will start at the end of the previous one.
Now we got the scrollview with 3 views added horrizontally.
Set the UIScrollView contentSize
scroll.contentSize = CGSizeMake(self.view.frame.size.width * numberOfViews, self.view.frame.size.height);
The contentSize is just the sum of the widths of the three UIViews, if the width of each UIView is 320, and we have three UIViews, your contentSize width will be 920.
[self.view addSubview:scroll];
[scroll release];
//Add scrollview to viewcontroller
Page Control:
A Page control presents the user with a set of horizontal dots representing pages. The current page is presented as a white dot. The user can go from the current page to the next or to the previous page.
To enable paging you need to add
scroll.pagingEnabled = YES;
pageControl = [[UIPageControl alloc] init]; //SET a property of UIPageControl
pageControl.frame = CGRectMake(100,self.view.frame.size.height-100,self.view.frame.size.width-200,100);
pageControl.numberOfPages = 3; //as we added 3 diff views
pageControl.currentPage = 0;
Add delegate method of scrollview to implement the pagecontrol dots while scrolling
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat pageWidth = self.scrollView.frame.size.width;
int page = floor((self.scrollView.contentOffset.x – pageWidth / 2 ) / pageWidth) + 1; //this provide you the page number
pageControl.currentPage = page;// this displays the white dot as current page
}
Now you can see pagecontrol for scrollview. Hope you understand. Refer this too
https://developer.apple.com/library/ios/documentation/uikit/reference/UIPageControl_Class/Reference/Reference.html
This is a very good tutorial by the Ray Wenderlich team on how to set up a paging scrollview: How To Use UIScrollView to Scroll and Zoom Content
Apple's documentation also has an example of how to implement a paging scroll view: PhotoScroller
My answer to this previous question may also be useful to you.
I'm trying to resize a text view according to content & also it's sibling and parent container.
Below code is working fine in iOS 6
if (/* less than ios 7 */) {
CGRect frame = _textView.frame;
CGSize conSize = _textView.contentSize;
CGFloat difference = conSize.height - frame.size.height;
frame.size.height += difference;
_textView.frame = frame;
UIScrollView *parentView = (UIScrollView *)_textView.superview;
// adjust views residing below this text view.
// sibling view
UIView *belowView = // access it somehow
CGRect frame1 = belowView.frame;
frame1.origin.y += difference;
belowView.frame = frame1;
// adjust parent scroll view, increase height.
CGSize frame3 = parentView.contentSize;
frame3.height += difference;
parentView.contentSize = frame3;
} else {
// tried
[_textView sizeToFit];
[_textView layoutIfNeeded];
[parentView sizeToFit];
[parentView layoutIfNeeded];
}
Tried to follow iOS 7 solution from:
How do I size a UITextView to its content on iOS 7?
but not working.
Any pointers?
Working code solution from #NSBouzouki
if (/* ios 7 */) {
[_textView.layoutManager ensureLayoutForTextContainer:_textView.textContainer];
[_textView layoutIfNeeded];
}
CGRect frame = _textView.frame;
CGSize conSize = _textView.contentSize;
CGFloat difference = conSize.height - frame.size.height;
frame.size.height += difference;
_textView.frame = frame;
UIScrollView *parentView = (UIScrollView *)_textView.superview;
// adjust views residing below this text view.
// sibling view
UIView *belowView = // access it somehow
CGRect frame1 = belowView.frame;
frame1.origin.y += difference;
belowView.frame = frame1;
// adjust parent scroll view, increase height.
CGSize frame3 = parentView.contentSize;
frame3.height += difference;
parentView.contentSize = frame3;
It seems UITextView's contentSize property is not correctly set in iOS 7 till viewDidAppear:. This is probably because NSLayoutManager lays out the text lazily and the entire text must be laid out for contentSize to be correct. The ensureLayoutForTextContainer: method forces layout of the provided text container after which usedRectForTextContainer: can be used for getting the bounds. In order to get total width and height correctly, textContainerInset property must be taken into account. The following method worked for me.
- (CGRect)contentSizeRectForTextView:(UITextView *)textView
{
[textView.layoutManager ensureLayoutForTextContainer:textView.textContainer];
CGRect textBounds = [textView.layoutManager usedRectForTextContainer:textView.textContainer];
CGFloat width = (CGFloat)ceil(textBounds.size.width + textView.textContainerInset.left + textView.textContainerInset.right);
CGFloat height = (CGFloat)ceil(textBounds.size.height + textView.textContainerInset.top + textView.textContainerInset.bottom);
return CGRectMake(0, 0, width, height);
}
Additionally, it seems UITextView's setContentSize: method is called from layoutSubviews. So, calling layoutIfNeeded on a textView (which itself calls layoutSubviews) after calling ensureLayoutForTextContainer: on its layoutManager, should make the textView's contentSize correct.
[someTextView.layoutManager ensureLayoutForTextContainer:someTextView.textContainer];
[someTextView layoutIfNeeded];
// someTextView.contentSize should now have correct value
GrowingTextViewHandler is an NSObject subclass which resizes text view as user types text.
Here is how you can use it.
#interface ViewController ()<UITextViewDelegate>
#property (weak, nonatomic) IBOutlet UITextView *textView;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *heightConstraint;
#property (strong, nonatomic) GrowingTextViewHandler *handler;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.handler = [[GrowingTextViewHandler alloc]initWithTextView:self.textView withHeightConstraint:self.heightConstraint];
[self.handler updateMinimumNumberOfLines:3 andMaximumNumberOfLine:8];
}
- (void)textViewDidChange:(UITextView *)textView {
[self.handler resizeTextViewWithAnimation:YES];
}
#end
I want to achieve a proper perspective "tilt" on two separate side-by-side UIView squares. In the image below the red and green squares are separate UIViews with the same transform applied. Visually this perspective is incorrect (is it?), or at least the superior illusion is shown by the Yellow/Blue square UIViews. The Yellow-Blue squares are actually subviews of a rectangular parent UIView, and the transform was applied to the parent view.
Here's the code:
#interface PEXViewController ()
#property (strong, nonatomic) IBOutlet UIView *redSquare;
#property (strong, nonatomic) IBOutlet UIView *greenSquare;
#property (strong, nonatomic) IBOutlet UIView *yellowSquareBlueSquare;
#end
#implementation PEXViewController
#define TILT_AMOUNT 0.65
-(void)tiltView:(UIView *)slave{
CATransform3D rotateX = CATransform3DIdentity;
rotateX.m34 = -1 / 500.0;
rotateX = CATransform3DRotate(rotateX, TILT_AMOUNT * M_PI_2, 1, 0, 0);
slave.layer.transform = rotateX;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self tiltView:self.greenSquare];
[self tiltView:self.redSquare];
[self tiltView:self.yellowSquareBlueSquare];
}
#end
1) Is there a simple way to apply a transform(s) to the separate red/green UIViews and achieve the same effect as the "grouped" yellow and blue UIViews? I prefer to keep the views separate, as this is a universal app and the UIViews are not side-by-side in (e.g.) the iPad layout.
2) If #1 is not possible, I am guessing the best thing to do is simply create a parent view that is present in say iPhone, but not present in iPad. Any other alternatives?
I opted for solution #2. I created a short routine that calculates a bounding box based on an array of UIViews, creates a new parent view from the bounding box, then adds the arrayed views as children. I then can apply the transform to the parent view for the desired effect. Here's the code for gathering and adopting children subviews.
-(UIView *)makeParentWithSubviews:(NSArray *)arrayOfViews{
// Creating a bounding box UIView and add the passed UIViews as subview
// "in-place".
CGFloat xMax = -HUGE_VALF;
CGFloat xMin = HUGE_VALF;
CGFloat yMax = -HUGE_VALF;
CGFloat yMin = HUGE_VALF;
for (UIView *myView in arrayOfViews) {
xMin = MIN(xMin, myView.frame.origin.x);
xMax = MAX(xMax, myView.frame.origin.x + myView.frame.size.width);
yMin = MIN(yMin, myView.frame.origin.y);
yMax = MAX(yMax, myView.frame.origin.y + myView.frame.size.height);
}
CGFloat parentWidth = xMax - xMin;
CGFloat parentHeight = yMax - yMin;
CGRect parentFrame = CGRectMake(xMin, yMin, parentWidth, parentHeight);
UIView *parentView = [[UIView alloc] initWithFrame:parentFrame];
// Replace each child's frame
for (UIView *myView in arrayOfViews){
myView.frame = [[myView superview] convertRect:myView.frame toView:parentView];
[myView removeFromSuperview];
[parentView addSubview:myView];
}
parentView.backgroundColor = [UIColor clearColor];
[self.view addSubview:parentView];
return parentView;
}
Hey I'm following this tutorial and currently on the first step but am experiencing some weird behavior. The tutorial is on creating scroll views for images and ends with showing how to create a paged horizontal ScrollView with a page controller. I followed the first section and things seemed to be working fine. However when I ran the code it did not perform properly, after about an hour of debugging I downloaded the final project, ran it on my phone to check if it was working correctly (it was) then copied the code into the required files in my project. My project still wouldn't run. So I figured it must be the way in which my story board was configured on the project setup. I then oped the working example cleared their storyboard and created the views and segues my self, in the exact same way and order I had done in my project. Bam! it still worked, when I deleted all views in my story board and recreated them in the exact same fashion, still nothing. Could someone look into this tutorial and tell me if I'm going crazy or they experience similar results. I have narrowed it down to two possibilities, the project provided at the end of the tutorial was created differently and the delegates or outlets were hooked up in a way that only works with the project, or the project was created with an earlier version of the iPhone development kit that works with the current version of XCode but not when I create a new project. Then again I am fairly new to this so I will throw up the code I'm using and hope for the best.
ViewController.h file:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UIScrollViewDelegate>
#property (nonatomic, strong) IBOutlet UIScrollView *scrollView;
#end
ViewController.m file:
#import "ViewController.h"
#interface ViewController ()
#property (nonatomic, strong) UIImageView *imageView;
- (void)centerScrollViewContents;
- (void)scrollViewDoubleTapped:(UITapGestureRecognizer*)recognizer;
- (void)scrollViewTwoFingerTapped:(UITapGestureRecognizer*)recognizer;
#end
#implementation ViewController
#synthesize scrollView = _scrollView;
#synthesize imageView = _imageView;
- (void)centerScrollViewContents {
CGSize boundsSize = self.scrollView.bounds.size;
CGRect contentsFrame = self.imageView.frame;
if (contentsFrame.size.width < boundsSize.width) {
contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0f;
} else {
contentsFrame.origin.x = 0.0f;
}
if (contentsFrame.size.height < boundsSize.height) {
contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0f;
} else {
contentsFrame.origin.y = 0.0f;
}
self.imageView.frame = contentsFrame;
}
- (void)scrollViewDoubleTapped:(UITapGestureRecognizer*)recognizer {
// Get the location within the image view where we tapped
CGPoint pointInView = [recognizer locationInView:self.imageView];
// Get a zoom scale that's zoomed in slightly, capped at the maximum zoom scale specified by the scroll view
CGFloat newZoomScale = self.scrollView.zoomScale * 1.5f;
newZoomScale = MIN(newZoomScale, self.scrollView.maximumZoomScale);
// Figure out the rect we want to zoom to, then zoom to it
CGSize scrollViewSize = self.scrollView.bounds.size;
CGFloat w = scrollViewSize.width / newZoomScale;
CGFloat h = scrollViewSize.height / newZoomScale;
CGFloat x = pointInView.x - (w / 2.0f);
CGFloat y = pointInView.y - (h / 2.0f);
CGRect rectToZoomTo = CGRectMake(x, y, w, h);
[self.scrollView zoomToRect:rectToZoomTo animated:YES];
}
- (void)scrollViewTwoFingerTapped:(UITapGestureRecognizer*)recognizer {
// Zoom out slightly, capping at the minimum zoom scale specified by the scroll view
CGFloat newZoomScale = self.scrollView.zoomScale / 1.5f;
newZoomScale = MAX(newZoomScale, self.scrollView.minimumZoomScale);
[self.scrollView setZoomScale:newZoomScale animated:YES];
}
- (void)viewDidLoad {
[super viewDidLoad];
// Set a nice title
self.title = #"Image";
// Set up the image we want to scroll & zoom and add it to the scroll view
UIImage *image = [UIImage imageNamed:#"photo1.png"];
self.imageView = [[UIImageView alloc] initWithImage:image];
self.imageView.frame = (CGRect){.origin=CGPointMake(0.0f, 0.0f), .size=image.size};
[self.scrollView addSubview:self.imageView];
// Tell the scroll view the size of the contents
self.scrollView.contentSize = image.size;
UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(scrollViewDoubleTapped:)];
doubleTapRecognizer.numberOfTapsRequired = 2;
doubleTapRecognizer.numberOfTouchesRequired = 1;
[self.scrollView addGestureRecognizer:doubleTapRecognizer];
UITapGestureRecognizer *twoFingerTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(scrollViewTwoFingerTapped:)];
twoFingerTapRecognizer.numberOfTapsRequired = 1;
twoFingerTapRecognizer.numberOfTouchesRequired = 2;
[self.scrollView addGestureRecognizer:twoFingerTapRecognizer];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Set up the minimum & maximum zoom scales
CGRect scrollViewFrame = self.scrollView.frame;
CGFloat scaleWidth = scrollViewFrame.size.width / self.scrollView.contentSize.width;
CGFloat scaleHeight = scrollViewFrame.size.height / self.scrollView.contentSize.height;
CGFloat minScale = MIN(scaleWidth, scaleHeight);
self.scrollView.minimumZoomScale = minScale;
self.scrollView.maximumZoomScale = 1.0f;
self.scrollView.zoomScale = minScale;
[self centerScrollViewContents];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
// Return the view that we want to zoom
return self.imageView;
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
// The scroll view has zoomed, so we need to re-center the contents
[self centerScrollViewContents];
}
#end
The above code doesn't work as it does in the tutorials project. The following UIScrollView delegate method viewForZoomingInScrollView produces this error:
2013-05-14 10:01:18.585 TestScrollView[3809:907] Made it
May 14 10:01:18 Scott-Laroses-iPhone TestScrollView[3809] : CGAffineTransformInvert: singular matrix.
May 14 10:01:18 Scott-Laroses-iPhone TestScrollView[3809] : CGAffineTransformInvert: singular matrix.
which makes me think the delate isn't properly set up despite me doing it exactly the same as in the tutorial and programmatically when that didn't work. Any ideas?
Your problem may be from the zoomScale and minimumZoomScale if the value assigned is 0. Make sure that the value is never 0 for those two properties.
I figured it out! The issues was that the auto layout constraints were messing up the off screen views. So I disabled auto layout and it worked fine.
In the latest Expedia app for iOS, they have a very interesting effect that I am trying to wrap my head around. The have two columns of infinitely scrolling subviews which I know can be accomplished with 2 scrollviews. The interesting part is that the overall scrollview appears to have a linen background that stays static and can be seen in the gap between each of the subview cells. The really cool part is that the subviews have a different background that stays in place. In the screenshot below it is city skyline image. When the subviews scroll, the city image can only be seen behind the subview cells. It appears to be some sort of masking trick but I can't quite figure out how the effect is done. How can I achieve the same result?
Essentially, how can you show a static background behind subviews that act as little windows and not show the linen. The linen should only be shown around the cells.
You can download the app, hit airplane mode and try it for yourself.
Here is a screenshot:
Here is another to show that the cells scrolled but the city stays the same:
I'd like to found an elegant solution, for now I would do it by tracking the visible subviews offset and configuring their appearance.
Please check the result at sample project.
For the future reference I'll attach the code below:
ViewController.m
//
// OSViewController.m
// ScrollMasks
//
// Created by #%$^Q& on 11/30/12.
// Copyright (c) 2012 Demo. All rights reserved.
//
#import "OSViewController.h"
#interface OSViewController ()
// subviews
#property (strong) IBOutlet UIScrollView * scrollView;
// all the subviews
#property (strong) NSArray * maskedSubviews;
// subviews visible at scrollview, we'll update only them
#property (strong) NSArray * visibleMaskedSubviews;
// updates the views from visibleMaskedSubviews
-(void) updateVisibleSubviews;
// updates the visibleMaskedSubviews array with the given scrollView offset
-(void) updateVisibleSubviewsArrayForOffset:(CGPoint) offset;
#end
#implementation OSViewController
-(void) unused {}
#pragma mark - view
-(void) viewWillAppear:(BOOL)animated {
[self updateVisibleSubviews];
[super viewWillAppear:animated];
}
- (void)viewDidLoad
{
[super viewDidLoad];
/*
See -updateVisibleSubviews comment for the class comments
*/
UIView * newMaskedView = nil;
NSMutableArray * newMaskedSubviews = [NSMutableArray array];
const CGSize scrollViewSize = self.scrollView.bounds.size;
const int totalSubviews = 10;
const float offset = 20.;
const float height = 100.;
UIImage * maskImage = [UIImage imageNamed:#"PeeringFrog.jpg"];
/*
// Uncomment to compare
UIImageView * iv = [[UIImageView alloc] initWithFrame:self.scrollView.bounds];
iv.image = maskImage;
[self.view insertSubview:iv atIndex:0];
*/
// add scrollview subviews
for (int i = 0; i < totalSubviews; i++) {
CGRect newViewFrame = CGRectMake(offset, offset*(i+1) + height*i, scrollViewSize.width - offset*2, height);
newMaskedView = [UIView new];
newMaskedView.frame = newViewFrame;
newMaskedView.clipsToBounds = YES;
newMaskedView.backgroundColor = [UIColor redColor];
newMaskedView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth;
UIImageView * maskImageView = [UIImageView new];
maskImageView.frame = CGRectMake(0, 0, self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
maskImageView.image = maskImage;
[newMaskedView addSubview:maskImageView];
[self.scrollView addSubview:newMaskedView];
[newMaskedSubviews addObject:newMaskedView];
}
self.scrollView.contentSize = CGSizeMake(scrollViewSize.width, (height+offset)*totalSubviews + offset*2);
self.maskedSubviews = [NSArray arrayWithArray:newMaskedSubviews];
[self updateVisibleSubviewsArrayForOffset:self.scrollView.contentOffset];
}
-(void) updateVisibleSubviews {
[self updateVisibleSubviewsArrayForOffset:self.scrollView.contentOffset];
for (UIView * view in self.visibleMaskedSubviews) {
/*
TODO:
view must be UIView subclass with the imageView initializer and imageView frame update method
*/
CGPoint viewOffset = [self.view convertPoint:CGPointZero fromView:view];
UIImageView * subview = [[view subviews] objectAtIndex:0];
CGRect subviewFrame = subview.frame;
subviewFrame = CGRectMake(-viewOffset.x, -viewOffset.y, subviewFrame.size.width, subviewFrame.size.height);
subview.frame = subviewFrame;
}
}
#pragma mark - scrollview delegate & utilities
-(void) scrollViewDidScroll:(UIScrollView *)scrollView {
[self updateVisibleSubviews];
}
-(void) updateVisibleSubviewsArrayForOffset:(CGPoint) offset {
NSMutableArray * newVisibleMaskedSubviews = [NSMutableArray array];
for (UIView * view in self.maskedSubviews) {
CGRect intersectionRect = CGRectIntersection(view.frame, self.scrollView.bounds);
if (NO == CGRectIsNull(intersectionRect)) {
[newVisibleMaskedSubviews addObject:view];
}
}
self.visibleMaskedSubviews = [NSArray arrayWithArray:newVisibleMaskedSubviews];
}
#pragma mark - memory
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
ViewController.h
//
// OSViewController.h
// ScrollMasks
//
// Created by #%$^Q& on 11/30/12.
// Copyright (c) 2012 Demo. All rights reserved.
//
/*
PeeringFrog image is taken (and resized) from Apple sample project "PhotoScroller"
*/
#import <UIKit/UIKit.h>
#interface OSViewController : UIViewController <UIScrollViewDelegate>
#end
I did something similar a few years ago. At first I tried using the CGImageMaskCreate stuff, but found it far easier just to create an image that had transparent "cutouts" and then used animation effects to scroll the picture(s) under it.
For your case, I'd find an image of linen the size of the screen. Then I'd use a image editor (I use GIMP) to draw some number of boxes on the linen using a flat color. Then I'd map that box color to transparent to make the cutouts. There's other ways to do this, but that's the way I do it.
In your app, add two or more image views to the main view. Don't worry about the placement because that will be determined at run time. You'll want to set these image views to contain the images you want to have "scroll" under. Then add your linen-with-cutouts UIImageView so it's on top and it's occupying the entire screen size. Make sure that the top UIImageView's background is set to transparent.
When the app starts, layout your "underneath" imageviews, top to bottom, and then start a [UIView beginAnimation] that scrolls your underneath images views up by modifying the "y" position. This animation should have a done callback that gets called when the top image view is completely off the screen. Then, in the done callback, layout the current state and start the animation again. Here's the guts of the code I used (but note, my scrolling was right to left, not bottom to top and my images were all the same size.)
- (void)doAnimationSet
{
[iv1 setFrame:CGRectMake(0, 0, imageWidth, imageHeight)];
[iv2 setFrame:CGRectMake(imageWidth, 0, imageWidth, imageHeight)];
[iv3 setFrame:CGRectMake(imageWidth*2, 0, imageWidth, imageHeight)];
[self loadNextImageSet];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:10];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
[iv1 setFrame:CGRectMake(-imageWidth, 0, imageWidth, imageHeight)];
[iv2 setFrame:CGRectMake(0, 0, imageWidth, imageHeight)];
[iv3 setFrame:CGRectMake(imageWidth, 0, imageWidth, imageHeight)];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(doneAnimation:finished:context:)];
[UIView commitAnimations];
}
- (void)doneAnimation:(NSString *)aid finished:(BOOL)fin context:(void *)context
{
[self doAnimationSet];
}
This should give you the effect that you are looking for. Good luck :)