UIViewPropertyAnimator not working in UIscrollView's contentoffset - swift

I want to animate the offset.x of the scrollview.
The animation does not work, but the offset.x of the scrollview works normally.
#objc private func didTapLabel(_ sender: UIButton) {
let x = CGFloat(sender.tag) * self.bounds.width
UIViewPropertyAnimator.runningPropertyAnimator(
withDuration: 0.5, delay: 0, options: .curveEaseInOut) {
[unowned self] in
scrollView.contentOffset.x = x
}
}

Related

Animations with safeAreaLayoutGuide (Swift 5)

I would like a smooth animation of this view whenever the search bar is selected and deselected. Right now it's choppy:
Heres my code below in the searchResultsUpdater. From what I understand, these functions should handle the animations, I'm not sure what's wrong here:
func updateSearchResults(for searchController: UISearchController) {
//MapView moves up when search bar is selected
if searchController.isActive == true{
UIView.animateKeyframes(withDuration: 0.25, delay: 0.0, options: UIView.KeyframeAnimationOptions(rawValue: 7), animations: {
self.mapView.frame.origin.y=self.view.safeAreaLayoutGuide.layoutFrame.origin.y
},completion: nil)
}
//MapView moves down when cancel button is selected
if searchController.isActive == false{
UIView.animateKeyframes(withDuration: 0.25, delay: 0.0, options: UIView.KeyframeAnimationOptions(rawValue: 7), animations: {
self.mapView.frame.origin.y=self.view.safeAreaLayoutGuide.layoutFrame.origin.y
},completion: nil)
}
}
Any help is appreciated, thank you!
I found the solution. Originally I had the CGRect for the mapView frame in viewDidLayoutSubviews():
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
//layout to fit frame of view
mapView.frame = CGRect(x: 0, y: view.safeAreaInsets.top, width:
view.frame.size.width, height: view.frame.size.height -
view.safeAreaInsets.top)
}
I moved it to viewDidLoad(), and now it works:
override func viewDidLoad() {
super.viewDidLoad()
//add gesture recognizer for mapView
mapView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(mapViewAnimation)))
//layout to fit frame of view
mapView.frame = CGRect(x: 0, y: view.safeAreaInsets.top, width:
view.frame.size.width, height: view.frame.size.height -
view.safeAreaInsets.top)
}
I also added a gesture recognizer instead so that the searchResultsUpdater wouldn't be so cluttered:
//handle the animation for mapview
#objc func mapViewAnimation(){
UIView.animateKeyframes(withDuration: 0.25, delay: 0.0, options:
UIView.KeyframeAnimationOptions(rawValue: 7), animations: {
self.mapView.frame.origin.y =
self.view.safeAreaLayoutGuide.layoutFrame.origin.y
},completion: nil)
}
If anyone knows why it didn't work when I had the CGRect in didLayoutSubViews() feel free to let me know. Thanks!

UIButton Animations not working on UIScrollView

I have button animations that are not working on my ScrollView. I have other VCs that are just UIViews and animations work just fine.
So I tried unchecking from the Attributes Inspector and adding...
self.scrollView.delaysContentTouches = false
...in my viewDidLoad, but it had effect.
I also found that tapping does not trigger the animation but holding on the button for a second or more does and haptic presses also trigger the animation.
This is my animation code:
extension UIButton {
func startAnimatingPressActions() {
addTarget(self, action: #selector(animateDown), for: [.touchDown, .touchDragEnter])
addTarget(self, action: #selector(animateUp), for: [.touchDragExit, .touchCancel, .touchUpInside, .touchUpOutside])
}
#objc private func animateDown(sender: UIButton) {
animate(sender, transform: CGAffineTransform.identity.scaledBy(x: 0.95, y: 0.95))
}
#objc private func animateUp(sender: UIButton) {
animate(sender, transform: .identity)
}
private func animate(_ button: UIButton, transform: CGAffineTransform) {
UIView.animate(withDuration: 0.4,
delay: 0,
usingSpringWithDamping: 0.5,
initialSpringVelocity: 3,
options: [.curveEaseInOut],
animations: {
button.transform = transform
}, completion: nil)
}
}
I am able to get this animation working, but it doesn't feel quite right. So I'd rather not use the code below. But it does work. So I'm not sure what in the code above is causing the problem.
extension UIButton {
func pulsate() {
let pulse = CASpringAnimation(keyPath: "transform.scale")
pulse.duration = 0.2
pulse.fromValue = 0.96
pulse.toValue = 1.0
pulse.repeatCount = 0
pulse.initialVelocity = 0.5
pulse.damping = 1.0
layer.add(pulse, forKey: nil)
}
}
I had the same issue and I solved unchecking the delay touch down button in the scrollview's attributes inspector :
ScrollView's attrobutes inspector

