วิธีใช้ uiviewcontroller สตอรี่บอร์ดเดียวสำหรับคลาสย่อยหลาย ๆ


118

สมมติว่าฉันมีสตอรีบอร์ดที่มีUINavigationControllerเป็นตัวควบคุมมุมมองเริ่มต้น ควบคุมดูรากของมันคือ subclass ของซึ่งเป็นUITableViewController BasicViewControllerมีIBActionซึ่งเชื่อมต่อกับปุ่มนำทางด้านขวาของแถบนำทาง

จากนั้นฉันต้องการใช้สตอรีบอร์ดเป็นเทมเพลตสำหรับมุมมองอื่น ๆ โดยไม่ต้องสร้างสตอรีบอร์ดเพิ่มเติม บอกว่ามุมมองเหล่านี้จะมีว่าอินเตอร์เฟซเดียวกัน แต่มีการควบคุมดูรากของการเรียนSpecificViewController1และซึ่งเป็นคลาสย่อยSpecificViewController2 ตัวควบคุมมุมมอง 2 ตัวนั้นจะมีฟังก์ชันและอินเทอร์เฟซเหมือนกันยกเว้นวิธี มันจะเป็นดังต่อไปนี้:BasicViewController
IBAction

@interface BasicViewController : UITableViewController

@interface SpecificViewController1 : BasicViewController

@interface SpecificViewController2 : BasicViewController

ฉันจะทำอะไรแบบนั้นได้ไหม?
ฉันสามารถเพียงอินสแตนซ์สตอรี่บอร์ดของBasicViewControllerแต่ต้องควบคุมดูราก subclass SpecificViewController1และSpecificViewController2?

ขอบคุณ


3
อาจเป็นการชี้ให้เห็นว่าคุณสามารถทำได้ด้วยปลายปากกา แต่ถ้าคุณเป็นเหมือนฉันที่ต้องการฟีเจอร์ดีๆที่มี แต่สตอรี่บอร์ดเท่านั้น (เช่นเซลล์คงที่ / ต้นแบบ) ฉันเดาว่าเราโชคไม่ดี
Joseph Lin

คำตอบ:


57

คำถามที่ดี - แต่น่าเสียดายที่มีเพียงคำตอบที่ง่อย ฉันไม่เชื่อว่าในปัจจุบันเป็นไปได้ที่จะทำสิ่งที่คุณเสนอเนื่องจากไม่มีตัวเริ่มต้นใน UIStoryboard ที่อนุญาตให้แทนที่ตัวควบคุมมุมมองที่เชื่อมโยงกับกระดานเรื่องราวตามที่กำหนดไว้ในรายละเอียดวัตถุในกระดานเรื่องราวเกี่ยวกับการเริ่มต้น อยู่ที่การเริ่มต้นองค์ประกอบ UI ทั้งหมดในสตอรี่บอร์ดจะเชื่อมโยงกับคุณสมบัติในตัวควบคุมมุมมอง

โดยค่าเริ่มต้นจะเริ่มต้นด้วยตัวควบคุมมุมมองที่ระบุไว้ในนิยามสตอรี่บอร์ด

หากคุณกำลังพยายามนำองค์ประกอบ UI ที่คุณสร้างขึ้นในสตอรีบอร์ดมาใช้ซ้ำองค์ประกอบเหล่านี้ยังคงต้องเชื่อมโยงหรือเชื่อมโยงกับคุณสมบัติที่ตัวควบคุมมุมมองใช้อยู่เพื่อให้สามารถ "บอก" ผู้ควบคุมมุมมองเกี่ยวกับเหตุการณ์ได้

การคัดลอกเค้าโครงสตอรี่บอร์ดไม่ใช่เรื่องใหญ่มากนักโดยเฉพาะอย่างยิ่งหากคุณต้องการการออกแบบที่คล้ายกันสำหรับ 3 มุมมองอย่างไรก็ตามหากคุณทำคุณต้องแน่ใจว่าได้ล้างการเชื่อมโยงก่อนหน้านี้ทั้งหมดมิฉะนั้นจะเกิดปัญหาเมื่อพยายาม เพื่อสื่อสารกับตัวควบคุมมุมมองก่อนหน้า คุณจะสามารถรับรู้ได้ว่าเป็นข้อความแสดงข้อผิดพลาด KVO ในเอาต์พุตบันทึก

