Reverse layer mask for label - ios

How do you reverse the mask layer for a label? I have a textLabel, which I use as a mask for an imageView that contains an arbitrary image as follows:
let image = UIImage(named: "someImage")
let imageView = UIImageView(image: image!)
let textLabel = UILabel()
textLabel.frame = imageView.bounds
textLabel.text = "Some text"
imageView.layer.mask = textLabel.layer
imageView.layer.masksToBounds = true
The above makes the text in textLabel have a font colour of the imageView as in How to mask the layer of a view by the content of another view?.
How do I reverse this so as to remove the text in textLabel from the imageView?

Make a subclass of UILabel:
class InvertedMaskLabel: UILabel {
override func drawTextInRect(rect: CGRect) {
guard let gc = UIGraphicsGetCurrentContext() else { return }
CGContextSaveGState(gc)
UIColor.whiteColor().setFill()
UIRectFill(rect)
CGContextSetBlendMode(gc, .Clear)
super.drawTextInRect(rect)
CGContextRestoreGState(gc)
}
}
This subclass fills its bounds with an opaque color (white in this example, but only the alpha channel matters). Then it draws the text using the Clear blend mode, which simply sets all channels of the context back to 0, including the alpha channel.
Playground demo:
let root = UIView(frame: CGRectMake(0, 0, 400, 400))
root.backgroundColor = .blueColor()
XCPlaygroundPage.currentPage.liveView = root
let image = UIImage(named: "Kaz-256.jpg")
let imageView = UIImageView(image: image)
root.addSubview(imageView)
let label = InvertedMaskLabel()
label.text = "Label"
label.frame = imageView.bounds
label.font = .systemFontOfSize(40)
imageView.maskView = label
Result:

Since I needed to implement this recently and the syntax has changed a bit, here's a Swift 4.x Version of #RobMayoff's excellent answer. Demo/GitHub repo with Swift Playground located here.
(If you do upvote this, please also upvote his original answer as well :) )
A playground demonstrating the technique. The method drawRect inside InvertedMaskLabel has the secret sauce.
import UIKit
import PlaygroundSupport
// As per https://stackoverflow.com/questions/36758946/reverse-layer-mask-for-label
class InvertedMaskLabel: UILabel {
override func drawText(in rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.saveGState()
UIColor.white.setFill()
UIRectFill(rect) // fill bounds w/opaque color
context.setBlendMode(.clear)
super.drawText(in: rect) // draw text using clear blend mode, ie: set *all* channels to 0
context.restoreGState()
}
}
class TestView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .green
let image = UIImage(named: "tr")
let imageView = UIImageView(image: image)
imageview.frame = bounds
addSubview(imageView)
let label = InvertedMaskLabel()
label.text = "Teddy"
label.frame = imageView.bounds
label.font = UIFont.systemFont(ofSize: 30)
imageView.mask = label
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
let testView = TestView(frame: CGRect(x: 0, y: 0, width: 400, height: 500))
PlaygroundPage.current.liveView = testView

Related

Is is possible to animate text color changing only in a part of text in iOS?

