UISplitViewController ในแนวตั้งบน iPhone แสดงรายละเอียด VC แทนต้นแบบ


177

ฉันใช้ Universal Storyboard ใน Xcode 6 โดยกำหนดเป้าหมายที่ iOS 7 ขึ้นไป ฉันได้ติดตั้งแล้วUISplitViewControllerซึ่งตอนนี้ได้รับการสนับสนุนบน iPhone ที่ใช้ iOS 8 และ Xcode จะทำการ backport อัตโนมัติสำหรับ iOS 7 มันทำงานได้ดีจริงๆยกเว้นเมื่อคุณเปิดแอปบน iPhone ในแนวตั้งที่ใช้ iOS 8 มุมมองรายละเอียดของมุมมองแยก ตัวควบคุมจะปรากฏขึ้นเมื่อฉันคาดว่าจะเห็นตัวควบคุมมุมมองหลักแรก ฉันเชื่อว่านี่เป็นข้อผิดพลาดกับ iOS 8 เพราะเมื่อคุณรันแอพใน iOS 7 มันจะแสดงตัวควบคุมมุมมองหลักอย่างถูกต้อง แต่ตอนนี้ iOS 8 เป็น GM และยังคงเกิดขึ้น ฉันจะตั้งค่าได้อย่างไรเมื่อตัวควบคุมมุมมองแยกจะถูกยุบ (มีเพียงหนึ่งตัวควบคุมมุมมองที่แสดงบนหน้าจอ) เมื่อตัวควบคุมมุมมองแยกจะแสดงขึ้นแสดงว่าตัวควบคุมมุมมองหลักไม่ใช่รายละเอียด

ฉันได้สร้างคอนโทรลเลอร์มุมมองแยกนี้ในตัวสร้างส่วนต่อประสาน ตัวแยกมุมมองแยกเป็นตัวควบคุมมุมมองแรกภายในตัวควบคุมแถบแท็บ ทั้งตัวควบคุมหลักและรายละเอียด VC เป็นตัวควบคุมการนำทางที่มีตัวควบคุมมุมมองตารางฝังอยู่ภายใน

คำตอบ:


238

โอ้มนุษย์นี่ทำให้ฉันปวดหัวสองสามวันและไม่สามารถหาวิธีทำสิ่งนี้ได้ ส่วนที่แย่ที่สุดคือการสร้างโครงการ Xcode iOS ใหม่โดยใช้แม่แบบรายละเอียดหลักทำงานได้ดี โชคดีในท้ายที่สุดความจริงเล็ก ๆ น้อย ๆ ก็คือฉันพบวิธีแก้ปัญหาได้อย่างไร

มีการโพสต์บางอย่างที่ฉันได้พบว่าชี้ให้เห็นว่าการแก้ปัญหาคือการใช้ใหม่ที่มีวิธีการในการprimaryViewControllerForCollapsingSplitViewController: UISplitViewControllerDelegateฉันพยายามที่จะไม่มีประโยชน์ สิ่งที่ Apple ทำในเทมเพลตรายละเอียดหลักที่ดูเหมือนว่าใช้งานได้นั้นจะใช้วิธีใหม่ (หายใจเข้าลึก ๆ เพื่อบอกวิธีทั้งหมด) splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:วิธีการมอบหมาย (อีกครั้งUISplitViewControllerDelegate) ตามเอกสารวิธีนี้:

ขอให้ผู้รับมอบสิทธิ์ในการปรับตัวควบคุมมุมมองหลักและรวมตัวควบคุมมุมมองรองลงในอินเทอร์เฟซที่ยุบ

อย่าลืมอ่านส่วนการสนทนาของวิธีนั้นเพื่ออ่านรายละเอียดที่เฉพาะเจาะจงมากขึ้น

วิธีที่ Apple จัดการกับสิ่งนี้คือ:

- (BOOL)splitViewController:(UISplitViewController *)splitViewController
collapseSecondaryViewController:(UIViewController *)secondaryViewController
  ontoPrimaryViewController:(UIViewController *)primaryViewController {

    if ([secondaryViewController isKindOfClass:[UINavigationController class]]
        && [[(UINavigationController *)secondaryViewController topViewController] isKindOfClass:[DetailViewController class]]
        && ([(DetailViewController *)[(UINavigationController *)secondaryViewController topViewController] detailItem] == nil)) {

        // Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
        return YES;

    } else {

        return NO;

    }
}

