I have an UIView which I want to scale to double its size. I've tried these 2 commands:
self.bottomView.transform = CGAffineTransform(translationX: 0, y: -125)
This one moves to the point I want but it moves the entire view so I get a gap at the bottom.(125 is my original height)
self.bottomView.transform = CGAffineTransform(scaleX: 1, y: 2)
This one stretches the view but it stretches both ways, up and down. I want it to only stretch in an upward Y-axis direction and not to both ways.
Which one should I continue with? Is there any way to choose which way the view should stretch? Furthermore, scaleX: y: stretches the subviews as well which isn't optimal for my cause.
I think you can use the below API.
CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy)
Update only one axis: X or Y with recognizer.scale and keep the other one 1.0f to achieve one direction scale.
Here is a simple Solution that may help you -- Just Animate your stretching operation on UIView.
UIView.animate(withDuration: 0.2) {
self.bottomView.frame.size.height = self.bottomView.frame.size.height * 2
}
Related
I'm using both ARKit & Vision, following along Apple's sample project, "Using Vision in Real Time with ARKit". So I am not setting up my camera as ARKit handles that for me.
Using Vision's VNDetectFaceRectanglesRequest, I'm able to get back a collection of VNFaceObservation objects.
Following various guides online, I'm able to transform the VNFaceObservation's boundingBox to one that I can use on my ViewController's UIView.
The Y-axis is correct when placed on my UIView in ARKit, but the X-axis is completely off & inaccurate.
// face is an instance of VNFaceObservation
let transform = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -view.frame.height)
let translate = CGAffineTransform.identity.scaledBy(x: view.frame.width, y: view.frame.height)
let rect = face.boundingBox.applying(translate).applying(transform)
What is the correct way to display the boundingBox on the screen (in ARKit/UIKit) so that the X & Y axis match up correctly to the detected face rectangle? I can't use self.cameraLayer.layerRectConverted(fromMetadataOutputRect: transformedRect) since I'm not using AVCaptureSession.
Update: Digging into this further, the camera's image is 1920 x 1440. Most of it is not displayed on ARKit's screen space. The iPhone XS screen is 375 x 812 points.
After I get Vision's observation boundingBox, I've transformed it to fit the current view (375 x 812). This isn't working since the actual width seems to be 500 (the left & right sides are out of the screen view). How do I CGAffineTransform the CGRect bounding box (seems like 500x812, a total guess) from 375x812?
The key piece missing here is ARFrame's displayTransform(for:viewportSize:). You can read the documentation for it here.
This function will generate the appropriate transform for a given frame and viewport size (the CGRect of the view you're displaying the image and bounding box in).
func visionTransform(frame: ARFrame, viewport: CGRect) -> CGAffineTransform {
let orientation = UIApplication.shared.statusBarOrientation
let transform = frame.displayTransform(for: orientation,
viewportSize: viewport.size)
let scale = CGAffineTransform(scaleX: viewport.width,
y: viewport.height)
var t = CGAffineTransform()
if orientation.isPortrait {
t = CGAffineTransform(scaleX: -1, y: 1)
t = t.translatedBy(x: -viewport.width, y: 0)
} else if orientation.isLandscape {
t = CGAffineTransform(scaleX: 1, y: -1)
t = t.translatedBy(x: 0, y: -viewport.height)
}
return transform.concatenating(scale).concatenating(t)
}
You can then use this like so:
let transform = visionTransform(frame: yourARFrame, viewport: yourViewport)
let rect = face.boundingBox.applying(transform)
I am trying to annotate a PDF using type .ink in my application using a UIBezierPath. I have included a snippet of the pertinent code below (can add the whole sample but issue is only with rotating the path). The issue is when I apply this path, it is rotated 180 degrees around the x- axis so basically it is flipped upside down. I would like to be able to rotate this path 180 degrees around the x-axis so it appears as initially intended. I have seen example of rotating around the z-axis but none around the x-axis. Any help would be greatly appreciated!
let rect = CGRect(x: 110, y: 100, width: 400, height: 300)
let annotation = PDFAnnotation(bounds: rect, forType: .ink, withProperties: nil)
annotation.backgroundColor = .blue
path.apply(CGAffineTransform(scaleX: 0.2, y: 0.2))
annotation.add(path)
// Add annotation to the first page
page.addAnnotation(annotation)
pdfView?.document?.page(at: 0)?.addAnnotation(annotation)
I was actually able to solve this issue using the following scale and a translation transforms:
let rect = CGRect(x: 110, y: 100, width: 400, height: 300)
let annotation = PDFAnnotation(bounds: rect, forType: .ink, withProperties: nil)
annotation.backgroundColor = .blue
// OVER HERE 🙂
path.apply(CGAffineTransform(scaleX: 1.0, y: -1.0))
path.apply(CGAffineTransform(translationX: 0, y: rect.size.height))
annotation.add(path)
// Add annotation to the first page
page.addAnnotation(annotation)
pdfView?.document?.page(at: 0)?.addAnnotation(annotation)
This solution is inspired from the Apple Developer Documentation example.
It was a bit tricky because the PDFKit Coordinate System uses the bottom/left as the origin, with the x- axis going left-to-right and the y- axis going bottom-to-top. That's contrary to the origin: top/left, x: left-to-right and y: top-to-bottom pattern usually encountered on iOS. But this is what we're doing:
CGAffineTransform(scaleX: 1.0, y: -1.0) - scaling the y coordinate to -1.0 makes your path flip 180 degrees around the x- axis (said axis visually being the bottom line of the rect). This means the path is now below of the rect, which might give you the impression that it has disappeared (you won't even find it by Capturing the View Hierarchy since it will show you the entire PDFView as one UIView component, which may or may not drive you insane).
CGAffineTransform(translationX: 0, y: rect.size.height) - now that the path is at the bottom of the rect (but actually at the "top" according to the PDFKit Coordinate System), we need to bring it back into the visible area. Which is why we need to apply a translation transform to move the path up (or down - thanks again PDFKit) into the rect.
Hope this helps! Cheers
I have this on github: https://github.com/evertoncunha/EPCSpinnerView
It does this animations:
There is a glitch in the animation, when the circle is filling and the view is resizing, and I wan't to fix that.
The glitch happens because the way I fill the circle by setting the layer corners, so it looks like a circle, and I increase the lineWidth with the animation progress, and it looks likes it's filling from outside in. From EPCDrawnIconSpinner.swift file:
let ovalPath = UIBezierPath()
ovalPath.addArc(withCenter: CGPoint(x: ovalRect.midX, y: ovalRect.midY),
radius: ovalRect.width / 2,
startAngle: start * CGFloat.pi/180,
endAngle: end * CGFloat.pi/180, clockwise: true)
layer.cornerRadius = rect.size.height/2
ovalPath.lineWidth = 14 + ((frame.size.width) * progress)
Is there a better way that I can achieve this animation without doing the layer cornerRadius thing? If I could only draw this
I can think of two ways to get away from using cornerRadius:
Use a mask to round the corners, and use a shape layer to fill
You can create a CAShapeLayer of a filled circle, and with a frame set to your view's bounds, then assign it to your layer's mask property. You can animate the size of this mask as you animate the size of your view. Secondly, you will need to create an additional shape later (probably similar to your spinning circle), and do the line width animation on this layer. The mask will ensure that the line does not grow outwards, but only inwards.
Animate the line width and radius of the fill shape
You will need to create a circle shape layer similar to the one described in the first option, but instead of just animating the line width, you'll also need to animate the radius. For example: Let's say your view size is 40x40. Of course, at the start of the fill animation, your shape layer will also need to be 40x40. You will then animate the line width to 20pts, and animate the radius of the circle from 20pts to 10pts. A circle with a radius of 10pts and a line width of 20pts will appear to be a filled circle with a radius of 20pts.
Hope this helps.
I ended up with the following solution:
fileprivate func drawPathCompleted(_ ovalRect: CGRect) {
var size: CGFloat = ovalRect.size.width
var current = size*(1-progress)
var pos: CGFloat = lineWidth/2
while size > current {
let ovalPath = UIBezierPath(ovalIn: CGRect(x: pos, y: pos, width: size, height: size))
ovalPath.lineCapStyle = .round
ovalPath.lineWidth = lineWidth
ovalPath.stroke()
let decr = lineWidth/2
size -= decr
pos += decr/2
}
}
At this commit, line 243:
https://github.com/evertoncunha/EPCSpinnerView/commit/87d968846d92aa97e85ff4c58b6664ad7b03f00b#diff-8dc22814328ed859a00acfcf2909854bR242
I'm trying to create a paper folding effect in Swift using CALayers and CATransform3DRotate. There are some libraries out there, but those are pretty outdated and don't fit my needs (they don't have symmetric folds, for example).
My content view controller will squeeze to the right half side of the screen, revealing the menu at the left side.
Everything went well, until I applied perspective: then the dimensions I calculate are not correct anymore.
To explain the problem, I created a demo to show you what I'm doing.
This the content view controller with three squares. I will use three folds, so each square will be on a separate fold.
The even folds will get anchor point (0, 0.5) and the odd folds will get anchor point (1, 0.5), plus they'll receive a shadow.
When fully folded, the content view will be half of the screen's width.
On an iPhone 7, each fold/plane will be 125 points unfolded and 62.5 points fully folded when looked at.
To calculate the rotation needed to achieve this 62.5 points width, we can use a trigonometric function. To illustrate, look at this top-down view:
We know the original plane size (125) and the 2D width (62.5), so we can calculate the angle α using arccos:
let angle = acos(width / originalWidth)
The result is 1.04719755 rad or 60 degrees.
When using this formula with CATransform3DRotate, I get the correct result:
Now for the problem: when I add perspective, my calculation isn't correct anymore. The planes are bigger. Probably because of the now different projection.
You can see the planes are now overlapping and being clipped.
I reconstructed the desired result on the right by playing with the angle, but the correction needed is not consistent, unfortunately.
Here's the code I use. It works perfectly without perspective.
// Loop layers
for i in 0..<self.layers.count {
// Get layer
let layer = self.layers[i]
// Get dimensions
let width = self.frame.size.width / CGFloat(self.numberOfFolds)
let originalWidth = self.sourceView.frame.size.width / CGFloat(self.numberOfFolds)
// Calculate angle
let angle = acos(width / originalWidth)
// Set transform
layer.transform = CATransform3DIdentity
layer.transform.m34 = 1.0 / -500
layer.transform = CATransform3DRotate(layer.transform, angle * (i % 2 == 0 ? -1 : 1), 0, 1, 0)
// Update position
if i % 2 == 0 {
layer.position = CGPoint(x: (width * CGFloat(i)), y: layer.position.y)
} else {
layer.position = CGPoint(x: (width * CGFloat(i + 1)), y: layer.position.y)
}
}
So my question is: how do I achieve the desired result? Do I need to correct the angle, or should I calculate the projected/2D width differently?
Thanks in advance! :)
I've been using CGAffineTransformMakeScale to scale a UIView. This scales it by it's default anchorPoint, which is (.5, .5) -- the center. I'd like to scale it so that it stretches towards the left, as if it were zooming towards the left. I can accomplish this by setting the anchorPoint to the right center. However, this causes problems with setting the frame, etc. Is there a better way to apply a scale transform like I want. I was thinking I could do it with a custom transform matrix, but I am no good with matrix math.
Use anchorPoint, to get around the frame weirdness just store and reload it as in this answer.
First run this code and you will have better understanding. Here lblLabel is my label, you can change accordingly. Also check CGPoint(x: 10, y: 98) and CGPoint(x: 10, y: 108) as par > your requirements.
This will animate from Top Left corner of label. Change anchor point and position as par your requirements.
self.lblLabel.layer.anchorPoint = CGPoint(x: 0.0,y :0.0)
self.lblLabel.layer.position = CGPoint(x: 10, y: 98)
lblLabel.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
UIView.animate(withDuration: 1.0) {
self.lblLabel.layer.position = CGPoint(x: 10, y: 108)
self.lblLabel.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
}