Program Tip

보기가 창 계층 구조에없는 UIViewController에 UIViewController를 제공하려고합니다.

programtip 2020. 10. 3. 11:32
반응형

보기가 창 계층 구조에없는 UIViewController에 UIViewController를 제공하려고합니다.


Xcode 4.5를 사용하기 시작했는데 콘솔에이 오류가 발생했습니다.

경고 : 뷰가 창 계층 구조에없는 <ViewController : 0x1ec3e000>에서 <finishViewController : 0x1e56e0a0>을 표시하려고합니다!

보기가 여전히 표시되고 앱의 모든 것이 제대로 작동합니다. iOS 6의 새로운 기능입니까?

다음은보기간에 변경하는 데 사용하는 코드입니다.

UIStoryboard *storyboard = self.storyboard;
finishViewController *finished = 
[storyboard instantiateViewControllerWithIdentifier:@"finishViewController"];

[self presentViewController:finished animated:NO completion:NULL];

이 메서드를 어디에서 호출합니까? viewDidLoad메서드 내에서 모달 뷰 컨트롤러를 제시하려고하는 문제가있었습니다 . 나를위한 해결책은이 호출을 viewDidAppear:메서드 로 옮기는 것이 었습니다 .

내 가정은 뷰 컨트롤러의 뷰가 있다는 것입니다 하지 합니다 (이로드 된 시점에서 윈도우의 뷰 계층에 viewDidLoad메시지가 전송), 그러나 그것은 이다 가 발표 된 후 윈도우 계층합니다 (경우에 viewDidAppear:메시지가 전송됩니다) .


주의

당신이를 호출 할 할 경우 presentViewController:animated:completion:viewDidAppear:모달 뷰 컨트롤러는 항상 제공되고, 이에 당신이 문제로 실행할 수를 때마다 뷰 컨트롤러의 뷰가 나타납니다 (어떤 의미!)과 모달 뷰 컨트롤러는 멀리 가지 않을 것되게되고, 그래서 .. .

모달 뷰 컨트롤러를 표시하기에 가장 좋은 장소가 아닐 수도 있고, 프레젠테이션 뷰 컨트롤러가 모달 뷰 컨트롤러를 즉시 표시할지 여부를 결정할 수 있도록 몇 가지 추가 상태를 유지해야 할 수도 있습니다.


또 다른 잠재적 원인 :

실수로 동일한 뷰 컨트롤러를 두 번 제시 할 때이 문제가 발생했습니다. (한 번은 performSegueWithIdentifer:sender:버튼을 눌렀을 때 호출되었고 두 번째는 버튼에 직접 연결된 segue와 함께).

실제로 두 개의 segue가 동시에 실행되었고 오류가 발생했습니다. Attempt to present X on Y whose view is not in the window hierarchy!


viewWillLayoutSubviews그리고 viewDidLayoutSubviews(iOS 5.0 이상)을 이러한 목적으로 사용할 수 있습니다. viewDidAppear보다 일찍 호출됩니다.


모든 하위보기를 기본보기에 표시하려면 다음 코드를 사용하십시오.

UIViewController *yourCurrentViewController = [UIApplication sharedApplication].keyWindow.rootViewController;

while (yourCurrentViewController.presentedViewController) 
{
   yourCurrentViewController = yourCurrentViewController.presentedViewController;
}

[yourCurrentViewController presentViewController:composeViewController animated:YES completion:nil];

기본보기에서 모든 하위보기를 닫으려면 다음 코드를 사용하십시오.

UIViewController *yourCurrentViewController = [UIApplication sharedApplication].keyWindow.rootViewController;

while (yourCurrentViewController.presentedViewController) 
{
   yourCurrentViewController = yourCurrentViewController.presentedViewController;
}

[yourCurrentViewController dismissViewControllerAnimated:YES completion:nil];

을 (를) 제시하려고 할 때도이 문제가 발생 UIViewController했습니다 viewDidLoad. James Bedford의 답변은 효과가 있었지만 내 앱은 1 ~ 2 초 동안 배경을 먼저 표시했습니다.

몇 가지 연구 끝에 addChildViewController.

- (void)viewDidLoad
{
    ...
    [self.view addSubview: navigationViewController.view];
    [self addChildViewController: navigationViewController];
    ...
}

