I have a bunch of subViews in my ViewController.
In the last layer I have a UIView, and from this view I want to call superview and go up until I find the UIView that belongs to my ViewController.
Is it possible to find out whether a UIView belongs to a ViewController or not?
UIView *someView = self.superView;
while (true)
{
if (someView BELONGS TO VIEWCONTROLLER)
{
// Now we know this view belongs to a VIewController
break;
}
someView = someView.superView;
}
If you want to find out if a certain view is in the hierarchy managed by a view controller and you have a pointer to the view controller:
BOOL belongsToController = [aView isDescendantOfView:viewController.view];
Alternatively, if you want to find out if a certain view is the root of the hierarchy managed by the view controller but you don't have a pointer to the view controller, you can traverse the responder chain. According to the UIResponder's nextResponder documentation:
UIView implements this method by returning the UIViewController object that manages it (if it has one) or its superview (if it doesn’t)
Therefore, if the next responder of a certain view is a UIViewController, that view must be the view associated with the view controller.
if ([[aView nextResponder] isKindOfClass:[UIViewController class]]) {
// aView is the root of the view hierarchy managed by the view controller
}
Vlad's and albertamg's approaches are correct as well. However you can also traverse the responder chain
for (UIView* next = [self superview]; next; next = next.superview) {
UIResponder* nextResponder = [next nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]]) {
UIViewController *theControllerThatYouWANT = (UIViewController*)nextResponder;
}
}
try going up in the hierarchy of views and check if current view object is the same as your controller's view
Code would be something like this: (wrote in textEdit, don't have dev tools here, sorry if any mistakes)
-(BOOL)view:(UIView *)aView belongsToController:(UIViewController *)viewController {
BOOL belongsToController = NO;
UIView *someView = [aView superView];
while (someView != nil) {
if (viewController.view == someView) {
belongsToController = YES;
}
someView = [someView superView];
}
return belongsToController;
}
just tested it and it works for me. I hope it was helpfull.
Vlad
Related
When the UIDocumentInteractionController is dismissed, the presenting view controller's views are removed, including elements from the UINavigationController.
The UIDocumentInteractionController dismisses and the presenting view controller's views are removed, leaving a plain white/gray box where the presenting view controller formerly existed. The app no longer responds to any touch events after this point.
This Occurs on iPad Simulator (iOS 7.0) and iPad 3 (Wifi) running iOS 7 for Quick Look Pdf Reader.
Does not matter whether the application was compiled against the iOS 6.1 or iOS 7 SDK
Please let me know your suggestions.
I have this same problem when presenting a UIDocumentInteractionController from a view controller presented as a modal form sheet on iPad in iOS 7 (worked fine in iOS 6).
It looks like that during the transition from the document interaction controller back to the presenting view controller, the presenting view controller's view is wrapped in a temporary UITransitionView, and then that transition view is being removed from the view hierarchy after the transition is complete, along with the presenting view controller's view, leaving just UIDropShadowView that is the backing view of the modal form sheet visible (the gray box).
I worked around the problem by keeping a reference to my presenting view controller's root view (the one just before the drop shadow view in the hierarchy) when the document interaction controller preview will begin, and restoring that view to the hierarchy when the document interaction controller preview has ended.
Here's sample code:
- (void)documentInteractionControllerWillBeginPreview:(__unused UIDocumentInteractionController *)controller {
if (UIUserInterfaceIdiomPad == UI_USER_INTERFACE_IDIOM()) {
// work around iOS 7 bug on ipad
self.parentView = [[[self.view superview] superview] superview];
self.containerView = [self.parentView superview];
if (![[self.containerView superview] isKindOfClass: [UIWindow class]]) {
// our assumption about the view hierarchy is broken, abort
self.containerView = nil;
self.parentView = nil;
}
}
}
- (void)documentInteractionControllerDidEndPreview:(__unused UIDocumentInteractionController *)controller {
if (UIUserInterfaceIdiomPad == UI_USER_INTERFACE_IDIOM()) {
if (!self.view.window && self.containerView) {
assert(self.parentView);
CGRect frame = self.parentView.frame;
frame.origin = CGPointZero;
self.parentView.frame = frame;
[self.containerView addSubview: self.parentView];
self.containerView = nil;
self.parentView = nil;
}
}
}
I found Michael Kuntscher answer to be right on target. A slight modification is necessary if the UIDocumentInteractionController is presented from a popover.
There is a slight dependence on the view hierarchy that can be eliminated by iterating over the parent views until one with a UIWindow superview is found. In addition, I found that when a document interaction controller is presented from within a popover it is slightly different views that need to be stored as the parentView and containerView (specifically we want to find a containerView such that its superview is a UIPopoverView). The following snippet is a re-worked version of Michael's answer to incorporate these changes (note that UIPopoverView is a private class, so we use string representations of the class rather than making a direct reference to each class):
- (void)documentInteractionControllerWillBeginPreview:(UIDocumentInteractionController *)controller {
/* iOS 7 DIC bug workaround */
if (UIUserInterfaceIdiomPad == UI_USER_INTERFACE_IDIOM()) {
UIView *a_view = self.view;
self.dicParentView = nil;
self.dicContainerView = nil;
while (a_view != nil) {
UIView *super_super_view = [[a_view superview] superview];
NSString *str_class = NSStringFromClass([super_super_view class]);
if ([str_class isEqualToString:#"UIWindow"] ||
[str_class hasSuffix:#"PopoverView"]) {
self.dicParentView = a_view;
self.dicContainerView = [a_view superview];
break;
}
a_view = [a_view superview];
}
if (self.dicParentView == nil) {
NSLog(#"Could not appropriate superview, unable to workaround DIC bug");
}
}
/* end work around */
}
- (void)documentInteractionControllerDidEndPreview:(__unused UIDocumentInteractionController *)controller {
/* iOS 7 DIC bug workaround */
if (UIUserInterfaceIdiomPad == UI_USER_INTERFACE_IDIOM()) {
if ((self.view.window == nil) &&
(self.dicContainerView != nil) &&
(self.dicParentView != nil)) {
NSLog(#"Restoring view for DIC presenter in the view hierarchy");
CGRect frame = self.dicParentView.frame;
frame.origin = CGPointZero;
self.dicParentView.frame = frame;
[self.dicContainerView addSubview: self.dicParentView];
self.dicContainerView = nil;
self.dicParentView = nil;
}
}
/* end work around */
}
I had the exact same problem with the app stalling on a grey form sheet modal view that had lost all of its content after a UIDocumentInteractionController had been presented and dismissed. The two solutions here are great but I simplified them to cater for my particular case, which was a UINavigationController inside a form sheet modal that can present a PDF in the UIDocumentInteractionController, which I wanted to be full screen modal instead of pushed in the navigation controller because the form sheet area is too small for the PDF to be easily readable.
I implemented two UIDocumentInteractionControllerDelegate methods. Assume the following:
self.navController is a reference to the UINavigationController that is presented inside the form sheet modal.
there is a member variable declared in the UIViewController subclass #property (nonatomic, strong) UIView* docInteractionControllerWorkaroundSuperview;
SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO is a #define for ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
Firstly:
-(UIViewController*)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController*)controller
{
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"7.0") && self.navigationController.modalPresentationStyle == UIModalPresentationFormSheet)
{
self.docInteractionControllerWorkaroundSuperview = [self.navigationController.view superview];
}
return self.navigationController.visibleViewController;
}
then:
- (void)documentInteractionControllerDidEndPreview:(UIDocumentInteractionController *)controller
{
if (self.docInteractionControllerWorkaroundSuperview != nil)
{
NSLog(#"Workaround iOS 7 document interaction bug... resetting nav controller view into modal");
//reset the nav controller view from whence it came.
self.navigationController.view.frame = CGRectMake(0.0, 0.0, self.navigationController.view.frame.size.width, self.navigationController.view.frame.size.height);
[self.docInteractionControllerWorkaroundSuperview addSubview:self.navigationController.view];
self.docInteractionControllerWorkaroundSuperview = nil;
}
}
i.e. when presenting the UIDocumentInteractionController, look at the superview of the navigation controller's view. It works out to be a UIDropShadowView which I assume is the partially transparent grey/black background to the form sheet modal that dims out the view behind that presented the modal.
When the PDF has been dismissed, in documentInteractionControllerDidEndPreview the superview of the navigation controller's view is now a UITransistionView. But then shortly after that (when the transition has completed), it gets set to nil. Somehow it got detached (or didn't get re-attached) from the UIDropShadowView. By keeping a reference to view this when presenting the UIDocumentInteractionController, we can reattach it manually and everything works fine. Then be sure to nil out the reference to make sure we don't accidentally retain it.
This method does not affect the behaviour on iOS 6 or any previous iOS versions.
Is their any Possibilty to Check which viewController is running in IPhone application Programmatically in Appdelegate
There is no easy answer to this. You need to walk the view controller hierarchy starting with the main window's root view controller. If you encounter a UINavigationController you need to look at the topViewController. Once you get to a UIViewController, you need to look at the modalViewController, if any. If you have any tab bar controllers then you need to look at the currently selected tab.
Things like UISplitViewController complicates things since this can show two view controllers at once.
Here is the start of a category method you could add to UIViewController. This only handles regular view controllers and navigation controllers.
- (UIViewController *)topMostController {
if (self.modalViewController) {
return [self.modalViewController topMostController];
} else {
if ([self isKindOfClass:[UINavigationController class]]) {
UINavigationController *nc = (UINavigationController *)self;
return [nc.topViewController topMostController];
} else {
return self;
}
}
}
Call this from your app delegate on the key window's rootViewController.
Assuming you've set the rootViewController property in your AppDelegate:
[UIApplication sharedApplication].keyWindow.rootViewController;
For view controller it is not possible to get the curent running viewcontroller name.
for that you write one following method in your app delegate file & then call getCurentViewController method in each viewcontroller view did load or view did appear if you are not allocating agin by passing self to it
-(void) getCurentViewController:(UIViewController*) vc
{
if([vc isMemberOfClass:NSClassFromString(#"vcName")])
{
//write your code here
}
else if([vc isMemberOfClass:NSClassFromString(#"vcName1")])
{
//write your code here
}
}
UIViewController *currentViewController = yourRootViewController;
while (currentViewController.presentedViewController) {
currentViewController = currentViewController.presentedViewController;
}
//currentViewController is now your top-most viewController
//I use this same snippet in my production code
I have several view controllers with one or multiple scrollviews. Although I have explicitly set the scrollsToTop flags in view controllers with more than one scroll view, some scroll views refuse to scroll up when I tap the status bar.
After pushing another view controller and popping it the gesture sometimes works in the view it previously hasn't.
It's very confusing and I just don't know what the problem is. How can this issue effectively be debugged? Is there a global (private) notification for the status bar tap so I could scroll the views manually?
I have used code like the following to debug this scenario, before:
- (void)findMisbehavingScrollViews
{
UIView *view = [[UIApplication sharedApplication] keyWindow];
[self findMisbehavingScrollViewsIn:view];
}
- (void)findMisbehavingScrollViewsIn:(UIView *)view
{
if ([view isKindOfClass:[UIScrollView class]])
{
NSLog(#"Found UIScrollView: %#", view);
if ([(UIScrollView *)view scrollsToTop])
{
NSLog(#"scrollsToTop = YES!");
}
}
for (UIView *subview in [view subviews])
{
[self findMisbehavingScrollViewsIn:subview];
}
}
Depending on how many UIScrollViews you find, you can modify that code to help debug your particular situation.
Some ideas:
Change the background colors of the various scrollviews to identify them on screen.
Print the view hierarchy of those scrollviews to identify all of their superviews.
Ideally, you should only find a single UIScrollView in the window hierarchy that has scrollsToTop set to YES.
I changed the accepted answers into swift for convenience:
Swift 5
func findMisbehavingScrollViews() {
let topView = UIApplication.shared.windows.first { $0.isKeyWindow }
findMisbehavingScrollViewsIn(topView!)
}
func findMisbehavingScrollViewsIn(_ topView: UIView) {
if let topView = topView as? UIScrollView {
if topView.scrollsToTop {
print("Found UIScrollView: \(topView)")
}
for nextView in topView.subviews {
findMisbehavingScrollViewsIn(nextView)
}
}
}
I have a UIView subclass -
#interface DatePickerPopup : UIView
UIToolbar *toolbar;
UIDatePicker *datePicker;
#end
#implementation
- (id)initWithFrame:(CGRect)frame
{
NSArray *xib =
[[NSBundle mainBundle]
loadNibNamed:#"DatePickerPopup"
owner:self
options:nil];
self = [xib objectAtIndex:0];
if (self) {
}
return self;
}
#end
and the nib looks like -
In my UIViewController containing the DatePickerPopup (datePopup):
- (void)viewDidLoad
{
datePopup = [[DatePickerPopup alloc] initWithRect:CGRectZero];
CGRect newFrame = datePopup.frame;
newFrame.y = 200.0f; //lets say this aligns it to the bottom in portrait
datePopup.frame = newFrame;
// Normally happens when accessory button pressed but for brevity...
[self.view.superview addSubview:datePopup];
}
- (void)willAnimateRotationToInterfaceOrientation:
(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration
{
CGRect screen = [[UIScreen mainScreen] bounds];
if (toInterfaceOrientation == UIInterfaceOrientationPortrait ||
toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
{
self.datePopup.frame =
CGRectMake(0.0f, newHeightPortrait, screen.size.width, 260.0f);
}
else
{
self.datePopup.frame =
CGRectMake(0.0f, newHeightLandscape, screen.size.width, 260.0f);
}
}
However, this gets stretched out for some reason when the orientation changes the view gets stretched to the height of the screen bounds - the navigation bar...
after viewDidLoad
after willAutorotate...
Since your view controller appears to be managed by a navigation controller, calling [self.view.superview addSubview:datePopup]; adds your popup as a subview of a UIViewControllerWrapperView, which is one of the private classes UIKit uses to implement the functionality of UINavigationController. Messing with UIKit's private view hierarchy is always risky. In this case, based on the behavior you're seeing, it seems likely that UIKit expects any subview of UIViewControllerWrapperView to be a view controller's view, so it resizes your popup accordingly.
I think the safest way to resolve this is to have your view controller's view be a wrapper that contains your tableView and, when necessary, your popup view. Unfortunately using a wrapper view means that the view controller can't be a UITableViewController. You'll have to change the superclass to UIViewController, set a custom tableView property, and manually adopt the UITableViewDataSource and UITableViewDelegate protocols.
Note: You might be tempted to add your popover as a subview of your window, but I'm not recommending that because UIWindow only autorotates its topmost subview corresponding to a view controller. This means that if you add your popover to your window, it won't autorotate.
EDIT: BTW, by reassigning self = [xib objectAtIndex:0]; in initWithFrame:, you're leaking the object that was originally alloc'd. If you're going to reassign self in this way, you should release the existing object first.
Add the
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
method in the viewController class and return YES. If this method returns YES, only then will the device support landscape orientation. Try out this extra code and see if it helps...
You can set the frame size for landscape in this method itself instead of the current method. PS: I just saw you've used a UIView instead of controller...you might want to change to controller.
I have a View Controller that initializes a UIView as its view. That view initializes another UIView as a subview. Both UIViews communicate with the View Controller through a delegate/protocol.
Each UIView creates an instance of the ViewController and makes it equal to the delegate:
ViewController *aDelegate = [[ViewController alloc] init];
self.delegate = aDelegate;
PROBLEM: The View Controller has a variable called (int)selection that is modified by both UIViews. Both views must know how each other modified the variable, but since each has a different instance of the View Controller that communication is impossible. How would I fix this problem?
Thanks a ton
EDIT: Peter mentioned assigning the delegate at the views creation which I like, but how would I do that for the subview since it is created in the UIView and not the View Controller. PS. In reality it is a subview of a subview of a subview so can I create them all in the View Controller and then assign it as the delegate?
Tried assigning the delegate as follows but it continually crashes when I attempt to call a ViewController method from the view:
MyView *mainView = [[MyView alloc] initWithFrame:frame];
self.view = mainView;
mainView.delegate = self;
[mainView release];
The views does not need to know about each other. In your view controller you define a property for the sub view
#property (nonatomic, retain) MyView *myView;
Then you create your sub view and assign the delegate. This can be done in viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
CGRect frame = ...;
MyView *subView = [[MyView alloc] initWithFrame:frame];
self.myView = subView;
subView.delegate = self;
[self.view addSubView:subView];
[subView release];
self.view.delegate = self;
}
Then in your delegate method, which I just guessed how it could look, you can update the view controller as well as the other view.
- (void)view:(MyView *)view didUpdateSelection:(int)newSelection {
self.selection = newSelection;
if (view == self.view) {
self.myView.selection = newSelection;
}
else {
self.view.selection = newSelection;
}
}
It sounds like instead of each view allocating a separate instance of the view controller, you want to assign the instance of the view controller that created the views as the delegate of each view.
One way to approach this is to have the view controller assign itself as the view's delegate when it creates the view.