เมื่อได้รับมุมมองฉันจะรับ viewController ได้อย่างไร


141

UIViewฉันมีตัวชี้ไปที่ ฉันจะเข้าถึงได้UIViewControllerอย่างไร [self superview]เป็นอีกสิ่งหนึ่งUIViewแต่ไม่ใช่UIViewControllerใช่ไหม


ฉันคิดว่ากระทู้นี้มีคำตอบ: ไปที่ UIViewController จาก UIView บน iPhone?
Ushox

โดยทั่วไปฉันกำลังพยายามเรียกวิวของฉัน viewControlWillAppear เนื่องจากมุมมองของฉันถูกไล่ออก มุมมองนั้นถูกมองข้ามโดยมุมมองตัวเองซึ่งกำลังตรวจจับการแตะและเรียก [self removeFromSuperview] viewController ไม่ได้เรียกใช้ viewWillAppear / WillDisappear / DidAppear / DidDisappear
mahboudz

ฉันหมายถึงฉันพยายามโทรหา viewWillDisappear เนื่องจากมุมมองของฉันถูกไล่ออก
mahboudz

1
มีความซ้ำซ้อนของGet to UIViewController จาก UIView หรือไม่
Efren

คำตอบ:


42

ใช่superviewเป็นมุมมองที่มีมุมมองของคุณ มุมมองของคุณไม่ควรรู้ว่าตัวควบคุมมุมมองของมันคืออะไรเพราะจะทำลายหลักการ MVC

ในทางกลับกันคอนโทรลเลอร์จะรับรู้ว่ามุมมองใดที่รับผิดชอบ ( self.view = myView) และโดยปกติมุมมองนี้จะมอบหมายวิธี / เหตุการณ์สำหรับการจัดการกับคอนโทรลเลอร์

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


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

คุณค่อนข้างถูกต้องเกี่ยวกับมุมมอง, รู้เกี่ยวกับพาเรนต์, มันไม่ใช่การตัดสินใจออกแบบที่ชัดเจน, แต่มันถูกกำหนดไว้แล้วให้ทำบางอย่าง, โดยใช้ตัวแปรสมาชิก superview โดยตรง (ตรวจสอบชนิดพาเรนต์, ลบออกจากพาเรนต์เป็นต้น) เมื่อเร็ว ๆ นี้ฉันได้ทำงานร่วมกับ PureMVC ฉันกลายเป็นคนที่ถนัดมากขึ้นเกี่ยวกับการออกแบบนามธรรม :) ฉันจะทำขนานกันระหว่าง iPhone ของ UIView และ UIViewController คลาสและ PureMVC's View และ Mediator class - ส่วนใหญ่แล้ว View class ไม่จำเป็นต้อง รู้เกี่ยวกับ MVC handler / interface (UIViewController / Mediator)
Dimitar Dimitrov

9
คำสำคัญ: "มากที่สุด"
Glenn Maynard

278

จากUIResponderเอกสารสำหรับnextResponder:

คลาส UIResponder ไม่ได้จัดเก็บหรือตั้งค่าการตอบกลับโดยอัตโนมัติต่อไปแทนที่จะส่งค่าศูนย์เป็นค่าเริ่มต้น คลาสย่อยต้องแทนที่วิธีนี้เพื่อตั้งค่าการตอบกลับครั้งต่อไป UIView ใช้วิธีนี้โดยการกลับวัตถุ UIViewController ที่จัดการมัน (ถ้ามีหนึ่ง) หรือ SuperView (ถ้ามันไม่ได้) ; UIViewController ใช้วิธีนี้โดยส่งคืน superview ของมุมมอง UIWindow ส่งคืนวัตถุแอปพลิเคชันและ UIApplication ส่งคืนศูนย์

ดังนั้นหากคุณเรียกคืนมุมมองnextResponderจนกว่าจะเป็นประเภทUIViewControllerนั้นคุณจะมี parent viewController

โปรดทราบว่ามันยังอาจไม่มีตัวควบคุมมุมมองหลัก แต่ถ้ามุมมองไม่ได้เป็นส่วนหนึ่งของลำดับชั้นการดูของ viewController

ส่วนขยายSwift 3 และSwift 4.1 :

extension UIView {
    var parentViewController: UIViewController? {
        var parentResponder: UIResponder? = self
        while parentResponder != nil {
            parentResponder = parentResponder?.next
            if let viewController = parentResponder as? UIViewController {
                return viewController
            }
        }
        return nil
    }
}

ส่วนขยาย Swift 2:

extension UIView {
    var parentViewController: UIViewController? {
        var parentResponder: UIResponder? = self
        while parentResponder != nil {
            parentResponder = parentResponder!.nextResponder()
            if let viewController = parentResponder as? UIViewController {
                return viewController
            }
        }
        return nil
    }
}