สองวิธีที่คุณสามารถทำได้:

  • จัดเก็บองค์ประกอบ UI ใน UIView - ในไฟล์ xib และสร้างอินสแตนซ์จากคลาสฐานของคุณและเพิ่มเป็นมุมมองย่อยในมุมมองหลักโดยทั่วไปคือ self.view จากนั้นคุณจะใช้เค้าโครงสตอรี่บอร์ดที่มีตัวควบคุมมุมมองว่างเปล่าโดยทั่วไปซึ่งถือตำแหน่งของพวกเขาในกระดานเรื่องราว แต่ด้วยคลาสย่อยของตัวควบคุมมุมมองที่ถูกกำหนดให้กับพวกเขา เนื่องจากพวกเขาจะได้รับมรดกจากฐานพวกเขาจะได้รับมุมมองนั้น

  • สร้างเค้าโครงในโค้ดและติดตั้งจากตัวควบคุมมุมมองฐานของคุณ เห็นได้ชัดว่าวิธีนี้เอาชนะจุดประสงค์ของการใช้สตอรีบอร์ด แต่อาจเป็นวิธีที่จะไปในกรณีของคุณ หากคุณมีส่วนอื่น ๆ ของแอปที่จะได้รับประโยชน์จากแนวทางกระดานเรื่องราวคุณสามารถเบี่ยงเบนไปที่นี่และที่นั่นได้ตามความเหมาะสม ในกรณีนี้เช่นข้างต้นคุณจะใช้ตัวควบคุมมุมมองธนาคารกับคลาสย่อยของคุณที่กำหนดและปล่อยให้ตัวควบคุมมุมมองพื้นฐานติดตั้ง UI

คงจะดีไม่น้อยหาก Apple มีวิธีทำในสิ่งที่คุณเสนอ แต่ปัญหาของการเชื่อมโยงองค์ประกอบกราฟิกล่วงหน้ากับคลาสย่อยของคอนโทรลเลอร์จะยังคงเป็นปัญหา

สวัสดีปีใหม่ !! สบายดี


นั่นเป็นไปอย่างรวดเร็ว อย่างที่คิดคงเป็นไปไม่ได้ ขณะนี้ฉันคิดวิธีแก้ปัญหาโดยการมีคลาส BasicViewController นั้นและมีคุณสมบัติเพิ่มเติมเพื่อระบุว่า "คลาส" / "โหมด" ใดที่จะทำหน้าที่เป็น ขอบคุณต่อไป
verdy

