UIViewController base class - is it good or not - ios

I have to apply single logic for gesture recongnizer to all UIViewController elements in my app.
I thought of creating a base class. However, as far as I know we should add extra layer to inheritance chain only in purpose of strong necessity.
What is the most efficient way to add functionality I mentioned above to my project, without copy-pasting code to each of mine controllers?

You can use extension of UIViewController
extension UIViewController
{
// write code that has to be used in all view controllers
func applyTheRequiredPropertiesToGestureRecognizer(gr:UIGestureRecognizer)
{
// do whatever you want to do
}
}
Later while using
class SomeViewController:UIViewController{
let gestureRecog = UIGestureRecognizer()
//call the method
func someMethod()
{
self.applyTheRequiredPropertiesToGestureRecognizer(gestureRecog)
}
}

Make a baseviewController and do all the functions which are generic or used at most of the view controllers like an alert.
class BaseViewController: UIViewController {
func yourGestureRecogniser() {
// do the process
}
}
Then inherit the base view to other view controllers like this,
class ViewController1: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
yourGestureRecogniser()
}
}
class ViewController2: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
yourGestureRecogniser()
}
}
customize the gesture function as per your needs.

Using base controller is certainly a common approach.
A common base controller often becomes a dumping ground for functionality that is used in multiple-but-not-all controllers, so it become overkill.
I also followed this pattern in my project but later it becomes complicated for me when i start using other controllers like UITableViewController, UICollectionViewController etc.
Agreed with Sulthan's comment: using a category and turn it on explicitly on every controller would definitely be a better option.

Related

Applying same logic on all the ViewControllers

The following implementation works on a single ViewController. However, I want to apply same logic all the other ViewControllers as well.
Rather than repeating(copy-paste) the same code again and again in each of the ViewController, what would be a good approach?
ViewControllerA
override func viewDidLoad() {
super.viewDidLoad()
//common
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func willEnterForeground() {
if (expired()){
navigationController?.popToRootViewController(animated: true)
}
}
Subclasses. That's what subclasses are for. This absolutely should not be applied to all UIViewControllers, since many of those are provided by Apple, and it would be very bad if you modified their viewDidLoad this way. But for every one of your view controllers, you just need to add this behavior as a superclass.
To create a subclass like this, you'd make an intermediate view controller type:
class ForegroundPoppingViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func willEnterForeground() {
if (expired()){
navigationController?.popToRootViewController(animated: true)
}
}
}
And then for all the view controller you want to have this behavior, you would subclass:
class MyViewController: ForegroundPoppingViewController { ... }
If MyViewController has its own viewDidLoad, you'd chain that to its superclass, just like in any other subclass:
class MyViewController: ForegroundPoppingViewController {
override func viewDidLoad() {
super.viewDidLoad()
// ... any other behaviors ...
}
}
This would not apply to Apple view controllers, such as UIDocumentBrowserViewController, but it shouldn't. That may not give valid behavior. You would need to decide on the proper behavior depending on what view controller you're presenting.
Also, as a general rule, you should observe notifications in viewDidAppear (or willAppear) and remove notification observations in viewWillDisappear (or didDisappear). You usually do not want notifications firing on view controllers that are not currently onscreen.
That said, for this particular problem, I probably would recommend moving this logic to a "presenter" type coordinator, or even a UINavigationController subclass. As written, this may call popToRootViewController many times (since many view controllers may exist at the same time), which may lead to animation glitches.
But for the general question of how to add functionality to view controllers, this is how you would do it.

Delegation Among Container View Controllers

