How to erase the thin side line right bottom? - ios

I want compound view's shape from Oval shape subtract Rect shape,
so first I create a mask image:
let paths = CGMutablePath()
//Oval shape path at left
let leftPath = CGPath(ellipseIn: CGRect(x: 0, y: 0, width: 200, height: 150), transform: nil)
//Rect shape path at right
let rightPath = CGPath(roundedRect: CGRect(x: 100, y: 100, width: 120, height: 100), cornerWidth: 8, cornerHeight: 8, transform: nil)
paths.addPath(leftPath)
paths.addPath(rightPath)
UIGraphicsBeginImageContext(CGSize(width: 220, height: 200))
let ctx = UIGraphicsGetCurrentContext()
ctx?.addPath(paths)
ctx?.clip(using: .evenOdd)
ctx?.addPath(leftPath.cgPath)
ctx?.clip(using: .evenOdd)
ctx?.setFillColor(UIColor.red.cgColor)
ctx?.fill(CGRect(x: 0, y: 0, width: 220, height: 200))
//the mask image
let maskImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
next I use this image for mask:
let maskView = UIImageView(image: maskImage)
maskView.contentMode = .center
imageView.mask = maskView
run App, I get this:
looks good?not really...
if you look carefully,you will notice a thin line at view right bottom
It's not OK for me!
How do I erase the sideline??? Thanks :)

Draw the ellipse, then clear the rounded rect.
import UIKit
let ellipse = CGPath(ellipseIn: CGRect(x: 0, y: 0, width: 200, height: 150), transform: nil)
let roundedRect = CGPath(roundedRect: CGRect(x: 100, y: 100, width: 120, height: 100), cornerWidth: 8, cornerHeight: 8, transform: nil)
let maskImage = UIGraphicsImageRenderer(size: CGSize(width: 220, height: 200)).image { rendererContext in
let ctx = rendererContext.cgContext
ctx.setFillColor(UIColor.red.cgColor)
ctx.addPath(ellipse)
ctx.fillPath()
ctx.setBlendMode(.clear)
ctx.addPath(roundedRect)
ctx.fillPath()
}
Result:

Related

Why draw(in: CGRect) draws border around ellipse when it should not?

This is my simple function I use for drawing an image in context:
let renderer=UIGraphicsImageRenderer(size: CGSize(width: 330, height: 330))
let img=renderer.image{ ctx in
let circle=CGRect(x:0,y:0,width: 330, height: 330)
ctx.cgContext.setFillColor(UIColor.white.cgColor)
ctx.cgContext.addEllipse(in: circle)
ctx.cgContext.drawPath(using: .fill)
let image = UIImage(named: "1")!
image.draw(in: CGRect(x: 80, y: 80, width: 100, height: 100))
}
And the result is following:
As you can see there is output of UIGraphicsImageRenderer with border around ellipse. Why? Border is not defined anywhere, but it is printed.
The image named 1 is the following one:
NOTE:
This issue appears only when compiling ios app. Using playground everything is fine and ok.
Does your UIImageView have a cornerRadius applied to its layer? That can cause a thin gray border like you see here. If you create a circular image, like you have with UIGraphicsImageRenderer, you should not need to do any masking or cornerRadius on the UIImageView.
If you only want to fill the path, and not stroke it, one could use fillPath rather than drawPath.
FWIW, you could also just bypass the CoreGraphics context and just fill the oval directly:
let image = renderer.image { _ in
UIColor.white.setFill()
UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 330, height: 330))
.fill()
UIImage(named: "1")!
.draw(in: CGRect(x: 80, y: 80, width: 100, height: 100))
}
OK, the updated code still does not match.
First, in your posted image, the background is not white.
Second, even accounting for that, there is no "edge" on the rendered UIImage.
So, I'm going to make a guess here....
Assuming you execute the img = renderer.image { .... code block, and then you set imageView.image = img, my suspicion is that you have something like this:
imageView.backgroundColor = .lightGray
imageView.layer.cornerRadius = imageView.frame.height / 2.0
So, the lightGray "circle" is the lightGray background anti-aliased to the .cornerRadius.
I would be that if set:
imageView.backgroundColor = .clear
and do not set the layer's cornerRadius (no need to), your ellipse border will be gone.
If it's still there, then you need to provide some code that actually reproduces the issue.
Edit
I'm still not seeing the "border" when setting the rendered image to an image view, but...
Doing some debug inspecting and using the "1" image you added, there IS a difference.
Try this, and see if it gets rid of the border:
let fmt = UIGraphicsImageRendererFormat()
fmt.preferredRange = .standard
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 330, height: 330), format: fmt)
You can then use either:
let img = renderer.image { ctx in
let circle = CGRect(x:0, y:0, width: 330, height: 330)
ctx.cgContext.setFillColor(UIColor.white.cgColor)
ctx.cgContext.addEllipse(in: circle)
ctx.cgContext.drawPath(using: .fill)
if let image = UIImage(named: "1") {
image.draw(in: CGRect(x: 80, y: 80, width: 100, height: 100))
}
}
or Rob's suggested:
let img = renderer.image { _ in
UIColor.white.setFill()
UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 330, height: 330))
.fill()
if let image = UIImage(named: "1") {
image.draw(in: CGRect(x: 80, y: 80, width: 100, height: 100))
}
}