2
แย่เกินไป :( เดาว่าฉันต้องคัดลอกและ
วางตัว

1
และนี่คือสาเหตุที่ฉันไม่ชอบ Storyboards ... ยังไงก็ตามมันใช้งานไม่ได้จริง ๆ เมื่อคุณทำมุมมองมากกว่ามาตรฐานเล็กน้อย ...
TheEye

เศร้ามากเมื่อได้ยินคุณพูดแบบนั้น ฉันกำลังหาทางแก้ไข
Tony

2
มีวิธีการอื่น: ระบุตรรกะที่กำหนดเองในผู้รับมอบสิทธิ์ที่แตกต่างกันและในการจัดเตรียมสำหรับผู้ร่วมประชุมกำหนดผู้รับมอบสิทธิ์ที่ถูกต้อง ด้วยวิธีนี้คุณจะสร้าง 1 UIViewController + 1 UIViewController ใน Storyboard แต่คุณมีเวอร์ชันการใช้งานหลายเวอร์ชัน
plam4u

45

รหัสของบรรทัดที่เรากำลังมองหาคือ:

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

5
ขอบคุณมันใช้งานได้จริงเช่นclass func instantiate() -> SubClass { let instance = (UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("SuperClass") as? SuperClass)! object_setClass(instance, SubClass.self) return (instance as? SubClass)! }
CocoaBob

3
ฉันไม่แน่ใจว่าฉันเข้าใจวิธีการทำงานนี้ ผู้ปกครองตั้งค่าคลาสเป็นของเด็กหรือไม่? จะมีลูกหลายคนได้ยังไง?!
user1366265

2
คุณชายทำให้วันของฉัน
เจเร

2
ตกลงพวกเราขออธิบายรายละเอียดอีกเล็กน้อย: เราต้องการบรรลุอะไร? เราต้องการซับคลาส ParentViewController ของเราเพื่อให้เราสามารถใช้ Storyboard สำหรับคลาสอื่น ๆ ได้มากขึ้น ดังนั้นเส้นเวทย์มนตร์ที่ทำทุกอย่างจึงถูกเน้นในโซลูชันของฉันและต้องใช้ใน AwakenFromNib ใน ParentVC สิ่งที่เกิดขึ้นคือมันใช้วิธีการทั้งหมดจาก ChildVC1 ที่ตั้งใหม่ซึ่งกลายเป็นคลาสย่อย หากคุณต้องการใช้สำหรับ ChildVC เพิ่มเติม? เพียงใช้ตรรกะของคุณใน AwakenFromNib .. if (type = a) {object_setClass (self, ChildVC1.self)} else {object_setClass (self.ChildVC2.self)} ขอให้โชคดี
JiříZahálka

10
ระวังให้มากเมื่อใช้สิ่งนี้! โดยปกติไม่ควรใช้เลย ... สิ่งนี้จะเปลี่ยนตัวชี้ isa ของตัวชี้ที่กำหนดและไม่ได้จัดสรรหน่วยความจำใหม่เพื่อรองรับเช่นคุณสมบัติที่แตกต่างกัน ตัวบ่งชี้หนึ่งสำหรับสิ่งนี้คือตัวชี้ที่selfจะไม่เปลี่ยนแปลง ดังนั้นการตรวจสอบวัตถุ (เช่นการอ่านค่า _ivar / คุณสมบัติ) หลังจากobject_setClassนั้นอาจทำให้เกิดปัญหาได้
Patrik

15

เนื่องจากคำตอบที่ได้รับการยอมรับดูเหมือนว่าจะไม่สามารถทำกับสตอรีบอร์ดได้

วิธีแก้ปัญหาของฉันคือใช้ Nib - เหมือนกับที่ devs ใช้ก่อนสตอรี่บอร์ด หากคุณต้องการมีตัวควบคุมมุมมองคลาสย่อยที่ใช้ซ้ำได้ (หรือแม้แต่มุมมอง) คำแนะนำของฉันคือการใช้ Nibs

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

เมื่อคุณเชื่อมต่อร้านค้าทั้งหมดของคุณกับ "เจ้าของไฟล์" ในMyViewController.xibคุณไม่ได้ระบุว่าควรโหลด Nib เป็นคลาสใดคุณเพียงระบุคู่คีย์ - ค่า: " มุมมองนี้ควรเชื่อมต่อกับชื่อตัวแปรอินสแตนซ์นี้" เมื่อเรียก[SubclassMyViewController alloc] initWithNibName:กระบวนการเริ่มต้นให้ระบุตัวควบคุมมุมมองที่จะใช้เพื่อ " ควบคุม " มุมมองที่คุณสร้างขึ้นในปลายปากกา


น่าแปลกใจที่สิ่งนี้เป็นไปได้กับสตอรีบอร์ดด้วยไลบรารีรันไทม์ของ ObjC ตรวจสอบคำตอบของฉันที่นี่: stackoverflow.com/a/57622836/7183675
Adam Tucholski

9

เป็นไปได้ที่จะมีสตอรี่บอร์ดสร้างอินสแตนซ์คลาสย่อยที่แตกต่างกันของตัวควบคุมมุมมองที่กำหนดเองแม้ว่าจะเกี่ยวข้องกับเทคนิคนอกรีตเล็กน้อย: การแทนที่allocเมธอดสำหรับตัวควบคุมมุมมอง เมื่อสร้างตัวควบคุมมุมมองแบบกำหนดเองเมธอดการจัดสรรที่ถูกแทนที่ในความเป็นจริงจะส่งคืนผลลัพธ์ของการรันallocบนคลาสย่อย

ฉันควรนำคำตอบด้วยเงื่อนไขที่ว่าแม้ว่าฉันได้ทดสอบในสถานการณ์ต่างๆและไม่ได้รับข้อผิดพลาด แต่ฉันไม่สามารถมั่นใจได้ว่าจะรับมือกับการตั้งค่าที่ซับซ้อนมากขึ้น (แต่ฉันไม่เห็นเหตุผลว่าทำไมจึงไม่ควรทำงาน) . นอกจากนี้ฉันยังไม่ได้ส่งแอพใด ๆ โดยใช้วิธีนี้ดังนั้นจึงมีโอกาสภายนอกที่กระบวนการตรวจสอบของ Apple อาจถูกปฏิเสธ (อีกครั้งฉันไม่เห็นเหตุผลว่าทำไมถึงควร)

เพื่อวัตถุประสงค์ในการสาธิตฉันมีคลาสย่อยของการUIViewControllerเรียกTestViewControllerซึ่งมี UILabel IBOutlet และ IBAction ในสตอรีบอร์ดของฉันฉันได้เพิ่มตัวควบคุมมุมมองและแก้ไขคลาสของมันTestViewControllerและเชื่อมต่อ IBOutlet กับ UILabel และ IBAction เข้ากับ UIButton ฉันนำเสนอ TestViewController โดยใช้ modal segue ที่เรียกใช้โดย UIButton บน viewController ก่อนหน้านี้

ภาพสตอรี่บอร์ด

ในการควบคุมคลาสที่ถูกสร้างอินสแตนซ์ฉันได้เพิ่มตัวแปรคงที่และเมธอดคลาสที่เกี่ยวข้องดังนั้นรับ / ตั้งค่าคลาสย่อยที่จะใช้ (ฉันเดาว่าอาจใช้วิธีอื่นในการพิจารณาว่าคลาสย่อยใดจะถูกสร้างอินสแตนซ์):

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: RedTestViewControllerและGreenTestViewController. คลาสย่อยแต่ละคลาสมีคุณสมบัติเพิ่มเติมและแต่ละการแทนที่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มีไว้เพื่อทดสอบฟังก์ชันนี้


เราได้ทำบางสิ่งที่คล้ายกันในโครงการนี้ แต่การลบล้างวิธีการจัดสรรของ UIViewController เพื่อรับคลาสย่อยจากคลาสภายนอกที่รวบรวมข้อมูลทั้งหมดเกี่ยวกับการแทนที่ทั้งหมด ทำงานได้อย่างสมบูรณ์
ทิม

โดยวิธีนี้อาจหยุดทำงานเป็น fas ar เนื่องจาก Apple หยุดเรียกการจัดสรรบนตัวควบคุมมุมมอง สำหรับตัวอย่างคลาส NSManagedObject ไม่เคยได้รับวิธีการจัดสรร ฉันคิดว่า Apple สามารถคัดลอกรหัสไปยังวิธีอื่น: อาจจะ + จัดสรรManagedObject
Tim

7

ลองใช้สิ่งนี้หลังจาก instantiateViewControllerWithIdentifier

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

ชอบ :

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

โปรดเพิ่มคำอธิบายที่เป็นประโยชน์เกี่ยวกับโค้ดของคุณ
codeforester

7
จะเกิดอะไรขึ้นหากคุณใช้ตัวแปรอินสแตนซ์จากคลาสย่อย ฉันเดาว่าขัดข้องเนื่องจากมีหน่วยความจำไม่เพียงพอที่จัดสรรให้พอดี ในการทดสอบของฉันฉันได้รับEXC_BAD_ACCESSดังนั้นไม่แนะนำสิ่งนี้
Legoless

1
สิ่งนี้จะไม่ได้ผลหากคุณจะเพิ่มตัวแปรใหม่ในคลาสย่อย และเด็กinitจะไม่ถูกเรียกด้วย ข้อ จำกัด ดังกล่าวทำให้แนวทางทั้งหมดใช้ไม่ได้
Al Zonke

6

โดยเฉพาะอย่างยิ่งในnickgzzjrและJiříZahálkaคำตอบและความคิดเห็นภายใต้ข้อที่สองจาก CocoaBob ฉันได้เตรียมวิธีการทั่วไปสั้น ๆ เพื่อทำสิ่งที่ OP ต้องการ คุณต้องตรวจสอบชื่อสตอรีบอร์ดและดูรหัสสตอรี่บอร์ดของคอนโทรลเลอร์เท่านั้น

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
    }

