我在关闭动画时遇到问题,无法以交互方式关闭第二个ViewController。我已经为我的课程实现了UIPercentDrivenInteraction,但是当我解雇它时,它会执行常规的基于时间的动画。了解我在做什么错的任何帮助都会有所帮助。这是我目前的动画课内容:
class leftSwipeAnimator: UIPercentDrivenInteractiveTransition,
UIViewControllerAnimatedTransitioning {
let time = 0.3
var presenting = true
var dismissCompletion: (()->Void)?
let screenSize = UIScreen.main.bounds
let screenWidth = UIScreen.main.bounds.width
let screenHeight = UIScreen.main.bounds.height
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return time
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)!
let secondView = presenting ? toView : transitionContext.view(forKey: .from)!
let originPoint: CGPoint = CGPoint(x: screenWidth, y: 0)
let originSize: CGSize = CGSize(width: screenWidth, height: screenHeight)
let originFrame = CGRect(origin: originPoint, size: originSize)
let initialFrame = presenting ? originFrame : secondView.frame
let finalFrame = presenting ? secondView.frame : originFrame
if presenting {
secondView.center = CGPoint(
x: initialFrame.midX,
y: initialFrame.midY)
secondView.clipsToBounds = true
}
containerView.addSubview(toView)
containerView.bringSubview(toFront: secondView)
UIView.animate(withDuration: time, delay:0.0,
animations: {
secondView.center = CGPoint(x: finalFrame.midX, y: finalFrame.midY)
}, completion: { _ in
if !self.presenting {
self.dismissCompletion?()
}
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
func handlePan( recognizer: UIPanGestureRecognizer){
let translation = recognizer.translation(in:
recognizer.view!.superview!)
var progress: CGFloat = abs(translation.x / 300.0) * 0.5
progress = min(max(progress, 0.01), 0.99)
print(progress)
switch recognizer.state {
case .cancelled, .ended:
if progress < 0.4 {
cancel()
} else {
finish() }
case .changed:
update(progress)
default:
break
}
}
}
这是我在主ViewController中的实现:
import UIKit
import Security
class ViewController: UIViewController {
var userId = "testing2" // This will have to be something unique to each user in the future. Don't know how we'll implement this yet
let transition = leftSwipeAnimator()
let right = rightSwipeAnimator()
var sideCheck: Int = 0 // Right is 0, left is 1
@IBAction func leftPage(_ sender: UIButton) {
sideCheck = 1
let mainStoryBoard = UIStoryboard(name: "Main", bundle: Bundle.main)
let screen3: UIViewController = mainStoryBoard.instantiateViewController(withIdentifier: "VC3") as! thirdViewController
screen3.transitioningDelegate = self
present(screen3, animated: true, completion: nil)
}
@IBAction func nextPage(_ sender: UIButton) {
rightPage()
}
func rightPage() {
sideCheck = 0
let mainStoryBoard = UIStoryboard(name: "Main", bundle: Bundle.main)
let screen2: UIViewController = mainStoryBoard.instantiateViewController(withIdentifier: "VC2") as! secondViewController
screen2.transitioningDelegate = self
present(screen2, animated: true, completion: nil)
}
@IBAction func deleteKey(_ sender: Any) { deleteKey() }
@IBOutlet weak var encryptString: UITextField!
@IBOutlet weak var encryptKey: UITextField!
@IBOutlet weak var encryptOutput: UITextField!
@IBOutlet weak var decryptString: UITextField!
@IBOutlet weak var decryptKey: UITextField!
@IBOutlet weak var decryptOutput: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
print(keyCheck(unique: userId))
let recognizer = UIPanGestureRecognizer(target: self, action: #selector(swipeAction(swipe:)))
self.view.addGestureRecognizer(recognizer)
// Do any additional setup after loading the view, typically from a nib.
//generateKey() // We will only have to run this function once per every new user to save the private key in their device
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBAction func encrypt(_ sender: UIButton) {
var item: CFTypeRef?
var error: Error
var enError: Unmanaged<CFError>?
var key: SecKey
let checkForKey = keyCheck(unique: userId)
let algorithm: SecKeyAlgorithm = .rsaEncryptionOAEPSHA512 // This is where we set the algorithm. Decryption algorithm and encyrption algorithm must match.
let stringToEn = encryptString.text!.data(using: .utf8)! // Take the input string and make it into a data object with UTF-8 formatting
let cfdataToEn = stringToEn as CFData // Take the above Data input string and make it into a CFData
// If we have no key in the device matching our tag, generate one
if (checkForKey == false) {
generateKey()
}
// Retrieve Private Key
let tag = "com.One28.One28-user.\(userId)".data(using: .utf8)!
let getQuery: [String: Any] = [kSecClass as String: kSecClassKey, // This is a query we make to find the Private key in the key chain based of the input we put inside of it
kSecAttrApplicationTag as String: tag, // We use the tag to search for our saved Private key in the KeyChain as it is unique
kSecAttrKeyType as String: kSecAttrKeyTypeRSA, // This says the keychain will be of type RSA
kSecReturnRef as String: true] // This says to return a reference to the key, and not the key data itself
let status = SecItemCopyMatching(getQuery as CFDictionary, &item)
if status == errSecSuccess {
key = item as! SecKey
}
else {
print("There was an error retrieving and setting the key in encryption.")
return
}
let pubKey = SecKeyCopyPublicKey(key) // Generate our public key from the Private Key for our User.
// This checks if the encryption type is supported by our public Key
do {
guard SecKeyIsAlgorithmSupported(pubKey!, .encrypt, .rsaEncryptionOAEPSHA512)
else {
error = "Error occurred when checking if encryption type is supported" as! Error // Errors like this will crach. Don't know how to fix it though.
throw error
}
} catch{ print("Our public key does not support this algorithm encryption") }
// This checks to make sure that our is smaller than 130 bytes smaller than our keys block size.
// This is necessary because the size has to be less than the allocated bits in the Private Key
do {
guard ((encryptString.text?.count)! < (SecKeyGetBlockSize(pubKey!)-130))
else {
error = "Text is too long to encrypt" as! Error
throw error
}
} catch { print("Input text is too large to encrypt.") }
//This does the actual encryption
do {
guard let cipherText = SecKeyCreateEncryptedData(pubKey!,
algorithm,
cfdataToEn as CFData,
&enError) as Data? else {
throw enError!.takeRetainedValue() as Error
}
let stringKey: String = cipherText.base64EncodedString()
encryptOutput.text = stringKey
} catch { print(enError) }
}
@IBAction func decrypt(_ sender: UIButton) {
var item: CFTypeRef?
var error: Error
var deError: Unmanaged<CFError>?
var key: SecKey
let algorithm: SecKeyAlgorithm = .rsaEncryptionOAEPSHA512 // Decryption algorithm matching the encyrption algorithm
let stringToDe: String = decryptString.text! // Next 3 lines are similar to encryption method
let dataToDe = Data(base64Encoded: stringToDe)
let cfdataToDe = dataToDe! as CFData
let tag = "com.One28.One28-user.\(userId)".data(using: .utf8)! // Here we see this important tag again
let getQuery: [String: Any] = [kSecClass as String: kSecClassKey, // Similar to encyrption getQuery
kSecAttrApplicationTag as String: tag,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecReturnRef as String: true]
let status = SecItemCopyMatching(getQuery as CFDictionary, &item)
if status == errSecSuccess {
key = item as! SecKey
}
else {
print("there was an error")
return
}
// This checks if the key supports our algorithm for decryption
do {
guard SecKeyIsAlgorithmSupported(key, .decrypt, algorithm)
else {
error = "Error occurred when checking if key supports decryption algorithm" as! Error
throw error
}
} catch { print(error) }
// This checks to see if our cipher text is the same as our keys blocksize
let dataSize: Int = (dataToDe?.count)!
do {
guard (dataSize == SecKeyGetBlockSize(key))
else {
error = "Error occurred when checking cipher text count" as! Error
throw error
}
} catch { print(error) }
// This does the decryption
do {
guard let clearText = SecKeyCreateDecryptedData(key,
algorithm,
cfdataToDe as CFData,
&deError) as Data? else {
throw deError!.takeRetainedValue() as Error
}
let stringKey: String = String(decoding: clearText, as: UTF8.self)
decryptOutput.text = stringKey
} catch { print(deError) }
}
// This function serves as a way to check if a Private Key with, a given unique identifier placed in our tag, exists
func keyCheck(unique: String) -> Bool {
var error: Unmanaged<CFError>?
let tag = "com.One28.One28-user.\(unique)".data(using: .utf8)! // As we see, this tag is important for finding the key in the KeyChain
let getquery: [String: Any] = [kSecClass as String: kSecClassKey, // This is a query we make to find the Private key in the key chain based of the input we put inside of it
kSecAttrApplicationTag as String: tag, // We use the tag to search for our saved Private key in the KeyChain as it is unique
kSecAttrKeyType as String: kSecAttrKeyTypeRSA, // This says the keychain will be of type RSA
kSecReturnRef as String: true] // This says to return a reference to the key, and not the key data itself
// Now that we have a query, we can use SecItemCopyMatching to find any keys that fit our descirption
var item: CFTypeRef? // This is an empty reference to populate with our private key temporarily
let status = SecItemCopyMatching(getquery as CFDictionary, &item) //This performs the search for our private key in the key chain
if status == errSecSuccess {
return true
}
else {
print("There was an error checking to see if a key existed. Most likely because a key with the given tag does not exist.")
}
return false
}
// This function generates a Private key and stores that Private key in the devices keychain
func generateKey(){
print("Generating a new key")
var error: Unmanaged<CFError>? // To catch error when making private key
let tag = "com.One28.One28-user.\(userId)".data(using: .utf8)! // This is to find the private key in the keychain later
let attributes: [String: Any] = // This is to create an asymmetric key pair
[kSecAttrKeyType as String: kSecAttrKeyTypeRSA, //This is the key types algorithm
kSecAttrKeySizeInBits as String: 2048, //Size in bits
kSecPrivateKeyAttrs as String: //To set up Private key to put in the keychain
[kSecAttrIsPermanent as String: true, //Making this true puts the private key in the keychain
kSecAttrApplicationTag as String: tag] // This is a unique value to find the key later. Which is why we put the users ID at the end of the tag because the users ID will always be unique.
]
do {
guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) // Creates random key like implied
else {
throw error!.takeRetainedValue() as Error
}
if let cfdata = SecKeyCopyExternalRepresentation(privateKey, &error) {
let data: Data = cfdata as Data
let stringKey: String = data.base64EncodedString() // This is to make the key human readable
//print("Private key generated: \(stringKey)") // Uncomment this line if you'd like to see the SecKey object
}
else {
print(error)
}
} catch { print(error) }
}
func deleteKey() {
var secret: AnyObject?
// Retrieve Private Key
let tag = "com.One28.One28-user.\(userId)".data(using: .utf8)!
print(secret)
let getQuery: [String: Any] = [kSecClass as String: kSecClassKey, // This is a query we make to find the Private key in the key chain based of the input we put inside of it
kSecAttrApplicationTag as String: tag, // We use the tag to search for our saved Private key in the KeyChain as it is unique
kSecAttrKeyType as String: kSecAttrKeyTypeRSA, // This says the keychain will be of type RSA
kSecReturnRef as String: kCFBooleanTrue,// This says to return a reference to the key, and not the key data itself
]
let status = SecItemCopyMatching(getQuery as CFDictionary, &secret)
print(secret!)
let delQuery: [String: Any] = [
kSecMatchItemList as String: secret,// This says to return a reference to the key, and not the key data itself
]
let delStatus = SecItemDelete(delQuery as CFDictionary)
guard delStatus == errSecSuccess || delStatus == errSecItemNotFound else {
print(delStatus)
print("Error")
return
}
print ("success")
if( delStatus == errSecSuccess || delStatus == errSecItemNotFound) {
print ("problem deleting the key chain")
}
else {
print("key deleted")
}
}
}
extension ViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if (sideCheck == 0) {
transition.presenting = true
return transition
}
else {
right.presenting = true
return right
}
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if (sideCheck == 0) {
transition.presenting = false
return transition
}
else {
right.presenting = false
return right
}
}
func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
transition.presenting = true
return transition
}
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
transition.presenting = false
return transition
}
@objc func swipeAction(swipe: UIPanGestureRecognizer) {
let mainStoryBoard = UIStoryboard(name: "Main", bundle: Bundle.main)
let screen2: UIViewController = mainStoryBoard.instantiateViewController(withIdentifier: "VC2") as! secondViewController
screen2.transitioningDelegate = self
switch swipe.state {
case .began:
present(screen2, animated: true, completion: nil)
default:
transition.handlePan(recognizer: swipe)
}
}
}
这是我在第二个ViewController中的实现:
import UIKit
class secondViewController: UIViewController, UIViewControllerTransitioningDelegate {
let transition = leftSwipeAnimator()
@IBAction func back(_ sender: UIButton) {
presentingViewController?.dismiss(animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let recognizer = UIPanGestureRecognizer(target: self, action: #selector(swipeAction(swipe:)))
self.view.addGestureRecognizer(recognizer)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
extension secondViewController{
@objc func swipeAction(swipe: UIPanGestureRecognizer) {
let mainStoryBoard = UIStoryboard(name: "Main", bundle: Bundle.main)
let screen1: UIViewController = mainStoryBoard.instantiateViewController(withIdentifier: "VC1") as! ViewController
screen1.transitioningDelegate = self
switch swipe.state {
case .began:
dismiss(animated: true, completion: nil)
default:
transition.handlePan(recognizer: swipe)
}
}
}
谢谢您的帮助!请注意,我已经尝试过开一门新课程,只是为了解雇而已,这没有用。最终有同样的问题。