Câu trả lời:
Vì đây là câu trả lời được chấp nhận trong một thời gian dài, tôi cảm thấy mình cần khắc phục nó bằng một câu trả lời tốt hơn.
Một số ý kiến về sự cần thiết:
Một ví dụ về cách thực hiện nó như sau:
@protocol MyViewDelegate < NSObject >
- (void)viewActionHappened;
@end
@interface MyView : UIView
@property (nonatomic, assign) MyViewDelegate delegate;
@end
@interface MyViewController < MyViewDelegate >
@end
Khung nhìn giao diện với đại biểu của nó ( UITableView
ví dụ, chẳng hạn) và nó không quan tâm nếu nó được triển khai trong trình điều khiển khung nhìn hoặc trong bất kỳ lớp nào khác mà bạn kết thúc sử dụng.
Câu trả lời ban đầu của tôi như sau: Tôi không khuyến nghị điều này, cả những câu trả lời còn lại đều đạt được quyền truy cập trực tiếp vào bộ điều khiển xem
Không có cách tích hợp để làm điều đó. Mặc dù bạn có thể khắc phục bằng cách thêm IBOutlet
vào UIView
và kết nối những thứ này trong Trình tạo giao diện, nhưng điều này không được khuyến khích. Chế độ xem không nên biết về bộ điều khiển xem. Thay vào đó, bạn nên làm như @Phil M gợi ý và tạo một giao thức được sử dụng làm đại biểu.
Sử dụng ví dụ được đăng bởi Brock, tôi đã sửa đổi nó để nó là một thể loại của UIView thay vì UIViewControll và làm cho nó đệ quy để bất kỳ người xem phụ nào cũng có thể (hy vọng) tìm thấy UIViewControll gốc.
@interface UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController;
- (id) traverseResponderChainForUIViewController;
@end
@implementation UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController {
// convenience function for casting and to "mask" the recursive function
return (UIViewController *)[self traverseResponderChainForUIViewController];
}
- (id) traverseResponderChainForUIViewController {
id nextResponder = [self nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]]) {
return nextResponder;
} else if ([nextResponder isKindOfClass:[UIView class]]) {
return [nextResponder traverseResponderChainForUIViewController];
} else {
return nil;
}
}
@end
Để sử dụng mã này, hãy thêm nó vào một tệp lớp mới (tôi đặt tên là "UIKitC chuyên mục") và xóa dữ liệu lớp ... sao chép @interface vào tiêu đề và @im THỰCation vào tệp .m. Sau đó, trong dự án của bạn, #import "UIKitC loại.h" và sử dụng trong mã UIView:
// from a UIView subclass... returns nil if UIViewController not available
UIViewController * myController = [self firstAvailableUIViewController];
UIView
là một lớp con của UIResponder
. UIResponder
đưa ra phương thức -nextResponder
với việc thực hiện trả về nil
. UIView
ghi đè phương thức này, như được ghi lại trong UIResponder
(vì một số lý do thay vì in UIView
) như sau: nếu chế độ xem có bộ điều khiển xem, nó được trả về bởi-nextResponder
. Nếu không có trình điều khiển xem, phương thức sẽ trả về giám sát.
Thêm điều này vào dự án của bạn và bạn đã sẵn sàng để tung ra.
@interface UIView (APIFix)
- (UIViewController *)viewController;
@end
@implementation UIView (APIFix)
- (UIViewController *)viewController {
if ([self.nextResponder isKindOfClass:UIViewController.class])
return (UIViewController *)self.nextResponder;
else
return nil;
}
@end
Bây giờ UIView
có một phương thức làm việc để trả về bộ điều khiển xem.
UIView
và UIViewController
. Câu trả lời của Phil M có tính đệ quy là con đường để đi.
Tôi sẽ đề xuất một cách tiếp cận nhẹ nhàng hơn để vượt qua chuỗi phản hồi hoàn chỉnh mà không cần phải thêm một danh mục trên UIView:
@implementation MyUIViewSubclass
- (UIViewController *)viewController {
UIResponder *responder = self;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
@end
Kết hợp một số câu trả lời đã được đưa ra, tôi cũng đang thực hiện nó với việc thực hiện:
@implementation UIView (AppNameAdditions)
- (UIViewController *)appName_viewController {
/// Finds the view's view controller.
// Take the view controller class object here and avoid sending the same message iteratively unnecessarily.
Class vcc = [UIViewController class];
// Traverse responder chain. Return first found view controller, which will be the view's view controller.
UIResponder *responder = self;
while ((responder = [responder nextResponder]))
if ([responder isKindOfClass: vcc])
return (UIViewController *)responder;
// If the view controller isn't found, return nil.
return nil;
}
@end
Danh mục này là một phần của thư viện tĩnh hỗ trợ ARC mà tôi gửi trên mọi ứng dụng tôi tạo. Nó đã được thử nghiệm nhiều lần và tôi không tìm thấy bất kỳ vấn đề hoặc rò rỉ nào.
Tái bút: Bạn không cần sử dụng danh mục như tôi đã làm nếu chế độ xem liên quan là lớp con của bạn. Trong trường hợp sau, chỉ cần đặt phương thức vào lớp con của bạn và bạn sẽ ổn.
Mặc dù điều này về mặt kỹ thuật có thể được giải quyết như pgb khuyến nghị, IMHO, đây là một lỗ hổng thiết kế. Khung nhìn không cần phải biết về bộ điều khiển.
Tôi sửa đổi de câu trả lời để tôi có thể vượt qua bất kỳ quan điểm, nút, nhãn vv để có được nó của cha mẹ UIViewController
. Đây là mã của tôi.
+(UIViewController *)viewController:(id)view {
UIResponder *responder = view;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
Chỉnh sửa phiên bản Swift 3
class func viewController(_ view: UIView) -> UIViewController {
var responder: UIResponder? = view
while !(responder is UIViewController) {
responder = responder?.next
if nil == responder {
break
}
}
return (responder as? UIViewController)!
}
Chỉnh sửa 2: - Mở rộng Swift
extension UIView
{
//Get Parent View Controller from any view
func parentViewController() -> UIViewController {
var responder: UIResponder? = self
while !(responder is UIViewController) {
responder = responder?.next
if nil == responder {
break
}
}
return (responder as? UIViewController)!
}
}
Đừng quên rằng bạn có thể truy cập vào bộ điều khiển xem gốc cho cửa sổ mà chế độ xem là một khung nhìn phụ. Từ đó, nếu bạn đang sử dụng bộ điều khiển chế độ xem điều hướng và muốn đẩy chế độ xem mới lên nó:
[[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES];
Tuy nhiên, trước tiên bạn cần phải thiết lập thuộc tính rootViewControll của cửa sổ. Làm điều này khi bạn lần đầu tiên tạo bộ điều khiển, ví dụ như trong đại biểu ứng dụng của bạn:
-(void) applicationDidFinishLaunching:(UIApplication *)application {
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
RootViewController *controller = [[YourRootViewController] alloc] init];
[window setRootViewController: controller];
navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
[controller release];
[window addSubview:[[self navigationController] view]];
[window makeKeyAndVisible];
}
[[self navigationController] view]
là chế độ xem "chính" (phụ) của cửa sổ, thuộc rootViewController
tính của cửa sổ phải được đặt thành navigationController
điều khiển chế độ xem "chính" ngay lập tức.
Mặc dù các câu trả lời này là đúng về mặt kỹ thuật, bao gồm Ushox, tôi nghĩ cách được phê duyệt là thực hiện một giao thức mới hoặc sử dụng lại một giao thức hiện có. Một giao thức cách ly người quan sát khỏi quan sát, giống như đặt một khe thư ở giữa chúng. Trong thực tế, đó là những gì Gabriel làm thông qua việc gọi phương thức PushViewControll; Chế độ xem "biết" rằng đó là giao thức thích hợp để yêu cầu một cách lịch sự điều hướng của bạn để điều khiển một khung nhìn, vì viewContoder tuân thủ giao thức navigationContoder. Mặc dù bạn có thể tạo giao thức của riêng mình, chỉ cần sử dụng ví dụ của Gabriel và sử dụng lại giao thức UINavestionControll là tốt.
Tôi tình cờ thấy một tình huống mà tôi có một thành phần nhỏ mà tôi muốn sử dụng lại và đã thêm một số mã trong chế độ xem có thể sử dụng lại (nó thực sự không khác gì một nút mở a PopoverController
).
Mặc dù điều này hoạt động tốt trong iPad ( UIPopoverController
bản thân quà tặng, do đó không cần tham chiếu đến a UIViewController
), nhưng việc sử dụng cùng một mã có nghĩa là đột nhiên tham chiếu presentViewController
từ bạn UIViewController
. Kinda không nhất quán phải không?
Giống như đã đề cập trước đây, đây không phải là cách tiếp cận tốt nhất để có logic trong UIView của bạn. Nhưng nó cảm thấy thực sự vô dụng khi bọc một vài dòng mã cần thiết trong một bộ điều khiển riêng.
Dù bằng cách nào, đây là một giải pháp nhanh chóng, bổ sung một thuộc tính mới cho bất kỳ UIView nào:
extension UIView {
var viewController: UIViewController? {
var responder: UIResponder? = self
while responder != nil {
if let responder = responder as? UIViewController {
return responder
}
responder = responder?.nextResponder()
}
return nil
}
}
Tôi không nghĩ rằng đó là ý tưởng "xấu" để tìm ra ai là người điều khiển chế độ xem cho một số trường hợp. Điều có thể là một ý tưởng tồi là lưu tham chiếu đến bộ điều khiển này vì nó có thể thay đổi khi giám sát thay đổi. Trong trường hợp của tôi, tôi có một getter đi qua chuỗi phản hồi.
//.h
@property (nonatomic, readonly) UIViewController * viewController;
//
- (UIViewController *)viewController
{
for (UIResponder * nextResponder = self.nextResponder;
nextResponder;
nextResponder = nextResponder.nextResponder)
{
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController *)nextResponder;
}
// Not found
NSLog(@"%@ doesn't seem to have a viewController". self);
return nil;
}
Vòng lặp do đơn giản nhất để tìm viewContoder.
-(UIViewController*)viewController
{
UIResponder *nextResponder = self;
do
{
nextResponder = [nextResponder nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController*)nextResponder;
} while (nextResponder != nil);
return nil;
}
(ngắn gọn hơn các câu trả lời khác)
fileprivate extension UIView {
var firstViewController: UIViewController? {
let firstViewController = sequence(first: self, next: { $0.next }).first(where: { $0 is UIViewController })
return firstViewController as? UIViewController
}
}
Trường hợp sử dụng của tôi mà tôi cần truy cập vào chế độ xem trước UIViewController
: Tôi có một đối tượng bao quanh AVPlayer
/ AVPlayerViewController
và tôi muốn cung cấp một show(in view: UIView)
phương thức đơn giản sẽ nhúng AVPlayerViewController
vào view
. Cho rằng, tôi cần phải truy cập view
's UIViewController
.
Điều này không trả lời trực tiếp câu hỏi, mà là đưa ra một giả định về ý định của câu hỏi.
Nếu bạn có chế độ xem và trong chế độ xem đó, bạn cần gọi một phương thức trên một đối tượng khác, như nói bộ điều khiển chế độ xem, bạn có thể sử dụng NSNotificationCenter thay thế.
Đầu tiên tạo chuỗi thông báo của bạn trong tệp tiêu đề
#define SLCopyStringNotification @"ShaoloCopyStringNotification"
Theo quan điểm của bạn, hãy gọi postNotificationName:
- (IBAction) copyString:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:SLCopyStringNotification object:nil];
}
Sau đó, trong trình điều khiển xem của bạn, bạn thêm một người quan sát. Tôi làm điều này trong viewDidLoad
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(copyString:)
name:SLCopyStringNotification
object:nil];
}
Bây giờ (cũng trong cùng một trình điều khiển khung nhìn) triển khai phương thức copyString của bạn: như được mô tả trong @selector ở trên.
- (IBAction) copyString:(id)sender
{
CalculatorResult* result = (CalculatorResult*)[[PercentCalculator sharedInstance].arrayTableDS objectAtIndex:([self.viewTableResults indexPathForSelectedRow].row)];
UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];
[gpBoard setString:result.stringResult];
}
Tôi không nói rằng đây là cách đúng đắn để làm điều này, nó chỉ có vẻ sạch hơn so với việc chạy chuỗi phản hồi đầu tiên. Tôi đã sử dụng mã này để triển khai UIMothyContoder trên UITableView và chuyển sự kiện trở lại cho UIViewContoder để tôi có thể làm gì đó với dữ liệu.
Đó chắc chắn là một ý tưởng tồi và một thiết kế sai, nhưng tôi chắc chắn tất cả chúng ta đều có thể tận hưởng một giải pháp Swift cho câu trả lời tốt nhất được đề xuất bởi @Phil_M:
static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? {
func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? {
if let nextResponder = responder.nextResponder() {
if let nextResp = nextResponder as? UIViewController {
return nextResp
} else {
return traverseResponderChainForUIViewController(nextResponder)
}
}
return nil
}
return traverseResponderChainForUIViewController(responder)
}
Nếu ý định của bạn là làm những việc đơn giản, như hiển thị hộp thoại theo chế độ hoặc dữ liệu theo dõi, điều đó không biện minh cho việc sử dụng giao thức. Cá nhân tôi lưu trữ chức năng này trong một đối tượng tiện ích, bạn có thể sử dụng nó từ bất cứ thứ gì thực hiện giao thức UIResponder như:
if let viewController = MyUtilityClass.firstAvailableUIViewController(self) {}
Tất cả tín dụng cho @Phil_M
Có lẽ tôi đến muộn. Nhưng trong tình huống này tôi không thích thể loại (ô nhiễm). Tôi thích cách này:
#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})
Giải pháp nhanh hơn
extension UIView {
var parentViewController: UIViewController? {
for responder in sequence(first: self, next: { $0.next }) {
if let viewController = responder as? UIViewController {
return viewController
}
}
return nil
}
}
sequence
bit). Vì vậy, nếu "swifty" có nghĩa là "nhiều chức năng hơn", thì tôi đoán nó sẽ tiện lợi hơn.
Phiên bản cập nhật cho swift 4: Cảm ơn @Phil_M và @ paul-slm
static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? {
func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? {
if let nextResponder = responder.next {
if let nextResp = nextResponder as? UIViewController {
return nextResp
} else {
return traverseResponderChainForUIViewController(responder: nextResponder)
}
}
return nil
}
return traverseResponderChainForUIViewController(responder: responder)
}
Phiên bản Swift 4
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
}
Ví dụ sử dụng
if let parent = self.view.parentViewController{
}
return
từ khóa ngay bây giờGiải pháp 1:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.first(where: { $0 is UIViewController })
.flatMap { $0 as? UIViewController }
}
}
Giải pháp 2:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.compactMap{ $0 as? UIViewController }
.first
}
}
Giải pháp của tôi có thể được coi là không có thật nhưng tôi cũng gặp tình huống tương tự như mayoneez (tôi muốn chuyển đổi chế độ xem theo cử chỉ trong EAGLView) và tôi đã sử dụng trình điều khiển chế độ xem của EAGL theo cách này:
EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;
EAGLViewController *vc = [(EAGLAppDelegate *)[UIApplication sharedApplication].delegate viewController];
.
ClassName *object
- bằng dấu hoa thị.
Tôi nghĩ rằng có một trường hợp khi quan sát cần phải thông báo cho người quan sát.
Tôi thấy một vấn đề tương tự trong đó UIView trong UIViewContoder đang phản ứng với một tình huống và trước tiên nó cần nói với bộ điều khiển khung nhìn cha mẹ của nó để ẩn nút quay lại và sau khi hoàn thành, hãy nói với bộ điều khiển khung nhìn cha mẹ rằng nó cần tự bật ra khỏi ngăn xếp.
Tôi đã thử điều này với các đại biểu không có thành công.
Tôi không hiểu tại sao điều này nên là một ý tưởng tồi?
Một cách dễ dàng khác là có lớp khung nhìn của riêng bạn và thêm một thuộc tính của trình điều khiển khung nhìn trong lớp khung nhìn. Thông thường bộ điều khiển khung nhìn tạo ra khung nhìn và đó là nơi bộ điều khiển có thể tự đặt thành thuộc tính. Về cơ bản, nó thay vì tìm kiếm xung quanh (với một chút hack) cho bộ điều khiển, có bộ điều khiển để tự đặt chế độ xem - điều này đơn giản nhưng có ý nghĩa vì nó là bộ điều khiển "điều khiển" chế độ xem.
Nếu bạn không tải nó lên App Store, bạn cũng có thể sử dụng phương pháp riêng tư của UIView.
@interface UIView(Private)
- (UIViewController *)_viewControllerForAncestor;
@end
// Later in the code
UIViewController *vc = [myView _viewControllerForAncestor];
Nếu rootViewControll của bạn là UINavlationViewContaptor, được thiết lập trong lớp AppDelegate, thì
+ (UIViewController *) getNearestViewController:(Class) c {
NSArray *arrVc = [[[[UIApplication sharedApplication] keyWindow] rootViewController] childViewControllers];
for (UIViewController *v in arrVc)
{
if ([v isKindOfClass:c])
{
return v;
}
}
return nil;}
Trường hợp c yêu cầu xem lớp điều khiển.
SỬ DỤNG:
RequiredViewController* rvc = [Utilities getNearestViewController:[RequiredViewController class]];
Không có cách nào.
Những gì tôi làm là chuyển con trỏ UIViewControll sang UIView (hoặc một kế thừa thích hợp). Tôi xin lỗi tôi không thể giúp đỡ với cách tiếp cận IB đối với vấn đề này vì tôi không tin vào IB.
Để trả lời người bình luận đầu tiên: đôi khi bạn cần biết ai đã gọi bạn vì nó quyết định bạn có thể làm gì. Ví dụ: với cơ sở dữ liệu, bạn có thể chỉ có quyền truy cập đọc hoặc đọc / ghi ...