Move and animate UIImageView using swift - uiimageview

I have what should be a fairly simple question here. Basically I'm trying to move an object (a UIImageView) from a point A (where it is set in the storyboard) to a point B which I define programatically.
My first pass through this resulted in this code
UIView.animateWithDuration(0.75, delay: 0.5, options: UIViewAnimationOptions.CurveLinear, animations: {
self.signInButton.alpha = 1
self.signInButton.center.y = 10
}, completion: nil)
However, what this code does is to basically move the button offcenter and then back to its original location.
I then looked into QuartzCore to help me, but everything is in Objective-C. I have this method:
func moveImage(view: UIImageView){
var toPoint: CGPoint = CGPointMake(0.0, -10.0)
var fromPoint : CGPoint = CGPointZero
var movement = CABasicAnimation(keyPath: "movement")
movement.additive = true
movement.fromValue = fromPoint
movement.toValue = toPoint
movement.duration = 0.3
view.layer.addAnimation(movement, forKey: "move")
}
However the problem here is that movement.fromValue cannot accept a CGPoint. I know that in objective C there was a function that converted a CGPoint to a NSValue, however this function seems to be deprecated out of Swift and I can't find the other way of doing this.
Therefore my question is either, how do I convert CGPoint to NSValue to make my moveImage() function work, or is there a better way to move an object from point A to point B?
Thanks!
I'm looking at this question Animate UIImage in UIImageView Up & Down (Like it's hovering) Loop

