Cut a UIImage into a circle - ios

I want to cut a UIImage into a circle so that I can then use it as an annotation. Every answer on this site that I've found describes creating an UIImageView, then modifying that and displaying it, but you cant set the image of an annotation to an UIImageView, only a UIImage.
How should I go about this?

Xcode 11 • Swift 5.1 or later
edit/update: For iOS10+ We can use UIGraphicsImageRenderer. For older Swift syntax check edit history.
extension UIImage {
var isPortrait: Bool { size.height > size.width }
var isLandscape: Bool { size.width > size.height }
var breadth: CGFloat { min(size.width, size.height) }
var breadthSize: CGSize { .init(width: breadth, height: breadth) }
var breadthRect: CGRect { .init(origin: .zero, size: breadthSize) }
var circleMasked: UIImage? {
guard let cgImage = cgImage?
.cropping(to: .init(origin: .init(x: isLandscape ? ((size.width-size.height)/2).rounded(.down) : 0,
y: isPortrait ? ((size.height-size.width)/2).rounded(.down) : 0),
size: breadthSize)) else { return nil }
let format = imageRendererFormat
format.opaque = false
return UIGraphicsImageRenderer(size: breadthSize, format: format).image { _ in
UIBezierPath(ovalIn: breadthRect).addClip()
UIImage(cgImage: cgImage, scale: format.scale, orientation: imageOrientation)
.draw(in: .init(origin: .zero, size: breadthSize))
}
}
}
Playground Testing
let profilePicture = UIImage(data: try! Data(contentsOf: URL(string:"http://i.stack.imgur.com/Xs4RX.jpg")!))!
profilePicture.circleMasked

Make sure to import QuarzCore if needed.
func maskRoundedImage(image: UIImage, radius: CGFloat) -> UIImage {
let imageView: UIImageView = UIImageView(image: image)
let layer = imageView.layer
layer.masksToBounds = true
layer.cornerRadius = radius
UIGraphicsBeginImageContext(imageView.bounds.size)
layer.render(in: UIGraphicsGetCurrentContext()!)
let roundedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return roundedImage!
}

UIImage extension:
extension UIImage {
func circularImage(size size: CGSize?) -> UIImage {
let newSize = size ?? self.size
let minEdge = min(newSize.height, newSize.width)
let size = CGSize(width: minEdge, height: minEdge)
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let context = UIGraphicsGetCurrentContext()
self.drawInRect(CGRect(origin: CGPoint.zero, size: size), blendMode: .Copy, alpha: 1.0)
CGContextSetBlendMode(context, .Copy)
CGContextSetFillColorWithColor(context, UIColor.clearColor().CGColor)
let rectPath = UIBezierPath(rect: CGRect(origin: CGPoint.zero, size: size))
let circlePath = UIBezierPath(ovalInRect: CGRect(origin: CGPoint.zero, size: size))
rectPath.appendPath(circlePath)
rectPath.usesEvenOddFillRule = true
rectPath.fill()
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return result
}
}
Usage:
UIImageView:
#IBDesignable class CircularImageView: UIImageView {
override var image: UIImage? {
didSet {
super.image = image?.circularImage(size: nil)
}
}
}
UIButton:
#IBDesignable class CircularImageButton: UIButton {
override func setImage(image: UIImage?, forState state: UIControlState) {
let circularImage = image?.circularImage(size: nil)
super.setImage(circularImage, forState: state)
}
}

Based on Nikos answer:
public extension UIImage {
func roundedImage() -> UIImage {
let imageView: UIImageView = UIImageView(image: self)
let layer = imageView.layer
layer.masksToBounds = true
layer.cornerRadius = imageView.frame.width / 2
UIGraphicsBeginImageContext(imageView.bounds.size)
layer.render(in: UIGraphicsGetCurrentContext()!)
let roundedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return roundedImage!
}
}
//Usage
let roundedImage = image.roundedImage()

