Подтвердить что ты не робот

Лучший способ переключения между UISplitViewController и другими контроллерами представлений?

Я создаю приложение для iPad. Один из экранов в приложении отлично подходит для использования UISplitViewController. Тем не менее, верхний уровень приложения - это главное меню, для которого я не хочу использовать UISplitViewController. Это создает проблему, потому что Apple заявляет, что:

  • UISplitViewController должен быть контроллером представления верхнего уровня в приложении, т.е. его представление должно быть добавлено как подзаголовок UIWindow

  • если он используется, UISplitViewController должен присутствовать на протяжении всего срока действия приложения - т.е. не удалять его представление из UIWindow и помещать другое на место или наоборот

Просматривая и экспериментируя, кажется, что это всего лишь жизнеспособный вариант для удовлетворения требований Apple, а наш собственный - использование модальных диалогов. Итак, наше приложение имеет UISplitViewController на корневом уровне (т.е. Его представление добавлено как подзадача UIWindow), и чтобы показать наше главное меню, мы нажимаем его как полноэкранный модальный диалог на UISplitViewController. Затем, отклонив модальный диалог диспетчера меню главного меню, мы можем фактически показать наше разделенное представление.

Эта стратегия работает нормально. Но он задает вопросы:

1) Есть ли лучший способ структурирования этого, без модалов, который также отвечает всем указанным требованиям? Кажется немного странным, если основной пользовательский интерфейс появляется из-за того, что его вытесняют как модальный диалог. (Предполагается, что модалы предназначены для целенаправленных задач пользователя.)

2) Есть ли у меня риск отказа магазина приложений из-за моего подхода? Эта модальная стратегия, вероятно, является "неправильным использованием" модальных диалогов, в соответствии с руководящими принципами человеческого интерфейса Apple. Но какой другой выбор они мне дали? Знают ли они, что я все это делаю?

4b9b3361

Ответ 1

Touche! Подходите к той же проблеме и решили ее так же, используя модалы. В моем случае это был вид входа в систему, а затем главное меню, которое должно быть показано перед splitview. Я использовал ту же стратегию, что и вы. Я (как и многие другие знающие iOS люди, с которыми я говорил) не смог найти лучшего выхода. Прекрасно работает для меня. Пользователь никогда не замечает модальность. Представьте их так. И да, я также могу сказать вам, что есть довольно много приложений, которые делают то же самое под трюками капюшона в магазине приложений.:) Еще одна заметка, дайте мне знать, если вы когда-нибудь увидите лучший выход:)

Ответ 2

Я серьезно не верил, что эта концепция наличия некоторого UIViewController для отображения перед UISplitViewController (форма входа), например, оказывается настолько сложной, пока мне не пришлось создавать такой вид hiearchy.

Мой пример основан на iOS 8 и XCode 6.0 (Swift), поэтому я не уверен, была ли эта проблема существовать ранее одним и тем же способом или из-за некоторых новых ошибок, введенных в iOS 8, но из всех аналогичные вопросы, которые я нашел, я не видел полного "не очень взломанного" решения этой проблемы.

Я проведу вас через некоторые из вещей, которые я пробовал, прежде чем я закончил решение (в конце этого сообщения). Каждый пример основан на создании нового проекта из шаблона Master-Detail без включенного CoreData.


Сначала попробуйте (modal segue для UISplitViewController):

  • создать новый подкласс UIViewController (например, LoginViewController)
  • добавить новый контроллер представления в раскадровку, установить его как начальный контроллер представления (вместо UISplitViewController) и подключить его к LoginViewController
  • добавьте UIButton в LoginViewController и создайте modal segue с этой кнопки в UISplitViewController
  • изменить код установки шаблона для UISplitViewController из AppDelegate didFinishLaunchingWithOptions в LoginViewController prepareForSegue

Это почти сработало. Я говорю почти, потому что после того, как приложение запускается с LoginViewController, и вы нажимаете кнопку и переходите к UISplitViewController, происходит странная ошибка: показ и скрытие главного контроллера просмотра при изменении ориентации больше не анимируется.

После некоторого времени, борющегося с этой проблемой и без реального решения, я подумал, что это как-то связано с этим странным правилом, что UISplitViewController должен быть rootViewController (и в этом случае это не так, LoginViewController) поэтому я отказался от этого не столь совершенного решения.


Вторая попытка (modal segue из UISplitViewController):

  • создать новый подкласс UIViewController (например, LoginViewController)
  • добавить новый контроллер представления в раскадровку и подключить его к LoginViewController (но на этот раз оставить UISplitViewController в качестве начального контроллера представления)
  • создать modal segue из UISplitViewController в LoginViewController
  • добавьте UIButton в LoginViewController и создайте разворот из этой кнопки

Наконец, добавьте этот код в AppDelegate didFinishLaunchingWithOptions после шаблона кода для настройки UISplitViewController:

