I want to implement CABasicAnimation and have the UIViewController notified when the animation is completed. From this resource:
http://www.informit.com/articles/article.aspx?p=1168314&seqNum=2
I understood that I can specify the viewcontroller as a delegate for the animation and override animationDidStopmethod within the viewcontroller. However when I convert the following line of code into Swift:
[animation setDelegate:self];
like so:
animation.delegate = self //there are no setDelegate method
XCode complains:
Cannot assign value of type 'SplashScreenViewController' to type 'CAAnimationDelegate?'
What am I doing wrong? Am I missing something?
You need to make sure your viewController conforms to the CAAnimationDelegate.
class SplashScreenViewController: UIViewController, CAAnimationDelegate {
// your code, viewDidLoad and what not
override func viewDidLoad() {
super.viewDidLoad()
let animation = CABasicAnimation()
animation.delegate = self
// setup your animation
}
// MARK: - CAAnimation Delegate Methods
func animationDidStart(_ anim: CAAnimation) {
}
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
}
// Add any other CAAnimationDelegate Methods you want
}
You can also conform to the delegate by use of an extension:
extension SplasScreenViewController: CAAnimationDelegate {
func animationDidStart(_ anim: CAAnimation) {
}
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
}
}
Related
I am trying to get UIView object from CAAnimation. I have implemented the following CAAnimationDelegate method
public func animationDidStop(_ animation:CAAnimation, finished:Bool) {
// Need respective View from "animation:CAAnimation"
}
This class will be performing multiple animations with different views. So I need to find out which View's animation is completed in this delegate method. Please guide me if there is any possibility to get the view from this animation.
As matt suggested here is the way you can find which animation has been completed.
First of all you need to add different key value to your animation when you are creating it like shown below:
let theAnimation = CABasicAnimation(keyPath: "opacity")
theAnimation.setValue("animation1", forKey: "id")
theAnimation.delegate = self
let theAnimation2 = CABasicAnimation(keyPath: "opacity")
theAnimation2.setValue("animation2", forKey: "id")
theAnimation2.delegate = self
And in animationDidStop method you can identify animations:
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
if let val = anim.value(forKey: "id") as? String {
switch val {
case "animation1":
print("animation1")
case "animation2":
print("animation2")
default:
break
}
}
}
I have taken THIS answer and converted Objective c code to swift with switch case.
I just use tag property in UIView
animatedView.tag = 10
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
for myView in view.subviews {
if myView.tag == 10 {
myView.removeFromSuperview()
return
}
}
}
I have this UIViewController in which i've overrided the touchBegan and touchEnded functions. I also have a button that segueues (push) to another view controller with an SKView on it. But the overrided function in the first controller are still active ei. the calculations done there are still showing on the second ViewController.
Maybe theres something im missing or something im assuming thats wrong. Any help would be appreciated
This is the first view controller
import UIKit
import CoreMotion
class PortraitViewController: UIViewController {
var vc: UIViewController?
var startPoint: CGPoint?
var endPoint: CGPoint?
var movedPoint: CGPoint?
var previousMove = CGPoint(x: 0, y: 0)
var beginTouch: UITouch?
var scaleSum = 0
var isPortrait = true
let DEBUG: Bool = true
// MARK:
// MARK: Overriden Variables
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask{return .portrait}
open override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation{return .portrait}
override var shouldAutorotate: Bool {return false}
#IBAction func pinch(_ sender: UIPinchGestureRecognizer) {
if (isPortrait){
let scale = sender.scale
scaleSum += scale.exponent
print(scaleSum)
if(scaleSum > 10) {
scaleSum = 0
print(">10")
}
else if(scaleSum < -10) {
print("<10")
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if(isPortrait){
if let theTouch = touches.first {
endPoint = theTouch.location(in: self.view)
let diffx = (endPoint!.x - startPoint!.x)
let diffy = (endPoint!.y - startPoint!.y)
if(diffx != 0 && diffy != 0){
let vector = CGVector(dx: diffx, dy: diffy)
var angle = atan2(vector.dy, vector.dx) * CGFloat(180.0 / M_PI)
if angle < 0 { angle *= -1 } else { angle = 360 - angle }
}
}
}
super.touchesEnded(touches, with: event)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if(isPortrait){
if let theTouch = touches.first {
startPoint = theTouch.location(in: self.view)
}
}
super.touchesBegan(touches, with: event)
}
//One of my attempts to jerry rig a solution
#IBAction func prepToLandscape(_ sender: UIBarButtonItem) {
self.isPortrait = false
print("isPortrait = \(self.isPortrait)")
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
navigationController?.setNavigationBarHidden(true, animated: false)
}
}
This is the second view controller
import UIKit
import CoreMotion
import SpriteKit
class LandScapeViewController: UIViewController {
var vc: UIViewController?
// MARK:
// MARK: Overriden Variables
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask{return .landscape}
open override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation{return .landscapeLeft
// MARK:
// MARK: Functions
override func viewDidLoad() {
super.viewDidLoad()
let controllerStoryBoard = UIStoryboard(name: "Main", bundle: nil)
vc = controllerStoryBoard.instantiateViewController(withIdentifier: "Root")
// Part of my attempt to jerry rig a solution
let vcP: PortraitViewController = UIStoryboard(name:"Controller",bundle: nil).instantiateViewController(withIdentifier: "PortraitController") as! PortraitViewController
vcP.isPortrait = false
print("vcP.isPortrait = \(vcP.isPortrait)")
}
override func viewWillAppear(_ animated: Bool) {
self.view.isMultipleTouchEnabled = true
let scene = GameScene(size: joystickView.bounds.size)
scene.backgroundColor = .gray
if let skView = joystickView as? SKView {
skView.showsFPS = false
skView.ignoresSiblingOrder = true
skView.backgroundColor = .red
skView.presentScene(scene)
}
navigationController?.setNavigationBarHidden(true, animated: false)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func toPortrait(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: {() -> Void in
}
}
Assuming the code shown is everything relevant, this makes sense. What's happening is this: the touch events are hit-tested to the joystickView, but because you haven't implemented a custom touchesBegan(_:with:) on that view, the touch event is passed up to the next UIResponder in the responder chain. That would be the LandScapeViewController. But that class also doesn't implement a custom touchesBegan(_:with:), so the event passes to the next class, which in this case is PortraitViewController. Because PortraitViewController does implement that method, it gets called. There's your confusion.
To fix this, implement the touches… methods on UIResponder for either joystickView or LandScapeViewController, even if they do nothing – but don't call super in them! Note the following, from the touchesBegan(_:with:) documentation:
If you override this method without calling super (a common use pattern), you must also override the other methods for handling touch events, even if your implementations do nothing.
Where you're overriding touchesBegan(_:with:), you probably don't want to call super. This is because the super implementation is the one that says "oh, shoot, I don't know how to handle this – pass it up the chain!" But when you handle the touch, it should end there, because you're handling it! So only call super when you're not handling the touch – which in your case looks like never, at least for PortraitViewController.
For more information, check out Event Delivery: The Responder Chain.
So I have a simple UIPopoverPresentationController that displays some content.
User can dismiss it by tapping anywhere on the screen (default popover behaviour).
I want the popover to be dismissed if the user does any kind of tap or gesture on the screen. Preferably drag gesture.
Any idea if this is possible? And how?
try using touchesBegan:withEvent method
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
if touch.view == self.view {
self.dismiss()
} else {
return
}
}
}
VC is the view presented in the popover.
in the presentViewController:animated:completion: block
[self presentViewController:vc animated:YES completion:^{
UIView *v1 = vc.view.superview.superview.superview;
for (UIView* vx in v1.subviews) {
Class dimmingViewClass = NSClassFromString(#"UIDimmingView");
if ([vx isKindOfClass:[dimmingViewClass class]])
{
UIPanGestureRecognizer* pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(closePopoverOnSwipe)];
[vx addGestureRecognizer:pan];
}
}
}];
you have a UIDimmingView that holds the tap gesture that will close. just add to it. I am using the Class dimmingViewClass = NSClassFromString(#"UIDimmingView"); to avoid making direct use of undocumented APIs. I have not tried yet to send this hack to apple, but will try next week. I hope it will pass. But I tested this and it did call my selector.
I resolved this problem using custom view:
typealias Handler = (() -> Void)?
final class InteractionView: UIView {
var dismissHandler: Handler = nil
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return self
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.dismissHandler?()
}
}
In the viewDidAppear configure this view and add to popover containerView:
fileprivate func configureInteractionView() {
let interactionView = InteractionView(frame: self.view.bounds)
self.popoverPresentationController?.containerView?.addSubview(interactionView)
interactionView.backgroundColor = .clear
interactionView.isUserInteractionEnabled = true
interactionView.dismissHandler = { [weak self] in
self?.hide()
}
}
fileprivate func hide() {
self.dismiss(animated: true, completion: nil)
}
my solution for this problem.
for example if you create a class UIViewController named MyPopoverViewController to present PopViewController.
then in the viewDidLoad() or viewWillAppear(_ animated:) Method add two GestureRecognizer as follows:
protocal MyPopoverControllerDelegate {
func shouldDismissPopover()
}
class MyPopoverViewController : UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// back trace to root view, if it is a UIWindows, add PanGestureRecognizer
// and LongPressGestureRecognizer, to dismiss this PopoverViewController
for c in sequence(first: self.view, next: { $0.superview}) {
if let w = c as? UIWindow {
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(dismissPopover(gesture:)))
w.addGestureRecognizer(panGestureRecognizer)
let longTapGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(dismissPopover(gesture:)))
w.addGestureRecognizer(longTapGestureRecognizer)
}
}
#objc private func dismissPopover(gesture: UIGestureRecognizer) {
delegate?.shouldDismissPopover()
}
}
then in your main ViewController, which this PopOverViewController presents, implements the Method of the Protocol.
extension YourMainViewController: MyPopoverControllerDelegate {
func shouldDismissPopover() {
self.presentedViewController?.dismiss(animated: true, completion: nil)
}
}
Explanation
I have a game that runs in landscape, and I'm trying to send an email, also, in portrait mode.
Basically:
if the actual view is GameViewController = .Landscape;
if the actual view is MailViewController = .AllButUpsideDown.
Demo code
I built this code below, but I'm having an issue with it:
Mail isn't being called when touching the screen.
You can download this code here.
GameScene
import SpriteKit
class GameScene: SKScene {
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
//Mail
NSNotificationCenter.defaultCenter().postNotificationName("openMail", object: nil)
}
}
GameViewController
import UIKit
import SpriteKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Set view size.
let scene = GameScene(size: view.bounds.size)
// Configure the view.
let skView = view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .ResizeFill
skView.presentScene(scene)
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return .Landscape
} else {
return .Landscape
}
}
}
MailViewController
import UIKit
import SpriteKit
import MessageUI
class MailViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Register mail observer (so I can call from GameScene)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(MailViewController.openMailController), name: "openMail", object: nil)
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return .AllButUpsideDown
} else {
return .All
}
}
}
//Mail
extension MailViewController: MFMailComposeViewControllerDelegate {
func openMailController() {
let mailComposerVC = MFMailComposeViewController()
mailComposerVC.mailComposeDelegate = self //extremely important to set the mailComposeDelegate property, not the delegate property
self.presentViewController(mailComposerVC, animated: true, completion: nil)
}
func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {
self.dismissViewControllerAnimated(true, completion: nil)
}
}
Thanks for any help,
Luiz.
Problem with your provided code is that observer in MailViewController class view did load method is not called. So Observer is not set to receive any notification.
Also you didn't called segue anywhere in your code to call MailViewController.
Solution: By using Delegate Method
GameScene class updated:
import SpriteKit
// Protocol
protocol gameSceneDelegate {
func openMail()
}
// Protocol
class GameScene: SKScene {
// Delegate
var sceneDelegate : gameSceneDelegate?
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
//Mail
// NSNotificationCenter.defaultCenter().postNotificationName("openMail", object: nil)
// delegate method called on receiving touches
self.sceneDelegate?.openMail()
}
}
GameViewController class updated:
import UIKit
import SpriteKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Set view size.
let scene = GameScene(size: view.bounds.size)
// Delegate Confirmation
scene.sceneDelegate = self
// Configure the view.
let skView = view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .ResizeFill
skView.presentScene(scene)
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return .Landscape
} else {
return .Landscape
}
}
}
// Delegate Method Implementation
extension GameViewController : gameSceneDelegate{
func openMail(){
self.performSegueWithIdentifier("openMail", sender: self);
}
}
Mail View Controller:
import UIKit
import SpriteKit
import MessageUI
class MailViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Register mail observer (so I can call from GameScene)
// NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(MailViewController.openMailController), name: "openMail", object: nil)
self.openMailController()
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return .AllButUpsideDown
} else {
return .All
}
}
}
//Mail
extension MailViewController: MFMailComposeViewControllerDelegate {
func openMailController() {
let mailComposerVC = MFMailComposeViewController()
mailComposerVC.mailComposeDelegate = self //extremely important to set the mailComposeDelegate property, not the delegate property
self.presentViewController(mailComposerVC, animated: true, completion: nil)
}
func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {
self.dismissViewControllerAnimated(true, completion: nil)
}
}
Last change in storyboard:
click segue connecting GameViewController and MailViewController and change identifier to - openMail or whatever you want to keep
I am wondering if this is actually possible to do. I know I can flip an UIImageView, but the camera on iOS 7 shows the picture flipped horizontally before you select it to display in a uiimageview. Is there any way around this annoyance? If there is please let me know. This can be very annoying to the user as they might not know any better.
Edit:here is my code.
UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init];
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
{
[imagePickerController setSourceType:UIImagePickerControllerSourceTypeCamera];
}
// image picker needs a delegate,
[imagePickerController setDelegate:self];
// Place image picker on the screen
[self presentModalViewController:imagePickerController animated:YES];
You cannot easily do it, according to Apple's UIImagePickerClass Reference:
Important: The UIImagePickerController class supports portrait mode only. This class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified, with one exception. You can assign a custom view to the cameraOverlayView property and use that view to present additional information or manage the interactions between the camera interface and your code.
This means that if you want to mess with the orientation, you are going to need to create your own camera controller and handle it all yourself. Check out this stack overflow question and answer for more along these lines. Happy coding!
Full Working Example in Swift, which answers to the initial question of this post (tested on an iPhone 5c using iOS 8.2):
import UIKit
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIActionSheetDelegate {
#IBOutlet var myUIImageView: UIImageView!
var myUIImagePickerController: UIImagePickerController!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
println("viewWillAppear(animated: Bool) method called.")
super.viewWillAppear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
override func viewWillDisappear(animated: Bool) {
println("viewWillDisappear(animated: Bool) method called.")
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "cameraChanged:", name: "AVCaptureDeviceDidStartRunningNotification", object: nil)
}
/* UIImagePickerControllerDelegate Section */
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
if(self.myUIImagePickerController.sourceType == UIImagePickerControllerSourceType.Camera) {
self.myUIImageView.image = info[UIImagePickerControllerEditedImage] as? UIImage
} else {
self.myUIImageView.image = info[UIImagePickerControllerOriginalImage] as? UIImage
}
self.dismissViewControllerAnimated(true, completion: nil)
}
func imagePickerControllerDidCancel(picker: UIImagePickerController) {
self.dismissViewControllerAnimated(true, completion: nil)
}
/*
You can choose to use one of the UIResponder methods:
touchesBegan, touchesMoved, touchesEnded etc, in order to detect the touch
on the UIImageView.
*/
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
let touch: UITouch? = touches.anyObject() as? UITouch
if (touch?.view == myUIImageView) {
println("myUIImageView has been tapped by the user.")
self.takingAPictureUsingTheCamera()
}
}
func takingAPictureUsingTheCamera() {
self.myUIImagePickerController = UIImagePickerController()
self.myUIImagePickerController.delegate = self // Set the delegate
self.myUIImagePickerController.sourceType = UIImagePickerControllerSourceType.Camera
self.myUIImagePickerController.cameraDevice = UIImagePickerControllerCameraDevice.Front
// self.myUIImagePickerController.editing = true
self.myUIImagePickerController.allowsEditing = true
self.presentViewController(self.myUIImagePickerController, animated: true, completion: nil)
}
func cameraChanged(notification: NSNotification) {
println("cameraChanged(notification: NSNotification) method called.")
self.myUIImagePickerController.cameraViewTransform = CGAffineTransformIdentity
if(self.myUIImagePickerController.cameraDevice == UIImagePickerControllerCameraDevice.Front){
self.myUIImagePickerController.cameraViewTransform = CGAffineTransformScale(self.myUIImagePickerController.cameraViewTransform, -1, 1)
}
}
}// End class