Swift remove UIview immediately and show animation duration 1sec at another place - ios

i want to remove UIView from superview immediately and show that view at another place by animation
i try this
mView.removeFromSuperview()
mView.frame = CGRect.init(x: 0, y: 0, width: 121, height: 58)
self.view.addSubview(mView)
UIView.transition(with: self.view, duration: 1, options: [.transitionCrossDissolve], animations: {
self.mView.alpha = 1
}, completion: nil)
this code will remove mView and add mView at the same time duration 1 sec
but i need remove mView immediately
maybe i need update my self.view after mView.removeFromSuperview() ?
thanks
i tried this
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
self.infoWindow.alpha = 0
self.view.layoutIfNeeded()
let data = marker.userData as! [String : Any]
let location = CLLocationCoordinate2D(latitude: (data["latitude"] as! Double), longitude: (data["longitude"] as! Double))
self.infoWindow = CustomGMSInfoWindow(frame: CGRect.init(x: 0, y: 0, width: 121, height: 58))
self.infoWindow.center = mapView.projection.point(for: location)
self.infoWindow.center.y = (infoWindow.center.y) - 65
self.infoWindow.frame = CGRect(infoWindow.frame.origin.x, infoWindow.frame.origin.y, infoWindow.frame.width, infoWindow.frame.height)
UIView.animate(withDuration: 1.0) {
self.infoWindow.alpha = 1.0
}
return false
}

There's no reason to remove / re-add mView. All you need to do is set its .alpha to Zero, change its frame, and then animate the .alpha back to 1 (will cause it to "fade in".
// set alpha to Zero (invisible)
self.mView.alpha = 0.0
// set its new frame
self.mView.frame = CGRect(x: 0, y: 0, width: 121, height: 58)
// "fade in"
UIView.animate(withDuration: 1.0) {
self.mView.alpha = 1.0
}
Here's a complete example. Each time you tap, the red mView will disappear and then "fade in" at a random location:
class ViewController: UIViewController {
let mView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
mView.backgroundColor = .red
mView.frame = CGRect(x: 100, y: 200, width: 121, height: 58)
view.addSubview(mView)
let tap = UITapGestureRecognizer(target: self, action: #selector(self.doAnim))
view.addGestureRecognizer(tap)
}
#objc func doAnim() -> Void {
let x = CGFloat.random(in: 0...(view.frame.size.width - 121))
let y = CGFloat.random(in: 0...(view.frame.size.height - 58))
// set alpha to Zero (invisible)
self.mView.alpha = 0.0
// set its new frame
self.mView.frame = CGRect(x: x, y: y, width: 121, height: 58)
// "fade in"
UIView.animate(withDuration: 1.0) {
self.mView.alpha = 1.0
}
}
}
Edit: the original question posted was not the actual question
Here is your posted code, with a couple changes. Read through the comments:
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
// (1) - if you are creating a NEW view at (3),
// remove this view first
//self.infoWindow.alpha = 0
self.infoWindow.removeFromSuperview()
// (2) - not needed
//self.view.layoutIfNeeded()
let data = marker.userData as! [String : Any]
let location = CLLocationCoordinate2D(latitude: (data["latitude"] as! Double), longitude: (data["longitude"] as! Double))
// (3) - create a NEW view
self.infoWindow = CustomGMSInfoWindow(frame: CGRect.init(x: 0, y: 0, width: 121, height: 58))
self.infoWindow.center = mapView.projection.point(for: location)
self.infoWindow.center.y = (infoWindow.center.y) - 65
// (4) - this is setting the frame to itself - you don't need this
self.infoWindow.frame = CGRect(infoWindow.frame.origin.x, infoWindow.frame.origin.y, infoWindow.frame.width, infoWindow.frame.height)
// (5) - you created a NEW view, so you need to set its alpha here
self.infoWindow.alpha = 0.0
// (6) - and you need to add it as a subview... I'm assuming it needs to be added to the mapView
mapView.addSubview(self.infoWindow)
// (7) if this doesn't fade in the new view...
UIView.animate(withDuration: 1.0) {
self.infoWindow.alpha = 1.0
}
// (8) try it like this - wait 5/100ths of a second before starting the fade-in
UIView.animate(withDuration: 1.0, delay: 0.05, animations: {
self.infoWindow.alpha = 1.0
})
return false
}

