วิธีโหลด UIView โดยใช้ไฟล์ปลายปากกาที่สร้างด้วย Interface Builder


241

ฉันพยายามทำบางสิ่งบางอย่างอย่างละเอียด แต่สิ่งที่ควรทำ ดังนั้นนี่คือความท้าทายสำหรับผู้เชี่ยวชาญทั้งหมดที่คุณมี (ฟอรัมนี้เป็นกลุ่มของพวกคุณจำนวนมาก :))

ฉันกำลังสร้าง "ส่วนประกอบ" ของแบบสอบถามซึ่งฉันต้องการโหลดNavigationContoller(ของฉันQuestionManagerViewController) "ส่วนประกอบ" คือ "ว่าง" UIViewControllerซึ่งสามารถโหลดมุมมองที่แตกต่างกันขึ้นอยู่กับคำถามที่ต้องตอบ

วิธีที่ฉันทำคือ:

  1. สร้างวัตถุ Question1View เป็นUIViewคลาสย่อยโดยกำหนดบางIBOutletsรายการ
  2. สร้าง (โดยใช้ตัวสร้างส่วนต่อประสาน) Question1View.xib (ที่นี่คือปัญหาของฉันที่เป็นปัญหา ) ฉันตั้งค่าทั้งสองUIViewControllerและUIViewเป็นของคลาส Question1View
  3. ฉันเชื่อมโยงร้านค้ากับองค์ประกอบของมุมมอง (โดยใช้ IB)
  4. ฉันแทนที่initWithNibของฉันQuestionManagerViewControllerจะมีลักษณะเช่นนี้:

    - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
        if (self = [super initWithNibName:@"Question1View" bundle:nibBundleOrNil]) {
            // Custom initialization
        }
        return self;
    }
    

เมื่อฉันเรียกใช้รหัสฉันได้รับข้อผิดพลาดนี้:

2009-05-14 15: 05: 37.152 iMobiDines [17148: 20b] *** การสิ้นสุดแอปเนื่องจากข้อยกเว้นที่ไม่ได้ตรวจสอบ ' NSInternalInconsistencyException' เหตุผล: ' -[UIViewController _loadViewFromNibNamed:bundle:]โหลด "คำถาม 1View" ปลายปากกา แต่ไม่มีการตั้งค่ามุมมอง'

ฉันแน่ใจว่ามีวิธีการโหลดมุมมองโดยใช้ไฟล์ปลายปากกาโดยไม่จำเป็นต้องสร้างคลาส viewController

คำตอบ:


224

นอกจากนี้ยังมีวิธีที่ง่ายกว่าในการเข้าถึงมุมมองแทนที่จะจัดการกับปลายปากกาเป็นอาร์เรย์

1 ) สร้างคลาสย่อย View ที่กำหนดเองด้วยช่องทางใด ๆ ที่คุณต้องการเข้าถึงในภายหลัง --มุมมองของฉัน

2 ) ใน UIViewController ที่คุณต้องการโหลดและจัดการปลายปากกาสร้างคุณสมบัติ IBOutlet ที่จะเก็บมุมมองของปากกาที่โหลดเป็นต้น

ใน MyViewController (คลาสย่อย UIViewController)

  @property (nonatomic, retain) IBOutlet UIView *myViewFromNib;

(อย่าลืมที่จะสังเคราะห์และปล่อยมันในไฟล์. m ของคุณ)

3)เปิดปลายปากกาของคุณ (เราจะเรียกมันว่า 'myViewNib.xib') ใน IB ตั้งค่าให้เจ้าของไฟล์เป็นMyViewController

4)เชื่อมต่อร้านค้าเจ้าของไฟล์ของคุณ myViewFromNib กับมุมมองหลักในปลายปากกา

5)ตอนนี้ใน MyViewController เขียนบรรทัดต่อไปนี้:

[[NSBundle mainBundle] loadNibNamed:@"myViewNib" owner:self options:nil];

ทันทีที่คุณทำเช่นนั้นการเรียกคุณสมบัติ "self.myViewFromNib" ของคุณจะช่วยให้คุณสามารถเข้าถึงมุมมองจากปลายปากกาของคุณ!


4
มันจะทำงานสำหรับมุมมองตัวควบคุมหลัก (self.view) หรือไม่ ด้วยเหตุผลบางอย่างฉันต้องโหลดมุมมองหลักจากปลายปากกาหลังจากวิธีการเริ่มต้นมาตรฐานคอนโทรลเลอร์ เหตุผล - โครงสร้างการแบ่งประเภทย่อยที่ซับซ้อน
Lukasz

@Lukasz คุณประสบความสำเร็จในการทำมันมากแค่ไหนถึงแม้ว่าฉันกำลังมองหาบางอย่างเช่นคุณ
Ajay Sharma

ขออภัยฉันอาจจะหนา แต่ฉันสับสนในจุดแรก ฉันจะสร้างช่องในคลาสย่อยของมุมมองที่กำหนดเองได้อย่างไร ฉันคิดว่าร้านค้ามีไว้สำหรับ UIViewControllers เท่านั้นหรือ
jowie

1
กรณีใดเมื่อมุมมองนี้ปรากฏในหลายมุมมองพาเรนต์ คุณเสนอให้แก้ไข xib สำหรับแต่ละรายการด้วยตนเองหรือไม่ มันเกินไป!
user2083364

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

120

ขอบคุณทุกคน. ฉันหาวิธีทำสิ่งที่ฉันต้องการ

  1. สร้างของคุณUIViewด้วยสิ่งIBOutletที่คุณต้องการ
  2. สร้าง xib ใน IB ออกแบบเพื่อให้คุณชอบและเชื่อมโยงมันเช่นนี้: เจ้าของไฟล์นั้นมีคลาสUIViewController(ไม่มีคลาสย่อยที่กำหนดเอง แต่เป็น "ของจริง") มุมมองของเจ้าของไฟล์เชื่อมต่อกับมุมมองหลักและคลาสถูกประกาศเป็นมุมมองจากขั้นตอนที่ 1)
  3. เชื่อมต่อส่วนควบคุมของคุณด้วยIBOutlets
  4. DynamicViewControllerสามารถเรียกใช้ตรรกะในการตัดสินใจเลือกสิ่งที่ดู / Xib โหลด เมื่อมันทำการตัดสินใจในloadViewวิธีการใส่บางสิ่งเช่นนี้:

    NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:@"QPickOneView"
                                                      owner:self
                                                    options:nil];
    
    QPickOneView* myView = [ nibViews objectAtIndex: 1];
    
    myView.question = question;
    

