developer tip

UIViewControllerContextTransitioning을 사용하여“From View Controller”가 사라집니다.

optionbox 2020. 8. 17. 08:45
반응형

UIViewControllerContextTransitioning을 사용하여“From View Controller”가 사라집니다.


한 가지 문제가 있으며 아래에 설명했습니다.

UIViewControllerContextTransitioning사용자 지정 전환에 사용 하고 있습니다.

두 개의 뷰 컨트롤러, 첫 번째 뷰 컨트롤러와 두 번째 뷰 컨트롤러가 있습니다.

이제 애니메이션과 함께 첫 번째 뷰 컨트롤러에 두 번째 뷰 컨트롤러를 추가하고 싶습니다. 이제 두 번째 뷰 컨트롤러가 투명하므로 두 번째 뷰 컨트롤러 아래에서 첫 번째 뷰 컨트롤러를 볼 수 있습니다.

하지만 첫 번째 뷰 컨트롤러가 보이지 않고 두 번째 뷰 컨트롤러 아래에는 검은 색 화면 만 보입니다.

-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
    self.transitionContext = transitionContext;
    if(self.isPresenting){
        [self executePresentationAnimation:transitionContext];
    }
    else{
       [self executeDismissalAnimation:transitionContext];
    }
  }

-(void)executePresentationAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
     UIView* inView = [transitionContext containerView];
     UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

     UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

     CGRect offScreenFrame = inView.frame;
     offScreenFrame.origin.y = inView.frame.size.height;
     toViewController.view.frame = offScreenFrame;

    toViewController.view.backgroundColor = [UIColor clearColor];
    fromViewController.view.backgroundColor = [UIColor clearColor];
    inView.backgroundColor = [UIColor  clearColor];
    [inView insertSubview:toViewController.view aboveSubview:fromViewController.view];
     // [inView addSubview:toViewController.view];
    CFTimeInterval duration = self.presentationDuration;
    CFTimeInterval halfDuration = duration/2;

    CATransform3D t1 = [self firstTransform];
    CATransform3D t2 = [self secondTransformWithView:fromViewController.view];

    [UIView animateKeyframesWithDuration:halfDuration delay:0.0 options:UIViewKeyframeAnimationOptionCalculationModeLinear animations:^{

    [UIView addKeyframeWithRelativeStartTime:0.0f relativeDuration:0.5f animations:^{
        fromViewController.view.layer.transform = t1;
    }];

    [UIView addKeyframeWithRelativeStartTime:0.5f relativeDuration:0.5f animations:^{
        fromViewController.view.layer.transform = t2;
    }];
    } completion:^(BOOL finished) {
    }];


    [UIView animateWithDuration:duration delay:(halfDuration - (0.3*halfDuration)) usingSpringWithDamping:0.7f initialSpringVelocity:6.0f options:UIViewAnimationOptionCurveEaseIn animations:^{
        toViewController.view.frame = inView.frame;
    } completion:^(BOOL finished) {
        [self.transitionContext completeTransition:YES];
    }];
}

경우 [self.transitionContext completeTransition:YES];라고 갑자기 제 뷰 컨트롤러가 사라지고 초 뷰 컨트롤러 아래에 블랙 스크린 디스플레이.

아무도 아이디어가 있습니까? 감사.


여기서도 같은 문제가 발생했습니다. iOS 8의 버그처럼 보입니다 . 레이더를 제출했습니다 .

화면이 검게 변한 후 Reveal사용 하여 뷰 계층 구조를 검사했습니다. UIWindow는 완전히 비어 있습니다. 뷰 계층 구조가 전혀 없습니다!

Reveal'd

나는 약간 놀았고 간단한 경우에 쉬운 해결 방법이있는 것 같습니다. toViewController의보기를 키 창의 하위보기로 다시 추가 할 수 있습니다 .

transitionContext.completeTransition(true)
UIApplication.sharedApplication().keyWindow!.addSubview(toViewController.view)

나는 확인했고 키 창 rootViewController은 여전히 ​​올바르게 설정되어 있으므로 괜찮습니다. 이미 제시된 모달 컨트롤러 내에서 컨트롤러를 제시하면 어떻게 될지 잘 모르겠습니다. 따라서 더 복잡한 경우에는 실험을해야합니다.


나는 이것의 이유가 더 잘 설명되어야한다고 생각한다.

