sourcecode

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

copyscript 2023. 5. 14. 10:57
반응형

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

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

경고:창 계층 구조에 없는 보기의 <ViewController: 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:버튼을 눌렀을 때 호출되었으며, 세그가 버튼에 직접 연결된 상태에서 두 번째로 호출되었습니다.)

두 에 발사되고 저는 다음과 오류를 : 사상두대의세구동발고었다되사, 저다과같얻었니습오류.Attempt to present X on Y whose view is not in the window hierarchy!

viewWillLayoutSubviews그리고.viewDidLayoutSubviews 5할 수 (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];

저는 또한 제가 발표하려고 할 때 이 문제에 직면했습니다.UIViewControllerviewDidLoad제임스 베드포드의 답변은 먹혔지만, 제 앱은 1~2초 동안 배경을 먼저 보여주었습니다.

몇 가지 조사 후에, 저는 이것을 해결할 방법을 찾았습니다.addChildViewController.

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

아마, 나처럼, 당신도 잘못된 뿌리를 가지고 있을 것입니다.viewController

는 다표다니합시를 .ViewController순식간에non-UIViewController 세션,

그래서 나는 그런 코드를 사용할 수 없습니다:

[self presentViewController:]

UIView 컨트롤러가 있습니다.

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

이유로 (버그), 어떤논로유 (논리적 버그),rootViewController예상과 인 경우).UIViewController. 그런 다음 버그를 수정하고, 교체합니다.rootViewControllerUINavigationController그리고 문제는 사라졌습니다.

Swift 5 - 배경 나사산

경보 컨트롤러가 백그라운드 스레드에서 실행되는 경우 "표시 시도 중...보기가 창 계층 구조에 없는 경우" 오류가 발생할 수 있습니다.

그래서 이것은:

present(alert, animated: true, completion: nil)
    

다음과 같이 수정되었습니다.

DispatchQueue.main.async { [weak self] in
    self?.present(alert, animated: true, completion: nil)
}

TL;DR rootViewController는 1개만 가질 수 있으며 가장 최근에 표시된 컨트롤러입니다.따라서 이미 무시되지 않은 뷰 컨트롤러가 표시된 경우 뷰 컨트롤러가 다른 뷰 컨트롤러를 표시하도록 하지 마십시오.

제가 직접 몇 가지 테스트를 해본 결과 결론이 났습니다.

모든 것을 표시하려는 루트뷰 컨트롤러가 있는 경우 이 문제가 발생할 수 있습니다.

루트 컨트롤러 코드는 다음과 같습니다(루트에서 뷰 컨트롤러를 표시하는 바로 가기는 열기입니다).

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-RENT-CALL의 rootViewController는 보기 계층에 있습니다(보기를 해제하거나 제거하지 않은 경우에도).저는 모든 로더 호출(viewDidLoad, viewDidAppear, 지연된 디스패치 호출)을 사용해 보았는데, 제가 작동할 수 있는 유일한 방법은 최상위 뷰 컨트롤러에서 현재 호출만 하는 것이라는 것을 알게 되었습니다.

Swift 4.2에서도 비슷한 문제가 있었지만 뷰 사이클에서 제 견해가 제시되지 않았습니다.동시에 여러 개의 segue를 제시해야 한다는 것을 알게 되었습니다.그래서 dispatch AsyncAfter를 사용했습니다.

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)
  }
}

내 문제는 내가 세그를 공연한다는 것이었습니다.UIApplicationDelegatedidFinishLaunchingWithOptions하기 makeKeyAndVisible()창문에

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

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)
}

이 블록 안에 있는 segue 또는 present, push 코드를 호출할 수 있습니다.

override func viewDidLoad() {
    super.viewDidLoad()
    OperationQueue.main.addOperation {
        // push or present the page inside this block
    }
}

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

- (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

}

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

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

작동이 잘 됩니다. 이것을 사용해보세요.링크

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

만약 누군가에게 도움이 된다면, 제 문제는 매우 바보같았습니다.물론 전적으로 제 잘못입니다.알림이 모달을 호출하는 메서드를 트리거하고 있습니다.그러나 알림을 제대로 제거하지 못했기 때문에 어느 시점에 두 개 이상의 알림이 표시되어 모달이 여러 번 호출되었습니다.물론 모달을 한 번 호출한 후에는 모달을 호출하는 뷰 컨트롤러가 뷰 계층에 더 이상 존재하지 않기 때문에 이 문제가 발생합니다.예상하신 대로 저의 상황은 많은 다른 문제를 야기했습니다.

요약하자면, 무엇을 하든지 간에 모달이 두 번 이상 호출되지 않도록 해야 합니다.

당신이 일부를 표시하고 싶어하는 것을 고려하면, 나는 마침내 내게 작동하는 코드(Swift)를 갖게 되었습니다.viewController거의 모든 곳에서.사용 가능한 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
        })
    }
}

