ios bar that changes colors while loading - ios

I am currently working on a project of my own and I would like to implement a custom loading control.
It's pretty much what you can find in the app Amen. The colored bar appears only when the app is loading content from the server.
Any tips will be much appreciated. Thanks.
Here are some images to make it more clear

The "hard" part will be writing a UIView subclass that will handle drawing the colors. You'll want to override the drawRect: method and figure out how to know the progress (or will it just "auto-increment"?) and draw/fill in based on that. Then, you can simply add a UIView in Interface Builder, change the Class type of the view, size it appropriately and off you go!
The "easy" part is that when you want the view to not be visible, you can do one of a number of things:
Move the view off-screen by changing its frame property. (This can be "instantaneous" or animated.)
Set the view invisible by changing its hidden property. (You can animate this, too!)
Get rid of the view entirely by using [barView removeFromSuperview].
Update/Edit
For the actual drawing, try this (done quickly and not tested, so YMMV):
// ColorProgressBar.h
#import <UIKit/UIKit.h>
#interface ColorProgressBar : UIView {
float colorWidth;
float progressPercent;
NSMutableArray *colors;
}
#property (assign, nonatomic) float colorWidth;
#property (assign, nonatomic) float progressPercent;
#property (strong, nonatomic) NSMutableArray *colors;
#end
// ColorProgressBar.m
#import "ColorProgressBar.h"
#implementation ColorProgressBar
#synthesize colors;
#synthesize colorWidth;
#synthesize progressPercent;
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Make the color array to use (this is spelled out for clarity)
[self setColors:[NSMutableArray array]];
[[self colors] addObject:[UIColor redColor]];
[[self colors] addObject:[UIColor orangeColor]];
[[self colors] addObject:[UIColor yellowColor]];
[[self colors] addObject:[UIColor greenColor]];
[[self colors] addObject:[UIColor blueColor]];
[[self colors] addObject:[UIColor purpleColor]];
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGFloat left = 0;
CGRect drawBox = CGRectZero;
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextClearRect(ctx, rect);
int colorIndex = -1;
// Draw each color block from left to right, switching to the next color each time
do {
colorIndex = colorIndex + 1;
if (colorIndex >= [[self colors] count]) colorIndex = 0;
[(UIColor *)[[self colors] objectAtIndex:colorIndex] setFill];
drawBox = CGRectMake(left, 0, [self colorWidth], rect.size.height);
CGContextFillRect(ctx, drawBox);
} while (left < rect.size.width);
// Figure out where the "faded/empty" part should start
left = [self progressPercent] * rect.size.width;
drawBox = CGRectMake(left, 0, rect.size.width - left, rect.size.height);
[[UIColor colorWithWhite:1.0 alpha:0.5] setFill];
CGContextFillRect(ctx, drawBox);
}
#end
With this code, you could use this UIView subclass and each time you wanted to update your progress, you would simply set your progressPercent (it's a float with a designed range from 0.00 to 1.00) and call [myView setNeedsDisplay]. That should be it! ;-)

Related

UIView disappears after close and reopen the app [custom UIView category]

I created a category of UIView to draw easily without having to make a subclass of a UIView.
The problem is that when I close the app and I re-open (normally in low memory conditions) the UIView drawing disappears.
I show you the code of the category:
UIView+DrawBlock.h
#import <UIKit/UIKit.h>
// Blocks
typedef void(^DrawBlock)(CGContextRef context, CGRect drawFrame);
typedef void(^CompletionBlock)(UIView *view);
#interface UIView (DrawBlock)
- (void)drawInside:(DrawBlock)block withResult:(CompletionBlock)completion;
#end
UIView+DrawBlock.m
#import "UIView+DrawBlock.h"
#pragma mark - Auxiliar UIView
#interface DrawingView : UIView
#property (strong, nonatomic) DrawBlock drawBlock;
- (void)setDrawRectBlock:(DrawBlock)block;
#end
#implementation DrawingView
- (void)setDrawRectBlock:(DrawBlock)block {
_drawBlock = block;
if (_drawBlock) {
[self setNeedsDisplay];
}
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
if (_drawBlock) {
_drawBlock(context, rect);
_drawBlock = nil;
}
}
#end
#pragma mark - Category
#implementation UIView (DrawBlock)
- (void)drawInside:(DrawBlock)block withResult:(CompletionBlock)completion {
if (block) {
DrawingView *drawView = [[DrawingView alloc] init];
drawView.translatesAutoresizingMaskIntoConstraints = NO;
drawView.userInteractionEnabled = NO;
drawView.backgroundColor = [UIColor clearColor];
[self addSubview:drawView];
NSDictionary *views = NSDictionaryOfVariableBindings(drawView);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[drawView]|"
options:0
metrics:nil
views:views];
[self addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[drawView]|"
options:0
metrics:nil
views:views];
[self addConstraints:constraints];
[drawView setDrawRectBlock:block];
if (completion != nil) {
completion(drawView);
}
}
}
#end
I think the problem is in this part of the code:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
if (_drawBlock) {
_drawBlock(context, rect);
_drawBlock = nil;
}
}
If I comment this line:
_drawBlock = nil;
it seems that everything works, but memory consumption increase without stopping and the app becomes very slow.
Any idea? Subclass is not an option.
Thanks!
Update 1 Example of usage
- (void)drawOnView {
[self.view drawInside:^(CGContextRef context, CGRect drawFrame) {
// Oblique lines
CGMutablePathRef obliquePath = CGPathCreateMutable();
CGFloat height = CGRectGetHeight(drawFrame);
for (CGFloat x = -height; x < CGRectGetWidth(drawFrame); x += 7.5) {
CGPathMoveToPoint(obliquePath, nil, x, 0.0);
CGPathAddLineToPoint(obliquePath, nil, x + height, height);
}
CGContextSetStrokeColorWithColor(context, [UIColor colorWithWhite:0.0 alpha:0.04].CGColor);
CGContextSetLineWidth(context, 2.0);
CGContextAddPath(context, obliquePath);
CGContextStrokePath(context);
CGPathRelease(obliquePath);
} withResult:^(UIView *view) {
[self.view sendSubviewToBack:view];
}];
}
First, blocks should be copied, not retained. Replace strong by copy in the drawBlock property declaration.
Next, you are right, you should not nil the block as DrawingView drawRect: will need it to redisplay.
The memory problem probably is a retain cycle. Make sure you don't acess self nor its ivars from inside the block. Weakify self outside the block as
__weak typeof(self) weakSelf = self;
[self drawInside:^{
// use only weakSelf
} ...
If this doesn't help show a drawInside: call example with your blocks. Also, from where do you call it?
Finally, given that DrawingView takes its full superview size, it might more efficient having a single DrawingView which stores the draw blocks in an internal array, and drawing all of them from a single drawRect call. Creating a new DrawingView for each block, with the constraints at all, seems a bit overhead.
But this is more advanced, I suggest you get the basic case working first.

How to create a UIImage RGB gradient with a portion of black programmatically

I'm wanting to generate a UIImage that's a RGB gradient that also has a portion of black on the left side. I would like to do this programmatically, instead of having to make the image in Photoshop and using it as an asset in the app.
I've searched on here and have seen the generation of iOS color wheels before, but nothing like a rectangle below which I mocked in Photoshop:
This will be for letting users change text color as they touch inside of the UIImage.
I'm not really sure where to begin however, and would appreciate some pointers.
The below code creates a gradient [from red,yellow,green,blue,red] and returns RGB values if touched on it.The below code can be used for any number of colors, just set their value in colors Array.The selected color is set as backgroundColor for selectedColorView(UIView).
ViewController.h file
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#end
ViewController.m file
#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>
#interface ViewController ()
#end
#implementation ViewController{
UIView *selectedColorView;
CAGradientLayer *layer;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
layer =[CAGradientLayer layer];
[layer setFrame:CGRectMake(20, 20, 280, 50)];
layer.colors =#[(id)[UIColor redColor].CGColor,(id)[UIColor yellowColor].CGColor,(id)[UIColor greenColor].CGColor,(id)[UIColor blueColor].CGColor,(id)[UIColor redColor].CGColor];
layer.startPoint =CGPointMake(0, .5);
layer.endPoint =CGPointMake(1, .5);
[self.view.layer addSublayer:layer];
selectedColorView =[[UIView alloc] initWithFrame:CGRectMake(20, 20+50, 280, 50)];
[self.view addSubview:selectedColorView];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint p = [[touches anyObject] locationInView:self.view];
if(CGRectContainsPoint(layer.frame, p)){
CGFloat xOffset = (p.x - layer.frame.origin.x);
CGFloat gap=(layer.frame.size.width/(layer.colors.count-1));
NSInteger index = xOffset/gap;
xOffset =xOffset -index*gap;
UIColor *color1=[UIColor colorWithCGColor:(CGColorRef)layer.colors[index]];
UIColor *color2=[UIColor colorWithCGColor:(CGColorRef)layer.colors[index+1]];
CGFloat r1,g1,b1,a1,r2,g2,b2,a2;
[color1 getRed:&r1 green:&g1 blue:&b1 alpha:&a1];
[color2 getRed:&r2 green:&g2 blue:&b2 alpha:&a2];
selectedColorView.backgroundColor =[UIColor colorWithRed:(1-(xOffset/gap))*r1 +(xOffset/gap)*r2 green:(1-(xOffset/gap))*g1 +(xOffset/gap)*g2 blue:(1-(xOffset/gap))*b1 +(xOffset/gap)*b2 alpha:1.0];
}
}
#end
i think you could draw a gradient color on a context and then create a image of this context.
or maybe you can also do it like this.
http://belencruz.com/2012/12/gradient-background-in-ios-without-images/

