Logging the class name of all UIViewControllers in a project
Asked Answered
H

7

12

We have received a HUGE project from outsourcing that we are trying to "repair". There are hundreds of view controllers within the project. Our goal is to easily determine which class we are currently looking at on the device.

Our solution (which didn't work, hence the SO question) follows.

Override the viewDidAppear method of UIViewController via a category with this:

-(void)viewDidAppear:(BOOL)animated
{
    NSLog(@"Current View Class: %@", NSStringFromClass(self.class));
    [self viewDidAppear:animated];
    //Also tried this:
    //[super viewDidAppear:animated];
}

This category would be put in the .pch of the project.

This would require no extra code to be put in the hundreds of View Controllers and be easily turned on and off. It didn't work because, as we've learned now, <meme>one does not simply override an existing method via category</meme>.

What are we missing?!?

Heronry answered 13/12, 2012 at 17:24 Comment(6)
This code has an infinite loop, doesn't it? You should call [super viewDidAppear:animated];Dosh
@BrunoDomingues since this is a category on UIViewController, calling super would call viewDidAppear on NSObject (UIViewController's superclass) which doesn't exist.Heronry
You could find current visible view (view controller) from UIWindow rootViewController.Adigranth
@Adigranth where exactly would that go?Heronry
I tried it here and with [self viewDidAppear:animated]; it really has an infinite loop, and with super crashes. In your project viewControllers' viewDidAppear does something? Because by default it does nothing. You can remove the line [self viewDidAppear:animated]; and it will work.Dosh
I not very familiar with iOS (only Cocoa), but all visible views are must be added to a super view that is eventually contained in a window. So you can track down which are visible from the top most view (the view of rootViewController). Starts with rootViewController.view.subviews and look up all subviews of 'subview's, check out hidden, frame, and so on.Adigranth
H
14

The answer is to swizzle the methods! Here is what we came up with:

#import "UIViewController+Logging.h"
#import <objc/runtime.h>

@implementation UIViewController (Logging)

-(void)swizzled_viewDidAppear:(BOOL)animated
{
    NSLog(@"Current View Class: %@", NSStringFromClass(self.class));
    [self swizzled_viewDidAppear:animated];
}

+ (void)load
{
    Method original, swizzled;

    original = class_getInstanceMethod(self, @selector(viewDidAppear:));
    swizzled = class_getInstanceMethod(self, @selector(swizzled_viewDidAppear:));

    method_exchangeImplementations(original, swizzled);

}
@end
Heronry answered 13/12, 2012 at 17:52 Comment(2)
Been using this today and there's one "gotcha" that we've come across. If the subclass calls viewDidAppear but doesn't call [super viewDidAppear], you will not get a log.Heronry
Just came across this. For the sake anyone else reading comments, all UIViewController subclasses MUST call [super viewDidAppear], so if that's missing, that's a different programmer error.Showboat
B
8

viewWillAppear log

Here is a solution to print the current view controller class name when it appears, in the console:

  • Create a Symbolic Breakpoint in Xcode
  • for Symbol, add -[UIViewController viewWillAppear:]
  • for Action, add a Debugger Command and this expression: expr -- (void) printf("🔘 %s\n", (char *)object_getClassName($arg1))
  • check Automatically continue after evaluating actions.

enter image description here

This has helped me a lot whenever I got lost in the project!

deinit log

You can also add a log to see when your view controllers deinit is called:

  • Create another Symbolic Breakpoint
  • for Symbol, add -[UIViewController dealloc]
  • for Action, add a Debugger Command and this expression: expr -- (void) printf("🗑 %s\n", (char *)object_getClassName($arg1))
  • check Automatically continue after evaluating actions.

enter image description here

This one is very handy to make sure the view controller gets released from memory and also a good indicator for catching the retain cycles.

Note: I wouldn't suggest using swizzling as it might risk your code being less maintainable.

Berkly answered 30/8, 2019 at 10:15 Comment(0)
G
3

Here is solution for this

In your .pch file include this

#define UIViewController MyViewController
#import "MyViewController.h"

Create your new UIViewController sub class as

.h file

#import <UIKit/UIKit.h>

#ifdef UIViewController
#undef UIViewController
#endif
@interface MyViewController : UIViewController

@end
#ifndef UIViewController
#define UIViewController MyViewController
#endif

And .m file

#import "MyViewController.h"

@implementation MyViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSLog(@"Current View Class: %@", NSStringFromClass(self.class));
}

@end
Gyre answered 13/12, 2012 at 18:8 Comment(4)
Just got an opportunity to test this code and it works as well. Does this method have any benefits/drawbacks when compared to method swizzling?Heronry
What my code is doing is that it is replacing the super class UIViewController to MyViewController. I couldn't find any drawbacks for both of the approaches right now..Gyre
Thanks for your solution, still waiting on further info before accepting an answer.Heronry
Sure :) look for the best oneGyre
B
1

Do the view controllers share a common base class? if so you could just put it there in the base class' implementation of [viewDidAppear:]. If they do not share a common base, then perhaps that would be a worthwhile task as it could be useful anyways going forwards (common analytics code, etc.)

Beckman answered 13/12, 2012 at 17:28 Comment(2)
Unfortunately not. They all are simply subclasses of UIViewController. Looking into method swizzling for the solution.Heronry
I know you've got hundreds of controllers, but a good find/replace could easily help you inherit from a common controller class. That would make this particular task easy, and have other benefits.Beckman
C
0

You can do a application wide find and replace from Xcode, but it won't necessarily find every case (but neither would the approaches that you tried). You could look for "[super viewDidLoad];" and replace with "[super viewDidLoad]; NSLog(@"Current View Class: %@", NSStringFromClass(self.class));"

Cryosurgery answered 13/12, 2012 at 17:53 Comment(1)
Looking for a simpler solution that would work application wide, rather than having to modify every class as described in the question.Heronry
O
0

Does the app use Navigation controllers to display the View Controllers? If so, you can use the NavigationController's methods to report the current controller:

    - (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
    {
        [self reportNewController:viewController];
    }

    - (void) reportNewController:(UIViewController *)viewController
    {
        NSString *name = viewController.title;
            NSLog(@"Name is %@",name);
    }
Orgulous answered 13/12, 2012 at 18:13 Comment(0)
M
0

You can use method swizzling. Here is a nice guide: http://nshipster.com/method-swizzling/

Mcmullen answered 29/9, 2016 at 8:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.