Đưa ra một khung nhìn, làm thế nào để tôi có được viewControll của nó?


141

Tôi có một con trỏ đến a UIView. Làm thế nào để tôi truy cập nó UIViewController? [self superview]là một UIView, nhưng không phải UIViewController, phải không?


Tôi nghĩ chủ đề này có câu trả lời: Truy cập UIViewContoder từ UIView trên iPhone?
Ushox

Về cơ bản, tôi đang cố gắng gọi viewWillAppear của viewContill vì quan điểm của tôi đang bị loại bỏ. Chế độ xem đang bị loại bỏ bởi chính chế độ xem đang phát hiện một cú chạm và gọi [self removeFromSuperview]; ViewContoder không gọi viewWillAppear / WillDisappear / DidAppear / DidDisappear.
mahboudz

Tôi có nghĩa là tôi đang cố gắng gọi viewWillDisappear vì quan điểm của tôi đang bị loại bỏ.
mahboudz

1
Bản sao có thể có của Get to UIViewControll từ UIView?
Efren

Câu trả lời:


42

Vâng, đó superviewlà chế độ xem chứa quan điểm của bạn. Khung nhìn của bạn không nên biết chính xác bộ điều khiển khung nhìn của nó là gì, bởi vì điều đó sẽ phá vỡ các nguyên tắc MVC.

Mặt khác, bộ điều khiển biết khung nhìn nào chịu trách nhiệm cho ( self.view = myView) và thông thường, khung nhìn này ủy nhiệm các phương thức / sự kiện để xử lý cho bộ điều khiển.

Thông thường, thay vì một con trỏ tới chế độ xem của bạn, bạn nên có một con trỏ tới bộ điều khiển của mình, điều này có thể thực hiện một số logic điều khiển hoặc chuyển một cái gì đó cho chế độ xem của nó.


24
Tôi không chắc chắn nếu nó sẽ phá vỡ các nguyên tắc MVC. Tại bất kỳ điểm nào, một khung nhìn chỉ có một bộ điều khiển khung nhìn. Có thể truy cập nó để gửi tin nhắn trở lại, phải là một tính năng tự động, không phải là một nơi bạn phải làm việc để đạt được (bằng cách thêm một thuộc tính để theo dõi). Người ta có thể nói điều tương tự về quan điểm: tại sao bạn cần biết bạn là ai? Hoặc liệu có quan điểm anh chị em khác. Tuy nhiên, có nhiều cách để có được những đối tượng.
mahboudz

Bạn có phần đúng về quan điểm, biết về cha mẹ của nó, đó không phải là quyết định thiết kế siêu rõ ràng, nhưng nó đã được thiết lập để thực hiện một số hành động, sử dụng trực tiếp một biến thành viên giám sát (kiểm tra loại cha mẹ, loại bỏ khỏi cha mẹ, v.v.). Gần đây đã làm việc với PureMVC, tôi trở nên khó tính hơn một chút về sự trừu tượng hóa thiết kế :) Tôi sẽ thực hiện song song giữa các lớp UIView và UIViewControll của iPhone và các lớp View và Mediator của PureMVC - hầu hết thời gian, lớp View không cần biết về trình xử lý / giao diện MVC của nó (UIViewControll / Mediator).
Dimitar Dimitrov

9
Từ khóa: "nhất".
Glenn Maynard

278

Từ UIRespondertài liệu cho nextResponder:

Lớp UIResponder không tự động lưu trữ hoặc thiết lập phản hồi tiếp theo, thay vào đó trả về nil theo mặc định. Các lớp con phải ghi đè phương thức này để đặt phản hồi tiếp theo. UIView thực hiện phương thức này bằng cách trả về đối tượng UIViewControll quản lý nó (nếu nó có) hoặc giám sát của nó (nếu không) ; UIViewControll thực hiện phương thức bằng cách trả về giám sát của khung nhìn; UIWindow trả về đối tượng ứng dụng và UIApplication trả về nil.