DrawRect not displaying shape?

I am trying to implement two subclasses at one time.
I am using the UIViewController <CLLocationManagerDelegate> subclass in my viewController to be able to work with the gps. While using UIView subclass to draw.
This is a summary of the code I have:
MapViewController.h:
#interface MapViewController: UIViewController <CLLocationManagerDelegate>{
DrawCircle *circleView;
}
#property (nonatomic, retain) DrawCircle *circleView;
#end
DrawCircle.h:
#interface DrawCircle : UIView
-(void)addPoint:(CGPoint)point;
#end
What I was trying to do here was to make DrawCircle a property of MapViewController. However I am still having no luck drawing anything to the screen.
Here is my DrawCircle.m code if it helps at all:
-(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if(self) {
_points = [[NSMutableArray alloc] init];
}
return self;
}
-(void)addPoint:(CGPoint)point {
//Wrap the point in an NSValue to add it to the array
[_points addObject:[NSValue valueWithCGPoint:point]];
//This will tell our view to redraw itself, calling drawRect
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
for(NSValue *pointValue in _points) {
CGPoint point = [pointValue CGPointValue];
CGContextAddEllipseInRect(ctx, CGRectMake(point.x - 10, point.y - 10, 20, 20));
}
CGContextSetFillColor(ctx, CGColorGetComponents([[UIColor redColor] CGColor]));
CGContextFillPath(ctx);
}
Also as a last bit of information I can give to you all. Here is a snapshot of my map view controller scene.
I have a feeling that the shape is being drawn, but being covered up somehow.
EDIT
After further investigation, it appears that the shapes being drawn are being covered by my UIImage. I was just messing around and removed the UIImage, and my shapes were there. Now the question is, how do i "move to back" my UIImage, so that my shapes are up front?