I wonder if it even possible in iOS to animate changing color in only a part of the text, preferably not char by char, but pixel by pixel, like on this picture?
I know how to change text color in static with NSAttributedString and I know how to animate the whole text with CADisplayLink, but this makes me worry.
Maybe I can dive into CoreText, but I'm still not sure it is possible even with it. Any thoughts?
UPD I decided to add a video with my first results to make the question more clear:
my efforts for now (the label is overlapping)
You can quite easily achieve this using CoreAnimation possibilities.
I've added a simple demo, you play with it here (just build the project and tap anywhere to see the animation).
The logic is the following:
Create a custom subclass of UIView.
When some text is set, create two similar CATextLayers, each with the same text and frame.
Set different foregroundColor and mask for those layers. The mask of the left layer will be the left part of the view, and the mask of the right layer will be the right part.
Animate foregroundColor for those layers (simultaneously).
The code of a custom view:
class CustomTextLabel: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .green
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private var textLayer1: CATextLayer?
private var textLayer2: CATextLayer?
func setText(_ text: String, fontSize: CGFloat) {
// create 2 layers with the same text and size, we'll set the colors for them later
textLayer1 = createTextLayer(text, fontSize: fontSize)
textLayer2 = createTextLayer(text, fontSize: fontSize)
// estimate the frame size needed for the text layer with such text and font size
let textSize = textLayer1!.preferredFrameSize()
let w = frame.width, h = frame.height
// calculate the frame such that both layers will be in center of view
let centeredTextFrame = CGRect(x: (w-textSize.width)/2, y: (h-textSize.height)/2, width: textSize.width, height: textSize.height)
textLayer1!.frame = centeredTextFrame
textLayer2!.frame = centeredTextFrame
// set up default color for the text
textLayer1!.foregroundColor = UIColor.yellow.cgColor
textLayer2!.foregroundColor = UIColor.yellow.cgColor
// set background transparent, that's very important
textLayer1!.backgroundColor = UIColor.clear.cgColor
textLayer2!.backgroundColor = UIColor.clear.cgColor
// set up masks, such that each layer's text is visible only in its part
textLayer1!.mask = createMaskLayer(CGRect(x: 0, y: 0, width: textSize.width/2, height: textSize.height))
textLayer2!.mask = createMaskLayer(CGRect(x: textSize.width/2, y: 0, width: textSize.width/2, height: textSize.height))
layer.addSublayer(textLayer1!)
layer.addSublayer(textLayer2!)
}
private var finishColor1: UIColor = .black, finishColor2: UIColor = .black
func animateText(leftPartColor1: UIColor, leftPartColor2: UIColor, rightPartColor1: UIColor, rightPartColor2: UIColor) {
finishColor1 = leftPartColor2
finishColor2 = rightPartColor2
if let layer1 = textLayer1, let layer2 = textLayer2 {
CATransaction.begin()
let animation1 = CABasicAnimation(keyPath: "foregroundColor")
animation1.fromValue = leftPartColor1.cgColor
animation1.toValue = leftPartColor2.cgColor
animation1.duration = 3.0
layer1.add(animation1, forKey: "animation1")
let animation2 = CABasicAnimation(keyPath: "foregroundColor")
animation2.fromValue = rightPartColor1.cgColor
animation2.toValue = rightPartColor2.cgColor
animation2.duration = 3.0
layer2.add(animation2, forKey: "animation2")
CATransaction.setCompletionBlock {
self.textLayer1?.foregroundColor = self.finishColor1.cgColor
self.textLayer2?.foregroundColor = self.finishColor2.cgColor
}
CATransaction.commit()
}
}
private func createTextLayer(_ text: String, fontSize: CGFloat) -> CATextLayer {
let textLayer = CATextLayer()
textLayer.string = text
textLayer.fontSize = fontSize // TODO: also set font name
textLayer.contentsScale = UIScreen.main.scale
return textLayer
}
private func createMaskLayer(_ holeRect: CGRect) -> CAShapeLayer {
let layer = CAShapeLayer()
let path = CGMutablePath()
path.addRect(holeRect)
path.addRect(bounds)
layer.path = path
layer.fillRule = CAShapeLayerFillRule.evenOdd
layer.opacity = 1
return layer
}
}
The calls of a custom view:
class ViewController: UIViewController {
var customLabel: CustomTextLabel!
override func viewDidLoad() {
super.viewDidLoad()
let viewW = view.frame.width, viewH = view.frame.height
let labelW: CGFloat = 200, labelH: CGFloat = 50
customLabel = CustomTextLabel(frame: CGRect(x: (viewW-labelW)/2, y: (viewH-labelH)/2, width: labelW, height: labelH))
customLabel.setText("Optimizing...", fontSize: 20)
view.addSubview(customLabel)
let tapRecogniner = UITapGestureRecognizer(target: self, action: #selector(onTap))
view.addGestureRecognizer(tapRecogniner)
}
#objc func onTap() {
customLabel.animateText(leftPartColor1: UIColor.blue,
leftPartColor2: UIColor.red,
rightPartColor1: UIColor.white,
rightPartColor2: UIColor.black)
}
}
Thanks to Olha's (#OlhaPavliuk) answer, I used two CATextLayer shapes and two CAShapeLayer masks for text layers. In draw method I just change masks frames to calculated size (bounds.width * progress value), and also change the second mask origin to a new start (bounds.width - bounds.width * progress value).
Also, it was very important to set layer.fillRule = CAShapeLayerFillRule.evenOdd while creating a mask, so that both layers became visible.
It turned out that I actually didn't need any animation code involved, because changing frames looks just ok.
In motion: https://giphy.com/gifs/LMbmlMoxY9oaWhXfO1
Full code: https://gist.github.com/joliejuly/a792c2ab8d97d304d731a4a5202f741a

