Method Swizzling to keep a cleaner Class implementation - ios

I'm in the situation where one of the viewControllers of my app is getting pretty big, specially since I've added a "Tutorial state" which adds a different implementation for many methods of this class that I control by checking
_tutorialEnabled?
So, my question is if this is a good use case for method swizzling, I could have this different implementations of these methods in a separate category and swizzle them when required, it might help me reduce the amount of code of the default implementation. Any comments or suggestions of other techniques are appreciated.

No, this is not what method swizzling was designed for.
Personally I would create a subclass of the view controller that manages tutorial related stuff. Then, depending on whether or not the tutorial is enabled, you instantiate either the tutorial controller or its superclass. This is what polymorphism was designed for: to avoid endless if/else/switches.

Why don't you subclass? Create a tutorial subclass of your view controller with all the needed logic. Present the tutorial in the real view controller's -viewDidAppear: using a full screen modal without animation. When the tutorial is over dismiss the model without animation.
if _tutorialEnabled != nil && _tutorialEnabled {
tutorialViewController = …
tutorialViewController.modalPresentationStyle = .FullScreen
presentViewController(tutorialViewController, animated: NO) {}
}

No, I wouldn't use method swizzling for this. It's a bit like using a sledgehammer to knock in a thumbtack.
Unlike others I also would not subclass a view controller, maintaining understandable flow around view lifecycle events is really important when you want to add other functionality later.
Instead I would use the strategy pattern for this. In your init you could do something like this:
if (tutorialEnabled) {
self.behaviour = [TutorialBehaviour new];
} else {
self.behaviour = [NormalBehaviour new];
}
Then when you need to do something that changes you just call a method on your behaviour eg.
- (void)viewDidLoad
{
...
[self.behaviour load]
...
}

Related

Can the same ViewController.view be added as subview to different views as a Singleton?

My app has a search view(search bar) which is used all over the app. I don't want to create duplicated code so I created a view controller called MySearchViewController to handle the search job, then I created a singleton object in AppDelegate. In every view controller, I added my search view like this:
- (void)viewDidLoad
{
MySearchViewController* search = [AppDelegate searchViewController];
[self.view addSubView:search.view];
}
My questions, Is it a good way? It's a singleton so it can be added to many views. Do I need to remove the view from last view before adding to current view?
Understand that you are mixing some concepts that are not necessarily related: avoid duplicated code and Singletons.
Wikipedia says this about singletons:
In software engineering, the singleton pattern is a design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. The concept is sometimes generalized to systems that operate more efficiently when only one object exists, or that restrict the instantiation to a certain number of objects. The term comes from the mathematical concept of a singleton.
The most important characteristic of a singleton (in my humble opinion) is that the object is instantiated only once and every single place in your application will use the same instance. Well, to use your search feature everywhere and avoid duplicated code you don't need the search view to be instantiated only once, maybe the data that comes with it, but not the view itself.
Two better ways of achieving this:
1 - You can create a ViewController with your search and just embed this on the other views using a Container View, you can use blocks or a delegate protocol to communicate between your controller and the view that is embedding it.
2 - You can create a Parent class of the ViewController that will include the search bar, like a SearchViewController and all the other viewControllers that needs the same feature will inherit from it.
The singleton could be useful if you are planing to share the same search data and text between all the ViewControllers of the application, but it would be a singleton only with these information, the UISearchBar and all other view elements should not be part of the singleton.
Ideally, you should instantiate a fresh instance of MySearchViewController every time when you want to add it to another view to avoid problems.
Do I need to remove the view from last view before adding to current view?
Its not required to remove it from previous super view because whenever you add this singleton MySearchViewController's view to some other view, it will automatically gets removed from last super view and now its super view is your new view where you have added it.
If you want to add a view from a different view controller, your view controller has to be that view controller's parent view controller:
- (void)viewDidLoad
{
MySearchViewController* search = [AppDelegate searchViewController];
[self addChildViewController:search];
[self.view addSubView:search.view];
}
also, make sure that when the search.view is added, it is already initialised.
Why you do not use NSObject class ?, i do not know your requirement , but if you want to store latest updated value in whole project(in execution) then you should use the singleton, but if you do not want to store value (i mean one result for whole project) then you should use NSObject derived Class. advantage is singleton consumes memory so memory will be wasted. NSObject class will be reusable and only allocated when it is required and then ARC will take care of all things. If you want to know how to create NSObject and use of it then you can give me reply.
Here is some code to load a XIB as part of a custom object with the object gets initialized.
Why are you not creating custom search component for search?
you can use this component all over the app.
also this is not creating duplicat code.
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[[[NSBundle mainBundle] loadNibNamed:#"SearchView" owner:self options:nil] objectAtIndex:0];
[self addSubview:self.view];
self.frame = self.view.frame;
}
return self;
}
Please check below code. Hope this is work for you.
- (void)viewDidLoad
{
if ([self.view viewWithTag:123456]) {
[[self.view viewWithTag:123456] removeFromSuperview];
}
MySearchViewController* search = [AppDelegate searchViewController];
search.view.tag = 123456; // give a any random tag to view
[self.view addSubView:search.view];
[self addChildViewController:search];
}
Please make sure given tag is not assign to other object except search.view in self.view.
Thanks