How to set the contentGravity variable of CALayer to 'resizeAspectFill'?

I am trying to set the contentGravity variable of a CALayer but I am not quite sure how to do this. I have tried the following without success:
var layer = CAShapeLayer.init()
layer.contentGravity = .resizeAspectFill // This is not recognised
My UIImageView presents the image taken from the camera of the phone. I then use the Vision Framework to detect rectangles, which return the four corner coordinates of the detected rectangle. I then draw the rectangle in the layer of the UIImageView. The content mode of the UIIMageView is 'Aspect Fill'. My rectangle is currently misaligned and i think it has to do with the contentGravity of the layer. Please see image below:
Rectangle drawn into CAShapelayer:
let rec = UIBezierPath.init()
rec.move(to: CGPoint.init(x: topLeft.x, y: topLeft.y))
rec.addLine(to: CGPoint.init(x: topRight.x, y: topRight.y))
rec.addLine(to: CGPoint.init(x: bottomRight.x, y: bottomRight.y))
rec.addLine(to: CGPoint.init(x: bottomLeft.x, y: bottomLeft.y))
rec.close()
self.layer.opacity = 0.4
self.layer.path = rec.cgPath
self.layer.lineWidth = 5
self.layer.strokeColor = UIColor.yellow.cgColor
self.layer.frame = self.imgPreViewOutlet.bounds
self.imgPreViewOutlet.layer.addSublayer(self.layer)
image show misaligned rectangle drawn in the layer of UIImageView
There is no purpose in setting the content gravity of a CAShapeLayer, as it will in no way affect how the shape is drawn. Content gravity is meaningful only for a layer with content, e.g. a CGImage that is drawn into its contents property.
Here's an example where contentsGravity matters; same image, same layer size, different content gravity:
override func viewDidLoad() {
super.viewDidLoad()
do {
let layer = CALayer()
layer.frame = CGRect(x: 100, y: 100, width: 250, height: 200)
layer.contents = UIImage(named:"smiley")!.cgImage
layer.borderWidth = 1
self.view.layer.addSublayer(layer)
layer.contentsGravity = .center
}
do {
let layer = CALayer()
layer.frame = CGRect(x: 100, y: 300, width: 250, height: 200)
layer.contents = UIImage(named:"smiley")!.cgImage
layer.borderWidth = 1
self.view.layer.addSublayer(layer)
layer.contentsGravity = .resize
}
}
But if we try exactly the same thing with a shape layer, the content gravity makes no difference:
override func viewDidLoad() {
super.viewDidLoad()
do {
let layer = CAShapeLayer()
layer.frame = CGRect(x: 100, y: 100, width: 250, height: 200)
layer.path = CGPath(rect: CGRect(x: 0, y: 0, width: 20, height: 20), transform: nil)
layer.borderWidth = 1
self.view.layer.addSublayer(layer)
layer.contentsGravity = .center
}
do {
let layer = CAShapeLayer()
layer.frame = CGRect(x: 100, y: 300, width: 250, height: 200)
layer.path = CGPath(rect: CGRect(x: 0, y: 0, width: 20, height: 20), transform: nil)
layer.borderWidth = 1
self.view.layer.addSublayer(layer)
layer.contentsGravity = .resize
}
}

