เข้าถึง Container View Controller จาก Parent iOS


203

ใน iOS6 ฉันสังเกตเห็นมุมมองคอนเทนเนอร์ใหม่ แต่ฉันยังไม่แน่ใจว่าจะเข้าถึงตัวควบคุมได้อย่างไรจากมุมมองที่มีอยู่

สถานการณ์:

ตัวอย่าง

ฉันต้องการเข้าถึงป้ายกำกับในตัวควบคุมมุมมองการแจ้งเตือนจากตัวควบคุมมุมมองที่บ้านมุมมองคอนเทนเนอร์

มีความแตกต่างระหว่างกันฉันจะใช้มันได้หรือไม่


อธิบายอย่างเต็มที่ที่นี่สำหรับมุมมองคอนเทนเนอร์ที่ทันสมัย: stackoverflow.com/a/23403979/294884
Fattie

คำตอบ:


362

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

- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
   NSString * segueName = segue.identifier;
   if ([segueName isEqualToString: @"alertview_embed"]) {
       AlertViewController * childViewController = (AlertViewController *) [segue destinationViewController];
       AlertView * alertView = childViewController.view;
       // do something with the AlertView's subviews here...
   }
}

1
เราไม่ได้แยกออกจากกัน? ฉันพลาดอะไรบางอย่างที่นี่ ...
Adam Waite

25
ใช่มีส่วนต่อฝังที่เกิดขึ้นเมื่อตัวควบคุมมุมมองที่สองถูกสร้างเป็นลูกของตัวควบคุมมุมมองแรก PreparForSegue: ถูกเรียกก่อนเกิดเหตุการณ์นี้ขึ้น คุณสามารถใช้โอกาสนี้เพื่อส่งผ่านข้อมูลไปยังเด็กหรือเพื่อเก็บการอ้างอิงไปยังเด็กเพื่อใช้ในภายหลัง ดูเพิ่มเติมdeveloper.apple.com/library/ios/#documentation/uikit/reference/ ......
Peter E

1
อ่าใช่ 'ตัวควบคุมมุมมองที่สองถูกสร้างเป็นลูกของตัวควบคุมมุมมองแรก' เมื่อมุมมองโหลดหรือไม่ ขอบคุณมากตอนนี้ ฉันไม่ได้อยู่ในโปรเจคของฉันตอนนี้ แต่จะทดสอบในภายหลัง
Adam Waite

1
ถูกเรียกมาก่อน viewDidLoad เมื่อถึงเวลา viewDidLoad ผู้ปกครองและเด็กได้เชื่อมต่อและ [self childViewControllers] ในผู้ปกครองจะกลับอาร์เรย์ของตัวควบคุมเด็กทั้งหมด (ดูคำตอบของ rdelmar ด้านล่าง)
Peter E

2
ฉันจะเพิ่มข้อแม้หนึ่งข้อในโซลูชันที่เสนอ: ระวังตัวมากเมื่อเข้าถึงคุณสมบัติมุมมองของตัวควบคุมมุมมองปลายทาง (เด็ก): ในบางสถานการณ์สิ่งนี้จะทำให้เรียกใช้ viewDidLoad ที่นั่นและฉันขอแนะนำให้ตั้งค่าข้อมูลเซกเมนต์ที่จำเป็นไว้ล่วงหน้า เพื่อให้ viewDidLoad สามารถยิงได้อย่างปลอดภัย
AlwaysLearning

56

คุณสามารถทำได้ง่ายๆด้วยself.childViewControllers.lastObject(สมมติว่าคุณมีลูกเพียงคนเดียวหรือใช้อย่างอื่นobjectAtIndex:)


1
@ RaphaelOliveira ไม่จำเป็นต้อง หากคุณมี childControllers หลายคนในมุมมองเดียวนี่จะเป็นแนวทางที่ต้องการ มันช่วยให้คุณสามารถประสานภาชนะบรรจุหลายรายการพร้อมกัน prepareForSegue มีการอ้างอิงไปยังอินสแตนซ์ของตัวควบคุมลูกเดียวที่ทำงานอยู่
Fydo

2
@Fydo และปัญหาในการจัดการภาชนะบรรจุหลาย ๆ อันใน 'เตรียมพร้อมสำหรับการทำต่อ' คืออะไร?
Lay González

1
จะเป็นอย่างไรถ้า (สยองขวัญ!) คุณตัดสินใจที่จะเปลี่ยนจากสตอรีบอร์ดหรือไม่ใช้ซีเควนซ์ ฯลฯ จากนั้นคุณต้องขุดโค้ดทำการเปลี่ยนแปลง ฯลฯ
Tom Andersen

2
นี่คือวิธีการตามปกติของฉัน แต่ตอนนี้เกิดปัญหาสำหรับผมตั้งแต่ผมกำลังเข้าถึงchildViewControllers"เร็วเกินไป"
Mazyod

24

สำหรับ Swift Programming

คุณสามารถเขียนแบบนี้

var containerViewController: ExampleViewController?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    // you can set this name in 'segue.embed' in storyboard
    if segue.identifier == "checkinPopupIdentifierInStoryBoard" {
        let connectContainerViewController = segue.destinationViewController as ExampleViewController
        containerViewController = connectContainerViewController
    }
}

การใช้เครื่องหมายคำถามหลัง segueName บนคำสั่ง if คืออะไร "ถ้า segueName?"
นาย

