UIDragInteractionDelegate: How to display transparent parts in the drag preview returned by dragInteraction(_:previewForLifting:session:) - ios

I'm building a drag and drop interaction for an iOS app. I want to enable the user to drag and drop images containing transparent parts.
However, the default preview for the dragged contents is a rectangle with an opaque white background that covers my app's background.
When I create a custom preview by implementing the UIDragInteractionDelegate method
dragInteraction(_:previewForLifting:session:), as in Apple's code sample Adopting Drag and Drop in a Custom View, the transparency of my source image is still not taken into account, meaning my preview image is still displayed in a rectangle with an opaque white background:
func dragInteraction(_ interaction: UIDragInteraction, previewForLifting item: UIDragItem, session: UIDragSession) -> UITargetedDragPreview? {
guard let image = item.localObject as? UIImage else { return nil }
// Scale the preview image view frame to the image's size.
let frame: CGRect
if image.size.width > image.size.height {
let multiplier = imageView.frame.width / image.size.width
frame = CGRect(x: 0, y: 0, width: imageView.frame.width, height: image.size.height * multiplier)
} else {
let multiplier = imageView.frame.height / image.size.height
frame = CGRect(x: 0, y: 0, width: image.size.width * multiplier, height: imageView.frame.height)
}
// Create a new view to display the image as a drag preview.
let previewImageView = UIImageView(image: image)
previewImageView.contentMode = .scaleAspectFit
previewImageView.frame = frame
/*
Provide a custom targeted drag preview that lifts from the center
of imageView. The center is calculated because it needs to be in
the coordinate system of imageView. Using imageView.center returns
a point that is in the coordinate system of imageView's superview,
which is not what is needed here.
*/
let center = CGPoint(x: imageView.bounds.midX, y: imageView.bounds.midY)
let target = UIDragPreviewTarget(container: imageView, center: center)
return UITargetedDragPreview(view: previewImageView, parameters: UIDragPreviewParameters(), target: target)
}
I tried to force the preview not to be opaque, but it did not help:
previewImageView.isOpaque = false
How can I get transparent parts in the lift preview?

Overwrite the backgroundColor in the UIDragPreviewParameters, as it defines the color for the background of a drag item preview.
Set it to UIColor.clear which is a color object whose grayscale and alpha values are both 0.0.
let previewParameters = UIDragPreviewParameters()
previewParameters.backgroundColor = UIColor.clear // transparent background
return UITargetedDragPreview(view: previewImageView,
parameters: previewParameters,
target: target)

You can define a UIBezierPath according to your image and set it to previewParameters.visiblePath
Example (Swift 4.2):
let previewParameters = UIDragPreviewParameters()
previewParameters.visiblePath = UIBezierPath(roundedRect: CGRect(x: yourX, y: yourY, width: yourWidth, height: yourHeight), cornerRadius: yourRadius)
//... Use the created previewParameters

Related

Disable anti-aliasing in UIKit

I've got a pixel art game app that uses UIKit for its menus and SpriteKit for the gameplay scene. The pixels are getting blurred due to anti-aliasing.
With the sprites I can turn off the anti-aliasing using...
node.texture?.filteringMode = .nearest
but in UIKit I can't find a way to turn off the anti-aliasing in the UIImageView's.
I saw this post but there's no example and the answer wasn't accepted. Not sure how to turn it off using CGContextSetShouldAntialias, or where to call it.
Based on the example I found here, tried using this subclass but it didn't seem to make a difference; according to my breakpoints the method is never called:
class NonAliasingView: UIImageView {
override func draw(_ rect: CGRect) {
guard let ctx = UIGraphicsGetCurrentContext() else { return }
// fill background with black color
ctx.addRect(bounds)
ctx.setFillColor(UIColor.black.cgColor)
ctx.fillPath()
if let img = image {
let pixelSize = CGSize(width: img.size.width * layer.contentsScale, height: img.size.height * layer.contentsScale)
UIGraphicsBeginImageContextWithOptions(pixelSize, true, 1)
guard let imgCtx = UIGraphicsGetCurrentContext() else { return }
imgCtx.setShouldAntialias(false)
img.draw(in: CGRect(x: 0, y: 0, width: pixelSize.width, height: pixelSize.height))
guard let cgImg = imgCtx.makeImage() else { return }
ctx.scaleBy(x: 1, y: -1)
ctx.translateBy(x: 0, y: -bounds.height)
ctx.draw(cgImg, in: CGRect(x: (bounds.width - img.size.width) / 2, y: (bounds.height - img.size.height) / 2, width: img.size.width, height: img.size.height))
}
}
}
Here's the code from my view controller where I tried to implement the subclass (modeImage is an IBOutlet to a UIImageView):
// modeImage.image = gameMode.displayImage
let modeImg = NonAliasingView()
modeImg.image = gameMode.displayImage
modeImage = modeImg
If I try to use UIGraphicsGetCurrentContext in the view controller it is nil and never passes the guard statement.
I've confirmed view.layer.allowsEdgeAntialiasing defaults to false. I don't need anti-aliasing at all, so if there's a way to turn off anti-aliasing app wide, or in the whole view controller, I'd be happy to use it.
How do you disable anti-aliasing with a UIImageView in UIKit?
UPDATE
Added imgCtx.setShouldAntialias(false) to method but still not working.
To remove all antialiasing on your image view and just use nearest-neighbor filtering, set the magnificationFilter and minificationFilter of the image view's layer to CALayerContentsFilter.nearest, as in:
yourImageView.layer.magnificationFilter = .nearest
yourImageView.layer.minificationFilter = .nearest