Run after your animation:
self.view.layoutIfNeeded()

Related

How to combine translate and rotate transform animation in Swift(iOS)?

I created a small UIView object, First I use translate transform move UIView to new location, after animation stop, I want UIView rotate in new location, so the code is:
//circle2 is UIView
UIView.animate(withDuration: 1, delay:0,options:[.curveLinear],animations:{
self.circle2.transform=CGAffineTransform(translationX: 0, y: 35)
},completion: {(result) in
UIView.animate(withDuration: 1, delay:0,options:[.repeat],animations: {
self.circle2.transform=CGAffineTransform(rotationAngle: CGFloat.pi)
//self.circle2.transform=CGAffineTransform(rotationAngle: CGFloat.pi).translatedBy(x: 0, y: 35)
//self.circle2.transform=CGAffineTransform(translationX: 0, y: 35).rotated(by: CGFloat.pi)
//self.circle2.transform=CGAffineTransform(rotationAngle: CGFloat.pi).concatenating(CGAffineTransform(translationX: 0, y: 35))
})
})
But I found that when UIView is rotating, it also move upward to origin position.
I try three another combination method, none of then works....
Two of your examples do work but animation may be different than what you expected. You will need to do it manually. It is painful but please try and examine the following code:
private func startAnimation()
let startTime: Date = .init()
let translationDuration: TimeInterval = 1.0
var rotation: CGFloat = 0.0
let rotationDuration: TimeInterval = 1.0
var translation: CGPoint = .zero
func refreshView(_ controller: ViewController) {
controller.circle2.transform = CGAffineTransform(rotationAngle: rotation).concatenating(CGAffineTransform(translationX: translation.x, y: translation.y))
}
Timer.scheduledTimer(withTimeInterval: 1.0/60.0, repeats: true) { timer in
let now: Date = .init()
let scale: Double = (now.timeIntervalSince(startTime))/translationDuration
guard scale > 0.0 else { return } // Waiting part if delay is applied
if scale < 1.0 {
// Animate the view
translation = CGPoint(x: 0.0, y: 35.0*scale)
refreshView(self)
} else {
// Animation ended
timer.invalidate()
translation = CGPoint(x: 0.0, y: 35.0)
refreshView(self)
var rotationStartTime: Date = .init()
Timer.scheduledTimer(withTimeInterval: 1.0/60.0, repeats: true) { [weak self] timer in
guard let self = self else { timer.invalidate(); return }
let now: Date = .init()
let scale: Double = (now.timeIntervalSince(rotationStartTime))/rotationDuration
guard scale > 0.0 else { return } // Waiting part if delay is applied
if scale < 1.0 {
// Animate the view
rotation = .pi * CGFloat(scale)
refreshView(self)
} else {
// Animation ended
rotation = 0.0
rotationStartTime = .init()
refreshView(self)
}
}
}
}
}
If you have a need to stop the animation then you will need some references to timers.

Why does my UI button stop responding after a certain amount of time has elapsed?