19

prepareForSegueวิธีการทำงาน แต่อาศัยสตริงมายากลระบุทำต่อ อาจจะมีวิธีที่ดีกว่า

หากคุณรู้จัก class ของ VC ที่คุณใช้อยู่คุณสามารถทำได้ด้วยคุณสมบัติที่คำนวณได้:

var camperVan: CamperVanViewController? {
  return childViewControllers.flatMap({ $0 as? CamperVanViewController }).first
  // This works because `flatMap` removes nils
}

childViewControllersนี้ต้องอาศัย ในขณะที่ฉันยอมรับว่ามันอาจเปราะบางที่จะพึ่งพาคนแรกการตั้งชื่อชั้นเรียนที่คุณค้นหาทำให้สิ่งนี้ดูค่อนข้างแข็ง


3
return childViewControllers.filter { $0 is CamperVanViewController }.firstในหนึ่งซับ
Adam Waite

1
ฉันทำเสร็จแล้วchildViewControllers.flatMap({ $0 as? CamperVanViewController }).firstซึ่งฉันคิดว่าดีขึ้นเล็กน้อยเพราะมันใช้งานได้นานและกำจัดสิ่งสกปรกใด ๆ
SimplGy

นี่เป็นวิธีแก้ปัญหาที่ดีมากหากคุณต้องการเข้าถึงตัวควบคุมมุมมองนั้นมากกว่าหนึ่งครั้ง
Gabriel Goncalves

นี่เป็นความสิ้นหวัง - ไม่มีเหตุผลใดที่คุณจะมีเพียงหนึ่งในคลาสนั้น นั่นเป็นเหตุผลว่าทำไมตัวระบุถึงมีอยู่จริง เพียงทำตามสูตรมาตรฐาน ... stackoverflow.com/a/23403979/294884
Fattie

อย่ากรองเฉพาะองค์ประกอบแรกเท่านั้น first(where:)เพียงแค่ใช้ childViewControllers.first(where: { $0 is CamperVanViewController })
Alexander - Reinstate Monica

9

คำตอบที่ปรับปรุงแล้วสำหรับ Swift 3 โดยใช้คุณสมบัติที่คำนวณได้:

var jobSummaryViewController: JobSummaryViewController {
    get {
        let ctrl = childViewControllers.first(where: { $0 is JobSummaryViewController })
        return ctrl as! JobSummaryViewController
    }
}

นี่จะทำซ้ำรายการเด็ก ๆ จนกว่าจะถึงคู่แรก


8

self.childViewControllers มีความเกี่ยวข้องมากขึ้นเมื่อคุณต้องการการควบคุมจากผู้ปกครอง ตัวอย่างเช่นถ้าตัวควบคุมลูกเป็นมุมมองตารางและคุณต้องการโหลดซ้ำอย่างแรงหรือเปลี่ยนคุณสมบัติผ่านการแตะปุ่มหรือเหตุการณ์อื่น ๆ บนตัวควบคุมมุมมองหลักคุณสามารถทำได้โดยการเข้าถึงอินสแตนซ์ของ ChildViewController ทั้งสองมีแอปพลิเคชันของพวกเขาในรูปแบบที่แตกต่างกัน


2

มีวิธีอื่นในการใช้คำสั่งเปลี่ยนของ Swift ในประเภทของตัวควบคุมมุมมอง:

override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
  switch segue.destination
  {
    case let aViewController as AViewController:
      self.aViewController = aViewController
    case let bViewController as BViewController:
      self.bViewController = bViewController
    default:
      return
  }
}

1

ฉันใช้รหัสเช่น:

- (IBAction)showCartItems:(id)sender{ 
  ListOfCartItemsViewController *listOfItemsVC=[self.storyboard instantiateViewControllerWithIdentifier:@"ListOfCartItemsViewController"];
  [self addChildViewController:listOfItemsVC];
 }

1

ในกรณีที่มีคนที่กำลังมองหาสวิฟท์ 3.0 ,

viewController1 , viewController2และจากนั้นจะสามารถเข้าถึงได้

let viewController1 : OneViewController!
let viewController2 : TwoViewController!

// Safety handling of optional String
if let identifier: String = segue.identifier {

    switch identifier {

    case "segueName1":
        viewController1 = segue.destination as! OneViewController
        break

    case "segueName2":
        viewController2 = segue.destination as! TwoViewController
        break

    // ... More cases can be inserted here ...

    default:
        // A new segue is added in the storyboard but not yet including in this switch
        print("A case missing for segue identifier: \(identifier)")
        break
    }

} else {
    // Either the segue or the identifier is inaccessible 
    print("WARNING: identifier in segue is not accessible")
}

1

ด้วยทั่วไปคุณสามารถทำสิ่งที่หวาน นี่คือส่วนขยายของ Array:

extension Array {
    func firstMatchingType<Type>() -> Type? {
        return first(where: { $0 is Type }) as? Type
    }
}

จากนั้นคุณสามารถทำได้ใน viewController ของคุณ:

var viewControllerInContainer: YourViewControllerClass? {
    return childViewControllers.firstMatchingType()!
}

0

คุณสามารถเขียนแบบนี้

- (IBAction)showDetail:(UIButton *)sender {  
            DetailViewController *detailVc = [self.childViewControllers firstObject];  
        detailVc.lable.text = sender.titleLabel.text;  
    }  
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.