Add To Cart Animation in Swift - xcode

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");

Related

SceneKIt - how to assign ARSCNView video feed as a texture to a SCNGeometry

Goal: get the video feed from ARSCNView. and assign as a video material to a SceneKIt SCNGeometry.
What I did:
// retrieve the ship node
let ship = scene.rootNode.childNode(withName: "shipMesh", recursively: true)!
// apply AR feed to the ship node material
let material = ship.geometry?.firstMaterial
material!.diffuse.contents = arView.scene.background.contents
Problem: the ship is white, without AR video feed
Full code bellow:
import UIKit
import QuartzCore
import SceneKit
import ARKit
class GameViewController: UIViewController, ARSCNViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let arView = ARSCNView()
// create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = UIColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
// retrieve the ship node
let ship = scene.rootNode.childNode(withName: "shipMesh", recursively: true)!
let material = ship.geometry?.firstMaterial
material!.diffuse.contents = arView.scene.background.contents
material!.lightingModel = .constant
// animate the 3d object
//ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))
// retrieve the SCNView
let scnView = self.view as! SCNView
// set the scene to the view
scnView.scene = scene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
// show statistics such as fps and timing information
scnView.showsStatistics = true
// configure the view
scnView.backgroundColor = UIColor.black
// add a tap gesture recognizer
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
scnView.addGestureRecognizer(tapGesture)
}
#objc
func handleTap(_ gestureRecognize: UIGestureRecognizer) {
// retrieve the SCNView
let scnView = self.view as! SCNView
// check what nodes are tapped
let p = gestureRecognize.location(in: scnView)
let hitResults = scnView.hitTest(p, options: [:])
// check that we clicked on at least one object
if hitResults.count > 0 {
// retrieved the first clicked object
let result = hitResults[0]
// get its material
let material = result.node.geometry!.firstMaterial!
// highlight it
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
// on completion - unhighlight
SCNTransaction.completionBlock = {
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
material.emission.contents = UIColor.black
SCNTransaction.commit()
}
material.emission.contents = UIColor.red
SCNTransaction.commit()
}
}
override var shouldAutorotate: Bool {
return true
}
override var prefersStatusBarHidden: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
}
Create a fresh (out of the box) ARKit SceneKit based App
(the one with the SpaceShip)
Then you modify your viewWillAppear section like this:
Add the DispatchQueue stuff below.
Important: Add a short delay (here 5.0 seconds) to give the AR Subsystem enough time to initialise the Camera.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
// Run the view's session
sceneView.session.run(configuration)
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
let shipMesh = self.sceneView.scene.rootNode.childNode(withName: "shipMesh", recursively: true)
shipMesh?.geometry?.firstMaterial?.diffuse.contents = self.sceneView.scene.background.contents
}
}
I hope, this is what you were looking for.

How to make the clicks from one UIViewController pass into the parent UIViewController through the hole

I have UIViewController (OverlayViewController), which overlay another UIViewController(RootViewController).
In OverlayViewController I add UIView for all frame size:
var overlayView = new UIView(OverlayViewController.View.Frame);
overlayView.Alpha = 0.5f;
overlayView.BackgroundColor = UIColor.Blue;
overlayView.UserInteractionEnabled = false;
View.AddSubview(overlayView);
Then I make a hole:
var path = new CGPath();
var radius = 50.0f;
var xOffset = 100;
var yOffset = 300;
path.AddArc(overlayView.Frame.Width - radius / 2 - xOffset, yOffset, radius, 0.0f, (nfloat) (2 * 3.14), false);
var cgRect = new CGRect(0, 0, overlayView.Frame.Width, overlayView.Frame.Height);
path.AddRect(cgRect);
maskLayer.BackgroundColor = UIColor.Black.CGColor;
maskLayer.Path = path;
maskLayer.FillRule = CAShapeLayer.FillRuleEvenOdd;
overlayView.Layer.Mask = maskLayer;
overlayView.ClipsToBounds = true;
When I set overlayView.UserInteractionEnabled = false; I could touch through OverlayViewController and all clicks worked.
How I can properly use UITapGestureRecognizer so that clicks by items on the RootViewController work only inside the circle? And all the other clicks were blocked.
I tried set overlayView.UserInteractionEnabled = true;, but it doesn't help me:
tap.ShouldReceiveTouch += (recognizer, touch) =>
{
return !path.ContainsPoint(touch.LocationInView(overlayView), true);
};
View.AddGestureRecognizer(tap);
I use Xamarin, but I can understand the decision on Swift too.
I found the solution...
I make custom UIView which can set CGPath.
public class OverlayView : UIView
{
public OverlayView(CGRect frame): base(frame)
{}
private CGPath _path;
public override bool PointInside(CGPoint point, UIEvent uievent)
{
return _path.ContainsPoint(point, true);
}
public void SetPoint(CGPath path)
{
_path = path;
}
}
And set overlayView.SetPoint(path);.
In OverlayView I override PointInside and check my touch points. Also, I set overlayView.UserInteractionEnabled = true;.

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.

