Transparent UIButton title - ios

How can I subtract the shape of the title text from the button background?
I created a custom UIButton class. Currently the code to add the border and text color looks simply like this
layer.borderWidth = 2.0
layer.borderColor = buttonColor.CGColor
layer.cornerRadius = (CGFloat(bounds.height) + paddingVertical * 2.0) / 2.0
setTitleColor(buttonColor, forState: UIControlState.Normal)
Any advice on how I can subtract the button title shape from background. Do I need to render all this as an image or is there a better alternative?

Extension for swift 4
extension UIButton{
func clearColorForTitle() {
let buttonSize = bounds.size
if let font = titleLabel?.font{
let attribs = [NSAttributedStringKey.font: font]
if let textSize = titleLabel?.text?.size(withAttributes: attribs){
UIGraphicsBeginImageContextWithOptions(buttonSize, false, UIScreen.main.scale)
if let ctx = UIGraphicsGetCurrentContext(){
ctx.setFillColor(UIColor.white.cgColor)
let center = CGPoint(x: buttonSize.width / 2 - textSize.width / 2, y: buttonSize.height / 2 - textSize.height / 2)
let path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: buttonSize.width, height: buttonSize.height))
ctx.addPath(path.cgPath)
ctx.fillPath()
ctx.setBlendMode(.destinationOut)
titleLabel?.text?.draw(at: center, withAttributes: [NSAttributedStringKey.font: font])
if let viewImage = UIGraphicsGetImageFromCurrentImageContext(){
UIGraphicsEndImageContext()
let maskLayer = CALayer()
maskLayer.contents = ((viewImage.cgImage) as AnyObject)
maskLayer.frame = bounds
layer.mask = maskLayer
}
}
}
}
}
Usage :
button.clearColorForTitle()

I've written some code for you, my UIButton subclass called AKStencilButton (available on github https://github.com/purrrminator/AKStencilButton):
#import "AKStencilButton.h"
#import <QuartzCore/QuartzCore.h>
#interface AKStencilButton ()
{
UIColor * _buttonColor;
}
#end
#implementation AKStencilButton
-(id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]){
[self setupDefaults];
}
return self;
}
-(instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]){
[self setupDefaults];
}
return self;
}
-(void)setupDefaults
{
self.backgroundColor = [UIColor redColor];
self.layer.cornerRadius = 4;
self.clipsToBounds = YES;
}
-(void)layoutSubviews
{
[super layoutSubviews];
[self refreshMask];
}
-(void)setTitleColor:(UIColor *)color forState:(UIControlState)state
{
[super setTitleColor:[UIColor clearColor] forState:state];
}
-(void)refreshMask
{
self.titleLabel.backgroundColor = [UIColor clearColor];
[self setTitleColor:[UIColor clearColor] forState:UIControlStateNormal];
NSString * text = self.titleLabel.text;
CGSize buttonSize = self.bounds.size;
UIFont * font = self.titleLabel.font;
NSDictionary* attribs = #{NSFontAttributeName: self.titleLabel.font};
CGSize textSize = [text sizeWithAttributes:attribs];
UIGraphicsBeginImageContextWithOptions(buttonSize, NO, [[UIScreen mainScreen] scale]);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(ctx, [UIColor whiteColor].CGColor);
CGPoint center = CGPointMake(buttonSize.width/2-textSize.width/2, buttonSize.height/2-textSize.height/2);
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, buttonSize.width, buttonSize.height)];
CGContextAddPath(ctx, path.CGPath);
CGContextFillPath(ctx);
CGContextSetBlendMode(ctx, kCGBlendModeDestinationOut);
[text drawAtPoint:center withAttributes:#{NSFontAttributeName:font}];
UIImage* viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CALayer *maskLayer = [CALayer layer];
maskLayer.contents = (__bridge id)(viewImage.CGImage);
maskLayer.frame = self.bounds;
self.layer.mask = maskLayer;
}
#end
it looks like this (sorry for the rainbow):