การใช้งานโดยทั่วไปจะทำสิ่งต่อไปนี้:

  1. หากsecondaryViewControllerเป็นสิ่งที่เราคาดหวัง (a UINavigationController) และมันแสดงสิ่งที่เราคาดหวัง (a DetailViewController- ตัวควบคุมมุมมองของคุณ) แต่ไม่มีรูปแบบ ( detailItem) จากนั้น " Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded."
  2. มิฉะนั้นส่งคืน " NOเพื่อให้ตัวควบคุมมุมมองแยกลองและรวมเนื้อหาของตัวควบคุมมุมมองรองลงในอินเทอร์เฟซที่ยุบ"

ผลลัพธ์มีดังต่อไปนี้สำหรับ iPhone ในแนวตั้ง (ไม่ว่าจะเริ่มในแนวตั้งหรือหมุนเป็นแนวตั้ง - หรือคลาสที่มีขนาดกะทัดรัดถูกต้องมากขึ้น):

  1. ถ้ามุมมองของคุณถูกต้อง
    • และมีรูปแบบการแสดงตัวควบคุมมุมมองรายละเอียด
    • แต่ไม่มีรุ่นแสดงตัวควบคุมมุมมองหลัก
  2. หากมุมมองของคุณไม่ถูกต้อง
    • แสดงตัวควบคุมมุมมองหลัก

ใสเหมือนโคลน


8
คำตอบที่ยอดเยี่ยม! ฉันเพียงแค่ subclassed UISplitViewControllerและกลับมาYESจากวิธีนั้นเสมอจากนั้นก็เปลี่ยน class view แบบแยกย่อยใน Storyboard เพราะฉันต้องการแสดงต้นแบบบน iPhone ในแนวตั้งเสมอ :)
จอร์แดน

2
ฉันต้องการให้ตัวควบคุมมุมมองหลักของฉันถูกซ่อนถ้า "iPhone" อยู่ในโหมด "แนวตั้ง" เนื่องจากฉันมีการตั้งค่าตัวควบคุมมุมมองรายละเอียดเริ่มต้น ฉันจะทำสิ่งนั้นได้อย่างไร ต้นแบบและรายละเอียดของฉันทั้งสองเป็นประเภท VC รายละเอียดของฉันคือ MMDrawerController โดยเฉพาะ กรุณาช่วย
Harshit Gupta

3
ฉันลองข้อเสนอแนะของ Joey เกี่ยวกับการทำคลาสย่อยUISplitViewControllerแต่พบว่าไม่ได้ผล: splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:ไม่เคยถูกเรียก ฉันคัดลอกแม่แบบของ Apple และวางไว้ใน AppDelagate สิ่งนี้จำเป็นต้องมีการเปลี่ยนแปลงเล็กน้อยในการสร้าง UISplitViewController ภายใต้application didFinishLaunchingWithOptions:เช่นกัน (ที่ฉันยังคัดลอกแม่แบบของ Apple)
Nick

7
ความคิดเห็นของ @ joey ทำงานร่วมกับการตั้งค่า self.delegate = self; ใน viewdidload! และเพิ่ม <UISplitViewControllerDelegate> ใน. h ขอบคุณมาก!
fellowworldcitizen

2
ดูเหมือนว่าคำตอบที่ถูกต้องสำหรับฉันเนื่องจากฉันมีปัญหาเดียวกันแน่นอน อย่างไรก็ตามด้วยเหตุผลบางอย่างที่ฉันsplitViewController:collapseSecondaryViewController:ontoPrimaryViewController:ไม่เคยได้รับเรียก ดูเหมือนว่าผู้รับมอบสิทธิ์จะถูกตั้งค่าapplicationDidFinishLaunchingWithOptions:วิธีการของตัวแทนแอพของฉันอย่างถูกต้อง มีคนอื่นเห็นปัญหานี้และวิธีนี้ไม่ได้ทำงาน?
Tim Dean

60

นี่คือคำตอบที่ได้รับการยอมรับใน Swift เพียงสร้างคลาสย่อยนี้และกำหนดให้กับ splitViewController ของคุณในกระดานเรื่องราวของคุณ

//GlobalSplitViewController.swift

