I have a UIScrollView, and I need the bottom to fade to transparent, so that it does not abruptly cut off the content. The background of the UIScrollView is a custom color. This is what I have so far, but the layer is showing up white, instead of the custom color. This is what I am going for, but only on the bottom.
:
Here is what I have so far:
maskLayer = [CAGradientLayer layer];
CGColorRef firstColor = [UIColor colorWithRed:141 green:211 blue:244 alpha:1.0].CGColor;
CGColorRef secondColor = [UIColor colorWithRed:141 green:211 blue:244 alpha:0.8].CGColor;
CGColorRef thirdColor = [UIColor colorWithRed:141 green:211 blue:244 alpha:0.2].CGColor;
CGColorRef fourthColor = [UIColor colorWithRed:141 green:211 blue:244 alpha:0.0].CGColor;
maskLayer.colors = [NSArray arrayWithObjects:(__bridge id)firstColor, (__bridge id)secondColor, (__bridge id)thirdColor, (__bridge id)fourthColor, nil];
maskLayer.locations = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.2],
[NSNumber numberWithFloat:0.8],
[NSNumber numberWithFloat:1.0], nil];
maskLayer.frame = CGRectMake(0, 285, self.loginScrollView.frame.size.width, 40);
maskLayer.anchorPoint = CGPointZero;
[self.loginScrollView.layer addSublayer:maskLayer];
For some reason, the layer is showing up as white, and only half the width of the scrollView.
The approach I used was to subclass UIScrollView, and create the mask layer in the layoutSubviews method.
Here's my code, which fades the top and bottom of the UIScrollView from the background colour to transparent:
#import "FadingScrollView.h"
#import <QuartzCore/QuartzCore.h>
static float const fadePercentage = 0.2;
#implementation FadingScrollView
// ...
- (void)layoutSubviews
{
[super layoutSubviews];
NSObject * transparent = (NSObject *) [[UIColor colorWithWhite:0 alpha:0] CGColor];
NSObject * opaque = (NSObject *) [[UIColor colorWithWhite:0 alpha:1] CGColor];
CALayer * maskLayer = [CALayer layer];
maskLayer.frame = self.bounds;
CAGradientLayer * gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = CGRectMake(self.bounds.origin.x, 0,
self.bounds.size.width, self.bounds.size.height);
gradientLayer.colors = [NSArray arrayWithObjects: transparent, opaque,
opaque, transparent, nil];
// Set percentage of scrollview that fades at top & bottom
gradientLayer.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:fadePercentage],
[NSNumber numberWithFloat:1.0 - fadePercentage],
[NSNumber numberWithFloat:1], nil];
[maskLayer addSublayer:gradientLayer];
self.layer.mask = maskLayer;
}
#end
If you just want to fade the bottom, change this line:
// Fade bottom of scrollview only
gradientLayer.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:1.0 - fadePercentage],
[NSNumber numberWithFloat:1], nil];
When I was implementing this myself, I found this SO question helpful, and this gist on github.
EDIT: I've put this code up on github, see here.
Really like Steph Sharp's implementation, I converted it to swift and thought I'd share in case anyone else needs it.
let fadePercentage = CGFloat(0.2)
override func layoutSubviews() {
super.layoutSubviews()
var transparent = UIColor.clearColor().CGColor
var opaque = UIColor.blackColor().CGColor
var maskLayer = CALayer()
maskLayer.frame = self.bounds
var gradientLayer = CAGradientLayer()
gradientLayer.frame = CGRectMake(self.bounds.origin.x, 0, self.bounds.size.width, self.bounds.size.height)
gradientLayer.colors = [transparent, opaque, opaque, transparent]
gradientLayer.locations = [0, fadePercentage, 1 - fadePercentage, 1]
maskLayer.addSublayer(gradientLayer)
self.layer.mask = maskLayer
}
Here goes the swift 3 version of Steph's answer:
let fadePercentage: Double = 0.2
override func layoutSubviews() {
super.layoutSubviews()
let transparent = UIColor.clear.cgColor
let opaque = UIColor.black.cgColor
let maskLayer = CALayer()
maskLayer.frame = self.bounds
let gradientLayer = CAGradientLayer()
gradientLayer.frame = CGRect(x: self.bounds.origin.x, y: 0, width: self.bounds.size.width, height: self.bounds.size.height)
gradientLayer.colors = [transparent, opaque, opaque, transparent]
gradientLayer.locations = [0, NSNumber(floatLiteral: fadePercentage), NSNumber(floatLiteral: 1 - fadePercentage), 1]
maskLayer.addSublayer(gradientLayer)
self.layer.mask = maskLayer
}
Theres also this answer which does not include any subclassing.
Related
I want to mask CALayer with another layer whose frame I want to keep changing in every draw call thereby animating. Is it possible to do change the frame of mask layer in every draw call rather than making a new mask layer?
Try the following code. This code will run on Cocoa App for macOS. Kindly change the classes accordingly if you are going for Cocoa Touch for iOS. I hope this works for you.
// ViewController.h
import
#interface ViewController : NSViewController{
CALayer * maskLayer;
CALayer * Layer;
}
// ViewController.m
#implementation ViewController
-(void)viewDidLoad{
[super viewDidLoad];
maskLayer = [CALayer layer];
Layer = [CALayer layer];
Layer.backgroundColor = [[NSColor blueColor] CGColor];
Layer.frame = CGRectMake(20, 20, 720, 900);
Layer.contents = [NSImage imageNamed:#"bg1"];
maskLayer.frame = CGRectMake(20, 20, 720, 900);
maskLayer.opacity = 0.5;
[maskLayer mask];
maskLayer.contents = [NSImage imageNamed:#"bg"];
[self.view.layer addSublayer:Layer];
[self.view.layer addSublayer:maskLayer];
[self maskLayerAnimation];
}
-(void)maskLayerAnimation{
[CATransaction begin];
CABasicAnimation* fadeAnim = [CABasicAnimation animationWithKeyPath:#"opacity"];
fadeAnim.fromValue = [NSNumber numberWithFloat:1.0];
fadeAnim.toValue = [NSNumber numberWithFloat:0.0];
fadeAnim.duration = 5.0;
[CATransaction setCompletionBlock:^{
NSLog(#"animation completed");
maskLayer.frame = CGRectMake(20, 20, 720, 700);
maskLayer.contents = [NSImage imageNamed:#"bg2"];
}];;
[maskLayer addAnimation:fadeAnim forKey:#"opacity"];
[CATransaction commit];
}
I tried the following:
UIColor *c = [UIColor colorWithPatternImage: [UIImage imageNamed: #"gradient.png"]];
CCColor *cc = [CCColor colorWithUIColor: c];
lblLevelName = [CCLabelTTF labelWithString: #"Level" fontName: #"" fontSize: 18.0f];
lblLevelName.anchorPoint = ccp(1.0f, 1.0f);
lblLevelName.position = ccp(screen.width - 20.0f, screen.height - 75.0f);
lblLevelName.fontColor = cc;
[self addChild: lblLevelName];
But I get a black label, while the gradient image was from yellow to orange.
Any suggestion?
I'm trying to take an NSGradient and save it as an image in RubyMotion, but I can't get it to work. This is the code I have so far:
gradient = NSGradient.alloc.initWithColors(colors,
atLocations: locations.to_pointer(:double),
colorSpace: NSColorSpace.genericRGBColorSpace
)
size = Size(width, height)
image = NSImage.imageWithSize(size, flipped: false, drawingHandler: lambda do |rect|
gradient.drawInRect(rect, angle: angle)
true
end)
data = image.TIFFRepresentation
data.writeToFile('output.tif', atomically: false)
It runs without error, but the file that is saved is blank and there is no image data. Can anyone help point me in the right direction?
I don’t know about RubyMotion, but here’s how to do it in Objective-C:
NSGradient *grad = [[NSGradient alloc] initWithStartingColor:[NSColor redColor]
endingColor:[NSColor blueColor]];
NSRect rect = CGRectMake(0.0, 0.0, 50.0, 50.0);
NSImage *image = [[NSImage alloc] initWithSize:rect.size];
NSBezierPath *path = [NSBezierPath bezierPathWithRect:rect];
[image lockFocus];
[grad drawInBezierPath:path angle:0.0];
NSBitmapImageRep *imgRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:rect];
NSData *data = [imgRep representationUsingType:NSPNGFileType properties:nil];
[image unlockFocus];
[data writeToFile: #"/path/to/file.png" atomically:NO];
In case you want to know how it works in Swift 5:
extension NSImage {
convenience init?(gradientColors: [NSColor], imageSize: NSSize) {
guard let gradient = NSGradient(colors: gradientColors) else { return nil }
let rect = NSRect(origin: CGPoint.zero, size: imageSize)
self.init(size: rect.size)
let path = NSBezierPath(rect: rect)
self.lockFocus()
gradient.draw(in: path, angle: 0.0)
self.unlockFocus()
}
}
I am developing a maps application. I draw an animated line in the map and then use an image (a ruler image) which should be draw just over the line. To draw the line I use a CABasicAnimation with path:
-(void)lineaAnimation {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.duration = animDuration;
animation.delegate=self;
animation.repeatCount = 0;
animation.autoreverses = NO;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fromValue = (id)inip;
animation.toValue = (id)finp;
animation.fillMode=kCAFillModeForwards;
animation.removedOnCompletion=NO;
[shapeLayer addAnimation:animation forKey:#"animatePath"];
}
-(void)linea:(CGPoint)origen to:(CGPoint)destino animado:(BOOL)anim duracion:(float)durac inicio:(float)delay {
inip = CGPathCreateMutable();
finp = CGPathCreateMutable();
CGPathMoveToPoint(inip, nil, origen.x, origen.y);
CGPathAddLineToPoint(inip, nil, origen.x, origen.y);
CGPathCloseSubpath(inip);
CGPathMoveToPoint(finp, nil, origen.x, origen.y);
CGPathAddLineToPoint(finp, nil, destino.x, destino.y);
CGPathCloseSubpath(finp);
rootLayer = [CALayer layer];
rootLayer.frame = fondo.bounds;
[fondo.layer addSublayer:rootLayer];
shapeLayer = [CAShapeLayer layer];
shapeLayer.path = inip;
UIColor *fillColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.7];
shapeLayer.fillColor = fillColor.CGColor;
UIColor *strokeColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.7];
shapeLayer.strokeColor = strokeColor.CGColor;
shapeLayer.lineWidth = 5.0;
shapeLayer.fillRule = kCAFillRuleEvenOdd;
[morrallaLayer addObject:shapeLayer];
[rootLayer addSublayer:shapeLayer];
animDuration=durac;
[self performSelector:#selector(lineaAnimation) withObject:nil afterDelay:delay];
}
Then I try to move the center of the rule to the point and rotate the ruler image using this code:
delta=acos((destino.y-origen.y)/(destino.x-origen.x));
giroIni=0.0;
giroFin=delta;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
animation.fromValue= [NSNumber numberWithFloat:giroIni];
animation.toValue= [NSNumber numberWithFloat:giroFin];
animation.delegate = self;
animation.repeatCount = 0;
animation.autoreverses = NO;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.duration = animDuration;
animation.fillMode=kCAFillModeForwards;
animation.removedOnCompletion=NO;
[ruleL addAnimation:animation forKey:#"giroAngulo"];
The problem is that the ruler borderline doesn't match the line draw, there are around a degree of difference depending on the final orientation... why?
Thanks a lot!
Ooooops... sorry for your time :(
I found the problem and.. as usual the error is not in the stars but in ourselves
I use the acos function when OBVIOUSLY I should use atan
Here is my horizontal UIPickerView (by rotate & scale the ori UIpicker) : htp://s7.postimage.org/qzez9d0pn/Original.png
And now, what I want to do is cut off these two spaces : http://s7.postimage.org/bryzp08uz/Process.png
To have this better look result : http://s7.postimage.org/6ulf3w6vv/Result.png
Somebody please help me how to do it...?
my rotate & scale code :
thePickerView.delegate = self;
thePickerView.showsSelectionIndicator =YES;
thePickerView.center = CGPointMake(xVar, yVar); // position
CGAffineTransform rotate = CGAffineTransformMakeRotation(-3.14/2); // rotate
rotate = CGAffineTransformScale(rotate, 0.1, 1); // scale
[thePickerView setTransform:rotate];
UILabel *theview[num];
CGAffineTransform rotateItem = CGAffineTransformMakeRotation(3.14/2);
for (int i=0;i<num;i++) {
theview[i] = [[UILabel alloc] init];
theview[i].text = [NSString stringWithFormat:#"%d",i+1];
theview[i].textColor = [UIColor blackColor];
theview[i].frame = CGRectMake(0,0, 100, 100);
theview[i].backgroundColor = [UIColor clearColor];
theview[i].textAlignment = UITextAlignmentCenter;
theview[i].shadowColor = [UIColor whiteColor];
theview[i].shadowOffset = CGSizeMake(-1,-1);
theview[i].adjustsFontSizeToFitWidth = YES;
theview[i].transform = CGAffineTransformScale(rotateItem, 1, 10);
}
itemArray = [[NSMutableArray alloc] init];
for (int j=0;j<num;j++) {[itemArray addObject:theview[j]];}
[thePickerView selectRow:row inComponent:0 animated:NO];
[self.view addSubview:thePickerView];
My temporary solution while searching & waiting for better coding answer is create an overlay background image then transparent the desire space of PickerView =.=