iOS在启动时呈现模态视图控制器而不使用flash
Posted
技术标签:
【中文标题】iOS在启动时呈现模态视图控制器而不使用flash【英文标题】:iOS Present modal view controller on startup without flash 【发布时间】:2014-10-14 08:16:16 【问题描述】:我想在首次启动时向用户展示一个教程向导。
有没有办法在应用程序启动时呈现模态UIViewController
,而至少在一毫秒内看不到它背后的rootViewController
?
现在我正在做这样的事情(为了清楚起见省略了首次启动检查):
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
// ...
UIStoryboard *storyboard = self.window.rootViewController.storyboard;
TutorialViewController* tutorialViewController = [storyboard instantiateViewControllerWithIdentifier:@"tutorial"];
tutorialViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self.window makeKeyAndVisible];
[self.window.rootViewController presentViewController:tutorialViewController animated:NO completion:NULL];
没有运气。我试图将[self.window makeKeyAndVisible];
移到[... presentViewController:tutorialViewController ...]
语句之前,但是模态甚至没有出现。
【问题讨论】:
为什么不将 TutorialViewController 设为 RootViewController? @UlasSancak 因为我希望在用户完成教程后以模态方式将其关闭(最后一个屏幕有一个Let's Start
按钮)。
这些答案有帮助吗?
@Pandara 的回答解决了主要问题(闪烁),但目前尚无模态 VC 的解决方案
【参考方案1】:
所有 presentViewController 方法都需要先出现呈现视图控制器。为了隐藏根 VC,必须呈现一个覆盖。启动屏幕可以继续在窗口上显示,直到显示完成,然后淡出覆盖。
UIView* overlayView = [[[UINib nibWithNibName:@"LaunchScreen" bundle:nil] instantiateWithOwner:nil options:nil] firstObject];
overlayView.frame = self.window.rootViewController.view.bounds;
overlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
UIStoryboard *storyboard = self.window.rootViewController.storyboard;
TutorialViewController* tutorialViewController = [storyboard instantiateViewControllerWithIdentifier:@"tutorial"];
tutorialViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self.window makeKeyAndVisible];
[self.window addSubview:overlayView];
[self.window.rootViewController presentViewController:tutorialViewController animated:NO completion:^
NSLog(@"displaying");
[UIView animateWithDuration:0.5 animations:^
overlayView.alpha = 0;
completion:^(BOOL finished)
[overlayView removeFromSuperview];
];
];
【讨论】:
如果有人面临开始/结束外观转换的不平衡调用,请参阅 Spoek 答案以获取使用DispatchQueue
的解决方案。
我认为 Spoek 把他的名字改成了 ullstrm,反正这是解决方案 Cœur 指的是:***.com/a/41469734/84783 【参考方案2】:
布鲁斯在 Swift 3 中的赞成答案:
if let vc = window?.rootViewController?.storyboard?.instantiateViewController(withIdentifier: "LOGIN")
let launch = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()!
launch.view.frame = vc.view.bounds
launch.view.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
window?.makeKeyAndVisible()
window?.addSubview(launch.view)
//Using DispatchQueue to prevent "Unbalanced calls to begin/end appearance transitions"
DispatchQueue.global().async
// Bounce back to the main thread to update the UI
DispatchQueue.main.async
self.window?.rootViewController?.present(vc, animated: false, completion:
UIView.animate(withDuration: 0.5, animations:
launch.view.alpha = 0
, completion: (_) in
launch.view.removeFromSuperview()
)
)
【讨论】:
迄今为止最好的解决方案。您甚至可以添加解释为什么必须使用DispatchQueue.global().async
来解决开始/结束外观转换的不平衡调用。
在某些设备上,这似乎偶尔会导致我在启动屏幕上无休止地挂起。其他人有这个问题吗?
似乎是一个“试图在视图上呈现不在视图层次结构中的视图”问题,我试图通过在切换到主线程时添加延迟来解决这个问题,尽管我可以更漂亮,并将其设置为等到主视图控制器完全加载。
我在 ios 13 beta 6 上遇到了这个问题。我设置了launch .modalPresentationStyle = .fullScreen
,但window?.addSubview(launch.view)
导致演示不是全屏,而是全屏和卡片演示之间出现了一些奇怪的混合.还有其他人在 iOS 13 上遇到问题吗?
好的,似乎使用启动 VC 的第二个实例并将那个视图添加到窗口而不是同一个实例修复了 iOS 13 上的问题。与此示例不同,我使用的是视图要提交的 vc 的数量。【参考方案3】:
也许你可以使用“childViewController”
UIStoryboard *storyboard = self.window.rootViewController.storyboard;
TutorialViewController* tutorialViewController = [storyboard instantiateViewControllerWithIdentifier:@"tutorial"];
[self.window addSubview: tutorialViewController.view];
[self.window.rootViewController addChildViewController: tutorialViewController];
[self.window makeKeyAndVisible];
当您需要解散您的导师时,您可以从超级视图中删除其视图。您还可以通过设置 alpha 属性在视图上添加一些动画。希望有帮助:)
【讨论】:
我只是在尝试这种方法。我不能接受这是正确的答案,因为它不使用模态演示,但我赞成它,谢谢! 哈哈哈,你可以参考一些关于“childViewController”的文档,这是一种管理视图的有效方法。做一件事没有唯一正确的方法,对吧?;) 是的,但是对于在这里寻找解决方案的人来说,恕我直言,这不是正确的。无论如何我都会使用这种方法,因为它解决了根本问题:) 在我看来这是正确的解决方案,因为它不能使用 modalviewcontroller :-)【参考方案4】:这个问题在 iOS 10 中仍然存在。我的解决方法是:
-
在
viewWillAppear
中将模态VC 作为childVC 添加到rootVC
在viewDidAppear
:
-
将 modalVC 作为 rootVC 的子项移除
以模态方式呈现不带动画的 childVC
代码:
extension UIViewController
func embed(childViewController: UIViewController)
childViewController.willMove(toParentViewController: self)
view.addSubview(childViewController.view)
childViewController.view.frame = view.bounds
childViewController.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
addChildViewController(childViewController)
func unembed(childViewController: UIViewController)
assert(childViewController.parent == self)
childViewController.willMove(toParentViewController: nil)
childViewController.view.removeFromSuperview()
childViewController.removeFromParentViewController()
class ViewController: UIViewController
let modalViewController = UIViewController()
override func viewWillAppear(_ animated: Bool)
super.viewWillAppear(animated)
//BUG FIX: We have to embed the VC rather than modally presenting it because:
// - Modal presentation within viewWillAppear(animated: false) is not allowed
// - Modal presentation within viewDidAppear(animated: false) is not visually glitchy
//The VC is presented modally in viewDidAppear:
if self.shouldPresentModalVC
embed(childViewController: modalViewController)
//...
override func viewDidAppear(_ animated: Bool)
super.viewDidAppear(animated)
//BUG FIX: Move the embedded VC to be a modal VC as is expected. See viewWillAppear
if modalViewController.parent == self
unembed(childViewController: modalViewController)
present(modalViewController, animated: false, completion: nil)
//....
【讨论】:
这是一个不错的替代解决方案。但是由于我们正在处理启动问题,因此处理变通方法不应该是 viewController 的责任。这就是为什么我更喜欢 Spoek 的解决方案,它完全在 AppDelegate 中完成。 注意:Spoek 名称现在是 ullstrm @Cœur 这个问题不一定只发生在启动时;当视图控制器第一次出现时,它可能会在视图控制器想要在其自身之上呈现模态视图时发生。该解决方案解决了一般情况,并且比破解 AppDelegate 的启动序列更好。如果显示模式的决定是由视图控制器做出的,那么 AppDelegate 与它有任何关系是不合适的。 @devios1 啊,我不知道这个问题可能发生在启动之外。我想我需要尝试一下。但我不能给出更多的分数:两年前我已经对这个答案投了赞成票。【参考方案5】:可能是一个糟糕的解决方案,但您可以制作一个包含 2 个容器的 ViewController,其中两个容器都链接到一个 VC。然后你可以控制哪个容器应该在代码中可见,这是一个想法
if (!firstRun)
// Show normal page
normalContainer.hidden = NO;
firstRunContainer.hidden = YES;
else if (firstRun)
// Show first run page or something similar
normalContainer.hidden = YES;
firstRunContainer.hidden = NO;
【讨论】:
【参考方案6】:Bruce 的回答为我指明了正确的方向,但是因为我的模式可能会比启动时更频繁地出现(这是一个登录屏幕,所以如果他们注销就需要出现),我不想绑定我的叠加层直接到视图控制器的呈现。
这是我想出的逻辑:
self.window.rootViewController = _tabBarController;
[self.window makeKeyAndVisible];
WSILaunchImageView *launchImage = [WSILaunchImageView new];
[self.window addSubview:launchImage];
[UIView animateWithDuration:0.1f
delay:0.5f
options:0
animations:^
launchImage.alpha = 0.0f;
completion:^(BOOL finished)
[launchImage removeFromSuperview];
];
在不同的部分中,我执行了以典型的self.window.rootViewController presentViewController:...
格式呈现我的登录 VC 的逻辑,无论它是应用启动还是其他方式,我都可以使用它。
如果有人关心,我是这样创建叠加视图的:
@implementation WSILaunchImageView
- (instancetype)init
self = [super initWithFrame:[UIScreen mainScreen].bounds];
if (self)
self.image = WSILaunchImage();
return self;
这是启动图像本身的逻辑:
UIImage * WSILaunchImage()
static UIImage *launchImage = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
if (WSIEnvironmentDeviceHas480hScreen()) launchImage = [UIImage imageNamed:@"LaunchImage-700"];
else if (WSIEnvironmentDeviceHas568hScreen()) launchImage = [UIImage imageNamed:@"LaunchImage-700-568h"];
else if (WSIEnvironmentDeviceHas667hScreen()) launchImage = [UIImage imageNamed:@"LaunchImage-800-667h"];
else if (WSIEnvironmentDeviceHas736hScreen()) launchImage = [UIImage imageNamed:@"LaunchImage-800-Portrait-736h"];
);
return launchImage;
Aaaa 只是为了完成,下面是那些 EnvironmentDevice 方法的样子:
static CGSize const kIPhone4Size = (CGSize).width = 320.0f, .height = 480.0f;
BOOL WSIEnvironmentDeviceHas480hScreen(void)
static BOOL result = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
result = CGSizeEqualToSize([UIScreen mainScreen].bounds.size, kIPhone4Size);
);
return result;
【讨论】:
【参考方案7】:let vc = UIViewController()
vc.modalPresentationStyle = .custom
vc.transitioningDelegate = noFlashTransitionDelegate
present(vc, animated: false, completion: nil)
class NoFlashTransitionDelegate: NSObject, UIViewControllerTransitioningDelegate
public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController?
if source.view.window == nil,
let overlayViewController = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController(),
let overlay = overlayViewController.view
source.view.addSubview(overlay)
UIView.animate(withDuration: 0, animations: ) (finished) in
overlay.removeFromSuperview()
return nil
【讨论】:
你能提供一些上下文吗?【参考方案8】:可能会迟到,但在您的 AppDelegate 中您可以这样做:
//Set your rootViewController
self.window.rootViewController=myRootViewController;
//Hide the rootViewController to avoid the flash
self.window.rootViewController.view.hidden=YES;
//Display the window
[self.window makeKeyAndVisible];
if(shouldPresentModal)
//Present your modal controller
UIViewController *lc_viewController = [UIViewController new];
UINavigationController *lc_navigationController = [[UINavigationController alloc] initWithRootViewController:lc_viewController];
[self.window.rootViewController presentViewController:lc_navigationController animated:NO completion:^
//Display the rootViewController to show your modal
self.window.rootViewController.view.hidden=NO;
];
else
//Otherwise display the rootViewController
self.window.rootViewController.view.hidden=NO;
【讨论】:
这仍然会在演示发生时导致一些闪烁。【参考方案9】:这就是我使用故事板的方式,它适用于多种模式。 此示例有 3 个。底部、中间和顶部。
请确保在界面生成器中正确设置每个 viewController 的 storyboardID。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
UIStoryboard * storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
BottomViewController *bottomViewController = [storyboard instantiateViewControllerWithIdentifier:@"BottomViewController"];
UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
[window setRootViewController:bottomViewController];
[window makeKeyAndVisible];
if (!_loggedIn)
MiddleViewController *middleViewController = [storyboard instantiateViewControllerWithIdentifier:@"middleViewController"];
TopViewController *topViewController = [storyboard instantiateViewControllerWithIdentifier:@"topViewController"];
[bottomViewController presentViewController:middleViewController animated:NO completion:nil];
[middleViewController presentViewController:topViewController animated:NO completion:nil];
else
// setup as you normally would.
self.window = window;
return YES;
【讨论】:
谢谢,不过这样短闪还是会出现 我在这段代码中根本看不到 rootviewcontroller,即使加载了 3 个控制器也是如此。尽管我使用的控制器并没有太多的功能。也许您一次尝试加载太多?会不会是您看到的启动屏幕? 无论我尝试在根视图控制器中加载什么,有时都会出现。sometimes
不适合我:(
您可能需要查看自定义过渡。首先加载顶视图控制器,并使用自定义过渡/转场创建类似于模态解雇的动画。以上是关于iOS在启动时呈现模态视图控制器而不使用flash的主要内容,如果未能解决你的问题,请参考以下文章
UIActivity activityViewController 在 iPad 上以模态方式呈现,而不是在弹出窗口中