How to set a tiled background image for a CALayer? - objective-c

When I set the background for the IKBrowserView the background is resized according to the view size: is there a way to set it as tiled image, repeated but never resized ?
NSImage* backgroundImage = [NSImage imageNamed:#"wood-bg2.jpg"];
CALayer* aLayer = [CALayer layer];
//aLayer.contents = backgroundImage;
CIColor *ci = [[CIColor alloc] initWithColor:[NSColor colorWithPatternImage:backgroundImage]];
CGColorSpaceRef cs = [ci colorSpace];
const CGFloat *comps = [ci components];
CGColorRef cgColor = CGColorCreate(cs, comps);
aLayer.backgroundColor = cgColor;
[itemThumbnailImageBrowserView setBackgroundLayer:aLayer];
I've tried both the commented out code and the current code, but none work.
thanks

There's a method of NSColor:
+ (NSColor *)colorWithPatternImage:(NSImage *)image
So you can do something like this:
NSImage* backgroundImage = [NSImage imageNamed:#"wood-bg2.jpg"];
CALayer* aLayer = [CALayer layer];
aLayer.backgroundColor = [[NSColor colorWithPatternImage:backgroundImage] CGColor]; // - (CGColor) CGColor; is only available in OS X 10.8 and later
[itemThumbnailImageBrowserView setBackgroundLayer:aLayer];
Update for OS X 10.8 and lower
Convert NSColor to CGColor (not tested):
CIColor *ci = [[CIColor alloc] initWithColor:[NSColor colorWithPatternImage:backgroundImage]];
CGColorSpaceRef cs = [ci colorSpace];
const CGFloat *comps = [ci components];
CGColorRef cgColor = CGColorCreate(cs, comps);

Related

Draw a border around an oval CAShapeLayer on MacOS

I need to display an oval layer with a predefined fillColor and a 1px borderColor.
Here is code:
CGRect r = CGRectMake(28, self.bounds.size.height - 5 - 12, 12.0, 12.0);
NSBezierPath *path = [NSBezierPath bezierPathWithOvalInRect:r];
NSColor *fillColor = [NSColor colorWithRed:255.0/255.0 green:193.0/255.0 blue:47.0/255.0 alpha:1.0f];
NSColor *borderColor = [NSColor colorWithRed:226.0/255.0 green:70.0/255.0 blue:84.0/255.0 alpha:1.0f];
minimizeButtonLayer = [CAShapeLayer layer];
minimizeButtonLayer.path = path.CGPath;
minimizeButtonLayer.fillColor = fillColor.CGColor;
minimizeButtonLayer.borderColor = borderColor.CGColor;
minimizeButtonLayer.borderWidth = 1.0;
The oval shape is correctly displayed but without any border.
Why? Please help me.
P.S. Please note that path.CGPath is from a NSBezierPath category.
I solved the problem by replacing borderColor with strokeColor
minimizeButtonLayer.borderColor = borderColor.CGColor;
with
minimizeButtonLayer.strokeColor = borderColor.CGColor;

Set CALayer Gradient Background

I am trying to add a gradient to a CALayer I have created. I can set the background colour of the CALayer with the following:
self.colorLayer = [CALayer layer];
[self.colorLayer setBackgroundColor:color.CGColor];
[self.colorView setWantsLayer:YES];
[self.colorView setLayer:self.colorLayer];
I've looked around with no success (surprisingly I thought this would have been answered many times).
I have made a gradient with:
NSGradient *gradient =
[[NSGradient alloc] initWithStartingColor:[NSColor orangeColor]
endingColor:[NSColor lightGrayColor]];
But can't add it to my CALayer or add an angle.
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.colors = [NSArray arrayWithObjects:(id)[[NSColor whiteColor] CGColor], (id)[[NSColor greenColor] CGColor], nil];
gradient.frame = self.colorView.bounds;
[self.colorView setLayer:gradient];
[self.colorView setWantsLayer:YES];
You don't need to add a gradient to a CALayer, because you can use a CAGradientLayer instead.
Swift 4 version of this answer
let gradient = CAGradientLayer()
gradient.colors = [NSColor.hlpMarineBlue, NSColor.hlpDarkishBlue, NSColor.hlpOceanBlue]
gradient.frame = self.backgroundView.bounds;
self.colorView.layer = gradient
self.colorView.wantsLayer = true
//To Use:
guard let firstColot: NSColor = NSColor(hexString: HexColorCode.code.leftMenuFirstColor.rawValue) else {return}
guard let secondColor: NSColor = NSColor(hexString: HexColorCode.code.leftMenuSecondColor.rawValue) else {return}
leftMenuBgView.gradient(fillView: leftMenuBgView,
withGradientFromColors: [firstColot, secondColor])
extension NSView {
func gradient(fillView view: NSView, withGradientFromColors colors: Array<NSColor>) {
self.wantsLayer = true
let gradientLayer = CAGradientLayer()
gradientLayer.frame = view.bounds
let color1 = colors[0].cgColor
let color2 = colors[1].cgColor
gradientLayer.colors = [color1, color2]
gradientLayer.locations = [0.0, 1.0]
self.layer?.insertSublayer(gradientLayer, at: 0)
}
}

Adding animated layer on video using CAKeyframeAnimation

I'm trying to add an overlay to my video which contains a sublayer that has the content changed over time. So I'm adding CAKeyframeAnimation to the layer as follows:
CALayer *overlayLayer = [CALayer layer];
[overlayLayer setFrame:CGRectMake(0, 0, size.width, size.height)];
[overlayLayer setMasksToBounds:YES];
NSArray *frames = [self.gifImage frames];
CALayer *aLayer = [CALayer layer];
UIImage *firstImage = [frames objectAtIndex:0];
CGFloat nativeWidth = CGImageGetWidth(firstImage.CGImage);
CGFloat nativeHeight = CGImageGetHeight(firstImage.CGImage);
CGRect frame = CGRectMake(0.0, 0.0, nativeWidth, nativeHeight);
aLayer.frame = frame;
aLayer.contents = (id)firstImage.CGImage;
[aLayer setMasksToBounds:YES];
CAKeyframeAnimation *animationSequence = [CAKeyframeAnimation animationWithKeyPath:#"contents"];
animationSequence.calculationMode = kCAAnimationDiscrete;
animationSequence.duration = [self.gifImage totalDuration];
animationSequence.repeatCount = HUGE_VALF;
NSMutableArray *animationSequenceArray = [[NSMutableArray alloc] init];
for (UIImage *image in frames) {
[animationSequenceArray addObject:(id)image.CGImage];
}
animationSequence.values = animationSequenceArray;
[aLayer addAnimation:animationSequence forKey:#"contents"];
[overlayLayer addSublayer:aLayer];
CALayer *parentLayer = [CALayer layer];
CALayer *videoLayer = [CALayer layer];
parentLayer.frame = CGRectMake(0, 0, size.width, size.height);
videoLayer.frame = CGRectMake(0, 0, size.width, size.height);
[parentLayer addSublayer:videoLayer];
[parentLayer addSublayer:overlayLayer];
composition.animationTool = [AVVideoCompositionCoreAnimationTool
videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];
Somehow when I add aLayer as a sublayer of my view, it animates properly. But When I add it to the overlay of the video, the animation doesn't work. Is there something I'm missing here? Any help is appreciated.
If you use kCAAnimationDiscrete, here's a checklist:
keyTimes needs a value from 0-1, not a time
keyTimes must start at 0.0 and end with value 1.0
keyTimes must have one more value than your animation frames count
I've finally managed to fix this issue by changing calculationMode to kCAAnimationLinear. This seems to be a silly issue, but I hope it may be useful for someone.

Using a CALayer as a mask to a backing layer of a GLKView blocks touch events?

I'm trying to use a CAShapeLayer as a mask to the backing layer of a GLKView like so:
CGRect circleFrame = CGRectMake(0, 0, 800, 800);
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:circleFrame];
CGPoint circleAnchor = CGPointMake(CGRectGetMidX(circleFrame) / CGRectGetMaxX(circleFrame),CGRectGetMidY(circleFrame) / CGRectGetMaxY(circleFrame));
shapeLayerMask = [[CAShapeLayer alloc] init];
shapeLayerMask.path = path.CGPath;
shapeLayerMask.anchorPoint = circleAnchor;
shapeLayerMask.frame = CGRectMake(0, 0,800, 800);
shapeLayerMask.fillColor = [UIColor blackColor].CGColor;
shapeLayerMask.backgroundColor = [UIColor clearColor].CGColor;
shapeLayerMask.opacity = 1.0f;
shapeLayerMask.hidden = NO;
self.view.layer.mask = shapeLayerMask;
But as soon as i do this, all my touch events stop firing. No more touchesBegan, touchesEnded, etc. Anyone knows why? Thanks in advance!

Retina Support for custom UITabBarController-like highlighting of UIImage?

I'm using BCTabBarController in my app, and I'm trying to customize it so that it uses Core Graphics to highlight the images automatically, so that I don't need four copies of each image. (Retina, Retina-selected, Legacy, Legacy-selected)
User Ephraim has posted a great starting point for this, but it returns legacy sized images. I've played with some of the settings, but I'm not very familiar with Core Graphics, so I'm shooting in the dark.
Ephraim's Code:
- (UIImage *) imageWithBackgroundColor:(UIColor *)bgColor
shadeAlpha1:(CGFloat)alpha1
shadeAlpha2:(CGFloat)alpha2
shadeAlpha3:(CGFloat)alpha3
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGSize)shadowOffset
shadowBlur:(CGFloat)shadowBlur {
UIImage *image = self;
CGColorRef cgColor = [bgColor CGColor];
CGColorRef cgShadowColor = [shadowColor CGColor];
CGFloat components[16] = {1,1,1,alpha1,1,1,1,alpha1,1,1,1,alpha2,1,1,1,alpha3};
CGFloat locations[4] = {0,0.5,0.6,1};
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef colorGradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, (size_t)4);
CGRect contextRect;
contextRect.origin.x = 0.0f;
contextRect.origin.y = 0.0f;
contextRect.size = [image size];
//contextRect.size = CGSizeMake([image size].width+5,[image size].height+5);
// Retrieve source image and begin image context
UIImage *itemImage = image;
CGSize itemImageSize = [itemImage size];
CGPoint itemImagePosition;
itemImagePosition.x = ceilf((contextRect.size.width - itemImageSize.width) / 2);
itemImagePosition.y = ceilf((contextRect.size.height - itemImageSize.height) / 2);
UIGraphicsBeginImageContext(contextRect.size);
CGContextRef c = UIGraphicsGetCurrentContext();
// Setup shadow
CGContextSetShadowWithColor(c, shadowOffset, shadowBlur, cgShadowColor);
// Setup transparency layer and clip to mask
CGContextBeginTransparencyLayer(c, NULL);
CGContextScaleCTM(c, 1.0, -1.0);
CGContextClipToMask(c, CGRectMake(itemImagePosition.x, -itemImagePosition.y, itemImageSize.width, -itemImageSize.height), [itemImage CGImage]);
// Fill and end the transparency layer
CGContextSetFillColorWithColor(c, cgColor);
contextRect.size.height = -contextRect.size.height;
CGContextFillRect(c, contextRect);
CGContextDrawLinearGradient(c, colorGradient,CGPointZero,CGPointMake(contextRect.size.width*1.0/4.0,contextRect.size.height),0);
CGContextEndTransparencyLayer(c);
//CGPointMake(contextRect.size.width*3.0/4.0, 0)
// Set selected image and end context
UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGColorSpaceRelease(colorSpace);
CGGradientRelease(colorGradient);
return resultImage;
}
To implement this code, I've added a category to UIImage in my project, and then made the following changes to BCTab.h:
- (id)initWithIconImageName:(NSString *)imageName {
if (self = [super init]) {
self.adjustsImageWhenHighlighted = NO;
self.background = [UIImage imageNamed:#"BCTabBarController.bundle/tab-background.png"];
self.rightBorder = [UIImage imageNamed:#"BCTabBarController.bundle/tab-right-border.png"];
self.backgroundColor = [UIColor clearColor];
// NSString *selectedName = [NSString stringWithFormat:#"%#-selected.%#",
// [imageName stringByDeletingPathExtension],
// [imageName pathExtension]];
UIImage *defImage = [UIImage imageNamed:imageName];
[self setImage:[defImage imageWithBackgroundColor:[UIColor lightGrayColor] shadeAlpha1:0.4 shadeAlpha2:0.0 shadeAlpha3:0.6 shadowColor:[UIColor blackColor] shadowOffset:CGSizeMake(0.0, -1.0f) shadowBlur:3.0] forState:UIControlStateNormal];
[self setImage:[defImage imageWithBackgroundColor:[UIColor redColor] shadeAlpha1:0.4 shadeAlpha2:0.0 shadeAlpha3:0.6 shadowColor:[UIColor blackColor] shadowOffset:CGSizeMake(0.0, -1.0f) shadowBlur:3.0] forState:UIControlStateSelected];
}
return self;
}
How can I use Ephraim's code to work correctly with Retina display?
After digging around the internet, a Google search lead me back to StackOverflow. I found this answer to this question which discusses a different method which should be used to set the scale factor of the UIImageGraphicsContext when it is initialized.
UIGraphicsBeginImageContext(contextRect.size); needs to be changed to UIGraphicsBeginImageContextWithOptions(contextRect.size, NO, scale);, where "scale" is the
value of the scale you want to use. I grabbed it from [[UIScreen mainScreen] scale].