UIView animation that mimics presentViewController easing - ios

I have a view which animates from the bottom of my ViewController, and I'm trying to get the animation to be the same as the default
present(viewController, animated:true, completion:nil)
I want to animate a view with the same easing and timing as the above code. Right now I'm using this:
UIView.animate(withDuration: 0.35, delay: 0, options: .curveEaseInOut, animations: {
self.myView.frame = newFrame
}, completion: nil)
The animations aren't similar though, and I don't know what values to use in my UIView animation to make it look like a viewController present. Anyone know what values could get me close?

here is sample code with somewhat animation like presenting viewcontroller
var subViewNew = UIView()
make initial dream setup of you subview which you want to make to add on main view as below
override func viewDidLoad() {
super.viewDidLoad()
subViewNew.frame = CGRect(x: 100,y: 100,width:self.view.frame.width - 200,height:200)
self.subViewNew.frame.origin.y = self.view.frame.origin.y + self.view.frame.size.height
subViewNew.backgroundColor = UIColor.gray
}
for appearance of the view subview you can go like this
UIView.animate(withDuration: 0.7, animations: {
self.subViewNew.frame.origin.y = 300 + 20
self.view.addSubview(self.subViewNew)
})
for dismiss or hiding it you can go like this
UIView.animate(withDuration: 0.7, animations: {
self.subViewNew.frame.origin.y = self.view.frame.origin.y + self.view.frame.size.height
self.subViewNew.removeFromSuperview() //If you want then only
})
Hope this help you

Related

How to move UITableViewCell back and forth to show it can be swiped?

