How would I outline a UILabel in black? [duplicate] - iphone

All I want is a one pixel black border around my white UILabel text.
I got as far as subclassing UILabel with the code below, which I clumsily cobbled together from a few tangentially related online examples. And it works but it's very, very slow (except on the simulator) and I couldn't get it to center the text vertically either (so I hard-coded the y value on the last line temporarily). Ahhhh!
void ShowStringCentered(CGContextRef gc, float x, float y, const char *str) {
CGContextSetTextDrawingMode(gc, kCGTextInvisible);
CGContextShowTextAtPoint(gc, 0, 0, str, strlen(str));
CGPoint pt = CGContextGetTextPosition(gc);
CGContextSetTextDrawingMode(gc, kCGTextFillStroke);
CGContextShowTextAtPoint(gc, x - pt.x / 2, y, str, strlen(str));
}
- (void)drawRect:(CGRect)rect{
CGContextRef theContext = UIGraphicsGetCurrentContext();
CGRect viewBounds = self.bounds;
CGContextTranslateCTM(theContext, 0, viewBounds.size.height);
CGContextScaleCTM(theContext, 1, -1);
CGContextSelectFont (theContext, "Helvetica", viewBounds.size.height, kCGEncodingMacRoman);
CGContextSetRGBFillColor (theContext, 1, 1, 1, 1);
CGContextSetRGBStrokeColor (theContext, 0, 0, 0, 1);
CGContextSetLineWidth(theContext, 1.0);
ShowStringCentered(theContext, rect.size.width / 2.0, 12, [[self text] cStringUsingEncoding:NSASCIIStringEncoding]);
}
I just have a nagging feeling that I'm overlooking a simpler way to do this. Perhaps by overriding "drawTextInRect", but I can't seem to get drawTextInRect to bend to my will at all despite staring at it intently and frowning really really hard.

I was able to do it by overriding drawTextInRect:
- (void)drawTextInRect:(CGRect)rect {
CGSize shadowOffset = self.shadowOffset;
UIColor *textColor = self.textColor;
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(c, 1);
CGContextSetLineJoin(c, kCGLineJoinRound);
CGContextSetTextDrawingMode(c, kCGTextStroke);
self.textColor = [UIColor whiteColor];
[super drawTextInRect:rect];
CGContextSetTextDrawingMode(c, kCGTextFill);
self.textColor = textColor;
self.shadowOffset = CGSizeMake(0, 0);
[super drawTextInRect:rect];
self.shadowOffset = shadowOffset;
}

A simpler solution is to use an Attributed String like so:
Swift 4:
let strokeTextAttributes: [NSAttributedStringKey : Any] = [
NSAttributedStringKey.strokeColor : UIColor.black,
NSAttributedStringKey.foregroundColor : UIColor.white,
NSAttributedStringKey.strokeWidth : -2.0,
]
myLabel.attributedText = NSAttributedString(string: "Foo", attributes: strokeTextAttributes)
Swift 4.2:
let strokeTextAttributes: [NSAttributedString.Key : Any] = [
.strokeColor : UIColor.black,
.foregroundColor : UIColor.white,
.strokeWidth : -2.0,
]
myLabel.attributedText = NSAttributedString(string: "Foo", attributes: strokeTextAttributes)
On a UITextField you can set the defaultTextAttributes and the attributedPlaceholder as well.
Note that the NSStrokeWidthAttributeName has to be negative in this case, i.e. only the inner outlines work.

After reading the accepted answer and the two corrections to it and the answer from Axel Guilmin, I decided to compile an overall solution in Swift, that suits me:
import UIKit
class UIOutlinedLabel: UILabel {
var outlineWidth: CGFloat = 1
var outlineColor: UIColor = UIColor.whiteColor()
override func drawTextInRect(rect: CGRect) {
let strokeTextAttributes = [
NSStrokeColorAttributeName : outlineColor,
NSStrokeWidthAttributeName : -1 * outlineWidth,
]
self.attributedText = NSAttributedString(string: self.text ?? "", attributes: strokeTextAttributes)
super.drawTextInRect(rect)
}
}
You can add this custom UILabel class to an existing label in the Interface Builder and change the thickness of the border and its color by adding User Defined Runtime Attributes like this:
Result:

There is one issue with the answer's implementation. Drawing a text with stroke has a slightly different character glyph width than drawing a text without stroke, which can produce "uncentered" results. It can be fixed by adding an invisible stroke around the fill text.
Replace:
CGContextSetTextDrawingMode(c, kCGTextFill);
self.textColor = textColor;
self.shadowOffset = CGSizeMake(0, 0);
[super drawTextInRect:rect];
with:
CGContextSetTextDrawingMode(context, kCGTextFillStroke);
self.textColor = textColor;
[[UIColor clearColor] setStroke]; // invisible stroke
self.shadowOffset = CGSizeMake(0, 0);
[super drawTextInRect:rect];
I'm not 100% sure, if that's the real deal, because I don't know if self.textColor = textColor; has the same effect as [textColor setFill], but it should work.
Disclosure: I'm the developer of THLabel.
I've released a UILabel subclass a while ago, which allows an outline in text and other effects. You can find it here: https://github.com/tobihagemann/THLabel

A Swift 4 class version based off the answer by kprevas
import Foundation
import UIKit
public class OutlinedText: UILabel{
internal var mOutlineColor:UIColor?
internal var mOutlineWidth:CGFloat?
#IBInspectable var outlineColor: UIColor{
get { return mOutlineColor ?? UIColor.clear }
set { mOutlineColor = newValue }
}
#IBInspectable var outlineWidth: CGFloat{
get { return mOutlineWidth ?? 0 }
set { mOutlineWidth = newValue }
}
override public func drawText(in rect: CGRect) {
let shadowOffset = self.shadowOffset
let textColor = self.textColor
let c = UIGraphicsGetCurrentContext()
c?.setLineWidth(outlineWidth)
c?.setLineJoin(.round)
c?.setTextDrawingMode(.stroke)
self.textColor = mOutlineColor;
super.drawText(in:rect)
c?.setTextDrawingMode(.fill)
self.textColor = textColor
self.shadowOffset = CGSize(width: 0, height: 0)
super.drawText(in:rect)
self.shadowOffset = shadowOffset
}
}
It can be implemented entirely in the Interface Builder by setting the UILabel's custom class to OutlinedText. You will then have the ability to set the outline's width and color from the Properties pane.

