I'm working on a small piece of an app where I need a view to move to the opposite end of the screen when tapped. Once it collides with the screen it should to a horizontal flip (transform). And this works when it moves from left to right. But when it moves from right to left it does a vertical flip and is upside down until it collides with the left side of the screen. I can't figure out why it's doing this. Here's the relevant portion of my code.
class ViewController: UIViewController {
#IBOutlet weak var fish: UIImageView!
#IBOutlet weak var fishLeading: NSLayoutConstraint!
enum Direction: CGFloat {
case left = -1
case right = 1
func reversed() -> Direction {
return Direction(rawValue: -rawValue)!
}
}
var fishAnimator: UIDynamicAnimator?
var fishDirection = Direction.right
#objc func onFishTap(_ sender: UITapGestureRecognizer) {
let animator = UIDynamicAnimator(referenceView: water)
let collisionBehavior = UICollisionBehavior(items: [fish])
collisionBehavior.translatesReferenceBoundsIntoBoundary = true
collisionBehavior.collisionDelegate = self
collisionBehavior.collisionMode = .boundaries
animator.addBehavior(collisionBehavior)
let pushBehavior = UIPushBehavior(items: [fish], mode: .instantaneous)
pushBehavior.magnitude = 0.0000001
pushBehavior.pushDirection = .init(dx: fishDirection.rawValue, dy: 0)
animator.addBehavior(pushBehavior)
self.fishAnimator = animator
}
}
extension ViewController: UICollisionBehaviorDelegate {
func collisionBehavior(_ behavior: UICollisionBehavior, beganContactFor item: UIDynamicItem, withBoundaryIdentifier identifier: NSCopying?, at p: CGPoint) {
fishDirection = fishDirection.reversed()
UIView.animate(withDuration: 0.3, animations: {
self.fish.transform = .init(scaleX: self.fishDirection.rawValue, y: 1)
})
fishLeading.constant = fish.center.x - fish.frame.size.width / 2 + fishDirection.rawValue * 2
fishAnimator?.removeAllBehaviors()
fishAnimator = nil
}
}
As you can see in the first picture, when the fish is moving to the right it is right side up. When it is moving to the left it is upside down.
Related
Hi i'm making an app where you can decorate a Christmas tree with baubles i have wrote code to be able to drag it around the view but i run out of baubles how do i make it so i can have lots of baubles to put on the christmas tree. I have tried duplicating one bauble lots of times but then added them all to the code and it doesn't work it drags them all at the same time. If you look in the picture it has the bauble in it but i want it to drag one but one to still be there to put on
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var red_bauble_1: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let gesture = UIPanGestureRecognizer(target: self, action: #selector(ViewController.wasDragged(_ :)))
red_bauble_1.addGestureRecognizer(gesture)
red_bauble_1.isUserInteractionEnabled = true
}
#objc func wasDragged (_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: self.view)
let red_bauble_1 = gesture.view
red_bauble_1?.center = CGPoint(x: red_bauble_1!.center.x + translation.x, y: (red_bauble_1?.center.y)! + translation.y)
gesture.setTranslation(CGPoint.zero, in: self.view)
}
}
If you are adding a variable number of ornaments as subviews and want to keep a reference to them, just have an array for them, e.g.
var ornaments = [UIView]() // or, if they're image views, `[UIImageView]()`
then you can add your ornaments to that array as you add them to your tree and you have an array to keep track of all of them.
For example:
class ViewController: UIViewController {
#IBOutlet weak var treeImageView: UIImageView!
#IBOutlet weak var ornamentToolImageView: UIImageView!
private var currentOrnament: UIView!
private var ornaments = [UIView]()
override func viewDidLoad() {
super.viewDidLoad()
let pan = UIPanGestureRecognizer(target: self, action: #selector(dragOrnamentFromToolbar(_:)))
ornamentToolImageView.addGestureRecognizer(pan)
}
/// Gesture for dragging ornament from toolbar on to the tree
///
/// This creates new ornament and adds it to view hierarchy as well as to our array of `ornaments`
#IBAction func dragOrnamentFromToolbar(_ gesture: UIPanGestureRecognizer) {
let location = gesture.location(in: treeImageView)
switch gesture.state {
case .began:
gesture.view?.isHidden = true // temporarily hide toolbar view so it feels like we're dragging it
let image = UIImage(named: "ornament")!
currentOrnament = UIImageView(image: image)
ornaments.append(currentOrnament)
treeImageView.addSubview(currentOrnament)
fallthrough
case .changed:
currentOrnament.center = location
case .ended, .cancelled:
// when done, add new pan gesture for dragging around new ornament
gesture.view?.isHidden = false // restore toolbar view
currentOrnament.isUserInteractionEnabled = true
let pan = UIPanGestureRecognizer(target: self, action: #selector(dragExistingOrnament(_:)))
currentOrnament.addGestureRecognizer(pan)
currentOrnament = nil
default:
break
}
}
/// Gesture for dragging existing ornament
///
/// This grabs existing and allows you to drag it around
#IBAction func dragExistingOrnament(_ gesture: UIPanGestureRecognizer) {
let location = gesture.location(in: treeImageView)
switch gesture.state {
case .began:
currentOrnament = gesture.view
fallthrough
case .changed:
currentOrnament.center = location
default:
break
}
}
}
That yields:
I am very new to Xcode, so this could be something very basic that i'm just not finding, but i'm trying to create an app that moves 2 images away from each other (at least one width away) when the user taps the screen, and then have the images return to their original positions when the user taps the screen again, however, i have been unable to find out how to move an image in a specific direction, a specific distance.
i'm also new to Stack Overflow, so i'm sorry if i'm doing something wrong
I am breaking the rules by giving up the answer when it appears you have not googled enough. The general rule is to show the code you have attempted and indicate what part you are having issues with.
The code below will achieve what you are attempting to do.
import UIKit
class slidingIamgesViewController: UIViewController {
#IBOutlet weak var topImage: UIImageView!
#IBOutlet weak var bottomImage: UIImageView!
var doubleTap: Bool! = false
//MARK: View LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
let singleFingerTap = UITapGestureRecognizer(target: self, action: #selector(slidingIamgesViewController.handleSingleTap(_:)))
self.view.addGestureRecognizer(singleFingerTap)
}
// MARK: gestutre recognizer
func handleSingleTap(_ recognizer: UITapGestureRecognizer) {
if (doubleTap) {
UIView.animate(withDuration: 0.7, delay: 1.0, options: .curveEaseOut, animations: {
var basketTopFrame = self.topImage.frame
basketTopFrame.origin.y += basketTopFrame.size.height
var basketBottomFrame = self.bottomImage.frame
basketBottomFrame.origin.y -= basketBottomFrame.size.height
self.topImage.frame = basketTopFrame
self.bottomImage.frame = basketBottomFrame
}, completion: { finished in
print("Images Moved back!")
})
doubleTap = false
} else {
UIView.animate(withDuration: 0.7, delay: 1.0, options: .curveEaseOut, animations: {
var basketTopFrame = self.topImage.frame
basketTopFrame.origin.y -= basketTopFrame.size.height
var basketBottomFrame = self.bottomImage.frame
basketBottomFrame.origin.y += basketBottomFrame.size.height
self.topImage.frame = basketTopFrame
self.bottomImage.frame = basketBottomFrame
}, completion: { finished in
print("Images sperated!")
})
doubleTap = true
}
}
}
Make sure in your storyboard to added a Tap Gesture Recognizer.
I'm looking for the best way to invert the place of two UIView, with animation if possible (first i need to change the place, animation is optionnal).
viewController :
So, i want view1 to invert his place with the view2. The views are set with autolayout in the storyboard at the init.
if state == true {
view1 at the top
view2 at the bot
} else {
view 2 at the top
view 1 at the top
}
I've tried to take view.frame.(x, y, width, height) from the other view and set it to the view but it doesn't work.
I think the best way to do this would be to have a topConstraint for both views connected to the header and then change their values and animate the transition. You can do it in a way similar to this:
class MyViewController: UIViewController {
var view1TopConstraint: NSLayoutConstraint!
var view2TopConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
view1TopConstraint = view1.topAnchor.constraintEqualToAnchor(header.bottomAnchor, constant: 0)
view1TopConstraint.isActive = true
view2TopConstraint = view2.topAnchor.constraintEqualToAnchor(header.bottomAnchor, constant: view1.frame.height)
view2TopConstraint.isActive = true
}
func changeView2ToTop() {
UIView.animateWithDuration(0.2, animations: { //this will animate the change between 2 and 1 where 2 is at the top now
self.view2TopConstraint.constant = 0
self.view1TopConstraint.constant = view2.frame.height
//or knowing that view2.frame.height represents 30% of the total view frame you can do like this as well
// self.view1TopConstraint.constant = view.frame.height * 0.3
self.view.layoutIfNeeded()
})
}
You could also create the NSLayoutConstraint in storyboard and have an outlet instead of the variable I have created or set the top constraint for both views in storyboard at "remove at build time" if you are doing both. This way you won't have 2 top constraints and no constraint warrning
I Made an Example: https://dl.dropboxusercontent.com/u/24078452/MovingView.zip
I just created a Storyboard with 3 View, Header, View 1 (Red) and View 2 (Yellow). Then I added IBoutlets and animated them at viewDid Appear, here is the code:
import UIKit
class ViewController: UIViewController {
var position1: CGRect?
var position2: CGRect?
#IBOutlet weak var view1: UIView!
#IBOutlet weak var view2: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
position1 = view1.frame
position2 = view2.frame
UIView.animate(withDuration: 2.5) {
self.view1.frame = self.position2!
self.view2.frame = self.position1!
}
}
}
Hello #Makaille I have try to resolve your problem.
I have made an example, which will help you for your required implementation.
Check here: Source Code
I hope, it will going to help you.
#IBAction func toggle(_ sender: UIButton) {
sender.isUserInteractionEnabled = false
if(sender.isSelected){
let topPinConstantValue = layout_view2TopPin.constant
layout_view1TopPin.constant = topPinConstantValue
layout_view2TopPin.priority = 249
layout_view1BottomPin_to_View2Top.priority = 999
layout_view1TopPin.priority = 999
layout_view2BottomPin_ToView1Top.priority = 249
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
}, completion: { (value) in
if(value){
sender.isUserInteractionEnabled = true
}
})
} else {
let topPinConstantValue = layout_view1TopPin.constant
layout_view2TopPin.constant = topPinConstantValue
layout_view1TopPin.priority = 249
layout_view2BottomPin_ToView1Top.priority = 999
layout_view2TopPin.priority = 999
layout_view1BottomPin_to_View2Top.priority = 249
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
}, completion: { (value) in
if(value){
sender.isUserInteractionEnabled = true
}
})
}
sender.isSelected = !sender.isSelected
}
I have a UIScrollView setup but when I change view the music doesn't stop. How do I make it so the music stops when you change view?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear( animated)
meditationState = .on
setTrackForPlayerWith(trackName: "Bigsur")
player.play()
player.numberOfLoops = -1
}
Here is the whole class from viewcontroller.swift. I've added the func scrollViewDidScroll and the self part that you mentioned but it's still not working.
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
let vc0 = ViewController0(nibName: "ViewController0", bundle: nil)
var frame0 = vc0.view.frame
frame0.origin.x = self.view.frame.size.width
vc0.view.frame = frame0
self.addChildViewController(vc0)
self.scrollView.addSubview(vc0.view)
vc0.didMove(toParentViewController: self)
let vc1 = ViewController1(nibName: "ViewController1", bundle: nil)
var frame1 = vc1.view.frame
frame1.origin.x = self.view.frame.size.width
vc1.view.frame = frame1
self.addChildViewController(vc1)
self.scrollView.addSubview(vc1.view)
vc1.didMove(toParentViewController: self)
let vc2 = ViewController2(nibName: "ViewController2", bundle: nil)
var frame2 = vc2.view.frame
frame2.origin.x = self.view.frame.size.width * 2
vc2.view.frame = frame2
self.addChildViewController(vc2)
self.scrollView.addSubview(vc2.view)
vc2.didMove(toParentViewController: self)
let vc3 = ViewController3(nibName: "ViewController3", bundle: nil)
var frame3 = vc3.view.frame
frame3.origin.x = self.view.frame.size.width * 3
vc1.view.frame = frame3
self.addChildViewController(vc3)
self.scrollView.addSubview(vc3.view)
vc3.didMove(toParentViewController: self)
self.scrollView.contentSize = CGSize(width: Double(self.view.frame.size.width * 4), height: Double(self.view.frame.size.height - 66))
self.scrollView.delegate = self
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x > self.view.frame.size.x {
player.stop()
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
ViewController3 where 'player' is declared:
import UIKit
import AVFoundation
enum MeditationState {
case on
case off
}
class ViewController3: UIViewController {
var player:AVAudioPlayer = AVAudioPlayer()
var player1:AVAudioPlayer = AVAudioPlayer()
var meditationState: MeditationState?
var replicatorLayer = CAReplicatorLayer()
var dot = CALayer()
func updateTimer(){
seconds += 1
timerclock.text = "\(seconds)"
}
// Animation starts running
func animation2() {
// A layer that creates a specified number of copies of its sublayers (the source layer), each copy potentially having geometric, temporal, and color transformations applied to it.
replicatorLayer = CAReplicatorLayer()
// The layer’s bounds rectangle. Animatable.
replicatorLayer.bounds = CGRect(x: 0.0, y: 0.0, width: 300.0, height: 300.0)
// The radius to use when drawing rounded corners for the layer’s background. Animatable.
replicatorLayer.cornerRadius = 10.0
// The background color of the receiver. Animatable.
replicatorLayer.backgroundColor = UIColor(white: 0.0, alpha: 0.0).cgColor
// The layer’s position in its superlayer’s coordinate space. Animatable.
replicatorLayer.position = view.center
// calling this method creates an array for that property and adds the specified layer to it.
view.layer.addSublayer(replicatorLayer)
// connectng the animation to the content
// An object that manages image-based content and allows you to perform animations on that content
dot = CALayer()
// The layer’s bounds rectangle. Animatable.
dot.bounds = CGRect(x: 0.0, y: 0.0, width: 12.0, height: 12.0)
//The layer’s position in its superlayer’s coordinate space. Animatable.
dot.position = CGPoint(x: 150.0, y: 40.0)
//The background color of the receiver. Animatable.
dot.backgroundColor = UIColor(white: 0.2, alpha: 1.0).cgColor
// The color of the layer’s border. Animatable.
dot.borderColor = UIColor(white: 1.0, alpha: 1.0).cgColor
// The width of the layer’s border. Animatable.
dot.borderWidth = 1.0
//The radius to use when drawing rounded corners for the layer’s background. Animatable.
dot.cornerRadius = 5.0
//Appends the layer to the layer’s list of sublayers.
replicatorLayer.addSublayer(dot)
// number of copies of layer is instanceCount
let nrDots: Int = 1000
//The number of copies to create, including the source layers.
replicatorLayer.instanceCount = nrDots
// The basic type for floating-point scalar values in Core Graphics and related frameworks.
let angle = CGFloat(2*M_PI) / CGFloat(nrDots)
// The transform matrix applied to the previous instance to produce the current instance. Animatable.
replicatorLayer.instanceTransform = CATransform3DMakeRotation(angle, 0.0, 0.0, 1.0)
// Type used to represent elapsed time in seconds.
let duration: CFTimeInterval = 10.0
// animation capabilities for a layer property.
// An object that provides basic, single-keyframe animation capabilities for a layer property.
let shrink = CABasicAnimation(keyPath: "transform.scale")
// Defines the value the receiver uses to start interpolation.
shrink.fromValue = 1.0
// Defines the value the receiver uses to end interpolation.
shrink.toValue = 0.1
// Specifies the basic duration of the animation, in seconds.
shrink.duration = duration
// Determines the number of times the animation will repeat.
shrink.repeatCount = Float.infinity
// Add the specified animation object to the layer’s render tree.
dot.add(shrink, forKey: "shrink")
// Specifies the delay, in seconds, between replicated copies. Animatable.
replicatorLayer.instanceDelay = duration/Double(nrDots)
// The transform applied to the layer’s contents. Animatable.
dot.transform = CATransform3DMakeScale(0.01, 0.01, 0.01)
}
// connecting the breathe in label
#IBOutlet weak var label: UILabel!
// instant delay
#IBOutlet weak var instantDelay: UIButton!
#IBAction func delayBtn(_ sender: Any) {
dot.removeAnimation(forKey: "shrink")
timer1.invalidate()
seconds = 0
timer2.invalidate()
timerclock.text = "\(seconds)"
time = 0
timerLabel.text = "Breathe in"
timerisOn = false
pauseBtn.isHidden = true
playBtn.isHidden = false
label.isHidden = true
replicatorLayer.isHidden = true
instantDelay.isHidden = true
instantDelay1.isHidden = false
slider.isHidden = false
}
// Delay 1
#IBOutlet weak var instantDelay1: UIButton!
#IBAction func delayBtn1(_ sender: Any) {
instantDelay1.isHidden = true
instantDelay.isHidden = false
label.isHidden = false
slider.isHidden = true
}
//Slider for changing animation speed
#IBOutlet weak var slider: UISlider!
#IBAction func slider(_ sender: Any) {
}
#IBAction func speed(_ sender: UISlider) {
view.layer.speed = sender.value
}
//Sound On button
#IBOutlet weak var soundOn: UIButton!
#IBAction func SoundOn(_ sender: Any) {
meditationState = .on
setTrackForPlayerWith(trackName: "Mute")
player.play()
soundoff.isHidden = false
soundOn.isHidden = true
}
//Sound Off button
#IBOutlet weak var soundoff: UIButton!
#IBAction func SoundOff(_ sender: Any) {
meditationState = .off
setTrackForPlayerWith(trackName: "Bigsur")
player.play()
soundoff.isHidden = true
soundOn.isHidden = false
}
//Timerclock at top of screen label
#IBOutlet weak var timerclock: UILabel!
// creating vars to set things
var animation = CFTimeInterval()
var timer1 = Timer()
var timer2 = Timer()
var time = 0
var seconds = 0
var timerisOn = false
// connecting breathe in label
#IBOutlet var question: UILabel!
var arrayOfStrings: [String] = [""]
// connecting timerclick and starting it
#IBOutlet var timerLabel: UILabel!
// changes the amount of time on the label of different labels
func increaseTimer() {
time += 1
switch time {
case 0 ... 7:
timerLabel.text = "Hold"
case 8 ... 10:
timerLabel.text = "Breathe Out"
case 11 ... 12:
timerLabel.text = "Breathe in"
default:
time = 0
}
}
// connecting the play button and vars
#IBOutlet weak var playBtn: UIButton!
#IBAction func play(sender: AnyObject) {
bell(trackName: "Bell")
player1.play()
timer1 = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController3.increaseTimer), userInfo: nil, repeats: true)
pauseBtn.isHidden = false
playBtn.isHidden = true
if timerisOn == false {
timer2 = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
timerisOn = true
}
animation2()
}
// pausing the timer with the vars
#IBOutlet weak var pauseBtn: UIButton!
#IBAction func pause(sender: AnyObject) {
dot.removeAnimation(forKey: "shrink")
timer1.invalidate()
seconds = 0
timer2.invalidate()
timerclock.text = "\(seconds)"
time = 0
timerLabel.text = "Breathe in"
timerisOn = false
pauseBtn.isHidden = true
playBtn.isHidden = false
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear( animated)
meditationState = .on
setTrackForPlayerWith(trackName: "Bigsur")
player.play()
player.numberOfLoops = -1
}
override func viewDidLoad() {
super.viewDidLoad()
time += 1
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient)
print("AVAudioSession Category Playback OK")
do {
try AVAudioSession.sharedInstance().setActive(true)
print("AVAudioSession is Active")
} catch let error as NSError {
print(error.localizedDescription)
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
func setTrackForPlayerWith(trackName: String) {
do
{
let audioPath = Bundle.main.path(forResource: trackName, ofType: "mp3")
try player = AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: audioPath!) as URL)
}
catch
{
//ERROR
}
}
func bell(trackName: String) {
do
{
let audioPath = Bundle.main.path(forResource: trackName, ofType: "mp3")
try player1 = AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: audioPath!) as URL)
}
catch
{
//ERROR
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Are you looking for viewWillDisappear(_:)? In that method you can just add player.stop() to stop the music when you change views.
The smartass way to do this is just ....
let's say it's a vertical table.
1. Each table view cell has an audio track associated with it. Think of one particular cell - C - it has an audio track A.
2. As the view scrolls (ie, whenever it is moving), just get the frame of C
3. Just take the height. Then take the height of the screen SH. Then get the distance of C from the center of the screen .. so Abs(SH - H). Then just get that figure as a fraction (zero to one) of the SH. So, Abs(SH - H)/H
(Depending on your situation, it may be better if that is divided by the height of cells, rather than screen height.)
4. Now ... simply set the volume of all the audio track A, to that fraction. And in fact, simply do that for every cell.
As you scroll, the audio will mix between the various tracks.
"Magic" :)
You can observe the changes made in your UIScrollView subclass in various delegate methods.
First of all, lets make sure your scrollView's delegate is assigned to the viewController. To do so, one option is to add following to viewDidLoad()
// `scrollView` should be whatever is your scrollView called in your VC
self.scrollView.delegate = self
Once this is done, lets make your UIViewController subclass conform to UIScrollViewDelegate
class ViewController: UIViewController, UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x > self.view.frame.size.x {
player.stop()
}
}
}
UIScrollViewDelegate has numerous methods to observe changes in your scrollView. scrollViewDidScroll(_:) will be called every time there is an interaction with the scrollView, so as soon as the contentOffset is greater than the width of the view, lets stop the music.
whenever I click a textfield inside the view, then click the other text field, the view disappears. Strange... Can anyone help?
I animate the view using facebook pop. Here is my animation engine code:
import UIKit
import pop
class AnimationEngine {
class var offScreenRightPosition: CGPoint {
return CGPoint(x: UIScreen.main.bounds.width + 250,y: UIScreen.main.bounds.midY - 75)
}
class var offScreenLeftPosition: CGPoint{
return CGPoint(x: -UIScreen.main.bounds.width,y: UIScreen.main.bounds.midY - 75)
}
class var offScreenTopPosition: CGPoint{
return CGPoint(x: UIScreen.main.bounds.midX,y: -UIScreen.main.bounds.midY)
}
class var screenCenterPosition: CGPoint {
return CGPoint(x: UIScreen.main.bounds.midX, y: UIScreen.main.bounds.midY - 75)
}
let ANIM_DELAY : Int = 1
var originalConstants = [CGFloat]()
var constraints: [NSLayoutConstraint]!
init(constraints: [NSLayoutConstraint]) {
for con in constraints {
originalConstants.append(con.constant)
con.constant = AnimationEngine.offScreenRightPosition.x
}
self.constraints = constraints
}
func animateOnScreen(_ delay: Int) {
let time = DispatchTime.now() + Double(Int64(Double(delay) * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: time) {
var index = 0
repeat {
let moveAnim = POPSpringAnimation(propertyNamed: kPOPLayoutConstraintConstant)
moveAnim?.toValue = self.originalConstants[index]
moveAnim?.springBounciness = 8
moveAnim?.springSpeed = 8
if (index < 0) {
moveAnim?.dynamicsFriction += 10 + CGFloat(index)
}
let con = self.constraints[index]
con.pop_add(moveAnim, forKey: "moveOnScreen")
index += 1
} while (index < self.constraints.count)
}
}
class func animateToPosisition(_ view: UIView, position: CGPoint, completion: ((POPAnimation?, Bool) -> Void)!) {
let moveAnim = POPSpringAnimation(propertyNamed: kPOPLayerPosition)
moveAnim?.toValue = NSValue(cgPoint: position)
moveAnim?.springBounciness = 8
moveAnim?.springSpeed = 8
moveAnim?.completionBlock = completion
view.pop_add(moveAnim, forKey: "moveToPosition")
}
}
Then here is my viewcontroller code where the view is inside in:
import UIKit
import pop
class LoginVC: UIViewController, UITextFieldDelegate {
override var prefersStatusBarHidden: Bool {
return true
}
#IBOutlet weak var emailLoginVCViewConstraint: NSLayoutConstraint!
#IBOutlet weak var emailLoginVCView: MaterialView!
#IBOutlet weak var emailAddressTextField: TextFieldExtension!
#IBOutlet weak var passwordTextField: TextFieldExtension!
var animEngine : AnimationEngine!
override func viewDidAppear(_ animated: Bool) {
self.emailLoginVCView.isUserInteractionEnabled = true
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.bringSubview(toFront: emailAddressTextField)
self.animEngine = AnimationEngine(constraints: [emailLoginVCViewConstraint])
self.emailAddressTextField.delegate = self
self.passwordTextField.delegate = self
emailAddressTextField.allowsEditingTextAttributes = false
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if (textField === emailAddressTextField) {
passwordTextField.becomeFirstResponder()
} else if (textField === passwordTextField) {
passwordTextField.resignFirstResponder()
} else {
// etc
}
return true
}
#IBAction func emailTapped(_ sender: AnyObject) {
AnimationEngine.animateToPosisition(emailLoginVCView, position: AnimationEngine.screenCenterPosition, completion: { (POPAnimation, Bool)
in
})
}
#IBAction func exitTapped(_ sender: AnyObject) {
AnimationEngine.animateToPosisition(emailLoginVCView, position: AnimationEngine.offScreenRightPosition, completion: { (POPAnimation, Bool)
in
})
}
}
Last here is my hierchy and options: (my view's name is emailLoginVCView). Also when I was debugging when I clicked another textfield I set a breakpoint so I got this info: enter image description here
I have a constraint that binds the center of the login view with the center of the main screen
when I create the AnimationEngine,I pass it that constraint, and it sets its constant to be the offScreenRightPosition.x
when I bring up the email login sheet, I'm not changing the constant of the constraint; I'm just changing the position of the view
which means that autolayout thinks it’s supposed to still be offscreen
when the second textfield becomes active, that’s somehow triggering auto-layout to re-evaluate the constraints, and it sees that the login view’s position doesn’t match what the constraint says it should be so....
Autolayout moves it offscreen
So if I add this in emailTapped(_:), the problem goes away :)
#IBAction func emailTapped(_ sender: AnyObject) {
AnimationEngine.animateToPosisition(emailLoginVCView, position: AnimationEngine.screenCenterPosition, completion: { (POPAnimation, Bool)
in
self.emailLoginVCViewConstraint.constant = 0
})
}