Move and animate UIImageView using swift

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")
}

Confused about NSImageView scaling

I'm trying to display a simple NSImageView with it's image centered without scaling it like this:
Just like iOS does when you set an UIView's contentMode = UIViewContentModeCenter
So I tried all NSImageScaling values, this is what I get when I chose NSScaleNone
I really don't understand what's going on :-/
You can manually generate the image of the correct size and content, and set it to be the image of the NSImageView so that NSImageView doesn't need to do anything.
NSImage *newImg = [self resizeImage:sourceImage size:newSize];
[aNSImageView setImage:newImg];
The following function resizes an image to fit the new size, keeping the aspect ratio intact. If the image is smaller than the new size, it is scaled up and filled with the new frame. If the image is larger than the new size, it is downsized, and filled with the new frame
- (NSImage*) resizeImage:(NSImage*)sourceImage size:(NSSize)size{
NSRect targetFrame = NSMakeRect(0, 0, size.width, size.height);
NSImage* targetImage = [[NSImage alloc] initWithSize:size];
NSSize sourceSize = [sourceImage size];
float ratioH = size.height/ sourceSize.height;
float ratioW = size.width / sourceSize.width;
NSRect cropRect = NSZeroRect;
if (ratioH >= ratioW) {
cropRect.size.width = floor (size.width / ratioH);
cropRect.size.height = sourceSize.height;
} else {
cropRect.size.width = sourceSize.width;
cropRect.size.height = floor(size.height / ratioW);
}
cropRect.origin.x = floor( (sourceSize.width - cropRect.size.width)/2 );
cropRect.origin.y = floor( (sourceSize.height - cropRect.size.height)/2 );
[targetImage lockFocus];
[sourceImage drawInRect:targetFrame
fromRect:cropRect //portion of source image to draw
operation:NSCompositeCopy //compositing operation
fraction:1.0 //alpha (transparency) value
respectFlipped:YES //coordinate system
hints:#{NSImageHintInterpolation:
[NSNumber numberWithInt:NSImageInterpolationLow]}];
[targetImage unlockFocus];
return targetImage;}
Here's an awesome category for NSImage: NSImage+ContentMode
It allows content modes like in iOS, works great.
Set image scaling property to NSImageScaleAxesIndependently which will scale image to fill rectangle.This will not preserve aspect ratio.
Swift version of #Shagru's answer (without the hints)
func resizeImage(_ sourceImage:NSImage, size:CGSize) -> NSImage
{
let targetFrame = CGRect(origin: CGPoint.zero, size: size);
let targetImage = NSImage.init(size: size)
let sourceSize = sourceImage.size
let ratioH = size.height / sourceSize.height;
let ratioW = size.width / sourceSize.width;
var cropRect = CGRect.zero;
if (ratioH >= ratioW) {
cropRect.size.width = floor (size.width / ratioH);
cropRect.size.height = sourceSize.height;
} else {
cropRect.size.width = sourceSize.width;
cropRect.size.height = floor(size.height / ratioW);
}
cropRect.origin.x = floor( (sourceSize.width - cropRect.size.width)/2 );
cropRect.origin.y = floor( (sourceSize.height - cropRect.size.height)/2 );
targetImage.lockFocus()
sourceImage.draw(in: targetFrame, from: cropRect, operation: .copy, fraction: 1.0, respectFlipped: true, hints: nil )
targetImage.unlockFocus()
return targetImage;
}

Resources