แค่นั้นแหละ!

วิธีการมัดหลักloadNibNamedจะดูแลการเริ่มต้นมุมมองและการสร้างการเชื่อมต่อ

ตอนนี้ ViewController สามารถแสดงมุมมองหรืออื่นขึ้นอยู่กับข้อมูลในหน่วยความจำและหน้าจอ "ผู้ปกครอง" ไม่จำเป็นต้องกังวลกับตรรกะนี้


2
ฉันมีปัญหาที่คล้ายกันมาก - ฉันแค่ต้องการโหลด UIView จากไฟล์ xib ขั้นตอนเหล่านี้ใช้ไม่ได้สำหรับฉัน - แอปขัดข้องด้วย "การเข้าถึงที่ไม่ดี" ในไม่ช้าหลังจากเพิ่มมุมมองที่โหลดเป็นมุมมองย่อย
Justicle

2
สำหรับ iPhone 3.0 ให้ใช้ objectAtIndex: 0 เพื่อรับองค์ประกอบแรก ปัญหานี้เกิดขึ้นกับฉันตามที่ Justicle อธิบายไว้ มีความคิดอะไรบ้าง
tba

1
+1 มันทำงานได้อย่างสมบูรณ์แบบสำหรับฉัน ฉันต้องการเพิ่มหลายมุมมองที่มีตัวควบคุมมุมมองเดียว (ฉันใช้สถานการณ์พลิกมุมมองที่ส่วนของมุมมองหมุน) ฉันต้องสร้างดัชนีในอาร์เรย์ 0 (iPhone 3.0)
Aran Mulholland

42
นี่คือคำตอบที่ถูกต้องอย่างไรก็ตามรหัสควรถูกแก้ไขเพื่อให้วนซ้ำผ่าน nibViews และทำการตรวจสอบคลาสในแต่ละวัตถุเพื่อให้คุณได้รับวัตถุที่ถูกต้องอย่างแน่นอน objectAtIndex: 1 เป็นข้อสันนิษฐานที่อันตราย
M. Ryan

2
จัสคอนเซียสถูกต้อง คุณควรวนซ้ำอาร์เรย์nibViews
leviathan

71

ฉันไม่แน่ใจว่าคำตอบบางคำพูดถึงอะไร แต่ฉันต้องใส่คำตอบนี้ไว้เมื่อฉันค้นหาใน Google ครั้งต่อไป คำสำคัญ: "วิธีโหลด UIView จากปลายปากกา" หรือ "วิธีโหลด UIView จาก NSBundle"

นี่คือรหัสตรงเกือบ 100% จากหนังสือ Apress Beginning iPhone 3 (หน้า 247, "การใช้เซลล์มุมมองตารางใหม่"):

- (void)viewDidLoad {
    [super viewDidLoad];
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:@"Blah"
                                                 owner:self options:nil];
    Blah *blah;
    for (id object in bundle) {
        if ([object isKindOfClass:[Blah class]]) {
            blah = (Blah *)object;
            break;
        }
    }   
    assert(blah != nil && "blah can't be nil");
    [self.view addSubview: blah];
} 

ซึมนี้คุณมีUIViewsubclass เรียกว่าBlah, ปลายปากกาที่เรียกว่าBlahซึ่งมีซึ่งมีชุดชั้นเลิศUIViewBlah


หมวดหมู่: NSObject + LoadFromNib

#import "NSObject+LoadFromNib.h"

@implementation NSObject (LoadFromNib)

+ (id)loadFromNib:(NSString *)name classToLoad:(Class)classToLoad {
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil];
    for (id object in bundle) {
        if ([object isKindOfClass:classToLoad]) {
            return object;
        }
    }
    return nil;
}

@end

ส่วนขยายที่รวดเร็ว

extension UIView {
    class func loadFromNib<T>(withName nibName: String) -> T? {
        let nib  = UINib.init(nibName: nibName, bundle: nil)
        let nibObjects = nib.instantiate(withOwner: nil, options: nil)
        for object in nibObjects {
            if let result = object as? T {
                return result
            }
        }
        return nil
    }
}

และตัวอย่างการใช้งาน:

class SomeView: UIView {
    class func loadFromNib() -> SomeView? {
        return self.loadFromNib(withName: "SomeView")
    }
}

2
หากคุณใช้isKindOfคุณจะได้รับการคุ้มครองจากการเปลี่ยนแปลงโครงสร้างของ Bundle
Dan Rosenstark

1
assertจะอาจจะเป็นทางออกที่ดีกว่า วิธีนี้คุณจะซ่อนปัญหาเท่านั้น
Sulthan

1
@Sulthan ยืนยันความแตกต่างโดยเนื้อแท้ ฉันต้องการวัตถุประเภท Blah ใดก็ตามที่มันตั้งอยู่ในปลายปากกา ฉันไม่สนใจว่าจะได้รับการส่งเสริมหรือลดระดับ อย่างไรก็ตามเพื่อเป็นการตอบสนองต่อความคิดเห็นของคุณฉันได้เพิ่มการยืนยัน แต่มันไม่ได้แทนที่forลูป มันเติมเต็มมัน
Dan Rosenstark

