黑屏 - 在viewWillAppear中Superview为空

9

我有一个使用选项卡应用程序模板创建的应用程序(ARC,iOS 4)。

  • 有几个选项卡,在第二个选项卡的视图控制器的视图(ViewCont2)上有一个按钮。
  • 此按钮通过presentModalViewController方法加载另一个视图控制器(ModalViewCont)的视图。
  • ModalViewCont上有一个关闭按钮,调用dismissModalViewControllerAnimated。
  • 在ViewCont2的viewDidDisappear中,我将self.view = nil和其他插座设置为nil以卸载视图,以便下一次出现在屏幕上时将其重新加载。我这样做是因为它继承自一个基类(BaseViewCont),该基类初始化视图控制器的一些通用属性,并在viewDidLoad方法中添加一些按钮、标签等。因此,从该基类继承的视图控制器可以根据需要在其viewDidLoad方法中不同地配置这些属性。

问题

现在,当ModalViewCont在屏幕上时,按Home按钮将应用程序放在后台,然后再获取应用程序,关闭ModalViewCont不会带回ViewCont2的视图,而是带回一个黑屏和底部的选项卡栏。如果在点击第二个选项卡之前点击其他选项卡,则会发生相同的情况。(编辑:如果在viewWillDisappear而不是viewDidDisappear中将self.view设置为nil,则会发生这种情况。)

我确定ViewCont2加载了一个新视图(检查了它的引用),但是视图的父视图为空,因此不显示新视图,而是黑屏。

未起作用的方法

  • 在设置self.view = nil之前使用[self.view removeFromSuperview];
  • 在viewWillAppear中将视图添加到父视图;[self.parentViewController.view addSubview:self.view]; 这个方法没有很流畅地工作,视图略微向上放置。这是因为层次结构中有几个其他的父视图。

我考虑的解决方法;

  • 1- 如果在viewDidLoad中Superview为空,它会在viewWillAppear中变为可用(假设)。因此,ViewCont2的viewWillAppear方法可以使用以下代码正确加载Superview;

_

if (self.view.superview == nil)
{
    self.tabBarController.selectedViewController = nil;
    self.tabBarController.selectedViewController = self;
}
  • 2- 可以使用基类的viewWillAppear方法进行初始化,这样就不需要卸载视图了。因此,性能可以优化,它将不会在每次视图消失时被卸载。而且,通过检查一个标志只执行一次初始化会更好,而不是每次出现都执行。

问题

  • 1- 为什么父视图没有恢复?我该怎么做才能解决这个问题?(这是我想要理解和解决的主要问题,而不是尝试替代方案...)
  • 2- 将nil分配给视图以卸载它是否有错?如果是,那么在这种情况下(选项卡应用程序),如何正确卸载视图?
  • 3- 第一种解决方案有什么问题吗?看起来像是一个临时解决办法吗?关于父视图和视图出现的假设是否正确?

编辑:看起来当viewDidLoad比它应该更早地被调用时(即在viewWillDisappear中nilled视图而不是在viewDidDisappear中),superview没有设置。


你已经准确地定义了它。在子类中我没有任何特殊的东西。只需使用选项卡应用程序模板并从其nib创建模态视图,然后使用presentmodalview方法即可。 - lockedscope
我正在对呈现的视图调用dismiss方法,而不是对呈现视图进行调用。可能这就是问题的原因。我还没有时间测试它。 - lockedscope
你应该尽快测试,因为赏金将在3天后过期,如果你有可能的修复待处理,没有人会尝试进行调试。它只会在过期时毫无用处。 - David H
我已经尝试直接调用parentViewController dismissModal...以及通过parent的代理调用它,但都没有帮助。 - lockedscope
我会看看能否找到合适的解决方案,如果你已经找到了解决方案,我也不想浪费时间。 - David H
显示剩余2条评论
6个回答

7

看起来很奇怪,但你的建议(1)确实是解决这个问题的正确方法:

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    if (!self.view.superview) { // check if view has been added to view hierarchy
        self.tabBarController.selectedViewController = nil;
        self.tabBarController.selectedViewController = self;
    }
}

您的第二个建议对于性能很好(因为视图加载是一项昂贵的操作)-但它将无法解决问题。 在以下情况下,您也可能遇到黑屏问题,而不在设置视图为nil之后(在iOS模拟器中测试此操作):
1.打开模态视图 2.模拟内存警告-这将卸载标签栏控制器中的视图 3.按Home键并重新打开应用程序 4.关闭模态视图-黑屏
通常,您可以假定在viewDidLoad中设置了视图属性,在viewWillAppear和viewDidAppear中添加到视图层次结构中; 因此,在那个时候应该有superview存在(这里的superview是UIViewControllerWrapperView类的tabbarcontroller的私有视图)。 但是,在我们的情况下,尽管重新加载了视图(在应用程序恢复时),但它没有添加到视图层次结构中,导致出现黑屏。 这似乎是UITabBarController中的一个错误。
解决方法强制执行外观选择器。 因此,将再次调用viewWillAppear,这次已经有了superview。 viewDidAppear也会被调用两次!
将self.view设置为nil是可以的,但在大多数情况下不应该必要。 让系统决定何时卸载视图(iOS可以在内存不足时卸载视图)。 视图控制器代码应该设计成可以在任何时候重新配置UI而无需重新加载视图。