My code is supposed to print out the current number of taps recorded, which it does do starting out, but then it randomly stops. Any tips on fixing this?
I think it has something to do with the animation, but I'm not sure what about it is causing the app to stop responding.
import UIKit
class ViewController: UIViewController {
var taps = 0 // tracks number of screen taps
override func viewDidLoad() {
super.viewDidLoad()
// get screen dimensions (in points) for current device
let screenBounds = UIScreen.main.bounds
let screenWidth = screenBounds.width
let screenHeight = screenBounds.height
// create rect
let rect = CGRect(x: 0, y: screenHeight, width: screenWidth, height: screenHeight)
let view = UIView(frame: rect)
view.backgroundColor = .red
self.view.addSubview(view)
// rising animation
UIView.animate(withDuration: 5.0, delay: 0.0, options: .curveEaseInOut, animations: {
view.frame.origin.y = CGFloat(0)
}, completion: { finished in
})
}
// when user taps screen
#IBAction func screenTapped(_ sender: Any) {
taps += 1 // increment number of taps
print(taps)
}
}
Try this
import UIKit
class ViewController: UIViewController {
var taps = 0 // tracks number of screen taps
override func viewDidLoad() {
super.viewDidLoad()
// get screen dimensions (in points) for current device
let screenBounds = UIScreen.main.bounds
let screenWidth = screenBounds.width
let screenHeight = screenBounds.height
// create rect
let rect = CGRect(x: 0, y: screenHeight, width: screenWidth, height: screenHeight)
let view = UIView(frame: rect)
view.backgroundColor = .red
self.view.addSubview(view)
//Add Tap gesture
let tap = UITapGestureRecognizer(target: self, action: #selector(screenTapped))
self.view.addGestureRecognizer(tap)
// rising animation
UIView.animate(withDuration: 5.0, delay: 0.0, options: .curveEaseInOut, animations: {
view.frame.origin.y = CGFloat(0)
}, completion: { finished in
})
}
// when user taps screen
#objc func screenTapped() {
taps += 1 // increment number of taps
print(taps)
}
}

Swift: restoring UIView center after moving it

