I'm trying to create a horizontally sliding segue that'll pan from one view to the next. I have the panning down, but it shows a black space in the area being revealed where it should be showing the next view controller. This is how I'm implementing my segue
class SlideSegue: UIStoryboardSegue {
enum Direction {
case Left
case Right
}
// MARK: - Properties
var direction = Direction.Left
// MARK: -
override func perform() {
let srcVc = self.sourceViewController
let destVc = self.destinationViewController
let destView = destVc.view.snapshotViewAfterScreenUpdates(true)
var frame = destView.frame
frame.origin.x = CGFloat(valForDirection()) * srcVc.view.frame.width
destView.frame = frame
srcVc.view.addSubview(destView)
UIView.animateWithDuration(0.3, animations: { () -> Void in
let x = CGFloat(self.valForDirection()) * srcVc.view.bounds.width
let slide = CGAffineTransformMakeTranslation(x, 0)
srcVc.view.transform = slide
}) { (finished: Bool) -> Void in
srcVc.presentViewController(destVc, animated: false, completion: nil)
destView.removeFromSuperview()
}
}
func valForDirection() -> Int {
return direction == Direction.Left ? -1 : 1
}
}
class SlideLeftSegue: SlideSegue {
override func perform() {
self.direction = Direction.Left
super.perform()
}
}
class SlideRightSegue: SlideSegue {
override func perform() {
self.direction = Direction.Right
super.perform()
}
}
EDIT
EDIT (SOLVED)
This is the final working solution thanks the two answers below
class SlideSegue: UIStoryboardSegue {
enum Direction: Int {
case Left = 1
case Right = -1
}
// MARK: - Properties
var direction = Direction.Left
// MARK: -
override func perform() {
let srcVc = self.sourceViewController
let destVc = self.destinationViewController
let destView = destVc.view.snapshotViewAfterScreenUpdates(true)
var frame = destView.frame
frame.origin.x = CGFloat(direction.rawValue) * srcVc.view.frame.width
destView.frame = frame
srcVc.view.addSubview(destView)
UIView.animateWithDuration(0.3, animations: { () -> Void in
let x = -CGFloat(self.direction.rawValue) * srcVc.view.bounds.width
let slide = CGAffineTransformMakeTranslation(x, 0)
srcVc.view.transform = slide
}) { (finished: Bool) -> Void in
srcVc.presentViewController(destVc, animated: false, completion: nil)
destView.removeFromSuperview()
}
}
}
class SlideLeftSegue: SlideSegue {
override func perform() {
self.direction = Direction.Left
super.perform()
}
}
class SlideRightSegue: SlideSegue {
override func perform() {
self.direction = Direction.Right
super.perform()
}
}
I test with the following modification, and it work well.
func valForDirection() -> Int {
return direction == Direction.Left ? 1 : -1
}
If sliding to left, the destView's originX should be it's width. else -width.
Try changing your SlideSegue class to this, removing the valForDirection function (I have taken the liberty to refactor it a little, if you don't mind)
class FirstCustomSegue: UIStoryboardSegue {
enum Direction: Int {
case Left = 1
case Right = -1
}
// MARK: - Properties
var direction = Direction.Left
// MARK: - Perform
override func perform() {
let srcVc = self.sourceViewController
let destVc = self.destinationViewController
destVc.view.frame = srcVc.view.frame
destVc.view.frame.origin.x = CGFloat(direction.rawValue) * srcVc.view.frame.width
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(destVc.view, aboveSubview: srcVc.view)
UIView.animateWithDuration(0.3, animations: { () -> Void in
let x = CGFloat(self.direction.rawValue) * srcVc.view.bounds.width
destVc.view.frame.origin.x = 0
srcVc.view.frame.origin.x = -x
}) { (finished: Bool) -> Void in
srcVc.presentViewController(destVc, animated: false, completion: nil)
}
}
}
I haven't tested this yet, so I'm not 100% sure that the syntax is correct.
UPDATE: MatthewLuiHK seems to be right. All you have to do is flip around the values.
Credits: http://www.appcoda.com/custom-segue-animations/
This code works for me, it reveals the new view controller from beneath the VC being swiped away, however when the segue is complete the view controller briefly flashes black.
Any ideas how to ensure a smooth transition every time?
import UIKit
class SlideSegue: UIStoryboardSegue {
enum Direction: Int {
case Left = 1
case Right = -1
}
// MARK: - Properties
var direction = Direction.Left
// MARK: -
override func perform() {
let srcVc = self.sourceViewController
let destVc = self.destinationViewController
let destView = destVc.view.snapshotViewAfterScreenUpdates(true)
var frame = destView.frame
frame.origin.x = CGFloat(direction.rawValue) * srcVc.view.frame.width
destView.frame = frame
srcVc.view.addSubview(destView)
UIView.animateWithDuration(0.3, animations: { () -> Void in
let x = -CGFloat(self.direction.rawValue) * srcVc.view.bounds.width
let slide = CGAffineTransformMakeTranslation(x, 0)
srcVc.view.transform = slide
}) { (finished: Bool) -> Void in
srcVc.presentViewController(destVc, animated: false, completion: nil)
destView.removeFromSuperview()
}
}
}
class SlideLeftSegue: SlideSegue {
override func perform() {
self.direction = Direction.Left
super.perform()
}
}
class SlideRightSegue: SlideSegue {
override func perform() {
self.direction = Direction.Right
super.perform()
}
}
Many thanks
Related
I am trying to make a pop over form the bottom of screen using UIPresentationController, so I followed raywenderlich guide here : https://www.raywenderlich.com/139277/uipresentationcontroller-tutorial-getting-started. I did the exact same thing, I only change the size and y position of the frame. The pop up consist of buttons that open the share sheet , but for some reason when I open the sheet then click "save to files", the "shave to files" view shows up and when I hit cancel my pop over goes full screen for a moment then changes to my custom size.
I tried to debug the app and found out that containerViewWillLayoutSubviews() doesn't get called untill the "save to file" view is dismissed. Anyone have an idea on how to solve this. Thank you
this is my code :
main :
final class MainViewController: UIViewController {
// MARK: - Properties
lazy var slideInTransitioningDelegate = SlideInPresentationManager()
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func showPopup(_ sender: Any) {
let controller = storyboard.instantiateViewController(withIdentifier: NSStringFromClass(MyPopUpController.self))
as! MyPopUpController
slideInTransitioningDelegate.direction = .bottom
slideInTransitioningDelegate.disableCompactHeight = true
controller.transitioningDelegate = slideInTransitioningDelegate
controller.modalPresentationStyle = .custom
}
mypopucontroller
final class MyPopUpController: UIViewController {
#IBAction func share(_ sender: Any) {
let activityController = UIActivityViewController(activityItems: ["message"], applicationActivities: nil)
present(activityController, animated: true)
}
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
}
slide in presentation controller :
final class SlideInPresentationController: UIPresentationController {
// MARK: - Properties
fileprivate var dimmingView: UIView!
private var direction: PresentationDirection
override var frameOfPresentedViewInContainerView: CGRect {
var frame: CGRect = .zero
frame.size = size(forChildContentContainer: presentedViewController, withParentContainerSize: containerView!.bounds.size)
switch direction {
case .right:
frame.origin.x = containerView!.frame.width*(1.0/3.0)
case .bottom:
frame.origin.y = containerView!.frame.height*0.5
default:
frame.origin = .zero
}
return frame
}
// MARK: - Initializers
init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, direction: PresentationDirection) {
self.direction = direction
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
setupDimmingView()
}
override func presentationTransitionWillBegin() {
containerView?.insertSubview(dimmingView, at: 0)
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:|[dimmingView]|", options: [], metrics: nil, views: ["dimmingView": dimmingView]))
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|[dimmingView]|", options: [], metrics: nil, views: ["dimmingView": dimmingView]))
guard let coordinator = presentedViewController.transitionCoordinator else {
dimmingView.alpha = 1.0
return
}
coordinator.animate(alongsideTransition: { _ in
self.dimmingView.alpha = 1.0
})
}
override func dismissalTransitionWillBegin() {
guard let coordinator = presentedViewController.transitionCoordinator else {
dimmingView.alpha = 0.0
return
}
coordinator.animate(alongsideTransition: { _ in
self.dimmingView.alpha = 0.0
})
}
override func containerViewWillLayoutSubviews() {
presentedView?.frame = frameOfPresentedViewInContainerView
}
override func size(forChildContentContainer container: UIContentContainer, withParentContainerSize parentSize: CGSize) -> CGSize {
switch direction {
case .left, .right:
return CGSize(width: parentSize.width*(2.0/3.0), height: parentSize.height)
case .bottom, .top:
return CGSize(width: parentSize.width, height: parentSize.height*0.67)
}
}
}
// MARK: - Private
private extension SlideInPresentationController {
func setupDimmingView() {
dimmingView = UIView()
dimmingView.translatesAutoresizingMaskIntoConstraints = false
dimmingView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
dimmingView.alpha = 0.0
let recognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(recognizer:)))
dimmingView.addGestureRecognizer(recognizer)
}
dynamic func handleTap(recognizer: UITapGestureRecognizer) {
presentingViewController.dismiss(animated: true)
}
}
slidein manager :
final class SlideInPresentationManager: NSObject {
// MARK: - Properties
var direction = PresentationDirection.left
var disableCompactHeight = false
}
// MARK: - UIViewControllerTransitioningDelegate
extension SlideInPresentationManager: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
let presentationController = SlideInPresentationController(presentedViewController: presented, presenting: presenting, direction: direction)
presentationController.delegate = self
return presentationController
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return SlideInPresentationAnimator(direction: direction, isPresentation: true)
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return SlideInPresentationAnimator(direction: direction, isPresentation: false)
}
}
// MARK: - UIAdaptivePresentationControllerDelegate
extension SlideInPresentationManager: UIAdaptivePresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
if traitCollection.verticalSizeClass == .compact && disableCompactHeight {
return .overFullScreen
} else {
return .none
}
}
func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
guard case(.overFullScreen) = style else { return nil }
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "RotateViewController")
}
}
slidein animator:
final class SlideInPresentationAnimator: NSObject {
// MARK: - Properties
let direction: PresentationDirection
let isPresentation: Bool
// MARK: - Initializers
init(direction: PresentationDirection, isPresentation: Bool) {
self.direction = direction
self.isPresentation = isPresentation
super.init()
}
}
// MARK: - UIViewControllerAnimatedTransitioning
extension SlideInPresentationAnimator: UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let key = isPresentation ? UITransitionContextViewControllerKey.to : UITransitionContextViewControllerKey.from
let controller = transitionContext.viewController(forKey: key)!
if isPresentation {
transitionContext.containerView.addSubview(controller.view)
}
let presentedFrame = transitionContext.finalFrame(for: controller)
var dismissedFrame = presentedFrame
switch direction {
case .left:
dismissedFrame.origin.x = -presentedFrame.width
case .right:
dismissedFrame.origin.x = transitionContext.containerView.frame.size.width
case .top:
dismissedFrame.origin.y = -presentedFrame.height
case .bottom:
dismissedFrame.origin.y = transitionContext.containerView.frame.size.height
}
let initialFrame = isPresentation ? dismissedFrame : presentedFrame
let finalFrame = isPresentation ? presentedFrame : dismissedFrame
let animationDuration = transitionDuration(using: transitionContext)
controller.view.frame = initialFrame
UIView.animate(withDuration: animationDuration, animations: {
controller.view.frame = finalFrame
}) { finished in
transitionContext.completeTransition(finished)
}
}
}
You can try to subclass UIPresentationController and override var presentedView: UIView? and enforce presentedView's frame.
override var presentedView: UIView? {
super.presentedView?.frame = frameOfPresentedViewInContainerView
return super.presentedView
}
See example: "Custom View Controller Presentation" from Kyle Bashour https://kylebashour.com/posts/custom-view-controller-presentation-tips
This question already has answers here:
How to disable user interaction on MKMapView?
(4 answers)
Closed 6 years ago.
I'm designing a Slide Navigation Panel due to that tutorial.
Everything works fine. Now I want to go further and work on some UX issues.
This is how my app look like now. But still I can manipulate MapView. My goal is to do two things:
1) Prevent all interaction with MapView, such as scaling and moving
2) Close navigation panel, while tapping on viewController with map.
The main file for controlling this two controlller is containerViewController. Here is the source code for it:
import UIKit
import QuartzCore
enum SlideOutState {
case BothCollapsed
case LeftPanelExpanded
}
class ContainerViewController: UIViewController {
var centerNavigationController: UINavigationController!
var centerViewController: ViewController!
var currentState: SlideOutState = .BothCollapsed {
didSet {
let shouldShowShadow = currentState != .BothCollapsed
showShadowForCenterViewController(shouldShowShadow)
}
}
var leftViewController: SidePanelViewController?
override func viewDidLoad() {
super.viewDidLoad()
centerViewController = UIStoryboard.centerViewController()
centerViewController.delegate = self
// wrap the centerViewController in a navigation controller, so we can push views to it
// and display bar button items in the navigation bar
centerNavigationController = UINavigationController(rootViewController: centerViewController)
view.addSubview(centerNavigationController.view)
addChildViewController(centerNavigationController)
centerNavigationController.didMoveToParentViewController(self)
}
}
let centerPanelExpandedOffset: CGFloat = 60
extension ContainerViewController: CenterViewControllerDelegate {
func toggleLeftPanel() {
let notAlreadyExpanded = (currentState != .LeftPanelExpanded)
if notAlreadyExpanded {
addLeftPanelViewController()
}
animateLeftPanel(shouldExpand: notAlreadyExpanded)
}
func addLeftPanelViewController() {
if (leftViewController == nil) {
leftViewController = UIStoryboard.leftViewController()
leftViewController!.categories = Category.allCats()
addChildSidePanelController(leftViewController!)
}
}
func addChildSidePanelController(sidePanelController: SidePanelViewController) {
view.insertSubview(sidePanelController.view, atIndex: 0)
addChildViewController(sidePanelController)
sidePanelController.didMoveToParentViewController(self)
}
func animateLeftPanel(shouldExpand shouldExpand: Bool) {
if (shouldExpand) {
currentState = .LeftPanelExpanded
animateCenterPanelXPosition(targetPosition: CGRectGetWidth(centerNavigationController.view.frame) - centerPanelExpandedOffset)
} else {
animateCenterPanelXPosition(targetPosition: 0) { finished in
self.currentState = .BothCollapsed
self.leftViewController!.view.removeFromSuperview()
self.leftViewController = nil;
}
}
}
func animateCenterPanelXPosition(targetPosition targetPosition: CGFloat, completion: ((Bool) -> Void)! = nil) {
UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .CurveEaseInOut, animations: {
self.centerNavigationController.view.frame.origin.x = targetPosition
}, completion: completion)
}
func showShadowForCenterViewController(shouldShowShadow: Bool) {
if (shouldShowShadow) {
centerNavigationController.view.layer.shadowOpacity = 0.8
} else {
centerNavigationController.view.layer.shadowOpacity = 0.0
}
}
}
private extension UIStoryboard {
class func mainStoryboard() -> UIStoryboard { return UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()) }
class func leftViewController() -> SidePanelViewController? {
return mainStoryboard().instantiateViewControllerWithIdentifier("LeftViewController") as? SidePanelViewController
}
class func centerViewController() -> ViewController? {
return mainStoryboard().instantiateViewControllerWithIdentifier("CenterViewController") as? ViewController
}
}
My idea is to create variable that stands for active side panel and write a function, that will solve my issues:
I see something like that:
func blockCenterView() {
let AlreadyExpanded = (currentState = .LeftPanelExpanded)
if AlreadyExpanded {
//no idea what to write here
}
}
Am I right, or there exist other variants for solving that problem?
Try to set the following properties to false
mapView.zoomEnabled = false
mapView.scrollEnabled = false
mapView.userInteraction = false
Also check the Interface Builder for the user Interactions:
Add this code whereever you want to stop interaction with mapView
mapView.userInteractionEnabled = false
To enable it back,
mapView.userInteractionEnabled = true
Note : mapView is an instance of MKMapView
PS
SWRevealViewController
Source : Github
Step By Step Tutorial : More Flexible Slide View
This is what I was able to create using this awesome framework
App Link : AirMate on AppStore
I have a storyboard like that :
Article View is presented from segue and animation :
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
if segue.identifier == "showArticleFromArticles" {
let ViewToShow = segue.destinationViewController as! ArticleView
ViewToShow.articleToShow = ArticleToShow2
ViewToShow.transitioningDelegate = self
}
}
My animation :
class TransitionManager: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerInteractiveTransitioning, UIViewControllerTransitioningDelegate, UIViewControllerContextTransitioning {
weak var transitionContext: UIViewControllerContextTransitioning?
var sourceViewController: UIViewController! {
didSet {
print("set")
print(sourceViewController)
enterPanGesture = UIScreenEdgePanGestureRecognizer()
enterPanGesture.addTarget(self, action:"panned:")
enterPanGesture.edges = UIRectEdge.Left
let newSource = sourceViewController as! ArticleView
newSource.WebView.addGestureRecognizer(enterPanGesture)
}
}
let duration = 1.0
var presenting = true
var originFrame = CGRectNull
private var didStartedTransition = false
private var animated = false
private var interactive = false
private var AnimationStyle = UIModalPresentationStyle(rawValue: 1)
private var didFinishedTransition = false
private var percentTransition: CGFloat = 0.0
private var enterPanGesture: UIScreenEdgePanGestureRecognizer!
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
// get reference to our fromView, toView and the container view that we should perform the transition in
let container = transitionContext.containerView()
let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
// set up from 2D transforms that we'll use in the animation
let offScreenRight = CGAffineTransformMakeTranslation(container!.frame.width, 0)
let offScreenLeft = CGAffineTransformMakeTranslation(container!.frame.width, 0)
// start the toView to the right of the screen
toView.transform = offScreenRight
// add the both views to our view controller
container!.addSubview(toView)
container!.addSubview(fromView)
// get the duration of the animation
// DON'T just type '0.5s' -- the reason why won't make sense until the next post
// but for now it's important to just follow this approach
let duration = self.transitionDuration(transitionContext)
// perform the animation!
// for this example, just slid both fromView and toView to the left at the same time
// meaning fromView is pushed off the screen and toView slides into view
// we also use the block animation usingSpringWithDamping for a little bounce
UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.8, options: UIViewAnimationOptions.TransitionFlipFromRight, animations: {
fromView.transform = offScreenLeft
toView.transform = CGAffineTransformIdentity
}, completion: { finished in
// tell our transitionContext object that we've finished animating
transitionContext.completeTransition(true)
})
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return duration
}
func startInteractiveTransition(transitionContext: UIViewControllerContextTransitioning) {
interactive = true
// get reference to our fromView, toView and the container view that we should perform the transition in
let container = transitionContext.containerView()
let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
// set up from 2D transforms that we'll use in the animation
let offScreenRight = CGAffineTransformMakeTranslation(container!.frame.width, 0)
let offScreenLeft = CGAffineTransformMakeTranslation(container!.frame.width, 0)
// start the toView to the right of the screen
toView.transform = offScreenRight
// add the both views to our view controller
container!.addSubview(toView)
container!.addSubview(fromView)
// get the duration of the animation
// DON'T just type '0.5s' -- the reason why won't make sense until the next post
// but for now it's important to just follow this approach
let duration = self.transitionDuration(transitionContext)
// perform the animation!
// for this example, just slid both fromView and toView to the left at the same time
// meaning fromView is pushed off the screen and toView slides into view
// we also use the block animation usingSpringWithDamping for a little bounce
UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.8, options: UIViewAnimationOptions.TransitionFlipFromRight, animations: {
fromView.transform = offScreenLeft
toView.transform = CGAffineTransformIdentity
}, completion: { finished in
// tell our transitionContext object that we've finished animating
transitionContext.completeTransition(true)
})
}
func containerView() -> UIView? {
return sourceViewController?.view
}
func viewControllerForKey(key: String) -> UIViewController? {
return sourceViewController?.storyboard!.instantiateViewControllerWithIdentifier(key)
}
func viewForKey(key: String) -> UIView? {
return sourceViewController?.storyboard!.instantiateViewControllerWithIdentifier(key).view
}
func initialFrameForViewController(vc: UIViewController) -> CGRect {
return vc.view.frame
}
func finalFrameForViewController(vc: UIViewController) -> CGRect {
return vc.view.frame
}
func isAnimated() -> Bool {
return animated
}
func isInteractive() -> Bool {
return interactive
}
func presentationStyle() -> UIModalPresentationStyle {
return AnimationStyle!
}
func completeTransition(didComplete: Bool) {
didFinishedTransition = didComplete
}
func updateInteractiveTransition(percentComplete: CGFloat) {
percentTransition = percentComplete
}
func finishInteractiveTransition() {
completeTransition(true)
}
func cancelInteractiveTransition() {
completeTransition(true)
}
func transitionWasCancelled() -> Bool {
return didFinishedTransition
}
func targetTransform() -> CGAffineTransform {
return CGAffineTransform()
}
func panned(pan: UIPanGestureRecognizer) {
//print(pan.translationInView(sourceViewController!.view))
switch pan.state {
case .Began:
animated = true
didStartedTransition = true
didFinishedTransition = false
sourceViewController?.dismissViewControllerAnimated(true, completion: nil)
if transitionContext != nil {
startInteractiveTransition(transitionContext!)
}
break
case .Changed:
percentTransition = CGFloat(pan.translationInView(sourceViewController!.view).x / sourceViewController!.view.frame.width)
print(percentTransition)
updateInteractiveTransition(percentTransition)
break
case .Ended, .Failed, .Cancelled:
animated = false
didStartedTransition = false
didFinishedTransition = true
finishInteractiveTransition()
break
case .Possible:
break
}
}
}
From Article View, I call dismiss view like that :
#IBAction func Quit(sender: UIBarButtonItem) {
self.dismissViewControllerAnimated(true, completion: nil)
}
and :
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.presenting = false
return transition
}
And i add the PanGesture like that :
let transition = TransitionManager()
self.transition.sourceViewController = self
But Pan Gesture just dismiss the view, and Interactive is not available
Because i call :
self.dismissViewControllerAnimated(true, completion: nil)
during UIPanGestureRecognizer.began
How can I do this ?
I am using Xcode 7, Swift 2, iOS 9
Thanks !
I found the solution :
i should just use
startInteractiveTransition
to instantiate some things
and use :
func updateInteractiveTransition(percentComplete: CGFloat) {
if self.reverse {
print(percentComplete)
self.tovc.view.frame.origin.x = (self.fromvc.view.frame.maxX * (percentComplete)) - self.fromvc.view.frame.maxX
}
}
to custom my transition.
Easy to use, Just inherent Your UIViewController with InteractiveViewController and you are done
InteractiveViewController
call method showInteractive() from your controller to show as Interactive.
I am developing an iOS application in Swift. I want to display a left slide out menu so I followed Ray Wenderlich's tutorial ( http://www.raywenderlich.com/78568/create-slide-out-navigation-panel-swift#comments )
This tutorial is for a left and right slide out menu, but I only want the left one. So I used the part about the left one; however, it only displays a black menu instead of the left one I want. How can I make the menu display correctly?
This is the code of the site modified to show only the left part, see it , it removes all the part where the right panel is open.
import UIKit
import QuartzCore
enum SlideOutState {
case Collapsed
case Expanded
}
class ContainerViewController: UIViewController, CenterViewControllerDelegate, UIGestureRecognizerDelegate {
var centerNavigationController: UINavigationController!
var centerViewController: CenterViewController!
var mainPanelExpandedOffset: CGFloat = 518
var currentState: SlideOutState = .Collapsed {
didSet {
let shouldShowShadow = currentState != .Collapsed
showShadowForCenterViewController(shouldShowShadow)
}
}
var leftViewController: SidePanelViewController?
var rightViewController: SidePanelViewController?
let centerPanelExpandedOffset: CGFloat = 60
override func viewDidLoad() {
super.viewDidLoad()
centerViewController = UIStoryboard.centerViewController()
centerViewController.delegate = self
// wrap the centerViewController in a navigation controller, so we can push views to it
// and display bar button items in the navigation bar
centerNavigationController = UINavigationController(rootViewController: centerViewController)
view.addSubview(centerNavigationController.view)
addChildViewController(centerNavigationController)
centerNavigationController.didMoveToParentViewController(self)
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: "handlePanGesture:")
centerNavigationController.view.addGestureRecognizer(panGestureRecognizer)
}
// MARK: CenterViewController delegate methods
func toggleLeftPanel() {
let notAlreadyExpanded = (currentState != .Expanded)
if notAlreadyExpanded {
addLeftPanelViewController()
}
animateLeftPanel(shouldExpand: notAlreadyExpanded)
}
func collapseSidePanels() {
switch (currentState) {
case .Expanded:
toggleLeftPanel()
default:
break
}
}
func addLeftPanelViewController() {
if (leftViewController == nil) {
leftViewController = UIStoryboard.leftViewController()
leftViewController!.animals = Animal.allCats()
addChildSidePanelController(leftViewController!)
}
}
func addChildSidePanelController(sidePanelController: SidePanelViewController) {
sidePanelController.delegate = centerViewController
view.insertSubview(sidePanelController.view, atIndex: 0)
addChildViewController(sidePanelController)
sidePanelController.didMoveToParentViewController(self)
}
func animateLeftPanel(#shouldExpand: Bool) {
if (shouldExpand) {
currentState = .Expanded
animateCenterPanelXPosition(targetPosition: CGRectGetWidth(leftViewController!.view.frame) - 100)
} else {
animateCenterPanelXPosition(targetPosition: 0) { finished in
self.currentState = .Collapsed
self.leftViewController?.view.removeFromSuperview()
self.leftViewController = nil;
}
}
}
func animateCenterPanelXPosition(#targetPosition: CGFloat, completion: ((Bool) -> Void)! = nil) {
UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .CurveEaseInOut, animations: {
self.centerNavigationController.view.frame.origin.x = targetPosition
}, completion: completion)
}
func showShadowForCenterViewController(shouldShowShadow: Bool) {
if (shouldShowShadow) {
centerNavigationController.view.layer.shadowOpacity = 0.8
} else {
centerNavigationController.view.layer.shadowOpacity = 0.0
}
}
// MARK: Gesture recognizer
func handlePanGesture(recognizer: UIPanGestureRecognizer) {
let gestureIsDraggingFromLeftToRight = (recognizer.velocityInView(view).x > 0)
switch(recognizer.state) {
case .Changed:
if (gestureIsDraggingFromLeftToRight && currentState == .Expanded){
recognizer.view!.center.x = recognizer.view!.center.x + recognizer.translationInView(view).x
recognizer.setTranslation(CGPointZero, inView: view)
currentState = .Collapsed
}
case .Ended:
if (gestureIsDraggingFromLeftToRight) {
// animate the side panel open or closed based on whether the view has moved more or less than halfway
let hasMovedGreaterThanHalfway = recognizer.view!.center.x > view.bounds.size.width
animateLeftPanel(shouldExpand: hasMovedGreaterThanHalfway)
}
default:
break
}
}
}
private extension UIStoryboard {
class func mainStoryboard() -> UIStoryboard { return UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()) }
class func leftViewController() -> SidePanelViewController? {
return mainStoryboard().instantiateViewControllerWithIdentifier("LeftViewController") as? SidePanelViewController
}
class func rightViewController() -> SidePanelViewController? {
return mainStoryboard().instantiateViewControllerWithIdentifier("RightViewController") as? SidePanelViewController
}
class func centerViewController() -> CenterViewController? {
return mainStoryboard().instantiateViewControllerWithIdentifier("CenterViewController") as? CenterViewController
}
}
You can try too this Creating a Sidebar Menu Using SWRevealViewController in Swift I thought it's a good place too.
I hope this help you
Error: Failed to instantiate the default view controller for UIMainStoryboardFile 'Main' - perhaps the designated entry point is not set?
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Storyboard (<UIStoryboard: 0x7ff949770c30>) doesn't contain a view controller with identifier 'CenterViewController''
I dont' know what's wrong with my Main Storyboard.I was building slide out navigation panel of my own.
CenterViewController.swift
import UIKit
#objc
protocol CenterViewControllerDelegate {
optional func toggleLeftPanel()
optional func collapseSidePanels()
}
class CenterViewController: UIViewController, SideMenuPanelViewControllerDelegate {
//#IBOutlet weak private var testimageView: UIImageView!
#IBOutlet weak private var testLabel:UILabel!
var delegate: CenterViewControllerDelegate?
// MARK: Button actions
#IBAction func MenuTapped(sender: AnyObject) {
delegate?.toggleLeftPanel?()
}
func localSelected(local: LocalMenus) {
//imageView.image = animal.image
testLabel.text = local.title
delegate?.collapseSidePanels?()
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
ContainerViewController.swift
import UIKit
import QuartzCore
enum SideOutState {
case BothCollapsed
case LeftPanelExpanded
case RightPanelExpanded
}
class ContainerViewController: UIViewController, CenterViewControllerDelegate, UIGestureRecognizerDelegate{
var centerNavigationController: UINavigationController!
var centerViewController: CenterViewController!
var currentState: SideOutState = .BothCollapsed {
didSet {
let shouldShowShadow = currentState != .BothCollapsed
showShadowForCenterViewController(shouldShowShadow)
}
}
var leftMenuController: SideMenuPanelViewController?
let centerPanelExpandedOffset: CGFloat = 60
override func viewDidLoad() {
super.viewDidLoad()
centerViewController = UIStoryboard.centerViewController()
centerViewController.delegate = self
// wrap the centerViewController in a navigation controller, so we can push views to it
// and display bar button items in the navigation bar
centerNavigationController = UINavigationController(rootViewController: centerViewController)
view.addSubview(centerNavigationController.view)
addChildViewController(centerNavigationController)
centerNavigationController.didMoveToParentViewController(self)
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: "handlePanGesture:")
centerNavigationController.view.addGestureRecognizer(panGestureRecognizer)
}
// MARK: CenterViewController delegate methods
func toggleLeftPanel() {
let notAlreadyExpanded = (currentState != .LeftPanelExpanded)
if notAlreadyExpanded {
addLeftPanelViewController()
}
animateLeftPanel(shouldExpand: notAlreadyExpanded)
}
func collapseSidePanels() {
switch (currentState) {
case .LeftPanelExpanded:
toggleLeftPanel()
default:
break
}
}
func addLeftPanelViewController() {
if (leftMenuController == nil) {
leftMenuController = UIStoryboard.leftMenuController()
leftMenuController!.local = LocalMenus.allLocal()
addChildSidePanelController(leftMenuController!)
}
}
func addChildSidePanelController(sidePanelController:SideMenuPanelViewController) {
sidePanelController.delegate = centerViewController
view.insertSubview(sidePanelController.view, atIndex: 0)
addChildViewController(sidePanelController)
sidePanelController.didMoveToParentViewController(self)
}
func animateLeftPanel(#shouldExpand: Bool) {
if (shouldExpand) {
currentState = .LeftPanelExpanded
animateCenterPanelXPosition(targetPosition: CGRectGetWidth(centerNavigationController.view.frame) - centerPanelExpandedOffset)
} else {
animateCenterPanelXPosition(targetPosition: 0) { finished in
self.currentState = .BothCollapsed
self.leftMenuController!.view.removeFromSuperview()
self.leftMenuController = nil;
}
}
}
func animateCenterPanelXPosition(#targetPosition: CGFloat, completion: ((Bool) -> Void)! = nil) {
UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .CurveEaseInOut, animations: {
self.centerNavigationController.view.frame.origin.x = targetPosition
}, completion: completion)
}
func showShadowForCenterViewController(shouldShowShadow: Bool) {
if (shouldShowShadow) {
centerNavigationController.view.layer.shadowOpacity = 0.8
} else {
centerNavigationController.view.layer.shadowOpacity = 0.0
}
}
// MARK: Gesture recognizer
func handlePanGesture(recognizer: UIPanGestureRecognizer) {
// we can determine whether the user is revealing the left or right
// panel by looking at the velocity of the gesture
let gestureIsDraggingFromLeftToRight = (recognizer.velocityInView(view).x > 0)
switch(recognizer.state) {
case .Began:
if (currentState == .BothCollapsed) {
// If the user starts panning, and neither panel is visible
// then show the correct panel based on the pan direction
if (gestureIsDraggingFromLeftToRight) {
addLeftPanelViewController()
}
showShadowForCenterViewController(true)
}
case .Changed:
// If the user is already panning, translate the center view controller's
// view by the amount that the user has panned
recognizer.view!.center.x = recognizer.view!.center.x + recognizer.translationInView(view).x
recognizer.setTranslation(CGPointZero, inView: view)
case .Ended:
// When the pan ends, check whether the left or right view controller is visible
if (leftMenuController != nil) {
// animate the side panel open or closed based on whether the view has moved more or less than halfway
let hasMovedGreaterThanHalfway = recognizer.view!.center.x > view.bounds.size.width
animateLeftPanel(shouldExpand: hasMovedGreaterThanHalfway)
}
default:
break
}
}
}
private extension UIStoryboard {
class func mainStoryboard() -> UIStoryboard {
return UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
}
class func leftMenuController() -> SideMenuPanelViewController? {
return mainStoryboard().instantiateViewControllerWithIdentifier("SideMenuPanelViewController") as? SideMenuPanelViewController
}
class func centerViewController() -> CenterViewController? {
return mainStoryboard().instantiateViewControllerWithIdentifier("CenterViewController") as? CenterViewController
}
}
Very simple.
In your main storyboard, you have no ViewController with a Storyboard ID "CenterViewController"
Look here:
https://stackoverflow.com/a/11604827/3324388
Short answer from Xcode 8.0 onwards,
Go to the Main.storyboard select the target ViewController, press Command+option+3 to display the attributes
Fill the StoryBoard ID input field
Check the Use Storyboard ID checkbox
I know this could be found on the duplicated referred answer but you need to read, try and error etc.