How to repeat flip animation? - swift

I'm trying to create my own activity indicator view like twitch chat activity indicator view. But I've tried to use animate .repeat:
override func animate() {
UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: [.repeat, .transitionFlipFromLeft], animations: {
self.flip()
}, completion: nil)
}
and also timer
let timer = Timer.init(timeInterval: 2, target: self, selector: #selector(flip), userInfo: nil, repeats: true)
Here is my flip() function
func flip() {
self.isFlipped = !self.isFlipped
let fromView = self.isFlipped ? self.view1 : self.view2
let toView = self.isFlipped ? self.view2 : self.view1
UIView.transition(from: fromView, to: toView, duration: 1, options: [.transitionFlipFromTop, .showHideTransitionViews], completion: nil)
}
But it did not work. I don't know what I missed. Can you guys help me please?

Related

how to factorize a function including a selector?

I have several views which have the same action. I tried to factorize the function, but it didn't work.
here is a part of the code in the ViewController:
class MenuViewController: UIViewController {
#IBOutlet weak var addButton: FloatingActionButton!
#IBOutlet weak var viewCALORIES: UIView!
#IBOutlet weak var viewPROTEINES: UIView!
#IBOutlet weak var viewLIPIDES: UIView!
#IBOutlet weak var viewGLUCIDES: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// BoingCalories Protéines Lipides Glucides
let tapCalories = UITapGestureRecognizer(target: self, action: #selector(boingCALORIES(gesture:)))
viewCALORIES.addGestureRecognizer(tapCalories)
let tapProteines = UITapGestureRecognizer(target: self, action: #selector(boingPROTEINES(gesture:)))
viewPROTEINES.addGestureRecognizer(tapProteines)
let tapLipides = UITapGestureRecognizer(target: self, action: #selector(boingLIPIDES(gesture:)))
viewLIPIDES.addGestureRecognizer(tapLipides)
let tapGlucides = UITapGestureRecognizer(target: self, action: #selector(boingGLUCIDES(gesture:)))
viewGLUCIDES.addGestureRecognizer(tapGlucides)
}
#objc private func boingCALORIES(gesture: UITapGestureRecognizer) {
viewCALORIES.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
UIView.animate(withDuration: 0.6, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.4, options: [], animations: { self.viewCALORIES.transform = .identity }, completion: nil)
}
#objc private func boingPROTEINES(gesture: UITapGestureRecognizer) {
viewPROTEINES.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
UIView.animate(withDuration: 0.6, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.4, options: [], animations: { self.viewPROTEINES.transform = .identity }, completion: nil)
}
#objc private func boingLIPIDES(gesture: UITapGestureRecognizer) {
viewLIPIDES.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
UIView.animate(withDuration: 0.6, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.4, options: [], animations: { self.viewLIPIDES.transform = .identity }, completion: nil)
}
#objc private func boingGLUCIDES(gesture: UITapGestureRecognizer) {
viewGLUCIDES.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
UIView.animate(withDuration: 0.6, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.4, options: [], animations: { self.viewGLUCIDES.transform = .identity }, completion: nil)
}
I've tried some tests, but it fails. So how could i factorize all of this?
Thank you
From the UITapGestureRecognizer, you can retrieve the view.
let view = gesture.view
So you can do:
#objc private func boingView(gesture: UITapGestureRecognizer) {
let view = gesture.view
view.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
UIView.animate(withDuration: 0.6,
delay: 0,
usingSpringWithDamping: 0.3,
initialSpringVelocity: 0.4,
options: [],
animations: { view.transform = .identity },
completion: nil)
}
And to factorize the calls:
let action = #selector(boingView(gesture:))
let tapCalories = UITapGestureRecognizer(target: self, action: action)
viewCALORIES.addGestureRecognizer(tapCalories)
let tapProteines = UITapGestureRecognizer(target: self, action: action)
viewPROTEINES.addGestureRecognizer(tapProteines)
let tapLipides = UITapGestureRecognizer(target: self, action: action)
viewLIPIDES.addGestureRecognizer(tapLipides)
let tapGlucides = UITapGestureRecognizer(target: self, action: action)
viewGLUCIDES.addGestureRecognizer(tapGlucides)
If we go a little further, we could put the view into an array and loop on them:
let views = [viewCALORIES, viewPROTEINES, viewLIPIDES, viewGLUCIDES]
let action = #selector(boingView(gesture:))
views.forEach { aView in
let tap = UITapGestureRecognizer(target: self, action: action)
aView.addGestureRecognizer(tap)
}