아마 나처럼 너도 뿌리가 틀렸나 봐 viewController

나는를 표시 할 ViewControllerA의 non-UIViewController컨텍스트

따라서 이러한 코드를 사용할 수 없습니다.

[self presentViewController:]

그래서 UIViewController를 얻습니다.

[[[[UIApplication sharedApplication] delegate] window] rootViewController]

어떤 이유로 (논리적 버그) rootViewController예상과 다른 것입니다 (정상 UIViewController). 그럼, 버그를 수정 교체 rootViewController로모그래퍼 UINavigationController하고 문제가 사라입니다.


TL; DR 당신은 하나의 rootViewController와 가장 최근에 제시된 것을 가질 수 있습니다. 따라서 이미 해제되지 않은 뷰 컨트롤러가 표시되었을 때 뷰 컨트롤러가 다른 뷰 컨트롤러를 표시하도록하지 마십시오.

내 자신의 테스트를 수행 한 후 결론에 도달했습니다.

모든 것을 표시하려는 rootViewController가 있다면이 문제에 부딪 힐 수 있습니다.

다음은 내 rootController 코드입니다 (open은 루트에서 뷰 컨트롤러를 표시하기위한 바로 가기입니다).

func open(controller:UIViewController)
{
    if (Context.ROOTWINDOW.rootViewController == nil)
    {
        Context.ROOTWINDOW.rootViewController = ROOT_VIEW_CONTROLLER
        Context.ROOTWINDOW.makeKeyAndVisible()
    }

    ROOT_VIEW_CONTROLLER.presentViewController(controller, animated: true, completion: {})
}

(경과 시간에 관계없이) 두 번 연속으로 open을 호출하면 첫 번째 오픈에서는 잘 작동하지만 두 번째 오픈에서는 작동하지 않습니다. 두 번째 열기 시도는 위의 오류를 발생시킵니다.

그러나 가장 최근에 제시된 뷰를 닫은 다음 open을 호출하면 다른 뷰 컨트롤러에서 다시 open을 호출하면 제대로 작동합니다.

func close(controller:UIViewController)
{
    ROOT_VIEW_CONTROLLER.dismissViewControllerAnimated(true, completion: nil)
}

내가 결론을 내린 것은 MOST-RECENT-CALL의 rootViewController만이 뷰 계층 구조에 있다는 것입니다 (해제하거나 뷰를 제거하지 않은 경우에도). 모든 로더 호출 (viewDidLoad, viewDidAppear 및 지연된 디스패치 호출 수행)을 사용해 보았습니다. 작업을 수행 할 수있는 유일한 방법은 최상위 뷰 컨트롤러에서 현재 호출하는 것뿐이라는 것을 알았습니다.


내 문제는 내가 창문에 전화하기 전에 UIApplicationDelegatedidFinishLaunchingWithOptions방법으로 segue를 수행하고 있었다는 것입니다 makeKeyAndVisible().


viewController거의 모든 곳에서 일부를 표시하려는 것을 고려하여 마침내 저에게 작동하는 코드 (Swift) 를 만들었습니다. 이 코드는 사용 가능한 rootViewController가 없을 때 분명히 충돌 할 것입니다. 또한 일반적으로 필요한 UI 스레드 전환을 포함하지 않습니다.