คุณสามารถทำ loadFromNib โดยไม่มีพารามิเตอร์: + (id) loadFromNib {NSArray * bundle = [[NSBundle mainBundle] loadNibNamed: เจ้าของ NSStringFromClass ([ระดับตนเอง]): ตัวเลือกด้วยตนเอง: ไม่มี สำหรับ (วัตถุ id ในห่อ) {ถ้า ([วัตถุ isKindOfClass: [ตนเองระดับ]]) {วัตถุกลับ; }} คืนค่าศูนย์; }
JVC

22

สำหรับทุกคนที่ต้องการจัดการมุมมองที่กำหนดเองมากกว่าหนึ่งอินสแตนซ์นั่นคือOutlet Collectionฉันได้รวมและปรับแต่ง @Gonso, @AVeryDev และ @Olie ด้วยวิธีนี้:

  1. สร้าง custom MyView : UIViewและตั้งเป็น " Custom Class " ของรูทUIViewในXIBที่ต้องการ; คลาสที่กำหนดเอง

  2. สร้างร้านค้าทั้งหมดที่คุณต้องการMyView(ทำตอนนี้เพราะหลังจากจุดที่ 3 แล้ว IB จะเสนอให้คุณเชื่อมต่อร้านค้ากับUIViewControllerและไม่ใช่มุมมองที่กำหนดเองตามที่เราต้องการ); เต้าเสียบระดับที่กำหนดเอง

  3. ตั้งค่าของคุณUIViewControllerเป็น " เจ้าของไฟล์ " ของXIBมุมมองที่กำหนดเอง; ป้อนคำอธิบายรูปภาพที่นี่

  4. ในการUIViewControllerเพิ่มใหม่UIViewsสำหรับแต่ละอินสแตนซ์ที่MyViewคุณต้องการและเชื่อมต่อพวกเขาเพื่อUIViewControllerสร้างOutlet Collection : มุมมองเหล่านี้จะทำหน้าที่เป็นมุมมอง "wrapper" สำหรับอินสแตนซ์มุมมองที่กำหนดเอง ป้อนคำอธิบายรูปภาพที่นี่

  5. สุดท้ายในการเพิ่มบรรทัดต่อไปนี้viewDidLoadของคุณUIViewController:

NSArray *bundleObjects;
MyView *currView;
NSMutableArray *myViews = [NSMutableArray arrayWithCapacity:myWrapperViews.count];
for (UIView *currWrapperView in myWrapperViews) {
    bundleObjects = [[NSBundle mainBundle] loadNibNamed:@"MyView" owner:self options:nil];
    for (id object in bundleObjects) {
        if ([object isKindOfClass:[MyView class]]){
            currView = (MyView *)object;
            break;
        }
    }

    [currView.myLabel setText:@"myText"];
    [currView.myButton setTitle:@"myTitle" forState:UIControlStateNormal];
    //...

    [currWrapperView addSubview:currView];
    [myViews addObject:currView];
}
//self.myViews = myViews; if need to access them later..

สวัสดี ... คุณรู้วิธีการทำเช่นนี้ทั้งหมดโดยทางโปรแกรม ไม่ได้ใช้ไฟล์ xib เลยเหรอ?
X-Coder

19

ฉันจะใช้ UINib เพื่อยกตัวอย่าง UIView ที่กำหนดเองเพื่อนำกลับมาใช้ใหม่

UINib *customNib = [UINib nibWithNibName:@"MyCustomView" bundle:nil];
MyCustomViewClass *customView = [[customNib instantiateWithOwner:self options:nil] objectAtIndex:0];
[self.view addSubview:customView];

ไฟล์ที่จำเป็นในกรณีนี้คือMyCustomView.xib , MyCustomViewClass.hและMyCustomViewClass.m โปรดทราบว่า[UINib instantiateWithOwner]จะส่งคืนอาร์เรย์ดังนั้นคุณควรใช้องค์ประกอบที่สะท้อนถึง UIView ที่คุณต้องการใช้ซ้ำ ในกรณีนี้มันเป็นองค์ประกอบแรก


1
คุณไม่หมายถึง "customNib" ในบรรทัด 2 แทนที่จะเป็น "rankNib"
Danny

11

คุณไม่ควรตั้งค่าคลาสของตัวควบคุมมุมมองของคุณให้เป็นคลาสย่อยUIViewใน Interface Builder นั่นคือปัญหาของคุณอย่างน้อยที่สุดแน่นอน ปล่อยให้เป็นUIViewControllerคลาสย่อยบางคลาสหรือคลาสแบบกำหนดเองอื่น ๆ ที่คุณมี

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

หากคุณใช้UIViewControllerเป็นเจ้าของไฟล์ของคุณสำหรับมุมมองของคุณคุณสามารถใช้initWithNibName:bundle:เพื่อโหลดและรับวัตถุตัวควบคุมมุมมองกลับมา ใน IB ตรวจสอบให้แน่ใจว่าคุณตั้งค่าviewเต้าเสียบให้เป็นมุมมองด้วยอินเทอร์เฟซของคุณใน xib ถ้าคุณใช้บางประเภทอื่น ๆ ของวัตถุเป็นไฟล์ของคุณเจ้าของคุณจะต้องใช้NSBundle's loadNibNamed:owner:options:วิธีการโหลดปลายปากกาผ่านตัวอย่างของไฟล์ของเจ้าของไปที่วิธีการ คุณสมบัติทั้งหมดจะถูกตั้งค่าอย่างถูกต้องตามร้านค้าที่คุณกำหนดใน IB


ฉันกำลังอ่านหนังสือ Apress "การเริ่มต้นพัฒนา iPhone: สำรวจ iPhone SDK" และพวกเขาก็พูดถึงวิธีการนี้ในบทที่ 9: ตัวควบคุมการนำทางและมุมมองตาราง ดูเหมือนจะไม่ซับซ้อนเกินไป
Lyndsey Ferguson

ฉันอยากรู้ว่าพวกเขาพูดว่าทำอย่างไร ฉันยังไม่มีสำเนาของหนังสือเล่มนั้นในตอนนี้
Marc W

10

คุณยังสามารถใช้ initWithNibName ของ UIViewController แทน loadNibNamed มันง่ายกว่าฉันเจอ

UIViewController *aViewController = [[UIViewController alloc] initWithNibName:@"MySubView" bundle:nil];
[self.subview addSubview:aViewController.view];
[aViewController release];  // release the VC

ตอนนี้คุณเพียงแค่ต้องสร้าง MySubView.xib และ MySubView.h / m ใน MySubView.xib ให้ตั้งค่าระดับเจ้าของไฟล์เป็น UIViewController และดูคลาสเป็น MySubView

คุณสามารถวางตำแหน่งและขนาดของการsubviewใช้ไฟล์ xib แม่


8

นี่เป็นคำถามที่ยอดเยี่ยม (+1) และคำตอบนั้นมีประโยชน์เกือบ ); ขอโทษด้วยนะ แต่ฉันมีเวลาหกล้มผ่านสิ่งนี้ถึงแม้ว่าทั้ง Gonso และ AVeryDev จะให้คำแนะนำที่ดี หวังว่าคำตอบนี้จะช่วยผู้อื่น