How to make a UIImageView have a square size based on a given bounds/container

So I am trying to create a simple UIImageView to make it have a square frame/size with CGSize. Based on a given bounds. So for example if the bounds container is the width & height of the screen then. The function should resize the UIImageView to fit like a perfect square base on those bounds on the screen.
Code:
let myImageView = UIImageView()
myImageView.frame.origin.y = (self.view?.frame.height)! * 0.0
myImageView.frame.origin.x = (self.view?.frame.width)! * 0.0
myImageView.backgroundColor = UIColor.blue
self.view?.insertSubview(myImageView, at: 0)
//("self.view" is the ViewController view that is the same size as the devices screen)
MakeSquare(view: myImageView, boundsOf: self.view)
func MakeSquare(view passedview: UIImageView, boundsOf container: UIView) {
let ratio = container.frame.size.width / container.frame.size.height
if container.frame.width > container.frame.height {
let newHeight = container.frame.width / ratio
passedview.frame.size = CGSize(width: container.frame.width, height: newHeight)
} else{
let newWidth = container.frame.height * ratio
passedview.frame.size = CGSize(width: newWidth, height: container.frame.height)
}
}
The problem is its giving me back the same bounds/size of the container & not changed
Note: I really have know idea how to pull this off, but wanted to see if its possible. My function comes from a question here. That takes a UIImage and resizes its parent view to make the picture square.
This should do it (and will centre the image view in the containing view):
func makeSquare(view passedView: UIImageView, boundsOf container: UIView) {
let minSize = min(container.bounds.maxX, container.bounds.maxY)
passedView.bounds = CGRect(x: container.bounds.midX - minSize / 2.0,
y: container.bounds.midY - minSize / 2.0,
width: minSize, height: minSize)
}

only detect in a section of camera preview layer, iOS, Swift

