Как использовать одну раскадровку uiviewcontroller для нескольких подклассов


Допустим, у меня есть раскадровка, которая содержит UINavigationController в качестве начального контроллера вида. Его корневой контроллер вида является подклассом UITableViewController, которая составляет BasicViewController. Он имеет IBAction который подключен к правой навигационной кнопке панели навигации

Оттуда я хотел бы использовать раскадровку в качестве шаблона для других представлений без необходимости создавать дополнительные раскадровки. Скажем, эти представления будут иметь точно такой же интерфейс, но с корневым контроллером представления класса SpecificViewController1 и SpecificViewController2, которые являются подклассами BasicViewController.
Эти 2 контроллера вида будут иметь ту же функциональность и интерфейс, за исключением IBAction метод.
Это было бы похоже на следующее:

@interface BasicViewController : UITableViewController

@interface SpecificViewController1 : BasicViewController

@interface SpecificViewController2 : BasicViewController

могу ли я сделать что-то подобное?
Могу ли я просто создать экземпляр раскадровки BasicViewController но есть корневой контроллер представления в подкласс SpecificViewController1 и SpecificViewController2?

Спасибо.

9 99

9 ответов:

большой вопрос - но, к сожалению, только слабый ответ. Я не верю, что в настоящее время можно сделать то, что вы предлагаете, потому что в UIStoryboard нет инициализаторов, которые позволяют переопределить контроллер представления, связанный с раскадровкой, как определено в деталях объекта в раскадровке при инициализации. При инициализации все элементы пользовательского интерфейса в stoaryboard связаны с их свойствами в контроллере представления.

Он будет по умолчанию инициализируйте с помощью контроллера вида, указанного в определении раскадровки.

Если вы пытаетесь получить повторное использование элементов пользовательского интерфейса, созданных в раскадровке, они все равно должны быть связаны или связаны со свойствами, в которых когда-либо контроллер представления использует их, чтобы они могли "рассказать" контроллеру представления о событиях.

Это не так уж и важно, копируя макет раскадровки, особенно если вам нужен только аналогичный дизайн для 3 видов, однако если вы сделайте, вы должны убедиться, что все предыдущие ассоциации очищены, или он получит сбои при попытке связаться с предыдущим контроллером представления. Вы сможете распознать их как сообщения об ошибках KVO в выводе журнала.

пару подходов вы могли бы взять:

  • хранить элементы пользовательского интерфейса в UIView-в файле xib и создать его экземпляр из базового класса и добавить его в качестве вложенного представления в главном представлении, как правило, самостоятельно.вид. Тогда вы бы просто используйте макет раскадровки С в основном пустыми контроллерами вида, занимающими свое место в раскадровке, но с правильным подклассом контроллера вида, назначенным им. Поскольку они унаследуют от базы, они получат этот вид.

  • создайте макет в коде и установите его с помощью контроллера базового вида. Очевидно, что этот подход побеждает цель использования раскадровки, но может быть способ пойти в вашем случае. Если у вас есть другие части приложения, которые было бы полезно использовать подход раскадровки, это нормально отклоняться здесь и там, если это уместно. В этом случае, как и выше, вы просто используете контроллеры банковских представлений с назначенным подклассом и позволяете контроллеру базового представления устанавливать пользовательский интерфейс.

было бы неплохо, если бы Apple придумала способ сделать то, что вы предлагаете, но проблема наличия графических элементов, предварительно связанных с подклассом контроллера, все равно будет проблемой.

хорошего Нового года!! быть ну

код строки, которую мы ищем:

object_setClass(AnyObject!, AnyClass!)

в раскадровке - > добавить UIViewController дать ему имя класса ParentVC.

class ParentVC: UIViewController {

    var type: Int?

    override func awakeFromNib() {

        if type = 0 {

            object_setClass(self, ChildVC1.self)
        }
        if type = 1 {

            object_setClass(self, ChildVC2.self)
        }  
    }

    override func viewDidLoad() {   }
}

class ChildVC1: ParentVC {

    override func viewDidLoad() {
        super.viewDidLoad()

        println(type)
        // Console prints out 0
    }
}

class ChildVC2: ParentVC {

    override func viewDidLoad() {
        super.viewDidLoad()

        println(type)
        // Console prints out 1
    }
}

как говорится в принятом ответе, это не похоже на то, что можно сделать с раскадровками.

мое решение-использовать Nib-так же, как разработчики использовали их до раскадровки. если вы хотите иметь многоразовый, подклассный контроллер представления (или даже представление), моя рекомендация-использовать Nibs.

SubclassMyViewController *myViewController = [[SubclassMyViewController alloc] initWithNibName:@"MyViewController" bundle:nil]; 

когда вы подключаете все свои розетки к "владельцу файла" в MyViewController.xib вы не указываете, какой класс перо должно быть загружено, вы просто указание пар ключ-значение:"это представление должно быть связано с именем переменной экземпляра."При звонке [SubclassMyViewController alloc] initWithNibName: процесс инициализации определяет, какой контроллер представления будет использоваться для "управления" вид, который вы создали в Сиб.

можно иметь раскадровку инстанцировать различные подклассы пользовательского контроллера вида, хотя это включает в себя несколько неортодоксальный метод: переопределение alloc метод контроллера представления. При создании пользовательского контроллера представления переопределенный метод alloc фактически возвращает результат выполнения alloc на подкласс.

я должен предварить ответ с условием, что, хотя я проверил его в различных сценариях и не получил ошибок, я не могу гарантировать, что он справится с более сложными настройками (но я не вижу причин, почему он не должен работать). Кроме того, я не представил никаких приложений, использующих этот метод, поэтому есть внешний шанс, что он может быть отклонен процессом обзора Apple (хотя опять же я не вижу причин, почему это должно быть).