หมวดหมู่ Objective-C:

@interface UIView (mxcl)
- (UIViewController *)parentViewController;
@end

@implementation UIView (mxcl)
- (UIViewController *)parentViewController {
    UIResponder *responder = self;
    while ([responder isKindOfClass:[UIView class]])
        responder = [responder nextResponder];
    return (UIViewController *)responder;
}
@end

มาโครนี้หลีกเลี่ยงมลภาวะประเภท:

#define UIViewParentController(__view) ({ \
    UIResponder *__responder = __view; \
    while ([__responder isKindOfClass:[UIView class]]) \
        __responder = [__responder nextResponder]; \
    (UIViewController *)__responder; \
})

มีเพียงสิ่งเดียว: ถ้าคุณกังวลเกี่ยวกับมลพิษในหมวดหมู่เพียงกำหนดเป็นฟังก์ชั่นคงที่แทนที่จะเป็นมาโคร นอกจากนี้ typecast ที่โหดร้ายก็เป็นอันตรายเช่นกันและยิ่งไปกว่านั้นแมโครอาจไม่ถูกต้อง แต่ไม่แน่ใจ
mojuba

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

ดีที่จะหลีกเลี่ยงผู้ได้รับมอบหมายหรือการแจ้งเตือน ขอบคุณ!
Ferran Maylinch

คุณควรทำให้มันเป็นส่วนขยายของUIResponder;) โพสต์ที่แก้ไขมาก
ScottyBlades

32

@คำตอบในหนึ่งบรรทัด (ทดสอบในSwift 4.1 ):

extension UIResponder {
    public var parentViewController: UIViewController? {
        return next as? UIViewController ?? next?.parentViewController
    }
}

การใช้งาน:

 let vc: UIViewController = view.parentViewController

parentViewControllerไม่สามารถกำหนดได้publicหากส่วนขยายอยู่ในไฟล์เดียวกันกับที่UIViewคุณสามารถตั้งค่าfileprivateมันรวบรวม แต่มันใช้งานไม่ได้! 😐

มันทำงานได้ดี ฉันใช้สิ่งนี้สำหรับการกระทำปุ่มย้อนกลับภายในไฟล์. xib ส่วนหัว
McDonal_11

23

สำหรับการดีบักเท่านั้นคุณสามารถเรียก_viewDelegateใช้มุมมองเพื่อรับตัวควบคุมมุมมองได้ นี่เป็น API ส่วนตัวดังนั้นไม่ปลอดภัยสำหรับ App Store แต่สำหรับการแก้ไขข้อบกพร่องจะมีประโยชน์

วิธีการที่มีประโยชน์อื่น ๆ :

  • _viewControllerForAncestorรับตัวควบคุมแรกที่จัดการมุมมองในห่วงโซ่ superview (ขอบคุณ n00neimp0rtant)
  • _rootAncestorViewController - รับตัวควบคุมบรรพบุรุษซึ่งมีมุมมองลำดับชั้นถูกตั้งค่าในหน้าต่างปัจจุบัน

นี่คือเหตุผลที่ฉันมาถึงคำถามนี้ เห็นได้ชัดว่า 'nextResponder' ทำสิ่งเดียวกัน แต่ฉันขอขอบคุณความเข้าใจอย่างลึกซึ้งที่คำตอบนี้มีให้ ฉันเข้าใจและชอบ MVC แต่การดีบักเป็นสัตว์อื่น!
mbm29414

4
ปรากฏว่าใช้งานได้ในมุมมองหลักของตัวควบคุมมุมมองเท่านั้นไม่ใช่ในมุมมองย่อยของมัน _viewControllerForAncestorจะเข้าไปสำรวจตัวอย่างก่อนจนกว่าจะพบคนแรกที่เป็นของตัวควบคุมมุมมอง
n00neimp0rtant

ขอบคุณ @ n00neimp0rtant! ฉัน upvoting คำตอบนี้เพื่อให้ผู้คนเห็นความคิดเห็นของคุณ
eyuelt

อัปเดตคำตอบด้วยวิธีการเพิ่มเติม
Leo Natan

1
สิ่งนี้มีประโยชน์เมื่อทำการดีบั๊ก
evanchin

10

ในการรับการอ้างอิงถึง UIViewController ที่มี UIView คุณสามารถขยาย UIResponder (ซึ่งเป็น super class สำหรับ UIView และ UIViewController) ซึ่งอนุญาตให้ขึ้นผ่านห่วงโซ่การตอบกลับและเข้าถึง UIViewController (มิเช่นนั้นจะส่งคืนศูนย์)

extension UIResponder {
    func getParentViewController() -> UIViewController? {
        if self.nextResponder() is UIViewController {
            return self.nextResponder() as? UIViewController
        } else {
            if self.nextResponder() != nil {
                return (self.nextResponder()!).getParentViewController()
            }
            else {return nil}
        }
    }
}

