Akses Pengendali Tampilan Kontainer dari Parent iOS

203

di iOS6 saya melihat Tampilan Kontainer baru tetapi saya tidak yakin cara mengakses controller itu dari tampilan yang mengandung.

Skenario:

contoh

Saya ingin mengakses label di pengontrol tampilan Alert dari pengontrol tampilan yang menampung tampilan wadah.

Ada segue di antara mereka, bisakah saya menggunakannya?

Adam Waite
sumber
sepenuhnya dijelaskan di sini, untuk tampilan wadah modern: stackoverflow.com/a/23403979/294884
Fattie

Jawaban:

362

Ya, Anda dapat menggunakan segue untuk mengakses pengontrol tampilan anak (dan tampilan serta subview). Berikan segue pengidentifikasi (seperti alertview_embed), menggunakan inspektur Atribut di Storyboard. Kemudian mintalah pengontrol tampilan induk (yang menampung tampilan wadah) menerapkan metode seperti ini:

- (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...
   }
}
Peter E
sumber
1
kita tidak segueing? apakah saya melewatkan sesuatu di sini ...?
Adam Waite
25
ya, ada embed segue yang terjadi ketika pengontrol tampilan kedua dibuat anak dari pengontrol tampilan pertama. prepForSegue: dipanggil sebelum ini terjadi. Anda dapat menggunakan kesempatan ini untuk meneruskan data ke anak, atau untuk menyimpan referensi ke anak untuk digunakan nanti. lihat juga developer.apple.com/library/ios/#documentation/uikit/reference/…
Peter E
1
Ah benar, apakah 'pengontrol tampilan kedua dibuat anak dari pengontrol tampilan pertama' saat tampilan dimuat? Ini lebih masuk akal sekarang, terima kasih. Saya tidak dengan proyek saya sekarang tetapi akan menguji nanti
Adam Waite
1
tepatnya, ini dipanggil sebelum viewDidLoad. Pada saat viewDidLoad tercapai, induk dan anak telah terhubung dan [self childViewControllers] di induk akan mengembalikan array semua pengendali anak (lihat jawaban rdelmar di bawah).
Peter E
2
Saya akan menambahkan satu peringatan ke solusi yang diusulkan: sangat berhati-hati ketika mengakses properti tampilan pengontrol tampilan (anak) tujuan: dalam beberapa keadaan ini akan menyebabkan viewDidLoad dipanggil di sana dan kemudian. Saya akan merekomendasikan pengaturan setiap data segue yang diperlukan sebelumnya sehingga viewDidLoad dapat dijalankan dengan aman.
AlwaysLearning
56

Anda dapat melakukannya hanya dengan self.childViewControllers.lastObject( dengan asumsi Anda hanya memiliki satu anak, jika tidak gunakan objectAtIndex:).

rdelmar
sumber
1
@ RaphaelOliveira, belum tentu. Jika Anda memiliki beberapa childControllers dalam satu tampilan, INI akan menjadi pendekatan yang disukai. Ini memungkinkan Anda mengoordinasi banyak wadah sekaligus. preparForSegue hanya memiliki referensi ke turunan pengendali satu anak yang digunakannya.
Fydo
2
@ Fydo, dan apa masalah dengan menangani semua beberapa kontainer di 'bersiap untuk segue'?
Lay González
1
Bagaimana jika (kengerian!) Anda memutuskan untuk beralih dari storyboard atau tidak menggunakan seques, dll. Kemudian Anda harus menggali kode untuk membuat perubahan, dll.
Tom Andersen
2
Ini adalah pendekatan saya yang biasa, tetapi sekarang crash untuk saya karena saya mengakses childViewControllers"terlalu cepat"
Mazyod
25

untuk Pemrograman Swift

Anda bisa menulis seperti ini

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
    }
}
Sruit A.Suk
sumber
Apa gunanya tanda tanya setelah segueName pada pernyataan if? "Jika segueName?"
Pendeta
19

The prepareForSeguependekatan bekerja, tapi hal itu bergantung pada segue identifier sihir tali. Mungkin ada cara yang lebih baik.

Jika Anda tahu kelas VC yang Anda cari, Anda dapat melakukannya dengan sangat rapi dengan properti yang dihitung:

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

Ini bergantung pada childViewControllers. Sementara saya setuju itu bisa rapuh untuk mengandalkan yang pertama, penamaan kelas yang Anda cari membuat ini tampak cukup solid.

SimplGy
sumber
3
return childViewControllers.filter { $0 is CamperVanViewController }.firstdalam satu liner
Adam Waite
1
Sejak childViewControllers.flatMap({ $0 as? CamperVanViewController }).firstitu saya melakukan yang saya pikir sedikit lebih baik, karena itu membuang dan menghilangkan nil.
SimplGy
Ini adalah solusi yang sangat bagus jika Anda ingin mengakses controller tampilan itu lebih dari satu kali
Gabriel Goncalves
ini tidak ada harapan - tidak ada alasan khusus Anda mungkin hanya memiliki satu dari kelas tertentu. itulah sebabnya ada pengidentifikasi. cukup ikuti rumus standar ... stackoverflow.com/a/23403979/294884
Fattie
jangan memfilter hanya untuk mengambil elemen pertama. gunakan saja first(where:). childViewControllers.first(where: { $0 is CamperVanViewController })
Alexander - Reinstate Monica
9

Jawaban yang diperbarui untuk Swift 3, menggunakan properti yang dihitung:

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

Ini hanya mengulangi daftar anak-anak sampai mencapai pertandingan pertama.

Robin Daugherty
sumber
8

self.childViewControllers lebih relevan ketika Anda membutuhkan kontrol dari orang tua. Misalnya, jika pengontrol anak adalah tampilan tabel dan Anda ingin memuatnya dengan paksa atau mengubah properti melalui ketukan tombol atau peristiwa lain pada Pengontrol Tampilan Orang Tua, Anda dapat melakukannya dengan mengakses instance ChildViewController dan tidak melalui prepForSegue. Keduanya memiliki aplikasi mereka dengan cara yang berbeda.

Gautam Jain
sumber
2

Ada cara lain menggunakan pernyataan switch Swift pada tipe view controller:

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
  }
}
Joanna Carter
sumber
1

Saya menggunakan Kode seperti:

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

Jika seseorang mencari Swift 3.0 ,

viewController1 , viewController2 dan seterusnya akan dapat diakses.

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")
}
Marco Leong
sumber
1

Dengan generik Anda dapat melakukan beberapa hal manis. Berikut ini adalah ekstensi untuk Array:

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

Anda kemudian dapat melakukan ini di viewController Anda:

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

Anda bisa menulis seperti ini

- (IBAction)showDetail:(UIButton *)sender {  
            DetailViewController *detailVc = [self.childViewControllers firstObject];  
        detailVc.lable.text = sender.titleLabel.text;  
    }  
}
Khurshid
sumber