import UIKit

class GlobalSplitViewController: UISplitViewController, UISplitViewControllerDelegate {

  override func viewDidLoad() {
    super.viewDidLoad()

    self.delegate = self
  }

  func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController: UIViewController!, ontoPrimaryViewController primaryViewController: UIViewController!) -> Bool{
    return true
  }

}

3
เยี่ยมมากมันช่วยได้มาก แต่ปัญหาใหม่เกิดขึ้น ปุ่มย้อนกลับที่พาฉันไปหาอาจารย์หายไปในขณะนี้ (ไม่แสดงเลย) ฉันจะรับมันคืนได้อย่างไร แก้ไข: ไม่เป็นไรคิดว่าตัวเอง :-) สำหรับผู้ใช้รายอื่น: เพิ่มสิ่งนี้ใน detailView: self.navigationItem.leftBarButtonItem = self.splitViewController? .displayModeButtonItem () self.navigationItem.leftItemsSupplementBackButton = true
Tom Tallak Solbu

3
ตอนนี้ใน Swift อะไรก็ตามfunc splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
Dan Rosenstark

2
ดูเหมือนว่าวิธีการมอบสิทธิ์นั้นจะถูกเรียกเมื่อขนาดของชั้นเรียนมีขนาดกะทัดรัดเท่านั้น มันถูกเรียกบน iPhone แต่ไม่ใช่ในแนวตั้งของ iPad ซึ่งหมายความว่ามันไม่สามารถแก้ปัญหาได้เนื่องจากแนวตั้งของ iPad ยังอยู่ในโหมดยุบ ทดสอบกับ iOS 12.1
Daniel

21

คำตอบที่ถูกต้องของ Mark S รุ่นที่รวดเร็ว

จัดทำโดยเทมเพลต Master-Detail ของ Apple

func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController:UIViewController, ontoPrimaryViewController primaryViewController:UIViewController) -> Bool {
    guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false }
    guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false }
    if topAsDetailController.detailItem == nil {
        // Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
        return true
    }
    return false
}

การอธิบาย

(สิ่งที่ Mark S พูดมีความสับสนเล็กน้อย)

วิธีการมอบหมายนี้ถูกเรียกใช้splitViewController: collapseSecondaryViewController: ontoPrimaryViewController:เพราะนั่นคือสิ่งที่ทำ เมื่อเปลี่ยนเป็นขนาดความกว้างที่กะทัดรัดยิ่งขึ้น (ตัวอย่างเช่นเมื่อหมุนโทรศัพท์จากแนวนอนเป็นแนวตั้ง) โทรศัพท์จะต้องยุบตัวควบคุมมุมมองแยกเป็นหนึ่งในนั้น

ฟังก์ชั่นนี้จะคืนค่าบูลีนเพื่อตัดสินใจว่าควรยุบรายละเอียดและแสดงต้นแบบหรือไม่

ดังนั้นในกรณีของเราเราจะตัดสินใจโดยพิจารณาจากรายละเอียดที่เลือกไว้หรือไม่ เราจะทราบได้อย่างไรว่ารายละเอียดของเราถูกเลือก? ถ้าเราทำตามเทมเพลต Master-Detail ของ Apple ตัวควบคุมมุมมองรายละเอียดควรมีตัวแปรเสริมที่มีข้อมูลรายละเอียดดังนั้นถ้ามันเป็นศูนย์ (. ไม่มี) ก็ยังไม่มีการเลือกอะไรเลยและเราควรจะแสดง Master เพื่อให้ผู้ใช้สามารถเลือกบางอย่าง

แค่นั้นแหละ.


เพียงชี้แจงว่าทำไมฉันถึงย้อนกลับจากการแก้ไขของ @ sschale รหัสนั้นเป็นคำพูดจากApple's Master-Detail templateมันไม่ได้ตั้งใจที่จะดีหรือกระชับเพียงแค่ความจริง :)
NiñoScript

10

จากเอกสารคุณต้องใช้ผู้รับมอบสิทธิ์เพื่อบอกUISplitViewController ไม่ให้รวมมุมมองรายละเอียดไว้ใน "ส่วนต่อประสานที่ยุบ" (เช่น "โหมดแนวตั้ง" ในกรณีของคุณ) ใน Swift 4 วิธีการมอบหมายเพื่อนำไปใช้สำหรับสิ่งนั้นได้ถูกเปลี่ยนชื่อ