dispatch_sync(dispatch_get_main_queue(), {
    guard !NSBundle.mainBundle().bundlePath.hasSuffix(".appex") else {
       return; // skip operation when embedded to App Extension
    }

    if let delegate = UIApplication.sharedApplication().delegate {
        delegate.window!!.rootViewController?.presentViewController(viewController, animated: true, completion: { () -> Void in
            // optional completion code
        })
    }
}

재생 된 비디오가있는 AVPlayer 개체가있는 경우 먼저 비디오를 일시 중지해야합니다.


나는 같은 문제가 있었다. 문제는 알림에 의해 performSegueWithIdentifier가 트리거되었다는 것입니다. 알림을 주 스레드에 올리 자마자 경고 메시지가 사라졌습니다.


잘 작동하고 있습니다. 링크

UIViewController *top = [UIApplication sharedApplication].keyWindow.rootViewController;
[top presentViewController:secondView animated:YES completion: nil];

경고의이 종류는 당신이있는 새에 노력하고 있음을 의미 할 수 있습니다 View Controller통해 Navigation Controller이 동안 Navigation Controller현재 다른를 제시한다 View Controller. 문제를 해결하려면 View Controller처음 에는 현재 제시된 내용을 닫고 완료되면 새로운 것을 제시해야합니다. 경고의 또 다른 원인이 존재하는 것을 시도 할 수있다 View Controller이상의 스레드 서로 main.


제 상황에서는 클래스 오버라이드에 제 것을 넣을 수 없었습니다. 그래서, 내가 얻은 것은 다음과 같습니다.

let viewController = self // I had viewController passed in as a function,
                          // but otherwise you can do this


// Present the view controller
let currentViewController = UIApplication.shared.keyWindow?.rootViewController
currentViewController?.dismiss(animated: true, completion: nil)

if viewController.presentedViewController == nil {
    currentViewController?.present(alert, animated: true, completion: nil)
} else {
    viewController.present(alert, animated: true, completion: nil)
}

나는 같은 문제가 있었다. 내비게이션 컨트롤러를 내장하고이를 통해 컨트롤러를 제시해야했습니다. 아래는 샘플 코드입니다.

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    UIImagePickerController *cameraView = [[UIImagePickerController alloc]init];
    [cameraView setSourceType:UIImagePickerControllerSourceTypeCamera];
    [cameraView setShowsCameraControls:NO];

    UIView *cameraOverlay = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 768, 1024)];
    UIImageView *imageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"someImage"]];
    [imageView setFrame:CGRectMake(0, 0, 768, 1024)];
    [cameraOverlay addSubview:imageView];

    [cameraView setCameraOverlayView:imageView];

    [self.navigationController presentViewController:cameraView animated:NO completion:nil];
//    [self presentViewController:cameraView animated:NO completion:nil]; //this will cause view is not in the window hierarchy error

}

누구에게나 도움이된다면 내 문제는 매우 어리석은 일이었습니다. 물론 내 잘못이다. 알림이 모달을 호출하는 메서드를 트리거했습니다. 하지만 알림을 올바르게 제거하지 않았기 때문에 어느 시점에서 알림이 두 개 이상있을 수 있으므로 모달이 여러 번 호출됩니다. 물론 모달을 한 번 호출하면이를 호출하는 뷰 컨트롤러가 더 이상 뷰 계층 구조에 있지 않으므로이 문제가 발생합니다. 내 상황은 예상대로 다른 많은 문제를 일으켰습니다.

요약 하면 모달이 두 번 이상 호출되지 않는지 확인하십시오 .


I fixed it by moving the start() function inside the dismiss completion block:

self.tabBarController.dismiss(animated: false) {
  self.start()
}

Start contains two calls to self.present() one for a UINavigationController and another one for a UIImagePickerController.

That fixed it for me.


You can also get this warning when performing a segue from a view controller that is embedded in a container. The correct solution is to use segue from the parent of container, not from container's view controller.


Have to write below line.

self.searchController.definesPresentationContext = true

instead of

self.definesPresentationContext = true

in UIViewController


With Swift 3...

Another possible cause to this, which happened to me, was having a segue from a tableViewCell to another ViewController on the Storyboard. I also used override func prepare(for segue: UIStoryboardSegue, sender: Any?) {} when the cell was clicked.

I fixed this issue by making a segue from ViewController to ViewController.


I had this issue, and the root cause was subscribing to a button click handler (TouchUpInside) multiple times.

It was subscribing in ViewWillAppear, which was being called multiple times since we had added navigation to go to another controller, and then unwind back to it.


It happened to me that the segue in the storyboard was some kind of broken. Deleting the segue (and creating the exact same segue again) solved the issue.


With your main window, there will likely always be times with transitions that are incompatible with presenting an alert. In order to allow presenting alerts at any time in your application lifecycle, you should have a separate window to do the job.

/// independant window for alerts
@interface AlertWindow: UIWindow

+ (void)presentAlertWithTitle:(NSString *)title message:(NSString *)message;

@end

@implementation AlertWindow

+ (AlertWindow *)sharedInstance
{
    static AlertWindow *sharedInstance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[AlertWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
    });
    return sharedInstance;
}