If your goal is something like this:
Here is how I achieved it: I added a new label of a custom class as a Subview to my current UILabel (inspired by this answer).
Just copy & paste it into your project and you are good to go:
extension UILabel {
func addTextOutline(usingColor outlineColor: UIColor, outlineWidth: CGFloat) {
class OutlinedText: UILabel{
var outlineWidth: CGFloat = 0
var outlineColor: UIColor = .clear
override public func drawText(in rect: CGRect) {
let shadowOffset = self.shadowOffset
let textColor = self.textColor
let c = UIGraphicsGetCurrentContext()
c?.setLineWidth(outlineWidth)
c?.setLineJoin(.round)
c?.setTextDrawingMode(.stroke)
self.textAlignment = .center
self.textColor = outlineColor
super.drawText(in:rect)
c?.setTextDrawingMode(.fill)
self.textColor = textColor
self.shadowOffset = CGSize(width: 0, height: 0)
super.drawText(in:rect)
self.shadowOffset = shadowOffset
}
}
let textOutline = OutlinedText()
let outlineTag = 9999
if let prevTextOutline = viewWithTag(outlineTag) {
prevTextOutline.removeFromSuperview()
}
textOutline.outlineColor = outlineColor
textOutline.outlineWidth = outlineWidth
textOutline.textColor = textColor
textOutline.font = font
textOutline.text = text
textOutline.tag = outlineTag
sizeToFit()
addSubview(textOutline)
textOutline.frame = CGRect(x: -(outlineWidth / 2), y: -(outlineWidth / 2),
width: bounds.width + outlineWidth,
height: bounds.height + outlineWidth)
}
}
USAGE:
yourLabel.addTextOutline(usingColor: .red, outlineWidth: 6)
it also works for a UIButton with all its animations:
yourButton.titleLabel?.addTextOutline(usingColor: .red, outlineWidth: 6)

If you want to animate something complicated, the best way is to programmaticly take a screenshot of it an animate that instead!
To take a screenshot of a view, you'll need code a little like this:
UIGraphicsBeginImageContext(mainContentView.bounds.size);
[mainContentView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Where mainContentView is the view you want to take a screenshot of. Add viewImage to a UIImageView and animate that.
Hope that speeds up your animation!!
N

As MuscleRumble mentioned, the accepted answer's border is a bit off center. I was able to correct this by setting the stroke width to zero instead of changing the color to clear.
i.e. replacing:
CGContextSetTextDrawingMode(c, kCGTextFill);
self.textColor = textColor;
self.shadowOffset = CGSizeMake(0, 0);
[super drawTextInRect:rect];
with:
CGContextSetTextDrawingMode(c, kCGTextFillStroke);
self.textColor = textColor;
CGContextSetLineWidth(c, 0); // set stroke width to zero
self.shadowOffset = CGSizeMake(0, 0);
[super drawTextInRect:rect];
I would've just commented on his answer but apparently I'm not "reputable" enough.

This won't create an outline per-se, but it will put a shadow around the text, and if you make the shadow radius small enough it could resemble an outline.
label.layer.shadowColor = [[UIColor blackColor] CGColor];
label.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
label.layer.shadowOpacity = 1.0f;
label.layer.shadowRadius = 1.0f;
I don't know whether it is compatible with older versions of iOS..
Anyway, I hope it helps...

if ALL you want is a one pixel black border around my white UILabel text,
then
i do think you're making the problem harder than it is...
I don't know by memory which 'draw rect / frameRect' function you should use, but it will be easy for you to find. this method just demonstrates the strategy (let the superclass do the work!):
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
[context frameRect:rect]; // research which rect drawing function to use...
}

I found an issue with the main answer. The text position is not necessarily centered correctly to sub-pixel location, so that the outline can be mismatched around the text. I fixed it using the following code, which uses CGContextSetShouldSubpixelQuantizeFonts(ctx, false):
- (void)drawTextInRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
[self.textOutlineColor setStroke];
[self.textColor setFill];
CGContextSetShouldSubpixelQuantizeFonts(ctx, false);
CGContextSetLineWidth(ctx, self.textOutlineWidth);
CGContextSetLineJoin(ctx, kCGLineJoinRound);
CGContextSetTextDrawingMode(ctx, kCGTextStroke);
[self.text drawInRect:rect withFont:self.font lineBreakMode:NSLineBreakByWordWrapping alignment:self.textAlignment];
CGContextSetTextDrawingMode(ctx, kCGTextFill);
[self.text drawInRect:rect withFont:self.font lineBreakMode:NSLineBreakByWordWrapping alignment:self.textAlignment];
}
This assumes that you defined textOutlineColor and textOutlineWidth as properties.

Here is the another answer to set outlined text on label.
extension UILabel {
func setOutLinedText(_ text: String) {
let attribute : [NSAttributedString.Key : Any] = [
NSAttributedString.Key.strokeColor : UIColor.black,
NSAttributedString.Key.foregroundColor : UIColor.white,
NSAttributedString.Key.strokeWidth : -2.0,
NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 12)
] as [NSAttributedString.Key : Any]
let customizedText = NSMutableAttributedString(string: text,
attributes: attribute)
attributedText = customizedText
}
}
set outlined text simply using the extension method.
lblTitle.setOutLinedText("Enter your email address or username")

it is also possible to subclass UILabel with the following logic:
- (void)setText:(NSString *)text {
[self addOutlineForAttributedText:[[NSAttributedString alloc] initWithString:text]];
}
- (void)setAttributedText:(NSAttributedString *)attributedText {
[self addOutlineForAttributedText:attributedText];
}
- (void)addOutlineForAttributedText:(NSAttributedString *)attributedText {
NSDictionary *strokeTextAttributes = #{
NSStrokeColorAttributeName: [UIColor blackColor],
NSStrokeWidthAttributeName : #(-2)
};
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithAttributedString:attributedText];
[attrStr addAttributes:strokeTextAttributes range:NSMakeRange(0, attrStr.length)];
super.attributedText = attrStr;
}
and if you set text in Storyboard then:
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
// to apply border for text from storyboard
[self addOutlineForAttributedText:[[NSAttributedString alloc] initWithString:self.text]];
}
return self;
}