I see in some apps when you come to a screen with a tableview there's a short animation of the cell starting to be swiped, showing the red "swipe to delete" button (UIContextualAction button) and then it returns to normal. It is giving the user the hint: "These rows can be swiped."
Is there a way to achieve this effect? Maybe a way to programmatically start a row swipe then cancel it?
Swift Solution
Well, about 1.5 years later I finally came up with a solution.
Step 1 - Cell UI Setup
I set up my custom table view cell like this:
A and B represent the swipe action colors.
C is the UIView that I will animate side-to-side.
Step 2 - Add Animation to Cell
func animateSwipeHint() {
slideInFromRight()
}
private func slideInFromRight() {
UIView.animate(withDuration: 0.5, delay: 0.3, options: [.curveEaseOut], animations: {
self.cellBackgroundView.transform = CGAffineTransform(translationX: -self.swipeHintDistance, y: 0)
self.cellBackgroundView.layer.cornerRadius = 10
}) { (success) in
UIView.animate(withDuration: 0.2, delay: 0, options: [.curveLinear], animations: {
self.cellBackgroundView.transform = .identity
}, completion: { (success) in
// Slide from left if you have leading swipe actions
self.slideInFromLeft()
})
}
}
private func slideInFromLeft() {
UIView.animate(withDuration: 0.5, delay: 0, options: [.curveEaseOut], animations: {
self.cellBackgroundView.transform = CGAffineTransform(translationX: self.swipeHintDistance, y: 0)
}) { (success) in
UIView.animate(withDuration: 0.2, delay: 0, options: [.curveLinear], animations: {
self.cellBackgroundView.transform = .identity
})
}
}
Step 3 - Trigger the Animation
In the viewDidLoad of the view controller that has the table view, I have this code:
if self.tableView.visibleCells.count > 0 {
let cell = self.tableView.visibleCells[0] as! TableViewCell
cell.animateSwipeHint()
}
Example:
Video Solution
I created a video if you'd like a more in-depth walkthrough of this solution:
https://youtu.be/oAGoFd_GrxE
I have a piece of code that I saw long time ago to animate a view. Since our UITableViewCell is also a view, we can use it :) You just need to get your visible cell to animate, like so:
if let visibleCell = self.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? CustomCell {
print("Started animation...")
let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.duration = 0.6
animation.values = [-20.0, 20.0, -20.0, 20.0, -10.0, 10.0, -5.0, 5.0, 0.0 ]
visibleCell.layer.add(animation, forKey: "shake")
}
Let me know if this helps. Tested it.
EDIT:
Animating your UITableView to let the user see that they can swipe on a cell is pretty easy, try it like so:
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
self.tableView.setEditing(true, animated: true)
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
self.tableView.setEditing(false, animated: true)
}
}
HOWEVER, if you want to swipe programmatically your cell to show your custom row actions, (I've been researching this for an hour), you can only achieve this, as far as I know, by using method swizzling. See this SO answer: http://codejaxy.com/q/186524/ios-swift-uitableview-how-to-present-uitableviewrowactions-from-pressing-a-button

UIView animation with autolayout and child views

I have a weird problem that seems to be fairly easy to solve. I think I could use some workaround to be able to have the behavior that I want but I want to know if there is a better way to do it.
I have a view called contentView (blue in the image) that it will expand its height using a UIView.animation, this is no problem at all and works as expected.
The problem here is that this view has a child component (a button) that has an autolayout constraint to its bottom equal to 22 like this:
This is the autolayout constraint:
If I do the height resize without the animation it works fine, the view height change and the button are always 22 points of the bottom of the contentView. But when I use an animation to make this change more smoothy and user-friendly the button moves to the end position before the animation even start.
I want to know how I could achieve a smooth animation here but with the button moving along its parent view
The part of the code that handles the animation is pretty straightforward but I'll post it in here:
#IBAction func openDetail(_ sender: ExpandCourseDetail) {
let rotation = sender.getOpen() ? CGAffineTransform.identity : CGAffineTransform(rotationAngle: CGFloat.pi)
UIView.animate(withDuration: 0.5, delay: 0.1, options: [.curveEaseInOut], animations: {
sender.transform = rotation
}, completion: {
success in
sender.setOpen(!sender.getOpen())
})
UIView.animate(withDuration: 1.0, delay: 0.5, options: [.curveEaseInOut], animations: {
self.contentView.frame.size.height = sender.getOpen() ? self.contentView.frame.height - 300 : self.contentView.frame.height + 300
}, completion: nil)
}
As a side note, the button itself has an animation that rotates the button 180 degrees to show the user that the view is expanding.
Thank you so much for your help.
It's super easy with constraints, just create a superView height constraint IBOutlet and change its constant value.
#IBAction func btnPressed(_ sender: UIButton) {
self.toggleButton.isSelected = !sender.isSelected
//Animation starts here
self.view.layoutIfNeeded()
UIView.animate(withDuration: 0.7) {
if self.toggleButton.isSelected {
//transform button
self.toggleButton.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi))
//change your ParentView height to desired one
self.constContentViewHeight.constant = self.view.frame.size.height - 220
self.view.layoutIfNeeded()
} else {
self.toggleButton.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi*2))
// Set height constraint to original value
self.constContentViewHeight.constant = 250
self.view.layoutIfNeeded()
}
}
}
I have created a demo, check it out.
The issue you are facing is due to two animation blocks. So I have changed some lines and put both button transformation and height animation into one animation block.
func openDetail(_ sender: ExpandCourseDetail) {
let isOpen = sender.getOpen() ? CGAffineTransform.identity : CGAffineTransform(rotationAngle: CGFloat.pi)
UIView.animate(withDuration: 1.0, delay: 0.5, options: [.curveEaseInOut], animations: {
sender.transform = rotation
self.contentView.frame.size.height = self.contentView.frame.height + (isOpen ? 300 : -300)
}, completion: { (success) in
sender.setOpen(!isOpen)
})
}

iOS/Swift: Use UIView.animate to animate text getting added/removed from UITextView

