How do i mask UIImage view inside UIView? - ios

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.

Related

CAShapeLayer Image Not Appearing

I have an image.cgImage that I set as the contents to a CAShapeLayer() but it's not appearing. I force unwrap the image so I know that it definitely exists. The round shape appears but the image inside of it doesn't.
let shapeLayer = CAShapeLayer()
var didSubviewsLayout = false
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if !didSubviewsLayout {
didSubviewsLayout = true
setupShapeLayer()
}
}
func setupShapeLayer() {
shapeLayer.path = UIBezierPath(roundedRect: CGRect(x: 200, y: 200, width: 50, height: 50), cornerRadius: 25).cgPath
shapeLayer.strokeColor = nil
shapeLayer.fillColor = UIColor.orange.cgColor
shapeLayer.contents = UIImage(named: "myIcon")!.cgImage // this doesn't crash so the image definitely exists
view.layer.addSublayer(shapeLayer)
}
The problem is that your shape layer has no size. You have not given it a frame, so it is just an invisible dot at the top right corner. Unfortunately, you can still see the shape, so you're not aware of the fact that you're doing this all wrong. But the content doesn't draw, because the layer is just a dot, so it reveals your code's feet of clay.
So, in this case, change
shapeLayer.path = UIBezierPath(roundedRect: CGRect(x: 200, y: 200, width: 50, height: 50), cornerRadius: 25).cgPath
To
shapeLayer.frame = CGRect(x: 200, y: 200, width: 50, height: 50)
shapeLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 50, height: 50), cornerRadius: 25).cgPath
Incidentally, that is not how to draw a circle correctly, so if that's your goal, stop it. Use ovalIn; that's what it's for. Complete example:
let shapeLayer = CAShapeLayer()
shapeLayer.frame = CGRect(x: 200, y: 200, width: 50, height: 50)
shapeLayer.path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 50, height: 50)).cgPath
shapeLayer.strokeColor = UIColor.orange.cgColor
shapeLayer.lineWidth = 4
shapeLayer.fillColor = nil
shapeLayer.contents = UIImage(named: "smiley")?.cgImage
self.view.layer.addSublayer(shapeLayer)
Result:
Note too that you are probably making this mistake with all your shape layers, not just this one, so you will want to go back through all the code in this app (or better, all the code you've ever written!) and write all your shape layers correctly. Layers need frames!
You could create the rounded path in a separate layer and add it as a mask to the layer that contains the image. Firstly, I would add a frame to the parent layer so that the image can be centred within the layer using contentsGravity.
shapeLayer.frame = CGRect(x: 200, y: 200, width: 50, height: 50)
shapeLayer.contents = UIImage(named: "myIcon")!.cgImage
// Center the image within the layer
shapeLayer.contentsGravity = .center
// We can set the orange color here
shapeLayer.backgroundColor = UIColor.orange.cgColor
Then, create the layer that would have the rounded path and set it as a mask to the image layer:
let maskLayer = CAShapeLayer()
maskLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 50, height: 50), cornerRadius: 25).cgPath
shapeLayer.mask = maskLayer
Your modified code:
func setupShapeLayer() {
shapeLayer.frame = CGRect(x: 200, y: 200, width: 50, height: 50)
shapeLayer.contents = UIImage(named: "myIcon")!.cgImage
// Center the image within the layer
shapeLayer.contentsGravity = .center
// We can set the orange color here
shapeLayer.backgroundColor = UIColor.orange.cgColor
// Create mask layer
let maskLayer = CAShapeLayer()
maskLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 50, height: 50), cornerRadius: 25).cgPath
shapeLayer.mask = maskLayer
view.layer.addSublayer(shapeLayer)
}

UIBezierPath corner didn't show correct when radius equalTo height

Here is test code, you can copy and test:
override func viewDidLoad() {
super.viewDidLoad()
let blueView = UIView(frame: .init(x: 20, y: 100, width: 100, height: 40))
blueView.backgroundColor = .blue
view.addSubview(blueView)
let blueViewPath = UIBezierPath.init(roundedRect: blueView.bounds, byRoundingCorners: [.bottomLeft, .bottomRight], cornerRadii: .init(width: 20, height: 20))
let blueViewLayer = CAShapeLayer()
blueViewLayer.frame = blueView.bounds
blueViewLayer.path = blueViewPath.cgPath
blueView.layer.mask = blueViewLayer
let orangeView = UIView(frame: .init(x: 20, y: 150, width: 100, height: 40))
orangeView.backgroundColor = .orange
view.addSubview(orangeView)
let orangeViewPath = UIBezierPath.init(roundedRect: orangeView.bounds, byRoundingCorners: [.bottomLeft, .bottomRight], cornerRadii: .init(width: 40, height: 40))
let orangeViewLayer = CAShapeLayer()
orangeViewLayer.frame = orangeView.bounds
orangeViewLayer.path = orangeViewPath.cgPath
orangeView.layer.mask = orangeViewLayer
}
blueView and orangeView's height are both 40, blueView's radius is 20, orangeView's radius is 40.
But as you see the orangeView's radius looks like 20.
Why? How to solve it?
According to Apple Documentation please read out to know why its doing this
bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:
The radius of each corner oval. Values larger than half the
rectangle’s width or height are clamped appropriately to half the
width or height.
Solution:
let orangeView = UIView(frame: .init(x: 150, y: 100, width: 260, height: 40))
orangeView.backgroundColor = .orange
view.addSubview(orangeView)
let orangeViewPath = UIBezierPath.init(roundedRect: .init(x: 0, y: -40, width: 260, height: 80), byRoundingCorners: [.bottomLeft, .bottomRight], cornerRadii: .init(width: 40, height: 40))
let orangeViewLayer = CAShapeLayer()
orangeViewLayer.frame = orangeView.bounds
orangeViewLayer.path = orangeViewPath.cgPath
orangeView.layer.mask = orangeViewLayer

How to erase the thin side line right bottom?

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:

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