Swift 3 draw uiimage programmatically

I don't have experience in Core Graphics but I need to draw a dynamic uiimage that look like these:
left
whole
(Actually I want the grey area to be clear. So the red color will look like floating)
This is the code I tried:
public extension UIImage {
public convenience init?(color: UIColor, size: CGSize = CGSize(width: 27, height: 5), isWhole: Bool = true) {
let totalHeight: CGFloat = 5.0
let topRectHeight: CGFloat = 1.0
//if (isWhole) {
let topRect = CGRect(origin: .zero, size: CGSize(width: size.width, height: topRectHeight))
UIGraphicsBeginImageContextWithOptions(topRect.size, false, 0.0)
color.setFill()
UIRectFill(topRect)
let bottomRect = CGRect(origin: CGPoint(x: 0, y: topRectHeight), size: CGSize(width: size.width, height: totalHeight - topRectHeight))
UIGraphicsBeginImageContextWithOptions(bottomRect.size, false, 0.0)
UIColor.blue.setFill()
UIRectFill(bottomRect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard let cgImage = image?.cgImage else { return nil }
self.init(cgImage: cgImage)
}
Here is example you can have first image if you set isWhole property to false and have second image if you set it to true. You can paste this code in viewDidLoad to test and play with it.
var isWhole = false
UIGraphicsBeginImageContextWithOptions(CGSize.init(width: 27, height: 5), false,0.0)
var context = UIGraphicsGetCurrentContext()
context?.setFillColor(UIColor.red.cgColor)
if(context != nil){
if(isWhole){
context?.fill(CGRect(x: 0, y: 0, width: 27, height: 2.5))
}
else{
context?.fill(CGRect(x: 0, y: 0, width: 13.5, height: 2.5))
}
context?.setFillColor(UIColor.gray.cgColor)
context?.fill(CGRect(x: 0, y: 2.5, width: 27, height: 2.5))
}
let newImage = UIGraphicsGetImageFromCurrentImageContext()
var imageView = UIImageView(frame: CGRect(x: 100, y: 200, width: 27, height: 5))
imageView.image = newImage
self.view.addSubview(imageView)
UIGraphicsEndImageContext()
If you need your red rectangle to be with rounder corners just change fill(rect:CGRect) with path like this:
if(isWhole){
context?.addPath(CGPath(roundedRect: CGRect(x: 0, y: 0, width: 27, height: 2.5), cornerWidth: 1, cornerHeight: 1, transform: nil))
context?.fillPath()
}
I'd recommend creating two paths in the first context that you create, i.e.
let topPath = UIBezierPath(rect: topRect)
color.setFill()
topPath.fill()
let bottomPath = UIBezierPath(rect: bottomRect)
UIColor.blue.setFill()
bottomPath.fill()
Then you can get the image from the current context.
I know you said in your comments that you can't use UIViews, but what you want "looks" easily done through views. Why not do that, then simply turn it into a UIImage?
override func viewDidLoad() {
super.viewDidLoad()
let imageContainer = UIView(frame: CGRect(x: 30, y: 40, width: 110, height: 60))
imageContainer.backgroundColor = view.backgroundColor
view.addSubview(imageContainer)
let redView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 30))
redView.backgroundColor = UIColor.red
let grayView = UIView(frame: CGRect(x: 10, y: 30, width: 100, height: 30))
grayView.backgroundColor = UIColor.gray
imageContainer.addSubview(redView)
imageContainer.addSubview(grayView)
let imageView = UIImageView(frame: CGRect(x: 100, y: 140, width: 200, height: 200))
imageView.contentMode = .scaleAspectFit
imageView.image = createImage(imageContainer)
view.addSubview(imageView)
}
func createImage(_ view: UIView) -> UIImage {
UIGraphicsBeginImageContextWithOptions(
CGSize(width: view.frame.width, height: view.frame.height), true, 1)
view.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}

