Контроллер доступа к контейнеру из родительской iOS

204

в iOS6 я заметил новый Контейнерный Вид, но не совсем уверен, как получить доступ к его контроллеру из содержащего представления.

Сценарий:

пример

Я хочу получить доступ к меткам в контроллере представления оповещения из контроллера представления, в котором находится вид контейнера.

Между ними есть связь, могу ли я это использовать?

Адам Уэйт
источник
Полное объяснение здесь, для современных представлений контейнера: stackoverflow.com/a/23403979/294884
Толстяк

Ответы:

362

Да, вы можете использовать segue для получения доступа к дочернему контроллеру представления (а также к его представлению и подпредставлениям). Присвойте sege идентификатор (например, 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
мы не спешим? я что-то здесь упускаю ...?
Адам Уэйт
25
да, есть встраивание, которое происходит, когда второй контроллер представления становится дочерним по отношению к первому контроллеру представления. prepareForSegue: вызывается как раз перед тем, как это произойдет. Вы можете использовать эту возможность для передачи данных ребенку или для сохранения ссылки на ребенка для последующего использования. см. также developer.apple.com/library/ios/#documentation/uikit/reference/…
Питер Е,
1
Правильно, «второй контроллер представления становится дочерним по отношению к первому контроллеру представления» при загрузке представления? Это имеет больше смысла, спасибо. Я не с моим проектом сейчас, но буду тестировать позже
Адам Уэйт
1
точно, он вызывается перед viewDidLoad. К моменту достижения viewDidLoad родитель и потомок уже связаны, и [self childViewControllers] в родительском элементе вернет массив всех дочерних контроллеров (см. Ответ rdelmar ниже).
Питер Э
2
Я бы добавил одно предостережение к предлагаемому решению: будьте очень осторожны при доступе к свойству view (дочернего) контроллера представления назначения: в некоторых случаях это будет вызывать его viewDidLoad тут же. Я бы порекомендовал заранее настроить любые необходимые данные segue. так что viewDidLoad может срабатывать безопасно.
AlwaysLearning
56

Вы можете сделать это просто с помощью self.childViewControllers.lastObject(при условии, что у вас есть только один ребенок, в противном случае используйте objectAtIndex:).

rdelmar
источник
1
@RaphaelOliveira, не обязательно. Если у вас есть несколько дочерних контроллеров в одном представлении, ЭТО будет предпочтительным подходом. Это позволяет координировать несколько контейнеров одновременно. prepareForSegue имеет ссылку только на один экземпляр дочернего контроллера, на который он воздействует.
Фидо
2
@Fydo, а в чем проблема с обработкой всех нескольких контейнеров в режиме «prepare to segue»?
Лай Гонсалес
1
Что, если (ужас!) Вы решите переключиться с раскадровки или не использовать seques и т. Д. Затем вы должны выкопать код, внести изменения и т. Д.
Том Андерсен
2
Это мой обычный подход, но теперь он терпит крах для меня, так как я получаю доступ к childViewControllers«слишком скоро»
Мазёд
25

для быстрого программирования

ты можешь написать так

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
источник
Какая польза от знака вопроса после segueName в операторе if? "если segueName?"
Преподобный
19

prepareForSegueПодход работает, но он полагается на строку магического идентификатора Segue. Может быть, есть лучший способ.

Если вы знаете класс VC, который вам нужен, вы можете сделать это очень аккуратно с помощью вычисляемого свойства:

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

Это зависит от childViewControllers. Хотя я согласен, что полагаться на первый вариант может быть хрупким, но название класса, который вы ищете, делает это довольно убедительным.

SimplGy
источник
3
return childViewControllers.filter { $0 is CamperVanViewController }.firstв одном лайнере
Адам Уэйт
1
С тех пор я сделал то, childViewControllers.flatMap({ $0 as? CamperVanViewController }).firstчто считаю немного лучше, так как он забрасывает и избавляет от любых нолей.
SimplGy
Это действительно хорошее решение, если вы хотите получить доступ к этому контроллеру более одного раза
Габриэль Гонсалвес
это безнадежно - нет особой причины, по которой вы можете иметь только одного из этого класса. именно поэтому существуют идентификаторы. просто следуйте стандартной формуле ... stackoverflow.com/a/23403979/294884
Толстяк
не фильтруйте только, чтобы взять первый элемент. просто используйте first(where:). childViewControllers.first(where: { $0 is CamperVanViewController })
Александр - Восстановить Монику
9

Обновленный ответ для Swift 3, используя вычисляемое свойство:

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

Это только повторяет список детей, пока не достигнет первого совпадения.

Робин Догерти
источник
8

self.childViewControllers более актуально, когда вам нужен контроль со стороны родителя. Например, если дочерний контроллер представляет собой табличное представление и вы хотите принудительно перезагрузить его или изменить свойство с помощью нажатия кнопки или любого другого события в Parent View Controller, вы можете сделать это, обратившись к экземпляру ChildViewController, а не через prepareForSegue. Оба имеют свои приложения по-разному.

Гаутам Джайн
источник
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

Если кто-то ищет Swift 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()!
}
Sunkas
источник
0

ты можешь написать так

- (IBAction)showDetail:(UIButton *)sender {  
            DetailViewController *detailVc = [self.childViewControllers firstObject];  
        detailVc.lable.text = sender.titleLabel.text;  
    }  
}
Хуршид
источник