표시하는 뷰 컨트롤러의 뷰를 원래 위치 (뷰 계층 구조)에서 꺼내서 애니메이터가 제공하는 containerView 안에 넣지 만 애니메이션이 완료된 후에는 다시 반환하지 않기 때문에 뷰가 사라집니다. 따라서 뷰 컨트롤러의 뷰는 창에서 수퍼 뷰 (containerView)와 함께 완전히 제거됩니다.

iOS 7에서 시스템은 전환이 자동으로 애니메이션을 마친 후 항상 프레젠테이션 (프레젠테이션 및 프레젠테이션)과 관련된 뷰 컨트롤러의 뷰를 원래 위치로 반환했습니다. iOS 8의 일부 프레젠테이션 스타일에서는 더 이상 발생하지 않습니다.

규칙은 매우 간단합니다. 애니메이터는 전환끝날 때 뷰 컨트롤러의 뷰가 완전히 숨겨 질 경우 (뷰 계층에서 제거됨) 프레젠테이션 뷰 컨트롤러의 뷰만 조작해야합니다 . 즉, 초기 프리젠 테이션 애니메이션이 완료된 후에는 프리젠 테이션 뷰 컨트롤러의 뷰가 아니라 제시된 뷰 컨트롤러의 뷰만 표시됩니다. 예를 들어 제시된 뷰 컨트롤러의 뷰의 불투명도를 50 %로 설정하고 UIModalPresentationFullScreen을 사용하면 제시된 뷰 컨트롤러의 뷰를 제시하는 것을 볼 수 없지만 UIModalPresentationOverFullscreen을 사용하면 (UIPresentationController의 shouldRemovePresentersView메서드가이를 지정합니다).

애니메이터가 항상 프레젠테이션 뷰 컨트롤러의 뷰를 조작하도록 허용하지 않는 이유는 무엇입니까? 우선, 프리젠 테이션 뷰 컨트롤러의 뷰가 전체 프리젠 테이션 수명주기 동안 애니메이션이 끝난 후에도 계속 보이게 될 경우에는 애니메이션을 적용 할 필요가 없습니다. 그대로 유지됩니다. 둘째, 해당 뷰 컨트롤러의 소유권이 프레젠테이션 컨트롤러로 전송되면 프레젠테이션 컨트롤러는 방향이 변경 될 때와 같이 필요할 때 해당 뷰 컨트롤러의 뷰를 레이아웃하는 방법을 알지 못하지만 프레젠테이션 뷰 컨트롤러의 원래 소유자는 .

iOS 8에서는 viewForKey:애니메이터가 조작하는 뷰를 가져 오는 방법이 도입되었습니다. 첫째, 애니메이터가 뷰를 터치하지 않아야 할 때마다 nil을 반환함으로써 위에서 설명한 규칙을 따르는 것이 도움이됩니다. 둘째, 애니메이터가 애니메이션 할 수 있도록 다른 뷰를 반환 할 수 있습니다 . 양식 시트와 유사한 프레젠테이션을 구현한다고 가정 해보십시오. 이 경우 제시된 뷰 컨트롤러의 뷰 주변에 그림자 나 장식을 추가하고 싶을 것입니다. 애니메이터는 대신 해당 장식을 애니메이션하고 표시된 뷰 컨트롤러의 뷰는 장식의 자식이됩니다.

viewControllerForKey: 사라지지 않습니다. 뷰 컨트롤러에 대한 직접 액세스가 필요하지만 애니메이터는 애니메이션에 필요한 뷰에 대해 어떤 가정도해서는 안됩니다.

애니메이터의 컨테이너 뷰 내부에 명시 적으로 배치 할 때 표시되는 뷰 컨트롤러의 뷰가 사라지는 문제 올바르게 수정하기 위해 수행 할 수있는 몇 가지 작업이 있습니다 .

  1. 프리젠 테이션 뷰 컨트롤러의 뷰를 애니메이션 할 필요가없는 경우 viewForKey:컨트롤러의 뷰를 직접 보는 대신 애니메이션 할 뷰를 가져 오는 데 사용합니다. viewForKey:nil 또는 완전히 다른 뷰를 반환 할 수 있습니다.

  2. 제시하는 뷰 컨트롤러의 뷰에 애니메이션을 적용하려면 UIModalPresentationFullScreen스타일 사용을 고려 UIModalPresentationCustom하거나을 shouldRemovePresentersView반환 하는 UIPresentationController의 자체 하위 클래스를 계속 사용 하고 구현해야합니다 YES. 실제로이 방법의 구현은에서 정의한 내부 프레젠테이션 컨트롤러 UIModalPresentationFullScreenUIModalPresentationCustom스타일 간의 주요 차이점 입니다. 후자는 사용자 지정 프레젠테이션 컨트롤러를 사용할 수 있다는 사실을 제외하고는 다릅니다 .

  3. 다른 모든 드문 경우에 다른 답변이 제안한대로 프레젠테이션 뷰 컨트롤러의 뷰를 원래 위치로 되돌려 야합니다.