I have a parent viewcontroller hosting 3 container viewcontrollers.
At certain points I need to pass data from one container viewcontroller to another container viewcontroller and thought I could accomplish this through the delegation pattern. However, I can't seem to figure out why the delegate is not triggered and the receiving container viewcontroller doesn't receive any data.
Can't seem to spot what's potentially wrong with the way I've set it up. If there's a recommended way to pass data between the containers, I'm all ears as well!
Below is a code summary on the setup:
class ParentViewController: UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let firstContainerVC = segue.destination as? FirstContainerVC {
//....
}
if let secondContainerVC = segue.destination as? SecondContainerVC {
//....
}
}
protocol Delegate {
func passX(a: String?)
func passY(b: String?)
}
}
class FirstContainerVC: UIViewController {
var delegate: Delegate?
if isTrue {
delegate.passX(a: "TestOne")
} else {
delegate.passY(b: "TestTwo")
}
}
class SecondContainerVC: UIViewController, Delegate {
override func viewDidLoad() {
let firstVC = self.storyboard?.instantiateViewController(withIdentifier: "firstContainer") as! FirstContainerVC
firstVC.delegate = self
}
func passX(a: String?) {
//if let a = a....
}
func passY(b: String?) {
//if let b = b....
}
}
Unfortunately, I don't know how the dragging and dropping works in Xcode, I do everything in code. However, when your parent view controller instantiates another view controller, just set the parent as the container's delegate.
Create the protocol:
protocol SomeProtocol: AnyObject {
func passX(a: String?)
func passY(b: String?)
}
And the containers will have delegates of type that protocol:
class FirstContainerVC: UIViewController {
weak var delegate: SomeProtocol?
}
class SecondContainerVC: UIViewController {
weak var delegate: SomeProtocol?
}
The parent must conform to the protocol so that it can become the delegate. Then when you instantiate the containers (which you must only do once in this scenario), set self as their delegates:
class ParentViewController: UIViewController, SomeProtocol {
// make the containers instance properties so that you
// can access them from the protocol methods
weak var firstContainerVC = FirstContainerVC()
weak var secondContainerVC = SecondContainerVC()
// set the delegates at some point
func setDelegates() {
firstContainerVC?.delegate = self
secondContainerVC?.delegate = self
}
func passX(a: String?) {
guard let a = a else {
return
}
secondContainerVC?.getFromFirst(a: a)
}
func passY(b: String?) {
//
}
}
Then when you want to go from first to second, go through the delegate from the first container to the parent, and from the parent to the second container.
class FirstContainerVC: UIViewController {
weak var delegate: SomeProtocol?
func sendToSecond() {
delegate?.passX(a: "slick")
}
}
class SecondContainerVC: UIViewController {
weak var delegate: SomeProtocol?
func getFromFirst(a: String) {
print(a)
}
}
This is a somewhat crude example. You should code your implementation how you feel most comfortable (i.e. gracefully unwrapping, how/where you instantiate, etc.). Also, if all of the view controllers are permanent view controllers (meaning that they are never deallocated), no need to make them weak var. However you do it, the concepts are all the same. The parent is the delegate for the containers, and the containers communicate with each other through the parent.
Some people may suggest using notification observers or singletons for the containers to communicate with each other, but I find that to be overkill when you have a parent right there.
I agree with #slickdaddy that your second container view controller has no business instantiating your first container view controller. I suspect you already had a first container VC and now you have 2.
To answer your question about the best way to pass the data, your container view controllers should know nothing about the parent view controller. Through either delegation or a callback that the parent registers, the data should go to the parent and the parent should then route it down to the other interested contained view controllers.
Keep knowledge of your hierarchy flowing "downwards". In other words, contained or owned VCs should not know anything about their owners or parents. It'll help with reuse, organization, etc..
Also consider another approach: passing the same model object into each contained view controller.
And finally, my preferred approach is letting each view controller (or actually its view model if doing MVVM) reach for a DependencyManager singleton where such model objects live. If done right, unit tests can still have full control by injecting mock models into the DependencyManager.

Use extension for my certain View Controller

