[objective-c] 여러 하위 클래스에 단일 스토리 보드 uiviewcontroller를 사용하는 방법

UINavigationController초기 뷰 컨트롤러 를 포함하는 스토리 보드가 있다고 가정 해 보겠습니다 . 그것의 루트 뷰 컨트롤러의 서브 클래스 UITableViewControllerBasicViewController. 그것은이 IBAction탐색 표시 줄의 오른쪽 탐색 버튼에 연결되는

추가 스토리 보드를 만들 필요없이 내가 다른 뷰의 템플릿으로 스토리 보드를 사용하고 싶습니다 거기에서. 이러한 뷰는 정확히 동일한 인터페이스하지만 루트 뷰 클래스의 컨트롤러를해야합니다 말 SpecificViewController1SpecificViewController2의 하위 클래스입니다 BasicViewController.
이 2 개의 뷰 컨트롤러는 IBAction메서드를 제외하고는 동일한 기능과 인터페이스를 갖습니다 .
다음과 같습니다.

@interface BasicViewController : UITableViewController

@interface SpecificViewController1 : BasicViewController

@interface SpecificViewController2 : BasicViewController

그런 식으로 할 수 있습니까?
난 그냥의 스토리 보드를 인스턴스화 할 수 BasicViewController있지만, 서브 클래스에 루트 뷰 컨트롤러가 SpecificViewController1SpecificViewController2?

감사.



답변

좋은 질문-그러나 불행히도 절름발이 대답입니다. UIStoryboard에는 초기화시 스토리 보드의 개체 세부 정보에 정의 된대로 스토리 보드와 연결된 뷰 컨트롤러를 재정의 할 수있는 이니셜 라이저가 없기 때문에 현재 제안한 작업을 수행 할 수 있다고 생각하지 않습니다. stoaryboard의 모든 UI 요소가 뷰 컨트롤러의 속성에 연결되는 것은 초기화 단계입니다.

기본적으로 스토리 보드 정의에 지정된 뷰 컨트롤러로 초기화됩니다.

스토리 보드에서 만든 UI 요소를 재사용하려는 경우에도 뷰 컨트롤러가 이벤트에 대해 “알려줄”수 있도록 뷰 컨트롤러가 사용하는 속성에 연결되거나 연결되어야합니다.

스토리 보드 레이아웃을 복사하는 것은 그다지 큰 문제가 아닙니다. 특히 3 개의 뷰에 대해 유사한 디자인 만 필요한 경우에는 그다지 중요하지 않습니다. 그러나 그렇게한다면 이전의 모든 연결이 지워 졌는지 확인해야합니다. 그렇지 않으면 시도 할 때 충돌이 발생합니다. 이전 뷰 컨트롤러와 통신합니다. 로그 출력에서 ​​KVO 오류 메시지로 인식 할 수 있습니다.

취할 수있는 몇 가지 접근 방식 :

  • xib 파일의 UIView에 UI 요소를 저장하고 기본 클래스에서 인스턴스화하고 기본보기 (일반적으로 self.view)의 하위보기로 추가합니다. 그런 다음 기본적으로 스토리 보드에서 해당 위치를 유지하지만 올바른 뷰 컨트롤러 하위 클래스가 할당 된 빈 뷰 컨트롤러가있는 스토리 보드 레이아웃을 사용합니다. 그들은 기지에서 물려 받기 때문에 그 관점을 얻게 될 것입니다.

  • 코드에서 레이아웃을 만들고 기본 뷰 컨트롤러에서 설치하십시오. 분명히이 접근 방식은 스토리 보드를 사용하는 목적을 무효화하지만 귀하의 경우에 갈 수있는 방법 일 수 있습니다. 스토리 보드 접근 방식의 이점을 얻을 수있는 앱의 다른 부분이있는 경우 적절한 경우 여기 저기에서 벗어나는 것이 좋습니다. 이 경우 위와 같이 하위 클래스가 할당 된 뱅크 뷰 컨트롤러를 사용하고 기본 뷰 컨트롤러가 UI를 설치하도록합니다.

애플이 당신이 제안한 것을 할 수있는 방법을 생각해 냈다면 좋겠지 만, 그래픽 요소가 컨트롤러 하위 클래스와 미리 연결되어있는 문제는 여전히 문제가 될 것입니다.

새해 복 많이 받으세요 !! 잘 지내다


답변

우리가 찾고있는 라인의 코드는 다음과 같습니다 :

object_setClass(AnyObject!, AnyClass!)