Use NSValue(CGPoint: cgpiont) instead of NSValue.valueWithCGPoint(<#point: CGPoint#>) which is deprecated in swift. NSValue(CGPoint: cgpiont) is constructor given for that which can be used to convert CGPoint to NSValue
in swift.Following code will work
func moveImage(view: UIImageView){
var toPoint: CGPoint = CGPointMake(0.0, -10.0)
var fromPoint : CGPoint = CGPointZero
var movement = CABasicAnimation(keyPath: "movement")
movement.additive = true
movement.fromValue = NSValue(CGPoint: fromPoint)
movement.toValue = NSValue(CGPoint: toPoint)
movement.duration = 0.3
view.layer.addAnimation(movement, forKey: "move")
}

SWIFT 3 UPDATES
func moveImageView(imgView: UIImageView){
var toPoint:CGPoint = CGPoint(x: 0.0, y: -10.0)
var fromPoint:CGPoint = CGPoint.zero
var movement = CABasicAnimation(keyPath: "movement")
movement.isAdditive = true
movement.fromValue = NSValue(cgPoint: fromPoint)
movement.toValue = NSValue(cgPoint: toPoint)
movement.duration = 0.3
imgView.layer.add(movement, forKey: "move")
}

Related

macOS Swift CALayer.contents does not load image

The code below creates a red rectangle that is animated to move across the view from left to right. I would like to have an arbitrary shape loaded from an image to either superimpose or replace the rectangle. However, the circleLayer.contents = NSImage statement in the initializeCircleLayer function doesn't produce any effect. The diagnostic print statement seems to verify that the image exists and has been found, but no image appears in the view. How do I get an image into the layer to replace the animated red rectangle? Thanks!
CODE BELOW:
import Cocoa
class ViewController: NSViewController {
var circleLayer = CALayer()
override func viewDidLoad() {
super.viewDidLoad()
self.view.wantsLayer = true
initializeCircleLayer()
simpleCAAnimationDemo()
}
func initializeCircleLayer(){
circleLayer.bounds = CGRect(x: 0, y: 0, width: 150, height: 150)
circleLayer.position = CGPoint(x: 50, y: 150)
circleLayer.backgroundColor = NSColor.red.cgColor
circleLayer.cornerRadius = 10.0
let testIm = NSImage(named: NSImage.Name(rawValue: "testImage"))
print("testIm = \(String(describing: testIm))")
circleLayer.contents = NSImage(named: NSImage.Name(rawValue: "testImage"))?.cgImage
circleLayer.contentsGravity = kCAGravityCenter
self.view.layer?.addSublayer(circleLayer)
}
func simpleCAAnimationDemo(){
circleLayer.removeAllAnimations()
let animation = CABasicAnimation(keyPath: "position")
let startingPoint = NSValue(point: NSPoint(x: 50, y: 150))
let endingPoint = NSValue(point: NSPoint(x: 600, y: 150))
animation.fromValue = startingPoint
animation.toValue = endingPoint
animation.repeatCount = Float.greatestFiniteMagnitude
animation.duration = 10.0
circleLayer.add(animation, forKey: "linearMovement")
}
}
Why it doesn't work
The reason why
circleLayer.contents = NSImage(named: NSImage.Name(rawValue: "testImage"))?.cgImage
doesn't work is because it's a reference to the cgImage(forProposedRect:context:hints:) method, meaning that its type is
((UnsafeMutablePointer<NSRect>?, NSGraphicsContext?, [NSImageRep.HintKey : Any]?) -> CGImage?)?
You can see this by assigning NSImage(named: NSImage.Name(rawValue: "testImage"))?.cgImage to a local variable and ⌥-clicking it to see its type.
The compiler allows this assignment because circleLayer.contents is an Any? property, so literally anything can be assigned to it.
How to fix it
As of macOS 10.6, you can assign NSImage objects to a layers contents directly:
circleLayer.contents = NSImage(named: NSImage.Name(rawValue: "testImage"))

Rotate NSImageView at its Center to Make it Spin

Swift 4, macOS 10.13
I have read a variety of answers on SO and still can't get an NSImageView to spin at its center instead of one of its corners.
Right now, the image looks like this (video): http://d.pr/v/kwiuwS
Here is my code:
//`loader` is an NSImageView on my storyboard positioned with auto layout
loader.wantsLayer = true
let oldFrame = loader.layer?.frame
loader.layer?.anchorPoint = CGPoint(x: 0.5, y: 0.5)
loader.layer?.position = CGPoint(x: 0.5, y: 0.5)
loader.layer?.frame = oldFrame!
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(-1 * .pi * 2.0)
rotateAnimation.duration = 2
rotateAnimation.repeatCount = .infinity
loader.layer?.add(rotateAnimation, forKey: nil)
Any ideas what I am still missing?
I just created a simple demo which contains the handy setAnchorPoint extension for all views.
The main reason you see your rotation from a corner is that your anchor point is somehow reset to 0,0.
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
var imageView: NSImageView!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
// Create red NSImageView
imageView = NSImageView(frame: NSRect(x: 100, y: 100, width: 100, height: 100))
imageView.wantsLayer = true
imageView.layer?.backgroundColor = NSColor.red.cgColor
window.contentView?.addSubview(imageView)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func applicationDidBecomeActive(_ notification: Notification) {
// Before animate, reset the anchor point
imageView.setAnchorPoint(anchorPoint: CGPoint(x: 0.5, y: 0.5))
// Start animation
if imageView.layer?.animationKeys()?.count == 0 || imageView.layer?.animationKeys() == nil {
let rotate = CABasicAnimation(keyPath: "transform.rotation")
rotate.fromValue = 0
rotate.toValue = CGFloat(-1 * .pi * 2.0)
rotate.duration = 2
rotate.repeatCount = Float.infinity
imageView.layer?.add(rotate, forKey: "rotation")
}
}
}
extension NSView {
func setAnchorPoint(anchorPoint:CGPoint) {
if let layer = self.layer {
var newPoint = NSPoint(x: self.bounds.size.width * anchorPoint.x, y: self.bounds.size.height * anchorPoint.y)
var oldPoint = NSPoint(x: self.bounds.size.width * layer.anchorPoint.x, y: self.bounds.size.height * layer.anchorPoint.y)
newPoint = newPoint.applying(layer.affineTransform())
oldPoint = oldPoint.applying(layer.affineTransform())
var position = layer.position
position.x -= oldPoint.x
position.x += newPoint.x
position.y -= oldPoint.y
position.y += newPoint.y
layer.anchorPoint = anchorPoint
layer.position = position
}
}
}
As I wondered many times myself on this question, here is my own simple method to rotate any NSView. I post it also as a self reminder. It can be defined in a category if needed.
This is a simple rotation, not a continuous animation. Should be applied to an NSView instance with wantsLayer = YES.
- (void)rotateByNumber:(NSNumber*)angle {
self.layer.position = CGPointMake(NSMidX(self.frame), NSMidY(self.frame));
self.layer.anchorPoint = CGPointMake(.5, .5);
self.layer.affineTransform = CGAffineTransformMakeRotation(angle.floatValue);
}
This is the result of a layout pass resetting your view's layer to default properties. If you check your layer's anchorPoint for example, you'll find it's probably reset to 0, 0.
A simple solution is to continually set the desired layer properties in viewDidLayout() if you're in a view controller. Basically doing the frame, anchorPoint, and position dance that you do in your initial setup on every layout pass. If you subclassed NSImageView you could likely contain that logic within that view, which would be much better than putting that logic in a containing view controller.
There is likely a better solution with overriding the backing layer or rolling your own NSView subclass that uses updateLayer but I'd have to experiment there to give a definitive answer.