How to prevent "bounce" effect when a custom view redraws after zooming?

Note to casual readers: Despite the title, this question has nothing to do with the UIScrollView properties bounces (scrolling related) or bouncesZoom.
I am using UIScrollView to add zooming to a custom view. The custom view uses sublayers to draw its content. Each sublayer is a CALayer instance that is added to the view's main layer with [CALayer addSublayer:]. Sublayers use CoreGraphics to render their content.
After each zoom completes, the custom view needs to redraw its content at the new zoom scale so that the content appears crisp and sharp again. I am currently trying to get the
approach to work that is shown in this SO question, i.e. I reset the scroll view's zoomScale property to 1.0 after each zoom operation, then I adjust the minimumZoomScale and maximumZoomScale properties so that the user cannot zoom in/out more than originally intended.
The content redrawing already works correctly (!), but what I am missing is a smooth GUI update so that the zoomed content is redrawn in place without appearing to move. With my current solution (code example follows at bottom of this question), I observe a kind of "bounce" effect: As soon as the zoom operation ends, the zoomed content briefly moves to a different location, then immediately moves back to its original location.
I am not entirely sure what the reason for the "bounce" effect is: Either there are two GUI update cycles (one for resetting zoomScale to 1.0, and another for setNeedsDisplay), or some sort of animation is taking place that makes both changes visible, one after the other.
My question is: How can I prevent the "bounce" effect described above?
UPDATE: The following is a minimal but complete code example that you can simply copy&paste to observe the effect that I am talking about.
Create a new Xcode project using the "Empty application" template.
Add the code below to AppDelegate.h and AppDelegate.m, respectively.
In the project's Link build phase, add a reference to QuartzCore.framework.
Stuff that goes into AppDelegate.h:
#import <UIKit/UIKit.h>
#class LayerView;
#interface AppDelegate : UIResponder <UIApplicationDelegate, UIScrollViewDelegate>
#property (nonatomic, retain) UIWindow* window;
#property (nonatomic, retain) LayerView* layerView;
#end
Stuff that goes into AppDelegate.m:
#import "AppDelegate.h"
#import <QuartzCore/QuartzCore.h>
#class LayerDelegate;
#interface LayerView : UIView
#property (nonatomic, retain) LayerDelegate* layerDelegate;
#end
#interface LayerDelegate : NSObject
#property(nonatomic, retain) CALayer* layer;
#property (nonatomic, assign) CGFloat zoomScale;
#end
static CGFloat kMinimumZoomScale = 1.0;
static CGFloat kMaximumZoomScale = 5.0;
#implementation AppDelegate
- (void) dealloc
{
self.window = nil;
self.layerView = nil;
[super dealloc];
}
- (BOOL) application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
[UIApplication sharedApplication].statusBarHidden = YES;
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
self.window.backgroundColor = [UIColor whiteColor];
UIScrollView* scrollView = [[[UIScrollView alloc] initWithFrame:self.window.bounds] autorelease];
[self.window addSubview:scrollView];
scrollView.contentSize = scrollView.bounds.size;
scrollView.delegate = self;
scrollView.minimumZoomScale = kMinimumZoomScale;
scrollView.maximumZoomScale = kMaximumZoomScale;
scrollView.zoomScale = 1.0f;
scrollView.bouncesZoom = NO;
self.layerView = [[[LayerView alloc] initWithFrame:scrollView.bounds] autorelease];
[scrollView addSubview:self.layerView];
[self.window makeKeyAndVisible];
return YES;
}
- (UIView*) viewForZoomingInScrollView:(UIScrollView*)scrollView
{
return self.layerView;
}
- (void) scrollViewDidEndZooming:(UIScrollView*)scrollView withView:(UIView*)view atScale:(float)scale
{
CGPoint contentOffset = scrollView.contentOffset;
CGSize contentSize = scrollView.contentSize;
scrollView.maximumZoomScale = scrollView.maximumZoomScale / scale;
scrollView.minimumZoomScale = scrollView.minimumZoomScale / scale;
// Big change here: This resets the scroll view's contentSize and
// contentOffset, and also the LayerView's frame, bounds and transform
// properties
scrollView.zoomScale = 1.0f;
CGFloat newZoomScale = self.layerView.layerDelegate.zoomScale * scale;
self.layerView.layerDelegate.zoomScale = newZoomScale;
self.layerView.frame = CGRectMake(0, 0, contentSize.width, contentSize.height);
scrollView.contentSize = contentSize;
[scrollView setContentOffset:contentOffset animated:NO];
[self.layerView setNeedsDisplay];
}
#end
#implementation LayerView
- (id) initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.layerDelegate = [[[LayerDelegate alloc] init] autorelease];
[self.layer addSublayer:self.layerDelegate.layer];
// super's initWithFrame already invoked setNeedsDisplay, but we need to
// repeat because at that time our layerDelegate property was still empty
[self setNeedsDisplay];
}
return self;
}
- (void) dealloc
{
self.layerDelegate = nil;
[super dealloc];
}
- (void) setNeedsDisplay
{
[super setNeedsDisplay];
// Zooming changes the view's frame, but not the frame of the layer
self.layerDelegate.layer.frame = self.bounds;
[self.layerDelegate.layer setNeedsDisplay];
}
#end
#implementation LayerDelegate
- (id) init
{
self = [super init];
if (self)
{
self.layer = [CALayer layer];
self.layer.delegate = self;
self.zoomScale = 1.0f;
}
return self;
}
- (void) dealloc
{
self.layer = nil;
[super dealloc];
}
- (void) drawLayer:(CALayer*)layer inContext:(CGContextRef)context
{
CGRect layerRect = self.layer.bounds;
CGFloat radius = 25 * self.zoomScale;
CGFloat centerDistanceFromEdge = 5 * self.zoomScale + radius;
CGPoint topLeftCenter = CGPointMake(CGRectGetMinX(layerRect) + centerDistanceFromEdge,
CGRectGetMinY(layerRect) + centerDistanceFromEdge);
[self drawCircleWithCenter:topLeftCenter radius:radius fillColor:[UIColor redColor] inContext:context];
CGPoint layerCenter = CGPointMake(CGRectGetMidX(layerRect), CGRectGetMidY(layerRect));
[self drawCircleWithCenter:layerCenter radius:radius fillColor:[UIColor greenColor] inContext:context];
CGPoint bottomRightCenter = CGPointMake(CGRectGetMaxX(layerRect) - centerDistanceFromEdge,
CGRectGetMaxY(layerRect) - centerDistanceFromEdge);
[self drawCircleWithCenter:bottomRightCenter radius:radius fillColor:[UIColor blueColor] inContext:context];
}
- (void) drawCircleWithCenter:(CGPoint)center
radius:(CGFloat)radius
fillColor:(UIColor*)color
inContext:(CGContextRef)context
{
const int startRadius = [self radians:0];
const int endRadius = [self radians:360];
const int clockwise = 0;
CGContextAddArc(context, center.x, center.y, radius,
startRadius, endRadius, clockwise);
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillPath(context);
}
- (double) radians:(double)degrees
{
return degrees * M_PI / 180;
}
#end
Based on your sample project, the key is that you're manipulating a CALayer directly. By default, setting CALayer properties, such as frame, cause animations. The suggestion to use [UIView setAnimationsEnabled:NO] was on the right track, but only affects UIView-based animations. If you do the CALayer equivalent, say in your setNeedsDisplay: method:
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.layerDelegate.layer.frame = self.bounds;
[CATransaction commit];
It prevents the implicit frame-changing animation and looks right to me. You can also disable these implicit animations via a CALayerDelegate method in your LayerDelegate class:
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
return (id)[NSNull null]; // NSNull means "don't do any implicit animations"
}
Original Suggestions:
Perhaps you are in an animation block without knowing it? Or, perhaps one of the methods you're calling is setting up an animation block? What if you [UIView setAnimationsEnabled:NO] before your code and re-enable them after?
If it's not an animation, then it's probably as you suspect; two view updates of some kind. (Perhaps one from the scroll view, and one from your code somehow?) Some runnable sample code would be great in that case.
(Out of curiosity, have you tried using CALayer's shouldRasterize and rasterizationScale rather than faking out the zoom level?)
In the X Code user interface builder there's a Bounce setting (it's under Scroll View).

Static subview background image effect

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 :)

Resources