1

我已经找到了UITabBarController的实际解决方案(内存警告,应用程序进入后台/前台,关闭模态)。使用UITabBarController作为根视图控制器是该错误的原因。因此,我们可以使用另一个视图控制器作为根视图控制器,并从中呈现选项卡栏。我已在iOS 5.1模拟器上进行了测试。

当然,额外UIViewController的开销是有争议的。而且,这违反了苹果的文档;

与其他视图控制器不同,选项卡栏界面不应安装为另一个视图控制器的子级。UITabBarController Class Reference

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // A root view controller other than the actual UITabBarController is required.
    self.window.rootViewController = [[UIViewController alloc] init];
    [self.window makeKeyAndVisible];    
    self.tabBarController = [[UITabBarController alloc] init];
    self.tabBarController.viewControllers = [NSArray arrayWithObjects:viewController1, ..., nil];

    [self.window.rootViewController 
        presentModalViewController:self.tabBarController animated:NO];
}

1

您无法完全控制视图何时加载和卸载,也不应该手动加载/卸载视图。

相反,您应该将视图的加载/卸载视为完全由您的UIViewController负责,您只需要负责以下内容:

  • 通过将您的UIViewController子类与nib文件相关联或手动实现loadView来实现实际加载。
  • 可选地实现viewDidLoadviewWillUnloadviewDidUnload回调函数,这些回调函数由视图控制器在决定加载/卸载其视图时调用。

您无法完全控制上述回调函数何时被调用,这对于应该放入其中的内容有影响。

在您的情况下,如果我理解正确,每当您的ViewCont2的视图消失时,您都希望重置它,以便当它重新出现时,它将处于某种“干净”的状态。我会在某个方法中实现此状态重置,并从viewDidLoadviewDidDisappear中调用它。或者,您可以在viewWillAppear中拥有“清洁”逻辑。

或者您只想在点击 present 按钮时清除 ViewCont2 的视图?如果是这样,请在 viewDidLoad 中和按钮被点击时都清除该视图。


1
我提供的是,当模态视图控制器处于活动状态时,您关闭视图时,将向导航视图控制器的viewControllers添加一个新视图,然后告诉该视图删除其前任。
您可以尝试使用我的项目来查看它是否适合您。
编辑:我对所选答案的评论是,这种技术现在显然有效,但我自己很难理解它。我的项目中的代码以简单直接的方式使用系统 - 当模态视图被告知关闭自身时,它调用一个方法(可以在任何类中),将一个新视图添加到导航控制器的数组中,然后关闭自身。有一段时间会有两个相同类型的视图控制器,新的视图控制器叠加在旧的视图控制器上。当新的视图控制器出现时,根据标志静默地在幕后从nab栏的堆栈中删除不需要的视图控制器,然后它就消失了。

0

我已经找到了其他的解决方案;

  • 第一个会导致警告:"应用程序窗口在应用程序启动时应该有一个根视图控制器",尽管有根视图控制器。

  • 虽然看起来有些笨拙,但是临时视图控制器将随着第一个被释放。

  • 第二个方案似乎更合理。

.

- (void) tabBarBlankScreenFix1
{
    self.window.rootViewController = [[UIViewController alloc] init];
    [self.window makeKeyAndVisible];
    [self.window addSubview:self.tabBarController.view];            
    self.window.rootViewController = self.tabBarController;

}

- (void) tabBarBlankScreenFix2
{
    self.window.rootViewController = [[UIViewController alloc] init];
    [self.window makeKeyAndVisible];
    [self.window addSubview:self.tabBarController.view];
}

-1
我认为你不应该将视图赋值为nil。 如果我理解正确,您希望每次视图出现时都刷新/重新加载内容。 因此,您不应将视图设置为nil,而应尝试刷新它。 您可以通过添加以下代码来实现:
    - (void)viewWillAppear{
    [self.view setNeedsDisplay];}

请告诉我,如果我对您的问题理解正确的话。

刷新并不能帮助我将其重新加载到其父视图中。实际上,它的父视图通常会被还原(但并非总是如此),但在应用程序发送到后台并进入前台后,它并没有被还原。 - lockedscope

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接