28

我正在創作一個iPad應用程序。該應用程序中的一個屏幕非常適合使用UISplitViewController。但是,應用程序的頂層是一個主菜單,我不想使用UISplitViewController。這就提出了一個問題,因爲蘋果的狀態:如果使用在UISplitViewController和其他視圖控制器之間切換的最佳方式?

  1. UISplitViewController應在應用程序中的頂級視圖控制器,即它認爲應增加爲UIWindow

  2. 子視圖,UISplitViewController應在那裏爲應用程序的生命週期 - 即不要從一個UIWindow其觀點,並把另一到位,反之亦然

看了四周,試驗,它似乎唯一可行的選擇以滿足蘋果的要求,我們自己的是使用模態對話框。因此,我們的應用程序在根級別有一個UISplitViewController(即它的視圖作爲UIWindow的子視圖添加),並顯示我們的主菜單,我們將它作爲全屏模式對話框推送到UISplitViewController。然後通過關閉主菜單視圖控制器模式對話框,我們可以實際顯示我們的分割視圖。

這一戰略似乎很好地工作。但它引出了一個問題:

1)是否有這種構造的沒有更好的辦法,沒有情態動詞,也符合所有要求提及?由於被推送爲模態對話框,主UI顯得有些奇怪。 (情態動詞應該是對重點用戶的任務。)

2)我是在應用程序商店拒絕的風險,因爲我的方法呢?按照蘋果的人機界面準則,這種模式策略可能是「濫用」模式對話。但他們給了我什麼其他選擇?無論如何,他們知道我在做這個嗎?

+0

如何將菜單視圖作爲全屏模式對話框推送到UISplitViewController上?我有同樣的問題,我從故事板中的分割視圖菜單視圖中定義模式segue,然後在我的splitviewcontroller代碼中使用viewDidApear中的performSegueWithIdentifier:但這種方式用戶總是在菜單模式之前​​看到分割視圖的一瞥?這個問題能解決嗎?我應該在哪裏調用performseguewithidentifier來防止這個問題? – 2012-02-20 16:19:30

回答

6

謝謝!同樣的問題,並用同樣的方式解決它使用模態。在我的情況下,它是一個登錄視圖,然後是主菜單以及在分割視圖之前顯示。我使用了和你想象的一樣的策略。我(以及其他幾位知識淵博的iOS人士)找不到更好的解決方案。對我來說工作得很好。無論如何,用戶永遠不會注意到模態。目前他們如此。是的,我也可以告訴你,有相當多的應用程序在應用程序商店的引擎下做同樣的事情。 :)在另一方面,不要讓我知道,如果你圖個更好的出路不知何故好歹某個時候:)

+0

謝謝Bourne!我們還有其他的登錄屏幕,但爲了簡潔起見,我將其留下了。我仍然很驚訝蘋果把所有這些限制放在了UISplitViewController(其中的東西),然後完全沒有告訴你如何避開這些限制,例如'使用模態'。我認爲蘋果文檔需要更多(任何?)高級UI設計理念/模式。 – occulus 2010-11-18 09:51:43

+0

我想你們回答了我的問題:這是不可能的。見http://stackoverflow.com/questions/4482526/stick-uisplitviewcontroller-in-its-own-xib – Krumelur 2010-12-19 16:31:59

1

對於運行到同樣的問題,未來的iOS開發:這裏的另一個答案和解釋。你必須讓它成爲根視圖控制器。如果不是,則覆蓋一個模式。

UISplitviewcontroller not as a rootview controller

+0

那麼你可以編寫你自己的splitview控制器。每當我需要時,我都會這樣做。 – Bourne 2010-12-20 07:03:26

3

是誰說過你只能有一個窗口? :)

看看我的回答on this similar question可以提供幫助。

這種方法的工作對我非常好。只要您不必擔心多個顯示或狀態恢復,此鏈接的代碼應該足以滿足您的需要:您不必讓邏輯看起來倒退或重寫現有代碼,並且仍然可以充分利用在您的應用程序中更深層次的UISplitView - 沒有(AFAIK)違反Apple準則。

19

我嚴重不相信有一些的UIViewController這個概念展現UISplitViewController (例如登錄表單)原來是這麼複雜,直到我不得不創建一個樣的觀點hiearchy之前。我的例子基於iOS 8和XCode 6.0(Swift),所以我不確定這個問題是否以同樣的方式存在,或者是由於iOS 8引入了一些新的錯誤,但是從所有我發現的類似問題,我沒有看到這個問題的完整'不是很hacky'的解決方案。

我會指導您完成一些我在結束解決方案之前嘗試過的一些事情(在本文結尾處)。每個示例均基於在未啓用CoreData的情況下從Master-Detail模板創建新項目。


第一次嘗試(模態SEGUE到UISplitViewController):

  1. 創建新UIViewController子類(LoginViewController例如)
  2. 在故事板中添加新的視圖控制器,其設置爲初始視圖控制器(代替UISplitViewController的),並把它連接到LoginViewController
  3. 添加的UIButton到LoginViewController,並從該按鈕創建模態順着接下去UISplitViewController
  4. 爲UISplitViewController
  5. 移動樣板設置代碼從AppDelegate中的didFinishLaunchingWithOptions到LoginViewController的prepareForSegue

