Transform collection view cell container after Swipe to right and left - swift

I'm trying to add Swipe to right and left to collection view cell to transform the container to the right and left with a certain angle
Here is my initial setup
private func setupGestures() {
let swipeToRight = UISwipeGestureRecognizer(target: self, action: #selector(respondToSwipeRight))
swipeToRight.direction = .right
container.addGestureRecognizer(swipeToRight)
let swipeToLeft = UISwipeGestureRecognizer(target: self, action: #selector(respondToSwipeLeft))
swipeToLeft.direction = .left
container.addGestureRecognizer(swipeToLeft)
}
#objc func respondToSwipeRight(gesture: UIGestureRecognizer) {
let angle = CGFloat(Double.pi/2)
UIView.animate(withDuration: 0.5) {
self.container.transform = CGAffineTransform(rotationAngle: angle)
}
}
#objc func respondToSwipeLeft(gesture: UIGestureRecognizer) {
let angle = -CGFloat(Double.pi/2)
UIView.animate(withDuration: 0.5) {
self.container.transform = CGAffineTransform(rotationAngle: angle)
}
}
But it completely rotate the container, which is I don't want, I want to make it something like it, and turn back to it's initial position after a sec or two
[![how it should transform][1]][1]
And it would be so awesome that move based on Swiping position, I mean not automatically goes to that level of position, move with finger tip and when it reach there, just stop moving.
Could anyone help me to implement it, I have no idea how I can do it
Many thanks

Adding completion in UIView.animate in order to turn back to initial state. After a sec or two is depends on your duration in UIView.animate
Code will be like this
UIView.animate(withDuration: 0.5, animations: {
self.container.transform = CGAffineTransform(rotationAngle: 0.5)
}, completion: {
_ in
// turn back to the previous before transform
self.container.transform = .identity
})
And for your second question, you can implement with touchesBegan and touchesMoved or add PanGesture to handle changing position container view frame like you want.

Related

View animating from the incorrect position

