Контроллер доступа к контейнеру из родительской 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:).


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
    }
}

Какая польза от знака вопроса после segueName в операторе if? "если segueName?"
Преподобный

19

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

Если вы знаете класс 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в одном лайнере
Адам Уэйт

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()!
}

0

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

- (IBAction)showDetail:(UIButton *)sender {  
            DetailViewController *detailVc = [self.childViewControllers firstObject];  
        detailVc.lable.text = sender.titleLabel.text;  
    }  
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.