มีการเพิ่มตัวเลือกเพื่อหลีกเลี่ยงการแกะแรง (คำเตือนแบบรวดเร็ว) แต่วิธีการส่งคืนวัตถุที่ถูกต้อง


5

แม้ว่าจะไม่ใช่คลาสย่อยอย่างเคร่งครัด แต่คุณสามารถ:

  1. option-drag ตัวควบคุมมุมมองคลาสพื้นฐานในเค้าร่างเอกสารเพื่อทำสำเนา
  2. ย้ายตัวควบคุมมุมมองใหม่คัดลอกไปยังตำแหน่งอื่นบนกระดานเรื่องราว
  3. เปลี่ยนคลาสเป็นตัวควบคุมมุมมองคลาสย่อยใน Identity Inspector

นี่คือตัวอย่างจากแบบฝึกหัดBloc ที่ฉันเขียนโดยViewControllerมีคลาสย่อยด้วยWhiskeyViewController:

ภาพเคลื่อนไหวของสามขั้นตอนข้างต้น

สิ่งนี้ช่วยให้คุณสร้างคลาสย่อยของคลาสย่อยของตัวควบคุมมุมมองในกระดานเรื่องราว จากนั้นคุณสามารถใช้instantiateViewControllerWithIdentifier:เพื่อสร้างคลาสย่อยเฉพาะ