Vì vậy, nếu bạn lặp lại một khung nhìn nextRespondercho đến khi nó thuộc kiểu UIViewController, thì bạn có bất kỳ khung nhìn cha mẹ nào của khung nhìn.

Lưu ý rằng nó vẫn có thể không có bộ điều khiển xem cha. Nhưng chỉ khi chế độ xem không phải là một phần của hệ thống phân cấp chế độ xem của chế độ xem.

Phần mở rộng Swift 3 và 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
    }
}

Tiện ích mở rộng 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
    }
}

Mục tiêu-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

Macro này tránh ô nhiễm thể loại:

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

Chỉ có một điều: nếu bạn lo lắng về ô nhiễm danh mục, chỉ cần định nghĩa nó là một hàm tĩnh chứ không phải là một macro. Ngoài ra, typecast tàn bạo là nguy hiểm, và trên hết, macro có thể không chính xác nhưng không chắc chắn.
mojuba

Macro hoạt động, tôi chỉ sử dụng phiên bản macro cá nhân. Tôi bắt đầu rất nhiều dự án và có một tiêu đề tôi chỉ cần bỏ đi khắp nơi với một loạt các chức năng tiện ích macro này. Tiết kiệm thời gian cho tôi. Nếu bạn không thích macro, bạn có thể điều chỉnh nó thành một hàm, nhưng các hàm tĩnh có vẻ tẻ nhạt, vì sau đó bạn phải đặt một trong mỗi tệp bạn muốn sử dụng. Có vẻ như thay vào đó bạn muốn một hàm không tĩnh được khai báo trong một tiêu đề và được xác định trong một nơi nào đó .m?
mxcl 6/03/2015

Rất vui để tránh một số đại biểu hoặc thông báo. Cảm ơn!
Ferran Maylinch

Bạn nên làm cho nó một phần mở rộng của UIResponder;). Rất chỉnh sửa bài.
ScottyBlades

32

@andrey trả lời trong một dòng (được thử nghiệm trong Swift 4.1 ):

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

sử dụng:

 let vc: UIViewController = view.parentViewController

parentViewControllerkhông thể được xác định publicnếu tiện ích mở rộng nằm trong cùng một tệp với UIViewbạn có thể đặt nó fileprivate, nó sẽ biên dịch nhưng nó không hoạt động! 😐

Nó đang hoạt động tốt. Tôi đã sử dụng điều này cho hành động nút quay lại bên trong tệp .xib tiêu đề phổ biến.
McDonal_11

23

Chỉ dành cho mục đích gỡ lỗi, bạn có thể gọi _viewDelegatevào các khung nhìn để có các bộ điều khiển xem của chúng. Đây là API riêng tư, vì vậy không an toàn cho App Store, nhưng để gỡ lỗi thì nó rất hữu ích.

Các phương pháp hữu ích khác:

  • _viewControllerForAncestor- có được bộ điều khiển đầu tiên quản lý chế độ xem trong chuỗi giám sát. (cảm ơn n00neimp0rtant)
  • _rootAncestorViewController - nhận bộ điều khiển tổ tiên có phân cấp chế độ xem được đặt trong cửa sổ hiện tại.

Đây chính xác là lý do tại sao tôi đến câu hỏi này. Rõ ràng, 'nextResponder' cũng làm điều tương tự, nhưng tôi đánh giá cao sự thấu hiểu mà câu trả lời này cung cấp. Tôi hiểu và thích MVC, nhưng gỡ lỗi là một động vật khác!
mbm29414

4
Có vẻ như điều này chỉ hoạt động trên chế độ xem chính của trình điều khiển chế độ xem, không phải trên bất kỳ bản xem trước nào của nó. _viewControllerForAncestorsẽ duyệt qua các giám sát cho đến khi tìm thấy cái đầu tiên thuộc về bộ điều khiển khung nhìn.
n00neimp0rtant

Cảm ơn @ n00neimp0rtant! Tôi ủng hộ câu trả lời này để mọi người thấy bình luận của bạn.
Eyuelt