Storyboard-> 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.xibNib 이로 드되어야하는 클래스를 지정하지 않고 “파일 소유자”에 모든 출력을 연결할 때 키-값 쌍을 지정하는 것입니다. ” 이보기는이 인스턴스 변수 이름에 연결되어야합니다 .” [SubclassMyViewController alloc] initWithNibName:초기화 프로세스를 호출 할 때 nib에서 생성 한 뷰 를 ” 제어 “하는 데 사용할 뷰 컨트롤러를 지정합니다 .


답변

스토리 보드가 사용자 정의 뷰 컨트롤러의 다른 하위 클래스를 인스턴스화하도록하는 것이 가능합니다. 약간 비 정통적인 기술인 alloc뷰 컨트롤러에 대한 메서드를 재정의해야합니다 . 커스텀 뷰 컨트롤러가 생성되면 오버라이드 된 alloc 메소드는 실제로 alloc서브 클래스 에서 실행 된 결과를 반환합니다 .

나는 다양한 시나리오에서 테스트했고 오류가 없었지만 더 복잡한 설정에 대처할 수는 없지만 작동하지 않아야 할 이유가 없다는 단서로 대답을 시작해야합니다. . 또한이 방법을 사용하여 앱을 제출하지 않았으므로 Apple의 검토 프로세스에서 거부 될 수있는 외부 기회가 있습니다 (다시 말하지만 그 이유는 알 수 없습니다).

데모 목적으로 UILabel IBOutlet 및 IBAction이있는 UIViewControllercalled 의 하위 클래스가 TestViewController있습니다. 내 스토리 보드에서 뷰 컨트롤러를 추가하고 클래스를으로 수정하고 TestViewControllerIBOutlet을 UILabel에 연결하고 IBAction을 UIButton에 연결했습니다. 앞의 viewController에서 UIButton에 의해 트리거되는 모달 segue를 통해 TestViewController를 제시합니다.

스토리 보드 이미지

어떤 클래스가 인스턴스화되는지 제어하기 위해 정적 변수와 관련 클래스 메서드를 추가 했으므로 사용할 서브 클래스를 가져 오거나 설정합니다 (어떤 서브 클래스를 인스턴스화 할 것인지 결정하는 다른 방법을 채택 할 수 있습니다).

TestViewController.m :

#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: RedTestViewControllerGreenTestViewController. 하위 클래스에는 각각 추가 속성이 있으며 각 재정의 viewDidLoad는 뷰의 배경색을 변경하고 UILabel IBOutlet의 텍스트를 업데이트합니다.

RedTestViewController.m :

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

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

GreenTestViewController.m :

- (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]];


답변

특히 nickgzzjrJiří Zahálka 답변을 바탕으로 CocoaBob 의 두 번째 답변 아래에 주석을 추가하여 OP에 필요한 작업을 정확히 수행하는 짧은 일반 방법을 준비했습니다. 스토리 보드 이름과 뷰 컨트롤러 스토리 보드 ID 만 확인하면됩니다.

class func instantiate<T: BasicViewController>(as _: T.Type) -> T? {
        let storyboard = UIStoryboard(name: "StoryboardName", bundle: nil)
        guard let instance = storyboard.instantiateViewController(withIdentifier: "Identifier") as? BasicViewController else {
            return nil
        }
        object_setClass(instance, T.self)
        return instance as? T
    }

강제 풀기 (swiftlint 경고)를 방지하기 위해 옵션이 추가되었지만 메서드는 올바른 객체를 반환합니다.


답변

엄격히 하위 클래스는 아니지만 다음을 수행 할 수 있습니다.

  1. option-문서 개요에서 기본 클래스 뷰 컨트롤러를 드래그하여 복사본을 만듭니다.
  2. 새 뷰 컨트롤러 사본을 스토리 보드의 별도 위치로 이동
  3. Identity Inspector에서 클래스 를 하위 클래스보기 컨트롤러로 변경 합니다.

다음은 내가 작성한 Bloc 튜토리얼 의 예 ViewController입니다 WhiskeyViewController.

위의 세 단계의 애니메이션

이를 통해 스토리 보드에서 뷰 컨트롤러 하위 클래스의 하위 클래스를 만들 수 있습니다. 그런 다음을 사용 instantiateViewControllerWithIdentifier:하여 특정 하위 클래스를 만들 수 있습니다 .

이 접근 방식은 약간 융통성이 없습니다. 스토리 보드 내에서 기본 클래스 컨트롤러에 대한 이후 수정 사항은 하위 클래스로 전파되지 않습니다. 당신이 경우 많은 서브 클래스를 당신은 다른 솔루션을 하나 더 좋을 수도 있지만,이 방법은 핀치에 할 것입니다.