func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
    return true
}

9
   #import <UIKit/UIKit.h>

    @interface SplitProductView : UISplitViewController<UISplitViewControllerDelegate>




    @end

.m:

#import "SplitProductView.h"
#import "PriceDetailTableView.h"

@interface SplitProductView ()

@end

@implementation SplitProductView

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.delegate = self;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/
- (BOOL)splitViewController:(UISplitViewController *)splitViewController
collapseSecondaryViewController:(UIViewController *)secondaryViewController
  ontoPrimaryViewController:(UIViewController *)primaryViewController {

    if ([secondaryViewController isKindOfClass:[UINavigationController class]]
        && [[(UINavigationController *)secondaryViewController topViewController] isKindOfClass:[PriceDetailTableView class]]

        //&& ([(PriceDetailTableView *)[(UINavigationController *)secondaryViewController topViewController] detailItem] == nil)

        ) {

        // Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
        return YES;

    } else {

        return NO;

    }
}
@end

9

แอพของฉันเขียนด้วย Swift 2.x และทำงานได้ดี หลังจากแปลงเป็น Swift 3.0 (โดยใช้ตัวแปลง XCode) มันจะเริ่มแสดงรายละเอียดก่อนแทนที่จะเป็นหลักในโหมดแนวตั้ง ปัญหาคือชื่อของฟังก์ชั่น splitViewController จะไม่เปลี่ยนเพื่อให้ตรงกับหนึ่งใหม่ของ UISplitViewControllerDelegate

หลังจากเปลี่ยนชื่อฟังก์ชั่นนั้นด้วยตนเองตอนนี้แอพของฉันสามารถทำงานได้อย่างถูกต้อง:

func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
    guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false }
    guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false }
    if topAsDetailController.game == nil {
        // Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
        return true
    }
    return false
}

ฉันมีปัญหาเช่นเดียวกับคุณ แต่ฉันไม่เข้าใจวิธีแก้ปัญหาของคุณ ฉันไม่เห็นการเปลี่ยนแปลงรหัสที่คุณโพสต์ไว้ที่นี่ คุณจะเจาะจงมากขึ้นได้ไหม ขอบคุณ
bibscy

วิธีการมากมายถูกเปลี่ยนชื่อเล็กน้อย
เดฟ

คำตอบของ Tony คือไวยากรณ์ Swift 3 ต่อคำตอบของ @ NiñoScript (ซึ่งเขียนขึ้นสำหรับ Swift เวอร์ชันก่อนหน้า)
Hellojeffy

2
สำหรับ SWIFT 3 อย่าลืมที่จะใส่self.delegate = selfในviewDidLoadวิธีการ
Fer

7

หากคุณไม่มีค่าเริ่มต้นที่จะแสดงในตัวควบคุมมุมมองรายละเอียดคุณเพียงแค่ลบการเริ่มต้นระหว่าง SplitViewController และ UIViewController รายละเอียดของคุณในกระดานเรื่องราว สิ่งนี้จะทำให้มันเข้าสู่ Master View Controller เสมอ

ผลข้างเคียงของสิ่งนี้คือแทนที่จะเห็นสองมุมมองในแนวนอนคุณจะเห็นหนึ่งมุมมองในขนาดเต็มใน SplitViewController จนกระทั่งแสดงรายละเอียด Segue ในตัวควบคุมมุมมองหลัก


เคล็ดลับดี แอพของฉันอยู่ในโหมดแนวตั้งเท่านั้นและฉันสามารถทำได้
Peacemoon

สิ่งนี้เป็นจริงยกเว้นในแนวนอนคุณจะเห็นส่วนด้านขวาว่างของมุมมองที่อาจเป็นสีเทา
vedrano

4

สำหรับทุกคนที่ไม่สามารถหาส่วนศุกร์ของ cs193p:

ใน Swift 3.1.1 การสร้างคลาสย่อยของ UISplitViewController และการใช้วิธีการมอบสิทธิ์อย่างใดอย่างหนึ่งทำให้ฉันชอบเสน่ห์:

class MainSplitViewController: UISplitViewController, UISplitViewControllerDelegate {

override func viewDidLoad() {
    super.viewDidLoad()
    self.delegate = self
}

func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
    return true
} }