You can use this code to circle Image
extension UIImage {
func circleImage(_ cornerRadius: CGFloat, size: CGSize) -> UIImage? {
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
if let context = UIGraphicsGetCurrentContext() {
var path: UIBezierPath
if size.height == size.width {
if cornerRadius == size.width/2 {
path = UIBezierPath(arcCenter: CGPoint(x: size.width/2, y: size.height/2), radius: cornerRadius, startAngle: 0, endAngle: 2.0*CGFloat(Double.pi), clockwise: true)
}else {
path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
}
}else {
path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
}
context.addPath(path.cgPath)
context.clip()
self.draw(in: rect)
// 从上下文上获取剪裁后的照片
guard let uncompressedImage = UIGraphicsGetImageFromCurrentImageContext() else {
UIGraphicsEndImageContext()
return nil
}
// 关闭上下文
UIGraphicsEndImageContext()
return uncompressedImage
}else {
return nil
}
}}

All these answers were really complex for a straight forward solution. I just replicated my Objective-C code and adjusted for Swift.
self.myImageView?.layer.cornerRadius = (self.myImageView?.frame.size.width)! / 2;
self.myImageView?.clipsToBounds = true

Xcode 8.1, Swift 3.0.1
My code will look like this:
let image = yourImage.resize(CGSize(width: 20, height: 20))?.circled(forRadius: 20)
Add UIImage Extension, then:
func resize(_ size: CGSize) -> UIImage? {
let rect = CGRect(origin: .zero, size: size)
return redraw(in: rect)
}
func redraw(in rect: CGRect) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.main.scale)
guard let context = UIGraphicsGetCurrentContext(), let cgImage = cgImage else { return nil }
let rect = CGRect(origin: .zero, size: size)
let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: rect.size.height)
context.concatenate(flipVertical)
context.draw(cgImage, in: rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
func circled(forRadius radius: CGFloat) -> UIImage? {
let rediusSize = CGSize(width: radius, height: radius)
let rect = CGRect(origin: .zero, size: size)
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)
guard let context = UIGraphicsGetCurrentContext(), let cgImage = cgImage else { return nil }
let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: rect.size.height)
context.concatenate(flipVertical)
let bezierPath = UIBezierPath(roundedRect: rect, byRoundingCorners: [.allCorners], cornerRadii: rediusSize)
context.addPath(bezierPath.cgPath)
context.clip()
context.drawPath(using: .fillStroke)
context.draw(cgImage, in: rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}

I managed to answer my own question by finding a use of BezierPath!
if let xyz = UIImage(contentsOfFile: readPath) {
var Rect: CGRect = CGRectMake(0, 0, xyz.size.width, xyz.size.height)
var x = UIBezierPath(roundedRect: Rect, cornerRadius: 200).addClip()
UIGraphicsBeginImageContextWithOptions(xyz.size, false, xyz.scale)
xyz.drawInRect(Rect)
var ImageNew = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
annotation.image = ImageNew
}

Swift 5.3, Xcode 12.2, Handles all imageOrientations
Based on answer Leo Dabus
Thanks, works perfectly! BUT only for images with imageOrientation .up or .down. For images with .right or .left orientation there are distortions in result. And from iPhone/iPad camera for original photos initially we get .right orientation.
Code below takes into account imageOrientation property:
extension UIImage {
func cropToCircle() -> UIImage? {
let isLandscape = size.width > size.height
let isUpOrDownImageOrientation = [0,1,4,5].contains(imageOrientation.rawValue)
let breadth: CGFloat = min(size.width, size.height)
let breadthSize = CGSize(width: breadth, height: breadth)
let breadthRect = CGRect(origin: .zero, size: breadthSize)
let xOriginPoint = CGFloat(isLandscape ?
(isUpOrDownImageOrientation ? ((size.width-size.height)/2).rounded(.down) : 0) :
(isUpOrDownImageOrientation ? 0 : ((size.height-size.width)/2).rounded(.down)))
let yOriginPoint = CGFloat(isLandscape ?
(isUpOrDownImageOrientation ? 0 : ((size.width-size.height)/2).rounded(.down)) :
(isUpOrDownImageOrientation ? ((size.height-size.width)/2).rounded(.down) : 0))
guard let cgImage = cgImage?.cropping(to: CGRect(origin: CGPoint(x: xOriginPoint, y: yOriginPoint),
size: breadthSize)) else { return nil }
let format = imageRendererFormat
format.opaque = false
return UIGraphicsImageRenderer(size: breadthSize, format: format).image {_ in
UIBezierPath(ovalIn: breadthRect).addClip()
UIImage(cgImage: cgImage, scale: format.scale, orientation: imageOrientation).draw(in: CGRect(origin: .zero, size: breadthSize))
}
}
}