MyVC เป็นตัวควบคุมมุมมองที่เก็บสิ่งนี้ทั้งหมด

MySubview เป็นมุมมองที่เราต้องการโหลดจาก xib

  • ใน MyVC.xib ให้สร้างประเภทมุมมองMySubViewที่มีขนาด & รูปร่างที่ถูกต้องและวางตำแหน่งที่คุณต้องการ
  • ใน MyVC.h มี

    IBOutlet MySubview *mySubView
    // ...
    @property (nonatomic, retain) MySubview *mySubview;
  • ใน MyVC.m, และไม่ลืมที่จะปล่อยมันใน@synthesize mySubView;dealloc

  • ใน MySubview.h มีร้านค้า / สถานที่ให้บริการสำหรับUIView *view(อาจไม่จำเป็น แต่ทำงานสำหรับฉัน) สังเคราะห์และปล่อยเป็น. m
  • ใน MySubview.xib
    • กำหนดประเภทเจ้าของไฟล์เป็นMySubviewและเชื่อมโยงคุณสมบัติมุมมองกับมุมมองของคุณ
    • จัดวางบิตทั้งหมดและเชื่อมต่อกับIBOutletของตามที่ต้องการ
  • ย้อนกลับไปใน MyVC.m ได้

    NSArray *xibviews = [[NSBundle mainBundle] loadNibNamed: @"MySubview" owner: mySubview options: NULL];
    MySubview *msView = [xibviews objectAtIndex: 0];
    msView.frame = mySubview.frame;
    UIView *oldView = mySubview;
    // Too simple: [self.view insertSubview: msView aboveSubview: mySubview];
    [[mySubview superview] insertSubview: msView aboveSubview: mySubview]; // allows nesting
    self.mySubview = msView;
    [oldCBView removeFromSuperview];

บิตที่ยุ่งยากสำหรับฉันคือ: คำแนะนำในคำตอบอื่น ๆ โหลดมุมมองของฉันจาก xib แต่ไม่ได้แทนที่มุมมองใน MyVC (duh!) - ฉันต้องสลับมันด้วยตัวเอง

นอกจากนี้ยังได้รับการเข้าถึงmySubviewวิธีการ 's ที่viewคุณสมบัติในไฟล์ .xib MySubviewจะต้องมีการตั้งค่าให้ UIViewมิฉะนั้นจะกลับมาเป็นธรรมดาเก่ามา

หากมีวิธีโหลดmySubviewโดยตรงจาก xib ของตัวเองมันจะสั่นสะเทือน แต่สิ่งนี้ทำให้ฉันได้ในที่ที่ฉันต้องการ


1
ฉันใช้วิธีนี้ขอบคุณ การเปลี่ยนแปลงหนึ่งอย่างที่ฉันทำเพื่ออนุญาตให้รองรับมุมมองแบบซ้อนคือเปลี่ยน [ self.view insertSubview: msView ด้านบนSubview: mySubview]; ถึง [ mySubview.superview insertSubview: msView ด้านบนSubview: mySubview];
Jason

8

นี่คือสิ่งที่ควรจะง่ายกว่า ฉันสิ้นสุดการขยายUIViewControllerและเพิ่มloadNib:inPlaceholder:ตัวเลือก ตอนนี้ฉันสามารถพูดได้

self.mySubview = (MyView *)[self loadNib:@"MyView" inPlaceholder:mySubview];

นี่คือรหัสสำหรับหมวดหมู่ (มันทำหน้าที่เหมือน rigamarole เดียวกันกับที่อธิบายโดย Gonso):

@interface UIViewController (nibSubviews)

- (UIView *)viewFromNib:(NSString *)nibName;
- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder;

@end

@implementation UIViewController (nibSubviews)

- (UIView *)viewFromNib:(NSString *)nibName
{
  NSArray *xib = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil]; 
  for (id view in xib) { // have to iterate; index varies
    if ([view isKindOfClass:[UIView class]]) return view;
  }
  return nil;
}

- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder
{
  UIView *nibView = [self viewFromNib:nibName];
  [nibView setFrame:placeholder.frame];
  [self.view insertSubview:nibView aboveSubview:placeholder];
  [placeholder removeFromSuperview];
  return nibView;
}

@end

7

อย่างรวดเร็ว

ที่จริงการแก้ไขปัญหานี้ของฉันคือการโหลดมุมมองใน viewDidLoad ใน CustonViewController ของฉันที่ฉันต้องการใช้มุมมองดังนี้:

myAccessoryView = NSBundle.mainBundle().loadNibNamed("MyAccessoryView", owner: self, options: nil)[0] as! MyAccessoryView

อย่าโหลดมุมมองด้วยloadView()วิธีใดวิธีหนึ่ง! วิธี loadView ทำหน้าที่สำหรับการโหลดมุมมองสำหรับ ViewController ที่กำหนดเองของคุณ


6

