Can't get background gradient to fill entire screen upon rotation - ios

I have a background gradient that draws nicely when the screen is initially rendered in portrait mode. However, when the device is rotated to landscape mode, the right 40% of the screen is black (i.e. the background is not drawn on the part of the screen that wasn't previously drawn). I don't know how to get the background to redraw to fill the entire screen.
Can anyone tell me what I am doing wrong? Thanks!
Here is how it looks when initially drawn.
And here is now it looks when I rotate it:
Here is the code for my background gradient class:
#import "BackgroundGradient.h"
#interface BackgroundGradient()
#property (strong, nonatomic) UIView *view;
#end
#implementation BackgroundGradient
- (id) initWithView:(UIView *) view
{
if (!_view)
_view = view;
return self;
}
- (void) makeGradient
{
UIColor *darkOp = [UIColor colorWithRed:(193.0 / 255.0) green:(215.0 / 255.0) blue:(46.0 / 255.0) alpha: 1];
UIColor *lightOp = [UIColor colorWithRed:(222.0 / 255.0) green:(233.0 / 255.0) blue:(143.0 / 255.0) alpha: 1];
// Create the gradient
CAGradientLayer *gradient = [CAGradientLayer layer];
// Set colors
gradient.colors = [NSArray arrayWithObjects:
(id)darkOp.CGColor,
(id)lightOp.CGColor,
(id)darkOp.CGColor,
nil];
// Update the start and end points
gradient.startPoint = CGPointMake(0.5, 0.0);
gradient.endPoint = CGPointMake(0.5, 1.0);
// Set bounds
gradient.frame = self.view.bounds;
// Add the gradient to the view
[self.view.layer insertSublayer:gradient atIndex:0];
gradient.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0f],
[NSNumber numberWithFloat:0.7],
[NSNumber numberWithFloat:0.9],
nil];
}
#end
And finally, here is how I instantiate the background object:
- (void)viewDidLoad
{
[super viewDidLoad];
self.background = [[BackgroundGradient alloc] initWithView:self.view];
[self.background makeGradient];
}
EDIT: I've added the following code and included updates to the start and end points (above):
- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self.background makeGradient];
}
I'm almost there, but now, I get this. Notice how there is still a vertical line. It looks to me like the new background is getting drawn behing the previous background such that the previous background is still there and is blocking the new background. This is particularly noticeable as the animation from portrait to landscape is occurring.

This is where you are doing it wrong: CAGradientLayer *gradient = [CAGradientLayer layer]; and [self.view.layer insertSublayer:gradient atIndex:0];
You are 'adding'a new instance of gradient layer each time the device rotates. You shouldn't do that. Instead, only set it's frame on rotation, and everything will work absolutely fine. Use only one instance. Create it in viewDidLoad
Use view debugging to help out in such cases.

I solved this issue with this:
Resize the sublayer of my background image.
Swift
override func willAnimateRotationToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
self.backgroundImageView.layer.sublayers?.first?.frame = self.view.bounds
}

Update gradient's frame in viewDidLayoutSubviews:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// find gradient layer
let gradLayers = gradientView.layer.sublayers?.flatMap { $0 as? CAGradientLayer }
// this assumes there is only one gradient layer
gradLayers?.first?.frame = gradientView.bounds
}

Related

How to avoid infinite layoutSubviews

I have a UITableViewCell subclass which overrides layoutSubviews to apply a fade effect to a webview. For the most part it works fine, but sometimes it ends up in a state where layoutSubvies is called constantly with alternating values for the content height for the webview.
Is there some other place I could apply this gradient to avoid this issue?
CAGradientLayer* gradientLayer = nil;
int webViewHeight = 0;
- (void)layoutSubviews
{
NSLog(#"layoutSubviews %f", self.webView.scrollView.contentSize.height);
[super layoutSubviews];
if (self.webView.scrollView.contentSize.height == webViewHeight) {
// nothing has changed
return;
}
if (gradientLayer != nil) {
[gradientLayer removeFromSuperlayer];
gradientLayer = nil;
}
// if cell height is less than web view height we must fade
if (self.frame.size.height < self.webView.scrollView.contentSize.height) {
webViewHeight = self.webView.scrollView.contentSize.height;
gradientLayer = [CAGradientLayer layer];
NSObject* fadeColor = (NSObject*)[UIColor colorWithWhite:1.0 alpha:.9].CGColor;
NSObject* transparentColor = (NSObject*)[UIColor colorWithWhite:1.0 alpha:0.0].CGColor;
gradientLayer.colors = [NSArray arrayWithObjects:transparentColor, fadeColor, nil];
gradientLayer.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.5],
[NSNumber numberWithFloat:1.0], nil];
gradientLayer.bounds = self.bounds;
gradientLayer.anchorPoint = CGPointZero;
[self.layer addSublayer:gradientLayer];
}
}
my TWO cents for similar issue. Hope can help others.
After debugging a little, I noted ALSO adding CALayer will re-invoke layoutSubviews
So if You call:
self.layer.addSublayer(anotherLayer)
you wil be re-called in layoutSubviews.