แนวทางนี้ไม่ยืดหยุ่นเล็กน้อย: การปรับเปลี่ยนในภายหลังภายในสตอรีบอร์ดไปยังคอนโทรลเลอร์คลาสพื้นฐานจะไม่เผยแพร่ไปยังคลาสย่อย หากคุณมีคลาสย่อยจำนวนมากคุณอาจจะทำได้ดีกว่าด้วยวิธีแก้ปัญหาอื่น ๆ แต่จะทำได้ในระยะสั้น ๆ


11
นี่ไม่ใช่เพื่อนร่วมคลาสย่อยนี่เป็นเพียงการทำซ้ำ ViewController
Ace Green

1
ไม่ถูกต้อง จะกลายเป็นคลาสย่อยเมื่อคุณเปลี่ยนคลาสเป็นคลาสย่อย (ขั้นตอนที่ 3) จากนั้นคุณสามารถทำการเปลี่ยนแปลงใด ๆ ที่คุณต้องการและเชื่อมต่อกับร้านค้า / การกระทำในคลาสย่อยของคุณ
Aaron Brager

6
ฉันไม่คิดว่าคุณจะได้รับแนวคิดเรื่องคลาสย่อย
Ace Green

5
หาก "การแก้ไขในภายหลังภายในสตอรีบอร์ดไปยังตัวควบคุมคลาสพื้นฐานไม่แพร่กระจายไปยังคลาสย่อย" จะไม่เรียกว่า "คลาสย่อย" คัดลอกและวาง
superarts.org

คลาสพื้นฐานที่เลือกใน Identity Inspector ยังคงเป็นคลาสย่อย อ็อบเจ็กต์ที่กำลังเริ่มต้นและควบคุมตรรกะทางธุรกิจยังคงเป็นคลาสย่อย เฉพาะข้อมูลมุมมองที่เข้ารหัสจัดเก็บเป็น XML ในไฟล์สตอรีบอร์ดและเริ่มต้นผ่านinitWithCoder:เท่านั้นไม่มีความสัมพันธ์ที่สืบทอดมา ไฟล์สตอรีบอร์ดไม่รองรับความสัมพันธ์ประเภทนี้
Aaron Brager

