18

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 ?

Community
  • 1
  • 1
Olexiy Burov
  • 1,007
  • 3
  • 11
  • 22

9 Answers9

26
imageView.layer.masksToBounds = true
imageView.layer.borderWidth = 1.5
imageView.layer.borderColor = UIColor.white.cgColor
imageView.layer.cornerRadius = imageView.bounds.width / 2

Try this.

Keval Vadoliya
  • 1,003
  • 1
  • 9
  • 17
  • 3
    in Swift 4.0 is imageView.layer.borderColor = UIColor.white.cgColor – Maziyar Dec 09 '17 at 11:45
  • 1
    @Maziyar, in Swift 4.0, it is : imageView.layer.borderColor = .white.cgColor – Antoine Rucquoy Jan 19 '18 at 12:10
  • 1
    @AntoineRucquoy even shorter and better! :) – Maziyar Oct 29 '18 at 16:32
  • @Maziyar is right. You can't omit the `UIColor.` if you access properties on it – heyfrank May 24 '19 at 09:50
  • 1
    @fl034 The point about being able to omit or not the type is related to the type of `borderColor`. If you declare a static white color extending `CGColor` you could omit the `CGColor` from `CGColor.white`. The compiler can infer the type so you don't need to type it. So just add this `extension CGColor { static let white: CGColor = .init(srgbRed: 1, green: 1, blue: 1, alpha: 1) }` to your project. Now you can assign `.white` to `borderColor` – Leo Dabus Jan 29 '20 at 03:05
9

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:"https://i.stack.imgur.com/Xs4RX.jpg")!))!
let pp = profilePicture.rounded(with: .red, width: 10)

enter image description here

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • This solved one problem for me however it created another. It was a huge performance hit and made the scrolling in my tableview very jerky. Although it gave the look I wanted it is unusable (for my purpose). Shame. I just need to round the corners on my UIImageView, but it seems that the "rounding" goes outside the frame and only displays partly. Can't seem to find any solution to this. – Aecasorg Sep 19 '18 at 06:36
  • Yes, thanks! It is a much better solution. Still a bit laggy on the scroll. I'm not too hot on using the NSCache though. If you read my question here it'll clarify what my problem was. https://stackoverflow.com/questions/52420096/rounded-corners-clipped-in-uiimageview?noredirect=1#comment91790570_52420096 – Aecasorg Sep 20 '18 at 13:48
8

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
    }
}
coldembrace
  • 549
  • 8
  • 19
4

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 :

Result

Aruna Mudnoor
  • 4,795
  • 14
  • 16
4

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

enter image description here

Also in your code, change

imageView.layer.clipsToBounds = true

to this,

imageView.layer.masksToBounds = true
Rajat
  • 10,977
  • 3
  • 38
  • 55
2

Supportive image added

I set masksToBounds, and It work.

layer.masksToBounds = true
Jaffer Wilson
  • 7,029
  • 10
  • 62
  • 139
黄旺鑫
  • 21
  • 2
1

simple one line code its works for me

self.profileImage.layer.cornerRadius = self.profileImage.frame.size.width / 2
Anand
  • 71
  • 1
  • 11
1

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)
    }
}

}
Pramodya Abeysinghe
  • 1,098
  • 17
  • 13
0

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.

Olexiy Burov
  • 1,007
  • 3
  • 11
  • 22
  • If test with iphone 6 plus in simulator i notice a lot of small lines (like tableview's separator) be disappear also – Tj3n Jan 25 '16 at 04:37