I added the following method to UIView to solve this:
-(void)maskSubviews:(NSArray*)subviews {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
CGContextFillRect(context, self.bounds);
CGContextSetBlendMode(context, kCGBlendModeDestinationOut);
for (UIView *view in subviews) {
CGPoint origin = [view convertPoint:CGPointZero toView:self];
CGContextTranslateCTM(context, origin.x, origin.y);
[view.layer renderInContext:context];
CGContextTranslateCTM(context, -origin.x, -origin.y);
}
UIImage* mask = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.maskView = [[UIImageView alloc] initWithImage:mask];
}
Simply call it with the views you want masked out. For example:
[button maskSubviews: #[button.titleLabel, button.imageView]]

I converted #purrrminator's solution to Swift 3, just the main method.
func clearTextButton(button: UIButton, title: NSString, color: UIColor) {
button.backgroundColor = color
button.titleLabel?.backgroundColor = UIColor.clear()
button.setTitleColor(UIColor.clear(), for: .normal)
button.setTitle(title as String, for: [])
let buttonSize: CGSize = button.bounds.size
let font: UIFont = button.titleLabel!.font
let attribs: [String : AnyObject] = [NSFontAttributeName: button.titleLabel!.font]
let textSize: CGSize = title.size(attributes: attribs)
UIGraphicsBeginImageContextWithOptions(buttonSize, false, UIScreen.main().scale)
let ctx: CGContext = UIGraphicsGetCurrentContext()!
ctx.setFillColor(UIColor.white().cgColor)
let center: CGPoint = CGPoint(x: buttonSize.width / 2 - textSize.width / 2, y: buttonSize.height / 2 - textSize.height / 2)
let path: UIBezierPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: buttonSize.width, height: buttonSize.height))
ctx.addPath(path.cgPath)
ctx.fillPath()
ctx.setBlendMode(.destinationOut)
title.draw(at: center, withAttributes: [NSFontAttributeName: font])
let viewImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
let maskLayer: CALayer = CALayer()
maskLayer.contents = ((viewImage.cgImage) as! AnyObject)
maskLayer.frame = button.bounds
button.layer.mask = maskLayer
}

Related

How to cut out a hole with radial gradient in view