Cập nhật câu trả lời với nhiều phương pháp hơn.
Leo Natan

1
Điều này rất hữu ích khi gỡ lỗi.
evanchin

10

Để có được tham chiếu đến UIViewContoder có UIView, bạn có thể tạo phần mở rộng của UIResponder (siêu hạng cho UIView và UIViewContoder), cho phép đi qua chuỗi phản hồi và do đó tiếp cận với UIViewContoder (nếu không trả về nil).

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

Cách nhanh chóng và chung chung trong 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

Nếu bạn không quen thuộc với mã và bạn muốn tìm lõi của ViewContoder cho chế độ xem đã cho, thì bạn có thể thử:

  1. Chạy ứng dụng trong gỡ lỗi
  2. Điều hướng đến màn hình
  3. Bắt đầu xem thanh tra
  4. Chọn Chế độ xem bạn muốn tìm (hoặc chế độ xem trẻ em thậm chí còn tốt hơn)
  5. Từ khung bên phải lấy địa chỉ (ví dụ 0x7fe523bd3000)
  6. Trong bảng điều khiển gỡ lỗi bắt đầu viết lệnh:
    po (UIView *) 0x7fe523bd3000
    po [(UIView *) 0x7fe523bd3000 nextResponder]
    po [[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder]
    po [[[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder] nextResponder]
    ...

Trong hầu hết các trường hợp, bạn sẽ nhận được UIView, nhưng đôi khi sẽ có lớp dựa trên UIViewControll.


1

Tôi nghĩ rằng bạn có thể truyền vòi cho bộ điều khiển xem và để nó xử lý nó. Đây là cách tiếp cận dễ chấp nhận hơn. Đối với việc truy cập bộ điều khiển chế độ xem từ chế độ xem của nó, bạn nên duy trì tham chiếu đến bộ điều khiển chế độ xem, vì không có cách nào khác. Xem chủ đề này, nó có thể giúp: Truy cập bộ điều khiển xem từ chế độ xem


Nếu bạn có một số lượt xem và một lần đóng tất cả các chế độ xem và bạn cần gọi viewWillDisappear, thì chế độ xem đó có dễ dàng hơn để phát hiện vòi hơn là đưa vòi vào bộ điều khiển xem và kiểm tra trình điều khiển xem với tất cả các quan điểm để xem cái nào được khai thác?
mahboudz

0

Thêm loại mã an toàn cho 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

Than ôi, điều này là không thể trừ khi bạn phân lớp khung nhìn và cung cấp cho nó một thuộc tính cá thể hoặc tương tự để lưu trữ tham chiếu của trình điều khiển khung nhìn bên trong nó khi chế độ xem được thêm vào cảnh ...

Trong hầu hết các trường hợp - rất dễ để giải quyết vấn đề ban đầu của bài đăng này vì hầu hết các bộ điều khiển xem là các thực thể nổi tiếng với lập trình viên chịu trách nhiệm thêm bất kỳ cuộc phỏng vấn nào vào Chế độ xem của ViewContoder ;-) Đó là lý do tại sao tôi đoán rằng Apple không bao giờ bận tâm để thêm tài sản đó.


0

Hơi muộn một chút, nhưng đây là một tiện ích mở rộng cho phép bạn tìm phản hồi của bất kỳ loại nào, bao gồm cả ViewContoder.

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

Nếu bạn đặt điểm dừng, bạn có thể dán điểm này vào trình gỡ lỗi để in phân cấp chế độ xem:

po [[UIWindow keyWindow] recursiveDescription]

Bạn sẽ có thể tìm thấy cha mẹ của bạn ở đâu đó trong mớ hỗn độn đó :)


recursiveDescriptionchỉ in phân cấp khung nhìn , không in bộ điều khiển xem.
Alan Zeino

1
từ đó bạn có thể xác định trình điều khiển khung nhìn @AlanZeino
Akshay
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.