I have created a class used for an extension at my certain view controllers,
class STPopupTransitionAnimator: NSObject, STPopupControllerTransitioning {
....
}
and now I would like to set it to my View Controller, so I must not use the same code in my Home, profile, browse, search, etc like this:
extension HomeViewController: STPopupControllerTransitioning {
func popupControllerTransitionDuration(_ context: STPopupControllerTransitioningContext) -> TimeInterval {
return context.action == .present ? 0.9 : 0.4
}
}
The question is, how could I use my STPopupTransitionAnimator for my home, profile, and search view controller ?
What I don't want to do is to extend my UIViewController like this:
extension UIViewController: STPopupTransitionAnimator {}
because I only used that protocol only at certain view controllers and I used it to check it for something.
create a subclass of UIViewController like
class STPopupViewController: UIViewController, STPopupControllerTransitioning {
//all functions implementations
}
and subclass your UIViewControllers from it
class HomeViewController: STPopupViewController {
}

Access tab controller views?

I started an app with a single view controller. I've since added a tab controller view and created 4 views that are setup as tabs, and that all seems ok.
The only viewcontroller.swift file I have is the original one. I'm not sure how to access the view controllers for each of the individual tabs.
Should I just use the one viewcontroller.swift for all my code and link the controls in each tab back to it?
To access the other view controllers, create classes of your UIViewController, like so. I'd recommend you put each class in a separate Swift file in your project, but it's not necessary.
class SecondViewController: UIViewController {
...
}
class ThirdViewController: UIViewController {
...
}
class FourthViewController: UIViewController {
...
}
Then assign them in the Identity Inspector by clicking each Storyboard UIViewController:
I make tabViewController just like this. Make my own viewcontrollers. First, init my own viewcontrollers. Then, push them into tabViewController.viewControllers
class TabbarController: UITabBarController {
override func viewDidLoad(){
super.viewDidLoad();
let first = YourFirstViewController();
let second = YourSecondViewController();
let thrid = YourThirdViewController();
self.viewControllers = [first, second, third];
}
}

how can i update other controllers' UI in swift?

I have several controllers in my app. When my app call one function in one controller, I want to update other controllers' UI. How can I achieve that?
class FirstViewController: UIViewController {
func updateUI {...}
}
class SecondViewController: UIViewController {
func updateUI {...}
}
class ThirdViewController: UIViewController{
func updateAllUI {...} # I want call FirstViewController().updateUI() and SecondViewController().updateUI() here
}
But FirstViewController() means I create a new FirstViewController which is I don't want, and FirstViewController has already been created. So how can I call all other controllers' updateUI() in updateAllUI()
Please help, Thank you!
It's usually a pretty bad practice to have view controllers communicate directly. I would use a NSNotification to communicate between view controllers. It's convention to have the name of your notification start with a capital letter and end with the word "Notification".
class FirstViewController: UIViewController {
func updateUI {...}
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateUI", name:"TimeToUpdateTheUINotificaiton", object: nil)
}
override deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}
class SecondViewController: UIViewController {
func updateUI {...}
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateUI", name:"TimeToUpdateTheUINotificaiton", object: nil)
}
override deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}
class ThirdViewController: UIViewController{
func updateAllUI {
NSNotificationCenter.defaultCenter().postNotificationName("TimeToUpdateTheUINotificaiton", object: nil)
}
}
Eliminate the parentheses. You don't use them when calling a class function.
FirstViewController.updateUI()
That said.. what you're trying to do is very weird to say the least. You shouldn't use class functions to modify properties of instances of a class. If you have both View controllers on screen at the same time, you should be using a parent controller to command both of them to update their UI when you need to.
If they're not both on screen at the same time, you don't really need to update both UIs.
If you want all your view controllers to react[in this case - updating ui] to an action in one of your view controller, you try posting a Notification.. That is how you broadcast a message in iOS
You post notification from the view controller when your desired action is complete.
All other View controllers would subscribe to that notification and react to it by updating their UI when it is posted.
The below post quickly shows an example to post/observe notifications..
https://stackoverflow.com/a/2677015/4236572
http://www.idev101.com/code/Cocoa/Notifications.html might also be helpful

Resources