Background colour of UIVIew inside UIPickerView does not set properly

I am trying to implement a UIPickerView with custom design. I've taken a UIVIew inside a UIPickerView and a label inside my UIVIew. Color of my picker background is black and Now I am trying to apply white background color to UIVIew and label inside View. But the colour is not what exact I want. I want pure white colour but it's returning gray. Can anyone help me for this?
Here is my code and output:
if pickerView == pkr1 {
let myView = UIView(frame: CGRect(x: 5, y: 0, width: pickerView.bounds.width - 10, height: Utilities.isDeviceiPad() ? 80.0 : 60.0))
myView.backgroundColor = UIColor.white
myView.isOpaque = false
let myLabel = UILabel(frame: CGRect(x: 5, y: 0, width: myView.bounds.width - 10, height: myView.frame.height))
myLabel.text = arrTemp[row]
// myLabel.center.y = myView.center.y
myLabel.textAlignment = .center
myLabel.numberOfLines = 2
myLabel.backgroundColor = UIColor.white
myLabel.alpha = CGFloat(1)
myLabel.lineBreakMode = NSLineBreakMode.byTruncatingTail
myView.addSubview(myLabel)
return myView
}
Output:
The reason why the selected view is transparent is that UIPickerView subview, that holds all the content, uses CAGradientLayer as a mask to achieve the depth effect. For some reason, Apple decided to make the selected view transparent as well. We can't simply rid of that mask because we will lose that fancy effect. So we need to figure out more about that gradient.
Here is how this gradient looks. I've added the alpha channel values so you could see how this gradient behave.
As you can see, it has 6 colors and everything is fine except the two values in the middle. They are 0.8 instead of 1.0, that what makes you selected view transparent.
This is a very bad approach and I strongly do not recommend do that but if you need to make your selected item opaque no matter what, you can simply create a subclass of you UIPickerView and change the gradient like that.
class MyPickerView: UIPickerView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
super.layoutSubviews()
self.changeGradientLayer()
}
func changeGradientLayer() {
guard let sublayers = self.subviews.first?.layer.sublayers else { return }
for case let sublayer as CAGradientLayer in sublayers {
sublayer.colors = [UIColor.black.withAlphaComponent(0).cgColor,
UIColor.black.withAlphaComponent(0.71).cgColor,
UIColor.black.withAlphaComponent(1.0).cgColor,
UIColor.black.withAlphaComponent(1.0).cgColor,
UIColor.black.withAlphaComponent(0.71).cgColor,
UIColor.black.withAlphaComponent(0).cgColor]
}
}
}
Hope it helps you.
Please add your view in picker view by below method and check it...hope it will work..
func pickerView(pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusingView view: UIView?) -> UIView {
let myView = UIView(frame: CGRect(x: 5, y: 0, width: pickerView.bounds.width - 10, height: Utilities.isDeviceiPad() ? 80.0 : 60.0))
myView.backgroundColor = UIColor.white
myView.isOpaque = false
let myLabel = UILabel(frame: CGRect(x: 5, y: 0, width: myView.bounds.width - 10, height: myView.frame.height))
myLabel.text = arrTemp[row]
// myLabel.center.y = myView.center.y
myLabel.textAlignment = .center
myLabel.numberOfLines = 2
myLabel.backgroundColor = UIColor.white
myLabel.alpha = CGFloat(1)
myLabel.lineBreakMode = NSLineBreakMode.byTruncatingTail
myView.addSubview(myLabel)
return myView
}

