I am using the below to modify UICollectionViewLayoutAttributes. Method 1 works but not Method 2, anyone can help explain the difference?
guard let newAttrs = attributes?.copy() as? UICollectionViewLayoutAttributes,
let imageViewInfo = imageViewInfo else { return nil }
let center = imageViewInfo.center
//Method 1
newAttrs.transform = CGAffineTransform(scaleX: scale, y: scale)
let x = (newAttrs.center.x-center.x) * scale + igCenter.x
let y = (newAttrs.center.y-center.y) * scale + igCenter.y
newAttrs.center = CGPoint(x: x, y: y)
//Method 2
newAttrs.transform = CGAffineTransform(translationX: -center.x, y: -center.y)
.scaledBy(x: scale, y: scale)
.translatedBy(x: igCenter.x, y: igCenter.y)
https://developer.apple.com/documentation/uikit/uiview/1622459-transform
to change position of the view/cell, modify 'center' instead.
Related
I would like to project ARPlaneAnchor point to captured image coordinate system.
At this moment i am able to get plane point and project it to view coordinate system by method
func translateTransform(_ x: Float, _ y: Float, _ z: Float) -> float4x4 {
var tf = float4x4(diagonal: SIMD4<Float>(repeating: 1))
tf.columns.3 = SIMD4<Float>(x: x, y: y, z: z, w: 1)
return tf
}
extension ARPlaneAnchor {
func worldPoints() -> (SCNVector3, SCNVector3, SCNVector3, SCNVector3) {
let worldTransform = transform * translateTransform(center.x, 0, center.z)
let width = extent.x
let height = extent.z
let topLeft = worldTransform * translateTransform(-width / 2.0, 0, -height / 2.0)
let topRight = worldTransform * translateTransform(width / 2.0, 0, -height / 2.0)
let bottomLeft = worldTransform * translateTransform(-width / 2.0, 0, height / 2.0)
let bottomRight = worldTransform * translateTransform(width / 2.0, 0, height / 2.0)
let pointTopLeft = SCNVector3(
x: topLeft.columns.3.x,
y: topLeft.columns.3.y,
z: topLeft.columns.3.z
)
let pointTopRight = SCNVector3(
x: topRight.columns.3.x,
y: topRight.columns.3.y,
z: topRight.columns.3.z
)
let pointBottomLeft = SCNVector3(
x: bottomLeft.columns.3.x,
y: bottomLeft.columns.3.y,
z: bottomLeft.columns.3.z
)
let pointBottomRight = SCNVector3(
x: bottomRight.columns.3.x,
y: bottomRight.columns.3.y,
z: bottomRight.columns.3.z
)
return (
pointTopLeft,
pointTopRight,
pointBottomLeft,
pointBottomRight
)
}
And then by projectPoint convert 3D point to 2D like in example
guard let point = arPlane?.worldPoints().0 else { return }
let pp = self.sceneView.session.currentFrame?.camera.projectPoint(SIMD3.init(point), orientation: .landscapeRight, viewportSize: self.view.bounds.size)
But how to convert this point to capturedImage coordinate system?
I am stuck with this point. I want an outline as per image and I want output as per this video
I tried this code but it was not working smooth.
extension CGPoint {
/**
Rotates the point from the center `origin` by `byDegrees` degrees along the Z axis.
- Parameters:
- origin: The center of he rotation;
- byDegrees: Amount of degrees to rotate around the Z axis.
- Returns: The rotated point.
*/
func rotated(around origin: CGPoint, byDegrees: CGFloat) -> CGPoint {
let dx = x - origin.x
let dy = y - origin.y
let radius = sqrt(dx * dx + dy * dy)
let azimuth = atan2(dy, dx) // in radians
let newAzimuth = azimuth + byDegrees * .pi / 180.0 // to radians
let x = origin.x + radius * cos(newAzimuth)
let y = origin.y + radius * sin(newAzimuth)
return CGPoint(x: x, y: y)
}
}
public extension UIImage {
/**
Returns the flat colorized version of the image, or self when something was wrong
- Parameters:
- color: The colors to user. By defaut, uses the ``UIColor.white`
- Returns: the flat colorized version of the image, or the self if something was wrong
*/
func colorized(with color: UIColor = .white) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, scale)
defer {
UIGraphicsEndImageContext()
}
guard let context = UIGraphicsGetCurrentContext(), let cgImage = cgImage else { return self }
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
color.setFill()
context.translateBy(x: 0, y: size.height)
context.scaleBy(x: 1.0, y: -1.0)
context.clip(to: rect, mask: cgImage)
context.fill(rect)
guard let colored = UIGraphicsGetImageFromCurrentImageContext() else { return self }
return colored
}
/**
Returns the stroked version of the fransparent image with the given stroke color and the thickness.
- Parameters:
- color: The colors to user. By defaut, uses the ``UIColor.white`
- thickness: the thickness of the border. Default to `2`
- quality: The number of degrees (out of 360): the smaller the best, but the slower. Defaults to `10`.
- Returns: the stroked version of the image, or self if something was wrong
*/
func stroked(with color: UIColor = .white, thickness: CGFloat = 2, quality: CGFloat = 10) -> UIImage {
guard let cgImage = cgImage else { return self }
// Colorize the stroke image to reflect border color
let strokeImage = colorized(with: color)
guard let strokeCGImage = strokeImage.cgImage else { return self }
/// Rendering quality of the stroke
let step = quality == 0 ? 10 : abs(quality)
let oldRect = CGRect(x: thickness, y: thickness, width: size.width, height: size.height).integral
let newSize = CGSize(width: size.width + 2 * thickness, height: size.height + 2 * thickness)
let translationVector = CGPoint(x: thickness, y: 0)
UIGraphicsBeginImageContextWithOptions(newSize, false, scale)
guard let context = UIGraphicsGetCurrentContext() else { return self }
defer {
UIGraphicsEndImageContext()
}
context.translateBy(x: 0, y: newSize.height)
context.scaleBy(x: 1.0, y: -1.0)
context.interpolationQuality = .high
for angle: CGFloat in stride(from: 0, to: 360, by: step) {
let vector = translationVector.rotated(around: .zero, byDegrees: angle)
let transform = CGAffineTransform(translationX: vector.x, y: vector.y)
context.concatenate(transform)
context.draw(strokeCGImage, in: oldRect)
let resetTransform = CGAffineTransform(translationX: -vector.x, y: -vector.y)
context.concatenate(resetTransform)
}
context.draw(cgImage, in: oldRect)
guard let stroked = UIGraphicsGetImageFromCurrentImageContext() else { return self }
return stroked
}
}
try this one
PLS do NOT add constraints to imageView except for top/left in Storyboard.
//
// ViewController.swift
// AddBorderANDZoom
//
// Created by ing.conti on 06/01/22.
//
import UIKit
import CoreGraphics
import AVFoundation
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var slider: UISlider!
let maZoom = 20.0
override func viewDidLoad() {
super.viewDidLoad()
self.slider.value = 1
self.imageView.image = UIImage(named: "apple")?.outline(borderSize: self.maZoom)
}
#IBAction func didSlide(_ sender: UISlider) {
let value = CGFloat(sender.value * 20)
self.imageView.image = UIImage(named: "apple")?.outline(borderSize: value)
}
}
/////
extension UIImage {
func outline(borderSize: CGFloat = 6.0) -> UIImage? {
let color = UIColor.black
let W = self.size.width
let H = self.size.height
let scale = 1 + (borderSize/W)
let outlinedImageRect = CGRect(x: -borderSize/2,
y: -borderSize/2,
width: W * scale,
height: H * scale)
let imageRect = CGRect(x: 0,
y: 0,
width: W,
height: H)
UIGraphicsBeginImageContextWithOptions(outlinedImageRect.size, false, scale)
self.draw(in: outlinedImageRect)
let context = UIGraphicsGetCurrentContext()!
context.setBlendMode(.sourceIn)
context.setFillColor(color.cgColor)
context.fill(outlinedImageRect)
self.draw(in: imageRect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
}
The following code is correctly adding physicsBodies for every "wall" tile in my SKTileMapNode. However, I would like to group them all under the same node with one physicsBody for the entire group. How do I achieve that?
private func setupTileMap() {
let tileSize = self.tileMap.tileSize
let halfWidth = CGFloat(self.tileMap.numberOfColumns) / 2.0 * tileSize.width
let halfHeight = CGFloat(self.tileMap.numberOfRows) / 2.0 * tileSize.height
for column in 0..<self.tileMap.numberOfColumns {
for row in 0..<self.tileMap.numberOfRows {
let tileDefinition = self.tileMap.tileDefinition(atColumn: column, row: row)
let tileX = CGFloat(column) * tileSize.width - halfWidth
let tileY = CGFloat(row) * tileSize.height - halfHeight
let rect = CGRect(x: 0, y: 0, width: tileSize.width, height: tileSize.height)
let tileNode = SKShapeNode(rect: rect)
tileNode.strokeColor = .clear
tileNode.position = CGPoint(x: tileX, y: tileY)
let physicsBodyCenter = CGPoint(x: tileSize.width / 2.0, y: tileSize.height / 2.0)
let physicsBody = SKPhysicsBody(rectangleOf: tileSize, center: physicsBodyCenter)
physicsBody.isDynamic = false
tileNode.physicsBody = physicsBody
if tileDefinition?.name == Nodes.wall.rawValue {
physicsBody.categoryBitMask = CategoryMask.wall.rawValue
}
self.tileMap.addChild(tileNode)
}
}
}
I have an animation like this:
bubble.frame.origin = CGPoint(x: 75, y: -120)
UIView.animate(
withDuration: 2.5,
animations: {
self.bubble.transform = CGAffineTransform(translationX: 0, y: self.view.frame.height * -1.3)
}
)
However, the animation goes in a straight line. I want the animation to do a little back and forth action on its way to its destination. Like a bubble. Any ideas?
If you want to animate along a path you can use CAKeyframeAnimation. The only question is what sort of path do you want. A dampened sine curve might be sufficient:
func animate() {
let box = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
box.backgroundColor = .blue
box.center = bubblePoint(1)
view.addSubview(box)
let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = bubblePath().cgPath
animation.duration = 5
box.layer.add(animation, forKey: nil)
}
where
private func bubblePath() -> UIBezierPath {
let path = UIBezierPath()
path.move(to: bubblePoint(0))
for value in 1...100 {
path.addLine(to: bubblePoint(CGFloat(value) / 100))
}
return path
}
/// Point on curve at moment in time.
///
/// - Parameter time: A value between 0 and 1.
/// - Returns: The corresponding `CGPoint`.
private func bubblePoint(_ time: CGFloat) -> CGPoint {
let startY = view.bounds.maxY - 100
let endY = view.bounds.minY + 100
let rangeX = min(30, view.bounds.width * 0.4)
let midX = view.bounds.midX
let y = startY + (endY - startY) * time
let x = sin(time * 4 * .pi) * rangeX * (0.1 + time * 0.9) + midX
let point = CGPoint(x: x, y: y)
return point
}
Yielding:
I try to draw a separator for slider, which must be at position of 1/3 of slider length. The slider body draws successfully, buy separator - not, it doesn't show.
Code is following
class RangeSliderTrackLayer:CALayer {
weak var rangeSlider:RangeSlider?
override func drawInContext(ctx: CGContext) {
if let slider = rangeSlider {
let cornerRadius = bounds.height * 1 / 2.0
let path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
CGContextAddPath(ctx, path.CGPath)
CGContextSetFillColorWithColor(ctx, UIColor.lightGrayColor().CGColor)
CGContextAddPath(ctx, path.CGPath)
CGContextFillPath(ctx)
CGContextSetFillColorWithColor(ctx, UIColor.yellowColor().CGColor)
let lowerValuePosition = CGFloat(40)
let upperValuePosition = CGFloat(80)
let rect = CGRect(x: lowerValuePosition, y: 0.0, width: upperValuePosition - lowerValuePosition, height: bounds.height)
CGContextFillRect(ctx, rect)
let separatorPath = UIBezierPath()
var x = bounds.width / 3
var y = bounds.height
separatorPath.moveToPoint(CGPoint(x: x, y: y))
separatorPath.addLineToPoint(CGPoint(x: x + 2, y: y))
separatorPath.addLineToPoint(CGPoint(x: x + 2, y: 0))
separatorPath.addLineToPoint(CGPoint(x: x, y: 0))
separatorPath.closePath()
UIColor.whiteColor().setFill()
separatorPath.stroke()
}
}
}
What am I doing wrong ?
You are calling setFill() but then then calling stroke(). Fill and stroke are two separate things. So, you either want:
Go ahead and set the fill color with setFill(), but then call fill() instead of stroke():
let separatorPath = UIBezierPath()
var x = bounds.width / 3
var y = bounds.height
separatorPath.moveToPoint(CGPoint(x: x, y: y))
separatorPath.addLineToPoint(CGPoint(x: x + 2, y: y))
separatorPath.addLineToPoint(CGPoint(x: x + 2, y: 0))
separatorPath.addLineToPoint(CGPoint(x: x, y: 0))
separatorPath.closePath()
UIColor.whiteColor().setFill()
// separatorPath.stroke()
separatorPath.fill()
Or call stroke() like you are not, but instead of calling setFill(), instead set lineWidth and call setStroke():
let separatorPath = UIBezierPath()
var x = bounds.width / 3
var y = bounds.height
separatorPath.moveToPoint(CGPoint(x: x, y: y))
separatorPath.addLineToPoint(CGPoint(x: x + 2, y: y))
separatorPath.addLineToPoint(CGPoint(x: x + 2, y: 0))
separatorPath.addLineToPoint(CGPoint(x: x, y: 0))
separatorPath.closePath()
// UIColor.whiteColor().setFill()
UIColor.whiteColor().setStroke()
separatorPath.lineWidth = 1
separatorPath.stroke()