我想在第一次启动时向用户演示一个教程向导。
有没有一种方法可以在应用程序启动时显示模式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 ...]
语句之前,但后来模态甚至没有出现。
所有presentalViewController方法都要求呈现视图控制器首先出现。为了隐藏根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];
}];
}];
Bruce在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()
})
})
}
}
}
可能是您可以使用的"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特性在视图上添加一些动画。希望有所帮助:)
此问题在iOS 10中仍然存在。我的解决方案是:
- 在
viewWillAppear
中,将模态VC作为子VC添加到根VC - 在CCD_ 6中:
- 删除作为rootVC的子级的modalVC
- 无动画的模式化呈现儿童VC
代码:
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)
}
//....
}
}
可能是一个糟糕的解决方案,但您可以制作一个包含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;
}
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;
}
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
}
}
可能会很晚,但在您的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;
}
这就是我对故事板的处理方式,它可以处理多个模式。这个例子有3。底部、中间和顶部。
只需确保在界面生成器中正确设置每个viewController的故事板ID即可。
- (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;
}