window?.makeKeyAndVisible()
splitViewController.performSegueWithIdentifier("segueToLogin", sender: self)
return true

или попробуйте с этим кодом:

window?.makeKeyAndVisible()
let loginViewController = splitViewController.storyboard?.instantiateViewControllerWithIdentifier("LoginVC") as LoginViewController
splitViewController.presentViewController(loginViewController, animated: false, completion: nil)
return true

Оба этих примера производят одни и те же самые плохие вещи:

  • вывод консоли: Unbalanced calls to begin/end appearance transitions for <UISplitViewController: 0x7fc8e872fc00>
  • UISplitViewController должен быть показан первым, прежде чем LoginViewController будет изменен модально (я предпочел бы представить только регистрационную форму, чтобы пользователь не видел UISplitViewController перед входом в систему)
  • Unwind segue не вызван (это совсем другая ошибка, и я не буду входить в эту историю)

Решение (обновление rootViewController)

Единственный способ, который я нашел, который работает правильно, - если вы меняете окно rootViewController на лету:

  • Определите идентификатор раскадровки для LoginViewController и UISplitViewController, и добавьте какое-то свойство loggedIn в AppDelegate.
  • На основе этого свойства создайте соответствующий контроллер представления и после этого установите его как rootViewController.
  • Сделать это без анимации в didFinishLaunchingWithOptions, но анимировать при вызове из пользовательского интерфейса.

Вот пример кода из AppDelegate:

var loggedIn = false

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    setupRootViewController(false)
    return true
}

func setupRootViewController(animated: Bool) {
    if let window = self.window {
        var newRootViewController: UIViewController? = nil
        var transition: UIViewAnimationOptions

        // create and setup appropriate rootViewController
        if !loggedIn {
            let loginViewController = window.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier("LoginVC") as LoginViewController
            newRootViewController = loginViewController
            transition = .TransitionFlipFromLeft

        } else {
            let splitViewController = window.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier("SplitVC") as UISplitViewController
            let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as UINavigationController
            navigationController.topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem()
            splitViewController.delegate = self

            let masterNavigationController = splitViewController.viewControllers[0] as UINavigationController
            let controller = masterNavigationController.topViewController as MasterViewController

            newRootViewController = splitViewController
            transition = .TransitionFlipFromRight
        }

        // update app rootViewController
        if let rootVC = newRootViewController {
            if animated {
                UIView.transitionWithView(window, duration: 0.5, options: transition, animations: { () -> Void in
                    window.rootViewController = rootVC
                    }, completion: nil)
            } else {
                window.rootViewController = rootVC
            }
        }
    }
}

И это пример кода из LoginViewController:

@IBAction func login(sender: UIButton) {
    let delegate = UIApplication.sharedApplication().delegate as AppDelegate
    delegate.loggedIn = true
    delegate.setupRootViewController(true)
}

Я также хотел бы услышать, есть ли какой-нибудь лучший/более чистый способ для правильной работы в iOS 8.

Ответ 3

И кто сказал, что у вас может быть только одно окно?:)

Посмотрите, может ли помочь мой ответ по этому аналогичному вопросу.

Этот подход работает очень хорошо для меня. До тех пор, пока вам не нужно беспокоиться о нескольких экранах или восстановлении состояния, этот связанный код должен быть достаточным для того, чтобы сделать то, что вам нужно: вам не нужно заставлять свою логику смотреть назад или переписывать существующий код и все еще можете воспользоваться UISplitView на более глубоком уровне в вашем приложении - без (AFAIK), нарушающего рекомендации Apple.

Ответ 4

Для будущих разработчиков iOS, работающих в одной и той же проблеме: вот еще один ответ и объяснения. Вы должны сделать это корневым контроллером. Если это не так, наложите модальный.

UISplitviewcontroller не как контроллер rootview

Ответ 5

Я хотел бы внести свой вклад в представление UISplitViewController, так как вам может понравиться через -presentViewController:animated:completion: (все мы знаем, что это не сработает). Я создал подкласс UISplitViewController, который отвечает на:

-presentAsRootViewController
-returnToPreviousViewController

Класс, который, как и другие успешные подходы, устанавливает UISplitViewController в качестве окна rootViewController, но делает это с анимацией, подобной той, которую вы получаете (по умолчанию) с помощью -presentViewController:animated:completion:

PresentableSplitViewController.h

#import <UIKit/UIKit.h>    
@interface PresentableSplitViewController : UISplitViewController    
- (void) presentAsRootViewController;
@end

PresentableSplitViewController.m

#import "PresentableSplitViewController.h"

@interface PresentableSplitViewController ()
@property (nonatomic, strong) UIViewController *previousViewController;
@end

@implementation PresentableSplitViewController