Why don't you create a 1px border UIView in Photoshop, then set a UIView with the image, and position it behind your UILabel?
Code:
UIView *myView;
UIImage *imageName = [UIImage imageNamed:#"1pxBorderImage.png"];
UIColor *tempColour = [[UIColor alloc] initWithPatternImage:imageName];
myView.backgroundColor = tempColour;
[tempColour release];
It's going to save you subclassing an object and it's fairly simple to do.
Not to mention if you want to do animation, it's built into the UIView class.

To put a border with rounded edges around a UILabel I do the following:
labelName.layer.borderWidth = 1;
labelName.layer.borderColor = [[UIColor grayColor] CGColor];
labelName.layer.cornerRadius = 10;
(don't forget to include QuartzCore/QuartzCore.h)

Related

How to draw vertical text in UILabel

I'm currently working on drawing vertical Chinese text in a label. Here's what I am trying to achieve, albeit with Chinese Characters:
I've been planning to draw each character, rotate each character 90 degrees to the left, then rotating the entire label via affine transformations to get the final result. However, it feels awfully complicated. Is there an easier way to draw the text without complicated CoreGraphics magic that I'm missing?
Well, You can do like below:
labelObject.numberOfLines = 0;
labelObject.lineBreakMode = NSLineBreakByCharWrapping;
and setFrame with -- height:100, width:20 It will work fine..
It works
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 30, 100)];
lbl.transform = CGAffineTransformMakeRotation((M_PI)/2);
Tried the method offered by Simha.IC but it didn't work well for me. Some characters are thinner than others and get placed two on a line. E.g.
W
ai
ti
n
g
The solution for me was to create a method that transforms the string itself into a multiline text by adding \n after each character. Here's the method:
- (NSString *)transformStringToVertical:(NSString *)originalString
{
NSMutableString *mutableString = [NSMutableString stringWithString:originalString];
NSRange stringRange = [mutableString rangeOfString:mutableString];
for (int i = 1; i < stringRange.length*2 - 2; i+=2)
{
[mutableString insertString:#"\n" atIndex:i];
}
return mutableString;
}
Then you just setup the label like this:
label.text = [self transformStringToVertical:myString];
CGRect labelFrame = label.frame;
labelFrame.size.width = label.font.pointSize;
labelFrame.size.height = label.font.lineHeight * myString.length;
label.frame = labelFrame;
Enjoy!
If you would like to rotate the whole label (including characters), you can do so as follows:
First add the QuartzCore library to your project.
Create a label:
UILabel* label = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 300.0, 30.0)];
[label setText:#"Label Text"];
Rotate the label:
[label setTransform:CGAffineTransformMakeRotation(-M_PI / 2)];
Depending on how you'd like to position the label you may need to set the anchor point. This sets the point around which a rotation occurs. Eg:
[label.layer setAnchorPoint:CGPointMake(0.0, 1.0)];
This is another way to draw vertical text, by subclassing UILabel. But it is some kind different of what the question want.
Objective-C
#implementation MyVerticalLabel
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGAffineTransform transform = CGAffineTransformMakeRotation(-M_PI_2);
CGContextConcatCTM(context, transform);
CGContextTranslateCTM(context, -rect.size.height, 0);
CGRect newRect = CGRectApplyAffineTransform(rect, transform);
newRect.origin = CGPointZero;
NSMutableParagraphStyle *textStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
textStyle.lineBreakMode = self.lineBreakMode;
textStyle.alignment = self.textAlignment;
NSDictionary *attributeDict =
#{
NSFontAttributeName : self.font,
NSForegroundColorAttributeName : self.textColor,
NSParagraphStyleAttributeName : textStyle,
};
[self.text drawInRect:newRect withAttributes:attributeDict];
}
#end
A sample image is following:
Swift
It can put on the storyboard, and watch the result directly. Like the image, it's frame will contain the vertical text. And text attributes, like textAlignment, font, work well too.
#IBDesignable
class MyVerticalLabel: UILabel {
override func drawRect(rect: CGRect) {
guard let text = self.text else {
return
}
// Drawing code
let context = UIGraphicsGetCurrentContext()
let transform = CGAffineTransformMakeRotation( CGFloat(-M_PI_2))
CGContextConcatCTM(context, transform)
CGContextTranslateCTM(context, -rect.size.height, 0)
var newRect = CGRectApplyAffineTransform(rect, transform)
newRect.origin = CGPointZero
let textStyle = NSMutableParagraphStyle.defaultParagraphStyle().mutableCopy() as! NSMutableParagraphStyle
textStyle.lineBreakMode = self.lineBreakMode
textStyle.alignment = self.textAlignment
let attributeDict: [String:AnyObject] = [
NSFontAttributeName: self.font,
NSForegroundColorAttributeName: self.textColor,
NSParagraphStyleAttributeName: textStyle,
]
let nsStr = text as NSString
nsStr.drawInRect(newRect, withAttributes: attributeDict)
}
}
Swift 4
override func draw(_ rect: CGRect) {
guard let text = self.text else {
return
}
// Drawing code
if let context = UIGraphicsGetCurrentContext() {
let transform = CGAffineTransform( rotationAngle: CGFloat(-Double.pi/2))
context.concatenate(transform)
context.translateBy(x: -rect.size.height, y: 0)
var newRect = rect.applying(transform)
newRect.origin = CGPoint.zero
let textStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
textStyle.lineBreakMode = self.lineBreakMode
textStyle.alignment = self.textAlignment
let attributeDict: [NSAttributedStringKey: AnyObject] = [NSAttributedStringKey.font: self.font, NSAttributedStringKey.foregroundColor: self.textColor, NSAttributedStringKey.paragraphStyle: textStyle]
let nsStr = text as NSString
nsStr.draw(in: newRect, withAttributes: attributeDict)
}
}
Swift 5
More easy way with CGAffineTransform
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var verticalText: UILabel
override func viewDidLoad() {
verticalText.transform = CGAffineTransform(rotationAngle:CGFloat.pi/2)
}
}
import UIKit
class VerticalLabel : UILabel {
private var _text : String? = nil
override var text : String? {
get {
return _text
}
set {
self.numberOfLines = 0
self.textAlignment = .center
self.lineBreakMode = .byWordWrapping
_text = newValue
if let t = _text {
var s = ""
for c in t {
s += "\(c)\n"
}
super.text = s
}
}
}
}

Displaying and array of colors using CGShading