How do i mask UIImage view inside UIView?

I was trying to mask the subview to super view but image view is not masked to super view
How it's displaying
How it suppose to be
Code
class TicketView: UIView {
var img = UIImage(named: "social-event.jpg")
var imageView = UIImageView()
override func draw(_ rect: CGRect) {
let path = UIBezierPath(rect: rect)
let width:CGFloat = 11
let height:CGFloat = 11
let roundLeft = UIBezierPath(roundedRect: CGRect(x: -(width/2), y: -(height/2), width: width, height: height), cornerRadius: (height/2))
let roundLeftBotton = UIBezierPath(roundedRect: CGRect(x: -(width/2), y: (rect.height)-(height/2), width: width, height: height), cornerRadius: (height/2))
let roundRight = UIBezierPath(roundedRect: CGRect(x: (rect.width)-(width/2), y: -(height/2), width: width, height: height), cornerRadius: (height/2))
let roundRightBottom = UIBezierPath(roundedRect: CGRect(x: (rect.width)-(width/2), y: (rect.height)-(height/2), width: width, height: height), cornerRadius: (height/2))
let roundRightMiddle = UIBezierPath(roundedRect: CGRect(x: (rect.width*0.7)-(width/2), y: -(height/2), width: width, height: height), cornerRadius: (height/2))
let roundRightMiddleBottom = UIBezierPath(roundedRect: CGRect(x: (rect.width*0.7)-(width/2), y:
(rect.height)-(height/2), width: width, height: height), cornerRadius: (height/2))
path.append(roundLeft.reversing())
path.append(roundLeftBotton.reversing())
path.append(roundRight.reversing())
path.append(roundRightBottom.reversing())
path.append(roundRightMiddle.reversing())
path.append(roundRightMiddleBottom.reversing())
UIColor.green.setFill()
path.fill()
imageView.frame = CGRect(x: 0, y: 0, width: (rect.width * 0.7), height: rect.height)
imageView.backgroundColor = UIColor.black
self.addSubview(imageView)
}
}
What i tried till now
1) mask layer
2) cut the image
Make new image that has layer like your corner and put on your View as a mask.

how to place a scrolling image behind a stationary image in Swift

I am trying to port an artificial horizon app I wrote for a PC in c# to swift. It has a bezel image which does not move and behind it is a horizon image which can move up and down behind the bezel. The "window" part of the bezel is yellow so in c# I just made the yellow opaque.
In swift I stated with the horizon inside of a UIScrollView but I'm not sure how to get that to work with a second image that should not scroll.
Not all that up to speed on this swift stuff, can someone point me in the right direction?
let view: UIView = UIView.init(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
let scrollView = UIScrollView.init(frame: view.bounds)
view.addSubview(scrollView)
let backImage: UIImage = fromColor(UIColor.redColor(), size: CGSize(width: 1000, height: 1000))
let backImageView: UIImageView = UIImageView.init(image: backImage)
scrollView.addSubview(backImageView)
scrollView.contentSize = CGSize.init(width: backImage.size.width, height: backImage.size.height)
let frontImage: UIImage = fromColor(UIColor.blueColor(), size: CGSize(width: 100, height: 100))
let layer: CALayer = CALayer.init()
layer.frame = CGRect.init(x: view.center.x - 50, y: view.center.y - 50, width: 100, height: 100)
layer.contents = frontImage.CGImage
view.layer.addSublayer(layer)
func fromColor(color: UIColor, size: CGSize) -> UIImage {
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
CGContextSetFillColorWithColor(context, color.CGColor)
CGContextFillRect(context, rect)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img
}
fromColor is a helper method.
Result of the code

Resources