I have a transparent light hole view, a view overlay super view, and there is a hole gradient round view in the cover view, the super view is still visiable.
I have no idea to implement the gradient light hole view, anyone can help me
EDIT ============
I try to add cover view to the full screen, the cover view background color is clear., and add custom CALayer to the cover view layer as sublayer.
the cover view implementation:
#implementation CoverView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
}
return self;
}
- (void)setGradientHoleFrame:(CGRect)gradientHoleFrame {
if (!CGRectEqualToRect(_gradientHoleFrame, gradientHoleFrame)) {
_gradientHoleFrame = gradientHoleFrame;
[self loadRadialGradientLayer];
}
}
- (void)loadRadialGradientLayer {
RadialGradientLayer *layer = [[RadialGradientLayer alloc] init];
layer.frame = self.bounds;
layer.raidalGradientFrame = self.gradientHoleFrame;
[layer setNeedsDisplay];
[self.layer addSublayer:layer];
}
#end
the custom radial gradient layer:
CGFloat const GRADIENT_WIDTH = 10.0f;
#implementation RadialGradientLayer
- (void)setRaidalGradientFrame:(CGRect)raidalGradientFrame {
if (!CGRectEqualToRect(_raidalGradientFrame, raidalGradientFrame)) {
_raidalGradientFrame = raidalGradientFrame;
[self setNeedsDisplay];
}
}
- (void)drawInContext:(CGContextRef)context {
CGContextSaveGState(context);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat colours[8] = { 0.0f, 0.0f, 0.0f, 0.0f, // Clear region colour.
0.0f, 0.0f, 0.0f, 0.8 }; // Blur region colour.
CGFloat locations[2] = { 0.0f, 1.0f };
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, colours, locations, 2);
CGPoint center = CGPointMake(self.raidalGradientFrame.origin.x + self.raidalGradientFrame.size.width / 2,
self.raidalGradientFrame.origin.y + self.raidalGradientFrame.size.height / 2);
CGFloat radius = MIN(self.raidalGradientFrame.size.width / 2, self.raidalGradientFrame.size.height / 2) + GRADIENT_WIDTH;
CGContextDrawRadialGradient(context, gradient, center, 0.0, center, radius, kCGGradientDrawsAfterEndLocation);
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
}
#end
and I user it:
CoverView *view = [[CoverView alloc] initWithFrame:[UIScreen mainScreen].bounds];
// for get hole frame
CGRect rect = [self.coinPointView.superview convertRect:self.coinPointView.frame toView:view];
view.gradientHoleFrame = rect;
[self.tabBarController.view addSubview:view];
Finally, I got result below.
thanks for #gbk and #matt
You can play around a little bit and get
To achieve such result I prepared sample code in playground - just copy-paste it and try.
import UIKit
import PlaygroundSupport
class RadialGradientLayer: CALayer {
required override init() {
super.init()
needsDisplayOnBoundsChange = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
required override init(layer: Any) {
super.init(layer: layer)
}
//default colors
public var colors = [UIColor.red.cgColor, UIColor.clear.cgColor]
override func draw(in ctx: CGContext) {
ctx.saveGState()
let colorSpace = CGColorSpaceCreateDeviceRGB()
var locations = [CGFloat]()
for i in 0...colors.count-1 {
locations.append(CGFloat(i) / CGFloat(colors.count))
}
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: locations)
let center = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)
let radius = min(bounds.width, bounds.height)
ctx.drawRadialGradient(gradient!, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: radius, options: CGGradientDrawingOptions(rawValue: 0))
}
}
let view = UIView(frame: CGRect(x: 0, y: 0, width: 375, height: 300))
view.backgroundColor = UIColor.green
let label = UILabel(frame: view.bounds)
label.text = "test"
label.font = UIFont.systemFont(ofSize: 30)
label.textAlignment = .center
view.addSubview(label)
let gradientLayer = RadialGradientLayer()
gradientLayer.frame = view.bounds
gradientLayer.colors = [UIColor.clear.cgColor, UIColor.black.cgColor]
gradientLayer.setNeedsDisplay()
view.layer.addSublayer(gradientLayer)
view

UISlider distorted rounded corner