4

เมธอด objc_setclass ไม่สร้างอินสแตนซ์ของ childvc แต่ในขณะที่โผล่ออกมาจาก childvc deinit ของ childvc กำลังถูกเรียก เนื่องจากไม่มีการจัดสรรหน่วยความจำแยกต่างหากสำหรับ childvc แอปจึงขัดข้อง Basecontroller มีอินสแตนซ์ในขณะที่ child vc ไม่มี


2

หากคุณไม่พึ่งสตอรีบอร์ดมากเกินไปคุณสามารถสร้างไฟล์. xib แยกต่างหากสำหรับคอนโทรลเลอร์ได้

ตั้งค่าเจ้าของไฟล์และร้านค้าที่เหมาะสมเป็นMainViewControllerและแทนที่init(nibName:bundle:)ใน VC หลักเพื่อให้ลูก ๆ สามารถเข้าถึง Nib และร้านค้าเดียวกันได้

รหัสของคุณควรมีลักษณะดังนี้:

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

และ Child VC ของคุณจะสามารถใช้หัวปากกาของผู้ปกครองซ้ำได้:

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

2

หาคำตอบจากที่นี่และที่นั่นฉันได้หาวิธีแก้ปัญหาที่ประณีตนี้

สร้างตัวควบคุมมุมมองหลักด้วยฟังก์ชันนี้

class ParentViewController: UIViewController {


    func convert<T: ParentViewController>(to _: T.Type) {

        object_setClass(self, T.self)

    }

}

สิ่งนี้ช่วยให้คอมไพลเลอร์ตรวจสอบให้แน่ใจว่าตัวควบคุมมุมมองลูกสืบทอดมาจากตัวควบคุมมุมมองหลัก

จากนั้นเมื่อใดก็ตามที่คุณต้องการเชื่อมต่อกับคอนโทรลเลอร์นี้โดยใช้คลาสย่อยคุณสามารถทำได้:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    super.prepare(for: segue, sender: sender)

    if let parentViewController = segue.destination as? ParentViewController {
        ParentViewController.convert(to: ChildViewController.self)
    }

}

ส่วนที่น่าสนใจคือคุณสามารถเพิ่มการอ้างอิงสตอรีบอร์ดให้กับตัวมันเองจากนั้นจึงเรียกตัวควบคุมมุมมองเด็ก "ถัดไป" ต่อไป


1

วิธีที่ยืดหยุ่นที่สุดคือการใช้มุมมองที่ใช้ซ้ำได้

(สร้างมุมมองในไฟล์ XIB แยกต่างหากหรือContainer viewเพิ่มลงในฉากควบคุมมุมมองคลาสย่อยแต่ละฉากในสตอรี่บอร์ด)


1
กรุณาแสดงความคิดเห็นเมื่อคุณลงคะแนน ฉันรู้ว่าฉันไม่ได้ตอบคำถามนั้นโดยตรง แต่ฉันเสนอวิธีแก้ปัญหาสำหรับปัญหาที่แท้จริง
DanSkeel

1

มีวิธีง่ายๆที่ชัดเจนในชีวิตประจำวัน

เพียงใส่สตอรีบอร์ด / คอนโทรลเลอร์ที่มีอยู่ภายในสตอรี่บอร์ด / คอนโทรลเลอร์ใหม่ IE เป็นมุมมองคอนเทนเนอร์

นี่เป็นแนวคิดที่คล้ายคลึงกับ "subclassing" สำหรับดูคอนโทรลเลอร์

ทุกอย่างทำงานเหมือนกับในคลาสย่อย

เช่นเดียวกับที่คุณมักใส่ subview ในมุมมองอีกมุมมองหนึ่งธรรมชาติคุณมักใส่ตัวควบคุมมุมมองภายในตัวควบคุมอีกมุมมองหนึ่ง

คุณจะทำได้อย่างไร?

เป็นส่วนพื้นฐานของ iOS ง่ายๆเหมือนแนวคิด "มุมมองย่อย"