것을 제시하려고 한다는 것을 의미할 수 있습니다.View Controller를 통하여Navigation Controller 이동에안▁▁whileNavigation Controller 또 다른 현재다항있제습니다고하시을목른ing▁another▁currently있을 제시하고 있습니다.View Controller수정하려면 현재 제시된 내용을 해제해야 합니다.View Controller처음에 그리고 완료되면 새로운 것을 제시합니다.경고의 또 다른 원인은 다음과 같습니다.View Controller…이 아닌 main.

나는 그것을 움직여서 고쳤습니다.start() 내의 dismiss완료 블록:

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

시작에 두 개의 호출이 포함되어 있습니다.self.present()는 UINavigation Controller용입니다.UIImagePickerController.

그것이 저를 위해 고쳐주었습니다.

rootViewController를 순환하는 동안 최상위 뷰 컨트롤러를 상수로 저장하여 이 오류를 해결했습니다.

if var topController = UIApplication.shared.keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }
    topController.present(controller, animated: false, completion: nil)
    // topController should now be your topmost view controller
}

컨테이너에 포함된 보기 컨트롤러에서 segue를 수행할 때도 이 경고가 표시될 수 있습니다.올바른 솔루션은 컨테이너의 뷰 컨트롤러가 아닌 컨테이너의 상위에서 segue를 사용하는 것입니다.

아래 줄에 글을 써야 합니다.

self.searchController.definesPresentationContext = true

대신에

self.definesPresentationContext = true

UIView 컨트롤러에서

스위프트 3와 함께...

제게 일어난 또 다른 가능한 원인은 테이블 ViewCell에서 스토리보드의 다른 ViewController로 segue를 보내는 것이었습니다.저도 사용했습니다.override func prepare(for segue: UIStoryboardSegue, sender: Any?) {}셀을 클릭했을 때

ViewController에서 ViewController로 segue를 만들어 이 문제를 해결했습니다.

이 문제가 있었는데, 근본 원인은 버튼 클릭 핸들러(TouchUpInside)를 여러 번 구독한 것입니다.

ViewWillAppear에서 구독하고 있었는데, 다른 컨트롤러로 이동하기 위한 탐색 기능을 추가한 이후로 여러 번 호출되었습니다.

저는 우연히 스토리보드의 세그가 고장이 났다는 것을 알게 되었습니다.segue를 삭제하고 동일한 segue를 다시 생성하면 문제가 해결되었습니다.

기본 창에서는 항상 알림 표시와 호환되지 않는 전환이 발생할 수 있습니다.응용 프로그램 수명 주기에서 언제든지 알림을 표시할 수 있도록 하려면 작업을 수행할 수 있는 별도의 창이 있어야 합니다.

/// 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

설계상 항상 성공할 수 있는 기본적인 용도:

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

안타깝게도, 수용된 해결책은 제 경우에 효과가 없었습니다.다른 View Controller에서 해제한 후 바로 새 View Controller로 이동하려고 했습니다.

저는 깃발을 사용하여 어떤 언윈드 세그가 호출되었는지 알려줌으로써 해결책을 찾았습니다.

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

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

다음 에 VC를 합니다.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
    }
}

기본적으로 위의 코드를 사용하면 로그인/사인업 VC에서 해제된 내용을 파악하고 대시보드로 이동하거나 암호 VC를 잊어버린 경우 해제된 작업을 파악하고 로그인 페이지로 이동할 수 있습니다.

나는 이 버그가 Xcode 업데이트 후에 도착했다는 것을 발견했는데, 나는 스위프트 5라고 생각합니다.뷰 컨트롤러를 풀고 바로 segue를 실행했을 때 문제가 발생했습니다.

관련 버그를 수정하는 중에 해결책이 도착했습니다. 사용자가 페이지를 쓸어 내려 segue를 풀 수 있게 되었습니다.이것은 제 프로그램의 논리를 깨트렸습니다.

모든 컨트롤러의 프레젠테이션 모드를 자동에서 전체 화면으로 변경하여 수정되었습니다.

인터페이스 작성기의 특성 패널에서 이 작업을 수행할 수 있습니다.또는 프로그램적으로 수행하는 방법은 다음 답변을 참조하십시오.

스위프트 5

를 는나선물부다릅니을▁present로 부릅니다.viewDidLayoutSubviews에제시 바된같이에 제시된 와 같이.viewDidAppear에 보기 흉한 초 시킵니다.

창 존재 여부를 확인하고 코드를 한 번만 실행하십시오.

var alreadyPresentedVCOnDisplay = false

override func viewDidLayoutSubviews() {
        
    super.viewDidLayoutSubviews()
    
    // we call present in viewDidLayoutSubviews as
    // presenting in viewDidAppear causes a split second showing 
    // of the view controller before the modal is loaded
    
    guard let _ = view?.window else {
        // window must be assigned
        return
    }
    
    if !alreadyPresentedVCOnDisplay {
        alreadyPresentedVCOnDisplay = true
        present(...)
    }
    
}

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

반응형