How To Create Animation of Segue perform and unwind segue?

i am working on application where i am trying to call controllers with animations
second view controller with animations
like when i call second controller with segue
second controller come from right and first controller start moving to left
like android push pop animations
i make some code
class SegueFromRight: UIStoryboardSegue {
override func perform() {
let src = self.source
let dst = self.destination
src.view.superview?.insertSubview(dst.view, aboveSubview: src.view)
dst.view.transform = CGAffineTransform(translationX: src.view.frame.size.width*2, y: 0)
//Double the X-Axis
UIView.animate(withDuration: 0.25, delay: 0.0, options: UIView.AnimationOptions.curveEaseInOut, animations: {
dst.view.transform = CGAffineTransform(translationX: 0, y: 0)
}) { (finished) in
src.present(dst, animated: false, completion: nil)
}
}
}
but from this code new controller come from right i need to move current controller to move left too
if anyone can help
Try this code:
class CustomSegue: UIStoryboardSegue {
override func perform() {
let firstVCView: UIView = self.source.view
let secondVCView: UIView = self.destination.view
self.destination.modalPresentationStyle = .fullScreen
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
secondVCView.frame = CGRect(x: screenWidth, y: 0, width: screenWidth, height: screenHeight)
let window = UIApplication.shared.keyWindow
window?.insertSubview(secondVCView, aboveSubview: firstVCView)
UIView.animate(withDuration: 0.4, animations: { () -> Void in
firstVCView.frame = firstVCView.frame.offsetBy(dx: -screenWidth, dy: 0.0)
secondVCView.frame = secondVCView.frame.offsetBy(dx: -screenWidth, dy: 0.0)
}) { (Finished) -> Void in
self.source.present(self.destination as UIViewController,
animated: false,
completion: nil)
}
}
}
class CustomSegueUnwind: UIStoryboardSegue {
override func perform() {
let secondVCView: UIView = self.source.view
let firstVCView: UIView = self.destination.view
self.destination.modalPresentationStyle = .fullScreen
let screenWidth = UIScreen.main.bounds.size.width
let window = UIApplication.shared.keyWindow
window?.insertSubview(firstVCView, aboveSubview: secondVCView)
UIView.animate(withDuration: 0.4, animations: { () -> Void in
firstVCView.frame = firstVCView.frame.offsetBy(dx: screenWidth, dy: 0.0)
secondVCView.frame = secondVCView.frame.offsetBy(dx: screenWidth, dy: 0.0)
}) { (Finished) -> Void in
self.source.dismiss(animated: false, completion: nil)
}
}
}
Reference HERE for more details

Swift Animation - button with circle behind

I'm trying to create de same effect than the app "Music" :
When I click on a button, there is a view behind and when the button is not focused anymore, the view is hidden. I do this with TouchUpInside and TouchDown funcs.
#IBAction func pressed(_ sender: UIButton) {
UIView.animate(withDuration: 0.25, animations: {
self.backgroundMoreView.alpha = 0.0
self.backgroundMoreView.transform = CGAffineTransform(scaleX:
1.2, y: 1.2)
sender.transform = CGAffineTransform.identity
}) { (_) in
self.backgroundMoreView.transform = CGAffineTransform.identity
}
}
#IBAction func unpressed(_ sender: UIButton) {
UIView.animate(withDuration: 0.25) {
self.backgroundMoreView.alpha = 0.3
sender.transform = CGAffineTransform(scaleX: 0.8, y:
0.8)
}
}
The problem is that, when I click and hold focus, and then I swipe out of the button, the function unpressed() is not called and the button stay "focused".
I tried also to add touchUpOutside function but no result. I don't know how to fix it.
For me this works (I prefer the exit on leaving the button)
#IBAction func touchDown(_ sender: UIButton) {
UIView.animate(withDuration: 0.25, animations: {
self.background.alpha = 1.0
}) { (_) in
print("do")
}
}
#IBAction func touchDragExit(_ sender: UIButton) {
UIView.animate(withDuration: 0.25, animations: {
self.background.alpha = 0.0
}) { (_) in
print("away")
}
}