- (void) presentAsRootViewController {

    UIWindow *window=[[[UIApplication sharedApplication] delegate] window];
    _previousViewController=window.rootViewController;

    UIView *windowSnapShot = [window snapshotViewAfterScreenUpdates:YES];
    window.rootViewController = self;

    [window insertSubview:windowSnapShot atIndex:0];

    CGRect dstFrame=self.view.frame;

    CGSize offset=CGSizeApplyAffineTransform(CGSizeMake(0, 1), window.rootViewController.view.transform);
    offset.width*=self.view.frame.size.width;
    offset.height*=self.view.frame.size.height;
    self.view.frame=CGRectOffset(self.view.frame, offset.width, offset.height);

    [UIView animateWithDuration:0.5
                          delay:0.0
         usingSpringWithDamping:1.0
          initialSpringVelocity:0.0
                        options:UIViewAnimationOptionCurveEaseInOut
                     animations:^{
                         self.view.frame=dstFrame;
                     } completion:^(BOOL finished) {
                         [windowSnapShot removeFromSuperview];
                     }];
}

- (void) returnToPreviousViewController {
    if(_previousViewController) {

        UIWindow *window=[[[UIApplication sharedApplication] delegate] window];

        UIView *windowSnapShot = [window snapshotViewAfterScreenUpdates:YES];
        window.rootViewController = _previousViewController;

        [window addSubview:windowSnapShot];

        CGSize offset=CGSizeApplyAffineTransform(CGSizeMake(0, 1), window.rootViewController.view.transform);
        offset.width*=windowSnapShot.frame.size.width;
        offset.height*=windowSnapShot.frame.size.height;

        CGRect dstFrame=CGRectOffset(windowSnapShot.frame, offset.width, offset.height);

        [UIView animateWithDuration:0.5
                              delay:0.0
             usingSpringWithDamping:1.0
              initialSpringVelocity:0.0
                            options:UIViewAnimationOptionCurveEaseInOut
                         animations:^{
                             windowSnapShot.frame=dstFrame;
                         } completion:^(BOOL finished) {
                             [windowSnapShot removeFromSuperview];
                             _previousViewController=nil;
                         }];
    }
}

@end

Ответ 6

Я сделал UISplitView в качестве начального представления, чем он имеет модально для полноэкранного UIView и обратно в UISplitView. Если вам нужно вернуться в SplitView, вы должны использовать пользовательский сегмент.

Прочитайте эту ссылку (переведите ее с японского)

UIViewController для UISplitViewController

Ответ 7

Добавление к ответу @tadija Я в подобной ситуации:

Мое приложение предназначалось только для телефонов, и я добавляю пользовательский интерфейс планшета. Я решил сделать это в Swift в том же приложении и, в конечном счете, перенести все приложение, чтобы использовать тот же раскадровки (когда я чувствую, что версия IPad стабильна, ее использование для телефонов должно быть тривиально с новыми классами из XCode6).

На моей сцене еще не было выделено никаких сегментов, и оно все еще работает.

Мой код в моем делете приложения находится в ObjectiveC и немного отличается - но использует ту же идею. Обратите внимание, что я использую контроллер представлений по умолчанию из сцены, в отличие от предыдущих примеров. Я чувствую, что это также будет работать на IOS7/IPhone, в котором среда выполнения будет генерировать обычный UINavigationController вместо UISplitViewController. Я мог бы даже добавить новый код, который будет вызывать контроллер входа в систему на IPhones, вместо изменения rootVC.

- (void) setupRootViewController:(BOOL) animated {
    UIViewController *newController = nil;
    UIStoryboard *board = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
    UIViewAnimationOptions transition = UIViewAnimationOptionTransitionCrossDissolve;

    if (!loggedIn) {
        newController = [board instantiateViewControllerWithIdentifier:@"LoginViewController"];
    } else {
        newController = [board instantiateInitialViewController];
    }

    if (animated) {
        [UIView transitionWithView: self.window duration:0.5 options:transition animations:^{
            self.window.rootViewController = newController;
            NSLog(@"setup root view controller animated");
        } completion:^(BOOL finished) {
            NSLog(@"setup root view controller finished");
        }];
    } else {
        self.window.rootViewController = newController;
    }
}

Ответ 8

Другая опция: в контроллере представления подробностей я покажу контроллер модального представления:

let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
if (!appDelegate.loggedIn) {
    // display the login form
    let storyboard = UIStoryboard(name: "Storyboard", bundle: nil)
    let login = storyboard.instantiateViewControllerWithIdentifier("LoginViewController") as UIViewController
    self.presentViewController(login, animated: false, completion: { () -> Void in
       // user logged in and is valid now
       self.updateDisplay()
    })
} else {
    updateDisplay()
}

Не отключайте контроллер входа, не устанавливая флаг входа. Обратите внимание, что в IPhones контроллер главного представления будет первым, поэтому очень нужен код на главном контроллере.