Screenshot in part of UIView masked to UIBezierPath / CGPath - swift

I'm building a drawing application. I'm drawing using CGMutablePath.
I want the user to be able to select a part of the drawn paths and then move that part, like this:
I thought, a possible solution would be to mask a view to the drawn area and then take a screenshot in that view.
In here, you can see the area drawn in which I want to take a screenshot:
To take the screenshot, I get the last path drawn, being the area the screenshot is to be taken in:
let shapeLayer = CAShapeLayer()
shapeLayer.path = last.closedPath // returns CGPath.closeSubpath()
shapeLayer.lineWidth = 10
I then create an overlayView that's the view I'm taking the screenshot in.
let overlayView = UIView(frame: view.bounds)
overlayView.backgroundColor = .black
overlayView.alpha = 0.4
view.addSubview(overlayView)
view.bringSubview(toFront: overlayView)
I'm then masking the view to the path:
overlayView.mask(withPath: UIBezierPath(cgPath: last.closedPath!))
The .mask(withPath:) method comes from here:
extension UIView {
func mask(withPath path: UIBezierPath) {
let path = path
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
self.layer.mask = maskLayer
}
}
Then, I take the screenshot in overlayView:
let image: UIImage = {
UIGraphicsBeginImageContextWithOptions(overlayView.bounds.size, false, 0)
defer { UIGraphicsEndImageContext() }
drawView.drawHierarchy(in: overlayView.bounds, afterScreenUpdates: true)
return UIGraphicsGetImageFromCurrentImageContext()!
}()
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
What happens, is that the overlayView has the screen's full size and also draws the screenshot in the full size.
When debugging the view hierarchy, I can also see that the overlayView is still full-size, not masked to the path.
So, instead of getting only the part drawn around as screenshot, I get an image of the whole view / screen.
Question
How do I successfully mask the view to the drawn area so I can take a screenshot in that part of the screen only?

I think the overlayView.frame equals self.view.frame, which is why the image is being taken full screen.
A solution to your issue may be solved as follows (although I may have understood incorrectly):
let shapeLayer = CAShapeLayer()
shapeLayer.path = last.closedPath // returns CGPath.closeSubpath()
shapeLayer.lineWidth = 10
let rect = shapeLayer.path.boundingBoxOfPath
let overlayView = UIView(frame: rect)

Related

How to center a CGImage on a CALayer?

I was trying to set a UIImage's CGImage as a layer's content and then add the layer to a view's layer.
It's should be five stars at the center of the yellow view. That's what I want it to be.
But it seems the center of the stars is aligned with the origin of the view.
What should I do to rectify it?
func putOnStars() {
let rect = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
rect.backgroundColor = .yellow
view.addSubview(rect)
let baseLayer = CALayer()
baseLayer.contents = UIImage(named: "stars")?.cgImage
baseLayer.contentsGravity = kCAGravityCenter
rect.layer.addSublayer(baseLayer)
}
Here is the stars image for you in case of you want to test.
baseLayer doesn't have a defined frame so baseLayer.contentsGravity = kCAGravityCenter will work fine but it'll still be on the potision (0, 0).
There are two possible solutions:
1 : Make a frame of baseLayer that is identical to rect. Implement this code:
baseLayer.frame = rect.frame
2 : Set the position of baseLayer to the center of rect. Implement this code:
baseLayer.position = rect.center
To place the stars image in the centre of the CALayer, give the frame of the layer, i.e.
let baseLayer = CALayer()
baseLayer.frame = rect.bounds //This Line
baseLayer.contents = UIImage(named: "stars")?.cgImage
baseLayer.contentsGravity = kCAGravityCenter
rect.layer.addSublayer(baseLayer)
For any kind of CALayer, you need to define its frame explicitly.

Zoom/Scroll through parts of image in a scrollview