I created a UISlider and then setting tint colour for min track like this :
[slider setMinimumTrackTintColor:[UIColor whiteColor]];
However I end up with distorted rounded corner on the left side like this. What do I do ?
Edit: Here is the code that reproduces the issue :
UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(40, 40, 180, 50)];
[slider setMinimumTrackTintColor:[UIColor whiteColor]];
I have experienced the same error while increasing the height of the slider.
The edges of the slider lost the rounded corners, when the height of slider increased. To fix it I created images with rounded corners and assigned it to the slider minimum track image.
Swift
override func viewDidLoad() {
super.viewDidLoad()
slider.maximumTrackTintColor = UIColor.gray;
let img:UIImage = GetImage(rectToDraw: CGRect(x: 0, y: 0, width: 400, height: 400));
slider.setMinimumTrackImage(img.resizableImage(withCapInsets: UIEdgeInsets(top: 13,left: 15,bottom: 15,right: 14)), for: UIControlState.normal)
slider.setMinimumTrackImage(img.resizableImage(withCapInsets: UIEdgeInsets(top: 13,left: 15,bottom: 15,right: 14)), for: UIControlState.selected)
}
func GetImage(rectToDraw:CGRect) -> UIImage {
let layer:CALayer = CALayer()
layer.frame = rectToDraw;
layer.cornerRadius = 20;
layer.backgroundColor = UIColor.red.cgColor;
UIGraphicsBeginImageContext(layer.frame.size)
layer.render(in: UIGraphicsGetCurrentContext()!)
let image :UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image;
}
Xamarin.iOS
Within the Custom Renderer
protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
{
MySlideriOS sliderIos = new MySlideriOS();
SetNativeControl(sliderIos);
base.OnElementChanged(e);
}
Within the Custom slider
public class MySlideriOS : UISlider
{
public UILabel label = null;
public MySlideriOS()
{
this.MaximumTrackTintColor = UIColor.Gray;
UIImage img = GetImage(new CGRect(0,0,400,400));
this.SetMinTrackImage(img.CreateResizableImage(new UIEdgeInsets(13, 15, 15,14)), UIControlState.Normal);
this.SetMinTrackImage(img.CreateResizableImage(new UIEdgeInsets(13, 15, 15, 14)), UIControlState.Selected);
this.MaxValue = 15;
}
public UIImage GetImage(CGRect recttoDraw)
{
CGRect rect = recttoDraw;
CALayer layer = new CALayer();
layer.Frame = recttoDraw;
layer.CornerRadius = 20;
layer.BackgroundColor = UIColor.Red.CGColor;
UIGraphics.BeginImageContext(layer.Frame.Size);
layer.RenderInContext(UIGraphics.GetCurrentContext());
UIImage image = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return image;
}
I've experienced the same glitch and found a solution by adding a minimumTrackImage for the slider, based on color.
UIImage *black = [UIImage imageWithColor:[UIColor blackColor]];
[self.slider setMinimumTrackImage:black forState:UIControlStateNormal];
Add a category of UIImage with the following method - imageWithColor
+ (UIImage *)imageWithColor:(UIColor *)color {
CGRect rect = CGRectMake(0.0f, 0.0f, 1.0, 1.0);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}

Add text on custom marker on google map for ios

Am trying to put marker with Textview .Is there any posibility to add text over marker on google map in ios.
Like This
You must make a view, where you must create an imageView (with your marker image) and Label (with your text) and take a screenshot of that view, and set as icon to your GMSMarker.
Something like this:
- (void)foo
{
GMSMarker *marker = [GMSMarker new];
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0,0,60,60)];
UIImageView *pinImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"myPin"]];
UILabel *label = [UILabel new];
label.text = #"1";
//label.font = ...;
[label sizeToFit];
[view addSubview:pinImageView];
[view addSubview:label];
//i.e. customize view to get what you need
UIImage *markerIcon = [self imageFromView:view];
marker.icon = markerIcon;
marker.map = self.mapView;
}
- (UIImage *)imageFromView:(UIView *) view
{
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)]) {
UIGraphicsBeginImageContextWithOptions(view.frame.size, NO, [[UIScreen mainScreen] scale]);
} else {
UIGraphicsBeginImageContext(view.frame.size);
}
[view.layer renderInContext: UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
If you want to display something like this , then just follow these steps. It is very simple, You can use this method.
-(UIImage *)createImage:(NSUInteger)count{ //count is the integer that has to be shown on the marker
UIColor *color = [UIColor redColor]; // select needed color
NSString *string = [NSString stringWithFormat:#"%lu",(unsigned long)count]; // the string to colorize
NSDictionary *attrs = #{ NSForegroundColorAttributeName : color };
NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:string attributes:attrs]; // add Font according to your need
UIImage *image = [UIImage imageNamed:#"ic_marker_orange"]; // The image on which text has to be added
UIGraphicsBeginImageContext(image.size);
[image drawInRect:CGRectMake(0,0,image.size.width,image.size.height)];
CGRect rect = CGRectMake(20,5, image.size.width, image.size.height);// change the frame of your text from here
[[UIColor whiteColor] set];
[attrStr drawInRect:rect];
UIImage *markerImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return markerImage;}
and when you set marker to the map then just set
GMSMarker *marker = [[GMSMarker alloc] init];
marker.icon = [self createImage:[model.strFriendCount integerValue]]; // pass any integer to the method.
Here is the Swift 5 version of Kunal's answer:
//count is the integer that has to be shown on the marker
func createImage(_ count: Int) -> UIImage {
let color = UIColor.red
// select needed color
let string = "\(UInt(count))"
// the string to colorize
let attrs: [NSAttributedString.Key: Any] = [.foregroundColor: color]
let attrStr = NSAttributedString(string: string, attributes: attrs)
// add Font according to your need
let image = UIImage(named: "ic_marker_orange")!
// The image on which text has to be added
UIGraphicsBeginImageContext(image.size)
image.draw(in: CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(image.size.width), height: CGFloat(image.size.height)))
let rect = CGRect(x: CGFloat(20), y: CGFloat(5), width: CGFloat(image.size.width), height: CGFloat(image.size.height))
attrStr.draw(in: rect)
let markerImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return markerImage
}
Hope this helps someone else.
func createImage(_ count: Int) -> UIImage? {
let string = String(count)
let paragraphStyle = NSMutableParagraphStyle()
let font = UIFont.systemFont(ofSize: 9.5)
paragraphStyle.alignment = .center
let attributes: [NSAttributedString.Key: Any] = [
.font : font,
.foregroundColor: UIColor.black,
.paragraphStyle: paragraphStyle
]
let attrStr = NSAttributedString(string: string, attributes: attributes)
let image = self.imageWithImage(image: UIImage(named: "pin_online.png")!, scaledToSize: CGSize(width: 50.0, height: 60.0))
UIGraphicsBeginImageContext(image.size)
UIGraphicsBeginImageContextWithOptions(image.size, false, 0.0)
image.draw(in: CGRect(x: 0, y: 0, width: CGFloat(image.size.width), height: CGFloat(image.size.height)))
let rect = CGRect(x: 14.7, y: CGFloat(3), width: CGFloat(image.size.width), height: CGFloat(image.size.height))
attrStr.draw(in: rect)
let markerImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return markerImage
}

SKSpriteNode - create a round corner node?

Is there a way to make a SKSpriteNode round cornered? I am trying to create a Tile likesqaure blocks with color filled SKSpriteNode:
SKSpriteNode *tile = [SKSpriteNode spriteNodeWithColor:[UIColor colorWithRed:0.0/255.0
green:128.0/255.0
blue:255.0/255.0
alpha:1.0] size:CGSizeMake(30, 30)];
How can I make it round cornered?
Thanks!
To get a rounded corner node you can use 2 approaches, each of them requires use of SKShapeNode.
First way is to use SKShapeNode and set its path to be a rounded rectangle like this:
SKShapeNode* tile = [SKShapeNode node];
[tile setPath:CGPathCreateWithRoundedRect(CGRectMake(-15, -15, 30, 30), 4, 4, nil)];
tile.strokeColor = tile.fillColor = [UIColor colorWithRed:0.0/255.0
green:128.0/255.0
blue:255.0/255.0
alpha:1.0];
The other one uses sprite node,crop node and SKShapeNode with rounded rectangle as crop nodes mask:
SKSpriteNode *tile = [SKSpriteNode spriteNodeWithColor:[UIColor colorWithRed:0.0/255.0
green:128.0/255.0
blue:255.0/255.0
alpha:1.0] size:CGSizeMake(30, 30)];
SKCropNode* cropNode = [SKCropNode node];
SKShapeNode* mask = [SKShapeNode node];
[mask setPath:CGPathCreateWithRoundedRect(CGRectMake(-15, -15, 30, 30), 4, 4, nil)];
[mask setFillColor:[SKColor whiteColor]];
[cropNode setMaskNode:mask];
[cropNode addChild:tile];
If your tiles are one solid colour, i suggest you go with the first approach.
In Swift 3 you can create with:
let tile = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 30, height: 30), cornerRadius: 10)
Hope this helps:
SKSpriteNode *make_rounded_rectangle(UIColor *color, CGSize size, float radius)
{
UIGraphicsBeginImageContext(size);
[color setFill];
CGRect rect = CGRectMake(0, 0, size.width, size.height);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius];
[path fill];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
SKTexture *texture = [SKTexture textureWithImage:image];
return [SKSpriteNode spriteNodeWithTexture:texture];
}
This is heavily inspired by the accepted answer, yet it uses a more readable way to create the SKShapeNode and also fixes the annoying pixel line around the crop. Seems like a minor detail, but it may cost someone a few minutes.
CGFloat cornerRadius = 15;
SKCropNode *cropNode = [SKCropNode node];
SKShapeNode *maskNode = [SKShapeNode shapeNodeWithRectOfSize:scoreNode.size cornerRadius:cornerRadius];
[maskNode setLineWidth:0.0];
[maskNode setFillColor:[UIColor whiteColor]];
[cropNode setMaskNode:maskNode];
[cropNode addChild:scoreNode];
It's really this simple.......
class YourSprite: SKSpriteNode {
func yourSetupFunction() {
texture = SKTexture( image: UIImage(named: "cat")!.circleMasked! )
There's nothing more to it.
You really can not use SKShapeNode.
It's just that simple. It's an insane performance hit using SKShapeNode, it's not the right solution, it's pointlessly difficult, and the purpose of SKShapeNode just has no relation to this problem.
Look at all them kitties!
The code for circleMasked is this simple:
(All projects that deal with images will need this anyway.)
extension UIImage {
var isPortrait: Bool { return size.height > size.width }
var isLandscape: Bool { return size.width > size.height }
var breadth: CGFloat { return min(size.width, size.height) }
var breadthSize: CGSize { return CGSize(width: breadth, height: breadth) }
var breadthRect: CGRect { return CGRect(origin: .zero, size: breadthSize) }
var circleMasked: UIImage? {
UIGraphicsBeginImageContextWithOptions(breadthSize, false, scale)
defer { UIGraphicsEndImageContext() }
guard let cgImage = cgImage?.cropping(to: CGRect(origin:
CGPoint(
x: isLandscape ? floor((size.width - size.height) / 2) : 0,
y: isPortrait ? floor((size.height - size.width) / 2) : 0),
size: breadthSize))
else { return nil }
UIBezierPath(ovalIn: breadthRect).addClip()
UIImage(cgImage: cgImage, scale: 1, orientation: imageOrientation)
.draw(in: breadthRect)
return UIGraphicsGetImageFromCurrentImageContext()
}
// classic 'circleMasked' stackoverflow fragment
// courtesy Leo Dabius /a/29047372/294884
}
That's all there is to it.
Swift 4:
A good solution could be created before you should add your sprite to his parent, I've maded a little extension that could be corrected as you wish:
extension SKSpriteNode {
func addTo(parent:SKNode?, withRadius:CGFloat) {
guard parent != nil else { return }
guard withRadius>0.0 else {
parent!.addChild(self)
return
}
let radiusShape = SKShapeNode.init(rect: CGRect.init(origin: CGPoint.zero, size: size), cornerRadius: withRadius)
radiusShape.position = CGPoint.zero
radiusShape.lineWidth = 2.0
radiusShape.fillColor = UIColor.red
radiusShape.strokeColor = UIColor.red
radiusShape.zPosition = 2
radiusShape.position = CGPoint.zero
let cropNode = SKCropNode()
cropNode.position = self.position
cropNode.zPosition = 3
cropNode.addChild(self)
cropNode.maskNode = radiusShape
parent!.addChild(cropNode)
}
}
Usage:
let rootNode = SKSpriteNode(color: .white, size: self.size)
rootNode.alpha = 1.0
rootNode.anchorPoint = CGPoint.zero
rootNode.position = CGPoint.zero
rootNode.zPosition = 1
rootNode.name = "rootNode"
rootNode.addTo(parent: self, withRadius: 40.0)
from the class reference:
"An SKSpriteNode is a node that draws a textured image, a colored square, or a textured image blended with a color."
It seems the easiest way is to draw a block with rounded corners and then use one of these class methods:
spriteNodeWithImageNamed:
spriteNodeWithTexture:
spriteNodeWithTexture:size:
Here's a Swift 3 snippet based on the second solution of the accepted answer.
func createPlayerRoundedNode(){
let tile = SKSpriteNode(color: .white, size: CGSize(width: 30, height: 30))
tile.zPosition = 3
tile.name = "tile node"
let cropNode = SKCropNode()
cropNode.zPosition = 2
cropNode.name = "crop node"
let mask = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 30, height: 30), cornerRadius: 10)
mask.fillColor = SKColor.darkGray
mask.zPosition = 2
mask.name = "mask node"
cropNode.maskNode = mask
self.addChild(cropNode)
cropNode.addChild(tile)
}

