มีวิธีในการรับจาก a UIView
ไปยังUIViewController
หรือไม่? ฉันรู้ว่าคุณจะได้รับจากผ่านUIViewController
ของมันแต่ฉันสงสัยว่ามีการอ้างอิงย้อนกลับ?UIView
[self view]
มีวิธีในการรับจาก a UIView
ไปยังUIViewController
หรือไม่? ฉันรู้ว่าคุณจะได้รับจากผ่านUIViewController
ของมันแต่ฉันสงสัยว่ามีการอ้างอิงย้อนกลับ?UIView
[self view]
คำตอบ:
เนื่องจากนี่เป็นคำตอบที่ได้รับการยอมรับมาเป็นเวลานานฉันรู้สึกว่าฉันต้องแก้ไขมันด้วยคำตอบที่ดีกว่า
ความคิดเห็นบางอย่างเกี่ยวกับความต้องการ:
ตัวอย่างของวิธีการใช้ดังต่อไปนี้:
@protocol MyViewDelegate < NSObject >
- (void)viewActionHappened;
@end
@interface MyView : UIView
@property (nonatomic, assign) MyViewDelegate delegate;
@end
@interface MyViewController < MyViewDelegate >
@end
อินเทอร์เฟซการดูด้วยตัวแทนของมัน (เช่นเดียวUITableView
กับ) และมันไม่สนใจว่ามันจะถูกนำไปใช้ในตัวควบคุมมุมมองหรือในคลาสอื่น ๆ ที่คุณใช้
คำตอบเดิมของฉันมีดังนี้: ฉันไม่แนะนำสิ่งนี้, ไม่ใช่คำตอบที่เหลือซึ่งการเข้าถึงตัวควบคุมมุมมองโดยตรง
ไม่มีวิธีการติดตั้งในตัว ในขณะที่คุณจะได้รับรอบโดยการเพิ่มIBOutlet
ในUIView
และการเชื่อมต่อเหล่านี้ใน Interface Builder นี้ไม่แนะนำ มุมมองไม่ควรรู้เกี่ยวกับตัวควบคุมมุมมอง คุณควรทำตามที่ @Phil M แนะนำและสร้างโปรโตคอลที่จะใช้เป็นผู้รับมอบสิทธิ์
จากการใช้ตัวอย่างที่โพสต์โดย Brock ฉันได้ทำการแก้ไขเพื่อให้เป็นหมวดหมู่ของ UIView แทน UIViewController และทำให้มันซ้ำเพื่อให้มุมมองย่อยใด ๆ (หวังว่า) จะค้นหา UIViewController หลัก
@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
หากต้องการใช้รหัสนี้ให้เพิ่มลงในไฟล์คลาสใหม่ (ฉันชื่อ mine "UIKitCategories") และลบข้อมูลคลาส ... คัดลอก @interface ไปยังส่วนหัวและ @implementation ลงในไฟล์. m จากนั้นในโครงการของคุณ #import "UIKitCategories.h" และใช้ภายในรหัส UIView:
// from a UIView subclass... returns nil if UIViewController not available
UIViewController * myController = [self firstAvailableUIViewController];
UIView
เป็น subclass UIResponder
ของ UIResponder
ออกวางวิธีการที่มีการดำเนินการที่ผลตอบแทน-nextResponder
แทนที่วิธีการนี้เป็นเอกสารใน(ด้วยเหตุผลบางอย่างแทนใน) เป็นดังนี้: ถ้ามุมมองที่มีตัวควบคุมมุมมองของมันจะถูกส่งกลับโดย หากไม่มีตัวควบคุมมุมมองวิธีการจะส่งคืนผู้ควบคุมnil
UIView
UIResponder
UIView
-nextResponder
เพิ่มสิ่งนี้ลงในโครงการของคุณและคุณพร้อมแล้ว
@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
ขณะนี้UIView
มีวิธีการทำงานสำหรับส่งคืนตัวควบคุมมุมมอง
UIView
UIViewController
คำตอบของ Phil M ที่มีการเรียกซ้ำเป็นวิธีที่จะไป
ฉันขอแนะนำวิธีที่มีน้ำหนักเบากว่าสำหรับการสำรวจห่วงโซ่การตอบกลับที่สมบูรณ์โดยไม่ต้องเพิ่มหมวดหมู่ใน UIView:
@implementation MyUIViewSubclass
- (UIViewController *)viewController {
UIResponder *responder = self;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
@end
เมื่อรวมหลายคำตอบที่ได้รับไปแล้วฉันก็ส่งไปพร้อม ๆ กับการติดตั้งของฉัน:
@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
หมวดหมู่เป็นส่วนหนึ่งของห้องสมุดคงที่เปิดใช้งาน ARCของฉันที่ฉันจัดส่งในทุกแอปพลิเคชันที่ฉันสร้าง ผ่านการทดสอบหลายครั้งและฉันไม่พบปัญหาหรือการรั่วไหล
PS: คุณไม่จำเป็นต้องใช้หมวดหมู่เหมือนที่ฉันทำถ้ามุมมองที่เกี่ยวข้องเป็นประเภทย่อยของคุณ ในกรณีหลังเพียงแค่ใส่วิธีการในคลาสย่อยของคุณและคุณดีไป
แม้ว่าจะสามารถแก้ไขได้ในทางเทคนิคตามที่pgbแนะนำ แต่ IMHO นี่เป็นข้อบกพร่องในการออกแบบ มุมมองที่ไม่จำเป็นต้องระวังของตัวควบคุม
ผมปรับเปลี่ยนdeคำตอบดังนั้นฉันสามารถผ่านมุมมองใด ๆ ปุ่มป้ายอื่น ๆ UIViewController
จะได้รับมันของผู้ปกครอง นี่คือรหัสของฉัน
+(UIViewController *)viewController:(id)view {
UIResponder *responder = view;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
แก้ไข Swift 3 Version
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)!
}
แก้ไข 2: - Extention สวิฟท์
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)!
}
}
อย่าลืมว่าคุณสามารถเข้าถึงตัวควบคุมมุมมองรูทสำหรับหน้าต่างที่มุมมองนั้นเป็นมุมมองย่อย จากตรงนั้นถ้าคุณใช้ตัวควบคุมมุมมองการนำทางและต้องการที่จะผลักดันมุมมองใหม่ไปที่มัน
[[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES];
อย่างไรก็ตามคุณจะต้องตั้งค่าคุณสมบัติ rootViewController ของหน้าต่างอย่างถูกต้องก่อน ทำสิ่งนี้เมื่อคุณสร้างคอนโทรลเลอร์เป็นครั้งแรกเช่นในตัวแทนแอพของคุณ:
-(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]
เป็นมุมมอง "หลัก" (ย่อย) ของหน้าต่างrootViewController
คุณสมบัติของหน้าต่างจะต้องถูกตั้งค่าให้navigationController
ควบคุมมุมมอง "หลัก" โดยทันที
ในขณะที่คำตอบเหล่านี้ถูกต้องทางเทคนิครวมถึง Ushox ฉันคิดว่าวิธีที่ได้รับการอนุมัติคือการใช้โปรโตคอลใหม่หรือนำมาใช้ใหม่ที่มีอยู่ โปรโตคอลป้องกันผู้สังเกตการณ์จากการสังเกตเช่นการใส่ช่องใส่อีเมลระหว่างพวกเขา ผลคือนั่นคือสิ่งที่กาเบรียลทำผ่านการเรียกใช้เมธอด pushViewController มุมมอง "รู้" ว่าเป็นโปรโตคอลที่เหมาะสมที่จะขอให้สุภาพนำทางของคุณเพื่อผลักดันมุมมองเนื่องจาก viewController เป็นไปตามโปรโตคอลนำทางController ในขณะที่คุณสามารถสร้างโปรโตคอลของคุณเองเพียงแค่ใช้ตัวอย่างของ Gabriel และการใช้โปรโตคอล UINavigationController อีกครั้งก็ใช้ได้
ฉันสะดุดกับสถานการณ์ที่ฉันมีส่วนประกอบขนาดเล็กที่ฉันต้องการนำมาใช้ใหม่และเพิ่มโค้ดบางส่วนในมุมมองที่นำกลับมาใช้ใหม่ได้เอง (มันไม่ได้เป็นอะไรมากไปกว่าปุ่มที่เปิดPopoverController
)
ในขณะที่วิธีนี้ใช้งานได้ดีใน iPad ( UIPopoverController
ของขวัญนั้นไม่จำเป็นต้องอ้างอิงถึง a UIViewController
) การใช้รหัสเดียวกันในการทำงานหมายถึงการอ้างอิงpresentViewController
จากคุณในUIViewController
ทันที ค่อนข้างไม่สอดคล้องใช่มั้ย
อย่างที่กล่าวไว้ก่อนหน้านี้ไม่ใช่วิธีที่ดีที่สุดที่จะมีตรรกะใน UIView ของคุณ แต่มันรู้สึกว่าไร้ประโยชน์จริงๆที่จะห่อโค้ดสองสามบรรทัดที่จำเป็นในคอนโทรลเลอร์แยกกัน
ไม่ว่าจะด้วยวิธีใดนี่คือโซลูชันที่รวดเร็วซึ่งจะเพิ่มคุณสมบัติใหม่ให้กับ UIView ใด ๆ :
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
}
}
ฉันไม่คิดว่าเป็นความคิดที่ "เลวร้าย" ในการค้นหาว่าใครเป็นตัวควบคุมมุมมองสำหรับบางกรณี สิ่งที่อาจเป็นความคิดที่ไม่ดีคือการบันทึกการอ้างอิงไปยังคอนโทรลเลอร์นี้เพราะมันสามารถเปลี่ยนแปลงได้เช่นเดียวกับการเปลี่ยนผู้ดูแล ในกรณีของฉันฉันมีทะเยอทะยานที่ลัดเลาะห่วงโซ่ตอบกลับ
//.h
@property (nonatomic, readonly) UIViewController * viewController;
//.m
- (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;
}
วิธีที่ง่ายที่สุดในขณะที่วนรอบเพื่อค้นหา viewController
-(UIViewController*)viewController
{
UIResponder *nextResponder = self;
do
{
nextResponder = [nextResponder nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController*)nextResponder;
} while (nextResponder != nil);
return nil;
}
(กระชับกว่าคำตอบอื่น ๆ )
fileprivate extension UIView {
var firstViewController: UIViewController? {
let firstViewController = sequence(first: self, next: { $0.next }).first(where: { $0 is UIViewController })
return firstViewController as? UIViewController
}
}
กรณีการใช้งานของฉันที่ฉันจำเป็นต้องเข้าถึงมุมมองแรกUIViewController
: ฉันมีวัตถุที่ล้อมรอบAVPlayer
/ AVPlayerViewController
และฉันต้องการให้ง่ายshow(in view: UIView)
วิธีการที่จะฝังลงในAVPlayerViewController
view
เพื่อที่ฉันจะต้องเข้าถึง'sview
UIViewController
สิ่งนี้ไม่ได้ตอบคำถามโดยตรง แต่ให้ตั้งสมมติฐานเกี่ยวกับเจตนาของคำถาม
ถ้าคุณมีมุมมองและในมุมมองนั้นคุณจำเป็นต้องเรียกใช้เมธอดบนวัตถุอื่นเช่นตัวควบคุมมุมมองคุณสามารถใช้ NSNotificationCenter แทน
ขั้นแรกสร้างสตริงการแจ้งเตือนของคุณในไฟล์ส่วนหัว
#define SLCopyStringNotification @"ShaoloCopyStringNotification"
ในมุมมองการโทรของคุณหมายเหตุชื่อ:
- (IBAction) copyString:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:SLCopyStringNotification object:nil];
}
จากนั้นในตัวควบคุมมุมมองของคุณคุณเพิ่มผู้สังเกตการณ์ ฉันทำสิ่งนี้ใน viewDidLoad
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(copyString:)
name:SLCopyStringNotification
object:nil];
}
ตอนนี้ (ในตัวควบคุมมุมมองเดียวกัน) ใช้วิธีการของคุณ copyString: ตามที่ปรากฎใน @selector ด้านบน
- (IBAction) copyString:(id)sender
{
CalculatorResult* result = (CalculatorResult*)[[PercentCalculator sharedInstance].arrayTableDS objectAtIndex:([self.viewTableResults indexPathForSelectedRow].row)];
UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];
[gpBoard setString:result.stringResult];
}
ฉันไม่ได้บอกว่านี่เป็นวิธีที่ถูกต้องในการทำสิ่งนี้ดูเหมือนว่าจะสะอาดกว่าการวิ่งขึ้นห่วงโซ่การตอบกลับแรก ฉันใช้รหัสนี้เพื่อใช้ UIMenuController บน UITableView และส่งเหตุการณ์กลับไปที่ UIViewController เพื่อให้ฉันสามารถทำอะไรกับข้อมูลได้
แน่นอนว่ามันเป็นความคิดที่ไม่ดีและการออกแบบที่ไม่ถูกต้อง แต่ฉันแน่ใจว่าเราทุกคนสามารถเพลิดเพลินไปกับโซลูชั่น Swift ของคำตอบที่ดีที่สุดที่เสนอโดย @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)
}
หากความตั้งใจของคุณคือการทำสิ่งง่าย ๆ เช่นการแสดงกล่องโต้ตอบหรือข้อมูลการติดตามที่ไม่ได้ปรับการใช้โปรโตคอล ฉันเองเก็บฟังก์ชันนี้ในวัตถุอรรถประโยชน์คุณสามารถใช้จากสิ่งที่ใช้โปรโตคอล UIResponder เป็น:
if let viewController = MyUtilityClass.firstAvailableUIViewController(self) {}
เครดิตทั้งหมดถึง @Phil_M
บางทีฉันอาจจะมาสาย แต่ในสถานการณ์นี้ฉันไม่ชอบหมวดหมู่ (มลพิษ) ฉันชอบวิธีนี้:
#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})
ทางออกที่รวดเร็ว
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
บิต) ดังนั้นถ้า "swifty" หมายถึง "การทำงานได้มากกว่า" ฉันก็เดาว่ามันจะเพิ่มขึ้นอย่างรวดเร็ว
อัปเดตเวอร์ชันสำหรับ swift 4: ขอบคุณสำหรับ @Phil_M และ @ 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)
}
รุ่น 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
}
ตัวอย่างการใช้งาน
if let parent = self.view.parentViewController{
}
return
คำหลักตอนนี้🤓โซลูชันที่ 1:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.first(where: { $0 is UIViewController })
.flatMap { $0 as? UIViewController }
}
}
โซลูชันที่ 2:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.compactMap{ $0 as? UIViewController }
.first
}
}
คำตอบของฟิล:
ในบรรทัด: id nextResponder = [self nextResponder];
ถ้าตัวเอง (UIView) ไม่ได้เป็น subview ของมุมมองของ ViewController ถ้าคุณรู้ว่าลำดับขั้นของตนเอง (UIView) คุณสามารถใช้เพิ่มเติม: id nextResponder = [[self superview] nextResponder];
...
โซลูชันของฉันอาจถูกพิจารณาว่าเป็นการหลอกลวง แต่ฉันมีสถานการณ์คล้ายกันกับ mayoneez (ฉันต้องการสลับมุมมองเพื่อตอบสนองต่อท่าทางใน EAGLView) และฉันได้ตัวควบคุมมุมมองของ EAGL ด้วยวิธีนี้:
EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;
EAGLViewController *vc = [(EAGLAppDelegate *)[UIApplication sharedApplication].delegate viewController];
กรุณาเขียนรหัสของคุณดังนี้
ClassName *object
ด้วยเครื่องหมายดอกจัน
ฉันคิดว่ามีกรณีเมื่อผู้สังเกตการณ์ต้องการแจ้งผู้สังเกตการณ์
ฉันเห็นปัญหาที่คล้ายกันซึ่ง UIView ใน UIViewController ตอบสนองต่อสถานการณ์และต้องบอกตัวควบคุมมุมมองหลักเพื่อซ่อนปุ่มย้อนกลับก่อนแล้วจึงบอกตัวควบคุมมุมมองหลักที่ต้องการให้ปรากฏตัวออกจากสแต็ก
ฉันได้ลองทำสิ่งนี้กับผู้ร่วมประชุมที่ไม่ประสบความสำเร็จ
ฉันไม่เข้าใจว่าทำไมจึงเป็นความคิดที่ไม่ดี
อีกวิธีง่าย ๆ คือการมีคลาสมุมมองของคุณเองและเพิ่มคุณสมบัติของตัวควบคุมมุมมองในมุมมองคลาส โดยปกติแล้วตัวควบคุมมุมมองจะสร้างมุมมองและเป็นที่ที่ตัวควบคุมสามารถตั้งค่าตัวเองกับคุณสมบัติ โดยพื้นฐานแล้วแทนที่จะค้นหารอบ ๆ (ด้วยการแฮ็กเล็กน้อย) สำหรับตัวควบคุมโดยให้ตัวควบคุมตั้งค่าตัวเองไปยังมุมมอง - นี่เป็นเรื่องง่าย แต่ก็สมเหตุสมผลเนื่องจากเป็นตัวควบคุมที่ "ควบคุม" มุมมอง
หากคุณจะไม่อัปโหลดไปที่ App Store คุณสามารถใช้วิธีการส่วนตัวของ UIView
@interface UIView(Private)
- (UIViewController *)_viewControllerForAncestor;
@end
// Later in the code
UIViewController *vc = [myView _viewControllerForAncestor];
var parentViewController: UIViewController? {
let s = sequence(first: self) { $0.next }
return s.compactMap { $0 as? UIViewController }.first
}
เพื่อให้ตัวควบคุมของมุมมองที่กำหนดหนึ่งสามารถใช้ UIFirstResponder ห่วงโซ่
customView.target(forAction: Selector("viewDidLoad"), withSender: nil)
หาก rootViewController ของคุณคือ UINavigationViewController ซึ่งถูกตั้งค่าในคลาส AppDelegate
+ (UIViewController *) getNearestViewController:(Class) c {
NSArray *arrVc = [[[[UIApplication sharedApplication] keyWindow] rootViewController] childViewControllers];
for (UIViewController *v in arrVc)
{
if ([v isKindOfClass:c])
{
return v;
}
}
return nil;}
โดยที่ c ต้องการคลาสตัวควบคุมมุมมอง
การใช้:
RequiredViewController* rvc = [Utilities getNearestViewController:[RequiredViewController class]];
ไม่มีทาง.
สิ่งที่ฉันทำคือส่งตัวชี้ UIViewController ไปยัง UIView (หรือการสืบทอดที่เหมาะสม) ฉันขอโทษที่ฉันไม่สามารถช่วยปัญหา IB ได้เพราะฉันไม่เชื่อใน IB
ในการตอบผู้วิจารณ์คนแรก: บางครั้งคุณจำเป็นต้องรู้ว่าใครโทรหาคุณเพราะมันเป็นตัวกำหนดสิ่งที่คุณสามารถทำได้ ตัวอย่างเช่นกับฐานข้อมูลคุณอาจมีสิทธิ์เข้าถึงเพื่ออ่านเท่านั้นหรืออ่าน / เขียน ...