I'm trying to show a custom view when I receive a notification from parse.
This view is showed with an animation and its hidden with another animation. This view also has a uitapgesturerecognizer that needs to be fired when the user taps the view.
My problem is that when the second animation gets fired the custom view's uitapgesture doesn't work :\
Any ideas? I paste the code.
Thanks!
func doManageObjectNotification(notification: COPushNotification){
mainView = UIApplication.sharedApplication().keyWindow!
customView = CustomNotificationView()
let height = customView.calculateViewHeight()
customView.frame = CGRectMake(0, -height, mainView.frame.width, height)
customView.targetMethod = notificationWasTapped
customView.setContent(notification)
customView.alpha = 0
mainView.addSubview(customView)
UIView.animateWithDuration(0.75, delay: 0, options: [UIViewAnimationOptions.CurveEaseInOut, UIViewAnimationOptions.AllowUserInteraction], animations: { () -> Void in
// Show the view
self.customView.frame.origin.y = 0
self.customView.alpha = 1
}) { (Bool) -> Void in
UIView.animateWithDuration(0.75, delay: 5, options: [UIViewAnimationOptions.CurveEaseInOut, UIViewAnimationOptions.AllowUserInteraction], animations: {
// Hide the view
self.customView.alpha = 0
}, completion: { finished in
self.customView.removeFromSuperview()
self.customView = nil
})
}
}
Im agree with Lion answer, but I want also focus your attention about customView.frame.origin.y = 0 during animation: if you use autolayout and you try to change frame dimensions or positions instead the correct constraints involved, you can disable you constraints effect causing warnings and unexpected view dimensions and movements. When you have this issue many times the UITapGestureRecognizer stop to responding.
The best way to do it is to create IBOutlets constraints and working with it (for example):
#IBOutlet weak var customViewTopConstraint: NSLayoutConstraint
...
UIView.animateWithDuration(0.75, delay: 0, options: [UIViewAnimationOptions.CurveEaseInOut, UIViewAnimationOptions.AllowUserInteraction], animations: { () -> Void in
// Show the view
self.customViewTopConstraint.constant = 0
For handling tap during animation you should implement touchesBegan method. by this you can detect touch and then check that touch is from your view,if yes then you can perform your desired task.
Hope this will help :)
Related
I have a view which contains 6 UIView vPreviews and each of them plays real-time stream video.
The target is:
When tap one of each UIView, it should become full screen.
Tap again, back to origin small screen layout
Code as below:
custom tap gesture with value
class FullScreenTapGesture: UITapGestureRecognizer{
var isFullScreen: Bool = false
var index: Int?
}
Add tap gesture recognizer in viewDidLoad()
let gesture = FullScreenTapGesture(target: self, action: #selector(fullScreenTap))
vPreviews[0].addGestureRecognizer(gesture)
gesture.index = 0
fullScreenTap implementation
#objc func fullScreenTap(sender: FullScreenTapGesture){
if sender.isFullScreen{
print("setsmall")
setSmallScreenLayoutConstrains()
// sender.isFullScreen = false
}else{
print("setfull")
setFullScreenLayoutConstrains(index: sender.index!)
// sender.isFullScreen = true
}
// Animate to full screen
UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.vc.view.layoutIfNeeded()
}) { (finished) in
// ...
}
}
func setSmallScreenLayoutConstrains(){
vPreviews[0].pin.top().left().width(previewWidth).height(previewHeight).margin(padding,padding,0,0)
vPreviews[1].pin.top().after(of: vPreviews[0]).width(previewWidth).height(previewHeight).margin(padding,padding,0,0)
vPreviews[2].pin.top().after(of: vPreviews[1]).width(previewWidth).height(previewHeight).margin(padding,padding,0,0)
vPreviews[3].pin.below(of: vPreviews[0]).left().width(previewWidth).height(previewHeight).margin(padding,padding,0,0)
vPreviews[4].pin.below(of: vPreviews[0]).after(of: vPreviews[3]).width(previewWidth).height(previewHeight).margin(padding,padding,0,0)
vPreviews[5].pin.below(of: vPreviews[0]).after(of: vPreviews[4]).width(previewWidth).height(previewHeight).margin(padding,padding,0,0)
}
func setFullScreenLayoutConstrains(index: Int){
self.vPreviews[index].pin.top().right().left().bottom().margin(3)
}
However, current result is like this: when tap is detected, full screen but automatically and immediately back to small screen.
Any suggestion would be greatly appreciated!
I coded a custom segue emulating a "Push Left". I takes screenshots of the 2 views, animates them from right to left, then present the destination view and remove the overlaying screenshots. I'm afraid that this ultimately results in stacking views on top of one another, which should be avoided in this case. I can't figure out how to properly dismiss the underlaying view once the animation is completed. I tried to use navigationController?.pushViewController instead of present but my attempts were not successful. How could I solve this issue ?
My custom segue :
class SeguePushLeft: UIStoryboardSegue
{
override func perform()
{
self.source.view.isUserInteractionEnabled = false
self.destination.view.isUserInteractionEnabled = false
let slideViewOut = UIImageView(image: source.view.capture()!)
let slideViewIn = UIImageView(image: destination.view.capture()!)
let screenWidth = source.view.frame.size.width
self.source.view.addSubview(slideViewIn)
self.source.view.addSubview(slideViewOut)
slideViewIn.transform = CGAffineTransform(translationX: screenWidth, y: 0)
UIView.animate(withDuration: 0.4,
delay: 0.0,
usingSpringWithDamping: 1,
initialSpringVelocity: 0,
options: UIViewAnimationOptions.curveEaseOut,
animations: {
slideViewIn.transform = CGAffineTransform.identity
slideViewOut.transform = CGAffineTransform(translationX: -screenWidth, y: 0)
}, completion: { finished in
DispatchQueue.main.async{
(self.source as UIViewController).present((self.destination as UIViewController), animated: false, completion: {
self.source.view.isUserInteractionEnabled = true
self.destination.view.isUserInteractionEnabled = true
slideViewIn.removeFromSuperview()
slideViewOut.removeFromSuperview()
})
}
})
}
}
[Please forgive my previous too-hasty answer. I'm entering another rather than editing the first, because of all the comments that now blot the first.]
The problem is simple: you're doing this wrong. What you wish to do is a present transition, but with a custom animation. That's perfectly viable, and you can certainly do it with a custom segue, but the way you're doing it is not how you do that. You do it with a custom transition animation. There is a fixed way of implementing that, and it isn't how you're going about things. You need to read up on how to write a custom transition animation for a presentation transition and you'll be all set.
I'm running into a weird situation where animating a UIImageView's alpha affects a UIButton which also exists on the same view.
My code:
func handleArrowAnimation(_ arrowImage: UIImageView, _ arrowImageXCenterConstraint: NSLayoutConstraint) {
arrowImageXCenterConstraint.constant = CGFloat(80)
UIView.animate(withDuration: 0.7, delay: 0, options: [.curveEaseInOut, .repeat, .autoreverse], animations: {
arrowImage.alpha = UIApplication.shared.userInterfaceLayoutDirection == .leftToRight ? 1 : 0.2
arrowImage.superview!.layoutIfNeeded()
}) { (completed) in
arrowImageXCenterConstraint.constant = CGFloat(0)
arrowImage.alpha = UIApplication.shared.userInterfaceLayoutDirection == .leftToRight ? 0.2 : 1
arrowImage.superview!.layoutIfNeeded()
}
}
The result:
I found that removing the call to layoutIfNeeded() prevents the UIButton alpha from changing, but of course it also prevents the arrow from moving - so it doesn't help me much.
The UIButton is not a subview of the arrowImage, and they don't share the same parent view (their parents share the same parent, though).
What am i missing here?
Thanks!
So apparently someone else had this issue and the answer is to make sure you start your animations after the view has loaded, for example in the viewDidAppear() method.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear()
handleArrowAnimation()
}
Here is the link to previous question. There does not appear to be any explanation for the strange behaviour at this time.
I am trying to get a view to animate from the centre of the screen, to leave the screen after 1 second when the view loads.
The problem I am having is that after a millisecond of the view being in the original (correct) position upon loading, it then snaps to the new position and animates back to the original position.
I have the following code in viewDidLoad
override func viewDidAppear(animated: Bool) {
UIView.animateWithDuration(0.7, delay: 1, options: .CurveEaseOut, animations: {
var loadingViewFrame = self.loadingView.frame
loadingViewFrame.origin.y += 600
self.loadingView.frame = loadingViewFrame
}, completion: { finished in
print("moved")
})
}
I have tried putting this code in a button action and it works fine, so is there some other method I should be using when animating on viewWillAppear or is there something else I have missed?
I have removed all autolayout constraints because I read that they may cause some problems.
I also have other code in viewDidAppear as well as viewWillAppear and viewDidLoad which I could show here if you think it is useful, but I have commented out all of this code to leave with only the basic code and the same error is still occurring.
Thanks a lot for your help.
Edit 1
I have moved the code to viewDidLayoutSubviews and have used dispatch_once to ensure it is only done once. The image still animates from the new position to the original position, but now the image is not located in the original position for a millisecond upon loading.
This is the code I have added
var token: dispatch_once_t = 0
override func viewDidLayoutSubviews() {
dispatch_once(&token) {
UIView.animateWithDuration(0.7, delay: 1, options: .CurveEaseOut, animations: {
var loadingViewFrame = self.loadingView.frame
loadingViewFrame.origin.x += 600
self.loadingView.frame = loadingViewFrame
}, completion: { finished in
print("moved")
})
}
}
First off, you need to call super.viewDidAppear(animated) when you override viewDidAppear: in your view controller subclass.
Unfortunately, it seems to work just fine for me with this view controller so there must be something else going on...
class ViewController: UITableViewController {
var loadingView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
loadingView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
loadingView.backgroundColor = UIColor.redColor()
loadingView.center = view.center
view.addSubview(loadingView)
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
UIView.animateWithDuration(0.7, delay: 1, options: .CurveEaseOut, animations: {
var loadingViewFrame = self.loadingView.frame
loadingViewFrame.origin.y += 600
self.loadingView.frame = loadingViewFrame
}, completion: nil)
}
}
Your issue is most-likely because layoutSubviews may be called after viewDidAppear: (I believe this is different for iOS 8 vs iOS 9) so the changes you made get overridden almost immediately. You can confirm this by overriding viewDidLayoutSubviews in your UIViewController subclass and breakpointing viewDidLoad, viewWillAppear:, viewDidAppear:, and viewDidLayoutSubviews to see what order they happen in.
One thing you can do to achieve a similar effect is use a dispatch_once block with a once_token that is a property of your class to execute the animation in viewDidLayoutSubviews. This will insure that your animation is executed once per instance of your class after the initial view layout has occurred. This might be what you're looking for.
If you could provide more of your view controller code or a github link I may be able to give you a better, less potentially hacky, answer about what is going on.
This is iPad app by using SWIFT. It having two views.
1. starting_View
2. login_View
These two views are in same ViewController.
starting_View will be first view. By Clicking, NEXT button in first View, Starting View will move to left side by using animateDuration and same time, login_View will come from right. If we click Username/password fields (Any TextField), it will navigates to previous view.
Same time,,
if login_View will be first screen means, textField is working, keyboard is appearing.
But in animateDuration, i couldn't type. Kindly help me. Am using XCODE 6.1.
Code (from comment):
#IBAction func getStart_button(sender: UIButton) {
UIView.animateWithDuration(0.7, delay: 0.25, options: .CurveEaseOut, animations: {
self.clt_login_vw.frame = CGRectMake(450, 56, 574, 660)
}, completion:nil)
}
In General try to never change frames directly when using Autolayout.
The best way to move views have been for me to use CGAffineTransform.
let move = CGAffineTransformMakeTranslation(translation_X , translation_Y)
and inside your animation closure:
self.clt_login_vw.transform = move
Then to return it back just do inside the animation Closure:
self.clt_login_vw.transform = CGAffineTransformIdentity
Resume:
let moveOut = CGAffineTransformMakeTranslation(translation_X , translation_Y)
#IBAction func getStart_button(sender: UIButton) {
UIView.animateWithDuration(0.7, delay: 0.25, options: .CurveEaseOut, animations: {
[weak self] // Remember no not create cycles
self!.clt_login_vw.transform = moveOut
}, completion:nil)
}
Edit:
Im gonna add Noah Witherspoon's option.
Another way to do this is getting the constraint that is holding your view horizontally, once you get it, you change its constant and call layoutIfNeeded() inside the closure:
#IBAction func getStart_button(sender: UIButton) {
constraint.constant = newConstant // enough to make it off the screen
UIView.animateWithDuration(0.7, delay: 0.25, options: .CurveEaseOut, animations: {
[weak self] // Remember no not create cycles
self!.view.layoutIfNeeded()
}, completion:nil)
}
The way to get the specific constraint, I'm more friend of making IBOutlets, so I get easily its reference in Code.
That is if you didn't make it by code.