I have an image which I divide into pages, such that each page shows a zoomed rectangle of the image. I think I should be able to do that with a UIImageView in a ScrollView, such that next page zooms the view to a given point. However I can't seem to get it to work.
This is the code for loading the image and setting the zoom on the first part (i.e. the first page) into scrollview:
scrollView.isPagingEnabled = true
scrollView.contentSize = CGSize(width: 1280, height: 1920)
scrollView.showsHorizontalScrollIndicator = false
scrollView.delegate = self as UIScrollViewDelegate
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame.origin.x = 0
scrollView.addSubview(imageView)
scrollView.zoom(toPoint: CGPoint(x:800,y:800), scale: 1, animated: false)
The image is obviously much larger than the size of the scrollview, which is 375/309.
I'm probably missing a lot here, or maybe there's a completely different way of achieving this.
the zoom function is borrowed from https://gist.github.com/TimOliver/71be0a8048af4bd86ede.
Thanks,
Z.
It seems like you'll want to set the content offset rather than zooming to a point. Try:
scrollView.contentOffset = CGPoint(x:800,y:800)

How to give an imageView in swift3.0.1 shadow at the same time with rounded corners

I want to give an imageView a shadow at the same time with rounded corners,but I failed.
Here is my solution
Basic idea :
Use an Extra view (say AView) as super view of image view (to those views on which you are willing to have shado) and assign that view class to DGShadoView
Pin Image view to AView (that super view)from left, right, top and bottom with constant 5
Set back ground color of the AView to clear color from storybosrd's Property inspector this is important
Inside idea: Here we are using a Bezier path on the Aview nearly on border and setting all rounded corner properties and shadow properties to that path and we are placing our target image view lie with in that path bound
#IBDesignable
class DGShadoView:UIView {
override func draw(_ rect: CGRect) {
self.rect = rect
decorate(rect: self.rect)
}
func decorate(rect:CGRect) {
//self.backgroundColor = UIColor.clear
//IMPORTANT: dont forgot to set bg color of your view to clear color from story board's property inspector
let ref = UIGraphicsGetCurrentContext()
let contentRect = rect.insetBy(dx: 5, dy: 5);
/*create the rounded oath and fill it*/
let roundedPath = UIBezierPath(roundedRect: contentRect, cornerRadius: 5)
ref!.setFillColor("your color for background".cgColor)
ref!.setShadow(offset: CGSize(width:0,height:0), blur: 5, color: "your color for shado".cgColor)
roundedPath.fill()
/*draw a subtle white line at the top of view*/
roundedPath.addClip()
ref!.setStrokeColor(UIColor.red.cgColor)
ref!.setBlendMode(CGBlendMode.overlay)
ref!.move(to: CGPoint(x:contentRect.minX,y:contentRect.minY+0.5))
ref!.addLine(to: CGPoint(x:contentRect.maxX,y:contentRect.minY+0.5))
}
}
Update
Extension Approach
There is another Approach. Just Make a class with empty and paste Following UIImageView Extension code, Assign this subclass to that ImageView on which you shadow.
import UIKit
class DGShadowView: UIImageView {
#IBInspectable var intensity:Float = 0.2{
didSet{
setShadow()
}
}
override func layoutSubviews()
{
super.layoutSubviews()
setShadow()
}
func setShadow(){
let shadowPath = UIBezierPath(rect: bounds)
layer.masksToBounds = false
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0.0, height: 0.3)
layer.shadowOpacity = intensity
layer.shadowPath = shadowPath.cgPath
}
}
The solution is to create two separate views. One for the shadow and one for the image itself. On the imageView you clipToBounds the layer so that the corner radius is properly added.
Put the imageView on top of the shadowView and you've got your solution!

Borders not covering background