ง่ายขนาดนี้ ...

/*

Search screen is just a modification of our List screen.

*/

import UIKit

class Search: UIViewController {
    
    var list: List!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        list = (_sb("List") as! List
        addChild(list)
        view.addSubview(list.view)
        list.view.bindEdgesToSuperview()
        list.didMove(toParent: self)
    }
}

เห็นได้ชัดว่าตอนนี้คุณต้องlistทำอะไรก็ได้ที่คุณต้องการ

list.mode = .blah
list.tableview.reloadData()
list.heading = 'Search!'
list.searchBar.isHidden = false

ฯลฯ เป็นต้น

มุมมองคอนเทนเนอร์จะ "เหมือนกับ" คลาสย่อยในลักษณะเดียวกับที่ "มุมมองย่อย" เป็น "เหมือน" คลาสย่อย

เห็นได้ชัดว่าคุณไม่สามารถ "sublcass a layout" ได้นั่นหมายความว่าอย่างไร

("Subclassing" เกี่ยวข้องกับซอฟต์แวร์ OO และไม่มีการเชื่อมต่อกับ "เค้าโครง")

เห็นได้ชัดว่าเมื่อคุณต้องการใช้มุมมองซ้ำคุณเพียงแค่ดูย่อยในมุมมองอื่น

เมื่อคุณต้องการใช้เค้าโครงคอนโทรลเลอร์อีกครั้งคุณเพียงแค่ดูคอนเทนเนอร์ภายในคอนโทรลเลอร์อื่น

นี่เป็นเหมือนกลไกพื้นฐานที่สุดของ iOS !!


หมายเหตุ - เป็นเวลาหลายปีแล้วที่จะโหลดตัวควบคุมมุมมองอื่นแบบไดนามิกเป็นมุมมองคอนเทนเนอร์ อธิบายในส่วนสุดท้าย: https://stackoverflow.com/a/23403979/294884

หมายเหตุ - "_sb" เป็นเพียงมาโครที่เราใช้บันทึกการพิมพ์

func _sb(_ s: String)->UIViewController {
    // by convention, for a screen "SomeScreen.storyboard" the
    // storyboardID must be SomeScreenID
    return UIStoryboard(name: s, bundle: nil)
       .instantiateViewController(withIdentifier: s + "ID")
}

1

ขอบคุณสำหรับคำตอบที่สร้างแรงบันดาลใจของ @ JiříZahálkaฉันตอบวิธีแก้ปัญหาเมื่อ 4 ปีที่แล้วที่นี่แต่ @Sayka แนะนำให้ฉันโพสต์เป็นคำตอบดังนั้นนี่คือคำตอบ

ในโปรเจ็กต์ของฉันโดยปกติถ้าฉันใช้ Storyboard สำหรับคลาสย่อย UIViewController ฉันจะเตรียมเมธอดแบบคงที่ที่เรียกว่าinstantiate()ในคลาสย่อยนั้นเสมอเพื่อสร้างอินสแตนซ์จาก Storyboard ได้อย่างง่ายดาย ดังนั้นสำหรับการแก้ปัญหาของ OP หากเราต้องการแชร์ Storyboard เดียวกันสำหรับคลาสย่อยที่แตกต่างกันเราสามารถsetClass()ไปที่อินสแตนซ์นั้นก่อนที่จะส่งคืน

class func instantiate() -> SubClass {
    let instance = (UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("SuperClass") as? SuperClass)!
    object_setClass(instance, SubClass.self)
    return (instance as? SubClass)!
}

0

ความคิดเห็นของ Cocoabob จากคำตอบของJiříZahálkaช่วยให้ฉันได้รับโซลูชันนี้และได้ผลดี

func openChildA() {
    let storyboard = UIStoryboard(name: "Main", bundle: nil);
    let parentController = storyboard
        .instantiateViewController(withIdentifier: "ParentStoryboardID") 
        as! ParentClass;
    object_setClass(parentController, ChildA.self)
    self.present(parentController, animated: true, completion: nil);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.