Resizing the window according to a variable swift

I have a NSViewController and a variable num. I want to change the size of the window dynamically according to that variable. Is there any way to do that in swift?
Let's say your window has an IBOutlet named "window", and your dynamic number is named "myDynamicNumber":
func resize() {
var windowFrame = window.frame
let oldWidth = windowFrame.size.width
let oldHeight = windowFrame.size.height
let toAdd = CGFloat(myDynamicNumber)
let newWidth = oldWidth + toAdd
let newHeight = oldHeight + toAdd
windowFrame.size = NSMakeSize(newWidth, newHeight)
window.setFrame(windowFrame, display: true)
}
In Swift 3 to resize the window you use setFrame.
An example from the ViewController:
func resizeWin(size:(CGFloat,CGFloat)){
self.view.window?.setFrame(NSRect(x:0,y:0,width:size.0,height:size.1), display: true)
}
I needed to toggle viewing a text view so I overlaid the window an invisible view - hideRect just short of the text view; in this way I can resize to the smaller (hideRect) and restore later to the original size - origRect. Hide and original rect captured at viewDidLoad(). Swift 3/Xcode 8.3.3
// class global contants
let kTitleUtility = 16
let kTitleNormal = 22
#IBOutlet var hideView: NSView!
var hideRect: NSRect?
var origRect: NSRect?
#IBAction func toggleContent(_ sender: Any) {
// Toggle content visibility
if let window = self.view.window {
let oldSize = window.contentView?.bounds.size
var frame = window.frame
if toggleButton.state == NSOffState {
frame.origin.y += ((oldSize?.height)! - (hideRect?.size.height)!)
window.setFrameOrigin(frame.origin)
window.setContentSize((hideRect?.size)!)
window.showsResizeIndicator = false
window.minSize = NSMakeSize((hideRect?.size.width)!,(hideRect?.size.height)!+CGFloat(kTitleNormal))
creditScroll.isHidden = true
}
else
{
let hugeSize = NSMakeSize(CGFloat(Float.greatestFiniteMagnitude), CGFloat(Float.greatestFiniteMagnitude))
frame.origin.y += ((oldSize?.height)! - (origRect?.size.height)!)
window.setFrameOrigin(frame.origin)
window.setContentSize((origRect?.size)!)
window.showsResizeIndicator = true
window.minSize = NSMakeSize((origRect?.size.width)!,(origRect?.size.height)!+CGFloat(kTitleNormal))
window.maxSize = hugeSize
creditScroll.isHidden = false
}
}
}
This also preserved the widow's visual origin, and sizing minimum.

Add To Cart Animation in Swift

