탐색 기반 응용 프로그램이 있고 푸시 및 팝 애니메이션의 애니메이션을 변경하고 싶습니다. 어떻게해야합니까?
2018 수정
이 질문에 대한 많은 답변이 있었으며 지금은 꽤 오래 걸렸습니다. 지금 가장 관련성이 있다고 생각되는 것에 대한 답변을 다시 선택했습니다. 다르게 생각하는 사람이 있으면 의견을 남겨주세요.
탐색 기반 응용 프로그램이 있고 푸시 및 팝 애니메이션의 애니메이션을 변경하고 싶습니다. 어떻게해야합니까?
2018 수정
이 질문에 대한 많은 답변이 있었으며 지금은 꽤 오래 걸렸습니다. 지금 가장 관련성이 있다고 생각되는 것에 대한 답변을 다시 선택했습니다. 다르게 생각하는 사람이 있으면 의견을 남겨주세요.
답변:
탐색 기반 앱에서 푸시 및 팝 애니메이션을 변경하는 방법 ...
전문:
iOS 개발에 익숙하지 않다고 가정 해보십시오. 혼란스럽게도 Apple은 쉽게 사용할 수있는 두 가지 전환을 제공합니다. "crossfade"와 "flip"이 있습니다.
그러나 "crossfade"와 "flip"은 쓸모가 없습니다. 그들은 결코 사용되지 않습니다. 애플이 왜이 두 가지 쓸모없는 전환을 제공했는지는 아무도 모른다!
그래서:
"슬라이드"와 같이 일반적인 일반적인 전환 을 수행하려고한다고 가정합니다 . 이 경우 엄청난 양의 작업을 수행해야합니다! .
이 작업은이 게시물에 설명되어 있습니다.
반복하기 만하면됩니다.
놀랍게도 : iOS를 사용하면 가장 단순하고 일반적인 일상 전환 (예 : 일반 슬라이드)을 원한다면 전체 사용자 정의 전환 을 구현하는 모든 작업 이 필요합니다 .
방법은 다음과 같습니다.
UIViewControllerAnimatedTransitioning
당신은 자신의 부울이 필요합니다 popStyle
. (튀어 나오거나 터지는가?)
transitionDuration
(사소한) 주요 전화를 포함해야합니다 .animateTransition
실제로 inside에 대해 두 가지 다른 루틴을 작성 해야합니다animateTransition
. 하나는 푸시 용이고 다른 하나는 팝용입니다. 아마 그 이름 animatePush
과 animatePop
. 내부 에서 두 루틴으로 animateTransition
분기하십시오.popStyle
아래 예제는 간단한 이동 / 이동을 수행합니다.
당신 animatePush
과 animatePop
일상에서. 당신은 해야한다 은 "보기"및 "보기에"를 얻는다. (이를 수행하는 방법은 코드 예제에 나와 있습니다.)
새로운 "to"보기 를 사용해야합니다 addSubview
.
그리고 당신 은completeTransition
애니메이션의 끝에서 전화 해야합니다
그래서 ..
class SimpleOver: NSObject, UIViewControllerAnimatedTransitioning {
var popStyle: Bool = false
func transitionDuration(
using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.20
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
if popStyle {
animatePop(using: transitionContext)
return
}
let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let f = transitionContext.finalFrame(for: tz)
let fOff = f.offsetBy(dx: f.width, dy: 55)
tz.view.frame = fOff
transitionContext.containerView.insertSubview(tz.view, aboveSubview: fz.view)
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
animations: {
tz.view.frame = f
}, completion: {_ in
transitionContext.completeTransition(true)
})
}
func animatePop(using transitionContext: UIViewControllerContextTransitioning) {
let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let f = transitionContext.initialFrame(for: fz)
let fOffPop = f.offsetBy(dx: f.width, dy: 55)
transitionContext.containerView.insertSubview(tz.view, belowSubview: fz.view)
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
animations: {
fz.view.frame = fOffPop
}, completion: {_ in
transitionContext.completeTransition(true)
})
}
}
그리고 ...
참고 : 이상하게도 "첫 번째"보기 컨트롤러 에서만이 작업을 수행해야합니다 . (아래에있는 것)
당신이 상단에 팝업 하나 , 아무것도 하지 마십시오 . 쉬운.
수업은 ...
class SomeScreen: UIViewController {
}
된다 ...
class FrontScreen: UIViewController,
UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {
let simpleOver = SimpleOver()
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.delegate = self
}
func navigationController(
_ navigationController: UINavigationController,
animationControllerFor operation: UINavigationControllerOperation,
from fromVC: UIViewController,
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
simpleOver.popStyle = (operation == .pop)
return simpleOver
}
}
그게 다야.
정상적으로 정상적으로 밀고 터지십시오. 밀어 ...
let n = UIStoryboard(name: "nextScreenStoryboardName", bundle: nil)
.instantiateViewController(withIdentifier: "nextScreenStoryboardID")
as! NextScreen
navigationController?.pushViewController(n, animated: true)
다음 화면에서 원하는 경우 팝업을 표시 할 수 있습니다.
class NextScreen: TotallyOrdinaryUIViewController {
@IBAction func userClickedBackOrDismissOrSomethingLikeThat() {
navigationController?.popViewController(animated: true)
}
}
휴
AnimatedTransitioning
요즘 iOS 앱 사용 방법에 대한 자세한 내용은 @AlanZeino 및 @elias 답변으로 스크롤하십시오 !
animatePush
이며 animatePop
.. 두 가지 다른 방향!
나는 다음을했고 잘 작동합니다 .. 간단하고 이해하기 쉽습니다 ..
CATransition* transition = [CATransition animation];
transition.duration = 0.5;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
//transition.subtype = kCATransitionFromTop; //kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom
[self.navigationController.view.layer addAnimation:transition forKey:nil];
[[self navigationController] popViewControllerAnimated:NO];
그리고 똑같은 것도 ..
스위프트 3.0 버전 :
let transition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionFade
self.navigationController?.view.layer.add(transition, forKey: nil)
_ = self.navigationController?.popToRootViewController(animated: false)
Animated:NO
부분은 매우 중요합니다. 경우 YES
전달, 애니메이션 믹스 재미있는 효과를 야기한다.
setViewControllers:
이것이 내가 항상이 작업을 완료 한 방법입니다.
푸시의 경우 :
MainView *nextView=[[MainView alloc] init];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[self.navigationController pushViewController:nextView animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
[UIView commitAnimations];
[nextView release];
팝의 경우 :
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
[UIView commitAnimations];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDelay:0.375];
[self.navigationController popViewControllerAnimated:NO];
[UIView commitAnimations];
나는 여전히 이것으로부터 많은 피드백을 얻으므로 어쨌든 애니메이션을 수행하는 Apple 권장 방법 인 애니메이션 블록을 사용하도록 업데이트 할 것입니다.
푸시의 경우 :
MainView *nextView = [[MainView alloc] init];
[UIView animateWithDuration:0.75
animations:^{
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[self.navigationController pushViewController:nextView animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
}];
팝의 경우 :
[UIView animateWithDuration:0.75
animations:^{
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
}];
[self.navigationController popViewControllerAnimated:NO];
super
으로self.navigationController
UIViewController
"ViewController"부분없이 이름 을 서브 클래스로 제공 한 이유는 무엇입니까 ? 이 이름은 UIView에 더 적합합니다.
푸시
CATransition *transition = [CATransition animation];
transition.duration = 0.3;
transition.type = kCATransitionFade;
//transition.subtype = kCATransitionFromTop;
[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
[self.navigationController pushViewController:ViewControllerYouWantToPush animated:NO];
팝
CATransition *transition = [CATransition animation];
transition.duration = 0.3;
transition.type = kCATransitionFade;
//transition.subtype = kCATransitionFromTop;
[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
[self.navigationController popViewControllerAnimated:NO];
@Magnus는 Swift (2.0)에 대해서만 대답합니다.
let transition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromTop
self.navigationController!.view.layer.addAnimation(transition, forKey: nil)
let writeView : WriteViewController = self.storyboard?.instantiateViewControllerWithIdentifier("WriteView") as! WriteViewController
self.navigationController?.pushViewController(writeView, animated: false)
일부 주석 :
Segue와 함께이 작업을 수행 할 수 있습니다 . prepareForSegue
또는 에서 구현하십시오 shouldPerformSegueWithIdentifier
. 하나 기본 애니메이션도 그대로 유지됩니다. 이 문제를 해결하려면 스토리 보드로 이동하여 Segue를 클릭하고 'Animates'상자를 선택 취소하십시오. 그러나 이것은 IOS 9.0 이상의 앱을 제한합니다 (Xcode 7에서 가장 좋았습니다).
segue에서 수행 할 때 마지막 두 줄은 다음과 같이 바꿔야합니다.
self.navigationController?.popViewControllerAnimated(false)
내가 잘못 설정했지만 무시합니다.
에 그 기억 스위프트 , 확장은 확실히 당신의 친구!
public extension UINavigationController {
/**
Pop current view controller to previous view controller.
- parameter type: transition animation type.
- parameter duration: transition animation duration.
*/
func pop(transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
self.addTransition(transitionType: type, duration: duration)
self.popViewControllerAnimated(false)
}
/**
Push a new view controller on the view controllers's stack.
- parameter vc: view controller to push.
- parameter type: transition animation type.
- parameter duration: transition animation duration.
*/
func push(viewController vc: UIViewController, transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
self.addTransition(transitionType: type, duration: duration)
self.pushViewController(vc, animated: false)
}
private func addTransition(transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
let transition = CATransition()
transition.duration = duration
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = type
self.view.layer.addAnimation(transition, forKey: nil)
}
}
Apple이 더 이상 앱을 승인하지 않기 때문에 개인 통화를 사용하는 것은 좋지 않습니다. 아마도 당신은 이것을 시도 할 수 있습니다 :
//Init Animation
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration: 0.50];
[UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.navigationController.view cache:YES];
//Create ViewController
MyViewController *myVC = [[MyViewController alloc] initWith...];
[self.navigationController pushViewController:myVC animated:NO];
[myVC release];
//Start Animation
[UIView commitAnimations];
-pushViewController:transition:forceImmediate:
나쁜 생각 일 것입니다.
이것이 Google의 최고 결과이기 때문에 제가 생각하는 것을 가장 제정신이라고 생각합니다. iOS 7+ 전환 API를 사용하는 것입니다. Swift 3을 사용하여 iOS 10에 대해 이것을 구현했습니다.
UINavigationController
서브 클래스의 서브 클래스를 작성 UINavigationController
하고 UIViewControllerAnimatedTransitioning
프로토콜 을 준수하는 클래스의 인스턴스를 리턴하는 경우이를 두 개의보기 컨트롤러 사이의 애니메이션 방식과 결합하는 것은 매우 간단 합니다.
예를 들어 여기 내 UINavigationController
하위 클래스가 있습니다.
class NavigationController: UINavigationController {
init() {
super.init(nibName: nil, bundle: nil)
delegate = self
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension NavigationController: UINavigationControllerDelegate {
public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return NavigationControllerAnimation(operation: operation)
}
}
내가 UINavigationControllerDelegate
자체로 설정 한 것을 볼 수 있고 , 서브 클래스의 확장 UINavigationControllerDelegate
에서는 사용자 정의 애니메이션 컨트롤러 (예 :)를 반환 할 수 있는 메소드를 구현합니다 NavigationControllerAnimation
. 이 사용자 정의 애니메이션 컨트롤러가 스톡 애니메이션을 대신합니다.
왜 NavigationControllerAnimation
초기화를 통해 인스턴스에 작업을 전달하는지 궁금 할 것입니다 . 나는 프로토콜 NavigationControllerAnimation
의 구현 UIViewControllerAnimatedTransitioning
에서 작업이 무엇인지 (즉, '푸시'또는 '팝') 알 수 있도록 이렇게합니다. 이것은 내가 어떤 종류의 애니메이션을해야하는지 아는 데 도움이됩니다. 대부분의 경우 작업에 따라 다른 애니메이션을 수행하려고합니다.
나머지는 꽤 표준입니다. UIViewControllerAnimatedTransitioning
프로토콜 에서 두 가지 필수 기능을 구현하고 원하는대로 애니메이션을 적용하십시오.
class NavigationControllerAnimation: NSObject, UIViewControllerAnimatedTransitioning {
let operation: UINavigationControllerOperation
init(operation: UINavigationControllerOperation) {
self.operation = operation
super.init()
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else { return }
let containerView = transitionContext.containerView
if operation == .push {
// do your animation for push
} else if operation == .pop {
// do your animation for pop
}
}
}
각기 다른 유형의 작업 (예 : '푸시'또는 '팝')에 대해 뷰 컨트롤러와 뷰 컨트롤러가 서로 다르다는 것을 기억해야합니다. 푸시 조작 중에는 to view 컨트롤러가 푸시됩니다. 팝 작업 중에는 to view 컨트롤러가 전환되는 컨트롤러가되고 from view 컨트롤러가 팝 컨트롤러가됩니다.
또한 to
뷰 컨트롤러는 containerView
전환 컨텍스트에서 하위 뷰로 추가해야합니다 .
애니메이션이 완료되면를 호출해야합니다 transitionContext.completeTransition(true)
. 대화 형 전환을 수행하는 경우, 당신은 동적를 반환해야합니다 Bool
으로 completeTransition(didComplete: Bool)
전환이 애니메이션의 끝에 완료된 경우에 따라.
마지막으로 ( 선택 사항 읽기 ), 내가 작업했던 전환을 어떻게 수행했는지보고 싶을 수도 있습니다. 이 코드는 조금 더 해키이며 꽤 빨리 작성하여 훌륭한 애니메이션 코드라고 말하지는 않지만 여전히 애니메이션 부분을 수행하는 방법을 보여줍니다.
광산은 정말 간단한 전환이었습니다. UINavigationController가 일반적으로하는 것과 동일한 애니메이션을 모방하고 싶었지만 '다음 페이지 위에있는'애니메이션 대신 새로운 뷰와 동시에 이전 뷰 컨트롤러의 1 : 1 애니메이션을 구현하고 싶었습니다. 컨트롤러가 나타납니다. 이는 두 개의 뷰 컨트롤러가 서로 고정되어있는 것처럼 보이게하는 효과가 있습니다.
푸시 작업의 경우 먼저 toViewController
x 축 오프 화면에서의 뷰 원점을 설정 하고을 서브 뷰로 추가하고이를 0 containerView
으로 설정하여 화면에 애니메이션을 적용해야 origin.x
합니다. 동시에 화면에서 화면을 꺼서 fromViewController
보기를 움직이게합니다 origin.x
.
toViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.size.width, dy: 0.0)
containerView.addSubview(toViewController.view)
UIView.animate(withDuration: transitionDuration(using: transitionContext),
delay: 0,
options: [ UIViewAnimationOptions.curveEaseOut ],
animations: {
toViewController.view.frame = containerView.bounds
fromViewController.view.frame = containerView.bounds.offsetBy(dx: -containerView.frame.size.width, dy: 0)
},
completion: { (finished) in
transitionContext.completeTransition(true)
})
팝 연산은 기본적으로 반대입니다. 추가 toViewController
의 하위 뷰로서 containerView
, 그리고 멀리 애니메이션 fromViewController
당신이에서 애니메이션으로 오른쪽 toViewController
왼쪽에서 :
containerView.addSubview(toViewController.view)
UIView.animate(withDuration: transitionDuration(using: transitionContext),
delay: 0,
options: [ UIViewAnimationOptions.curveEaseOut ],
animations: {
fromViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.width, dy: 0)
toViewController.view.frame = containerView.bounds
},
completion: { (finished) in
transitionContext.completeTransition(true)
})
전체 신속한 파일이있는 요지가 있습니다.
https://gist.github.com/alanzeino/603293f9da5cd0b7f6b60dc20bc766be
UINavigationControllerDelegate 및 UIViewControllerAnimatedTransitioning이 있으며 원하는대로 애니메이션을 변경할 수 있습니다.
예를 들어 VC의 수직 팝 애니메이션입니다.
@objc class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.5
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
let containerView = transitionContext.containerView()
let bounds = UIScreen.mainScreen().bounds
containerView!.insertSubview(toViewController.view, belowSubview: fromViewController.view)
toViewController.view.alpha = 0.5
let finalFrameForVC = fromViewController.view.frame
UIView.animateWithDuration(transitionDuration(transitionContext), animations: {
fromViewController.view.frame = CGRectOffset(finalFrameForVC, 0, bounds.height)
toViewController.view.alpha = 1.0
}, completion: {
finished in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
})
}
}
그리고
func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if operation == .Pop {
return PopAnimator()
}
return nil;
}
유용한 튜토리얼 https://www.objc.io/issues/5-ios7/view-controller-transitions/
swift 4 에 대한 업데이트 된 답변을 바탕으로jordanperry
푸시 UIViewController
let yourVC = self.storyboard?.instantiateViewController(withIdentifier: "yourViewController") as! yourViewController
UIView.animate(withDuration: 0.75, animations: {() -> Void in
UIView.setAnimationCurve(.easeInOut)
self.navigationController?.pushViewController(terms, animated: true)
UIView.setAnimationTransition(.flipFromRight, for: (self.navigationController?.view)!, cache: false)
})
팝
UIView.animate(withDuration: 0.75, animations: {() -> Void in
UIView.setAnimationCurve(.easeInOut)
UIView.setAnimationTransition(.flipFromLeft, for: (self.navigationController?.view)!, cache: false)
})
navigationController?.popViewController(animated: false)
다음은 Swift에서 동일한 작업을 수행 한 방법입니다.
푸시의 경우 :
UIView.animateWithDuration(0.75, animations: { () -> Void in
UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)
self.navigationController!.pushViewController(nextView, animated: false)
UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromRight, forView: self.navigationController!.view!, cache: false)
})
팝의 경우 :
실제로 위의 일부 응답과는 약간 다르게이 작업을 수행했지만 Swift 개발을 처음 접했을 때 올바르지 않을 수 있습니다. viewWillDisappear:animated:
팝 코드를 재정의 하고 추가했습니다.
UIView.animateWithDuration(0.75, animations: { () -> Void in
UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)
UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromLeft, forView: self.navigationController!.view, cache: false)
})
super.viewWillDisappear(animated)
스위프트 4.2 @ @Luca Davanzo의 답변
public extension UINavigationController {
/**
Pop current view controller to previous view controller.
- parameter type: transition animation type.
- parameter duration: transition animation duration.
*/
func pop(transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
self.addTransition(transitionType: type, duration: duration)
self.popViewController(animated: false)
}
/**
Push a new view controller on the view controllers's stack.
- parameter vc: view controller to push.
- parameter type: transition animation type.
- parameter duration: transition animation duration.
*/
func push(viewController vc: UIViewController, transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
self.addTransition(transitionType: type, duration: duration)
self.pushViewController(vc, animated: false)
}
private func addTransition(transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
let transition = CATransition()
transition.duration = duration
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
transition.type = type
self.view.layer.add(transition, forKey: nil)
}
}
나는 최근에 비슷한 것을하려고했습니다. 나는 UINavigationController의 슬라이딩 애니메이션을 좋아하지 않기로 결정했지만 UIView가 컬이나 이와 유사한 것을주는 애니메이션을하고 싶지 않았습니다. 밀거나 터질 때 뷰 사이에서 크로스 페이드를하고 싶었습니다.
문제는 뷰가 말 그대로 뷰를 제거하거나 현재 뷰의 상단에 터지는 것이므로 페이드가 작동하지 않는다는 사실과 관련이 있습니다. 해결책은 새보기를 가져 와서 UIViewController 스택의 현재 상위보기에 하위보기로 추가하는 것입니다. 알파 0으로 추가 한 다음 크로스 페이드를 수행합니다. 애니메이션 시퀀스가 끝나면 뷰를 애니메이션하지 않고 스택에 밀어 넣습니다. 그런 다음 이전 topView로 돌아가 변경 한 내용을 정리합니다.
탐색 항목이 있으므로 전환이 올바르게 보이도록 조정해야하기 때문에 그보다 조금 더 복잡합니다. 또한 회전을 수행하는 경우 뷰를 하위 뷰로 추가 할 때 프레임 크기가 화면에 올바르게 표시되도록 프레임 크기를 조정해야합니다. 다음은 내가 사용한 코드 중 일부입니다. UINavigationController를 서브 클래스 화하고 push 및 pop 메소드를 대체했습니다.
-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
UIViewController *currentViewController = [self.viewControllers lastObject];
//if we don't have a current controller, we just do a normal push
if(currentViewController == nil)
{
[super pushViewController:viewController animated:animated];
return;
}
//if no animation was requested, we can skip the cross fade
if(!animation)
{
[super pushViewController:viewController animated:NO];
return;
}
//start the cross fade. This is a tricky thing. We basically add the new view
//as a subview of the current view, and do a cross fade through alpha values.
//then we push the new view on the stack without animating it, so it seemlessly is there.
//Finally we remove the new view that was added as a subview to the current view.
viewController.view.alpha = 0.0;
//we need to hold onto this value, we'll be releasing it later
NSString *title = [currentViewController.title retain];
//add the view as a subview of the current view
[currentViewController.view addSubview:viewController.view];
[currentViewController.view bringSubviewToFront:viewController.view];
UIBarButtonItem *rButtonItem = currentViewController.navigationItem.rightBarButtonItem;
UIBarButtonItem *lButtonItem = currentViewController.navigationItem.leftBarButtonItem;
NSArray *array = nil;
//if we have a right bar button, we need to add it to the array, if not, we will crash when we try and assign it
//so leave it out of the array we are creating to pass as the context. I always have a left bar button, so I'm not checking to see if it is nil. Its a little sloppy, but you may want to be checking for the left BarButtonItem as well.
if(rButtonItem != nil)
array = [[NSArray alloc] initWithObjects:currentViewController,viewController,title,lButtonItem,rButtonItem,nil];
else {
array = [[NSArray alloc] initWithObjects:currentViewController,viewController,title,lButtonItem,nil];
}
//remove the right bar button for our transition
[currentViewController.navigationItem setRightBarButtonItem:nil animated:YES];
//remove the left bar button and create a backbarbutton looking item
//[currentViewController.navigationItem setLeftBarButtonItem:nil animated:NO];
//set the back button
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:title style:kButtonStyle target:self action:@selector(goBack)];
[currentViewController.navigationItem setLeftBarButtonItem:backButton animated:YES];
[viewController.navigationItem setLeftBarButtonItem:backButton animated:NO];
[backButton release];
[currentViewController setTitle:viewController.title];
[UIView beginAnimations:@"push view" context:array];
[UIView setAnimationDidStopSelector:@selector(animationForCrossFadePushDidStop:finished:context:)];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.80];
[viewController.view setAlpha: 1.0];
[UIView commitAnimations];
}
-(void)animationForCrossFadePushDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{
UIViewController *c = [(NSArray*)context objectAtIndex:0];
UIViewController *n = [(NSArray*)context objectAtIndex:1];
NSString *title = [(NSArray *)context objectAtIndex:2];
UIBarButtonItem *l = [(NSArray *)context objectAtIndex:3];
UIBarButtonItem *r = nil;
//not all views have a right bar button, if we look for it and it isn't in the context,
//we'll crash out and not complete the method, but the program won't crash.
//So, we need to check if it is there and skip it if it isn't.
if([(NSArray *)context count] == 5)
r = [(NSArray *)context objectAtIndex:4];
//Take the new view away from being a subview of the current view so when we go back to it
//it won't be there anymore.
[[[c.view subviews] lastObject] removeFromSuperview];
[c setTitle:title];
[title release];
//set the search button
[c.navigationItem setLeftBarButtonItem:l animated:NO];
//set the next button
if(r != nil)
[c.navigationItem setRightBarButtonItem:r animated:NO];
[super pushViewController:n animated:NO];
}
코드에서 언급했듯이 항상 왼쪽 막대 버튼 항목이 있으므로 애니메이션 대리자의 컨텍스트로 전달하는 배열에 배치하기 전에 그것이 없는지 확인하지 않습니다. 이 작업을 수행하면 확인을 원할 수 있습니다.
내가 찾은 문제는 대리자 메서드에서 전혀 충돌하면 프로그램이 충돌하지 않는다는 것입니다. 델리게이트가 완료되는 것을 막지 만 경고는 표시되지 않습니다.
따라서 해당 델리게이트 루틴에서 정리를 수행 한 이후 정리가 완료되지 않아 이상한 시각적 동작이 발생했습니다.
내가 만든 뒤로 버튼은 "goBack"메소드를 호출하고 해당 메소드는 팝 루틴을 호출합니다.
-(void)goBack
{
[self popViewControllerAnimated:YES];
}
또한, 여기 내 팝 루틴이 있습니다.
-(UIViewController *)popViewControllerAnimated:(BOOL)animated
{
//get the count for the number of viewControllers on the stack
int viewCount = [[self viewControllers] count];
//get the top view controller on the stack
UIViewController *topViewController = [self.viewControllers objectAtIndex:viewCount - 1];
//get the next viewController after the top one (this will be the new top one)
UIViewController *newTopViewController = [self.viewControllers objectAtIndex:viewCount - 2];
//if no animation was requested, we can skip the cross fade
if(!animated)
{
[super popViewControllerAnimated:NO];
return topViewController;
}
//start of the cross fade pop. A bit tricky. We need to add the new top controller
//as a subview of the curent view controler with an alpha of 0. We then do a cross fade.
//After that we pop the view controller off the stack without animating it.
//Then the cleanup happens: if the view that was popped is not released, then we
//need to remove the subview we added and change some titles back.
newTopViewController.view.alpha = 0.0;
[topViewController.view addSubview:newTopViewController.view];
[topViewController.view bringSubviewToFront:newTopViewController.view];
NSString *title = [topViewController.title retain];
UIBarButtonItem *lButtonItem = topViewController.navigationItem.leftBarButtonItem;
UIBarButtonItem *rButtonItem = topViewController.navigationItem.rightBarButtonItem;
//set the new buttons on top of the current controller from the new top controller
if(newTopViewController.navigationItem.leftBarButtonItem != nil)
{
[topViewController.navigationItem setLeftBarButtonItem:newTopViewController.navigationItem.leftBarButtonItem animated:YES];
}
if(newTopViewController.navigationItem.rightBarButtonItem != nil)
{
[topViewController.navigationItem setRightBarButtonItem:newTopViewController.navigationItem.rightBarButtonItem animated:YES];
}
[topViewController setTitle:newTopViewController.title];
//[topViewController.navigationItem.leftBarButtonItem setTitle:newTopViewController.navigationItem.leftBarButtonItem.title];
NSArray *array = nil;
if(rButtonItem != nil)
array = [[NSArray alloc] initWithObjects:topViewController,title,lButtonItem,rButtonItem,nil];
else {
array = [[NSArray alloc] initWithObjects:topViewController,title,lButtonItem,nil];
}
[UIView beginAnimations:@"pop view" context:array];
[UIView setAnimationDidStopSelector:@selector(animationForCrossFadePopDidStop:finished:context:)];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.80];
[newTopViewController.view setAlpha: 1.0];
[UIView commitAnimations];
return topViewController;
}
-(void)animationForCrossFadePopDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{
UIViewController *c = [(NSArray *)context objectAtIndex:0];
//UIViewController *n = [(NSArray *)context objectAtIndex:1];
NSString *title = [(NSArray *)context objectAtIndex:1];
UIBarButtonItem *l = [(NSArray *)context objectAtIndex:2];
UIBarButtonItem *r = nil;
//Not all views have a right bar button. If we look for one that isn't there
// we'll crash out and not complete this method, but the program will continue.
//So we need to check if it is therea nd skip it if it isn't.
if([(NSArray *)context count] == 4)
r = [(NSArray *)context objectAtIndex:3];
//pop the current view from the stack without animation
[super popViewControllerAnimated:NO];
//if what was the current veiw controller is not nil, then lets correct the changes
//we made to it.
if(c != nil)
{
//remove the subview we added for the transition
[[c.view.subviews lastObject] removeFromSuperview];
//reset the title we changed
c.title = title;
[title release];
//replace the left bar button that we changed
[c.navigationItem setLeftBarButtonItem:l animated:NO];
//if we were passed a right bar button item, replace that one as well
if(r != nil)
[c.navigationItem setRightBarButtonItem:r animated:NO];
else {
[c.navigationItem setRightBarButtonItem:nil animated:NO];
}
}
}
거의 다됐다. 회전을 구현하려면 추가 코드가 필요합니다. 하위 뷰로 표시하기 전에 뷰를 추가하기 전에 뷰의 프레임 크기를 설정해야합니다. 따라서 하위 뷰로 추가하고 페이드 인하지만 세로로 표시됩니다. 애니메이션없이 팝업하면 동일한 뷰이지만 스택에있는 뷰는 이제 가로입니다. 모든 것이 약간 펑키 해 보입니다. 모든 사람의 회전 구현은 약간 다르므로 여기에 내 코드를 포함시키지 않았습니다.
그것이 사람들에게 도움이되기를 바랍니다. 나는 이것과 비슷한 것을 온통 찾았고 아무것도 찾을 수 없었습니다. 나는 이것이 완벽한 대답이라고 생각하지는 않지만이 시점에서 실제로 잘 작동하고 있습니다.
이제 사용할 수 있습니다 UIView.transition
. 참고하십시오 animated:false
. 이것은 모든 전환 옵션, 팝, 푸시 또는 스택 교체와 함께 작동합니다.
if let nav = self.navigationController
{
UIView.transition(with:nav.view, duration:0.3, options:.transitionCrossDissolve, animations: {
_ = nav.popViewController(animated:false)
}, completion:nil)
}
iJordan의 답변을 영감으로 사용하여 UINavigationController에서 카테고리를 만들어서이 애니메이션 코드를 복사 / 붙여 넣기 대신 앱 전체에서 사용할 수있는 이유는 무엇입니까?
UINavigationController + Animation.h
@interface UINavigationController (Animation)
- (void) pushViewControllerWithFlip:(UIViewController*) controller;
- (void) popViewControllerWithFlip;
@end
UINavigationController + Animation.m
@implementation UINavigationController (Animation)
- (void) pushViewControllerWithFlip:(UIViewController *) controller
{
[UIView animateWithDuration:0.50
animations:^{
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[self pushViewController:controller animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO];
}];
}
- (void) popViewControllerWithFlip
{
[UIView animateWithDuration:0.5
animations:^{
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO];
}];
[self popViewControllerAnimated:NO];
}
@end
그런 다음 UINavigationController + Animation.h 파일을 가져 와서 정상적으로 호출하십시오.
[self.navigationController pushViewControllerWithFlip:[[NewViewController alloc] init]];
[self.navigationController popViewControllerWithFlip];
매우 간단합니다
self.navigationController?.view.semanticContentAttribute = .forceRightToLeft
여기에있는 모든 대답은 훌륭하고 대부분 잘 작동하지만 동일한 효과를 얻는 약간 더 간단한 방법이 있습니다 ...
푸시의 경우 :
NextViewController *nextViewController = [[NextViewController alloc] init];
// Shift the view to take the status bar into account
CGRect frame = nextViewController.view.frame;
frame.origin.y -= 20;
frame.size.height += 20;
nextViewController.view.frame = frame;
[UIView transitionFromView:self.navigationController.topViewController.view toView:nextViewController.view duration:0.5 options:UIViewAnimationOptionTransitionFlipFromRight completion:^(BOOL finished) {
[self.navigationController pushViewController:nextViewController animated:NO];
}];
팝의 경우 :
int numViewControllers = self.navigationController.viewControllers.count;
UIView *nextView = [[self.navigationController.viewControllers objectAtIndex:numViewControllers - 2] view];
[UIView transitionFromView:self.navigationController.topViewController.view toView:nextView duration:0.5 options:UIViewAnimationOptionTransitionFlipFromLeft completion:^(BOOL finished) {
[self.navigationController popViewControllerAnimated:NO];
}];}
전환 애니메이션을 공개적으로 변경할 수있는 방법은 없습니다.
"뒤로"버튼이 필요하지 않은 경우 당신이 해야 사용 모달 뷰 컨트롤러 / "페이드"/ (≥3.2) "페이지 컬"전환 / "플립"는 "바닥에서 밀어 넣기"를 가지고.
온 개인 측면, 방법은 -pushViewController:animated:
문서화되지 않은 메서드를 호출 -pushViewController:transition:forceImmediate:
예를 들면 있도록 왼쪽 플립부터 오른쪽으로의 전환, 당신이 사용할 수있는 것인지,
[navCtrler pushViewController:ctrler transition:10 forceImmediate:NO];
그러나 "팝"전환은이 방법으로 변경할 수 없습니다.
훨씬 적은 수의 코드 줄로 처리하는 방법 은이 질문에 대한 나의 답변 을 참조하십시오 . 이 방법을 사용하면 원하는 방식으로 새 뷰 컨트롤러의 의사 "푸시"에 애니메이션을 적용 할 수 있으며 애니메이션이 완료되면 마치 표준 푸시 방법을 사용한 것처럼 내비게이션 컨트롤러가 설정됩니다. 내 예제에서는 왼쪽 또는 오른쪽에서 슬라이드 인을 애니메이션 할 수 있습니다. 편의를 위해 여기에 코드를 반복했습니다.
-(void) showVC:(UIViewController *) nextVC rightToLeft:(BOOL) rightToLeft {
[self addChildViewController:neighbor];
CGRect offscreenFrame = self.view.frame;
if(rightToLeft) {
offscreenFrame.origin.x = offscreenFrame.size.width * -1.0;
} else if(direction == MyClimbDirectionRight) {
offscreenFrame.origin.x = offscreenFrame.size.width;
}
[[neighbor view] setFrame:offscreenFrame];
[self.view addSubview:[neighbor view]];
[neighbor didMoveToParentViewController:self];
[UIView animateWithDuration:0.5 animations:^{
[[neighbor view] setFrame:self.view.frame];
} completion:^(BOOL finished){
[neighbor willMoveToParentViewController:nil];
[neighbor.view removeFromSuperview];
[neighbor removeFromParentViewController];
[[self navigationController] pushViewController:neighbor animated:NO];
NSMutableArray *newStack = [[[self navigationController] viewControllers] mutableCopy];
[newStack removeObjectAtIndex:1]; //self, just below top
[[self navigationController] setViewControllers:newStack];
}];
}
샘플 앱에서이 변형을 확인하십시오. https://github.com/mpospese/MPFoldTransition/
#pragma mark - UINavigationController(MPFoldTransition)
@implementation UINavigationController(MPFoldTransition)
//- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
- (void)pushViewController:(UIViewController *)viewController foldStyle:(MPFoldStyle)style
{
[MPFoldTransition transitionFromViewController:[self visibleViewController]
toViewController:viewController
duration:[MPFoldTransition defaultDuration]
style:style
completion:^(BOOL finished) {
[self pushViewController:viewController animated:NO];
}
];
}
- (UIViewController *)popViewControllerWithFoldStyle:(MPFoldStyle)style
{
UIViewController *toController = [[self viewControllers] objectAtIndex:[[self viewControllers] count] - 2];
[MPFoldTransition transitionFromViewController:[self visibleViewController]
toViewController:toController
duration:[MPFoldTransition defaultDuration]
style:style
completion:^(BOOL finished) {
[self popViewControllerAnimated:NO];
}
];
return toController;
}
그냥 사용하십시오 :
ViewController *viewController = [[ViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController];
navController.navigationBarHidden = YES;
[self presentViewController:navController animated:YES completion: nil];
[viewController release];
[navController release];
이것은 오래된 질문입니다. viewControllers
제안 된 답변으로 몇 가지 를 채우는 데 문제가 있었으므로 여전히이 답변을 게시하고 싶습니다 . 내 솔루션은 UINavigationController
모든 pop 및 push 메소드 를 서브 클래스 화 하고 재정의하는 것입니다.
FlippingNavigationController.h
@interface FlippingNavigationController : UINavigationController
@end
FlippingNavigationController.m :
#import "FlippingNavigationController.h"
#define FLIP_DURATION 0.5
@implementation FlippingNavigationController
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
[UIView transitionWithView:self.view
duration:animated?FLIP_DURATION:0
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionFlipFromRight
animations:^{ [super pushViewController:viewController
animated:NO]; }
completion:nil];
}
- (UIViewController *)popViewControllerAnimated:(BOOL)animated
{
return [[self popToViewController:[self.viewControllers[self.viewControllers.count - 2]]
animated:animated] lastObject];
}
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated
{
return [self popToViewController:[self.viewControllers firstObject]
animated:animated];
}
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
{
__block NSArray* viewControllers = nil;
[UIView transitionWithView:self.view
duration:animated?FLIP_DURATION:0
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionFlipFromLeft
animations:^{ viewControllers = [super popToViewController:viewController animated:NO]; }
completion:nil];
return viewControllers;
}
@end
나는이 실이 오래되었다는 것을 알고 있지만, 나는 2 센트를 넣을 것이라고 생각했다. 커스텀 애니메이션을 만들 필요는 없습니다. 간단하고 해킹이있는 방법이 있습니다. push를 사용하는 대신 새 탐색 컨트롤러를 만들고 새보기 컨트롤러를 해당 탐색 컨트롤러의 루트보기 컨트롤러로 만든 다음 원래 탐색 컨트롤러에서 탐색 컨트롤러를 표시합니다. 현재는 다양한 스타일로 쉽게 사용자 정의 할 수 있으며 사용자 정의 애니메이션을 만들 필요가 없습니다.
예를 들면 다음과 같습니다.
UIViewcontroller viewControllerYouWantToPush = UIViewController()
UINavigationController newNavController = UINavigationController(root: viewControllerYouWantToView)
newNavController.navBarHidden = YES;
self.navigationController.present(newNavController)
원하는 프리젠 테이션 스타일을 변경할 수 있습니다.
내 목적에 맞는이 작업을 수행하는 온화한 재귀 방법을 찾았습니다. 일반 팝업 애니메이션을 차단하고 애니메이션이 아닌 팝업 메시지를 대체하는 데 사용하는 인스턴스 변수 BOOL이 있습니다. 변수는 처음에 NO로 설정됩니다. 뒤로 버튼을 누르면 델리게이트 메소드가이를 YES로 설정하고 애니메이션되지 않은 새 팝업 메시지를 탐색 모음에 전송하여 이번에는 변수를 YES로 설정하여 동일한 델리게이트 메소드를 다시 호출합니다. 변수가 YES로 설정된 경우 대리자 메서드는 변수를 NO로 설정하고 애니메이션이없는 팝이 발생하도록 YES를 반환합니다. 두 번째 델리게이트 호출이 반환 된 후 첫 번째 델리게이트 호출로 돌아 가면 NO가 반환되어 원래 애니메이션 팝이 차단됩니다. 실제로 들리는 것만 큼 지저분하지는 않습니다. 내 shouldPopItem 메소드는 다음과 같습니다.
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
if ([[navigationBar items] indexOfObject:item] == 1)
{
[expandedStack restack];
}
if (!progPop)
{
progPop = YES;
[navBar popNavigationItemAnimated:NO];
return NO;
}
else
{
progPop = NO;
return YES;
}
}
나를 위해 작동합니다.