//Swift 3
extension UIResponder {
    func getParentViewController() -> UIViewController? {
        if self.next is UIViewController {
            return self.next as? UIViewController
        } else {
            if self.next != nil {
                return (self.next!).getParentViewController()
            }
            else {return nil}
        }
    }
}

let vc = UIViewController()
let view = UIView()
vc.view.addSubview(view)
view.getParentViewController() //provide reference to vc

4

วิธีที่รวดเร็วและทั่วไปใน Swift 3:

extension UIResponder {
    func parentController<T: UIViewController>(of type: T.Type) -> T? {
        guard let next = self.next else {
            return nil
        }
        return (next as? T) ?? next.parentController(of: T.self)
    }
}

//Use:
class MyView: UIView {
    ...
    let parentController = self.parentController(of: MyViewController.self)
}

2

หากคุณไม่คุ้นเคยกับรหัสและคุณต้องการค้นหา ViewController ที่สอดคล้องกับมุมมองที่กำหนดคุณสามารถลอง:

  1. เรียกใช้แอปในการดีบัก
  2. นำทางไปยังหน้าจอ
  3. เริ่มดูสารวัตร
  4. คว้ามุมมองที่คุณต้องการค้นหา (หรือมุมมองเด็กที่ดียิ่งขึ้น)
  5. จากบานหน้าต่างด้านขวารับที่อยู่ (เช่น 0x7fe523bd3000)
  6. ในคอนโซลดีบักเริ่มเขียนคำสั่ง:
    po (UIView *) 0x7fe523bd3000
    po [(UIView *) 0x7fe523bd3000 หน้าถัดไปคำตอบ]
    po [[(UIView *) 0x7fe523bd3000 ถัดไปตอบกลับ] ถัดไปตอบกลับ]
    po [[[(UIView *) 0x7fe523bd3000 ถัดไปตอบกลับ] ถัดไปตอบกลับ] ถัดไปตอบกลับ]
    ...

ในกรณีส่วนใหญ่คุณจะได้รับ UIView แต่ในบางครั้งจะมีคลาสที่ใช้ UIViewController


1

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


หากคุณมีจำนวนการดูจำนวนหนึ่งและหนึ่งปิดการดูทั้งหมดและคุณจำเป็นต้องเรียก viewWillDisappear มันจะไม่ง่ายกว่าสำหรับมุมมองที่จะตรวจจับการแตะกว่าจะส่งแตะไปที่ตัวควบคุมมุมมองและมีการตรวจสอบตัวควบคุมมุมมอง มีมุมมองทั้งหมดเพื่อดูว่ามีรายการใดบ้าง
mahboudz

0

รหัสปลอดภัยเพิ่มเติมสำหรับ Swift 3.0

extension UIResponder {
    func owningViewController() -> UIViewController? {
        var nextResponser = self
        while let next = nextResponser.next {
            nextResponser = next
            if let vc = nextResponser as? UIViewController {
                return vc
            }
        }
        return nil
    }
}

0

อนิจจาสิ่งนี้เป็นไปไม่ได้เว้นแต่คุณจะ subclass มุมมองและจัดให้มีคุณสมบัติอินสแตนซ์หรือเหมือนกันซึ่งเก็บการอ้างอิงตัวควบคุมมุมมองภายในเมื่อมีการเพิ่มมุมมองในฉาก ...

ในกรณีส่วนใหญ่ - มันง่ายมากที่จะแก้ไขปัญหาดั้งเดิมของโพสต์นี้เนื่องจากคอนโทรลเลอร์ส่วนใหญ่เป็นที่รู้จักกันดีในกลุ่มโปรแกรมเมอร์ซึ่งรับผิดชอบในการเพิ่มมุมมองย่อยลงใน ViewController's ;-) ซึ่งเป็นเหตุผลที่ฉันเดาว่า Apple ไม่เคยใส่ใจที่จะเพิ่มคุณสมบัตินั้น


0

สายไปหน่อย แต่นี่เป็นส่วนขยายที่ช่วยให้คุณค้นหาผู้ตอบคำถามทุกประเภทรวมถึง ViewController

extension NSObject{
func findNext(type: AnyClass) -> Any{
    var resp = self as! UIResponder

    while !resp.isKind(of: type.self) && resp.next != nil
    {
        resp = resp.next!
    }

    return resp
  }                       
}

-1

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

po [[UIWindow keyWindow] recursiveDescription]

คุณควรจะสามารถพบผู้ปกครองของมุมมองของคุณที่ใดที่หนึ่งในระเบียบ :)


recursiveDescriptionพิมพ์ลำดับชั้นมุมมองเท่านั้นไม่ใช่มุมมองคอนโทรลเลอร์
Alan Zeino

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