• Warning: Attempt to present A on B whose view is not in the window hierarchy!


    昨天写豆瓣发广播Demo的时候,为了写Demo的简单,就使用了Storyboard,结果执行视图跳转时遇到了这个问题:

    Warning: Attempt to present <UINavigationController: 0x8d514e0> on <OAuthViewController: 0xa044a60> whose view is not in the window hierarchy!

    其功能是OAuthViewController用于用户授权,在获取用户的授权后将拿到的access_token通过NSUserDefaults保存起来。那么在下一次打开程序时,首先判断access_token是否已经存在,如果已经存在就直接跳转到UINavigationController。代码如下:

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        spinner_view.hidesWhenStopped = YES;
        [webView setDelegate:self];
        [webView setScalesPageToFit:YES];
        
        
        // 如果已经保存了授权用户的access_token,那么直接跳转到UINavigationController
        NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
        access_token = [userDefaults objectForKey:@"access_token"];
        if (access_token) {
            [self performSegueWithIdentifier:@"gotoSay" sender:nil];
            return;
        }
        
        NSString *paramClientID = [NSString stringWithFormat:@"client_id=%@", API_KEY];
        NSString *paramRedirect_uri = [NSString stringWithFormat:@"redirect_uri=%@", REDIRECT_URI];
        NSString *paramResponse_type = @"response_type=code";
        NSString *paramScope = @"shuo_basic_r,shuo_basic_w,douban_basic_common"; // 在其它API调用时可能要扩展该作用域参数
        NSString *getAuthCode = [NSString stringWithFormat:@"%@?%@&%@&%@&%@", GET_AUTHORIZATION_CODE_URL, paramClientID, paramRedirect_uri, paramResponse_type, paramScope];
        
        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:getAuthCode]];
        [spinner_view startAnimating];
        [webView loadRequest:request];
    }


    问题出现了,无法完成视图的跳转,但是用户的access_token已经在preferences的plist文件中成功保存了。控制台输出的错误信息如上所示。

    查了一下资料,发现了问题所在(StackOverflow上面的高手真的很多啊),先给出两个对我很有帮助的网址:

    [ios开发异常]whose view is not in the window hierarchy! 

    loadView、viewDidLoad、viewWillAppear、viewDidAppear等详解 

    解决方法:

    将验证access_token是否已经存在的代码转移到viewDidAppear方法中:

    - (void)viewDidAppear:(BOOL)animated {
        // 如果已经保存了授权用户的access_token,那么直接跳转到UINavigationController
        NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
        access_token = [userDefaults objectForKey:@"access_token"];
        if (access_token) {
            [self performSegueWithIdentifier:@"gotoSay" sender:nil];
            return;
        }
    }


    分析:

    相信进行单步调试的程序员都知道,只有就viewDidLoad方法完全执行完毕,才会真正在window中加载对应的界面。在修改前的程序中就有问题出现了,注意该动作发生在A的viewDidLoad方法中:

    [self performSegueWithIdentifier:@"gotoSay" sender:nil];


    作用是从A跳转到B中。

    那么在segue调用后,程序将调用B的viewDidLoad方法,在B的viewDidLoad方法执行完后,又回到了A的viewDidLoad的方法中执行剩下的语句。那么在window加载视图时其层次结构便发生了混乱,于是报错(当然如果从A跳转到B后面还有语句的话,最后加载的还是A的view)。

    viewDidAppear方法和viewDidLoad方法的区别在于:viewDidLoad方法调用时视图还没完全过渡到window中,viewDidAppear方法调用时,视图已经完全过渡到window中了。

    所以在viewDidAppear方法中调用performSegue方法实现视图跳转就不会出现以上的问题了,因为程序将有序地先执行A的viewDidLoad方法,在该方法完结后,再在viewDidAppear方法中执行segue跳转并执行B的viewDidLoad方法,这样就不会发生混乱了。

    当我想从一个VC跳转到另一个VC的时候,一般会用

    - (void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^)(void))completion;当然也可以用导航push。

    可是昨天就遇到了题目中的warning,在stackoverflow找到了答案:点击打开链接

    大意就是页面跳转必须在viewDidLoad和viewDidAppear之后才能进行。解决的办法就是确保页面跳转要在view load完毕之后进行。大神说可以通过设置延时来实现,通过实验我觉得这个不好控制,到底需要延时多少呢》

    后来换了一个方案,在viewDidLoad里用

     [selfperformSelectorOnMainThread:@selector(login)withObject:nilwaitUntilDone:NO];

    把页面跳转的代码写进函数里,然后将 waitUntilDone 设为NO,就是viewDidLoad直接返回不用等login执行。这样就可以确保login里的页面跳转是在viewDidLoad之后执行。

     

    另外,我还遇到下面这个情况:A页面跳转到B,在B里又封装了一个页面跳转(至C)。

    也就是在前面的login函数里,从A跳转至B时会传入token、secret等用来获取用户授权的数据,然后在B里再调各大公司封装好的授权页面(如百度网盘、新浪微博)。

    一开始我的login实现如下:

     

    -(void)login

    {

        loginViewController *loginVC=[[loginViewControllerallocinit];

        loginVC.delegate=self;

        [self  presentViewController:loginVC animated:NOcompletion:nil];

        [loginVC   auth:BAIDU_API_KEY   secret:nil  disk:BAIDU];//这个函数里有跳转至授权页面

    }

    这同样会出现上面的问题,解决的办法就是把最后一行的函数放在completion的block里。

     

    -(void)login

    {

        loginViewController *loginVC=[[loginViewController allocinit];

        loginVC.delegate=self;

        [self  presentViewController:loginVC animated:NO completion:^{[loginVC auth:BAIDU_API_KEY secret:nil disk:BAIDU];}];

     }

    因为:

     

    The completion handler, if provided, will be invoked after the presented  controllers viewDidAppear: callback is invoked.

  • 相关阅读:
    spring mvc实现登录验证码
    sqlite可视化工具推荐
    【转载】oracle更新语法
    解决首次访问jenkins,输入默认密码之后,一直卡住问题
    Selenium+java
    ubuntu16.04安装Ros(kinetic版本)【亲测好用】
    Selenium+java
    Selenium+java
    Selenium+Java
    Selenium+java
  • 原文地址:https://www.cnblogs.com/dzhs/p/5577215.html
Copyright © 2020-2023  润新知