I have an array of CGColors that I need to display across a path. I tried doing this with a CGGradient, but I don't want the colors the blend between between values. It looks like the best solution would be to use a GGShading object, but I am having trouble figuring out exactly how they work. I'm mainly confused about what I need to have for the CGFunction input for the CGShading.
Can someone point me in the right direction on what I would need to make this CGFunction look like to to simply display an array go CGColors on a specified CGPath?
Thanks!
Perhaps a little late, so I hope this is still of use to you. I've listed the code for a simple UIView subclass that draws a circle using the shading you described. The code with comments should be self-explanatory.
#implementation CGShadingCircle
// This is the callback of our shading function.
// info: a pointer to our NSMutableArray of UIColor objects
// inData: contains a single float that gives is the current position within the gradient
// outData: we fill this with the color to display at the given position
static void CGShadingCallback(void* info, const float* inData, float* outData) {
// Our colors
NSMutableArray* colors = (NSMutableArray*)info;
// Position within the gradient, ranging from 0.0 to 1.0
CGFloat position = *inData;
// Find the color that we want to used based on the current position;
NSUInteger colorIndex = position * [colors count];
// Account for the edge case where position == 1.0
if (colorIndex >= [colors count])
colorIndex = [colors count] - 1;
// Get our desired color from the array
UIColor* color = [colors objectAtIndex:colorIndex];
// Copy the 4 color components (red, green, blue, alpha) to outData
memcpy(outData, CGColorGetComponents(color.CGColor), 4 * sizeof(CGFloat));
}
// Set up our colors and shading function
- (void)initInternal {
_colors = [[NSMutableArray alloc] init];
// Creating the colors in this way ensures that the underlying color space is UIDeviceRGBColorSpace
// and thus has 4 color components: red, green, blue, alpha
[_colors addObject:[UIColor colorWithRed:1.0f green:0.0f blue:0.0f alpha:1.0f]]; // Red
[_colors addObject:[UIColor colorWithRed:0.0f green:1.0f blue:0.0f alpha:1.0f]]; // Green
[_colors addObject:[UIColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:1.0f]]; // Blue
[_colors addObject:[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f]]; // White
[_colors addObject:[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:1.0f]]; // Black
// Define the shading callbacks
CGFunctionCallbacks callbacks;
callbacks.version = 0; // Defaults to 0
callbacks.evaluate = CGShadingCallback; // This is our color selection function
callbacks.releaseInfo = NULL; // Not used
// As input to our function we want 1 value in the range [0.0, 1.0].
// This is our position within the 'gradient'.
size_t domainDimension = 1;
CGFloat domain[2] = {0.0f, 1.0f};
// The output of our function is 4 values, each in the range [0.0, 1.0].
// This is our selected color for the input position.
// The 4 values are the red, green, blue and alpha components.
size_t rangeDimension = 4;
CGFloat range[8] = {0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f};
// Create the shading function
_shadingFunction = CGFunctionCreate(_colors, domainDimension, domain, rangeDimension, range, &callbacks);
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initInternal];
}
return self;
}
- (id)initWithCoder:(NSCoder*)decoder {
self = [super initWithCoder:decoder];
if (self) {
[self initInternal];
}
return self;
}
- (void)dealloc {
[_colors release];
CGFunctionRelease(_shadingFunction);
[super dealloc];
}
- (void)drawRect:(CGRect)rect {
CGRect b = self.bounds;
CGContextRef ctx = UIGraphicsGetCurrentContext();
// Create a simple elliptic path
CGContextAddEllipseInRect(ctx, b);
// Set the current path as the clipping path
CGContextClip(ctx);
// Create our shading using the function that was defined earlier.
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGShadingRef shading = CGShadingCreateAxial(colorspace,
CGPointMake(CGRectGetMinX(b), CGRectGetMidY(b)),
CGPointMake(CGRectGetMaxX(b), CGRectGetMidY(b)),
_shadingFunction,
true,
true);
// Draw the shading
CGContextDrawShading(ctx, shading);
// Cleanup
CGShadingRelease(shading);
CGColorSpaceRelease(colorspace);
}
#end
This gives me the following output:
I hope this helps!
I rewrote the accepted and rewarded answer in Swift 5+, this works exactly on the same way as the previous solution. I remove the comments in order to be the code the minimal that needed, but the called functions etc. are the same.
final class CGShadingCircle: UIView {
private var shadingFunction: CGFunction?
private var colors: Colors?
final class Colors {
let colors: [UIColor]
init(colors: [UIColor]) {
self.colors = colors
}
}
init(colors: [UIColor]) {
self.colors = Colors(colors: colors)
super.init(frame: .zero)
backgroundColor = .clear
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
}
extension CGShadingCircle {
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let shadingFunction = shadingFunction,
let context = UIGraphicsGetCurrentContext(),
let shading = CGShading(axialSpace: CGColorSpaceCreateDeviceRGB(),
start: CGPoint(x: bounds.minX, y: bounds.midY),
end: CGPoint(x: bounds.maxX, y: bounds.midY),
function: shadingFunction,
extendStart: true,
extendEnd: true) else { return }
context.addEllipse(in: bounds)
context.clip()
context.drawShading(shading)
// CGShadingRelease(shading) 'CGShadingRelease' is unavailable: Core Foundation objects are automatically memory managed
}
}
extension CGShadingCircle {
private func commonInit() {
let callback: CGFunctionEvaluateCallback = { info, inData, outData in
guard let colors = info?.load(as: Colors.self).colors else { return }
var colorIndex = Int(inData.pointee * CGFloat(colors.count))
if colorIndex >= colors.count {
colorIndex = colors.count - 1
}
let color = colors[colorIndex]
memcpy(outData, color.cgColor.components, 4 * MemoryLayout<CGFloat>.size)
}
var callbacks = CGFunctionCallbacks(version: 0, evaluate: callback, releaseInfo: nil)
var domain: [CGFloat] = [0, 1]
var range: [CGFloat] = [0, 1, 0, 1, 0, 1, 0, 1]
shadingFunction = CGFunction(info: &colors,
domainDimension: domain.count / 2,
domain: &domain,
rangeDimension: range.count / 2,
range: &range,
callbacks: &callbacks)
}
}
Note that CGShadingRelease(shading) is not needed anymore. If you try to use it, Xcode will give you an error: "Core Foundation objects are automatically memory managed."
(CGFunctionEvaluateCallback can be replaced with #convention(c) (UnsafeMutableRawPointer?, UnsafePointer<CGFloat>, UnsafeMutablePointer<CGFloat>) -> (), but the former is way more concise and clear in a Swift codebase.)
And here is a complete example with the exact same output as the Objective-C based solution:
final class ViewController: UIViewController {
private let colors: [UIColor] = {
var colors = [UIColor]()
colors.append(UIColor(red: 1, green: 0, blue: 0, alpha: 1))
colors.append(UIColor(red: 0, green: 1, blue: 0, alpha: 1))
colors.append(UIColor(red: 0, green: 0, blue: 1, alpha: 1))
colors.append(UIColor(red: 1, green: 1, blue: 1, alpha: 1))
colors.append(UIColor(red: 0, green: 0, blue: 0, alpha: 1))
return colors
}()
override func viewDidLoad() {
super.viewDidLoad()
let circle = CGShadingCircle(colors: colors)
view.addSubview(circle)
circle.translatesAutoresizingMaskIntoConstraints = false
circle.heightAnchor.constraint(equalToConstant: 200).isActive = true
circle.widthAnchor.constraint(equalToConstant: 200).isActive = true
circle.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
circle.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}

Add text to CALayer

Is it possible to add a UILabel to a CALayer without subclassing and drawing it in drawInContext:?
Thanks!
CATextLayer *label = [[CATextLayer alloc] init];
[label setFont:#"Helvetica-Bold"];
[label setFontSize:20];
[label setFrame:validFrame];
[label setString:#"Hello"];
[label setAlignmentMode:kCAAlignmentCenter];
[label setForegroundColor:[[UIColor whiteColor] CGColor]];
[layer addSublayer:label];
[label release];
I don't think you can add a UIView subclass to a CALayer object. However if you want to draw text on a CALayer object, it can be done using the drawing functions provided in NSString UIKit additions as shown below. While my code is done in the delegate's drawLayer:inContext method, the same can be used in subclass' drawInContext: method. Is there any specific UILabel functionality that you want to leverage?
- (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
CGContextSetFillColorWithColor(ctx, [[UIColor darkTextColor] CGColor]);
UIGraphicsPushContext(ctx);
/*[word drawInRect:layer.bounds
withFont:[UIFont systemFontOfSize:32]
lineBreakMode:UILineBreakModeWordWrap
alignment:UITextAlignmentCenter];*/
[word drawAtPoint:CGPointMake(30.0f, 30.0f)
forWidth:200.0f
withFont:[UIFont boldSystemFontOfSize:32]
lineBreakMode:UILineBreakModeClip];
UIGraphicsPopContext();
}
Just to document my approach, I did it like this in Swift 4+ :
let textlayer = CATextLayer()
textlayer.frame = CGRect(x: 20, y: 20, width: 200, height: 18)
textlayer.fontSize = 12
textlayer.alignmentMode = .center
textlayer.string = stringValue
textlayer.isWrapped = true
textlayer.truncationMode = .end
textlayer.backgroundColor = UIColor.white.cgColor
textlayer.foregroundColor = UIColor.black.cgColor
caLayer.addSublayer(textlayer) // caLayer is and instance of parent CALayer
Your UILabel already has a CALayer behind it. If you are putting together several CALayers, you can just add the UILabel's layer as a sublayer of one of those (by using its layer property).
If it's direct text drawing in a layer that you want, the UIKit NSString additions that Deepak points to are the way to go. For an example of this in action, the Core Plot framework has a Mac / iPhone platform-independent CALayer subclass which does text rendering, CPTextLayer.
Add a CATextLayer as a sublayer and set the string property. That would be easiest and you can easily use a layout manager to make it very generic.
The answers below are fine, just make sure you add otherwise you text will be blurry:
textLayer.contentsScale = UIScreen.main.scale
Final code for Swift:
let textLayer = CATextLayer()
textLayer.frame = CGRect(x: 0, y: 0, width: 60, height: 15)
textLayer.fontSize = 12
textLayer.string = "my text"
textLayer.foregroundColor = UIColor.red.cgColor
textLayer.contentsScale = UIScreen.main.scale
class MyCALayer: CALayer {
......
override func draw(in ctx: CGContext) {
UIGraphicsPushContext(ctx)
let text = "這是一段普通的文字"
let textAttrs: [NSAttributedString.Key: Any] = [.font: UIFont.systemFont(ofSize: 20), .foregroundColor: UIColor.blue]
var drawPoint = CGPoint(x: 0, y: 0)
for char in text {
let word = NSAttributedString(string: String(char),
attributes: textAttrs)
let wordBounds = word.boundingRect(with: CGSize(width: .max, height: .max), context: nil)
word.draw(at: drawPoint)
drawPoint = CGPoint(x: drawPoint.x + wordBounds.width, y: drawPoint.y)
let whitespace = NSAttributedString(string: " ", attributes: textAttrs)
let whitespaceBounds = whitespace.boundingRect(with: CGSize(width: .max, height: .max), context: nil)
whitespace.draw(at: drawPoint)
drawPoint = CGPoint(x: drawPoint.x + whitespaceBounds.width, y: drawPoint.y)
}
UIGraphicsPopContext()
}
......
}
The result
Always remember to remove previous sublayers, if you gonna add another one, to prevent duplicating views:
if let sublayers = layer.sublayers {
for sublayer in sublayers {
sublayer.removeFromSuperlayer()
}
}

How do I add a gradient to the text of a UILabel, but not the background?

hey, I want to be able to have a gradient fill on the text in a UILabel I know about CGGradient but i dont know how i would use it on a UILabel's text
i found this on google but i cant manage to get it to work
http://silverity.livejournal.com/26436.html
I was looking for a solution and DotSlashSlash has the answer hidden in one of the comments!
For the sake of completeness, the answer and the simplest solution is:
UIImage *myGradient = [UIImage imageNamed:#"textGradient.png"];
myLabel.textColor = [UIColor colorWithPatternImage:myGradient];
(Skip to bottom for full class source code)
Really useful answers by both Brad Larson and Bach. The second worked for me but it requires an image to be present in advance. I wanted something more dynamic so I combined both solutions into one:
draw the desired gradient on a UIImage
use the UIImage to set the color pattern
The result works and in the screenshot below you can see some Greek characters rendered fine too. (I have also added a stroke and a shadow on top of the gradient)
Here's the custom init method of my label along with the method that renders a gradient on a UIImage (part of the code for that functionality I got from a blog post I can not find now to reference it):
- (id)initWithFrame:(CGRect)frame text:(NSString *)aText {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
self.text = aText;
self.textColor = [UIColor colorWithPatternImage:[self gradientImage]];
}
return self;
}
- (UIImage *)gradientImage
{
CGSize textSize = [self.text sizeWithFont:self.font];
CGFloat width = textSize.width; // max 1024 due to Core Graphics limitations
CGFloat height = textSize.height; // max 1024 due to Core Graphics limitations
// create a new bitmap image context
UIGraphicsBeginImageContext(CGSizeMake(width, height));
// get context
CGContextRef context = UIGraphicsGetCurrentContext();
// push context to make it current (need to do this manually because we are not drawing in a UIView)
UIGraphicsPushContext(context);
//draw gradient
CGGradientRef glossGradient;
CGColorSpaceRef rgbColorspace;
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 0.0, 1.0, 1.0, 1.0, // Start color
1.0, 1.0, 0.0, 1.0 }; // End color
rgbColorspace = CGColorSpaceCreateDeviceRGB();
glossGradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);
CGPoint topCenter = CGPointMake(0, 0);
CGPoint bottomCenter = CGPointMake(0, textSize.height);
CGContextDrawLinearGradient(context, glossGradient, topCenter, bottomCenter, 0);
CGGradientRelease(glossGradient);
CGColorSpaceRelease(rgbColorspace);
// pop context
UIGraphicsPopContext();
// get a UIImage from the image context
UIImage *gradientImage = UIGraphicsGetImageFromCurrentImageContext();
// clean up drawing environment
UIGraphicsEndImageContext();
return gradientImage;
}
I'll try to complete that UILabel subclass and post it.
EDIT:
The class is done and it's on my GitHub repository. Read about it here!
Swift 4.1
class GradientLabel: UILabel {
var gradientColors: [CGColor] = []
override func drawText(in rect: CGRect) {
if let gradientColor = drawGradientColor(in: rect, colors: gradientColors) {
self.textColor = gradientColor
}
super.drawText(in: rect)
}
private func drawGradientColor(in rect: CGRect, colors: [CGColor]) -> UIColor? {
let currentContext = UIGraphicsGetCurrentContext()
currentContext?.saveGState()
defer { currentContext?.restoreGState() }
let size = rect.size
UIGraphicsBeginImageContextWithOptions(size, false, 0)
guard let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(),
colors: colors as CFArray,
locations: nil) else { return nil }
let context = UIGraphicsGetCurrentContext()
context?.drawLinearGradient(gradient,
start: CGPoint.zero,
end: CGPoint(x: size.width, y: 0),
options: [])
let gradientImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard let image = gradientImage else { return nil }
return UIColor(patternImage: image)
}
}
Usage:
label.gradientColors = [UIColor.blue.cgColor, UIColor.red.cgColor]
SWIFT 3+
This solution is based on #Dimitris's answer. It is an extension on the UILabel class that will create a gradient over the label's text per your passed startColor and endColor. The UILabel extension is below:
extension UILabel {
func applyGradientWith(startColor: UIColor, endColor: UIColor) -> Bool {
var startColorRed:CGFloat = 0
var startColorGreen:CGFloat = 0
var startColorBlue:CGFloat = 0
var startAlpha:CGFloat = 0
if !startColor.getRed(&startColorRed, green: &startColorGreen, blue: &startColorBlue, alpha: &startAlpha) {
return false
}
var endColorRed:CGFloat = 0
var endColorGreen:CGFloat = 0
var endColorBlue:CGFloat = 0
var endAlpha:CGFloat = 0
if !endColor.getRed(&endColorRed, green: &endColorGreen, blue: &endColorBlue, alpha: &endAlpha) {
return false
}
let gradientText = self.text ?? ""
let name:String = NSFontAttributeName
let textSize: CGSize = gradientText.size(attributes: [name:self.font])
let width:CGFloat = textSize.width
let height:CGFloat = textSize.height
UIGraphicsBeginImageContext(CGSize(width: width, height: height))
guard let context = UIGraphicsGetCurrentContext() else {
UIGraphicsEndImageContext()
return false
}
UIGraphicsPushContext(context)
let glossGradient:CGGradient?
let rgbColorspace:CGColorSpace?
let num_locations:size_t = 2
let locations:[CGFloat] = [ 0.0, 1.0 ]
let components:[CGFloat] = [startColorRed, startColorGreen, startColorBlue, startAlpha, endColorRed, endColorGreen, endColorBlue, endAlpha]
rgbColorspace = CGColorSpaceCreateDeviceRGB()
glossGradient = CGGradient(colorSpace: rgbColorspace!, colorComponents: components, locations: locations, count: num_locations)
let topCenter = CGPoint.zero
let bottomCenter = CGPoint(x: 0, y: textSize.height)
context.drawLinearGradient(glossGradient!, start: topCenter, end: bottomCenter, options: CGGradientDrawingOptions.drawsBeforeStartLocation)
UIGraphicsPopContext()
guard let gradientImage = UIGraphicsGetImageFromCurrentImageContext() else {
UIGraphicsEndImageContext()
return false
}
UIGraphicsEndImageContext()
self.textColor = UIColor(patternImage: gradientImage)
return true
}
}
And usage:
let text = "YAAASSSSS!"
label.text = text
if label.applyGradientWith(startColor: .red, endColor: .blue) {
print("Gradient applied!")
}
else {
print("Could not apply gradient")
label.textColor = .black
}
SWIFT 2
class func getGradientForText(text: NSString) -> UIImage {
let font:UIFont = UIFont(name: "YourFontName", size: 50.0)!
let name:String = NSFontAttributeName
let textSize: CGSize = text.sizeWithAttributes([name:font])
let width:CGFloat = textSize.width // max 1024 due to Core Graphics limitations
let height:CGFloat = textSize.height // max 1024 due to Core Graphics limitations
//create a new bitmap image context
UIGraphicsBeginImageContext(CGSizeMake(width, height))
// get context
let context = UIGraphicsGetCurrentContext()
// push context to make it current (need to do this manually because we are not drawing in a UIView)
UIGraphicsPushContext(context!)
//draw gradient
let glossGradient:CGGradientRef?
let rgbColorspace:CGColorSpaceRef?
let num_locations:size_t = 2
let locations:[CGFloat] = [ 0.0, 1.0 ]
let components:[CGFloat] = [(202 / 255.0), (197 / 255.0), (52 / 255.0), 1.0, // Start color
(253 / 255.0), (248 / 255.0), (101 / 255.0), 1.0] // End color
rgbColorspace = CGColorSpaceCreateDeviceRGB();
glossGradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);
let topCenter = CGPointMake(0, 0);
let bottomCenter = CGPointMake(0, textSize.height);
CGContextDrawLinearGradient(context, glossGradient, topCenter, bottomCenter, CGGradientDrawingOptions.DrawsBeforeStartLocation);
// pop context
UIGraphicsPopContext();
// get a UIImage from the image context
let gradientImage = UIGraphicsGetImageFromCurrentImageContext();
// clean up drawing environment
UIGraphicsEndImageContext();
return gradientImage;
}
Props to #Dimitris
The example you provide relies on private text drawing functions that you don't have access to on the iPhone. The author provides an example of how to do this using public API in a subsequent post. His later example uses a gradient image for the color of the text. (Unfortunately, it appears his blog has since been removed, but see Bach's answer here for the approach he used.)
If you still want to draw the gradient for your text color in code, it can be done by subclassing UILabel and overriding -drawRect: to have code like the following within it:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0.0f, self.bounds.size.height);
CGContextScaleCTM(context, 1.0f, -1.0f);
CGContextSelectFont(context, "Helvetica", 20.0f, kCGEncodingMacRoman);
CGContextSetTextDrawingMode(context, kCGTextClip);
CGContextSetTextPosition(context, 0.0f, round(20.0f / 4.0f));
CGContextShowText(context, [self.text UTF8String], strlen([self.text UTF8String]));
CGContextClip(context);
CGGradientRef gradient;
CGColorSpaceRef rgbColorspace;
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 1.0, 1.0, 1.0, 1.0, // Start color
1.0, 1.0, 1.0, 0.1 }; // End color
rgbColorspace = CGColorSpaceCreateDeviceRGB();
gradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);
CGRect currentBounds = self.bounds;
CGPoint topCenter = CGPointMake(CGRectGetMidX(currentBounds), 0.0f);
CGPoint midCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMidY(currentBounds));
CGContextDrawLinearGradient(context, gradient, topCenter, midCenter, 0);
CGGradientRelease(gradient);
CGColorSpaceRelease(rgbColorspace);
CGContextRestoreGState(context);
One shortcoming of this approach is that the Core Graphics functions I use don't handle Unicode text properly.
What the code does is it flips the drawing context vertically (the iPhone inverts the normal Quartz coordinate system on for the Y axis), sets the text drawing mode to intersect the drawn text with the clipping path, clips the area to draw to the text, and then draws a gradient. The gradient will only fill the text, not the background.
I tried using NSString's -drawAtPoint: method for this, which does support Unicode, but all the characters ran on top of one another when I switched the text mode to kCGTextClip.
Here's what I'm doing in Swift 3
override func viewDidLoad() {
super.viewDidLoad()
timerLabel.textColor = UIColor(patternImage: gradientImage(size: timerLabel.frame.size, color1: CIColor(color: UIColor.green), color2: CIColor(color: UIColor.red), direction: .Left))
}
func gradientImage(size: CGSize, color1: CIColor, color2: CIColor, direction: GradientDirection = .Up) -> UIImage {
let context = CIContext(options: nil)
let filter = CIFilter(name: "CILinearGradient")
var startVector: CIVector
var endVector: CIVector
filter!.setDefaults()
switch direction {
case .Up:
startVector = CIVector(x: size.width * 0.5, y: 0)
endVector = CIVector(x: size.width * 0.5, y: size.height)
case .Left:
startVector = CIVector(x: size.width, y: size.height * 0.5)
endVector = CIVector(x: 0, y: size.height * 0.5)
case .UpLeft:
startVector = CIVector(x: size.width, y: 0)
endVector = CIVector(x: 0, y: size.height)
case .UpRight:
startVector = CIVector(x: 0, y: 0)
endVector = CIVector(x: size.width, y: size.height)
}
filter!.setValue(startVector, forKey: "inputPoint0")
filter!.setValue(endVector, forKey: "inputPoint1")
filter!.setValue(color1, forKey: "inputColor0")
filter!.setValue(color2, forKey: "inputColor1")
let image = UIImage(cgImage: context.createCGImage(filter!.outputImage!, from: CGRect(x: 0, y: 0, width: size.width, height: size.height))!)
return image
}
There is a really simple solution for this! Here's how you add gradient colors to UILabel text.
We will achieve this in just two steps:
Create Gradient Image
Apply Gradient Image As textColor to UILabel
1.Create Gradient Image
extension UIImage {
static func gradientImageWithBounds(bounds: CGRect, colors: [CGColor]) -> UIImage {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = colors
UIGraphicsBeginImageContext(gradientLayer.bounds.size)
gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
Use this as follows:
let gradientImage = UIImage.gradientImageWithBounds(bounds: myLabel.bounds, colors: [firstColor.cgColor, secondColor.cgColor])
⠀
2.Apply Gradient Image As textColor to UILabel
myLabel.textColor = UIColor.init(patternImage: gradientImage)
⠀
Note:
If you want the gradient to be horizontal, just add these two lines to gradientLayer instance:
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
⠀
Note 2:
The UIImage extension function works with other UIViews too; not just UILabel! So feel free to use this method no matter which UIView you use to apply gradient color.
yourLabel.textColor = UIColor(patternImage: UIImage(named: "ur gradient image name ")!)
SwiftUI
Although we use Text in SwiftUI instead of UILabel, If you consider how to apply a gradient on a Text, you should apply it as a mask. But since gradients are stretchable, you can make a simple extension like this:
extension View {
func selfSizeMask<T: View>(_ mask: T) -> some View {
ZStack {
self.opacity(0)
mask.mask(self)
}.fixedSize()
}
}
Demo
And then you can assign any gradient or other type of view as a self-size mask like:
Text("Gradient is on FIRE !!!")
.selfSizeMask(
LinearGradient(
gradient: Gradient(colors: [.red, .yellow]),
startPoint: .bottom,
endPoint: .top)
)
This method contains some bonus advantages that you can see here in this answer
Simplest Swift 3 Solution
Add an image to your project assets or create one programmatically then do the following:
let image = UIImage(named: "myGradient.png")!
label.textColor = UIColor.init(patternImage: image)
You could sub-class out UILable and do the draw method yourself. That would probably be the more difficult approach, there might be an easier way.

How do I draw a shadow under a UIView?

I'm trying to draw a shadow under the bottom edge of a UIView in Cocoa Touch. I understand that I should use CGContextSetShadow() to draw the shadow, but the Quartz 2D programming guide is a little vague:
Save the graphics state.
Call the function CGContextSetShadow, passing the appropriate values.
Perform all the drawing to which you want to apply shadows.
Restore the graphics state
I've tried the following in a UIView subclass:
- (void)drawRect:(CGRect)rect {
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextSaveGState(currentContext);
CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
CGContextRestoreGState(currentContext);
[super drawRect: rect];
}
..but this doesn't work for me and I'm a bit stuck about (a) where to go next and (b) if there's anything I need to do to my UIView to make this work?
A by far easier approach is to set some layer attributes of the view on initialization:
self.layer.masksToBounds = NO;
self.layer.shadowOffset = CGSizeMake(-15, 20);
self.layer.shadowRadius = 5;
self.layer.shadowOpacity = 0.5;
You need to import QuartzCore.
#import <QuartzCore/QuartzCore.h>
self.layer.masksToBounds = NO;
self.layer.cornerRadius = 8; // if you like rounded corners
self.layer.shadowOffset = CGSizeMake(-15, 20);
self.layer.shadowRadius = 5;
self.layer.shadowOpacity = 0.5;
This will slow down the application.
Adding the following line can improve performance as long as your view is visibly rectangular:
self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
Same solution, but just to remind you: You can define the shadow directly in the storyboard.
Ex:
In your current code, you save the GState of the current context, configure it to draw a shadow .. and the restore it to what it was before you configured it to draw a shadow. Then, finally, you invoke the superclass's implementation of drawRect: .
Any drawing that should be affected by the shadow setting needs to happen after
CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
but before
CGContextRestoreGState(currentContext);
So if you want the superclass's drawRect: to be 'wrapped' in a shadow, then how about if you rearrange your code like this?
- (void)drawRect:(CGRect)rect {
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextSaveGState(currentContext);
CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
[super drawRect: rect];
CGContextRestoreGState(currentContext);
}
You can try this .... you can play with the values.
The shadowRadius dictates the amount of blur. shadowOffset dictates where the shadow goes.
Swift 2.0
let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height))
//Change 2.1 to amount of spread you need and for height replace the code for height
demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.blackColor().CGColor
demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4) //Here you control x and y
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
demoView.layer.masksToBounds = false
demoView.layer.shadowPath = shadowPath.CGPath
Swift 3.0
let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height))
//Change 2.1 to amount of spread you need and for height replace the code for height
demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.black.cgColor
demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4) //Here you control x and y
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
demoView.layer.masksToBounds = false
demoView.layer.shadowPath = shadowPath.cgPath
Example with spread
To create a basic shadow
demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.blackColor().CGColor
demoView.layer.shadowOffset = CGSizeMake(0.5, 4.0); //Here your control your spread
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
Basic Shadow example in Swift 2.0
Simple and clean solution using Interface Builder
Add a file named UIView.swift in your project (or just paste this in any file) :
import UIKit
#IBDesignable extension UIView {
/* The color of the shadow. Defaults to opaque black. Colors created
* from patterns are currently NOT supported. Animatable. */
#IBInspectable var shadowColor: UIColor? {
set {
layer.shadowColor = newValue!.CGColor
}
get {
if let color = layer.shadowColor {
return UIColor(CGColor:color)
}
else {
return nil
}
}
}
/* The opacity of the shadow. Defaults to 0. Specifying a value outside the
* [0,1] range will give undefined results. Animatable. */
#IBInspectable var shadowOpacity: Float {
set {
layer.shadowOpacity = newValue
}
get {
return layer.shadowOpacity
}
}
/* The shadow offset. Defaults to (0, -3). Animatable. */
#IBInspectable var shadowOffset: CGPoint {
set {
layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
}
get {
return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
}
}
/* The blur radius used to create the shadow. Defaults to 3. Animatable. */
#IBInspectable var shadowRadius: CGFloat {
set {
layer.shadowRadius = newValue
}
get {
return layer.shadowRadius
}
}
}
Then this will be available in Interface Builder for every view in the Utilities Panel > Attributes Inspector :
You can easily set the shadow now.
Notes:
- The shadow won't appear in IB, only at runtime.
- As Mazen Kasser said
To those who failed in getting this to work [...] make sure Clip Subviews (clipsToBounds) is not enabled
I use this as part of my utils. With this we can not only set shadow but also can get a rounded corner for any UIView. Also you could set what color shadow you prefer. Normally black is preferred but sometimes, when the background is non-white you might want something else. Here's what I use -
in utils.m
+ (void)roundedLayer:(CALayer *)viewLayer
radius:(float)r
shadow:(BOOL)s
{
[viewLayer setMasksToBounds:YES];
[viewLayer setCornerRadius:r];
[viewLayer setBorderColor:[RGB(180, 180, 180) CGColor]];
[viewLayer setBorderWidth:1.0f];
if(s)
{
[viewLayer setShadowColor:[RGB(0, 0, 0) CGColor]];
[viewLayer setShadowOffset:CGSizeMake(0, 0)];
[viewLayer setShadowOpacity:1];
[viewLayer setShadowRadius:2.0];
}
return;
}
To use this we need to call this - [utils roundedLayer:yourview.layer radius:5.0f shadow:YES];
Swift 3
extension UIView {
func installShadow() {
layer.cornerRadius = 2
layer.masksToBounds = false
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 1)
layer.shadowOpacity = 0.45
layer.shadowPath = UIBezierPath(rect: bounds).cgPath
layer.shadowRadius = 1.0
}
}
If you would like to use StoryBoard and wouldnt like to keep typing in runtime attributes, you can easily create an extension to views and make them usable in storyboard.
Step 1. create extension
extension UIView {
#IBInspectable var shadowRadius: CGFloat {
get {
return layer.shadowRadius
}
set {
layer.shadowRadius = newValue
}
}
#IBInspectable var shadowOpacity: Float {
get {
return layer.shadowOpacity
}
set {
layer.shadowOpacity = newValue
}
}
#IBInspectable var shadowOffset: CGSize {
get {
return layer.shadowOffset
}
set {
layer.shadowOffset = newValue
}
}
#IBInspectable var maskToBound: Bool {
get {
return layer.masksToBounds
}
set {
layer.masksToBounds = newValue
}
}
}
step 2. you can now use these attributes in storyboard
To those who failed in getting this to work (As myself!) after trying all the answers here, just make sure Clip Subviews is not enabled at the Attributes inspector...
Sketch Shadow Using IBDesignable and IBInspectable in Swift 4
HOW TO USE IT
SKETCH AND XCODE SIDE BY SIDE
CODE
#IBDesignable class ShadowView: UIView {
#IBInspectable var shadowColor: UIColor? {
get {
if let color = layer.shadowColor {
return UIColor(cgColor: color)
}
return nil
}
set {
if let color = newValue {
layer.shadowColor = color.cgColor
} else {
layer.shadowColor = nil
}
}
}
#IBInspectable var shadowOpacity: Float {
get {
return layer.shadowOpacity
}
set {
layer.shadowOpacity = newValue
}
}
#IBInspectable var shadowOffset: CGPoint {
get {
return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
}
set {
layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
}
}
#IBInspectable var shadowBlur: CGFloat {
get {
return layer.shadowRadius
}
set {
layer.shadowRadius = newValue / 2.0
}
}
#IBInspectable var shadowSpread: CGFloat = 0 {
didSet {
if shadowSpread == 0 {
layer.shadowPath = nil
} else {
let dx = -shadowSpread
let rect = bounds.insetBy(dx: dx, dy: dx)
layer.shadowPath = UIBezierPath(rect: rect).cgPath
}
}
}
}
OUTPUT
You can use my utility function created for shadow and corner radius as below:
- (void)addShadowWithRadius:(CGFloat)shadowRadius withShadowOpacity:(CGFloat)shadowOpacity withShadowOffset:(CGSize)shadowOffset withShadowColor:(UIColor *)shadowColor withCornerRadius:(CGFloat)cornerRadius withBorderColor:(UIColor *)borderColor withBorderWidth:(CGFloat)borderWidth forView:(UIView *)view{
// drop shadow
[view.layer setShadowRadius:shadowRadius];
[view.layer setShadowOpacity:shadowOpacity];
[view.layer setShadowOffset:shadowOffset];
[view.layer setShadowColor:shadowColor.CGColor];
// border radius
[view.layer setCornerRadius:cornerRadius];
// border
[view.layer setBorderColor:borderColor.CGColor];
[view.layer setBorderWidth:borderWidth];
}
Hope it will help you!!!
Swift 3
self.paddingView.layer.masksToBounds = false
self.paddingView.layer.shadowOffset = CGSize(width: -15, height: 10)
self.paddingView.layer.shadowRadius = 5
self.paddingView.layer.shadowOpacity = 0.5
All Answer all well but I want to add one more point
If you encounter a problem when you have table cells, Deque a new cell there is a mismatch in shadow so in this case, you need to place your shadow code in a layoutSubviews method so that it will behave nicely in all conditions.
-(void)layoutSubviews{
[super layoutSubviews];
[self.contentView setNeedsLayout];
[self.contentView layoutIfNeeded];
[VPShadow applyShadowView:self];
}
or in ViewControllers for specific view place shadow code inside the following method so that it's work well
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
[self.viewShadow layoutIfNeeded];
[VPShadow applyShadowView:self.viewShadow];
}
I have modified my shadow implementation for new devs for more generalized form ex:
/*!
#brief Add shadow to a view.
#param layer CALayer of the view.
*/
+(void)applyShadowOnView:(CALayer *)layer OffsetX:(CGFloat)x OffsetY:(CGFloat)y blur:(CGFloat)radius opacity:(CGFloat)alpha RoundingCorners:(CGFloat)cornerRadius{
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:layer.bounds cornerRadius:cornerRadius];
layer.masksToBounds = NO;
layer.shadowColor = [UIColor blackColor].CGColor;
layer.shadowOffset = CGSizeMake(x,y);// shadow x and y
layer.shadowOpacity = alpha;
layer.shadowRadius = radius;// blur effect
layer.shadowPath = shadowPath.CGPath;
}
For fellow Xamarians, the Xamarin.iOS/C# version of the answer would look like the following:
public override void DrawRect(CGRect area, UIViewPrintFormatter formatter)
{
CGContext currentContext = UIGraphics.GetCurrentContext();
currentContext.SaveState();
currentContext.SetShadow(new CGSize(-15, 20), 5);
base.DrawRect(area, formatter);
currentContext.RestoreState();
}
The main difference is that you acquire an instance of CGContext on which you directly call the appropriate methods.
You can use this Extension to add shadow
extension UIView {
func addShadow(offset: CGSize, color: UIColor, radius: CGFloat, opacity: Float)
{
layer.masksToBounds = false
layer.shadowOffset = offset
layer.shadowColor = color.cgColor
layer.shadowRadius = radius
layer.shadowOpacity = opacity
let backgroundCGColor = backgroundColor?.cgColor
backgroundColor = nil
layer.backgroundColor = backgroundCGColor
}
}
you can call it like
your_Custom_View.addShadow(offset: CGSize(width: 0, height: 1), color: UIColor.black, radius: 2.0, opacity: 1.0)