iOS 8에서는에서 반환 한 뷰 컨트롤러 viewForKey:.view속성 대신에서 반환 한 뷰를 조작해야합니다 viewControllerForKey:. 이것은 베타 문서에서 특히 명확하지 않지만 UIViewControllerTransitioning.h의 소스를 살펴보면 위의 주석을 볼 수 있습니다 viewControllerForKey:.

// Currently only two keys are defined by the
// system - UITransitionContextToViewControllerKey, and
// UITransitionContextFromViewControllerKey.
// Animators should not directly manipulate a view controller's views and should
// use viewForKey: to get views instead.
- (UIViewController *)viewControllerForKey:(NSString *)key;

따라서의 프레임 등을 조정하는 대신의 toViewController.view반환 값을 사용하십시오 [transitionContext viewForKey:UITransitionContextToViewKey].

앱이 iOS7 및 / 또는 Xcode 5를 지원해야하는 경우 다음과 같이 UIViewController에서 간단한 카테고리 메서드를 사용할 수 있습니다.

- (UIView *)viewForTransitionContext:(id<UIViewControllerContextTransitioning>)transitionContext
{
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
    if ([transitionContext respondsToSelector:@selector(viewForKey:)]) {
        NSString *key = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey] == self ? UITransitionContextFromViewKey : UITransitionContextToViewKey;
        return [transitionContext viewForKey:key];
    } else {
        return self.view;
    }
#else
    return self.view;
#endif
}

그런 다음 평소 toViewControllerfromViewController같이 을 가져 오지만 [toViewController viewForTransitionContext:transitionContext].

편집 :에서 반환 될 때 프레젠테이션보기 컨트롤러의보기가 nil 인 버그가있는 것으로 보입니다 viewForKey. 이로 인해 프레젠테이션보기를 전혀 애니메이션화하는 모달 전환 (예 : 슬라이드 끄기 또는 뒤집기)을 만들 수 없습니다. rdar : // 17961976 ( http://openradar.appspot.com/radar?id=5210815787433984 ) 에서 iOS8에 대한 버그를 신고했습니다 . http://github.com/bcherry/TransitionBug 에서 샘플 프로젝트도 참조하십시오.

편집 2 : 제안에 대한 graveley 덕분에 UIModalPresentationFullScreen을 사용하여 문제를 해결합니다. 아마도 이것은 버그가 아닙니다. Apple은 UIModalPresentationCustom이 들어오는 모달의보기 만 수정하도록 의도 할 수 있습니다. 나가는보기를 수정하려면 새보기의 전체 화면 표시를 보장해야합니까? viewForKey어쨌든 UIModalPresentationFullScreen 을 사용해야합니다 .


modalPresentationStyleUIModalPresentationCustom으로 설정하지 않으면 문제가 해결되었습니다.

즉, UIModalPresentationCustom을 지정하는 대신 기본값 인 UIModalPresentationFullScreen을 그대로두면 사라지는보기 문제가 해결되었습니다. UIViewControllerTransitioningDelegate 프로토콜은 기본값으로 두더라도 여전히 따르는 것처럼 보입니다. 내가 올바르게 기억한다면, 옛날 옛적에 UIModalPresentationCustom이 요구 사항이었습니다.

지금까지 작동하는 작업은 비대화 형 애니메이션에 대해서만 시도했습니다.


Lefteris의 관련 스레드에서이 매우 유용한 답변을 찾았습니다. https://stackoverflow.com/a/27165723/3709173

그것을 요 ​​약하기:

  1. modalPresentationStyle을 .Custom으로 설정하십시오.
  2. 하위 클래스 UIPresentationController, shouldRemovePresentersView 재정의 (NO)
  3. TransitionDelegate 클래스에서 presentationControllerForPresentedViewController를 재정의하고 사용자 지정 UIPresentationController를 반환합니다.

사용자 지정 전환에 +1하고, 해제 애니메이션이 발생할 때 toView를 추가하지 마세요.