Is it a MUST for viewWillAppear to have [super viewWillAppear] method as well

I placed my code for iAd/AdMob ads in...
-(void)viewWillAppear:(BOOL)animated{}
Ads work perfectly fine the way I have them now on all iOS devices.
When I connected my iPhone to Xcode and clicked on Product -->Analyze a message states...
The viewWillAppear:instance method in UIViewController subclass 'iPhoneSIX' is missing a [super viewWillAppear:] call
I just accidentally stumbled upon this Product-->Analyze thing. Do I really need to add [super viewWillAppear] even though everything works perfectly fine on all devices as it currently is. Will Apple reject my app if I don't pay attention to the Product-->Analyze issue navigator?
Also, what does ...
[super viewWillAppear:YES];
What does calling this do?
According to Apple: (emphasis mine)
This method is called before the receiver's view is about to be
added to a view hierarchy and before any animations are configured for
showing the view. You can override this method to perform custom tasks
associated with displaying the view. For example, you might use this
method to change the orientation or style of the status bar to
coordinate with the orientation or style of the view being presented.
If you override this method, you must call super at some point in your
implementation.
Apple doesn't gets that specific when deciding to Accept or Reject your app. It only follows the guidelines, which doesn't get that much into the weeds of your specific methods.
Calling [super viewWillAppear:YES] is a best practice, and I would recommend it. Always including super ensures that any code in the super classes get called before executing any additional code. So if you or someone else coded a super class that expected some code to be executed, you are guaranteed to still execute it, rather than just overwriting the whole method in the subclass.
Say you have a view controller of type MyViewController which is a subclass of UIViewController. Then say you have another view controller of type MyOtherViewController, which is a subclass of MyViewController. Say you're coding now some things in viewWillAppear in MyOtherViewController. If you call super first, it will call viewWillAppear in MyViewController before executing any code. If viewWillAppear in MyViewController calls super first, then it will call viewWillAppear in UIViewController before executing any code.
I'm quite certain Apple will not reject your app for failing to call super on an overridden method, primarily because there are cases where you may specifically want to avoid calling super.
That said, as Josh Gafni mentions it is definitely a best practice to do so, unless you have a very good reason for not. Also bear in mind some view controller subclasses (can't recall specifically which ones, but maybe UICollectionViewController) will only work properly if their view lifecycle methods get called appropriately, so not calling super can definitely break some classes (sometimes in subtle ways you may not realize).
Therefore my suggestion is add the call to super (generally as the first line in the method) and see if things continue to work fine. If not, spend a bit of time trying to understand what is happening differently and see if you can solve it in a different way. In general you should always (as a force of habit) provide calls to super on any view lifecycle methods you override whenever possible.

Best design/pattern to use for first-use tutorial in app?

The background:
I have an app with 5 tabs. The first time a user navigates to each tab, I would like to show a one-time "tutorial". I intend to do this by creating a "TutorialViewController" that will handle displaying these "tutorial" views and will have buttons for next/back etc...
The problem:
I'm not sure the best pattern to use for implementing the logic for whether or not to show these screens and instantiating the "TutorialViewController" to display them. The goal is to have a single line of code (a single method call) that would show the tutorial if necessary. I'm trying to avoid duplication of code across the 5 view controllers. The problem is where/how to implement this single method. As a class method on TutorialViewController? As a global C function?
Things I've considered:
1) Implementing a class method on TutorialViewController called "displayTutorialIfNecessary". In this case, each view controller that has a tutorial would call this class method from their "viewDidAppear" methods. This class method would check to see if the tutorial has already been shown, and if not, it would instantiate a TutorialViewController object to handle to display it. In this option, I guess I would have to pass in "self" from each calling view controller and the class method would use that to display the TutorialViewController.
2) Implementing a class method on TutorialViewController called "tutorialShouldBeDisplayedForScreen: ". In this option, each calling view controller would call this method, and if it returns true, each vc would instantiate and present the "TutorialViewController" which would handle displaying the tutorial.
I'm sure there is a "best practice" or a pattern that fits this scenario, but I'm not sure what the best implementation is. Thanks in advance for your recommendations.
To summarize: Instead of having something like this in each view controller:
if ([TutorialViewController shouldDisplayTutorialForScreen:<someEnum>])
{
TutorialViewController *myTutorialVC = [[TutorialViewController alloc] init];
[self displayModalViewController: myTutorialVC];
}
I'd like something more like this:
[FirstUseViewController displayTutorialIfNecessaryForScreen: <someEnum> forParentViewController: self];
store the tutorial has shown state into NSUserDefaults and use factory method design pattern to let each UIViewController you'll need create and return tutorial UIViewController like:
- (UIViewController *)tutorialVC {
return [[MYHomeScreenTutorialVC alloc] init];
}