I am using the code below to perform "add to cart animation",
I recently build a new app using swift and I'm having a hard time to convert this code from Objective C to Swift.
this code is animating a UITableView button To Jump into Cart(UItabBar Item)
// AddToCart button (cell Button)
-(void)AddToCart:(UIButton*)sender {
// get the selected index
CGPoint center= sender.center;
CGPoint rootViewPoint = [sender.superview convertPoint:center toView:self.Tableview];
NSIndexPath *indexPath = [self.Tableview indexPathForRowAtPoint:rootViewPoint];
// add to cart
[checkoutCart AddItem:SandwichArray[indexPath.row]];
MyCell* cell =(MyCell*)[self.Tableview dequeueReusableCellWithIdentifier:#"Cell"];
// grab the imageview
UIImageView *imgV = (UIImageView*)[cell viewWithTag:400];
// get the exact location of image
CGRect rect = [imgV.superview convertRect:imgV.frame fromView:nil];
rect = CGRectMake(5, (rect.origin.y*-1)-10, imgV.frame.size.width, imgV.frame.size.height);
// create new duplicate image
UIImageView *starView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"AddItem.png"]];
[starView setFrame:rect];
starView.layer.cornerRadius=5;
starView.layer.borderColor=[[UIColor blackColor]CGColor];
starView.layer.borderWidth=1;
[self.view addSubview:starView];
// apply position animation
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.calculationMode = kCAAnimationPaced;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
pathAnimation.duration=0.75;
pathAnimation.delegate=self;
// tabbar Position
CGPoint endPoint = CGPointMake(210+rect.size.width/2, 390+rect.size.height/2);
CGMutablePathRef curvedPath = CGPathCreateMutable();
CGPathMoveToPoint(curvedPath, NULL, starView.frame.origin.x, starView.frame.origin.y);
CGPathAddCurveToPoint(curvedPath, NULL, endPoint.x, starView.frame.origin.y, endPoint.x, starView.frame.origin.y, endPoint.x, endPoint.y);
pathAnimation.path = curvedPath;
CGPathRelease(curvedPath);
// apply transform animation
CABasicAnimation *basic=[CABasicAnimation animationWithKeyPath:#"transform"];
[basic setToValue:[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.25, 0.25, 0.25)]];
[basic setAutoreverses:NO];
[basic setDuration:0.75];
[starView.layer addAnimation:pathAnimation forKey:#"curveAnimation"];
[starView.layer addAnimation:basic forKey:#"transform"];
[starView performSelector:#selector(removeFromSuperview) withObject:nil afterDelay:0.75];
[self performSelector:#selector(reloadBadgeNumber) withObject:nil afterDelay:0.75];
}
This is my swift code
//AddToCart button (of cell)
func AddToCart(sender:UIButton){
// get the selected index
var center:CGPoint = sender.center;
var rootViewPoint:CGPoint = sender.superview!.convertPoint(center, toView:self.TableView)
var indexPath:NSIndexPath = self.TableView!.indexPathForRowAtPoint(rootViewPoint)!
// add to cart
//ShopingCart.AddItem(item)
var cell:Menu_Cell = self.TableView!.dequeueReusableCellWithIdentifier("cell") as Menu_Cell
//grab the imageview using cell
var imgV:UIImageView = cell.imageView!
// get the exact location of image
var rect:CGRect = imgV.superview!.convertRect(imgV.frame ,fromView:nil)
rect = CGRectMake(5, (rect.origin.y*(-1))-10, imgV.frame.size.width, imgV.frame.size.height);
// create new duplicate image
var starView:UIImageView = cell.imageView!
starView.frame = rect
starView.layer.cornerRadius=5;
starView.layer.borderWidth=1;
self.view.addSubview(starView)
// position animation
// var pathAnimation:CAKeyframeAnimation = CAKeyframeAnimation.animationWithKeyPath("position")
var pathAnimation:CAPropertyAnimation = CAPropertyAnimation(keyPath: "position")
// pathAnimation.calculationMode = kCAAnimationPaced
pathAnimation.fillMode = kCAFillModeForwards
pathAnimation.removedOnCompletion = false
pathAnimation.duration=0.75
pathAnimation.delegate=self
// tab-bar right side item frame-point = end point
var endPoint:CGPoint = CGPointMake(210+rect.size.width/2, 390+rect.size.height/2);
// animation position animation
var curvedPath:CGMutablePathRef = CGPathCreateMutable();
CGPathMoveToPoint(curvedPath, nil, starView.frame.origin.x, starView.frame.origin.y);
CGPathAddCurveToPoint(curvedPath, nil, endPoint.x, starView.frame.origin.y, endPoint.x, starView.frame.origin.y, endPoint.x, endPoint.y);
// pathAnimation.path = curvedPath;
// apply transform animation
// var basic:CABasicAnimation = CABasicAnimation.animationWithKeyPath("transform")
var basic:CAPropertyAnimation = CAPropertyAnimation(keyPath: "transform")
// basic.valueForKeyPath(NSValue.valueWithCATransform3D(CATransform3DMakeScale(0.25, 0.25, 0.25)))
// basic.setAutoreverses(false)
basic.duration = 0.75
starView.layer.addAnimation(pathAnimation,forKey: "curveAnimation")
starView.layer.addAnimation(basic,forKey:"transform")
starView.removeFromSuperview()
// [self performSelector:#selector(reloadBadgeNumber) withObject:nil afterDelay:0.75];
i am getting Error here:
starView.layer.addAnimation(pathAnimation,forKey: "curveAnimation")
tarView.layer.addAnimation(basic,forKey:"transform")
**'-[CAPropertyAnimation _copyRenderAnimationForLayer:]: unrecognized selector sent to instance 0x7fd612c11780'**
any suggestions ?
import QuartzCore is not proper answer. Make sure your key value for animation in layer is given proper. I also facing same and changed to CAKeyframeAnimation. My task is differ from you.
That error appears, if there is the import of the QuartzCore-header missing in your code. So you need to import the QuartzCore-framework:
import QuartzCore
var center:CGPoint = sender.center;
var rootViewPoint:CGPoint = sender.superview!.convertPoint(center, toView:self.tableView)
var indexPath:NSIndexPath = self.tableView!.indexPathForRowAtPoint(rootViewPoint)!
var cell:Cell_3 = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! Cell_3
var imgV:UITextField = cell.tf_adet!
// get the exact location of image
var rect:CGRect = imgV.superview!.convertRect(imgV.frame ,fromView:nil)
rect = CGRectMake(rect.origin.x, (rect.origin.y*(-1))-10, imgV.frame.size.width, imgV.frame.size.height);
// create new duplicate image
var starView:UITextField = cell.tf_adet
starView.frame = rect
starView.layer.cornerRadius=5;
starView.layer.borderWidth=1;
self.view.addSubview(starView)
// now create a bezier path that defines our curve
// the animation function needs the curve defined as a CGPath
// but these are more difficult to work with, so instead
// we'll create a UIBezierPath, and then create a
// CGPath from the bezier when we need it
let path = UIBezierPath()
// tab-bar right side item frame-point = end point
var endPoint:CGPoint = CGPointMake(140+rect.size.width/2, 790+rect.size.height/2);
path.moveToPoint(CGPointMake(starView.frame.origin.x, starView.frame.origin.y))
path.addCurveToPoint(CGPoint(x: endPoint.x, y: endPoint.y),
controlPoint1: CGPoint(x: endPoint.x, y: starView.frame.origin.y),
controlPoint2: CGPoint(x: endPoint.x, y: starView.frame.origin.y ))
// create a new CAKeyframeAnimation that animates the objects position
let anim = CAKeyframeAnimation(keyPath: "position")
// set the animations path to our bezier curve
anim.path = path.CGPath
// set some more parameters for the animation
// this rotation mode means that our object will rotate so that it's parallel to whatever point it is currently on the curve
// anim.rotationMode = kCAFillModeForwards
anim.fillMode = kCAFillModeForwards
//anim.repeatCount = Float.infinity
anim.duration = 0.65
anim.removedOnCompletion = false
anim.delegate=self
// apply transform animation
var animation : CABasicAnimation = CABasicAnimation(keyPath: "transform");
var transform : CATransform3D = CATransform3DMakeScale(2,2,1 ) //0.25, 0.25, 0.25);
//animation.setValue(NSValue(CATransform3D: transform), forKey: "scaleText");
animation.duration = 0.75;
starView.layer.addAnimation(anim, forKey: "curveAnimation")
starView.layer.addAnimation(animation, forKey: "transform");

SCNScene issue in Swift: nothing shows in SCNView

I'm trying to do this in Swift. However my SCNView doesn't show anything. I checked the connections in IB and everything is fine. I assume that I made an error while translating the source code from Objective C to Swift. Here is my code:
#IBOutlet var sceneview: SCNView
#IBOutlet var status: NSTextField
var statusCounter: Int = 1
#IBAction func paintRectButton (sender: AnyObject) {
status.stringValue = "Paint (#\(statusCounter++))"
var scene: SCNScene = SCNScene()
var cameraNode: SCNNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3Make(0, 15, 30)
cameraNode.transform = CATransform3DRotate(cameraNode.transform, 7.0, 1, 0, 0)
scene.rootNode.addChildNode(cameraNode)
var spotlight: SCNLight = SCNLight()
spotlight.type = SCNLightTypeSpot
spotlight.color = NSColor.redColor()
var spotlightNode: SCNNode = SCNNode()
spotlightNode.light = spotlight
spotlightNode.position = SCNVector3Make(-2, 1, 0)
cameraNode.addChildNode(spotlightNode)
let boxSide = 15.0
var box: SCNBox =
SCNBox(width: boxSide, height: boxSide, length: boxSide, chamferRadius: 0)
var boxNode: SCNNode = SCNNode(geometry: box)
boxNode.transform = CATransform3DMakeRotation(3, 0, 1, 0)
scene.rootNode.addChildNode(boxNode)
sceneview.scene = scene
}
The reason why nothing shows up is that the camera is looking in a direction where there isn't any geometry object to be rendered. The Objective-C code uses -M_PI/7.0 (≈ -0.4488 radians) for the rotation angle of the camera and but your Swift code is using 7.0 (≈ 0.7168 radians (the remainder after dividing by π)). Change the Swift code to:
cameraNode.transform = CATransform3DRotate(cameraNode.transform, -M_PI/7.0, 1, 0, 0)
A similar mistake seem to have happened with the rotation of the box where the original code uses the angle M_PI_2/3 and the Swift code is using the angle 3.
boxNode.transform = CATransform3DMakeRotation(M_PI_2/3.0, 0, 1, 0)

Resources