swift 3 conform to MVC pattern
create an external file
#IBDesignable
class RoundImage: UIImageView{
#IBInspectable var cornerRadius: CGFloat = 0 {
didSet{
self.layer.cornerRadius = cornerRadius
}
}
// set border width
#IBInspectable var borderWidth: CGFloat = 0 {
didSet{
self.layer.borderWidth = borderWidth
}
}
// set border color
#IBInspectable var borderColor: UIColor = UIColor.clear {
didSet{
self.layer.borderColor = borderColor.cgColor
}
}
override func awakeFromNib() {
self.clipsToBounds = true
}
}// class
call class in the IB on storyboard
set cornerradius as you please (1/2 of width if desire circle)
Done!

I am using RoundedImageView class, the problem facing is that when browse image from gallery the image not show in round or circle.. I simply change the properties of UIImageView/RoundedImageView -> view -> Content Mode -> Aspect Fillsee screenshot

The accepted answer by #Leo Dabus is good but here's a better approach ✅
import UIKit
public extension UIImage {
/// Returns a circle image with diameter, color and optional padding
class func circle(_ color: UIColor, diameter: CGFloat, padding: CGFloat = .zero) -> UIImage {
let rectangle = CGSize(width: diameter + padding * 2, height: diameter + padding * 2)
return UIGraphicsImageRenderer(size: rectangle).image { context in
let rect = CGRect(x: padding, y: padding, width: diameter + padding, height: diameter + padding)
color.setFill()
UIBezierPath(ovalIn: rect).fill()
}
}
}
How to use
let image = UIImage.circle(.black, diameter: 8.0)
Fewer code lines
Ability to add padding
Non-optional result

Related

Why is my image quality decreasing when setting the radius and size?