여기에서 설명 :

해킹없이 https://www.dropbox.com/s/7rpkyamv9k9j18v/CustomModalTransition.zip?dl=0 ! 마법 같아! :)


iOS 8에서는 UIPresentationController를 생성하고 UIViewControllerTransitioningDelegate에서 아래 메서드를 구현해야합니다.

- (UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source;

뷰 컨트롤러를 표시 할 때 뷰 계층을 관리하는 데 사용할 사용자 지정 프레젠테이션 컨트롤러를 대리인에게 요청합니다.

반환 값 :

모달 프레젠테이션을 관리하기위한 사용자 지정 프레젠테이션 컨트롤러입니다.

토론:

UIModalPresentationCustom 프레젠테이션 스타일을 사용하여 뷰 컨트롤러를 표시하면 시스템이이 메서드를 호출하고 사용자 지정 스타일을 관리하는 프레젠테이션 컨트롤러를 요청합니다. 이 메서드를 구현하는 경우이를 사용하여 프레젠테이션 프로세스를 관리하는 데 사용할 사용자 지정 프레젠테이션 컨트롤러 개체를 만들고 반환합니다.

이 메서드를 구현하지 않거나이 메서드의 구현이 nil을 반환하는 경우 시스템은 기본 프레젠테이션 컨트롤러 개체를 사용합니다. 기본 프리젠 테이션 컨트롤러는보기 계층 구조에보기 또는 컨텐츠를 추가하지 않습니다.

iOS 8.0 이상에서 사용 가능합니다.

자세한 내용은 WWDC 2014 비디오를 시청하십시오.

https://developer.apple.com/videos/wwdc/2014/?include=228

WWDC 2014 샘플 코드 페이지에서 다운로드 할 수있는 "LookInside : Presentation Controllers Adaptivity and Custom Animator Objects"라는 WWDC의 샘플 코드도 있습니다.

샘플 코드를 약간 변경해야 할 수도 있습니다. UIPresentationController init 메소드가 다음과 같이 변경되었습니다.

initWithPresentedViewController:presented presentingViewController:presenting

발표하기 전에 발표했습니다. 그것들을 바꾸면 작동합니다.


[inView insertSubview : toViewController.view aboveSubview : fromViewController.view] 대신; 그냥 추가하십시오 : [inView addSubview : toViewController.view];

if (self.presenting) {

    [transitionContext.containerView addSubview:toViewController.view];
    // your code

} else {
    // your code
}

여기에서 예제를 볼 수 있습니다. 링크 는 iOS 7 및 iOS 8에서 작동합니다.


다음은 Ash 수정의 Objective C 버전입니다.

// my attempt at obj-c version of Ash's fix
UIView *theToView = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view;
[[[UIApplication sharedApplication] keyWindow] addSubview:theToView];
[transitionContext completeTransition:YES]

제대로 작동하려면 다른 뷰 컨트롤러의 해제 완료 블록에서 새 뷰 컨트롤러를 표시하기 위해 뷰를 다시 추가 한 후 순서를 바꾸고 [transitionContext completeTransition :] 메서드를 호출해야했습니다.

이것이 모든 사람에게 문제를 해결할 수 있을지 모르겠지만 내 앱에서는 작동합니다. 건배!


나는 이것이 Obj-C에서 잘 작동한다는 것을 알았습니다.

    [transitionContext completeTransition:YES];
    if(![[UIApplication sharedApplication].keyWindow.subviews containsObject:toViewController.view]) {
        [[UIApplication sharedApplication].keyWindow addSubview:toViewController.view];
    }

ios7과 ios8 모두에서 잘 작동하는 것 같습니다.


viewForKey:UITransitionContextToViewKeyios8에서 nil 반환합니다. 그래서 그것이 nil이면 'to'뷰 컨트롤러에서 뷰를 가져옵니다.

However, this seems to result in the 'to' view not being moved from the container to the window when completeTransition:YES is called. So if viewForKey:UITransitionContextToViewKey returns nil, I fall over to toVC.view, and keep track of the fact that it returned nil, and after the completion I move it to the container's initial superview (which happens to be the window).

So this code works on iOS7 as well as iOS8, and should work on iOS9 too even if they fix it or not.

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    // Get the 'from' and 'to' views/controllers.
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    BOOL hasViewForKey = [transitionContext respondsToSelector:@selector(viewForKey:)]; // viewForKey is iOS8+.
    UIView *fromView = hasViewForKey ?
        [transitionContext viewForKey:UITransitionContextFromViewKey] :
        fromVC.view;
    UIView *toView = hasViewForKey ?
        [transitionContext viewForKey:UITransitionContextToViewKey] :
        toVC.view;

    // iOS8 has a bug where viewForKey:to is nil: http://stackoverflow.com/a/24589312/59198
    // The workaround is: A) get the 'toView' from 'toVC'; B) manually add the 'toView' to the container's
    // superview (eg the root window) after the completeTransition call.
    BOOL toViewNilBug = !toView;
    if (!toView) { // Workaround by getting it from the view.
        toView = toVC.view;
    }
    UIView *container = [transitionContext containerView];
    UIView *containerSuper = container.superview; // Used for the iOS8 bug workaround.

    // Perform the transition.
    toView.frame = container.bounds;
    [container insertSubview:toView belowSubview:fromView];
    [UIView animateWithDuration:kDuration delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
        fromView.frame = CGRectOffset(container.bounds, 0, CGRectGetHeight(container.bounds));
    } completion:^(BOOL finished) {
        [transitionContext completeTransition:YES];

        if (toViewNilBug) {
            [containerSuper addSubview:toView];
        }
    }];
}

