Apakah ada cara bawaan untuk mendapatkan dari a UIView
ke miliknya UIViewController
? Saya tahu Anda bisa mendapatkan dari UIViewController
ke-nya UIView
melalui [self view]
tapi saya bertanya-tanya apakah ada referensi terbalik?
Apakah ada cara bawaan untuk mendapatkan dari a UIView
ke miliknya UIViewController
? Saya tahu Anda bisa mendapatkan dari UIViewController
ke-nya UIView
melalui [self view]
tapi saya bertanya-tanya apakah ada referensi terbalik?
Jawaban:
Karena ini sudah menjadi jawaban yang diterima sejak lama, saya merasa perlu memperbaikinya dengan jawaban yang lebih baik.
Beberapa komentar tentang kebutuhan:
Contoh cara menerapkannya adalah sebagai berikut:
@protocol MyViewDelegate < NSObject >
- (void)viewActionHappened;
@end
@interface MyView : UIView
@property (nonatomic, assign) MyViewDelegate delegate;
@end
@interface MyViewController < MyViewDelegate >
@end
Tampilan berinteraksi dengan delegasinya (seperti UITableView
halnya, misalnya) dan tidak peduli jika diterapkan di controller tampilan atau di kelas lain yang akhirnya Anda gunakan.
Jawaban asli saya berikut: Saya tidak merekomendasikan ini, baik sisa jawaban mana akses langsung ke pengontrol tampilan tercapai
Tidak ada cara bawaan untuk melakukannya. Meskipun Anda bisa mendapatkan sekitar itu dengan menambahkan IBOutlet
pada UIView
dan menghubungkan ini di Interface Builder, ini tidak dianjurkan. Pandangan seharusnya tidak tahu tentang pengontrol tampilan. Sebagai gantinya, Anda harus melakukan seperti yang disarankan oleh @Phil M dan membuat protokol untuk digunakan sebagai delegasi.
Menggunakan contoh yang diposting oleh Brock, saya memodifikasinya sehingga itu adalah kategori UIView, bukan UIViewController dan membuatnya rekursif sehingga setiap subview dapat (semoga) menemukan induk 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
Untuk menggunakan kode ini, tambahkan ke file kelas baru (saya beri nama saya "UIKitCategories") dan hapus data kelas ... salin antarmuka @ ke header, dan implementasi @ ke file .m. Kemudian dalam proyek Anda, # impor "UIKitCategories.h" dan gunakan dalam kode UIView:
// from a UIView subclass... returns nil if UIViewController not available
UIViewController * myController = [self firstAvailableUIViewController];
UIView
adalah subkelas dari UIResponder
. UIResponder
menjabarkan metode -nextResponder
dengan implementasi yang mengembalikan nil
. UIView
mengganti metode ini, seperti yang didokumentasikan dalam UIResponder
(untuk beberapa alasan, bukan dalam UIView
) sebagai berikut: jika tampilan memiliki pengontrol tampilan, itu dikembalikan oleh -nextResponder
. Jika tidak ada view controller, metode ini akan mengembalikan superview.
Tambahkan ini ke proyek Anda dan Anda siap untuk roll.
@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
Sekarang UIView
memiliki metode yang berfungsi untuk mengembalikan pengontrol tampilan.
UIView
dan penerima UIViewController
. Jawaban Phil M menampilkan rekursi adalah jalan yang harus ditempuh.
Saya akan menyarankan pendekatan yang lebih ringan untuk melintasi rantai responden lengkap tanpa harus menambahkan kategori pada UIView:
@implementation MyUIViewSubclass
- (UIViewController *)viewController {
UIResponder *responder = self;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
@end
Menggabungkan beberapa jawaban yang sudah diberikan, saya mengirimnya juga dengan implementasi saya:
@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
Kategori ini adalah bagian dari pustaka statis berkemampuan ARC yang saya kirimkan pada setiap aplikasi yang saya buat. Sudah diuji beberapa kali dan saya tidak menemukan masalah atau kebocoran.
PS: Anda tidak perlu menggunakan kategori seperti yang saya lakukan jika pandangan yang bersangkutan adalah subkelas dari Anda. Dalam kasus terakhir, cukup masukkan metode ini ke dalam subkelas Anda dan Anda siap melakukannya.
Meskipun ini secara teknis dapat diselesaikan seperti rekomendasi pgb , IMHO, ini adalah cacat desain. Tampilan tidak perlu mewaspadai pengontrol.
Saya memodifikasi de answer sehingga saya bisa memberikan tampilan, tombol, label dll. Untuk mendapatkan orang tua itu UIViewController
. Ini kode saya.
+(UIViewController *)viewController:(id)view {
UIResponder *responder = view;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
Edit Versi 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)!
}
Sunting 2: - Ekstensi 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)!
}
}
Jangan lupa bahwa Anda bisa mendapatkan akses ke pengendali tampilan root untuk jendela yang tampilannya adalah subview. Dari sana, jika Anda mis. Menggunakan pengontrol tampilan navigasi dan ingin mendorong tampilan baru ke dalamnya:
[[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES];
Anda perlu mengatur properti rootViewController dari jendela dengan benar terlebih dahulu. Lakukan ini saat pertama kali Anda membuat controller misalnya di delegasi aplikasi Anda:
-(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]
merupakan tampilan "utama" (sub) dari jendela, rootViewController
properti jendela harus diatur ke navigationController
yang mengontrol tampilan "utama" dengan segera.
Meskipun jawaban ini secara teknis benar, termasuk Ushox, saya pikir cara yang disetujui adalah dengan mengimplementasikan protokol baru atau menggunakan kembali yang sudah ada. Protokol melindungi pengamat dari yang diamati, seperti menempatkan slot surat di antaranya. Akibatnya, itulah yang dilakukan Gabriel melalui pemanggilan metode pushViewController; tampilan "tahu" bahwa itu adalah protokol yang tepat untuk dengan sopan meminta navigationController Anda untuk mendorong tampilan, karena viewController sesuai dengan protokol navigationController. Meskipun Anda dapat membuat protokol sendiri, cukup gunakan contoh Gabriel dan gunakan kembali protokol UINavigationController.
Saya menemukan situasi di mana saya memiliki komponen kecil yang ingin saya gunakan kembali, dan menambahkan beberapa kode dalam tampilan yang dapat digunakan kembali sendiri (itu sebenarnya tidak lebih dari sebuah tombol yang membuka a PopoverController
).
Meskipun ini berfungsi dengan baik di iPad ( UIPopoverController
hadiah itu sendiri, karenanya tidak perlu referensi ke a UIViewController
), mendapatkan kode yang sama berfungsi berarti tiba-tiba merujuk Anda presentViewController
dari Anda UIViewController
. Agak tidak konsisten kan?
Seperti disebutkan sebelumnya, itu bukan pendekatan terbaik untuk memiliki logika di UIView Anda. Tetapi rasanya benar-benar tidak berguna untuk membungkus beberapa baris kode yang diperlukan dalam pengontrol terpisah.
Bagaimanapun, inilah solusi cepat, yang menambahkan properti baru ke UIView apa pun:
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
}
}
Saya tidak berpikir itu "buruk" ide untuk mencari tahu siapa pengontrol tampilan untuk beberapa kasus. Apa yang bisa menjadi ide yang buruk adalah menyimpan referensi ke pengontrol ini karena dapat berubah seperti perubahan superviews. Dalam kasus saya, saya memiliki seorang pengambil yang melintasi rantai responden.
//.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;
}
Simpel do while yang paling sederhana untuk menemukan viewController.
-(UIViewController*)viewController
{
UIResponder *nextResponder = self;
do
{
nextResponder = [nextResponder nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController*)nextResponder;
} while (nextResponder != nil);
return nil;
}
(lebih ringkas daripada jawaban lainnya)
fileprivate extension UIView {
var firstViewController: UIViewController? {
let firstViewController = sequence(first: self, next: { $0.next }).first(where: { $0 is UIViewController })
return firstViewController as? UIViewController
}
}
Kasus penggunaan saya yang saya perlu mengakses tampilan pertama UIViewController
: Saya memiliki objek yang membungkus AVPlayer
/ AVPlayerViewController
dan saya ingin memberikan show(in view: UIView)
metode sederhana yang akan AVPlayerViewController
dimasukkan ke dalam view
. Untuk itu, saya perlu untuk mengakses view
's UIViewController
.
Ini tidak menjawab pertanyaan secara langsung, tetapi membuat asumsi tentang maksud pertanyaan.
Jika Anda memiliki tampilan dan dalam tampilan itu Anda perlu memanggil metode pada objek lain, seperti katakanlah controller tampilan, Anda dapat menggunakan NSNotificationCenter sebagai gantinya.
Pertama buat string notifikasi Anda dalam file header
#define SLCopyStringNotification @"ShaoloCopyStringNotification"
Dalam panggilan view postNotificationName panggilan Anda:
- (IBAction) copyString:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:SLCopyStringNotification object:nil];
}
Kemudian di pengontrol tampilan, Anda menambahkan pengamat. Saya melakukan ini di viewDidLoad
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(copyString:)
name:SLCopyStringNotification
object:nil];
}
Sekarang (juga di controller tampilan yang sama) mengimplementasikan metode copyString Anda: seperti yang digambarkan dalam pemilih @ di atas.
- (IBAction) copyString:(id)sender
{
CalculatorResult* result = (CalculatorResult*)[[PercentCalculator sharedInstance].arrayTableDS objectAtIndex:([self.viewTableResults indexPathForSelectedRow].row)];
UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];
[gpBoard setString:result.stringResult];
}
Saya tidak mengatakan ini adalah cara yang tepat untuk melakukan ini, sepertinya lebih bersih daripada menjalankan rantai responden pertama. Saya menggunakan kode ini untuk mengimplementasikan UIMenuController pada UITableView dan meneruskan kembali peristiwa tersebut ke UIViewController sehingga saya dapat melakukan sesuatu dengan data tersebut.
Ini tentu saja ide yang buruk dan desain yang salah, tapi saya yakin kita semua dapat menikmati solusi Swift dari jawaban terbaik yang diajukan oleh @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)
}
Jika niat Anda adalah untuk melakukan hal-hal sederhana, seperti menampilkan dialog modal atau melacak data, itu tidak membenarkan penggunaan protokol. Saya pribadi menyimpan fungsi ini dalam objek utilitas, Anda dapat menggunakannya dari apa pun yang mengimplementasikan protokol UIResponder sebagai:
if let viewController = MyUtilityClass.firstAvailableUIViewController(self) {}
Semua kredit ke @Phil_M
Mungkin saya terlambat ke sini. Tetapi dalam situasi ini saya tidak suka kategori (polusi). Saya suka seperti ini:
#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})
Solusi yang lebih cepat
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). Jadi jika "cepat" berarti "lebih fungsional", maka saya kira itu lebih cepat.
Versi terbaru untuk swift 4: Terima kasih atas @Phil_M dan @ 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)
}
Versi 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
}
Contoh penggunaan
if let parent = self.view.parentViewController{
}
return
kata kunci sekarang 🤓Solusi 1:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.first(where: { $0 is UIViewController })
.flatMap { $0 as? UIViewController }
}
}
Solusi 2:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.compactMap{ $0 as? UIViewController }
.first
}
}
Untuk jawaban Phil:
Sejalan: id nextResponder = [self nextResponder];
jika diri (UIView) bukan subview dari pandangan ViewController, jika Anda tahu hierarki diri (UIView) Anda dapat menggunakan juga: id nextResponder = [[self superview] nextResponder];
...
Solusi saya mungkin akan dianggap sebagai jenis palsu tetapi saya memiliki situasi yang sama dengan mayoneez (saya ingin mengalihkan pandangan sebagai tanggapan terhadap isyarat dalam EAGLView), dan saya mendapatkan pengontrol tampilan EAGL dengan cara ini:
EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;
EAGLViewController *vc = [(EAGLAppDelegate *)[UIApplication sharedApplication].delegate viewController];
.
ClassName *object
- dengan tanda bintang.
Saya pikir ada kasus ketika yang diamati perlu memberi tahu pengamat.
Saya melihat masalah yang sama di mana UIView dalam UIViewController merespons situasi dan pertama-tama perlu memberi tahu pengontrol tampilan induknya untuk menyembunyikan tombol kembali dan kemudian setelah selesai memberi tahu pengontrol tampilan induk bahwa ia perlu keluar dari tumpukan.
Saya telah mencoba ini dengan delegasi yang tidak berhasil.
Saya tidak mengerti mengapa ini ide yang buruk?
Cara mudah lain adalah memiliki kelas tampilan Anda sendiri dan menambahkan properti controller tampilan di kelas tampilan. Biasanya pengontrol tampilan membuat tampilan dan di sanalah pengontrol dapat mengatur sendiri ke properti. Pada dasarnya itu bukan mencari-cari (dengan sedikit peretasan) untuk controller, memiliki controller untuk mengatur sendiri ke tampilan - ini sederhana tetapi masuk akal karena itu adalah controller yang "mengontrol" tampilan.
Jika Anda tidak akan mengunggah ini ke App Store, Anda juga dapat menggunakan metode pribadi 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
}
Jika rootViewController Anda adalah UINavigationViewController, yang dibuat di kelas AppDelegate, maka
+ (UIViewController *) getNearestViewController:(Class) c {
NSArray *arrVc = [[[[UIApplication sharedApplication] keyWindow] rootViewController] childViewControllers];
for (UIViewController *v in arrVc)
{
if ([v isKindOfClass:c])
{
return v;
}
}
return nil;}
Di mana c diperlukan kelas pengontrol tampilan.
PEMAKAIAN:
RequiredViewController* rvc = [Utilities getNearestViewController:[RequiredViewController class]];
Tidak ada jalan.
Apa yang saya lakukan adalah meneruskan pointer UIViewController ke UIView (atau warisan yang sesuai). Maaf saya tidak bisa membantu dengan pendekatan IB untuk masalah karena saya tidak percaya pada IB.
Untuk menjawab komentator pertama: kadang-kadang Anda perlu tahu siapa yang memanggil Anda karena itu menentukan apa yang dapat Anda lakukan. Misalnya dengan database Anda mungkin hanya memiliki akses baca atau baca / tulis ...