ฉันก็อยากจะทำสิ่งที่คล้ายกันนี่คือสิ่งที่ฉันพบ: (SDK 3.1.3)

ฉันมีตัวควบคุมมุมมอง A (เป็นเจ้าของโดยตัวควบคุม Nav) ซึ่งโหลด VC B บนการกดปุ่ม:

ใน AViewController.m

BViewController *bController = [[BViewController alloc] initWithNibName:@"Bnib" bundle:nil];
[self.navigationController pushViewController:bController animated:YES];
[bController release];

ตอนนี้ VC B มีส่วนต่อประสานจาก Bnib แต่เมื่อกดปุ่มฉันต้องการไปที่ 'โหมดแก้ไข' ซึ่งมี UI แยกต่างหากจากปลายปากกาที่แตกต่างกัน แต่ฉันไม่ต้องการ VC ใหม่สำหรับโหมดแก้ไข ฉันต้องการให้ไส้ปากกาใหม่เชื่อมโยงกับ B VC ปัจจุบันของฉัน

ดังนั้นใน BViewController.m (ในวิธีการกดปุ่ม)

NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:@"EditMode" owner:self options:nil];
UIView *theEditView = [nibObjects objectAtIndex:0];
self.editView = theEditView;
[self.view addSubview:theEditView];

จากนั้นกดปุ่มอีกปุ่มหนึ่ง (เพื่อออกจากโหมดแก้ไข):

[editView removeFromSuperview];

และฉันกลับไปที่ Bnib ดั้งเดิมของฉัน

ใช้งานได้ดี แต่ EditMode.nib ของฉันมี obj ระดับบนสุดเพียง 1 อัน UIView obj ไม่สำคัญว่าเจ้าของไฟล์ในปลายปากกานี้จะถูกตั้งค่าเป็น BViewController หรือค่าเริ่มต้น NSObject แต่จะต้องตรวจสอบให้แน่ใจว่า View Outlet ในเจ้าของไฟล์นั้นไม่ได้ถูกตั้งค่าเป็นอะไร ถ้าเป็นเช่นนั้นฉันจะได้รับความผิดพลาด exc_bad_access และ xcode ดำเนินการโหลดเฟรมสแต็ก 6677 ที่แสดงวิธี UIView ภายในที่เรียกซ้ำ ๆ กันว่า ... ดังนั้นดูเหมือนว่าวงวนไม่สิ้นสุด (The View Outlet IS ตั้งอยู่ใน Bnib ดั้งเดิมของฉัน)

หวังว่านี่จะช่วยได้


การอ่านระหว่างบรรทัดสิ่งนี้ช่วยฉันด้วย
เตอร์

ตามข้อมูลที่ลิงค์คุณไม่ควรจัดการกับตัวควบคุมมุมมองด้วยวิธีนี้
T. Markle

6

ฉันทำหมวดหมู่ที่ฉันชอบ:

UIView+NibInitializer.h

#import <UIKit/UIKit.h>

@interface UIView (NibInitializer)
- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil;
@end

UIView+NibInitializer.m

#import "UIView+NibInitializer.h"

@implementation UIView (NibInitializer)

- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
    if (!nibNameOrNil) {
        nibNameOrNil = NSStringFromClass([self class]);
    }
    NSArray *viewsInNib = [[NSBundle mainBundle] loadNibNamed:nibNameOrNil
                                                        owner:self
                                                      options:nil];
    for (id view in viewsInNib) {
        if ([view isKindOfClass:[self class]]) {
            self = view;
            break;
        }
    }
    return self;
}

@end

จากนั้นเรียกสิ่งนี้:

MyCustomView *myCustomView = [[MyCustomView alloc] initWithNibNamed:nil];

ใช้ชื่อปลายปากกาหากปากกาของคุณมีชื่ออย่างอื่นที่ไม่ใช่ชื่อของคลาสของคุณ

หากต้องการแทนที่ในคลาสย่อยของคุณสำหรับพฤติกรรมเพิ่มเติมอาจมีลักษณะเช่นนี้:

- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
    self = [super initWithNibNamed:nibNameOrNil];
    if (self) {
        self.layer.cornerRadius = CGRectGetHeight(self.bounds) / 2.0;
    }
    return self;
}

5

สำหรับผู้ใช้ Swift พร้อมตัวเลือกที่ออกแบบได้:

  1. สร้างUIViewคลาสย่อยที่กำหนดเองและไฟล์ xibซึ่งเราจะตั้งชื่อตามชื่อคลาสของเรา: ในกรณี MemeView ของเรา ภายในคลาส Meme View อย่าลืมกำหนดว่าสามารถออกแบบได้พร้อมกับ@IBDesignableแอตทริบิวต์ก่อนการประกาศคลาส
  2. โปรดจำไว้ว่าให้ตั้งค่าเจ้าของไฟล์ใน xib ด้วยUIViewคลาสย่อยที่กำหนดเองของเราในแผงควบคุมการตรวจสอบ Indetity

    ป้อนคำอธิบายรูปภาพที่นี่

  3. ในไฟล์ xib ตอนนี้เราสามารถสร้างส่วนต่อประสานของเราสร้างข้อ จำกัด สร้างช่องทางดำเนินการ ฯลฯ ป้อนคำอธิบายรูปภาพที่นี่

  4. เราจำเป็นต้องใช้วิธีการบางอย่างกับคลาสที่กำหนดเองของเราเพื่อเปิด xib เมื่อเริ่มต้น

    class XibbedView: UIView {

    weak var nibView: UIView!
    
    override convenience init(frame: CGRect) {
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        self.init(nibName: nibName)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        let nib = loadNib(nibName)
        nib.frame = bounds
        nib.translatesAutoresizingMaskIntoConstraints = false
        addSubview(nib)
        nibView = nib
        setUpConstraints()
    }
    
    init(nibName: String) {
        super.init(frame: CGRectZero)
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        let nib = loadNib(nibName)
        nib.frame = bounds
        nib.translatesAutoresizingMaskIntoConstraints = false
        addSubview(nib)
        nibView = nib
        setUpConstraints()
    }
    
    func setUpConstraints() {
        ["V","H"].forEach { (quote) -> () in
            let format = String(format:"\(quote):|[nibView]|")
            addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(format, options: [], metrics: nil, views: ["nibView" : nibView]))
        }
    }
    
    func loadNib(name: String) -> UIView {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: name, bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
    
        return view
    }

    }

  5. ในคลาสที่กำหนดเองของเราเรายังสามารถกำหนดคุณสมบัติที่ตรวจสอบได้บางอย่างเพื่อให้สามารถควบคุมได้อย่างสมบูรณ์จากเครื่องมือสร้างอินเตอร์เฟส

    @IBDesignable class MemeView: XibbedView {

    @IBInspectable var memeImage: UIImage = UIImage() {
        didSet {
            imageView.image = memeImage
        }
    }
    @IBInspectable var textColor: UIColor = UIColor.whiteColor() {
        didSet {
            label.textColor = textColor
        }
    }
    @IBInspectable var text: String = "" {
        didSet {
            label.text = text
        }
    }
    @IBInspectable var roundedCorners: Bool = false {
        didSet {
            if roundedCorners {
                layer.cornerRadius = 20.0
                clipsToBounds = true
            }
            else {
                layer.cornerRadius = 0.0
                clipsToBounds = false
            }
        }
    }
    
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var imageView: UIImageView!

}