+ (void)presentAlertWithTitle:(NSString *)title message:(NSString *)message
{
    // Using a separate window to solve "Warning: Attempt to present <UIAlertController> on <UIViewController> whose view is not in the window hierarchy!"
    UIWindow *shared = AlertWindow.sharedInstance;
    shared.userInteractionEnabled = YES;
    UIViewController *root = shared.rootViewController;
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
    alert.modalInPopover = true;
    [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
        shared.userInteractionEnabled = NO;
        [root dismissViewControllerAnimated:YES completion:nil];
    }]];
    [root presentViewController:alert animated:YES completion:nil];
}

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];

    self.userInteractionEnabled = NO;
    self.windowLevel = CGFLOAT_MAX;
    self.backgroundColor = UIColor.clearColor;
    self.hidden = NO;
    self.rootViewController = UIViewController.new;

    [NSNotificationCenter.defaultCenter addObserver:self
                                           selector:@selector(bringWindowToTop:)
                                               name:UIWindowDidBecomeVisibleNotification
                                             object:nil];

    return self;
}

/// Bring AlertWindow to top when another window is being shown.
- (void)bringWindowToTop:(NSNotification *)notification {
    if (![notification.object isKindOfClass:[AlertWindow class]]) {
        self.hidden = YES;
        self.hidden = NO;
    }
}

@end

Basic usage that, by design, will always succeed:

[AlertWindow presentAlertWithTitle:@"My title" message:@"My message"];

Sadly, the accepted solution did not work for my case. I was trying to navigate to a new View Controller right after unwind from another View Controller.

I found a solution by using a flag to indicate which unwind segue was called.

@IBAction func unwindFromAuthenticationWithSegue(segue: UIStoryboardSegue) {
    self.shouldSegueToMainTabBar = true
}

@IBAction func unwindFromForgetPasswordWithSegue(segue: UIStoryboardSegue) {
    self.shouldSegueToLogin = true
}

Then present the wanted VC with present(_ viewControllerToPresent: UIViewController)

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    if self.shouldSegueToMainTabBar {
        let mainTabBarController = storyboard.instantiateViewController(withIdentifier: "mainTabBarVC") as! MainTabBarController
        self.present(mainTabBarController, animated: true)
        self.shouldSegueToMainTabBar = false
    }
    if self.shouldSegueToLogin {
        let loginController = storyboard.instantiateViewController(withIdentifier: "loginVC") as! LogInViewController
        self.present(loginController, animated: true)
        self.shouldSegueToLogin = false
    }
}

Basically, the above code will let me catch the unwind from login/SignUp VC and navigate to the dashboard, or catch the unwind action from forget password VC and navigate to the login page.


I just had this issue too, but it had nothing to do with the timing. I was using a singleton to handle scenes, and I set it as the presenter. In other words "Self" wasn't hooked up to anything. I just made its inner "scene" the new presenter and voila, it worked. (Voila loses its touch after you learn its meaning, heh).

So yeah, it's not about "magically finding the right way", it's about understanding where your code stands and what it's doing. I'm happy Apple gave such a plain-English warning message, even with emotion to it. Kudos to the apple dev who did that!!


If other solutions does not look good for some reason, you can still use this good old workaround of presenting with the delay of 0, like this:

dispatch_after(0, dispatch_get_main_queue(), ^{
    finishViewController *finished = [self.storyboard instantiateViewControllerWithIdentifier:@"finishViewController"];
    [self presentViewController:finished animated:NO completion:NULL];    
});

While I've seen no documented guarantee that your VC would be on the view hierarchy on the time dispatch block is scheduled to execution, I've observed it would work just fine.

Using delay of e.g. 0.2 sec is also an option. And the best thing - this way you don't need to mess with boolean variable in viewDidAppear:


I had similar issue on Swift 4.2 but my view was not presented from the view cycle. I found that I had multiple segue to be presented at same time. So I used dispatchAsyncAfter.

func updateView() {

 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in

// for programmatically presenting view controller 
// present(viewController, animated: true, completion: nil)

//For Story board segue. you will also have to setup prepare segue for this to work. 
 self?.performSegue(withIdentifier: "Identifier", sender: nil)
  }
}

This works for to present any view controller ,if you have navigation controller available. self.navigationController?.present(MyViewController, animated: true, completion: nil) Also , I can able to present alerts and mail controller also.

참고URL : https://stackoverflow.com/questions/11862883/attempt-to-present-uiviewcontroller-on-uiviewcontroller-whose-view-is-not-in-the

반응형