Switching between UITextViews resets view Layout

I implemented some functions to move the UIView, whenever a TextField or a TextView is tapped. The height for the UIView to move upwards is calculated depending how much the keyboard would overlap the active TextField or TextView. When I tap outside of the TextField, or -View the keyboard will be dismissed and the View will be resetted. Now everything is working fine, but when I switch from one TextField directly to another above (without dismissing the keyboard) it seems like the UIView will return to the initial position, instead of just keeping the shifted view (because the now active TextView would not be overlapped by the keyboard since it is above the former). It looks like some method is called to reset the view, resulting in the keyboard overlapping the upper TextView. Is there a way to suppress this behavior?
private func observeKeyboardNotification(){
NotificationCenter.default.addObserver(self, selector: #selector(keyboardShow), name: .UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardHide), name: .UIKeyboardWillHide, object: nil)
}
var distanceOfKeyboardToTextView: CGFloat = 0
var activeTextElement: UIView?
var viewIsShifted = false
func keyboardShow(notification: NSNotification){
findActiveTextField(subviews: self.view.subviews, textField : &activeTextElement)
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue, let activeTextElement = activeTextElement, !viewIsShifted {
let viewYPosition = (activeTextElement.superview?.convert(activeTextElement.frame.origin, to: nil).y)! + activeTextElement.frame.height
let keyboardYPosition = view.frame.height - keyboardSize.height
distanceOfKeyboardToTextView = viewYPosition - keyboardYPosition
if distanceOfKeyboardToTextView > 0 {
UIView.animate(withDuration: 0.5,
delay: 0,
usingSpringWithDamping: 1,
initialSpringVelocity: 1, options: .curveEaseOut,
animations: {
self.customView.frame = CGRect(x: 0, y: self.customView.frame.origin.y - self.distanceOfKeyboardToTextView, width: self.customView.frame.width, height: self.customView.frame.height)
}, completion: nil)
viewIsShifted = true
}
}
activeTextElement = nil
}
func keyboardHide(){
UIView.animate(withDuration: 0.5,
delay: 0,
usingSpringWithDamping: 1,
initialSpringVelocity: 1, options: .curveEaseOut,
animations: {
self.customView.frame = CGRect(x: 0, y: self.customView.frame.origin.y + self.distanceOfKeyboardToTextView, width: self.customView.frame.width, height: self.customView.frame.height)
}, completion: nil)
viewIsShifted = false
distanceOfKeyboardToTextView = 0
}
func findActiveTextField (subviews : [UIView], textField : inout UIView?) {
guard textField == nil else { return }
for view in subviews {
if view.isFirstResponder {
textField = view
break
}
else if !view.subviews.isEmpty {
findActiveTextField (subviews: view.subviews, textField: &textField)
}
}
}
Update:
After tapping from an active textfield directly onto another textfield, keyboardShow is call, but since the view is already shifed, UIView.animate will not be performed. However, the view is resetted like no keyboard would be displayed, but since the other textfield is active, the keyboard is visible.
I dismiss the keyboard using this extension:
extension UIViewController {
//functions to hide the keyboard
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
view.addGestureRecognizer(tap)
}
func dismissKeyboard() {
view.endEditing(true)
}
}
and in my ViewController:
override func viewDidLoad() {
super.viewDidLoad()
observeKeyboardNotification()
self.hideKeyboardWhenTappedAround()
...
}