Trying change Button Title and label.center.x in UIView.animate when press button but stuck. Why? Swift 4 Xcode 9.4

My Problem
I'm trying do some animation with label using UIView.animate(..) when touchInside button . Everything still be OK until I added a line: "self?.confirm.setTitle("Đăng nhập", for: .normal). The animation doesn't work.
My Will
I want the yellow underline below Đăng Ký switch to below Đăng nhập.
It good when code is
#IBAction func signUpAction(_ sender:Any?){
if (signup == false){
signup = true
UIView.animate(withDuration: 0.4, delay: 0, options: [.curveEaseIn], animations: {[weak self] in
self?.underlineSignup.center.x -= (self?.underlineSignup.bounds.width)!
self?.view.layoutIfNeeded()
}, completion: nil)
}
}
#IBAction func signInAction(_ sender:Any?){
if (signup == true){
signup = false
UIView.animate(withDuration: 0.4, delay: 0, options: [.curveEaseIn], animations: {[weak self] in
self?.underlineSignup.center.x += (self?.underlineSignup.bounds.width)!
self?.view.layoutIfNeeded()
}, completion: nil)
}
}
It work
But when I add .setTitle
#IBAction func signUpAction(_ sender:Any?){
if (signup == false){
signup = true
UIView.animate(withDuration: 0.4, delay: 0, options: [.curveEaseIn], animations: {[weak self] in
self?.underlineSignup.center.x -= (self?.underlineSignup.bounds.width)!
self?.confirm.setTitle("Đăng ký", for: .normal)
self?.view.layoutIfNeeded()
}, completion: nil)
}
}
#IBAction func signInAction(_ sender:Any?){
if (signup == true){
signup = false
UIView.animate(withDuration: 0.4, delay: 0, options: [.curveEaseIn], animations: {[weak self] in
self?.underlineSignup.center.x += (self?.underlineSignup.bounds.width)!
self?.confirm.setTitle("Đăng nhập", for: .normal)
self?.view.layoutIfNeeded()
}, completion: nil)
}
}
It stuck, only the title of button confirm change, the underline doesn't move
Please anyone can explain this situation.
EDIT:
The animation working but it's destination is always the first place which under Đăng ký ( animation come from left or right of it, the result always first place )
I think I knew how it is. Because in setTitle has updateFrame Constraintlayout of view. I have a contraintlayout when build button.
When I setTitles it reset the view using constraint.
That's what I think about this.
Try putting 'setTitle' into completion.
UIView.animate(withDuration: 0.15, delay: 0, options: .curveLinear, animations: {
self?.underlineSignup.center.x -= (self?.underlineSignup.bounds.width)!
self?.view.layoutIfNeeded()
}){
//completion
self?.confirm.setTitle("Đăng ký", for: .normal)
}

UIViewAnimation with constraint not working in Swift