I am trying to get a detection zone in a live preview on my camera preview layer.
Is it possible for this, say there is a live feed and you have face detect on and as you look around it will only put a box around the face in a certain area for example a rectangle in the centre of the screen. all other faces in the preview that are outside of the rectangle don't get detected?
Im using Vision, iOS, Swift.
I figured this out by adding a guard before the CALayer adding
Before View did load
#IBOutlet weak var scanAreaImage: UIImageView!
var regionOfInterest: CGRect!
In View did load
scanAreaImage.frame is a image view that I put in via storyboard and this would represent the area I only wanted detection in,
let someRect: CGRect = scanAreaImage.frame
regionOfInterest = someRect
then in the vision text detection section.
func highlightLetters(box: VNRectangleObservation) {
let xCord = box.topLeft.x * (cameraPreviewlayer?.frame.size.width)!
let yCord = (1 - box.topLeft.y) * (cameraPreviewlayer?.frame.size.height)!
let width = (box.topRight.x - box.bottomLeft.x) * (cameraPreviewlayer?.frame.size.width)!
let height = (box.topLeft.y - box.bottomLeft.y) * (cameraPreviewlayer?.frame.size.height)!
// This is the section I Added for the rec of interest detection zone.
//////////////////////////////////////////////
let wordRect = CGRect(x: xCord, y: yCord, width: width, height: height)
guard regionOfInterest.contains(wordRect.origin) else { return } // only draw a box if the orgin of the word box is within the regionOfInterest
// regionOfInterest being the cgRect you created earlier
//////////////////////////////////////////////
let outline = CALayer()
outline.frame = CGRect(x: xCord, y: yCord, width: width, height: height)
outline.borderWidth = 1.0
if textColour == 1 {
outline.borderColor = UIColor.blue.cgColor
}else {
outline.borderColor = UIColor.clear.cgColor
}
cameraPreviewlayer?.addSublayer(outline)
this will only show outlines of the things inside the rectangle you created in storyboard. (Mine being the scanAreaImage)
I hope this helps someone.

How to set half-round UIImage in Swift like this screenshot

https://www.dropbox.com/s/wlizis5zybsvnfz/File%202017-04-04%2C%201%2052%2024%20PM.jpeg?dl=0
Hello all Swifters,
Could anyone tell me how to set this kind of UI? Is there any half rounded image that they have set?
Or there are two images. One with the mountains in the background and anohter image made half rounded in white background and placed in on top?
Please advise
Draw an ellipse shape using UIBezier path.
Draw a rectangle path exactly similar to imageView which holds your image.
Transform the ellipse path with CGAffineTransform so that it will be in the center of the rect path.
Translate rect path with CGAffineTransform by 0.5 to create intersection between ellipse and the rect.
Mask the image using CAShapeLayer.
Additional: As Rob Mayoff stated in comments you'll probably need to calculate the mask size in viewDidLayoutSubviews. Don't forget to play with it, test different cases (different screen sizes, orientations) and adjust the implementation based on your needs.
Try the following code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
guard let image = imageView.image else {
return
}
let size = image.size
imageView.clipsToBounds = true
imageView.image = image
let curveRadius = size.width * 0.010 // Adjust curve of the image view here
let invertedRadius = 1.0 / curveRadius
let rect = CGRect(x: 0,
y: -40,
width: imageView.bounds.width + size.width * 2 * invertedRadius,
height: imageView.bounds.height)
let ellipsePath = UIBezierPath(ovalIn: rect)
let transform = CGAffineTransform(translationX: -size.width * invertedRadius, y: 0)
ellipsePath.apply(transform)
let rectanglePath = UIBezierPath(rect: imageView.bounds)
rectanglePath.apply(CGAffineTransform(translationX: 0, y: -size.height * 0.5))
ellipsePath.append(rectanglePath)
let maskShapeLayer = CAShapeLayer()
maskShapeLayer.frame = imageView.bounds
maskShapeLayer.path = ellipsePath.cgPath
imageView.layer.mask = maskShapeLayer
}
}
Result:
You can find an answer here:
https://stackoverflow.com/a/34983655/5479510
But generally, I wouldn't recommend using a white image overlay as it may appear distorted or pixelated on different devices. Using a masking UIView would do just great.
Why you could not just create (draw) rounded transparent image and add UIImageView() with UIImage() at the top of the view with required height and below this view add other views. I this this is the easiest way. I would write comment but I cant.

Swift / cut background image

My background image is set with UIView extension.
extension UIView {
func addBackground() {
// screen width and height:
let width = UIScreen.mainScreen().bounds.size.width
let height = UIScreen.mainScreen().bounds.size.height
let imageViewBackground = UIImageView(frame: CGRectMake(0, 0, width, height))
imageViewBackground.image = UIImage(named: "index_clear")
// you can change the content mode:
imageViewBackground.contentMode = UIViewContentMode.ScaleAspectFill
self.addSubview(imageViewBackground)
self.sendSubviewToBack(imageViewBackground)
}}
For segue animation the view moves from left to right. The background image is way larger than the screen, what is visible while the animation.
How can I cut the background image to perfectly fit into the view?
.ScaleAspectFit does the job, but since it's a picture it looks bad.
Help is very appreciated.
You can use .ScaleAspectFill to crop the image rather than scale it to fit. This won't distort your image, but obviously you won't be able to see the whole thing.
You need to ensure that you set clipsToBounds=true on the UIImageView so that the cropped area isn't visible
extension UIView {
func addBackground() {
// screen width and height:
let width = UIScreen.mainScreen().bounds.size.width
let height = UIScreen.mainScreen().bounds.size.height
let imageViewBackground = UIImageView(frame: CGRectMake(0, 0, width, height))
imageViewBackground.image = UIImage(named: "index_clear")
// you can change the content mode:
imageViewBackground.contentMode = UIViewContentMode.ScaleAspectFill
imageViewBackground.clipsToBounds=true
self.addSubview(imageViewBackground)
self.sendSubviewToBack(imageViewBackground)
}

Resources