UIGraphicsBeginImageContextWithOptions not capturing shadows / gradients

THE PROBLEM
I'm using UIGraphicsBeginImageContextWithOptions to save a screenshot into a UIImage. While this works, the resulting UIImage is missing any shadows and gradients.
How do I get it to also screenshot the layer stuff?
TEST CODE SHOWING THE PROBLEM
#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>
#implementation ViewController
- (void)viewDidLoad {
//super
[super viewDidLoad];
//background
self.view.backgroundColor = [UIColor whiteColor];
//container
UIView *container = [[UIView alloc] initWithFrame:CGRectMake(10, 10, 300, 200)];
container.backgroundColor = [UIColor blackColor];
[self.view addSubview:container];
//circle
UIView *circle = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 10, 10)];
circle.backgroundColor = [UIColor whiteColor];
circle.layer.cornerRadius = 5;
circle.layer.shadowColor = [UIColor redColor].CGColor;
circle.layer.shadowOffset = CGSizeZero;
circle.layer.shadowOpacity = 1;
circle.layer.shadowRadius = 10;
circle.layer.shadowPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(-10, -10, 30, 30)].CGPath;
[container addSubview:circle];
//screenshot
UIGraphicsBeginImageContextWithOptions(container.bounds.size, container.opaque, 0.0);
[container.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//image view
UIImageView *iv = [[UIImageView alloc] initWithFrame:CGRectMake(10, 568-200-20-10, 300, 200)];
iv.image = screenshot;
[self.view addSubview:iv];
}
#end
SIMULATOR SCREENSHOT SHOWING THE PROBLEM
The top area is the original view. The bottom area is a UIImageView with the screenshot.
In this line ,
UIGraphicsBeginImageContextWithOptions(container.bounds.size, container.opaque, 0.0);
put NO instead of container.opaque
Use this method ,
- (UIImage *)imageOfView:(UIView *)view
{
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)]) {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, 0.0);
} else {
UIGraphicsBeginImageContext(view.bounds.size);
}
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
For drop shadow on an Image:
public func generateShadow(for image: UIImage, shadowOffset: CGSize,blur: CGFloat, shadowColor: CGColor ) -> UIImage? {
let size = CGSize(width: image.size.width + blur, height:
image.size.height + blur)
return MediaPickerFramework.generateImage(CGSize(width: size.width, height: size.width), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setShadow(offset: shadowOffset,
blur: blur,
color: shadowColor)
let center = CGPoint(x: size.width / 2, y: size.height / 2)
let imageOrigin = CGPoint(x: center.x - (image.size.width / 2), y: center.y - (image.size.height / 2))
context.draw(image.cgImage!,
in: CGRect(x: imageOrigin.x, y: imageOrigin.y, width: image.size.width, height: image.size.height),
byTiling:false)
})
}
and for draw Gradient on an image, you can use the below function
func generateGradientTintedImage(image: UIImage?, colors: [UIColor]) -> UIImage? {
guard let image = image else {
return nil
}
let imageSize = image.size
UIGraphicsBeginImageContextWithOptions(imageSize, false, image.scale)
if let context = UIGraphicsGetCurrentContext() {
let imageRect = CGRect(origin: CGPoint(), size: imageSize)
context.saveGState()
context.translateBy(x: imageRect.midX, y: imageRect.midY)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -imageRect.midX, y: -imageRect.midY)
context.clip(to: imageRect, mask: image.cgImage!)
let gradientColors = colors.map { $0.cgColor } as CFArray
let delta: CGFloat = 1.0 / (CGFloat(colors.count) - 1.0)
var locations: [CGFloat] = []
for i in 0 ..< colors.count {
locations.append(delta * CGFloat(i))
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: imageRect.height), end: CGPoint(x: 0.0, y: 0.0), options: CGGradientDrawingOptions())
context.restoreGState()
}
let tintedImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return tintedImage
}

Resources