I found that this bug (and many more!) vanishes if you set modalPresentationStyle = UIModalPresentationFullScreen. You of course still get your custom transition animation.


After encountering this issue, I was very confused, because I'd written something almost identical not too long ago that worked fine. Came here looking for answers to find fixes that look pretty hacky, and don't seem to understand the root cause... it's actually very easy to fix.

Some answers mention changing modalPresentationStyle to .overFullScreen. This is correct, .overCurrentContext would work too. This is expected, and the behaviour Apple documents. But why isn't this working for everyone? Why all the hacky code, and combinations of this with something else, and crazy stuff that you shouldn't be doing?

Turns out, you need to set the presentation style BEFORE THE VIEW LOADS. Not after. Do it in init, or do it from the previous controller, or however you'd like - as long as it is before the view loads.


Using the new UIModalPresentationOverCurrentContext fixed it for me. My original transition on iOS 7 was just to have a blurred background of the view underneath the modal.


I got stuck on this issue too. I was looking to create a custom transition with a semi-transparent background where I could still see the view controller I was coming from but I only got a black background. I found Mark Aron's answer in this thread helped me but it is written in Objective C so here is a Swift 3 version of that answer which I have tested for iOS 9 and iOS 10:

  1. Create a subclass of UIPresentationController. Override the shouldRemovePresentersView to false as follows:

    class ModalPresentationController: UIPresentationController {
    
    override var shouldRemovePresentersView: Bool {
    return false
    }
    
    override func containerViewWillLayoutSubviews() {
    presentedView?.frame = frameOfPresentedViewInContainerView
    }
    }
    
  2. In the place you are instantiating the new view controller and setting its transition delegate, indicate that you want it to show a custom modal presentation style as follows:

    let newVC = mainStoryboard.instantiateViewController(withIdentifier: "newVC") as! NewViewController 
    
    newVC.transitioningDelegate = self
    
    newVC.modalPresentationStyle = UIModalPresentationStyle.custom
    
    newVC.modalPresentationCapturesStatusBarAppearance = true //optional
    
    present(newVC, animated: true, completion: nil)
    
  3. 이제 UIViewControllerTransitioningDelegate의 presentationController 메서드를 재정의하고 사용자 지정 UIPresentationController를 반환합니다. 나는 현재 수업의 확장으로 내 것을 가지고 있었다.

    extension CurrentViewController: UIViewControllerTransitioningDelegate {
    
    //this is where you implement animationController(forPresented) and animationController(forDismissed) methods
    
    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
    
    return ModalPresentationController(presentedViewController: presented, presenting: source)
    
    }
    }
    

주목해야 할 또 다른 사항은 presentAnimator 클래스에서 fromView를 참조하지 않아야한다는 것입니다. 이것은 nil이고 런타임에 오류가 발생합니다. 그 외에 같은 것을 구현하면 애니메이션과 반투명 배경으로 사용자 정의 전환을 얻을 수 있습니다.


뷰 컨트롤러를 다른 뷰 컨트롤러의 자식으로 추가합니다.

[self addChildViewController:childViewController];                 

확인하고 알려주세요.

참고 URL : https://stackoverflow.com/questions/24338700/from-view-controller-disappears-using-uiviewcontrollercontexttransitioning

반응형