กระดานเรื่องราวของฉัน


ตามที่ @olito ได้ชี้ให้เห็นแล้วว่าใน Swift 4 ไวยากรณ์ของสิ่งนี้ได้เปลี่ยนเป็น: public func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool
Robuske

3

ในความคิดของฉันคุณควรแก้ปัญหานี้ให้ทั่วๆไป คุณสามารถคลาสย่อย UISplitViewController และใช้โปรโตคอลในตัวควบคุมมุมมองที่ฝังตัว

class MasterShowingSplitViewController: UISplitViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = self
    }
}

extension MasterShowingSplitViewController: UISplitViewControllerDelegate {
    func splitViewController(splitViewController: UISplitViewController,
                             collapseSecondaryViewController secondaryViewController: UIViewController,
                             ontoPrimaryViewController primaryViewController: UIViewController) -> Bool {
        guard let masterNavigationController = primaryViewController as? UINavigationController,
                  master = masterNavigationController.topViewController as? SplitViewControllerCollapseProtocol else {
            return true
        }
        return master.shouldShowMasterOnCollapse()

    }
}

protocol SplitViewControllerCollapseProtocol {
    func shouldShowMasterOnCollapse() -> Bool
}

ตัวอย่างการนำไปใช้ใน UITableViewController:

extension SettingsTableViewController: SplitViewControllerCollapseProtocol {
    func shouldShowMasterOnCollapse() -> Bool {
        return tableView.indexPathForSelectedRow == nil
    }
}

หวังว่ามันจะช่วย ดังนั้นคุณสามารถนำคลาสนี้กลับมาใช้ใหม่และเพียงแค่ต้องใช้โปรโตคอล


วิธีการมอบหมายไม่เคยถูกเรียก!
K_Mohit

มันไม่ได้ถูกเรียกบน iPad และ iPhone 6/7/8 Plus นั่นคือปัญหาของคุณ? ดูได้ที่: stackoverflow.com/questions/29767614/…
Maik639

2

เพียงแค่ลบ DetailViewController จากตัวควบคุม SplitView เมื่อคุณต้องการให้เริ่มจาก Master

UISplitViewController *splitViewController = (UISplitViewController *)[self.storyboard instantiateViewControllerWithIdentifier:@"SETTINGS"];
splitViewController.delegate = self;
[self.navigationController presentViewController:splitViewController animated:YES completion:nil];
if (IPHONE) {
    NSMutableArray * cntlrs = [splitViewController.viewControllers mutableCopy];
    [cntlrs removeLastObject];
    splitViewController.viewControllers = cntlrs;
}

2

สิ่งนี้ใช้ได้กับฉันใน iOS-11 และ Swift 4:

//Following code in application didFinishLaunching (inside Application Delegate)
guard let splitViewController = window?.rootViewController as? UISplitViewController,
            let masterNavVC = splitViewController.viewControllers.first as? UINavigationController,
            let masterVC = masterNavVC.topViewController as? MasterViewController
else { fatalError() }
splitViewController.delegate = masterVC

//Following code in MasterViewController class
extension MasterViewController:UISplitViewControllerDelegate {
    func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
        return true
    }
}

2

ฟังก์ชันได้รับการเปลี่ยนชื่อใน Swift เวอร์ชันใหม่ดังนั้นโค้ดนี้จึงใช้งานได้กับ Swift 4:

import UIKit

class GlobalSplitViewController: UISplitViewController, UISplitViewControllerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = self
    }

    func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
        return true
    }

}

0

โซลูชัน Xamarin / C #

public partial class MainSplitViewController : UISplitViewController
{
    public MainSplitViewController(IntPtr handle) : base(handle)
    {
    }

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();

        Delegate = new MainSplitViewControllerDelegate();
    }
}

public class MainSplitViewControllerDelegate : UISplitViewControllerDelegate
{
    public override bool CollapseSecondViewController(UISplitViewController splitViewController, UIViewController secondaryViewController, UIViewController primaryViewController)
    {
        return true;
    }
}

0

เพียงตั้งค่าpreferredDisplayModeคุณสมบัติของUISplitViewControllerเป็น.allVisible

class MySplitViewController: UISplitViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.preferredDisplayMode = .allVisible
    }

}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.