Чи є вбудований спосіб дістатися від точки UIView
до його UIViewController
? Я знаю, що ви можете дістатися UIViewController
до його UIView
через, [self view]
але мені було цікаво, чи є зворотна довідка?
Чи є вбудований спосіб дістатися від точки 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 і створити протокол, який буде використовуватися як делегат.
Використовуючи приклад, опублікований Броком, я змінив його так, що він є категорією 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
Щоб використовувати цей код, додайте його до нового файлу класу (я назвав шахту "UIKitCategories") і видаліть дані класу ... скопіюйте @interface у заголовок, а @implementation - у файл .m. Потім у своєму проекті #import "UIKitCategories.h" та використовуйте в коді UIView:
// from a UIView subclass... returns nil if UIViewController not available
UIViewController * myController = [self firstAvailableUIViewController];
UIView
є підкласом 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
. Відповідь Філа М із рекурсією - це шлях.
Я б запропонував більш легкий підхід для проходження всього ланцюжка реагування без необхідності додавати категорію в 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, це є вадою дизайну. Погляд не повинен знати про контролер.
Я змінив де відповідь , щоб я міг пройти будь-який вид, кнопки, етикетки і т.д. , щоб отримати його батьків 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
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: - Швидкий розтяг
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 відповідає протоколу navigationController. Хоча ви можете створити власний протокол, просто використовуючи приклад Габріеля та повторне використання протоколу 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
. Для цього мені потрібно отримати доступ view
до UIViewController
.
Це не відповідає на питання безпосередньо, а скоріше робить припущення про наміри питання.
Якщо у вас є перегляд, і в цьому поданні вам потрібно викликати метод на іншому об'єкті, наприклад скажіть контролер перегляду, замість цього можна використовувати NSNotificationCenter.
Спочатку створіть рядок сповіщень у файлі заголовка
#define SLCopyStringNotification @"ShaoloCopyStringNotification"
На ваш погляд, виклик postNotificationName:
- (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, щоб я міг щось зробити з даними.
Це, звичайно, погана ідея і неправильний дизайн, але я впевнений, що ми всі зможемо насолодитися швидким рішенням найкращої відповіді, запропонованого @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
біт). Тож якщо "стрімкий" означає "більш функціональний", то, мабуть, він швидший.
Оновлена версія для 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
}
}
Моє рішення, ймовірно, вважатиметься невірним, але у мене була подібна ситуація, як і майонез (я хотів переключити погляди у відповідь на жест в 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];
Якщо ваш 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;}
Там, де вимагається клас контролерів перегляду.
ВИКОРИСТАННЯ:
RequiredViewController* rvc = [Utilities getNearestViewController:[RequiredViewController class]];
Немає способу.
Те, що я роблю, - передати покажчик UIViewController на UIView (або відповідну спадщину). Вибачте, що не можу допомогти з підходом до цієї проблеми, оскільки не вірю в ІБ.
Щоб відповісти першому коментатору: іноді вам потрібно знати, хто вас зателефонував, оскільки це визначає, що ви можете зробити. Наприклад, у базі даних, можливо, ви мали лише доступ для читання або читання / запис ...