I am trying to create an animation where I want to slide a uiview from right to left once parent UIViewController has loaded although I am able to display the view but the animation is not working. My code for animation:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
view.isOpaque = false
UIView.animate(withDuration: 1.0, delay: 1.0, options: .curveEaseOut, animations: {
self.contentXConstraint.constant = 500
self.contentXConstraint.constant = 80
}) { (status) in
}
}
I have displayed the parent view controller as presented viewcontroller:
present(navController, animated: false, completion: nil)
You forgot self.view.layoutIfNeeded()
self.contentXConstraint.constant = 500
self.contentXConstraint.constant = 80
UIView.animate(withDuration: 1.0, delay: 1.0, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
}) { (status) in
}
from https://developer.apple.com/documentation/uikit/uiview/1622507-layoutifneeded
Use this method to force the view to update its layout immediately. When using Auto Layout, the layout engine updates the position of views as needed to satisfy changes in constraints
Move your animation code from viewDidAppear to viewDidLayoutSubviews
You need to add layoutIfNeeded() method after updating constraints
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
view.isOpaque = false
UIView.animate(withDuration: 1.0, delay: 1.0, options: .curveEaseOut, animations: {
self.contentXConstraint.constant = 500
self.contentXConstraint.constant = 80
self.view.layoutIfNeeded()
}) { (status) in
}
}

Input Accessory View Behave Unexpectedly with keyboard hide and show events

I am facing an strange behavior with InputAccessoryView, I am working on chat screen where I have used InputAccessoryView where I have registered for KeyboardWillShow and KeyboardWillHide notifications. When my chat screen appears it automatically calls the KeyboardWillShowMethod once and after that it hides automatically without calling the KeyboardWillHide notification. After Loading chats when I click on textbox to type text it calls KeyboardWillShow which is fine. But when I try to hide keybaord it calls two methods first it will call KeyboardWillHide and after that it will call KeyboardWillShow which is strange.
This is my chat screen Image when keyboard is hidden.
This is when keyboard is shown Image
I am using this InputAccessoryView Code Programatically inputAccessoryView
This is how I have registered for keyboard notifications.
func handleKeyBoard(){
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
var contentInset = self.collectionView?.contentInset
contentInset?.bottom = keyboardSize.maxY
self.collectionView?.contentInset = contentInset!
self.collectionView?.scrollIndicatorInsets = contentInset!
// collectionViewBottomAnchor?.constant = keyboardSize.height + 50
// print ("input height \(inputAccessoryView?.frame.maxY) ")
// print("keyboard height \(keyboardSize.height)")
// print("keyboard Y \(keyboardSize.maxY)")
// print("keyboard Y \(keyboardSize.minY)")
//print("keyboard Y \(inputAccessoryView.framemaxY)")
if self.messages.count > 0{
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (completed:Bool) in
let indexPath = IndexPath(item: self.messages.count - 1, section: 0)
self.collectionView?.scrollToItem(at: indexPath, at: .bottom, animated: true)
})
}
}
}
#objc func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
print("keyboard hide")
self.collectionView?.contentInset = UIEdgeInsets(top: 8, left: 0, bottom: 52, right: 0)
self.collectionView?.scrollIndicatorInsets = UIEdgeInsets(top: 8, left: 0, bottom: 52, right: 0)
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (completed:Bool) in
})
}
}
In selectors I am trying to change my CollectionView Insets according to a Y Index of Keyboard because I am not getting Height of keybaord that is also an issue. Height of kyeboard is always 50 as of height of inputAccessoryView.
Here is the solution which I have found Thanx to #Amit. Instead of using UIKeyboardFrameBeginUserInfoKey I have used UIKeyboardFrameEndUserInfoKey after doing this I was able to get accurate hight of keyboard in KeyboardWillAppear method. Now the problem which remains was that KeyboardWillShow method was get called after KeyboardWillHide but at that time the KeyboardWillShow have a hight of keyboard 50.
That means when i try to hide a keyboard it will call KeyboardWillHide which is fine and after that It automatically calls KeyboardWillShow but height of keyboard remains 50 so I put condition there.
Now following method will make effect only when height is more than 50.
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
if keyboardSize.height > 50{
var contentInset = self.collectionView?.contentInset
contentInset?.bottom = keyboardSize.height + 50
self.collectionView?.contentInset = contentInset!
self.collectionView?.scrollIndicatorInsets = contentInset!
if self.messages.count > 0{
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (completed:Bool) in
let indexPath = IndexPath(item: self.messages.count - 1, section: 0)
self.collectionView?.scrollToItem(at: indexPath, at: .bottom, animated: true)
})
}
}
}
}
#objc func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
self.collectionView?.contentInset = UIEdgeInsets(top: 8, left: 0, bottom: 52, right: 0)
self.collectionView?.scrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: 52, right: 0)
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (completed:Bool) in
})
}
}
When keyboard has input accessory view, keyboardDidHideNotification is observed twice:
When keyboard is hidden.
When input accessroy view is hidden (note that input accessory view is visible for a while after keyboard is dismissed).
If your implementation depends on selector to be called just once, you can do one of following workarounds:
Option A: Check keyboard frame:
#objc
private func keyboardDidHide(_ notification: Notification) {
guard let keyboardRect = notification.keyboardRect, keyboardRect.origin.y == view.frame.maxY else {
return
}
// Do whatever you need...
}
extension Notification {
var keyboardRect: CGRect? {
guard let keyboardSize = userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else {
return nil
}
return keyboardSize.cgRectValue
}
}
Option B: Throttle reaction using GDC:
private var pendingKeyboardDidHideRequestWorkItem: DispatchWorkItem?
private func keyboardDidHide(_ notification: Notification) {
pendingKeyboardDidHideRequestWorkItem?.cancel()
let keyboardDidHideRequestWorkItem = DispatchWorkItem { [weak self] in
// Do whatever you need...
}
pendingKeyboardDidHideRequestWorkItem = keyboardDidHideRequestWorkItem
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500), execute: keyboardDidHideRequestWorkItem)
}