I've got a UILabel is using a border the same color as a background which it is half obscuring, to create a nice visual effect. However the problem is that there is still a tiny, yet noticeable, sliver of the label's background color on the OUTSIDE of the border.
The border is not covering the whole label!
Changing the border width doesn't change anything either, sadly.
Here's a picture of what's going on, enlarged so you can see it:
And my code follows:
iconLbl.frame = CGRectMake(theWidth/2-20, bottomView.frame.minY-20, 40, 40)
iconLbl.font = UIFont.fontAwesomeOfSize(23)
iconLbl.text = String.fontAwesomeIconWithName(.Info)
iconLbl.layer.masksToBounds = true
iconLbl.layer.cornerRadius = iconLbl.frame.size.width/2
iconLbl.layer.borderWidth = 5
iconLbl.layer.borderColor = topBackgroundColor.CGColor
iconLbl.backgroundColor = UIColor.cyanColor()
iconLbl.textColor = UIColor.whiteColor()
Is there something I'm missing?
Or am I going to have to figure out another to achieve this effect?
Thanks!
EDIT:
List of things I've tried so far!
Changing layer.borderWidth
Fussing around with clipsToBounds/MasksToBounds
Playing around the the layer.frame
Playing around with an integral frame
EDIT 2:
No fix was found! I used a workaround by extending this method on to my UIViewController
func makeFakeBorder(inputView:UIView,width:CGFloat,color:UIColor) -> UIView {
let fakeBorder = UIView()
fakeBorder.frame = CGRectMake(inputView.frame.origin.x-width, inputView.frame.origin.y-width, inputView.frame.size.width+width*2, inputView.frame.size.height+width*2)
fakeBorder.backgroundColor = color
fakeBorder.clipsToBounds = true
fakeBorder.layer.cornerRadius = fakeBorder.frame.size.width/2
fakeBorder.addSubview(inputView)
inputView.center = CGPointMake(fakeBorder.frame.size.width/2, fakeBorder.frame.size.height/2)
return fakeBorder
}
I believe this is the way a border is drawn to a layer in iOS. In the document it says:
When this value is greater than 0.0, the layer draws a border using the current borderColor value. The border is drawn inset from the receiver’s bounds by the value specified in this property. It is composited above the receiver’s contents and sublayers and includes the effects of the cornerRadius property.
One way to fix this is to apply a mask to a view's layer, but I found out that even if so we still can see a teeny tiny line around the view when doing snapshot tests. So to fix it more, I put this code to layoutSubviews
class MyView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
let maskInset: CGFloat = 1
// Extends the layer's frame.
layer.frame = layer.frame.inset(dx: -maskInset, dy: -maskInset)
// Increase the border width
layer.borderWidth = layer.borderWidth + maskInset
layer.cornerRadius = bounds.height / 2
layer.maskToBounds = true
// Create a circle shape layer with true bounds.
let mask = CAShapeLayer()
mask.path = UIBezierPath(ovalIn: bounds.inset(dx: maskInset, dy: maskInset)).cgPath
layer.mask = mask
}
}
CALayer's mask

Crop image in swift

I am trying to crop image in swift. I'm trying to implement something like, user will capture a photo. Once photo is captured user will be allowed to set the crop area. I'm able to get the image from that crop area, but I want that the crop image should be resized to particular width and height. That is, if particular height or width is smaller then it should be resized.
This image should be of frame of it's maximum width and height. Currently it is just adding transparency to the other area.
I had also added my code for cropping
let tempLayer = CAShapeLayer()
tempLayer.frame = self.view.frame
let path = UIBezierPath()
var endPoint: CGPoint!
for (var i = 0; i<4; i++){
let tag = 101+i
let pointView = viewCrop.viewWithTag(tag)
switch (pointView!.tag){
case 101:
endPoint = CGPointMake(pointView!.center.x-20, pointView!.center.y-20)
path.moveToPoint(endPoint)
default:
path.addLineToPoint(CGPointMake(pointView!.center.x-20, pointView!.center.y-20))
}
}
path.addLineToPoint(endPoint)
path.closePath()
tempLayer.path = path.CGPath
tempLayer.fillColor = UIColor.whiteColor().CGColor
tempLayer.backgroundColor = UIColor.clearColor().CGColor
imgReceiptView.layer.mask = tempLayer
UIGraphicsBeginImageContextWithOptions(viewCrop.bounds.size, imgReceiptView.opaque, 0.0);
imgReceiptView.layer.renderInContext(UIGraphicsGetCurrentContext())
let cropImg = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext();
UIImageWriteToSavedPhotosAlbum(cropImg, nil, nil, nil)
imgReceiptView.hidden = true
let tempImageView = UIImageView(frame: CGRectMake(20,self.view.center.y-80, self.view.frame.width-40,160))
tempImageView.backgroundColor = UIColor.grayColor()
tempImageView.image = cropImg
tempImageView.tag = 1001
tempImageView.layer.masksToBounds = true
self.view.addSubview(tempImageView)
Any help will be appreciable
Thanks in advance
Use this Library to crop image as User Specific
https://github.com/kishikawakatsumi/PEPhotoCropEditor
Thanks
Hope this will help you!