I am adding and removing attributed text from a UITextView. I wish to use UIView.animate to add an animation to when text is appended to the text view and when that appended text is removed from the text view. So far, I have this, but it does not cause any noticeable animation on the text view:
UIView.animate(withDuration: 1, delay: 0, options: .CurveLinear, animations: {
self.view.layoutIfNeeded()
self.textView.attributedText = newAttributedText
}, completion: { finished in
print("Animation completed")
}
// prints "Animation completed", but no animation occurs
You cannot animate changing of text in that manner. There is a list of animatable properties of CALayer class and UIView class.
Set text cannot be animated by UIView.animate. Only changes like transparency, colors, shape, location, etc can be animated by the UIView.animate.
Here is an example of animating origin and transparency change with code looks like
self.textView.text = "test Animation"
self.textView.alpha = 0
UIView.animate(withDuration: 3, delay: 0, options: .curveLinear, animations: {
var frame = self.textView.frame
frame.origin.x = frame.origin.x + 50
frame.origin.y = frame.origin.y + 50
self.textView.frame = frame
self.textView.alpha = 1
}, completion: { finished in
print("Animation completed")
})
And animation looks like
Here is my solution (swift 3 ) :
let animator = UIViewPropertyAnimator(duration: 0.2, curve: .easeOut, animations: {
self.textView2.frame = self.textView2.frame.offsetBy(dx: 20 , dy: 25)
} )
animator.startAnimation()

User interaction disabled for parent viewController when removed from child ViewController

I am using below code to move from left to right.
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.rightNavController?.view.frame = CGRect(x: 0, y: 0, width: (appDelegate.window?.frame.size.width)!, height: (appDelegate.window?.frame.size.height)!)
self.removeNavigationController()
appDelegate.rightNavController = UINavigationController(rootViewController: leftmenuViewController)
appDelegate.rightNavController?.navigationBar.isHidden = true
baseController.addChildViewController(appDelegate.rightNavController!)
baseController.view.addSubview((appDelegate.rightNavController?.view)!)
UIView.animate(withDuration: 0.25, delay: 0.0, options: UIViewAnimationOptions(), animations: {
}, completion: { finished in
appDelegate.rightNavController?.view.frame = CGRect(x: 0, y: 0, width: 320, height: 568)
})
my view hierarchy in side menu is UIView->UiTableView.I have added tap gesture on UIView to dismiss side menu.
Below is the code used to dismiss:(tap gesture method)
self.willMove(toParentViewController: nil)
UIView.animate(withDuration: 0.5, delay: 0.0, options: UIViewAnimationOptions(), animations: {
self.view.frame = CGRect(x:-self.view.frame.size.width, y: 0 ,width : self.view.frame.size.width, height :self.view.frame.size.height)
}, completion: { finished in
self.removeFromParentViewController()
self.view.removeFromSuperview()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.rightNavController = nil
appDelegate.rightNavController?.view = nil
})
Now the issue is when child viewController is removed, parent view controller user interaction is disabled.
Any help to solve this issue.
It's not entirely clear what you're trying to achieve from the wording of the question but I would look for the isUserInteractionEnabled property on the view that's causing you trouble.
Also, maybe have a look to yourView.layer.zPosition. The view getting the interaction is the one with the highest zPosition value. Everyone defaults to zPosition = 0. All else being equal, the order in which the views where added will dictate who gets the events.

Custom Segue Class UIView Animation Issue

I am having trouble creating a custom segue using swift 3 and iOS 8. I am trying to transition between view controllers by fading from one VC to a black screen and then fading from black to my second VC. I tried to implement the segue by creating a custom segue using the code below, but it is not working as I would like it to. My goal is to perform an animation when the black square goes from 0.5 alpha to 1.0 alpha, then present my second view controller, then set the black square from 1.0 alpha back to 0.5 alpha and delete it. Right now, it does the first part correctly but after the animation finishes you can see the first VC for a brief instant before the second VC pops up. How should I change my code to make the transition smoother and get the desired result?
override func perform() {
let sourceVC = self.source
let destinationVC = self.destination
let square = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
square.alpha = 0.5
square.backgroundColor = UIColor.black
sourceVC.view.addSubview(square)
UIView.animate(withDuration: 0.2, animations: {
square.alpha = 1.0
})
UIView.animate(withDuration: 0.2, delay: 0.2, animations: {
square.alpha = 0.5
}) { (finished) in
sourceVC.present(destinationVC, animated: false, completion: nil)
}
}
Update your code like below and try:
UIView.animate(withDuration: 0.2, animations: {
square.alpha = 1.0
UIView.animate(withDuration: 0.2, delay: 0.2, animations: {
square.alpha = 0.5
}) { (finished) in
DispatchQueue.main.async {
sourceVC.present(destinationVC, animated: false, completion: nil)
}
})
}

Resources