Var mı yerleşik bir şekilde bir almak UIView
onun için UIViewController
? UIViewController
Onun UIView
üzerinden alabilirsiniz biliyorum [self view]
ama ters bir referans olup olmadığını merak ediyordum?
Var mı yerleşik bir şekilde bir almak UIView
onun için UIViewController
? UIViewController
Onun UIView
üzerinden alabilirsiniz biliyorum [self view]
ama ters bir referans olup olmadığını merak ediyordum?
Yanıtlar:
Bu uzun zamandır kabul edilen cevap olduğu için, daha iyi bir cevapla düzeltmem gerektiğini hissediyorum.
İhtiyaç üzerine bazı yorumlar:
Nasıl uygulanacağına dair bir örnek aşağıdadır:
@protocol MyViewDelegate < NSObject >
- (void)viewActionHappened;
@end
@interface MyView : UIView
@property (nonatomic, assign) MyViewDelegate delegate;
@end
@interface MyViewController < MyViewDelegate >
@end
Görünüm, temsilcisiyle ( UITableView
örneğin, olduğu gibi) arabirim oluşturur ve görünüm denetleyicisinde veya sonunda kullandığınız başka bir sınıfta uygulanıp uygulanmadığı önemli değildir.
Orijinal cevabım şu: Bunu önermiyorum, ne de görünüm denetleyicisine doğrudan erişimin sağlandığı cevapların geri kalanı
Bunu yapmanın yerleşik bir yolu yok. Bir ekleyerek etrafında almak mümkün olmakla birlikte IBOutlet
üzerinde UIView
ve Interface Builder bu bağlantı, bu tavsiye edilmez. Görünüm, görünüm denetleyicisini bilmemelidir. Bunun yerine, @Phil M'nin önerdiği gibi yapmalı ve delege olarak kullanılacak bir protokol oluşturmalısınız.
Brock tarafından yayınlanan örneği kullanarak, UIViewController yerine UIView bir kategori olacak şekilde değiştirdim ve herhangi bir alt görünüm (umarım) üst UIViewController bulabilmesi için özyinelemeli hale getirdim.
@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
Bu kodu kullanmak için, yeni bir sınıf dosyasına ekleyin (mayın "UIKitCategories" adını verdim) ve sınıf verilerini kaldırın ... @interface başlığa ve @implementation dosyasını .m dosyasına kopyalayın. Ardından projenizde #import "UIKitCategories.h" dosyasını içe aktarın ve UIView kodu içinde kullanın:
// from a UIView subclass... returns nil if UIViewController not available
UIViewController * myController = [self firstAvailableUIViewController];
UIView
bir alt sınıfıdır UIResponder
. UIResponder
yöntemi -nextResponder
döndüren bir uygulama ile ortaya koyar nil
. UIView
aşağıdaki gibi belgelendiği gibi UIResponder
(bu nedenle içinde değil UIView
) bu yöntemi geçersiz kılar : görünümde bir görünüm denetleyicisi varsa, tarafından döndürülür -nextResponder
. Herhangi bir görünüm denetleyicisi yoksa, yöntem denetimi döndürür.
Bunu projenize ekleyin ve ilerlemeye hazırsınız.
@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
Artık UIView
görünüm denetleyicisini döndürmek için çalışan bir yöntem var.
UIView
ile UIViewController
. Arasında yanıtlayıcı zincirinde hiçbir şey yoksa çalışır . Phil M'in özyineleme özelliğine sahip yanıtı gitmenin yolu.
Ben UIView üzerinde bir kategori eklemek zorunda kalmadan, tam yanıtlayıcı zincirini geçmek için daha hafif bir yaklaşım öneriyoruz:
@implementation MyUIViewSubclass
- (UIViewController *)viewController {
UIResponder *responder = self;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
@end
Zaten verilen birkaç cevabı birleştirerek, uygulamamla da gönderiyorum:
@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, oluşturduğum her uygulamada gönderdiğim ARC etkin statik kütüphanemin bir parçası . Birkaç kez test edildi ve herhangi bir sorun veya sızıntı bulamadım.
Not: İlgili görünüm sizin alt sınıfınızsa benim yaptığım gibi bir kategori kullanmanıza gerek yoktur. İkinci durumda, yöntemi alt sınıfınıza koyun ve hazırsınız.
Bu teknik olarak pgb'nin önerdiği gibi IMHO tarafından çözülebilse de , bu bir tasarım hatasıdır. Görünümün kontrolörden haberdar olması gerekmez.
Değiştirilmiş de ben, herhangi görünümü, düğmeye geçmesi 's üst almak için vs. etiket böylece cevabı UIViewController
. İşte kodum.
+(UIViewController *)viewController:(id)view {
UIResponder *responder = view;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
Swift 3 Sürümünü Düzenle
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)!
}
Düzenleme 2: - Swift Uzantısı
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)!
}
}
Görünümün alt görünümü olduğu pencere için kök görünüm denetleyicisine erişebileceğinizi unutmayın. Oradan, örneğin bir navigasyon görünüm denetleyicisi kullanıyorsanız ve üzerine yeni bir görünüm göndermek istiyorsanız:
[[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES];
Ancak, önce pencerenin rootViewController özelliğini doğru şekilde ayarlamanız gerekir. Denetleyiciyi ilk kez oluşturduğunuzda, örneğin uygulama temsilcinizde:
-(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];
}
Bu cevaplar Ushox dahil teknik olarak doğru olsa da, onaylanan yolun yeni bir protokol uygulamak veya mevcut bir protokolü yeniden kullanmak olduğunu düşünüyorum . Bir protokol, gözlemciyi, aralarına bir posta yuvası koymak gibi, gözlemlenen gibi yalıtır. Aslında Gabriel, pushViewController yöntemi çağırma yoluyla bunu yapar; viewController, navigationController protokolüne uyduğundan, görünüm, navigationController cihazınızdan kibarca bir görünüm göndermesini istemek için uygun protokol olduğunu bilir. Kendi protokolünüzü oluşturabilirken, sadece Gabriel'in örneğini kullanarak ve UINavigationController protokolünü tekrar kullanmak gayet iyi.
Yeniden kullanmak istediğim küçük bir bileşene sahip olduğum bir durum üzerine tökezledim ve yeniden kullanılabilir bir görünümde bazı kodlar ekledim (gerçekten açılan bir düğmeden çok fazla değil PopoverController
).
Bu (iPad cezası çalışır iken UIPopoverController
bir hiçbir başvuru ihtiyacı bunun hediyelerin kendisi UIViewController
aniden referans çalışma araçlarına aynı kodu alma,) presentViewController
, hesabınızla ilgili UIViewController
. Biraz tutarsız değil mi?
Daha önce de belirtildiği gibi, UIView'inizde mantığa sahip olmak en iyi yaklaşım değildir. Ancak, ayrı bir denetleyicide gereken birkaç kod satırını sarmak gerçekten işe yaramaz hissetti.
Her iki durumda da, herhangi bir UIView'e yeni bir özellik ekleyen hızlı bir çözüm:
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
}
}
Bazı durumlarda görünüm denetleyicisinin kim olduğunu bulmanın "kötü" bir fikir olduğunu düşünmüyorum. Kötü bir fikir, kontrolör değiştikçe değişebileceğinden referansı bu kontrolöre kaydetmektir. Benim durumumda, cevaplayıcı zincirini geçen bir alıcı var.
//.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'ı bulmak için en basit do döngüsü.
-(UIViewController*)viewController
{
UIResponder *nextResponder = self;
do
{
nextResponder = [nextResponder nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController*)nextResponder;
} while (nextResponder != nil);
return nil;
}
(diğer cevaplardan daha kısa)
fileprivate extension UIView {
var firstViewController: UIViewController? {
let firstViewController = sequence(first: self, next: { $0.next }).first(where: { $0 is UIViewController })
return firstViewController as? UIViewController
}
}
Önce görünümüne erişmek için gereken benim kullanım durum UIViewController
: AVPlayer
/ / etrafında saran bir nesne var AVPlayerViewController
ve içine show(in view: UIView)
gömmek basit bir yöntem sağlamak istiyorum . Bunun için, erişmeye gerek 's .AVPlayerViewController
view
view
UIViewController
Bu, soruyu doğrudan cevaplamaz, aksine sorunun amacı hakkında bir varsayım yapar.
Bir görünümünüz varsa ve bu görünümde, görünüm denetleyicisi gibi başka bir nesnede bir yöntem çağırmanız gerekir, bunun yerine NSNotificationCenter'ı kullanabilirsiniz.
Önce bildirim dizenizi bir başlık dosyasında oluşturun
#define SLCopyStringNotification @"ShaoloCopyStringNotification"
Görünümünüzde postNotificationName arayın:
- (IBAction) copyString:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:SLCopyStringNotification object:nil];
}
Sonra görünüm denetleyicinize bir gözlemci eklersiniz. Bunu viewDidLoad içinde yapıyorum
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(copyString:)
name:SLCopyStringNotification
object:nil];
}
Şimdi (aynı görünüm denetleyicisinde de) yukarıdaki @selector'da gösterildiği gibi copyString: yönteminizi uygulayın.
- (IBAction) copyString:(id)sender
{
CalculatorResult* result = (CalculatorResult*)[[PercentCalculator sharedInstance].arrayTableDS objectAtIndex:([self.viewTableResults indexPathForSelectedRow].row)];
UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];
[gpBoard setString:result.stringResult];
}
Bunu yapmanın doğru yolu olduğunu söylemiyorum, sadece ilk yanıtlayıcı zincirini çalıştırmaktan daha temiz görünüyor. Bu kodu bir UITableView üzerinde bir UIMenuController uygulamak ve veri ile bir şeyler yapmak böylece olay geri UIViewController kadar geçirmek için kullanılır.
Kesinlikle kötü bir fikir ve yanlış bir tasarım, ama eminim hepimiz @Phil_M tarafından önerilen en iyi cevabın Swift çözümünün tadını çıkarabiliriz:
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)
}
Amacınız, kalıcı bir iletişim kutusu göstermek veya verileri izlemek gibi basit şeyler yapmaksa, bu bir protokol kullanımını haklı çıkarmaz. Şahsen bu işlevi bir yardımcı program nesnesinde saklıyorum, UIResponder protokolünü uygulayan herhangi bir şeyden kullanabilirsiniz:
if let viewController = MyUtilityClass.firstAvailableUIViewController(self) {}
@Phil_M'ye tüm kredi
Belki buraya geç kaldım. Ancak bu durumda kategoriden (kirlilik) hoşlanmıyorum. Bu şekilde seviyorum:
#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})
Daha hızlı çözüm
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) güzel bir gösterisine sahipsiniz . Eğer "swifty" "daha işlevsel" anlamına geliyorsa, sanırım daha hızlı olur.
Hızlı 4 için güncellenmiş sürüm: @Phil_M ve @ paul-slm için teşekkürler
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 sürümü
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
}
Kullanım örneği
if let parent = self.view.parentViewController{
}
return
anahtar kelimeye gerek yok 🤓Çözüm 1:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.first(where: { $0 is UIViewController })
.flatMap { $0 as? UIViewController }
}
}
Çözüm 2:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.compactMap{ $0 as? UIViewController }
.first
}
}
Benim çözüm muhtemelen tür sahte kabul olurdu ama mayoneez ile benzer bir durum vardı (bir EAGLView bir jest yanıt olarak görünümleri değiştirmek istedim) ve ben EAGL's görünüm denetleyicisi bu şekilde var:
EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;
EAGLViewController *vc = [(EAGLAppDelegate *)[UIApplication sharedApplication].delegate viewController];
.
ClassName *object
- yıldız işareti ile bildirmek .
Gözlemcinin gözlemciyi bilgilendirmesi gereken bir durum olduğunu düşünüyorum.
Bir UIViewController UIView bir duruma yanıt veriyor benzer bir sorun görüyorum ve öncelikle üst görünüm denetleyicisine geri düğmesini gizlemek için söyle ve sonra tamamlandığında üst görünüm denetleyicisine kendisini yığının dışına çıkarmak gerektiğini söyle.
Bunu başarılı olmayan delegelerle deniyorum.
Bunun neden kötü bir fikir olması gerektiğini anlamıyorum?
Başka bir kolay yol, kendi görünüm sınıfınıza sahip olmak ve görünüm sınıfında görünüm denetleyicisinin bir özelliğini eklemektir. Genellikle görünüm denetleyicisi görünümü oluşturur ve burada denetleyici kendini özelliğe ayarlayabilir. Temel olarak, denetleyiciyi görünüme ayarlamak için denetleyiciye sahip olmak, denetleyiciyi aramak (biraz hack'le) yerine - bu basittir, ancak mantıklıdır, çünkü görünümü "kontrol eden" denetleyicidir.
Bunu App Store'a yüklemeyecekseniz, özel bir UIView yöntemi de kullanabilirsiniz.
@interface UIView(Private)
- (UIViewController *)_viewControllerForAncestor;
@end
// Later in the code
UIViewController *vc = [myView _viewControllerForAncestor];
RootViewController'ınız AppDelegate sınıfında kurulmuş olan UINavigationViewController ise,
+ (UIViewController *) getNearestViewController:(Class) c {
NSArray *arrVc = [[[[UIApplication sharedApplication] keyWindow] rootViewController] childViewControllers];
for (UIViewController *v in arrVc)
{
if ([v isKindOfClass:c])
{
return v;
}
}
return nil;}
Burada c gerekli görünüm denetleyicileri sınıfı.
KULLANIM:
RequiredViewController* rvc = [Utilities getNearestViewController:[RequiredViewController class]];
Hiçbir yolu yok.
Ne UIViewController işaretçisini UIView (veya uygun bir miras) geçmek olduğunu. Soruna IB yaklaşımında yardımcı olamadığım için özür dilerim çünkü IB'ye inanmıyorum.
İlk yorumcuyu cevaplamak için: bazen sizi kimin aradığını bilmeniz gerekir, çünkü ne yapabileceğinizi belirler. Örneğin bir veritabanıyla yalnızca okuma erişimine veya okuma / yazma özelliğine sahip olabilirsiniz ...