I am using the following methods in a UIImage extension to use an image from a URL and displaying it as a UIBarButtonItem. However, when I use the following to adjust the corner radius and size, the quality of the image gets significantly worse. What needs to change to prevent it from decreasing in quality?
extension UIImage {
public func withRoundedCornersAndSetSize(radius: CGFloat? = nil, size: CGSize) -> UIImage? {
let maxRadius = min(size.width, size.height) / 2
let cornerRadius: CGFloat
if let radius = radius, radius > 0 && radius <= maxRadius {
cornerRadius = radius
} else {
cornerRadius = maxRadius
}
UIGraphicsBeginImageContextWithOptions(size, false, scale)
let rect = CGRect(origin: .zero, size: size)
UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip()
draw(in: rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
I figured out that I could resize the UIImage without losing quality using the resize() method in the following code. Then, I just used this in the withRoundedCornersAndSetSize() method that I had initially asked about.
extension UIImage {
func withRoundedCornersAndSetSize(radius: CGFloat? = nil, targetSize: CGSize) -> UIImage? {
let resizedImage = self.resize(targetSize: targetSize)
let maxRadius = min(resizedImage.size.width, resizedImage.size.height) / 2
let cornerRadius: CGFloat
if let radius = radius, radius > 0 && radius <= maxRadius {
cornerRadius = radius
} else {
cornerRadius = maxRadius
}
UIGraphicsBeginImageContextWithOptions(resizedImage.size, false, resizedImage.scale)
let rect = CGRect(origin: .zero, size: resizedImage.size)
UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip()
draw(in: rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
func resize(targetSize: CGSize) -> UIImage {
return UIGraphicsImageRenderer(size:targetSize).image { _ in
self.draw(in: CGRect(origin: .zero, size: targetSize))
}
}
}

Merge two UIImages swift 4

I have a captured image from a device's camera and I want to add a UITextView on it and then save it into the gallery. I'm using the following code to get an image from the text view
extension UITextView {
var imageOfView: UIImage {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0)
self.drawHierarchy(in: self.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
Then, by using the following code, I try to add the above image to the taken image:
extension UIImage {
func combine(with image: UIImage, at point: CGPoint, isLandscapeMode: Bool = false) -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.size, false, 0.0)
self.draw(at: .zero)
image.draw(at: point)
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return result!
}
}
finally, I call the method here to send the edited image to the previous page:
onImageEdited?(capturedImage.combine(with: textArea.imageOfView, at: textArea.frame.origin, isLandscapeMode: true))
Looking at the below screenshots, it's seen that the size changes and the position seem to be randomly chosen. Any idea why and how can I fix that?
During the editing:
After it's saved:
P.S: The captured image and the editing page are in Landscape mode
Try this code in Swift 4.2
func stitchImages(images: [UIImage], isVertical: Bool) -> UIImage {
var stitchedImages : UIImage!
if images.count > 0 {
var maxWidth = CGFloat(0), maxHeight = CGFloat(0)
for image in images {
if image.size.width > maxWidth {
maxWidth = image.size.width
}
if image.size.height > maxHeight {
maxHeight = image.size.height
}
}
var totalSize : CGSize
let maxSize = CGSize(width: maxWidth, height: maxHeight)
if isVertical {
totalSize = CGSize(width: maxSize.width, height: maxSize.height * (CGFloat)(images.count))
} else {
totalSize = CGSize(width: maxSize.width * (CGFloat)(images.count), height: maxSize.height)
}
UIGraphicsBeginImageContext(totalSize)
for image in images {
let offset = (CGFloat)(images.index(of: image)!)
let rect = AVMakeRect(aspectRatio: image.size, insideRect: isVertical ?
CGRect(x: 0, y: maxSize.height * offset, width: maxSize.width, height: maxSize.height) :
CGRect(x: maxSize.width * offset, y: 0, width: maxSize.width, height: maxSize.height))
image.draw(in: rect)
}
stitchedImages = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
return stitchedImages
}
}
and you can use this:
#IBOutlet weak var imageView: UIImageView!
let imageArray = [image1, image3, image4, image4]
override func viewDidLoad() {
super.viewDidLoad()
let image = stitchImages(images: imageArray, isVertical: false)
imageView.image = image
}
func combine(text: String, withImage image: UIImage, atPoint point: CGPoint) -> UIImage? {
// Variables
let textColor = UIColor.white
let font = UIFont.systemFont(ofSize: 12)
let attributes: [NSAttributedStringKey: Any] = [
.foregroundColor: textColor,
.font: font
]
// Set scale and begin context
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(image.size, false, scale)
// Draw
image.draw(in: CGRect(origin: .zero, size: image.size))
let rect = CGRect(origin: point, size: image.size)
text.draw(in: rect, withAttributes: attributes)
// Get the new image
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
You could also create an extension for UIImageView and render a new image from all of its subviews (e.x. the UITextView)
extension UIImageView {
func renderSubviewsToImage() -> UIImage? {
guard let image = image else { return nil }
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(image.size, false, scale)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
self.layer.render(in: context)
let renderedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return renderedImage
}
}
Try this one, I have commented the code which I have edited from the above code, please check it out and let me know it worked for you.
func stitchImages(images: [UIImage], isVertical: Bool) -> UIImage {
var stitchedImages : UIImage!
if images.count > 0 {
var maxWidth = CGFloat(0), maxHeight = CGFloat(0)
for image in images {
if image.size.width > maxWidth {
maxWidth = image.size.width
}
if image.size.height > maxHeight {
maxHeight = image.size.height
}
}
var totalSize : CGSize
let maxSize = CGSize(width: maxWidth, height: maxHeight)
if isVertical {
totalSize = CGSize(width: maxSize.width, height: maxSize.height * (CGFloat)(images.count))
} else {
totalSize = CGSize(width: maxSize.width, height: maxSize.height)
}
UIGraphicsBeginImageContext(totalSize)
let offset = (CGFloat)(0)
// Need to change or adjust the frame get get reach your reqirement
let rect1 = AVMakeRect(aspectRatio: images[1].size, insideRect: isVertical ? CGRect(x: 0, y: maxSize.height * offset, width: maxSize.width, height: maxSize.height) : CGRect(x: maxSize.width * offset, y: 0, width: maxSize.width, height: maxSize.height))
images[1].draw(in: rect1)
let rect2 = AVMakeRect(aspectRatio: images[0].size, insideRect: isVertical ? CGRect(x: 0, y: maxSize.height * offset, width: maxSize.width - 60, height: maxSize.height - 500) : CGRect(x: maxSize.width * offset, y: 250, width: maxSize.width - 60, height: maxSize.height - 500))
images[0].draw(in: rect2)
// for image in images {
// let offset = (CGFloat)(0)
// let rect = AVMakeRect(aspectRatio: image.size, insideRect: isVertical ?
// CGRect(x: 0, y: maxSize.height * offset, width: maxSize.width, height: maxSize.height) :
// CGRect(x: maxSize.width * offset, y: 0, width: maxSize.width, height: maxSize.height))
// image.draw(in: rect)
// }
stitchedImages = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
return stitchedImages
}

How do I set the line width of a UIBezierPath when drawing to a CGContext?

I'm trying to create a UIImage using a provided UIBezierPath. Unfortunately, no matter what I set setLineWidth to, the result is always a stroke of 1 point:
extension UIBezierPath {
func image(fillColor: UIColor, strokeColor: UIColor) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, 1.0)
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
context.setLineWidth(10)
context.setFillColor(fillColor.cgColor)
context.setStrokeColor(strokeColor.cgColor)
self.fill()
self.stroke()
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
Trying this out in a test project with a circle, for example:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView()
imageView.frame = CGRect(x: 100, y: 100, width: 100, height: 100)
view.addSubview(imageView)
let bezierPath = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 100, height: 100))
let image = bezierPath.image(fillColor: UIColor.blue, strokeColor: UIColor.red)
imageView.image = image
}
}
No matter what I set setLineWidth to, it always appears to be 1 point.
You are calling stroke on the UIBezierPath, so you need to set the lineWidth property of that using self.lineWidth = 10.
extension UIBezierPath {
func image(fillColor: UIColor, strokeColor: UIColor) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, 1.0)
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
context.setFillColor(fillColor.cgColor)
self.lineWidth = 10
context.setStrokeColor(strokeColor.cgColor)
self.fill()
self.stroke()
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}

Rounding UIImage and adding a border

so I want to show some pictures as annotations on the map. In order to do that I need to add the image property of the MKAnnotationView. I'm using the regular images but I want them to be rounded and with a border. So I found a way to round UIImage and I found the way to add a border to UIImage, but border doesn't seem to add (I'm not actually having the image on the screen, maybe that is the problem?).
I used this answer https://stackoverflow.com/a/29047372/4665643 with a slight modification for border. Namely:
imageView.layer.borderWidth = 1.5
imageView.layer.borderColor = UIColor.whiteColor().CGColor
imageView.clipsToBounds = true
But my image on the map doesn't have any border. Any suggestions ?
imageView.layer.masksToBounds = true
imageView.layer.borderWidth = 1.5
imageView.layer.borderColor = UIColor.white.cgColor
imageView.layer.cornerRadius = imageView.bounds.width / 2
Try this.
If you would like to add a border to your image you need to make sure you add some extra room to it otherwise your border will be placed in top of your image. The solution is to add twice the width of your stroke to your image's width and height.
extension UIImage {
var isPortrait: Bool { size.height > size.width }
var isLandscape: Bool { size.width > size.height }
var breadth: CGFloat { min(size.width, size.height) }
var breadthSize: CGSize { .init(width: breadth, height: breadth) }
var breadthRect: CGRect { .init(origin: .zero, size: breadthSize) }
func rounded(with color: UIColor, width: CGFloat) -> UIImage? {
let bleed = breadthRect.insetBy(dx: -width, dy: -width)
UIGraphicsBeginImageContextWithOptions(bleed.size, false, scale)
defer { UIGraphicsEndImageContext() }
guard let cgImage = cgImage?.cropping(to: CGRect(origin: CGPoint(
x: isLandscape ? ((size.width-size.height)/2).rounded(.down) : 0,
y: isPortrait ? ((size.height-size.width)/2).rounded(.down) : 0),
size: breadthSize))
else { return nil }
UIBezierPath(ovalIn: .init(origin: .zero, size: bleed.size)).addClip()
var strokeRect = breadthRect.insetBy(dx: -width/2, dy: -width/2)
strokeRect.origin = .init(x: width/2, y: width/2)
UIImage(cgImage: cgImage, scale: 1, orientation: imageOrientation)
.draw(in: strokeRect.insetBy(dx: width/2, dy: width/2))
color.set()
let line: UIBezierPath = .init(ovalIn: strokeRect)
line.lineWidth = width
line.stroke()
return UIGraphicsGetImageFromCurrentImageContext()
}
}
For iOS10+ We can use UIGraphicsImageRenderer.
extension UIImage {
var isPortrait: Bool { size.height > size.width }
var isLandscape: Bool { size.width > size.height }
var breadth: CGFloat { min(size.width, size.height) }
var breadthSize: CGSize { .init(width: breadth, height: breadth) }
var breadthRect: CGRect { .init(origin: .zero, size: breadthSize) }
func rounded(with color: UIColor, width: CGFloat) -> UIImage? {
guard let cgImage = cgImage?.cropping(to: .init(origin: .init(x: isLandscape ? ((size.width-size.height)/2).rounded(.down) : .zero, y: isPortrait ? ((size.height-size.width)/2).rounded(.down) : .zero), size: breadthSize)) else { return nil }
let bleed = breadthRect.insetBy(dx: -width, dy: -width)
let format = imageRendererFormat
format.opaque = false
return UIGraphicsImageRenderer(size: bleed.size, format: format).image { context in
UIBezierPath(ovalIn: .init(origin: .zero, size: bleed.size)).addClip()
var strokeRect = breadthRect.insetBy(dx: -width/2, dy: -width/2)
strokeRect.origin = .init(x: width/2, y: width/2)
UIImage(cgImage: cgImage, scale: 1, orientation: imageOrientation)
.draw(in: strokeRect.insetBy(dx: width/2, dy: width/2))
context.cgContext.setStrokeColor(color.cgColor)
let line: UIBezierPath = .init(ovalIn: strokeRect)
line.lineWidth = width
line.stroke()
}
}
}
Playground Testing:
let profilePicture = UIImage(data: try! Data(contentsOf: URL(string:"http://i.stack.imgur.com/Xs4RX.jpg")!))!
let pp = profilePicture.rounded(with: .red, width: 10)
Leo Dabus's solution in Swift 3:
extension UIImage {
func roundedImageWithBorder(width: CGFloat, color: UIColor) -> UIImage? {
let square = CGSize(width: min(size.width, size.height) + width * 2, height: min(size.width, size.height) + width * 2)
let imageView = UIImageView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: square))
imageView.contentMode = .center
imageView.image = self
imageView.layer.cornerRadius = square.width/2
imageView.layer.masksToBounds = true
imageView.layer.borderWidth = width
imageView.layer.borderColor = color.cgColor
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, scale)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
imageView.layer.render(in: context)
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return result
}
}
Use this extension to UIImageView :
func cropAsCircleWithBorder(borderColor : UIColor, strokeWidth: CGFloat)
{
var radius = min(self.bounds.width, self.bounds.height)
var drawingRect : CGRect = self.bounds
drawingRect.size.width = radius
drawingRect.origin.x = (self.bounds.size.width - radius) / 2
drawingRect.size.height = radius
drawingRect.origin.y = (self.bounds.size.height - radius) / 2
radius /= 2
var path = UIBezierPath(roundedRect: CGRectInset(drawingRect, strokeWidth/2, strokeWidth/2), cornerRadius: radius)
let border = CAShapeLayer()
border.fillColor = UIColor.clearColor().CGColor
border.path = path.CGPath
border.strokeColor = borderColor.CGColor
border.lineWidth = strokeWidth
self.layer.addSublayer(border)
path = UIBezierPath(roundedRect: drawingRect, cornerRadius: radius)
let mask = CAShapeLayer()
mask.path = path.CGPath
self.layer.mask = mask
}
Usage :
self.circleView.cropAsCircleWithBorder(UIColor.redColor(), strokeWidth: 20)
Result :
For making an image rounded with border, you can do that from User Defined Runtime Attributes also, no need to write code for that.
Please check the below image for setting that
Also in your code, change
imageView.layer.clipsToBounds = true
to this,
imageView.layer.masksToBounds = true
I set masksToBounds, and It work.
layer.masksToBounds = true
simple one line code its works for me
self.profileImage.layer.cornerRadius = self.profileImage.frame.size.width / 2
you can create an IBDesignable class and set it to your Image. Then change properties in Storyboard with realtime changes
#IBDesignable class CircularImageView: UIImageView {
#IBInspectable var borderWidth : CGFloat {
get { layer.borderWidth }
set {
layer.masksToBounds = true
layer.borderWidth = newValue
layer.cornerRadius = frame.size.width / 2
}
}
#IBInspectable var borderColor: UIColor? {
set {
guard let uiColor = newValue else { return }
layer.borderColor = uiColor.cgColor
}
get {
guard let color = layer.borderColor else { return nil }
return UIColor(cgColor: color)
}
}
}
Just fixed it. Apparently everything was working perfectly but I wasn't seeing the border. The original image is about 300x300 pixels and with 1.5 pixel border I was cropping it to fit 40x40 frame so the border was barely noticeable. Changing border width to a bigger number made it visible.