Creating a shadow for a UIImageView that has rounded corners?

I am trying to create an ImageView that has rounded corners and a shadow to give it some depth. I was able to create a shadow for the UIImageView, but whenever I added the code to also make it have rounded corners, it only had rounded corners with no shadow. I have an IBOutlet named myImage, and it is inside of the viewDidLoad function. Does anybody have any ideas on how to make it work? What am I doing wrong?
override func viewDidLoad() {
super.ViewDidLoad()
myImage.layer.shadowColor = UIColor.black.cgColor
myImage.layer.shadowOpacity = 1
myImage.layer.shadowOffset = CGSize.zero
myImage.layer.shadowRadius = 10
myImage.layer.shadowPath = UIBezierPath(rect: myImage.bounds).cgPath
myImage.layer.shouldRasterize = false
myImage.layer.cornerRadius = 10
myImage.clipsToBounds = true
}
If you set clipsToBounds to true, this will round the corners but prevent the shadow from appearing. In order to resolve this, you can create two views. The container view should have the shadow, and its subview should have the rounded corners.
The container view has clipsToBounds set to false, and has the shadow properties applied. If you want the shadow to be rounded as well, use the UIBezierPath constructor that takes in a roundedRect and cornerRadius.
let outerView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
outerView.clipsToBounds = false
outerView.layer.shadowColor = UIColor.black.cgColor
outerView.layer.shadowOpacity = 1
outerView.layer.shadowOffset = CGSize.zero
outerView.layer.shadowRadius = 10
outerView.layer.shadowPath = UIBezierPath(roundedRect: outerView.bounds, cornerRadius: 10).cgPath
Next, set the image view (or any other type of UIView) to be the same size of the container view, set clipsToBounds to true, and give it a cornerRadius.
let myImage = UIImageView(frame: outerView.bounds)
myImage.clipsToBounds = true
myImage.layer.cornerRadius = 10
Finally, remember to make the image view a subview of the container view.
outerView.addSubview(myImage)
The result should look something like this:
Swift 5:
You can use the below extension:
extension UIImageView {
func applyshadowWithCorner(containerView : UIView, cornerRadious : CGFloat){
containerView.clipsToBounds = false
containerView.layer.shadowColor = UIColor.black.cgColor
containerView.layer.shadowOpacity = 1
containerView.layer.shadowOffset = CGSize.zero
containerView.layer.shadowRadius = 10
containerView.layer.cornerRadius = cornerRadious
containerView.layer.shadowPath = UIBezierPath(roundedRect: containerView.bounds, cornerRadius: cornerRadious).cgPath
self.clipsToBounds = true
self.layer.cornerRadius = cornerRadious
}
}
How to use:
Drag a UIView on the storyboard
Drag an ImageView inside that UIView
Storyboard should look like this:
Create IBOutlet for both Views, call extension on your ImageView, and pass above created UIView as an argument.
Here is the output :
Finally here is how to
Properly have an image view, with rounded corners AND shadows.
It's this simple:
First some bringup code ..
class ShadowRoundedImageView: UIView {
override init(frame: CGRect) {
super.init(frame: frame); common() }
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder); common() }
private func common() {
backgroundColor = .clear
clipsToBounds = false
self.layer.addSublayer(shadowLayer)
self.layer.addSublayer(imageLayer) // (in that order)
}
#IBInspectable var image: UIImage? = nil {
didSet {
imageLayer.contents = image?.cgImage
shadowLayer.shadowPath = (image == nil) ? nil : shapeAsPath
}
}
and then the layers ...
var imageLayer: CALayer = CALayer()
var shadowLayer: CALayer = CALayer()
var shape: UIBezierPath {
return UIBezierPath(roundedRect: bounds, cornerRadius:50)
}
var shapeAsPath: CGPath {
return shape.cgPath
}
var shapeAsMask: CAShapeLayer {
let s = CAShapeLayer()
s.path = shapeAsPath
return s
}
override func layoutSubviews() {
super.layoutSubviews()
imageLayer.frame = bounds
imageLayer.contentsGravity = .resizeAspectFill // (as preferred)
imageLayer.mask = shapeAsMask
shadowLayer.shadowPath = (image == nil) ? nil : shapeAsPath
shadowLayer.shadowOpacity = 0.80 // etc ...
}
}
Here is the
Explanation
UIImageView is useless, you use a UIView
You need two layers, one for the shadow and one for the image
To round an image layer you use a mask
To round a shadow layer you use a path
For the shadow qualities, obviously add code as you see fit
shadowLayer.shadowOffset = CGSize(width: 0, height: 20)
shadowLayer.shadowColor = UIColor.purple.cgColor
shadowLayer.shadowRadius = 5
shadowLayer.shadowOpacity = 0.80
For the actual shape (the bez path) make it any shape you wish.
(For example this tip https://stackoverflow.com/a/41553784/294884 shows how to make only one or two corners rounded.)
Summary:
• Use two layers on a UIView
Make your bezier and ...
• Use a mask on the image layer
• Use a path on the shadow layer
Here is a another solution (tested code) in swift 2.0
If you set clipsToBounds to true, this will round the corners but prevent the shadow from appearing. So, you can add same size UIView in storyboard behind imageview and we can give shadow to that view
SWIFT 2.0
outerView.layer.cornerRadius = 20.0
outerView.layer.shadowColor = UIColor.blackColor().CGColor
outerView.layer.shadowOffset = CGSizeMake(0, 2)
outerView.layer.shadowOpacity = 1
outerView.backgroundColor = UIColor.whiteColor()
You can use a simple class I have created to add image with rounded corners and shadow directly from Storyboard
You can find the class here

Creating circle and using it as "bar graph"

I want to create a circle with an inner circle that looks like the image below. I'm having trouble with the inner circle and I don't know how to create it so it's easy to adjust percentage (like the image is showing).
So far I have this CircleGraph class which can draw the ouster circle and an inner circle which can only draw 50 %.
import Foundation
import UIKit
class CircleGraph: UIView
{
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect)
{
super.drawRect(rect)
// Outer circle
Colors().getMainColor().setFill()
let outerPath = UIBezierPath(ovalInRect: rect)
outerPath.fill()
// inner circle so far
let percentage = 0.5
UIColor.whiteColor().setFill()
let circlePath = UIBezierPath(arcCenter: CGPoint(x: rect.height/2,y: rect.height/2), radius: CGFloat(rect.height/2), startAngle: CGFloat(-M_PI_2), endAngle:CGFloat(M_PI * 2 * percentage - M_PI_2), clockwise: true)
circlePath.fill()
}
}
Can anyone assist me?
What I want is something simliar to the image below:
I would go for the easy solution and create a UIView with a UIView and UILabel as subviews. If you use something like:
// To make it round
let width = self.frame.width
self.view.layer.cornerRadius = width * 0.5
self.view.layer.masksToBounds = true
for each of the sublayers. If you have set the background colour of the UIView's background layer to something like Red and the UIView layer above to have a whiteish background colour with alpha 0.5 than you already achieve this effect.
If you do not know how to proceed with this tip ill try to provide a code sample.
-- EDIT --
Here is the code sample:
import UIKit
class CircleView: UIView {
var percentage : Int?
var transparency : CGFloat?
var bottomLayerColor : UIColor?
var middleLayerColor : UIColor?
init(frame : CGRect, percentage : Int, transparency : CGFloat, bottomLayerColor : UIColor, middleLayerColor : UIColor) {
super.init(frame : frame)
self.percentage = percentage
self.transparency = transparency
self.bottomLayerColor = bottomLayerColor
self.middleLayerColor = middleLayerColor
viewDidLoad()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
viewDidLoad()
}
func viewDidLoad() {
let width = self.frame.width
let height = self.frame.height
let textFrame = CGRectMake(0, 0, width, height)
guard let percentage = self.percentage
else {
print("Error")
return
}
let newHeight = (CGFloat(percentage)/100.0)*height
let middleFrame = CGRectMake(0,height - newHeight, width, newHeight)
// Set Background Color
if let bottomLayerColor = self.bottomLayerColor {
self.backgroundColor = bottomLayerColor
}
// Make Bottom Layer Round
self.layer.cornerRadius = width * 0.5
self.layer.masksToBounds = true
// Create Middle Layer
let middleLayer = UIView(frame: middleFrame)
if let middleLayerColor = self.middleLayerColor {
middleLayer.backgroundColor = middleLayerColor
}
if let transparency = self.transparency {
middleLayer.alpha = transparency
}
// The Label
let percentageLayer = UILabel(frame: textFrame)
percentageLayer.textAlignment = NSTextAlignment.Center
percentageLayer.textColor = UIColor.whiteColor()
if let percentage = self.percentage {
percentageLayer.text = "\(percentage)%"
}
// Add Subviews
self.addSubview(middleLayer)
self.addSubview(percentageLayer)
}
}
To use in a View Controller:
let redColor = UIColor.redColor()
let blueColor = UIColor.blueColor()
let frame = CGRectMake(50, 50, 100, 100)
// 50% Example
let circleView = CircleView(frame: frame, percentage: 50, transparency: 0.5, bottomLayerColor: redColor, middleLayerColor: blueColor)
self.view.addSubview(circleView)
// 33% Example
let newFrame = CGRectMake(50, 150, 120, 120)
let newCircleView = CircleView(frame: newFrame, percentage: 33, transparency: 0.7, bottomLayerColor: UIColor.redColor(), middleLayerColor: UIColor.whiteColor())
self.view.addSubview(newCircleView)
This will yield something like this:

How to add shadow to ImageView? [duplicate]

I am trying to create an ImageView that has rounded corners and a shadow to give it some depth. I was able to create a shadow for the UIImageView, but whenever I added the code to also make it have rounded corners, it only had rounded corners with no shadow. I have an IBOutlet named myImage, and it is inside of the viewDidLoad function. Does anybody have any ideas on how to make it work? What am I doing wrong?
override func viewDidLoad() {
super.ViewDidLoad()
myImage.layer.shadowColor = UIColor.black.cgColor
myImage.layer.shadowOpacity = 1
myImage.layer.shadowOffset = CGSize.zero
myImage.layer.shadowRadius = 10
myImage.layer.shadowPath = UIBezierPath(rect: myImage.bounds).cgPath
myImage.layer.shouldRasterize = false
myImage.layer.cornerRadius = 10
myImage.clipsToBounds = true
}
If you set clipsToBounds to true, this will round the corners but prevent the shadow from appearing. In order to resolve this, you can create two views. The container view should have the shadow, and its subview should have the rounded corners.
The container view has clipsToBounds set to false, and has the shadow properties applied. If you want the shadow to be rounded as well, use the UIBezierPath constructor that takes in a roundedRect and cornerRadius.
let outerView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
outerView.clipsToBounds = false
outerView.layer.shadowColor = UIColor.black.cgColor
outerView.layer.shadowOpacity = 1
outerView.layer.shadowOffset = CGSize.zero
outerView.layer.shadowRadius = 10
outerView.layer.shadowPath = UIBezierPath(roundedRect: outerView.bounds, cornerRadius: 10).cgPath
Next, set the image view (or any other type of UIView) to be the same size of the container view, set clipsToBounds to true, and give it a cornerRadius.
let myImage = UIImageView(frame: outerView.bounds)
myImage.clipsToBounds = true
myImage.layer.cornerRadius = 10
Finally, remember to make the image view a subview of the container view.
outerView.addSubview(myImage)
The result should look something like this:
Swift 5:
You can use the below extension:
extension UIImageView {
func applyshadowWithCorner(containerView : UIView, cornerRadious : CGFloat){
containerView.clipsToBounds = false
containerView.layer.shadowColor = UIColor.black.cgColor
containerView.layer.shadowOpacity = 1
containerView.layer.shadowOffset = CGSize.zero
containerView.layer.shadowRadius = 10
containerView.layer.cornerRadius = cornerRadious
containerView.layer.shadowPath = UIBezierPath(roundedRect: containerView.bounds, cornerRadius: cornerRadious).cgPath
self.clipsToBounds = true
self.layer.cornerRadius = cornerRadious
}
}
How to use:
Drag a UIView on the storyboard
Drag an ImageView inside that UIView
Storyboard should look like this:
Create IBOutlet for both Views, call extension on your ImageView, and pass above created UIView as an argument.
Here is the output :
Finally here is how to
Properly have an image view, with rounded corners AND shadows.
It's this simple:
First some bringup code ..
class ShadowRoundedImageView: UIView {
override init(frame: CGRect) {
super.init(frame: frame); common() }
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder); common() }
private func common() {
backgroundColor = .clear
clipsToBounds = false
self.layer.addSublayer(shadowLayer)
self.layer.addSublayer(imageLayer) // (in that order)
}
#IBInspectable var image: UIImage? = nil {
didSet {
imageLayer.contents = image?.cgImage
shadowLayer.shadowPath = (image == nil) ? nil : shapeAsPath
}
}
and then the layers ...
var imageLayer: CALayer = CALayer()
var shadowLayer: CALayer = CALayer()
var shape: UIBezierPath {
return UIBezierPath(roundedRect: bounds, cornerRadius:50)
}
var shapeAsPath: CGPath {
return shape.cgPath
}
var shapeAsMask: CAShapeLayer {
let s = CAShapeLayer()
s.path = shapeAsPath
return s
}
override func layoutSubviews() {
super.layoutSubviews()
imageLayer.frame = bounds
imageLayer.contentsGravity = .resizeAspectFill // (as preferred)
imageLayer.mask = shapeAsMask
shadowLayer.shadowPath = (image == nil) ? nil : shapeAsPath
shadowLayer.shadowOpacity = 0.80 // etc ...
}
}
Here is the
Explanation
UIImageView is useless, you use a UIView
You need two layers, one for the shadow and one for the image
To round an image layer you use a mask
To round a shadow layer you use a path
For the shadow qualities, obviously add code as you see fit
shadowLayer.shadowOffset = CGSize(width: 0, height: 20)
shadowLayer.shadowColor = UIColor.purple.cgColor
shadowLayer.shadowRadius = 5
shadowLayer.shadowOpacity = 0.80
For the actual shape (the bez path) make it any shape you wish.
(For example this tip https://stackoverflow.com/a/41553784/294884 shows how to make only one or two corners rounded.)
Summary:
• Use two layers on a UIView
Make your bezier and ...
• Use a mask on the image layer
• Use a path on the shadow layer
Here is a another solution (tested code) in swift 2.0
If you set clipsToBounds to true, this will round the corners but prevent the shadow from appearing. So, you can add same size UIView in storyboard behind imageview and we can give shadow to that view
SWIFT 2.0
outerView.layer.cornerRadius = 20.0
outerView.layer.shadowColor = UIColor.blackColor().CGColor
outerView.layer.shadowOffset = CGSizeMake(0, 2)
outerView.layer.shadowOpacity = 1
outerView.backgroundColor = UIColor.whiteColor()
You can use a simple class I have created to add image with rounded corners and shadow directly from Storyboard
You can find the class here

Resources