для демонстрационных целей, у меня есть подкласс UIViewController под названием TestViewController, который имеет UILabel IBOutlet и IBAction. В моей раскадровке я добавил контроллер вида и исправил его класс до TestViewController, и подключил IBOutlet к UILabel и IBAction к UIButton. Я представляю TestViewController через модальный сегмент, вызванный UIButton на предыдущем viewController.

Storyboard image

чтобы контролировать, какой класс создается, я добавил статическую переменную и связанные с ней методы класса, чтобы получить / установить подкласс, который будет использоваться (я думаю, можно было бы принять другие способы определения того, какой подкласс должен быть instantiated):

TestViewController.м:

#import "TestViewController.h"

@interface TestViewController ()
@end

@implementation TestViewController

static NSString *_classForStoryboard;

+(NSString *)classForStoryboard {
    return [_classForStoryboard copy];
}

+(void)setClassForStoryBoard:(NSString *)classString {
    if ([NSClassFromString(classString) isSubclassOfClass:[self class]]) {
        _classForStoryboard = [classString copy];
    } else {
        NSLog(@"Warning: %@ is not a subclass of %@, reverting to base class", classString, NSStringFromClass([self class]));
        _classForStoryboard = nil;
    }
}

+(instancetype)alloc {
    if (_classForStoryboard == nil) {
        return [super alloc];
    } else {
        if (NSClassFromString(_classForStoryboard) != [self class]) {
            TestViewController *subclassedVC = [NSClassFromString(_classForStoryboard) alloc];
            return subclassedVC;
        } else {
            return [super alloc];
        }
    }
}

для моего теста у меня есть два подкласса TestViewController:RedTestViewController и GreenTestViewController. Каждый подкласс имеет дополнительные свойства и каждое переопределение viewDidLoad чтобы изменить цвет фона представления и обновить текст UILabel IBOutlet:

RedTestViewController.м:

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

    self.view.backgroundColor = [UIColor redColor];
    self.testLabel.text = @"Set by RedTestVC";
}

GreenTestViewController.м:

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor greenColor];
    self.testLabel.text = @"Set by GreenTestVC";
}

в некоторых случаях я могу захотеть создать экземпляр TestViewController сам, в других случаях RedTestViewController или GreenTestViewController. В предыдущем контроллере представления я делаю это случайным образом следующим образом:

NSInteger vcIndex = arc4random_uniform(4);
if (vcIndex == 0) {
    NSLog(@"Chose TestVC");
    [TestViewController setClassForStoryBoard:@"TestViewController"];
} else if (vcIndex == 1) {
    NSLog(@"Chose RedVC");
    [TestViewController setClassForStoryBoard:@"RedTestViewController"];
} else if (vcIndex == 2) {
    NSLog(@"Chose BlueVC");
    [TestViewController setClassForStoryBoard:@"BlueTestViewController"];
} else {
    NSLog(@"Chose GreenVC");
    [TestViewController setClassForStoryBoard:@"GreenTestViewController"];
}

отметим, что setClassForStoryBoard метод проверяет, что запрошенное имя класса действительно является подклассом TestViewController, чтобы избежать каких-либо путаниц. Ссылка выше на BlueTestViewController есть ли возможность проверить эту функциональность.

попробуйте это, после instantiateViewControllerWithIdentifier.

- (void)setClass:(Class)c {
    object_setClass(self, c);
}

как :

SubViewController *vc = [sb instantiateViewControllerWithIdentifier:@"MainViewController"];
[vc setClass:[SubViewController class]];

хотя это не строго подкласс, вы можете:

  1. опции-перетащите контроллер представления базового класса в контур документа, чтобы сделать копию
  2. переместить новую копию контроллера вида в отдельное место на раскадровке
  3. изменить класс к контроллеру представления подкласса в Инспекторе идентификации

вот пример блок учебник я написал, подклассы ViewController с WhiskeyViewController:

animation of the above three steps

Это позволяет создавать подклассы подклассы вид контроллера в раскадровке. Затем вы можете использовать instantiateViewControllerWithIdentifier: для создания конкретных подклассов.

этот подход немного негибкий: более поздние модификации в раскадровке контроллера базового класса не распространяются на подкласс. Если у вас есть много подклассов вы можете быть лучше с одним из других решений, но это будет делать в крайнем случае.

Objc_setclass метод не создает экземпляр childvc. Но в то время как выскакивают из childvc, deinit childvc вызывается. Поскольку нет памяти, выделенной отдельно для childvc, приложение аварийно завершает работу. Basecontroller имеет экземпляр , в то время как дочерний vc не имеет.

вероятно, наиболее гибким способом является использование многоразовых представлений.

(создать представление в отдельном файле XIB или Container view и добавьте его в каждую сцену контроллера вида подкласса в раскадровке)

если вы не слишком полагаетесь на раскадровки, вы можете создать отдельный .xib файл для контроллера.

установите владельца соответствующего файла и выходы в MainViewController и заменить init(nibName:bundle:) В основном VC, чтобы его дети могли получить доступ к одному и тому же наконечнику и его выходам.

ваш код должен выглядеть так:

class MainViewController: UIViewController {
    @IBOutlet weak var button: UIButton!

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: "MainViewController", bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        button.tintColor = .red
    }
}

и ваш ребенок VC сможет повторно использовать перо своего родителя:

class ChildViewController: MainViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        button.tintColor = .blue
    }
}