Create UIImage with solid color in Swift

I want to programmatically create an UIImage filled by a solid color. Anyone have an idea of how to do this in Swift?
Another nice solution,
Swift 3.0
public extension UIImage {
convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) {
let rect = CGRect(origin: .zero, size: size)
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
color.setFill()
UIRectFill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard let cgImage = image?.cgImage else { return nil }
self.init(cgImage: cgImage)
}
}
Swift 2.2 compatible, is to create another constructor in UIImage, in this way:
public extension UIImage {
public convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) {
let rect = CGRect(origin: .zero, size: size)
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
color.setFill()
UIRectFill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard let cgImage = image?.CGImage else { return nil }
self.init(CGImage: cgImage)
}
}
In this way you can create the custom colored-image in this way:
let redImage = UIImage(color: .redColor())
Or, optionally, create the image with a custom size:
let redImage200x200 = UIImage(color: .redColor(), size: CGSize(width: 200, height: 200))
Swift 4 version:
extension UIColor {
func image(_ size: CGSize = CGSize(width: 1, height: 1)) -> UIImage {
return UIGraphicsImageRenderer(size: size).image { rendererContext in
self.setFill()
rendererContext.fill(CGRect(origin: .zero, size: size))
}
}
}
Usage:
let image0 = UIColor.orange.image(CGSize(width: 128, height: 128))
let image1 = UIColor.yellow.image()
Here's another option. I believe you wanted an exact UIImage object.
func getImageWithColor(color: UIColor, size: CGSize) -> UIImage {
let rect = CGRectMake(0, 0, size.width, size.height)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
color.setFill()
UIRectFill(rect)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
Stick this in your Swift code and call it
Swift 3.1:
func getImageWithColor(color: UIColor, size: CGSize) -> UIImage {
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
color.setFill()
UIRectFill(rect)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
A cleaner approach would be to encapsulate the logic inside an UIImage extension:
import UIKit
extension UIImage {
class func imageWithColor(color: UIColor) -> UIImage {
let rect: CGRect = CGRect(x: 0, y: 0, width: 1, height: 1)
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), false, 0)
color.setFill()
UIRectFill(rect)
let image: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
Now the consumer can call, UIImage.imageWithColor(UIColor.blackColor()) to create an image with black background.
A minor tweak to #neoneye's excellent answer, allowing the calling code not to need to create the CGSize, and altered the name to not collide with numerous others:
Swift 4
extension UIColor {
func imageWithColor(width: Int, height: Int) -> UIImage {
let size = CGSize(width: width, height: height)
return UIGraphicsImageRenderer(size: size).image { rendererContext in
self.setFill()
rendererContext.fill(CGRect(origin: .zero, size: size))
}
}
}
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
imageView.backgroundColor = UIColor.redColor()
}
}
Similar method if you want draw the image yourself vs. connecting one via IBOutlet.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var frame = CGRectMake(100,100,100,100)
var imageView2 = UIImageView(frame: frame)
imageView2.backgroundColor = UIColor.redColor()
self.view.addSubview(imageView2)
}
}
Third method borrowing from anthonyliao. A little more complicated:
class ViewController: UIViewController {
func getImageWithColor(color: UIColor, size: CGSize) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, 0)
color.setFill()
UIRectFill(CGRectMake(0, 0, 100, 100))
var image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
override func viewDidLoad() {
super.viewDidLoad()
var imageView = UIImageView(frame: CGRectMake(100,100,100,100))
let screenImage = getImageWithColor(UIColor.redColor(), size: CGSize(width: 100, height: 100))
imageView.image = screenImage
self.view.addSubview(imageView)
}
}
You can use the new iOS 10 UIGraphicsImageRenderer API.
Here is an extension on UIColor in Swift 3.1
extension UIColor {
func getImage(size: CGSize) -> UIImage {
let renderer = UIGraphicsImageRenderer(size: size)
return renderer.image(actions: { rendererContext in
self.setFill()
rendererContext.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height))
})
}}
A nice way is to have a computed property like this:
extension UIColor {
var imageRepresentation : UIImage {
let rect = CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
context?.setFillColor(self.cgColor)
context?.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
Usage:
let redImage = UIColor.red.imageRepresentation
Swift 3 version of #anthonyliao Accepted answer:
class func getImageWithColor(color: UIColor, size: CGSize) -> UIImage
{
let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: size.width, height: size.height))
UIGraphicsBeginImageContextWithOptions(size, false, 0)
color.setFill()
UIRectFill(rect)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
Rect with rounded corners
extension UIImage {
convenience init?(color: UIColor) {
let rect = CGRect(x: 0, y: 0, width: 10, height: 2)
UIGraphicsBeginImageContextWithOptions(CGSize(width: 10, height: 2), false, 0)
let bezierPath = UIBezierPath(roundedRect: rect, cornerRadius: 8)
color.setFill()
bezierPath.fill()
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
guard let cgImage = image.cgImage else {
return nil
}
self.init(cgImage: cgImage)
}
}
Swift 5, Xcode 12.4 - UIImageView's solution
If you have to deal with UIImageViews like in my case, you could opt for a more compact solution like: exampleImageView.fill() or exampleImageView.fill(with: .black).
Extension's code:
extension UIImageView {
func fill(with color: UIColor = .lightGray) {
let size = self.bounds.size
let image = UIGraphicsImageRenderer(size: size).image { rendererContext in
color.setFill()
rendererContext.fill(CGRect(origin: .zero, size: size))
}
self.image = image
}
}
we can use UIGraphicsImageRenderer to create the UIImage or use traditional CGContext approach, below is convenience init function which creates UIImage with given color
extension UIImage {
/// create UIImage with color and given size
convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1), alpha: CGFloat = 1, scale: CGFloat = 0) {
if #available(tvOS 10.0, *) {
let renderer = UIGraphicsImageRenderer(size: size)
let image = renderer.image { (ctx) in
let size = renderer.format.bounds.size
ctx.fill(CGRect(origin: .zero, size: size))
ctx.cgContext.setFillColor(color.cgColor)
}
guard let cgImage = image.cgImage else { return nil }
self.init(cgImage: cgImage)
} else {
let rect = CGRect(origin: .zero, size: size)
UIGraphicsBeginImageContextWithOptions(rect.size, false, scale)
let context = UIGraphicsGetCurrentContext()!
let fillColor = color.withAlphaComponent(alpha)
context.setFillColor(fillColor.cgColor)
context.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard let cgImage = image?.cgImage else { return nil }
self.init(cgImage: cgImage)
}
}
}
Swift 5
/ Get not optional UIImage
public static func withColor(_ color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) -> UIImage {
let format = UIGraphicsImageRendererFormat()
format.scale = 1
let image = UIGraphicsImageRenderer(size: size, format: format).image { rendererContext in
color.setFill()
rendererContext.fill(CGRect(origin: .zero, size: size))
}
return image
}
Don't forget to use format.scale = 1 if it needed
If your application support iOS 10 or above, we should use UIGraphicsImageRenderer since it is faster.
Here are some static methods / init methods (optional UIImage) in UIImage's extension:
import UIKit
extension UIImage {
static func from(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) -> UIImage {
let format = UIGraphicsImageRendererFormat()
format.scale = 1
return UIGraphicsImageRenderer(size: size, format: format).image { context in
color.setFill()
context.fill(CGRect(origin: .zero, size: size))
}
}
static func from(color: UIColor, size: CGFloat = 1) -> UIImage {
return from(color: color, size: CGSize(width: size, height: size))
}
static func from(color: UIColor) -> UIImage {
return from(color: color, size: 1)
}
convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) {
let format = UIGraphicsImageRendererFormat()
format.scale = 1
let image = UIGraphicsImageRenderer(size: size, format: format).image { context in
color.setFill()
context.fill(CGRect(origin: .zero, size: size))
}
guard let cgImage = image.cgImage else { return nil }
self.init(cgImage: cgImage)
}
convenience init?(color: UIColor, size: CGFloat = 1) {
self.init(color: color, size: CGSize(width: size, height: size))
}
convenience init?(color: UIColor) {
self.init(color: color, size: 1)
}
}
struct Example {
func createAnImageWithColorRed() {
UIImage(color: .red, size: CGSize(width: 10, height: 10))
UIImage(color: .red, size: 10)
UIImage(color: .red)
UIImage.from(color: .red, size: CGSize(width: 10, height: 10))
UIImage.from(color: .red, size: 10)
UIImage.from(color: .red)
}
}
Preview:

Resources