Clean way to force view to load subviews early

Recently I wrote some code where I tried to refer to an outlet on a UIViewController I'd just instantiated with [storyboard instantiateViewControllerWithIdentifier] and modify the subview that the outlet pointed to before presenting the ViewController. It didn't work because the ViewController's view hadn't loaded its subviews yet, including the one that my outlet referred to, so the property just gave me a null pointer.
After (with some struggle) tracking down the cause of my issue in the debugger, I Googled around and learned, through answers like this one, that I can cause the view to load its subviews without being displayed by calling the myViewController.view getter. After that, I can access my outlet without any problems.
It's a clear hack, though, and Xcode - quite rightly - doesn't like it, and angrily protests with this warning:
Property access result unused - getters should not be used for side effects
Is there a non-hacky alternative way to do this that doesn't involved abusing the .view getter? Alternatively, are there canonical/idiomatic patterns for this scenario involving something like dynamically adding a handler to be called as soon as the subviews are loaded?
Or is the standard solution just to replace myViewController.view with [myViewController view] to shut up Xcode's warning, and then live with the hack?
On iOS 9 or newer, one can use:
viewController.loadViewIfNeeded()
Docs: https://developer.apple.com/reference/uikit/uiviewcontroller/1621446-loadviewifneeded
I agree that forcing a view to load should be avoided but I ran into a case where it seemed the only reasonable solution to a problem (popping a UINavigationController containing a UISearchController that had yet to be invoked causes a nasty console says warning).
What I did was use new iOS9 API loadViewIfNeeded and for pre-iOS9 used viewController.view.alpha = 1.0. Of course a good comment above this code will prevent you (or someone else) removing this code later thinking it is unneeded.
The fact that Apple is now providing this API signals it can be needed from time to time.
Not sure how much cleaner this way, but it still works fine:
_ = vc.view
UPD: for your convenience, you can declare extension like below:
extension UIViewController {
func preloadView() {
let _ = view
}
}
You can read explaination by following URL: https://www.natashatherobot.com/ios-testing-view-controllers-swift/
merged Rudolph/Swany answers for pre ios9 deployment targets
if #available(iOS 9.0, *) {
loadViewIfNeeded()
}
else {
// _ = self.view works but some Swift compiler genius could optimize what seems like a noop out
// hence this perversion from this recipe http://stackoverflow.com/questions/17279604/clean-way-to-force-view-to-load-subviews-early
view.alpha = 1
}
If I understand you correctly, I think there's another fairly standard solution: move the outlet modification/configuration code into a viewDidLoad method (of the recently instantiated VC).
The topic is also discussed in this question.
It would require some restructuring, but it might give you a "cleaner" design in terms of MVC if your incoming VC handled its own configuration, and it would avoid the "You should never call this method directly" stricture on loadView.
You can call [myViewController loadView] to explicitly load the view, instead of abusing the .view getter. The .view getter actually calls loadView if necessary when called.
It's still not a very nice solution, since the UIView Documentation's section on loadView explicitly instructs that
You should never call this method directly

Reusing data entry methods across views

I have written code for scrolling my table view even when keyboard hides it from entering data, using the notification center and the keyboardDidShow and keyboardDidHide methods.
The problem is that I have almost 8 views in my app where I need to enter some data.
Should I write the whole code in every single .m file, or is there any other easy way I could do it?
You could write some kind of BaseTableViewController which handles all the keyboard notifications.
Then let all the other TableViewControllers inherit from this base controller.
Either you define that method in your application delegate file or create a separate class file which contains the method and you can call it whenever it required.
myMethod.h file
#interface myMethod : NSObject
{
}
- (void) callMyMethod;
myMethod.m file
- (void) callMyMethod
{
// your code
}
In your view, call this method....
myMethod *objMyMethod = [[myMethod alloc] init];
[objMyMethod callMyMethod];
The DRY (Don't Repeat Yourself) principal would lead to creating one set of code to handle the input, not many copies that do the same thing.
The principal of decoupling would lead to a separate class for the code.
A separate class would also allow easier Unit Test to be written.
This sounds like a perfect use-case for a category.

Resources