I am wanting to create a subview which slides in when my viewController loads.
However the view is presenting without the animation effect, how would I get the view to animate?
I am calling on my viewContoller:
menuBarView.show(viewController: self)
class menuBarView {
static func show(viewController: UIViewController){
menuBarSubView = UINib(nibName: "MenuBar", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as? UIView
menuBarSubView?.frame.size.height = viewController.view.frame.height * 0.1
menuBarSubView?.frame.size.width = viewController.view.frame.width
menuBarSubView?.frame.origin.x = viewController.view.frame.origin.x
menuBarSubView?.frame.origin.y = viewController.view.frame.origin.y - menuBarSubView.frame.height
menuBarSubView?.tag = 1
viewController.view.addSubview(menuBarSubView!)
viewController.view.layoutIfNeeded()
UIView.animate(withDuration: 2, animations: {
viewController.view.viewWithTag(1)?.frame.origin.y += (viewController.view.viewWithTag(1)?.frame.height)!
viewController.view.layoutIfNeeded()
})
}
}
Related
I have a custom presentation controller which shows a UIViewController as a "bottom sheet" above the current ViewController, and allows an interactive gesture to dismiss the presented modal. The modal's modalPresentationStyle is set to .current, and the transitioningDelegate is set to my custom class during init(). The current code works perfectly on iOS 11 and 12, but appears to "freeze" the device on iOS 9 and 10.
Here is where the animation takes place. I have confirmed the presentedFrame and dismissedFrame are correct.
extension BottomSheetPresentationManager: UIViewControllerAnimatedTransitioning {
func transitionDuration(
using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.25
}
func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return self
}
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return self
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let key = presenting ? UITransitionContextViewControllerKey.to
: UITransitionContextViewControllerKey.from
let keyV = presenting ? UITransitionContextViewKey.to
: UITransitionContextViewKey.from
let controller = transitionContext.viewController(forKey: key)!
let view = transitionContext.view(forKey: keyV)!
if presenting {
transitionContext.containerView.addSubview(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 = presenting ? dismissedFrame : presentedFrame
let finalFrame = presenting ? presentedFrame : dismissedFrame
// Runs when the animation finishes
let completionBlock: (Bool) -> Void = { (finished) in
// tell our transitionContext object that we've finished animating
if transitionContext.transitionWasCancelled {
if self.interactive {
transitionContext.cancelInteractiveTransition()
}
transitionContext.completeTransition(false)
} else {
if self.interactive {
finished ? transitionContext.finishInteractiveTransition() : transitionContext.cancelInteractiveTransition()
}
transitionContext.completeTransition(finished)
}
}
// Put what you want to animate here.
let animationBlock: () -> Void = {
view.frame = finalFrame
}
// Set up for the animation
let animationDuration = transitionDuration(using: transitionContext)
view.frame = initialFrame
// Perform a different animation based on whether we're interactive (performing a gesture) or not
if interactive {
// Do a linear animation so we match our dragging with our transition
UIView.animate(withDuration: animationDuration,
delay: 0,
options: .curveLinear,
animations: animationBlock,
completion: completionBlock)
} else {
// Do a spring animation with easing
UIView.animate(withDuration: animationDuration,
delay: 0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 0.45,
options: [.curveEaseInOut],
animations: animationBlock,
completion: completionBlock)
}
}
}
After much testing, I have figured out that the completion block on the UIView.animate block never gets called (completionBlock in the code), and in the Capture View Hierarchy view, it actually shows my presented view on top, with what looks like a screenshot of the view (UIVisualEffectView) directly under the presented menu.
What you are seeing there is my bottom menu (the UITableView with cells that you see in front), with a UIVisualEffectView behind it (appears to be a screenshot of the UICollection view behind that), and a UICollectionView that called the present() method for the menu.
What would cause the UIView.animate block not to complete? Is there any reason the behavior would differ between iOS 10 and 11? Any help would be greatly appreciated.
Trying to get this transition https://www.youtube.com/watch?v=4TVnRx7PTTs
First VC has a card like image and second one is tableview with same image as sqare on first cell.
let storyboard = self.storyboard?.instantiateViewController(withIdentifier: "vc2")
let firstClassView = self.view
let secondClassView = storyboard?.view
secondClassView?.frame = self.card.frame
if let window = UIApplication.shared.keyWindow {
window.insertSubview(secondClassView!, aboveSubview: firstClassView!)
UIView.animate(withDuration: 0.5, animations: { () -> Void in
secondClassView?.frame = self.view.frame
}, completion: {(Finished) -> Void in
self.present(storyboard!, animated: false, completion: nil)
})
}
so i am adding the second vc's view to first vc with the frame of cardImage and animating it to full frame of a view. but the cardImage is not square so it doesn't look as good as app store. any suggestions?
I made a custom modal transition by using UIViewControllerAnimatedTransitioning. After the transition has completed, a modally shown view controller fills part of parent view that some part of parent view controller is still visible.
I need to know how to detect tap on outside of shown view controller(or partially shown parent view controller) so I can use it to close the modal. I understand how to add gesture to a view but I cannot find which view to add a gesture to.
I looked through stackoverflow but there aren't example specifically for custom modal transition.
Here is a code for custom transition
class PartialSlideInAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
var direction: SlideAnimationDirectionStyle
var duration: TimeInterval = 0.125
var presentedControllerWidth: CGFloat = 250
let fadeAlpha: CGFloat = 0.5
init(direction: SlideAnimationDirectionStyle) {
self.direction = direction
super.init()
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return TimeInterval(duration)
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// Setup Views
guard let presentedFromVC = transitionContext.viewController(forKey: .from) else {transitionContext.completeTransition(false); return }
guard let presentingToVC = transitionContext.viewController(forKey: .to) else {transitionContext.completeTransition(false); return }
let contrainerView = transitionContext.containerView // アニメーションはこのViewで行われる、アニメーションを実装する presenter, presenteeをこの上に載せないとアニメーションが動かない
// Setup Frames
let positionHidden = CGRect(x: contrainerView.frame.width + 1, y: 0, width: presentedControllerWidth, height: contrainerView.frame.height)
let positionShown = CGRect(x: contrainerView.frame.width - presentedControllerWidth, y: 0, width: presentedControllerWidth, height: contrainerView.frame.height)
switch direction {
case .slideIn:
contrainerView.addSubview(presentingToVC.view)
presentingToVC.view.frame = positionHidden
UIView.animate(withDuration: duration, animations: {
presentingToVC.view.frame = positionShown
presentedFromVC.view.alpha = self.fadeAlpha
}, completion: { success in
transitionContext.completeTransition( success )
})
case .slideOut:
contrainerView.addSubview(presentedFromVC.view)
presentedFromVC.view.frame = positionShown
UIView.animate(withDuration: duration, animations: {
presentedFromVC.view.frame = positionHidden
presentingToVC.view.alpha = 1.0
}, completion: { success in
transitionContext.completeTransition( success )
})
}
}
}
Have you tried using UIGestureRecognizer yet? The UIGestureRecognizer can be utilized to recognize tap events, both inside a child view controller and a parent view controller. For swift 3, there should be a handleTapBehind function of the UIRecognizerDelegate that should allow you to do what you're looking for.
See if the what is documented here
See the "Swift 3.1 solution that works in both portrait and landscape" in the linked post.
So I just upgraded to Swift 3.0 from 2.3. Everything else about my project is fine except for this line of code and its error:
if let delegate: AnyObject = completionDelegate {
leftToRightTransition.delegate = delegate //ERROR MESSAGE
}
The error message says:
Cannot assign value of type 'AnyObject' to type CAAnimation
Below is the full related block of code. Essentially this code is the animation. Elsewhere in my project (which I'm positive is not related to the problem, but just for context) there's code that allows the user to swipe through an array of images. This block is the animation part of it (the 'swipe away' animation'):
extension UIView {
func rightToLeftAnimation(_ duration: TimeInterval = 0.5, completionDelegate: AnyObject? = nil) {
// Create a CATransition object
let rightToLeftTransition = CATransition()
// Set its callback delegate to the completionDelegate that was provided
if let delegate: AnyObject = completionDelegate {
rightToLeftTransition.delegate = delegate //ERROR MESSAGE ON THIS LINE
}
rightToLeftTransition.type = kCATransitionPush
rightToLeftTransition.subtype = kCATransitionFromLeft
rightToLeftTransition.duration = duration
rightToLeftTransition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
rightToLeftTransition.fillMode = kCAFillModeRemoved
// Add the animation to the View's layer
self.layer.add(rightToLeftTransition, forKey: "rightToLeftTransition")
}
This worked just fine in Swift 2.3 but now it doesn't work. It's little stuff like this that drives me nuts... Any help would be appreciated ;)
It's now a CAAnimationDelegate:
if let delegate = completionDelegate as? CAAnimationDelegate {
rightToLeftTransition.delegate = delegate
}
Or just:
func rightToLeftAnimation(_ duration: TimeInterval = 0.5, completionDelegate: CAAnimationDelegate? = nil) {
// Create a CATransition object
let rightToLeftTransition = CATransition()
// Set its callback delegate to the completionDelegate that was provided
rightToLeftTransition.delegate = completionDelegate
rightToLeftTransition.type = kCATransitionPush
rightToLeftTransition.subtype = kCATransitionFromLeft
rightToLeftTransition.duration = duration
rightToLeftTransition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
rightToLeftTransition.fillMode = kCAFillModeRemoved
// Add the animation to the View's layer
layer.add(rightToLeftTransition, forKey: "rightToLeftTransition")
}
Try this , Its working in my code:
let swipeRight:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(ViewController.swipedRight))
swipeRight.direction = .right
SLIDEVIEW.addGestureRecognizer(swipeRight)
let swipeLeft:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(ViewController.swipedLeft))
swipeLeft.direction = .left
SLIDEVIEW.addGestureRecognizer(swipeLeft)
func swipedRight()
{
print("swiped right")
updateFrames(towards: "right")
}
func swipedLeft()
{
print("swiped left")
updateFrames(towards: "left")
}
I can detect the button pressed in UIView through this simple code
func handlePan(recognizer:UIPanGestureRecognizer) {
if let buttonRecognize = recognizer.view as? UIButton {
let subviews = self.view.subviews as [UIView]
for v in subviews {
if let button = v as? UIButton {
if button.tag != -1 {
var p = button.superview?.convertPoint(button.center, toView: view)
println(p)
}
UIView.animateWithDuration(Double(slideFactor * 2),
delay: 0,
options: UIViewAnimationOptions.CurveEaseOut,
animations: {recognizer.view!.center = finalPoint },
completion: nil)
}}}}
}
As can detect the position of a button in UIView ?
for (0,0) of Uiview = (367.666656494141,207.33332824707) for func handlePan
I would like to find the value of the button than the UIView where the center is that of UIView itself and not the value of func handlePan where the center is the top left corner !!
P.S. I tried to move the let subview etc after
completion: nil
but the result does not change !!
You can convert a point coordinate system to that of the specified view using
convertPoint(_ point: CGPoint, toView view: UIView?)
The toView is the coordinate system to which it will be converted.