ตัวอย่างไม่กี่:
ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่

หากเราต้องการเพิ่มข้อมูลมุมมองในขณะที่แสดงอยู่ภายในกระดานเรื่องราวหรือ xib อื่นเพื่อให้เราสามารถใช้งานได้prepareForInterfaceBuilder()วิธีนี้จะถูกเรียกใช้งานเฉพาะในขณะที่เปิดไฟล์ในตัวสร้างส่วนต่อประสาน หากคุณทำทุกอย่างที่ฉันเขียน แต่ไม่มีอะไรทำงานนี่เป็นวิธีการดีบั๊กมุมมอง sigle โดยเพิ่มจุดพักในการนำไปใช้ นี่คือลำดับชั้นมุมมอง ป้อนคำอธิบายรูปภาพที่นี่
ดู hiearchy

หวังว่านี่จะช่วยดาวน์โหลดตัวอย่างทั้งหมดได้ที่นี่


บทความเต็มสามารถดูได้จากบล็อกของฉันcloudintouch.it/2016/04/21/designable-xib-in-xcode-hell-yeahบล็อกของฉันแต่ฉันโพสต์ส่วนที่เกี่ยวข้องแล้ว
Andrea

let nib = loadNib(nibName)นี่เป็นอินสแตนซ์ XibbedView ถ้าคุณเรียกใช้ `` `addSubview (ปลายปากกา)` `'คุณจะเพิ่มอินสแตนซ์ XibbedView หนึ่งอินสแตนซ์ลงในอินสแตนซ์ XibbedView อีกอันหนึ่งหรือไม่
William Hu

ฉันเห็นด้วยกับประเด็นนี้ เมื่อคุณพยายามที่จะเห็นมันจาก "ลำดับชั้นมุมมองการดีบัก" ดูเหมือนว่า XibbedView มี XibbedView คุณสามารถเห็นมัน
William Hu

@WilliamHu ถ้าคุณไม่ใส่ตัวอย่างให้คุณเห็นว่า MemeView.xib เป็นเพียงตัวอย่างของ UIView ที่มีกลุ่มย่อยของ subviews ผู้ถือแฟ้มคือ MemeView ที่เป็นคลาสย่อยของ XibbedView ดังนั้นอินสแตนซ์เดียวของ XibbedView เป็นเพียง MemeView ที่โหลดไฟล์ xib โดยที่มุมมองหลักเป็นเพียงมุมมอง
Andrea

โอเคแล้ว ฉันจะทดสอบมัน ขอบคุณ!
William Hu

4

ฉันพบบล็อกนี้โพสต์โดย Aaron Hillegass (ผู้แต่งผู้สอนผู้สอนนินจาโกโก้) ให้ความรู้แจ้งอย่างมาก แม้ว่าคุณจะไม่ใช้วิธีการที่ได้รับการดัดแปลงของเขาในการโหลดไฟล์ NIB ผ่านเครื่องมือกำหนดค่าเริ่มต้นคุณอาจจะเข้าใจกระบวนการที่เกิดขึ้นเป็นอย่างน้อย ฉันใช้วิธีนี้เมื่อเร็ว ๆ นี้เพื่อความสำเร็จที่ยิ่งใหญ่!


ลิงค์เสียชีวิต ฉันเชื่อว่าตอนนี้อยู่ที่bignerdranch.com/blog/…
joelliusp

3

คำตอบก่อนหน้านี้ไม่ได้คำนึงถึงการเปลี่ยนแปลงในโครงสร้าง NIB (XIB) ที่เกิดขึ้นระหว่าง 2.0 ถึง 2.1 ของ iPhone SDK เนื้อหาของผู้ใช้เริ่มต้นที่ดัชนี 0 แทนที่จะเป็น 1

คุณสามารถใช้มาโคร 2.1 ซึ่งใช้ได้กับทุกเวอร์ชั่น 2.1 ขึ้นไป (นั่นคือขีดล่างสองอันก่อน IPHONE:

 // Cited from previous example
 NSArray* nibViews =  [[NSBundle mainBundle] loadNibNamed:@"QPickOneView" owner:self options:nil];
 int startIndex;
 #ifdef __IPHONE_2_1
 startIndex = 0;
 #else
 startIndex = 1;
 #endif
 QPickOneView* myView = [ nibViews objectAtIndex: startIndex];
 myView.question = question;

เราใช้เทคนิคที่คล้ายกับสิ่งนี้สำหรับการใช้งานส่วนใหญ่ของเรา

บาร์นีย์


2
Barney: "คำตอบก่อนหน้านี้" ไม่มีความหมายต่อ Stack Overflow เพราะคำตอบจะเปลี่ยนจากการโหวต ดีกว่าที่จะอ้างถึง "คำตอบของ Fred" หรือ "คำตอบของ Barney" หรือ "คำตอบของ olie"
Olie

3

ฉันมีเหตุผลที่จะทำสิ่งเดียวกัน (โดยทางโปรแกรมโหลดมุมมองจากไฟล์ XIB) แต่ฉันต้องทำสิ่งนี้ทั้งหมดจากบริบทของคลาสย่อยของคลาสย่อยของ a UIView(เช่นโดยไม่เกี่ยวข้องกับตัวควบคุมมุมมองในทางใดทางหนึ่ง) เมื่อต้องการทำสิ่งนี้ฉันได้สร้างวิธีอรรถประโยชน์นี้:

+ (id) initWithNibName:(NSString *)nibName withSelf:(id)myself {

    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:nibName
                                                    owner:myself options:nil];
    for (id object in bundle) {
        if ([object isKindOfClass:[myself class]]) {
            return object;
        }
    }  

    return nil;
} 

แล้วฉันจะเรียกมันจากinitWithFrameวิธีsubclass ของฉันเช่น:

- (id)initWithFrame:(CGRect)frame {

    self = [Utilities initWithNibName:@"XIB1" withSelf:self];
    if (self) {
        // Initialization code.
    }
    return self;
}

โพสต์เพื่อประโยชน์ทั่วไป; หากใครเห็นปัญหาโดยไม่ทำเช่นนี้โปรดแจ้งให้เราทราบ


ฉันได้ติดตั้งรหัสของคุณแล้ว ฉันสร้างเมธอดสแตติกของคุณในคลาส Utilities ฉันสร้างไฟล์ h / m ชื่อ MyView (subclassed จาก UIView) และ xib ชื่อเดียวกัน ใน IB ฉันตั้งเจ้าของไฟล์ xib และมุมมองเป็น "MyView" ในตัวดีบั๊กมันอยู่นอกขอบเขต ฉันขาดอะไรใน IB หรือเปล่า
TigerCoding

โปรดดูคำถาม SO ใหม่ของฉันที่นี่: stackoverflow.com/questions/9224857/…
TigerCoding

self = [โปรแกรมอรรถประโยชน์ initWithNibName: @ "XIB1" ด้วยตนเอง: self]; คุณกำลังผ่านตัวเองเมื่อยังไม่ได้ตั้งค่า รหัสดังกล่าวไม่เหมือนกับสิ่งนี้: self = [Utilities initWithNibName: @ "XIB1" withSelf: nil]; ?
Ken Van Hoeylandt

@Javy: ตัวอย่างรหัสของฉันที่นี่อาจไม่ทำงานกับ ARC - ฉันไม่ได้ทดสอบด้วยการเปิดใช้งาน ARC
MusiGenesis

3

นี่คือวิธีการทำใน Swift (ปัจจุบันเขียน Swift 2.0 ใน XCode 7 เบต้า 5)

จากUIViewคลาสย่อยของคุณที่คุณตั้งเป็น "Custom Class" ในเครื่องมือสร้างส่วนต่อประสานสร้างวิธีการเช่นนี้ (คลาสย่อยของฉันเรียกว่า RecordingFooterView):

class func loadFromNib() -> RecordingFooterView? {
    let nib = UINib(nibName: "RecordingFooterView", bundle: nil)
    let nibObjects = nib.instantiateWithOwner(nil, options: nil)
    if nibObjects.count > 0 {
        let topObject = nibObjects[0]
        return topObject as? RecordingFooterView
    }
    return nil
}

จากนั้นคุณสามารถเรียกมันว่า:

let recordingFooterView = RecordingFooterView.loadFromNib()


2

ไม่มีคำตอบใดที่อธิบายถึงวิธีการสร้าง XIB แบบสแตนด์อะโลนซึ่งเป็นรากของคำถามนี้ ไม่มีXcode 4ตัวเลือก "สร้างไฟล์ XIB ใหม่"

เพื่อทำสิ่งนี้

1) Choose "New File..."
2) Choose the "User Interface" category under the iOS section
3) Choose the "View" item
4) You will then be prompted to choose an iPhone or iPad format

สิ่งนี้อาจดูเรียบง่าย แต่สามารถช่วยให้คุณประหยัดเวลาได้ไม่กี่นาทีเนื่องจากคำว่า "XIB" ไม่ปรากฏที่ใดก็ได้


1

@AVeryDev

6)เมื่อต้องการแนบมุมมองที่โหลดไปยังมุมมองของตัวควบคุมมุมมองของคุณ:

[self.view addSubview:myViewFromNib];

สันนิษฐานว่ามีความจำเป็นต้องลบออกจากมุมมองเพื่อหลีกเลี่ยงการรั่วไหลของหน่วยความจำ

ในการชี้แจง: ตัวควบคุมมุมมองมี IBOutlets หลายตัวบางตัวเชื่อมต่อกับรายการในไฟล์ปลายปากกาดั้งเดิม (ตามปกติ) และบางตัวเชื่อมต่อกับรายการในปลายปากกาที่โหลด ปลายปากกาทั้งสองมีระดับเจ้าของเหมือนกัน มุมมองที่โหลดจะซ้อนทับมุมมองดั้งเดิม

คำแนะนำ: ตั้งค่าความทึบของมุมมองหลักในปลายปากกาที่โหลดให้เป็นศูนย์จากนั้นจะไม่ปิดบังรายการจากปลายปากกาต้นฉบับ


สิ่งที่ดีที่ควรระวัง แต่การเพิ่มมุมมองเป็นมุมมองย่อยจะลบออกจากพาเรนต์ก่อนหน้าโดยอัตโนมัติดังนั้นจึงไม่ควรรั่วไหล
averydev

1

หลังจากใช้เวลาหลายชั่วโมงฉันก็ออกมาแก้ปัญหาต่อไปนี้ UIViewทำตามขั้นตอนเหล่านี้เพื่อสร้างที่กำหนดเอง

1) การสร้างระดับmyCustomViewสืบทอดมาจากUIView
ป้อนคำอธิบายรูปภาพที่นี่

2) การสร้าง.xibมีชื่อmyCustomView
ป้อนคำอธิบายรูปภาพที่นี่

3) เปลี่ยน Class ของUIViewภายในไฟล์. xib ของคุณกำหนดmyCustomView Class ที่นั่น
ป้อนคำอธิบายรูปภาพที่นี่

4) สร้างIBOutlets
ป้อนคำอธิบายรูปภาพที่นี่

5) โหลด .xib ในmyCustomView * customView ใช้รหัสตัวอย่างต่อไปนี้

myCustomView * myView = [[[NSBundle mainBundle] loadNibNamed:@"myCustomView" owner:self options:nil] objectAtIndex:0];
[myView.description setText:@"description"];

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



0

ฉันมีแบบแผนของการตั้งชื่อ xibs ที่มีมุมมองเหมือนกับมุมมอง เช่นเดียวกับที่ทำสำหรับตัวควบคุมมุมมอง จากนั้นฉันไม่ต้องเขียนชื่อคลาสในรหัส ฉันโหลด UIView จากไฟล์ปลายปากกาด้วยชื่อเดียวกัน

ตัวอย่างคลาสที่เรียกว่า MyView

  • สร้างไฟล์ปลายปากกาที่ชื่อว่า MyView.xib ใน Interface Builder
  • ในเครื่องมือสร้างส่วนต่อประสานเพิ่ม UIView ตั้งค่าระดับเป็น MyView ปรับแต่งให้เข้ากับเนื้อหาหัวใจของคุณวางสายอินสแตนซ์ตัวแปรของ MyView เพื่อดูพรีวิวย่อยที่คุณอาจต้องการเข้าถึงในภายหลัง
  • ในรหัสของคุณสร้าง MyView ใหม่เช่นนี้:

    MyView * myView = [MyView nib_viewFromNibWithOwner: owner];

นี่คือหมวดหมู่สำหรับสิ่งนี้:

@implementation UIView (nib)

+ (id) nib_viewFromNib {
    return [self nib_viewFromNibWithOwner:nil];
}

+ (id) nib_viewFromNibWithOwner:(id)owner {
    NSString *className = NSStringFromClass([self class]);
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:className owner:owner options:nil];
    UIView *view = nil;
    for(UIView *v in nib) {
        if ([v isKindOfClass:[self class]]) {
            view = v;
            break;
        }
    }
    assert(view != nil && "View for class not found in nib file");
    [view nib_viewDidLoad];
    return view;
}

// override this to do custom setup
-(void)nib_viewDidLoad {

}

จากนั้นฉันจะวางสายปุ่มต่างๆโดยใช้แอคชั่นจากคอนโทรลเลอร์ที่ฉันใช้และตั้งสิ่งต่าง ๆ บนฉลากโดยใช้ช่องเสียบในคลาสย่อยของมุมมองที่กำหนดเอง


0

ในการโหลดมุมมองจาก nib / xib โดยทางโปรแกรมใน Swift 4:

// Load a view from a Nib given a placeholder view subclass
//      Placeholder is an instance of the view to load.  Placeholder is discarded.
//      If no name is provided, the Nib name is the same as the subclass type name
//
public func loadViewFromNib<T>(placeholder placeholderView: T, name givenNibName: String? = nil) -> T {

    let nib = loadNib(givenNibName, placeholder: placeholderView)
    return instantiateView(fromNib: nib, placeholder: placeholderView)
}

// Step 1: Returns a Nib
//
public func loadNib<T>(_ givenNibName: String? = nil, placeholder placeholderView: T) -> UINib {
    //1. Load and unarchive nib file
    let nibName = givenNibName ?? String(describing: type(of: placeholderView))

    let nib = UINib(nibName: nibName, bundle: Bundle.main)
    return nib
}

// Step 2: Instantiate a view given a nib
//
public func instantiateView<T>(fromNib nib: UINib, placeholder placeholderView: T) -> T {
    //1. Get top level objects
    let topLevelObjects = nib.instantiate(withOwner: placeholderView, options: nil)

    //2. Have at least one top level object
    guard let firstObject = topLevelObjects.first else {
        fatalError("\(#function): no top level objects in nib")
    }

    //3. Return instantiated view, placeholderView is not used
    let instantiatedView = firstObject as! T
    return instantiatedView
}

-1

ฉันสิ้นสุดการเพิ่มหมวดหมู่ใน UIView สำหรับสิ่งนี้:

 #import "UIViewNibLoading.h"

 @implementation UIView (UIViewNibLoading)

 + (id) loadNibNamed:(NSString *) nibName {
    return [UIView loadNibNamed:nibName fromBundle:[NSBundle mainBundle] retainingObjectWithTag:1];
 }

 + (id) loadNibNamed:(NSString *) nibName fromBundle:(NSBundle *) bundle retainingObjectWithTag:(NSUInteger) tag {
    NSArray * nib = [bundle loadNibNamed:nibName owner:nil options:nil];
    if(!nib) return nil;
    UIView * target = nil;
    for(UIView * view in nib) {
        if(view.tag == tag) {
            target = [view retain];
            break;
        }
    }
    if(target && [target respondsToSelector:@selector(viewDidLoad)]) {
        [target performSelector:@selector(viewDidLoad)];
    }
    return [target autorelease];
 }

 @end

คำอธิบายที่นี่: viewcontroller โหลด view น้อยลงใน ios & mac


การบริหารทรัพยากรมนุษย์ แท็กคืออะไร
n13

เพื่อทราบว่าวัตถุระดับบนสุดที่จะเก็บ ในเวลาที่จำเป็น แต่คุณอาจจะเอาออกไปรักษาเพื่อให้สอดคล้องกับ ARC ในขณะนี้
gngrwzrd
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.