這幾乎工作。我差不多說了,因爲在應用程序啓動後使用LoginViewController並點擊按鈕並繼續使用UISplitViewController,出現了一個奇怪的錯誤:顯示和隱藏主視圖控制器在方向更改不再動畫。

後,這個問題並沒有真正解決一段時間掙扎,我認爲它以某種方式與奇怪的規則是UISplitViewController必須是RootViewController的連接(在這種情況下它是不是,LoginViewController是),所以我給了從這個不完美的解決方案。


第二個嘗試(從UISplitViewController模式賽格瑞):

  1. 創造新的UIViewController子類(LoginViewController例如)
  2. 在故事板添加新的視圖控制器,並將其連接到LoginViewController(但此時讓UISplitViewController成爲初始視圖控制器)
  3. 從UISplitViewController創建模態segue到LoginViewController
  4. add UIB utton到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 

這兩個例子都會產生相同的幾個不好的東西:

  1. 控制檯輸出:Unbalanced calls to begin/end appearance transitions for <UISplitViewController: 0x7fc8e872fc00>
  2. UISplitViewController必須首先顯示LoginViewController有模式地segued之前(我寧願只存在登錄表單,因此用戶不會看到UISplitViewController登錄之前)
  3. 開卷賽格瑞沒有得到所謂的(這完全是其他錯誤,我不會進入那個故事現在)

解決方案(更新RootViewController的)

我發現的唯一途徑,其正常工作是如果你改變窗口RootViewController的對飛:

  1. 定義故事板ID爲LoginViewController和UISplitViewController, 並添加某種的loggedIn屬性到AppDelegate中。
  2. 基於此屬性,實例化適當的視圖控制器,然後將其設置爲rootViewController。
  3. didFinishLaunchingWithOptions中沒有動畫的情況下執行動畫,但在從UI調用時動畫。

下面是從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's 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中正常工作。

+0

我已經得到了這個工作,感謝代碼,但是如果已經登錄,它仍然顯示登錄(帶有註銷按鈕)。當我第一次打開應用程序並且您已經登錄時,我該如何跳過? – 2014-12-30 22:14:22

+0

@naturalc我正在使用NSUserDefaults爲我存儲一個布爾值。就這個解決方案而言,我希望我可以選擇提供多個upvote。這拯救了我的生命。非常感謝!! – 2015-01-08 17:20:13

+0

這很好,但它填滿了這些內存之間的每一次切換......對於這個問題的任何幫助?我需要更改rootViewController更經常比只當登錄... – Heckscheibe 2015-08-13 08:14:28

0

我想提出我的方法來呈現一個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 
0

我做了一個UISplitView作爲初始視圖,而不是模擬到全屏UIView並返回到UISplitView。如果您需要返回SplitView,則必須使用自定義的Segue。

閱讀此鏈接(從日本翻譯)

UIViewController to UISplitViewController

0

添加到@tadija的答案我在一個類似的情況:

我的應用是僅用於手機,和我添加平板電腦UI。我決定在同一個應用程序中使用Swift,並最終將所有應用程序遷移到使用相同的故事板(當我覺得IPad版本穩定時,使用XCode6的新類對於手機來說應該是微不足道的)。

在我的場景中沒有定義任何賽段,它仍然有效。

我的應用程序委託中的代碼位於ObjectiveC中,並略有不同 - 但使用了相同的想法。 請注意,與以前的示例不同,我使用場景中的默認視圖控制器。我覺得這也適用於IOS7/IPhone,其中運行時將生成一個普通的UINavigationController而不是UISplitViewController。我甚至可能會添加新的代碼來推動IPhone上的登錄視圖控制器,而不是改變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; 
    } 
} 
0

另一種選擇:在詳細視圖控制器I顯示模態視圖控制器:

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中,主視圖控制器將首先出現,因此需要在主視圖控制器上使用非常類似的代碼。

+0

你在文件中的哪個位置放置了這個?在配置視圖中?在視圖中加載?別的地方? - 我把它放在viewdidload中,它說detailviewcontroller沒有名爲updateDisplay的成員 – 2014-12-30 21:44:19

+0

此代碼屬於初始控制器(登錄後)。缺少的功能是填充顯示控制器中的細節。 – elcuco 2015-01-04 07:49:40

0

剛剛遇到這個問題的項目,並認爲我會分享我的解決方案。在我們的情況下(對於iPad),我們想從UISplitViewController開始,並且兩個視圖控制器都可見(使用preferredDisplayMode = .allVisible)。在詳細(右)層次結構中的某個點(我們也有一個用於這一邊的導航控制器),我們想要在整個分割視圖控制器上推送一個新的視圖控制器(不使用模態轉換)。

在iPhone上,這種行爲是免費的 - 因爲任何時候只有一個視圖控制器可見。但在iPad上我們不得不找出其他的東西。我們結束了一個根容器視圖控制器,它將分割視圖控制器作爲子視圖控制器添加到它。此根視圖控制器嵌入在導航控制器中。當分割視圖控制器中的詳細視圖控制器想要在整個分割視圖控制器上推送新控制器時,根視圖控制器將其新的視圖控制器與其導航控制器一起推送。

相關問題