I am trying to animate a cover view in my code. Essentially the cover slides up (closes) from off-screen to on-screen, and after the user presses a button the cover slides down (opens up) all the way off-screen.
My code to slide the cover view up the screen and into view is working perfectly. However, the code to slide the cover view down the screen and out of view is not working! The starting position for the cover sliding down is what seems to be incorrect. The cover slides down as expected, but starts far too high, such that the animation ends where it should start.
I've got a screen recording of the error in action (obviously slowed the animation down so you can see what I mean by the error). The cover sliding down should be sliding down from its original position where it slid up to.
Link to recording.
Can anyone spot where I'm going wrong in my code?
func showScoresCover() {
verticalDistance = ScoresCoverView.frame.size.height
self.ScoresCoverView.frame.origin.y += verticalDistance
UIView.animate(
withDuration: 0.5,
delay: 0.25,
options: .curveEaseOut,
animations:
{ self.ScoresCoverView.frame.origin.y -= self.verticalDistance},
completion:
{ finished in
print("Score cover closed.")
})
}
func hideScoresCover() {
verticalDistance = ScoresCoverView.frame.size.height
UIView.animate(
withDuration: 0.5,
delay: 0.25,
options: .curveEaseIn,
animations:
{ self.ScoresCoverView.frame.origin.y += self.verticalDistance},
completion:
{ finished in
print("Score cover opened.")
})
}
EDIT
hey #Nickolans thanks for your help on this issue. So... I have a bizarre update for you on this query. I implemented your code, and it works if I have a pre-programmed button which fires the showScoreCover and hideScoresCover. Turns out even my original code works, but again, only if I have a pre-programmed button which fires the show/hide functions.
However, I need to fire the function using a button where I programmatically reconfigure what the button does. How I've implemented this is through .addTarget and a number of selectors, and those selectors will differ based on where in the game the users are.
Anyway, this is what my code looks like:
self.continueButton.addTarget(self, action: #selector(self.hideScoresCover), for: UIControlEvents.touchUpInside)
self.continueButton.addTarget(self, action: #selector(self.startTeamB), for: UIControlEvents.touchUpInside)
Originally I only used one .addTarget and included the hideScoresCover() in the startTeamB function. This was resulting in the original issue where the cover doesn't hide correctly (starts too high on the screen). So I split it up into the above two .addTargets, and that also results in the same issue. HOWEVER, if I comment out the entire .addTarget line above related to the startTeamB selector and leave the selector related to hideScoresCover, then the cover is shown and is hidden absolutely as I want to to be, the animation is perfect. This means the underlying code in hideScoresCover() is working fine, but that the issue is arising somehow in how hideScoresCover is being run when the continue button is pressed.
Do you have any idea why this would be? somehow that continueButton target is firing incorrectly whenever there are two addTargets, or when there is only one target but hideScoresCover() is included in startTeamB().
I'm stumped as to why this would be the case. startTeamB() is not a complex function at all, so shouldn't be interfering with hideScoresCover(). So this leads me only to conclude it must be the way the continueButton fires hideScoresCover() when its either a separate target in addition to startTeamB being a target, or when hideScoresCover() is included in startTeamB itself. Unusual that it does work correctly when the only .addTarget is a selector calling hidesScoresCover?
#objc func startTeamB() {
self.UpdateTeam() //Just changes the team name
self.TeamBTimer() //Starts a timer to countdown teamB's remaining time
self.TeamAIndicator.isHidden = true //Just hides teamA image
self.TeamBIndicator.isHidden = false //Just shows teamB image
print("Hintify: ","-----Start Team B.")
}
I took it into Xcode and set it up myself. I believe the issue may be how you're changing your Y value, instead of changing the origin change the view layers y position.
The following code works so you can just take it! Make sure to change the frame to whatever you need it to be when you first start.
class ViewController: UIViewController {
//Bool to keep track if view is open
var isOpen = false
var verticalDistance: CGFloat = 0
var ScoresCoverView = UIView()
var button = UIButton()
override func viewDidLoad() {
//Setup Score view
ScoresCoverView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.width)
ScoresCoverView.backgroundColor = .blue
view.addSubview(ScoresCoverView)
//Set vertical distance
verticalDistance = ScoresCoverView.frame.height
//TEST button to test view animations
button.frame = CGRect(x: 200, y: 500, width: 100, height: 100)
button.backgroundColor = .purple
button.addTarget(self, action: #selector(hasBeenFired), for: .touchUpInside)
self.view.addSubview(button)
}
//When button pressed
#objc func hasBeenFired() {
if isOpen {
self.hideScoresCover()
self.isOpen = false
} else {
self.showScoresCover()
self.isOpen = true
}
}
//Show
func showScoresCover() {
self.ScoresCoverView.isHidden = false
UIView.animate(withDuration: 0.5, delay: 0.25, options: .curveEaseOut, animations: {
self.ScoresCoverView.layer.position.y -= self.verticalDistance
}) { (finished) in
print("Score cover closed.")
}
}
//Hide
func hideScoresCover() {
UIView.animate(withDuration: 0.5, delay: 0.25, options: .curveEaseIn, animations: {
self.ScoresCoverView.layer.position.y += self.verticalDistance
}) { (finished) in
self.ScoresCoverView.isHidden = true
print("Score cover Opened.")
}
}
}
EDIT
If you'd like to change the functionality of the button, make sure to remove the previous target before adding a new one.
For example, if you want to switch from show/hide to teamB remove show/hide and add teamB-- and vice versa.
When you first start, add your initial target:
self.continueButton.addTarget(self, action: #selector(self.hideScoresCover), for: UIControlEvents.touchUpInside)
Switch to startTeamB:
self.continueButton.removeTarget(self, action: #selector(self.hideScoresCover), for: UIControlEvents.touchUpInside)
self.continueButton.addTarget(self, action: #selector(self.startTeamB), for: UIControlEvents.touchUpInside)
Switch to hideScoreCover:
self.continueButton.removeTarget(self, action: #selector(self.startTeamB), for: UIControlEvents.touchUpInside)
self.continueButton.addTarget(self, action: #selector(self.hideScoresCover), for: UIControlEvents.touchUpInside)
I just want to say one thing, maybe the += is not necessary here.
func showScoresCover() {
verticalDistance = ScoresCoverView.frame.size.height
self.ScoresCoverView.frame.origin.y += verticalDistance
UIView.animate(
withDuration: 0.5,
delay: 0.25,
options: .curveEaseOut,
animations:
{ self.ScoresCoverView.frame.origin.y -= self.verticalDistance},
completion:
{ finished in
print("Score cover closed.")
})
}
should be
`self.ScoresCoverView.frame.origin.y = verticalDistance`

How can i interact with views while they keep animating in UIKit (swift 4)?

I have a set of shapes (UIViews), that can be moved on the screen using a UIPanGestureRecognizer, and that are supposed to wiggle continuously and forever after the user triggers the animation. But I want them to keep wiggling, even while the user picks one up and moves it.
What is practically happening is that when the user picks up a shape, it stops animating, and when he puts it back down, it doesn't even restart the animation.
Here's some code for the context:
func wiggle() {
UIView.animate(withDuration: 0.15, options: [.repeat, .autoreverse, .allowUserInteraction], animation: {
self.view.subviews.forEach { $0.transform = CGAffineTransform(rotationAngle: CGFloat.pi/8) })
}
func pickUpShape() {
[.....]
shape.transform = CGAffineTransform(scaleX: 2, scaleY: 2)
shape.layer.shadowOpacity = 0.7
}
func moveShape() {
//apply UIPanGestureRecognizer
}
func placeShape() {
[.....]
shape.transform = .identity
shape.layer.shadowOpacity = 0
}
What I want it to do is, if the user triggers the wiggle action, and picks a shape up and moves it, i want the shape to continue wiggling under the user's finger, and after he places it down.

Textfield frame animation is not in sync with keyboard animation duration

I have a text field and I am able to move it up a with keyboard. However, the animation is not in sync. The textfield frame moves up about .5 second ahead of keyboard duration. I think they should be in sync but I'm not able to figure out the problem. Below is my keyboard function animation.
Notification observer:
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardWillShow), name: .UIKeyboardWillShow, object: nil)
Animation func:
#objc func handleKeyboardWillShow(notification: NSNotification){
if let keyboardFrame: NSValue = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue, let keyboardDuration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? Double, let tabBarHeight = tabBarController?.tabBar.frame.size.height{
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
UIView.animate(withDuration: keyboardDuration, animations: {
self.textfieldBottomeConstraint?.constant = -keyboardHeight + tabBarHeight
self.view.layoutIfNeeded()
})
}
}
I did try this but still get the same results:
self.textfieldBottomeConstraint?.constant = -keyboardHeight + tabBarHeight
UIView.animate(withDuration: keyboardDuration, animations: {
self.view.layoutIfNeeded()
})
Two things to try:
Try using UIKeyboardWillChangeFrame instead of UIKeyboardWillShow
If it does not change anything, try using the keyboard curve inside your animation.
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationCurve(context.animationCurve)
UIView.setAnimationDuration(context.animationDuration)
where curve and duration are
public var animationCurve: UIViewAnimationCurve {
let value = userInfo[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber
return UIViewAnimationCurve(rawValue: value.intValue)!
}
public var animationDuration: Double {
return userInfo[UIKeyboardAnimationDurationUserInfoKey] as! Double
}
The keyboard notification also includes a
UIKeyboardAnimationCurveUserInfoKey and a UIKeyboardAnimationDurationUserInfoKey. You can interrogate those values to make sure your animation uses the same duration and animation curve that the keyboard uses. (Search on "Keyboard Notification User Info Keys" in the Xcode help system to find all the different key/value pairs provided in keyboard notifications.

What is the best way to zoom and deplace nodes?

I'm working with Swift 3, SpriteKit and Xcode.
So I have a node named backgroundNode, and I attach every nodes of my game to this backgroundNode.
Now I would like to be able to zoom on my game with a pinch gesture, and when I'm zoomed in to navigate in my game.
I see 2 possibilities to do this :
deplace the background node and change its scale to zoom in and zoom out,
use SKCameraNode
What do you think is the best option ?
I already tried the first option but the zoom gesture is quite complex as if I scale the backgroundNode up when I want to zoom, the anchor point is in 0;0 and not 0.5;0.5 so it doesnt zoom where the pinch gesture is detected, but from the bottom right corner, I don't know if you see what I mean.
And for the second option, I can't manage to move the camera without having a glitchy effect, maybe my code is wrong but it really seems correct.
Can you help me ?
Edit : So I got it working using SKCameraNode and UIPanGestureRecognizer, here is the code :
var cam: SKCameraNode!
let panGesture = UIPanGestureRecognizer()
override func didMove(to view: SKView)
{
cam = SKCameraNode()
camera = cam
cam.position = CGPoint(x: playableRect.midWidth, y: playableRect.midHeight)
addChild(cam)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(GameScene.panFunction))
view.addGestureRecognizer(panGesture)
}
func panFunction(pan : UIPanGestureRecognizer)
{
let deltaX = pan.translation(in: view).x
let deltaY = pan.translation(in: view).y
cam.position.x -= deltaX
cam.position.y += deltaY
pan.setTranslation(CGPoint(x: 0, y: 0), in: view)
}
Now I'm struggling with the Zoom. I tried using UIPinchGestureRecognizer but it doesn't work as good as the pan gesture, here is what I tried :
var firstPinch: CGFloat = 0
var pinchGesture = UIPinchGestureRecognizer()
let panGesture = UIPanGestureRecognizer()
var cam: SKCameraNode!
override func didMove(to view: SKView)
{
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(GameScene.pinchFunction))
view.addGestureRecognizer(pinchGesture)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(GameScene.panFunction))
view.addGestureRecognizer(panGesture)
}
func pinchFunction(pinch : UIPinchGestureRecognizer)
{
if UIGestureRecognizerState.began == pinch.state
{
firstPinch = pinch.scale
}
actualPinch = pinch.scale
cam.xScale -= actualPinch - firstPinch
cam.yScale -= actualPinch - firstPinch
}
How would you do it ?
You need to post your code. I helped someone with this in another forum. Their code + my answer should give a general idea of what to do:
https://forums.developer.apple.com/message/192823#192823
Basically it involves UIPanGestureRecognizer etc, and then a bit of delta-scale logic to adjust for the new bounds of the camera.

How to drag a SKSpriteNode without touching it with Swift

I'm trying to move SKSpriteNode horizontally by dragging. To get the idea of what I try to achieve you can watch this. I want player to be able to drag sprite without touching it. But I don't really know how to implement it correctly.
I tried to do something like:
override func didMoveToView(view: SKView) {
/* Setup your scene here */
capLeft.size = self.capLeft.size
self.capLeft.position = CGPointMake(CGRectGetMinX(self.frame) + self.capLeft.size.height * 2, CGRectGetMinY(self.frame) + self.capLeft.size.height * 1.5)
capLeft.zPosition = 1
self.addChild(capLeft)
let panLeftCap: UIPanGestureRecognizer = UIPanGestureRecognizer(target: capLeft, action: Selector("moveLeftCap:"))
And when I'm setting a moveLeftCap function, code that I've found for UIPanGestureRecognizer is requiring "View" and gives me an error. I also wanted to limit min and max positions of a sprite through which it shouldn't go.
Any ideas how to implement that?
You probably get that error because you can't just access the view from any node in the tree. You could to refer to it as scene!.view or you handle the gesture within you scene instead which is preferable if you want to keep things simple.
I gave it a try and came up with this basic scene:
import SpriteKit
class GameScene: SKScene {
var shape:SKNode!
override func didMoveToView(view: SKView) {
//creates the shape to be moved
shape = SKShapeNode(circleOfRadius: 30.0)
shape.position = CGPointMake(frame.midX, frame.midY)
addChild(shape)
//sets up gesture recognizer
let pan = UIPanGestureRecognizer(target: self, action: "panned:")
view.addGestureRecognizer(pan)
}
var previousTranslateX:CGFloat = 0.0
func panned (sender:UIPanGestureRecognizer) {
//retrieve pan movement along the x-axis of the view since the gesture began
let currentTranslateX = sender.translationInView(view!).x
//calculate translation since last measurement
let translateX = currentTranslateX - previousTranslateX
//move shape within frame boundaries
let newShapeX = shape.position.x + translateX
if newShapeX < frame.maxX && newShapeX > frame.minX {
shape.position = CGPointMake(shape.position.x + translateX, shape.position.y)
}
//(re-)set previous measurement
if sender.state == .Ended {
previousTranslateX = 0
} else {
previousTranslateX = currentTranslateX
}
}
}
when you move you finger across the screen, the circle gets moves along the x-axis accordingly.
if you want to move the sprite in both x and y directions, remember to invert the y-values from the view (up in view is down in scene).