fade a UITextView while scrolling uiscrollview

I have a UIScrollView containing UIImageviews wrapped inside programmatically created UIScrollViews.
Inside that main UIScrollView i have a UITextView that will change it's content accordingly to the image view showing while scrolling between the various uiimageviews. I want to decrease the alpha of the showing UITextView accordingly to the position of the scrolled UIScrollView.
e.g. if the user is half way through the scrolling process while moving from one set of uiimageviews to another, the uitextview's alpha should be 0.5. Any Ideas how can i achieve this result ?
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGFloat offset = scrollView.contentOffset.x; // here you will get the offset value
CGFloat value = offset / totalcontentsize;
// use 'value' for setting the textview alpha value
}
I just add logic how to approach.
Or you can use a category. Make a category on UIView called UIView+Looks
#interface UIView (Looks)
-(void)fadeTail;
#end
#implementation UIView (Looks)
-(void)fadeTail
{
// it's used to fade away the bottom,
// perhaps of a slab of text, web view or similar
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = self.bounds;
gradient.colors = #[
(id)[[UIColor whiteColor] CGColor],
(id)[[UIColor clearColor] CGColor]
];
gradient.startPoint = CGPointMake(0.5, 0.93);
gradient.endPoint = CGPointMake(0.5, 1);
[self.layer setMask:gradient];
}
#end
example usage (in this case on a web view
#property (strong) IBOutlet UIWebView *dossierBlock;
-(void)_fillMainArea
{
[self _loadBookImage];
NSString *longHtml = CLOUD.caMeeting[#"yourJsonTag"];
nullsafe(longHtml);
[self.dossierBlock longHtml baseURL:nil];
[self.dossierBlock fadeTail];
}
You can trivially make a "fadeTop" in the same way.

Trying to create a custom horizontal UIPickerView using UIScrollView - how do I fade the edges out like in UIPickerView?

In iOS 7, the UIPickerViews have a nice shadow on the text as it gets closer to the edges:
If I'm using a UIScrollView, is it possible to achieve a similar effect where the text at the edges is slightly shadowed/blended into the background?
See here and here for my answers to similar questions.
My solution was to subclass UIScrollView, and create a mask layer in the layoutSubviews method.
The code in the above answers is also here on github.
Hope that helps!
I did a similar thing with this class to fade the left and right edges of a scrolling marquee. Much less hassle than using two images as you can resize or recolor it easily:
#interface MarqueeFadeOverlay : UIView
#property(assign, nonatomic) CGGradientRef gradientLayer;
- (id)initWithFrame:(CGRect)frame andEndFadeWidth:(float)fadeWidth andFadeBarColor:(UIColor *)color;
#end
#implementation MarqueeFadeOverlay {
UIColor *fadeBarColor;
float endFadeWidth;
}
- (id)initWithFrame:(CGRect)frame andEndFadeWidth:(float)fadeWidth andFadeBarColor:(UIColor *)color {
self = [super initWithFrame:frame];
if (self) {
endFadeWidth = fadeWidth;
fadeBarColor = color;
[self setOpaque:NO];
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawLinearGradient(context, self.gradientLayer, CGPointMake(0.0, 0.0),
CGPointMake(self.frame.size.width, 0.0), kCGGradientDrawsBeforeStartLocation);
}
- (CGGradientRef)gradientLayer {
if (_gradientLayer == nil) {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat *colorComponents = [self rgbaFrom:fadeBarColor];
if (endFadeWidth == 0) endFadeWidth = 0.1;
CGFloat locations[] = {0.0, endFadeWidth, 1.0 - endFadeWidth, 1.0};
CGFloat colors[] = {colorComponents[0], colorComponents[1], colorComponents[2], 1.00,
colorComponents[0], colorComponents[1], colorComponents[2], 0,
colorComponents[0], colorComponents[1], colorComponents[2], 0,
colorComponents[0], colorComponents[1], colorComponents[2], 1.00};
_gradientLayer = CGGradientCreateWithColorComponents(colorSpace, colors, locations, sizeof(colors) / (sizeof(colors[0]) * 4));
CGColorSpaceRelease(colorSpace);
}
return _gradientLayer;
}
-(CGFloat *)rgbaFrom:(UIColor *)color {
CGColorRef colorRef = [color CGColor];
return (CGFloat *) CGColorGetComponents(colorRef);
}
You just init one with a frame, a fade width between 0 and 1 and a color to match the background, and add to the parent view.
I would place two custom overlay views for this that you place on top of the scroll view. The views could either contain a pre-made image with a gradient from full white to an alpha of 0, or you could draw the image in code, either with Core Graphics or a CAGradientLayer.
If you set userInteractionEnabled = NO; on the views, they won't interfere with the touch handling.

UIButton with a gradient layer slow

I am customizing the UIButton with GradientLayer. I see a performance issue as view loading slowly and looks like a Jerk effect. Also when changing the orientation its same. I am using this code for an iPad application. With normal UIButton its smooth loading / rotating.
I am using the sample code found in google (GradientButton class) and is working fine. The container UIViewController which is pushed onto navigationcontroller stack to display the screen.
Here is the code:
- (void)awakeFromNib {
[self initLayers];
}
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self initLayers];
}
return self;
}
- (void)initLayers {
[self initBorder];
[self addShineLayer];
[self addHighlightLayer];
self.clipsToBounds = YES;
}
- (void)initBorder {
CALayer *layer = self.layer;
layer.cornerRadius = 8.0f;
layer.masksToBounds = YES;
layer.borderWidth = 1.0f;
layer.borderColor = [UIColor colorWithWhite:0.5f alpha:0.2f].CGColor;
}
- (void)addShineLayer {
shineLayer = [CAGradientLayer layer];
shineLayer.frame = self.layer.bounds;
shineLayer.colors = [NSArray arrayWithObjects:
(id)[UIColor colorWithWhite:1.0f alpha:0.4f].CGColor,
(id)[UIColor colorWithWhite:1.0f alpha:0.2f].CGColor,
(id)[UIColor colorWithWhite:0.75f alpha:0.2f].CGColor,
(id)[UIColor colorWithWhite:0.4f alpha:0.2f].CGColor,
(id)[UIColor colorWithWhite:1.0f alpha:0.4f].CGColor,
nil];
shineLayer.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0f],
[NSNumber numberWithFloat:0.5f],
[NSNumber numberWithFloat:0.5f],
[NSNumber numberWithFloat:0.8f],
[NSNumber numberWithFloat:1.0f],
nil];
// shineLayer.shouldRasterize = YES;
// shineLayer.rasterizationScale = [UIScreen mainScreen].scale;
[self.layer addSublayer:shineLayer];
}
#pragma mark -
#pragma mark Highlight button while touched
- (void)addHighlightLayer {
highlightLayer = [CALayer layer];
highlightLayer.backgroundColor = [UIColor colorWithRed:0.25f green:0.25f blue:0.25f alpha:0.75].CGColor;
highlightLayer.frame = self.layer.bounds;
highlightLayer.hidden = YES;
[self.layer insertSublayer:highlightLayer below:shineLayer];
}
- (void)setHighlighted:(BOOL)highlight {
highlightLayer.hidden = !highlight;
[super setHighlighted:highlight];
}
Is the performance issue due to Gradient Layer ?? Please tell me how to rectify this issue.
Try commenting out the different property assignments in your code one at a time and see if you get any performance benefit. I worked on an app once that was getting about 12FPS in core animation when I used setCornerRadius on the layer compared to nearly full frame rate when I turned that off. The issue may be your gradient, however, you won't know for sure unless you just do some a/b comparisons turning off/on the various properties.
I'll tell you right now, though, that when I've used gradient layers on buttons in table view cells, I was able to speed up the table scrolling dramatically when I just used a background image for the button instead of a gradient layer. You can try to turn on shouldRasterize, however, make sure you do that at the top level of your layer tree and not on any sublayers (although the issues that I saw with setting that parameter on all layers may have been fixed in iOS6. I just haven't tested it).

How to apply partial fade in-out in IOS?

I have 2 images. One is in the back of the other. I want to fade-out the top image starting from the left corner. How can I do this?
Should I use gradient mask?
Here's a technique that will fade out whatever's in a CALayer, from top-left to bottom-right, revealing whatever is underneath the CALayer.
Let's say we're going to fade out the layer of a UIImageView called self.fadeView.
We'll create a CAGradientLayer and use it as self.fadeView.layer.mask. The gradient will go from alpha=0 (transparent) to alpha=1 (opaque) using four stops: 0, 0, 1, 1. Yes, two zeros and then two ones. When we want fadeView to be opaque, we'll set the stop locations to -1, -.5, 0, 1. That way the two alpha=0 stops are completely outside of the layer bounds. When we want fadeView to be transparent, we'll set the stop locations to 0, 1, 1.5, 2. That way the two alpha=1 stops are completely outside of the layer bounds. The CAGradientLayer will automatically animate changes to its stop locations, creating a cross-fade effect.
Here's the code:
#import "ViewController.h"
#implementation ViewController
#synthesize fadeView = _fadeView;
static NSArray *locations(float a, float b, float c, float d)
{
return [NSArray arrayWithObjects:
[NSNumber numberWithFloat:a],
[NSNumber numberWithFloat:b],
[NSNumber numberWithFloat:c],
[NSNumber numberWithFloat:d],
nil];
}
// In my test project, I put a swipe gesture recognizer on fadeView in my XIB
// with direction = Up and connected it to this action.
- (IBAction)fadeIn
{
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithDouble:2.0] forKey:kCATransactionAnimationDuration];
((CAGradientLayer *)self.fadeView.layer.mask).locations = locations(-1, -.5, 0, 1);
[CATransaction commit];
}
// In my test project, I put a swipe gesture recognizer on fadeView in my XIB
// with direction = Down and connected it to this action.
- (IBAction)fadeOut
{
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithDouble:2.0] forKey:kCATransactionAnimationDuration];
((CAGradientLayer *)self.fadeView.layer.mask).locations = locations(0, 1, 1.5, 2);
[CATransaction commit];
}
- (void)viewDidLoad
{
[super viewDidLoad];
CAGradientLayer *mask = [CAGradientLayer layer];
mask.frame = self.fadeView.bounds;
mask.colors = [NSArray arrayWithObjects:
(__bridge id)[UIColor clearColor].CGColor,
(__bridge id)[UIColor clearColor].CGColor,
(__bridge id)[UIColor whiteColor].CGColor,
(__bridge id)[UIColor whiteColor].CGColor,
nil];
mask.startPoint = CGPointZero; // top left corner
mask.endPoint = CGPointMake(1, 1); // bottom right corner
self.fadeView.layer.mask = mask;
[self fadeIn]; // initialize mask.locations
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#end
Define your own CALayer subclass with its own actionForKey animation for the key contents.
#interface CustomLayer: CALayer
#end
#implementation CustomLayer
+ (id<CAAction>)actionForKey:(NSString*)event {
if ([event isEqualToString:#"contents"]) {
CATransition* basicAnimation = [CATransition animation];
basicAnimation.type = kCATransitionMoveIn;
basicAnimation.subtype = kCATransitionFromLeft;
return basicAnimation;
} else {
return nil;
}
}
#end
Doing like this, whenever you set the contents property of your CustomLayer object, the transition will be shown:
CustomLayer* layer = [CustomLayer layer];
layer.contents = <FIRST_CGIMAGEREF_HERE>;
...
layer.contents = <SECOND_CGIMAGEREF_HERE>;
As usual, you can wrap the property assignment in a transaction:
[CATransaction begin];
....
[CATransaction end];
if you want more control on the duration of the transition, but in any case the transition will be applied.
EDIT:
For the kind of transition you would like to have, you can have a look at this sample on GitHub. Look for the ShutterTransition. It is not exactly what you are looking for, but it could lead you along the right path.

Resources