I'm using gestures to zoom and move a UIImageView (like Instagram zoom, for example). When the gesture ends, I want to restore UIImageView initial position, but I cannot get a copy of the initial center because it's a reference type. Using:
let prevCenter = myImage.center
is of course useless. How can I copy it?
On zoom and move apply transform to view. On the end just set .identity value.
Edit
Example:
#IBOutlet weak var butt: UIButton!
var offsetTransform: CGAffineTransform = .identity
var zoomTransform: CGAffineTransform = .identity
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let pan = UIPanGestureRecognizer(target: self, action: #selector(onPan(pan:)))
pan.minimumNumberOfTouches = 2
pan.delegate = self
view.addGestureRecognizer(pan)
let pinch = UIPinchGestureRecognizer(target: self, action: #selector(onPinch(pinch:)))
pinch.delegate = self
view.addGestureRecognizer(pinch)
}
#objc func onPinch(pinch: UIPinchGestureRecognizer) {
let s = pinch.scale
zoomTransform = CGAffineTransform(scaleX: s, y: s)
butt.transform = offsetTransform.concatenating(zoomTransform)
if (pinch.state == .ended) {
finish()
}
}
#objc func onPan(pan: UIPanGestureRecognizer) {
let t = pan.translation(in: view)
offsetTransform = CGAffineTransform(translationX: t.x, y: t.y)
}
func updatePos() {
butt.transform = offsetTransform.concatenating(zoomTransform)
}
func finish() {
butt.transform = .identity
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
Are you sure that you are not updating the prevCenter somewhere else?
center is struct so you shouldn't have problems copying it.
Just make sure to take it (the center) when the view is in original state, before starting to zoom it and after it is drawn with correct frame
let imageView = UIImageView(frame: CGRect(x: 10, y: 10, width: 20, height: 20))
let prevCenter = view.center // 20, 20
imageView.center = CGPoint(x: 50, y: 50)
// imageView.center - 50, 50
// prevCenter - 20, 20
imageView.frame = CGRect(x: 40, y: 40, width: 10, height: 10)
// imageView.center - 45, 45
// prevCenter - 20, 20

Tapping a UIImage while it's being animated

I've been trying to be able to tap a UIImage as it animates to the top of my screen and print("Image Tapped"), yet to no success.
override func viewDidLoad() {
super.viewDidLoad()
redBalloon.image = UIImage(named: "redBalloon")
redBalloon.contentMode = .scaleAspectFit
redBalloon.frame = CGRect(x: Int(xOrigin), y: 667, width: Int(redBalloon.frame.size.width), height: Int(redBalloon.frame.size.height))
UIView.animate(withDuration: 5, delay: 0, options: UIImageView.AnimationOptions.allowUserInteraction, animations: {
self.redBalloon.frame = CGRect(x: Int(self.xEnding), y: -192, width: 166, height: 192)
}, completion: {(finished:Bool) in
self.endGame()
})
let imageTap = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
redBalloon.isUserInteractionEnabled = true
redBalloon.addGestureRecognizer(imageTap)
}
#objc func imageTapped(_ sender: UITapGestureRecognizer) {
// do something when image tapped
print("image tapped")
}
The problem is that the image view is not in the spot where you see it during animation (it's at the endpoint of the animation). So you are not tapping on the image view at the point where it is, and thus the tap is not detected.
Therefore, either you must hit-test the presentation layer or, if you don't want to do that, you must use a UIViewPropertyAnimator instead of calling UIView.animate.
As an example of the first approach, I'll subclass UIImageView. Make your UIImageView an instance of this subclass:
class TouchableImageView : UIImageView {
override func hitTest(_ point: CGPoint, with e: UIEvent?) -> UIView? {
let pres = self.layer.presentation()!
let suppt = self.convert(point, to: self.superview!)
let prespt = self.superview!.layer.convert(suppt, to: pres)
return super.hitTest(prespt, with: e)
}
}
However, personally I think it's a lot simpler to use UIViewPropertyAnimator. In that case, do not make your UIImageView a TouchableImageView! You don't want to do extra hit-test munging. Just let the property animator do all the work:
redBalloon.image = UIImage(named: "redBalloon")
redBalloon.contentMode = .scaleAspectFit
redBalloon.frame = CGRect(x: Int(xOrigin), y: 667, width: Int(redBalloon.frame.size.width), height: Int(redBalloon.frame.size.height))
let anim = UIViewPropertyAnimator(duration: 5, timingParameters: UICubicTimingParameters(animationCurve: .easeInOut))
anim.addAnimations {
self.redBalloon.frame = CGRect(x: Int(xEnding), y: -192, width: 166, height: 192)
}
anim.addCompletion { _ in
self.endGame()
}
let imageTap = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
redBalloon.isUserInteractionEnabled = true
redBalloon.addGestureRecognizer(imageTap)
anim.startAnimation()

Changing frame of a UIView is not working fine

I have a small problem which I am not able to get why this is happening.
I have a customised NavigationView. My requirement is to change the frame of this NavigationView when tap on google map. I mean I have to show and hide (toggle) the frame with animation.
Below is my code :-
func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D) {
if false == boolTapOnNavigationView {
UIView.animate(withDuration: 0.5, animations: {
self.viewNavigation.frame = CGRect(x: 0, y: -100, width: self.view.frame.width, height: 60)
//self.viewNavigation.isHidden = true
})
boolTapOnNavigationView = true
}
else {
UIView.animate(withDuration: 0.5, animations: {
//self.viewNavigation.isHidden = false
self.viewNavigation.frame = CGRect(x: 0, y: 20, width: self.view.frame.width, height: 60)
})
boolTapOnNavigationView = false
}
}
In my code you can see I have a flag called 'boolTapOnNavigationView'.
That code is working fine if I will tap on google map but when I will tap on any other button in same ViewController then frame of that NavigationView is reflecting automatically.
You should change center not frame
func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D) {
if false == boolTapOnNavigationView {
UIView.animate(withDuration: 0.5, animations: {
self.viewNavigation.centre.y = self.viewNavigation.centre.y - 100
//self.viewNavigation.isHidden = true
})
boolTapOnNavigationView = true
}
else {
UIView.animate(withDuration: 0.5, animations: {
//self.viewNavigation.isHidden = false
self.viewNavigation.centre.y = self.viewNavigation.centre.y + 20
})
boolTapOnNavigationView = false
}
}

Resources