Custom Slide Segue - Xcode 8.0 Swift 3.0

What is the best way to create a custom segue that lets me press a button and then a view controller slides on. I have created a left to right segue but I want to make it go the other way. I have looked at some Youtube videos and this question but they don't show me what I want.
My code:
import UIKit
class SegueFromLeft: UIStoryboardSegue
{
override func perform()
{
let src = self.sourceViewController
let dst = self.destinationViewController
src.view.superview?.insertSubview(dst.view, aboveSubview: src.view)
dst.view.transform = CGAffineTransformMakeTranslation(-src.view.frame.size.width, 0)
UIView.animateWithDuration(0.25,
delay: 0.0,
options: UIViewAnimationOptions.CurveEaseInOut,
animations: {
dst.view.transform = CGAffineTransformMakeTranslation(0, 0)
},
completion: { finished in
src.presentViewController(dst, animated: false, completion: nil)
}
)
}
}
Basically, I want to create a segue that goes from right to left.
Thanks
I am using Xcode 8.0 and Swift 3.0
Try this:
class SegueFromLeft: UIStoryboardSegue{
override func perform(){
let src=sourceViewController
let dst=destinationViewController
let slide_view=destinationViewController.view
src.view.addSubview(slide_view)
slide_view.transform=CGAffineTransform.init(translationX: src.view.frame.size.width, 0)
UIView.animateWithDuration(0.25,
delay: 0.0,
options: UIViewAnimationOptions.CurveEaseInOut,
animations: {
slide_view.transform=CGAffineTransform.identity
}, completion: {finished in
src.present(dst, animated: false, completion: nil)
slide_view.removeFromSuperview()
})
}
}
Swift 3.1:
class ProceedToAppStoryboardSegue: UIStoryboardSegue {
override func perform(){
let slideView = destination.view
source.view.addSubview(slideView!)
slideView?.transform = CGAffineTransform(translationX: source.view.frame.size.width, y: 0)
UIView.animate(withDuration: 0.25,
delay: 0.0,
options: UIViewAnimationOptions.curveEaseInOut,
animations: {
slideView?.transform = CGAffineTransform.identity
}, completion: { finished in
self.source.present(self.destination, animated: false, completion: nil)
slideView?.removeFromSuperview()
})
}
}