In my code, I've correctly moved the UITextfield into view when the keyboard pops up. However, I've struggled to create another animation that acts only on the UIImageView as this current solution permanently offsets the logo in the UIImageView.
var logoImage:UIImageView!
var logoImageX:CGFloat = 85
var logoImageY:CGFloat = 35
// Logo Code
let logo:UIImage = UIImage(named:"color-logo.png")
let logoHeight:CGFloat = 150
let logoWidth:CGFloat = logoHeight
logoImage = UIImageView(image:Logo)
logoImage.frame = CGRectMake(logoImageX,logoImageY,logoWidth, logoHeight)
self.view.insertSubview(logoImage, atIndex: 1)
// Keyboard Auto Scroll
func textFieldDidBeginEditing(textField: UITextField!) {
self.animateTextField(textField, up: true)
self.animateWithDuration(true)
}
func textFieldDidEndEditing(textField: UITextField!) {
self.animateTextField(textField, up: false)
self.animateWithDuration(false)
}
func animateTextField(textField:UITextField,up: Bool){
let movementDistance:Int = -100
let movementDuration:NSTimeInterval = 0.25
var movement = CGFloat(Int((up ? movementDistance : -movementDistance)))
UIView.beginAnimations("animateTextField", context:nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(movementDuration)
self.view.frame = CGRectOffset(self.view.frame, 0, movement)
UIView.commitAnimations()
}
Here's the function for the UIImageView:
//Logo (UIImageView) Scroll
func animateWithDuration(up:Bool){
let logoMovementDuration = 0.25
let logoMovementDistance:Int = 20
var logoMovement = CGFloat(Int((up ? logoMovementDistance : -logoMovementDistance + (logoImageY))))
UIView.beginAnimations("animateWithDuration", context:nil)
UIView.setAnimationDuration(logoMovementDuration)
logoImage.frame = CGRectMake(logoImageX,logoMovement,logoImage.frame.size.width,logoImage.frame.size.height)
UIView.commitAnimations()
}
I've found a solution by creating multiple UIViews, or in my case 3 specifically.
The code looks like this, depending on what you want to achieve, segregated into different groups in order to apply animations to each of them (these are declared outside of the viewDidLoad() to make them publicly accessible):
var otherView = UIView(frame: UIScreen.mainScreen().bounds)
var logoImageView = UIView(frame:CGRect(x: 0, y: 0, width: xx, height: xx))
var resultView = UIView(frame:CGRect(x: 0, y: 0, width: xx, height: xx))
After which, I added the subviews to UIView instances and then finally into the super view (declared within the viewDidLoad()):
self.logoImageView.insertSubview(logoImage, atIndex:1)
self.view.insertSubview(logoImageView, atIndex: 1)
Then finally applied the same func's now using the block based animations as suggested by rdelmar and recommended by Apple since iOS 4.0 (outside of, and after, the viewDidLoad()):
// Keyboard Auto Scroll
func textFieldDidBeginEditing(textField: UITextField!) {
self.animateTextField(textField, up: true)
}
func textFieldDidEndEditing(textField: UITextField!) {
self.animateTextField(textField, up: false)
}
func animateTextField(textField:UITextField,up: Bool){
let resultMovementDistance:Int = -xx
let movementDistance:Int = -xx
let movementDuration:NSTimeInterval = 0.xx
var resultMovement = CGFloat(Int((up ? resultMovementDistance : -resultMovementDistance)))
var otherMovement = CGFloat(Int((up ? movementDistance : -movementDistance)))
var logoMovement = otherMovement/5
UIView.animateWithDuration(
movementDuration,
delay: 0,
options: UIViewAnimationOptions.CurveEaseIn, animations:{
self.otherView.frame = CGRectOffset(self.otherView.frame,0,otherMovement)},
completion:nil)
UIView.animateWithDuration(
movementDuration,
delay: 0,
options: UIViewAnimationOptions.CurveEaseIn, animations:{
self.logoImageView.frame = CGRectOffset(self.logoImageView.frame,0,logoMovement)},
completion:nil)
UIView.animateWithDuration(
movementDuration,
delay: 0,
options: UIViewAnimationOptions.CurveEaseIn, animations:{
self.resultView.frame = CGRectOffset(self.resultView.frame,0,resultMovement)},
completion:nil)
}
Note: I did struggle with nesting the animations in the block, but I'm glad that it worked out. I hope this helps.
Related
I'm working on a project to learn animations and am having trouble using the completion block for the UIView.animate(withDuration:) func. MY animation is a shoebox that falls from the top of the screen, lands on a pedestal, then opens. Once the box opens I want a UIImageView to come out of the box, grow to full size of the screen and then segue to the next page, but the completion handler that the code for segueing is called before my animation completes and the UIImageView doesn't appear at all.
Here's my code:
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 1.5, delay: 0.0, options: .curveEaseInOut,
animations: {
//Opening the box
self.shoeBoxImage.shoeBox.animationImages = self.boxOpeningAnimation
self.shoeBoxImage.shoeBox.animationDuration = 1.5
self.shoeBoxImage.shoeBox.animationRepeatCount = 1
self.shoeBoxImage.shoeBox.contentMode = .scaleAspectFill
self.shoeBoxImage.shoeBox.startAnimating()
//set to the final image
self.shoeBoxImage.shoeBox.image = UIImage(named: "frame13")
},completion: {_ in
let nextPage = UIImageView()
nextPage.frame = CGRect(origin: self.shoeBoxImage.center, size: CGSize(width: 0.0, height: 0.0))
nextPage.image = UIImage(named: "FirstLoadBackgroundImg.jpeg")
nextPage.autoresizesSubviews = true
self.view.addSubview(nextPage)
self.view.bringSubviewToFront(nextPage)
UIView.animate(withDuration: 5.0, animations: {
nextPage.transform = CGAffineTransform(scaleX: 428, y: 926)
})
self.performSegue(withIdentifier: "FinishedLoading", sender: self)
})
}
This is my first time working with animations and programatically creating views so if someone could explain how to make the completion block wait for the animation to complete. In order to make the UIImageView appear and animate then once it's full screen, segue to the next page it would be very much appreciated.
The size is 0, 0. Transforming zero by any scale is still zero. I would advise you to not use transform at all, but rather just set the final frame to be what you want.
E.g.,
let startFrame = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
let endFrame = view.bounds
let imageView = UIImageView(image: ...)
imageView.contentMode = .scaleAspectFill
view.addSubview(imageView)
imageView.frame = startFrame
UIView.animate(withDuration: 3, delay: 0, options: .curveEaseInOut) {
imageView.frame = endFrame
} completion: { _ in
// do something here
}
That yields:
By the way, the performSegue probably should be inside a completion closure of the inner animate call.
I have no idea why this gesture recognizer is not working as intended:
class SlideInMenuLauncher: NSObject, UITableViewDelegate, UITableViewDataSource {
fileprivate let dimmerView = UIView()
fileprivate let tableView: UITableView = {
let tbv = UITableView(frame: .zero)
return tbv
}()
fileprivate let resourceArray: [Content]
weak var delegate: SlideInMenuDelegate?
let cellIdentifier = "SlideInMenuTableViewCell"
init(withContentArray contentArray: [Content]) {
resourceArray = contentArray
super.init()
tableView.dataSource = self
tableView.delegate = self
tableView.register(UINib(nibName: cellIdentifier, bundle: nil), forCellReuseIdentifier: cellIdentifier)
}
func showMenu() {
if let window = UIApplication.shared.keyWindow {
dimmerView.backgroundColor = UIColor(white: 0, alpha: 0.5)
dimmerView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(dismissMenu)))
dimmerView.isUserInteractionEnabled = true
window.addSubview(dimmerView)
window.addSubview(tableView)
let height: CGFloat = 200
let y = window.frame.height - height
tableView.frame = CGRect(x: 0, y: window.frame.height, width: window.frame.width, height: height)
dimmerView.frame = window.frame
dimmerView.alpha = 0
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.dimmerView.alpha = 1
self.tableView.frame = CGRect(x: 0, y: y, width: self.tableView.frame.width, height: self.tableView.frame.height)
}, completion: nil)
}
}
func dismissMenu() {
UIView.animate(withDuration: 0.5) {
self.dimmerView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.tableView.frame = CGRect(x: 0, y: window.frame.height, width: self.tableView.frame.width, height: self.tableView.frame.height)
}
}
}
I've put a breakpoint in the dismissMenu function but it is never triggered. Perhaps my tired eyes (and mind) missed something simple?
Here I am adding the code where I actually call this class in case something is wrong there:
///Some other VC
let slideInMenuLauncher = SlideInMenuLauncher(withContentArray: [content])
slideInMenuLauncher.showMenu()
I would expect that to give a compiler error. You're missing the #objc declaration on your dismissMenu() function.
I added a view to a view controller in a storyboard and used a small variant of your code and it responds to taps just fine. Thus my guess is that there's something wrong with the way you're adding your views to the view controller.
EDIT:
I know what the problem is: If you set a view's alpha to 0 it stops responding to taps. Try setting the view's opaque flag to false and setting it's background color to clearColor.
Here's the code from the test project I created:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tapView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
tapView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap)))
tapView.isUserInteractionEnabled = true
}
#objc func handleTap() {
print("You tapped on the view")
}
}
Can you try instead of this:
window.addSubview(dimmerView)
window.addSubview(tableView)
Do this:
window.addSubview(dimmerView)
window.addSubview(tableView)
window.bringSubviewToFront(dimmerView)
Try use following code:
let tapGes = UITapGestureRecognizer(target: self, action:#selector(dismissMenu))
tapGes.delegate = self
dimmerView.addGestureRecognizer(tapGes)
Hope it help you!
I think your problem is here:
dimmerView.alpha = 0
If I remember correctly, views that have their alpha set to 0 don't get touches. Try setting is to something like 0.5 just to see if it works, and then dial it back to a smaller value that's still greater than 0.
So I found the problem thanks to this post. When I was using the slideInMenuLauncher class elsewhere, I was not saving it to a strong reference or anything, so once I moved out of the scope of the function where I created the menu, the SlideInMenuLauncher object I used to create the dimmerView and tableView disappeared (despite still being able to see the views). Once I tapped the dimmerView the gesture recognizer sent a message to an object that no longer existed.
Setting the object to a strong property fixed the issue.
I'm writing an app where I need to use a fairly "complex" UIStoryboardSegue. The designer gave me the following:
Now the basic idea behind the segue is simple, sliding up the source.view, then sliding up one more view before eventually sliding the destination.view. However, my question is the following:
How do I insert a second view in-between the source.view and the destination.view from a subclass of UIStoryboardSegue ? Obviously I can't just do addSubview, since there is no view to add to. Is there another place to add a view in a UIStoryboardSegue, so I can still create this segue?
Thanks.
If this is what you want
You can achieve it very easily,
Step 1:
Create a subclass of UIStoryboardSegue and override perform
import UIKit
class SpecialEffectSegue: UIStoryboardSegue {
override func perform() {
let firstVCView = self.source.view as UIView!
let secondVCView = self.destination.view as UIView!
let intermediateView = UIView()
intermediateView.backgroundColor = UIColor.red
// Get the screen width and height.
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
// Specify the initial position of the destination view.
secondVCView?.frame = CGRect(x: 0.0, y: screenHeight, width: screenWidth, height: screenHeight)
intermediateView.frame = CGRect(x: 0.0, y: screenHeight, width: screenWidth, height: screenHeight)
// Access the app's key window and insert the destination view above the current (source) one.
let window = UIApplication.shared.keyWindow
window?.insertSubview(intermediateView, aboveSubview: firstVCView!)
window?.insertSubview(secondVCView!, aboveSubview: secondVCView!)
UIView.animate(withDuration: 0.4, animations: { () -> Void in
firstVCView?.frame = ((firstVCView?.frame)?.offsetBy(dx: 0.0, dy: -screenHeight))!
intermediateView.frame = (intermediateView.frame.offsetBy(dx: 0.0, dy: -screenHeight))
}) { (Finished) -> Void in
UIView.animate(withDuration: 0.4, animations: { () -> Void in
secondVCView?.frame = (secondVCView?.frame.offsetBy(dx: 0.0, dy: -screenHeight))!
}) { (Finished) -> Void in
self.source.present(self.destination, animated: false, completion: {
intermediateView.removeFromSuperview()
})
}
}
}
}
Though code looks huge and complicated what is happening in it is pretty simple.
Get the source and destination viewController's view using
let firstVCView = self.source.view as UIView!
let secondVCView = self.destination.view as UIView!
Because you need a intermediate view which is red in color here you create one more view
let intermediateView = UIView()
intermediateView.backgroundColor = UIColor.red
Now get the UIScreen width and heght so that you can configure these views frame so that it looks good as per your need
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
// Specify the initial position of the destination view.
secondVCView?.frame = CGRect(x: 0.0, y: screenHeight, width: screenWidth, height: screenHeight)
intermediateView.frame = CGRect(x: 0.0, y: screenHeight, width: screenWidth, height: screenHeight)
Note that the I set the frame of SecondVC and intermediateView such that they are below screen bounds and Ill animate them to come up in UIView.animate block
Now obviously because animations are happening on key window of your app access the key window
let window = UIApplication.shared.keyWindow
Now insert the subviews to window as per your need.
window?.insertSubview(intermediateView, aboveSubview: firstVCView!)
window?.insertSubview(secondVCView!, aboveSubview: secondVCView!)
So after this I have views stacked up as FirstVCView -> IntermediateView -> SecondVCView
Now we have pretty much what we need isnt it. Now animate it using UIView.animate
UIView.animate(withDuration: 0.4, animations: { () -> Void in
firstVCView?.frame = ((firstVCView?.frame)?.offsetBy(dx: 0.0, dy: -screenHeight))!
intermediateView.frame = (intermediateView.frame.offsetBy(dx: 0.0, dy: -screenHeight))
}) { (Finished) -> Void in
UIView.animate(withDuration: 0.4, animations: { () -> Void in
secondVCView?.frame = (secondVCView?.frame.offsetBy(dx: 0.0, dy: -screenHeight))!
}) { (Finished) -> Void in
self.source.present(self.destination, animated: false, completion: {
intermediateView.removeFromSuperview()
})
}
}
Important thing to notice is intermediateView.removeFromSuperview() in completion block.
Now I have decided to present destination using self.source.present( if you need to push destination VC with your funky animation say
self.source.navigationController?.pushViewController(destination, animated: false)
Thats all :)
Now open your storyboard, drag a segue from one your FirstVC to SecondVC and select the segue class as SpecialEffectSegue thats it now enjoy
Hope it helps :)
I have a UIView that I want to reveal after 0.5 seconds, and hide again after 0.5 seconds, creating a simple animation. My code is as follows:
let animation = UIViewPropertyAnimator.init(duration: 0.5, curve: .linear) {
self.timerBackground.alpha = 1
let transition = UIViewPropertyAnimator.init(duration: 0.5, curve: .linear) {
self.timerBackground.alpha = 0
}
transition.startAnimation(afterDelay: 0.5)
}
animation.startAnimation()
When I test it out, nothing happens. I assume it's because they're both running at the same time, which would mean they cancel each other out, but isn't that what the "afterDelay" part should prevent?
If I run them separately, i.e. either fading from hidden to visible, or visible to hidden, it works, but when I try to run them in a sequence, it doesn't work.
My UIView is not opaque or hidden.
You can use Timer, and add appearing / hiding animations blocks on every timer tick to your UIViewPropertyAnimatorobject.
Here's a codebase:
#IBOutlet weak var timerBackground: UIImageView!
private var timer: Timer?
private var isShown = false
private var viewAnimator = UIViewPropertyAnimator.init(duration: 0.5, curve: .linear)
override func viewDidLoad() {
super.viewDidLoad()
viewAnimator.addAnimations {
self.timerBackground.alpha = 1
}
viewAnimator.startAnimation()
isShown = true
self.timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(self.startReversedAction), userInfo: nil, repeats: true)
}
func startReversedAction() {
// stop the previous animations block if it did not have time to finish its movement
viewAnimator.stopAnimation(true)
viewAnimator.addAnimations ({
self.timerBackground.alpha = self.isShown ? 0 : 1
})
viewAnimator.startAnimation()
isShown = !isShown
}
I've implemented the very similar behavior for dots jumping of iOS 10 Animations demo project.
Please, feel free to look at it to get more details.
Use UIView.animateKeyframes you'll structure your code nicely if you have complicated animations. If you'll use UIView animations nested within the completion blocks of others, it will probably result in ridiculous indentation levels and zero readability.
Here's an example:
/* Target frames to move our object to (and animate)
or it could be alpha property in your case... */
let newFrameOne = CGRect(x: 200, y: 50, width: button.bounds.size.width, height: button.bounds.size.height)
let newFrameTwo = CGRect(x: 300, y: 200, width: button.bounds.size.width, height: button.bounds.size.height)
UIView.animateKeyframes(withDuration: 2.0,
delay: 0.0,
options: .repeat,
animations: { _ in
/* First animation */
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.5, animations: { [weak self] in
self?.button.frame = newFrameOne
})
/* Second animation */
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5, animations: { [weak self] in
self?.button.frame = newFrameTwo
})
/* . . . */
}, completion: nil)
What worked for me, was using sequence of UIViewPropertyAnimators. Here is example of my code:
let animator1 = UIViewPropertyAnimator(duration:1, curve: .easeIn)
animator1.addAnimations {
smallCoin.transform = CGAffineTransform(scaleX: 4, y: 4)
smallCoin.center = center
}
let animator2 = UIViewPropertyAnimator(duration:1, curve: .easeIn)
animator2.addAnimations {
center.y -= 20
smallCoin.center = center
}
let animator3 = UIViewPropertyAnimator(duration:10, curve: .easeIn)
animator3.addAnimations {
smallCoin.alpha = 0
}
animator1.addCompletion { _ in
animator2.startAnimation()
}
animator2.addCompletion { _ in
animator3.startAnimation()
}
animator3.addCompletion ({ _ in
print("finished")
})
animator1.startAnimation()
You can even add afterdelay attribute to manage speed of animations.
animator3.startAnimation(afterDelay: 10)
I want to cover the statusbar with a view like the following code shows. I have read a lot that this needs to be done in an separate(?) window that is on the same windows layer as the statusbar but I just don't get it to work.
I tried this (first code)
Display UIView Above Apple Status Bar in iOS 8
but my self.view.window? is nil
Trying to create a new UIWindow XCODE wants a rootViewController which I would have to fake... IMHO That cannot be the right way
Here is my code:
class GroupSelectionTVC: UITableViewController
{
override func viewDidLoad()
{
// Toast
let frame = CGRectMake(0, -20, self.view.frame.width, 20)
let message = UILabel(frame: frame)
message.backgroundColor = UIColor.blackColor()
message.text = "Testing"
message.textAlignment = NSTextAlignment.Center
message.textColor = UIColor.darkGrayColor()
self.view.addSubview(message)
UIView.animateWithDuration(1.0, animations: {
message.frame = CGRectMake(0, 0, self.view.frame.width, 20)}, completion:
{
(value: Bool) in UIView.animateWithDuration(1.0, delay: 2.0, options: nil, animations: {message.frame = CGRectMake(0, -20, self.view.frame.width, 20)}, completion:
{
(value: Bool) in message.removeFromSuperview()
}
)
}
)
I would recommend you to use CRToast. It's a framework for exactly your kind of problem. So you don't have to worry about UIViews etc. It's written in Objective-C but you can easily use it in a Swift project.