Swinject register UIViewController best practice - ios

At this point in my project I use Swinject (DI Container).
I want to know how to register the ViewController, if I have the following hierarchy:
class RateAnswersBaseVC: UIViewController { }
class RateAnswersDoctorVC: RateAnswersBaseVC { }
class RateAnswersQualityVC: RateAnswersBaseVC { }
How will their right to register and use? I have two options at this point in the project I use the first option, but there is a sense that we should use the second :)
// ------- 1 --------
container.register(RateAnswersDoctorVC.self) { r in
let vc: RateAnswersDoctorVC = RateAnswersDoctorVC()
self.configureBasicFields(with: vc, container: container, resolver: r)
return vc
}
container.register(RateAnswersQualityVC.self) { r in
let vc: RateAnswersQualityVC = RateAnswersQualityVC()
self.configureBasicFields(with: vc, container: container, resolver: r)
return vc
}
// ------- 2 --------
container.register(RateAnswersBaseVC.self, name: "doctor") { r in
let vc: RateAnswersDoctorVC = RateAnswersDoctorVC()
self.configureBasicFields(with: vc, container: container, resolver: r)
return vc
}
container.register(RateAnswersBaseVC.self, name: "quality") { r in
let vc: RateAnswersQualityVC = RateAnswersQualityVC()
self.configureBasicFields(with: vc, container: container, resolver: r)
return vc
}
I would be glad if you explain why you use one or the other method.
Thanks for the replies

Last time I use MVVM pattern in my projects. So I split business logic of the app to modules - a completed part of code which provides some feature or business logic (like Login in the app, playing video in fullscreen etc). For each module I create an Assembly object to configure dependencies in the module.
class PasswordAssembly: Assembly {
func assemble(container: Container) {
container.register(PasswordViewInterface.self) { (_: Resolver) in
PasswordViewController(nibName: "PasswordViewController", bundle: nil)
}
.initCompleted { resolver, view in
var view = view as PasswordViewInterface
view.viewModel = resolver.resolve(PasswordViewModelInterface.self)
view.router = resolver.resolve(PasswordRouterInterface.self)
}
container.register(PasswordViewModelInterface.self) { (resolver: Resolver) in
return PasswordViewModel(coreModel: resolver.resolve(CoreViewModelInterface.self)!)
}
container.register(PasswordRouterInterface.self) { _ in
return PasswordRouter()
}
.initCompleted { (resolver, router) in
var router = router as PasswordRouterInterface
router.view = resolver.resolve(PasswordViewInterface.self)
router.resolver = container
}
}
}
Also I inject in router Resolver (Container) object to make Router build hierarchy
protocol PasswordRouterInterface {
var view: PasswordViewInterface! { get set }
var resolver: Resolver! { get set }
func presentFilesListView()
func presentContentView()
}
class PasswordRouter: PasswordRouterInterface {
var view: PasswordViewInterface!
var resolver: Resolver!
func presentFilesListView() {
if let listViewController = resolver.resolve(FilesListViewInterface.self)?.getViewController() {
let navigationController = UINavigationController(rootViewController: listViewController)
view.getViewController().present(navigationController, animated: true) {
}
}
}
func presentContentView() {
}
}

Umh, either with 1 or 2, you are still registering a instance.
I don't really see any difference in this case.
These are ViewControllers so things are a bit more complicated, but what would make a difference would be to register them not as RateAnswersBaseVC, but as a protocol RateAnswersBaseVCService or RateAnswersBaseVCProtocol so that you could then create a different implementation of the View Controller in the mocks for testing.

Related

add variable to all UIViewControllers

I'm new to Swift and I'm trying to implement a custom UIKeyCommand architecture in a practice app. I wrote the extension below for the base UISplitViewController to show all UIKeyCommands in the current views on screen.
extension UISplitViewController {
open override var canBecomeFirstResponder: Bool {
return true
}
var BPKeyCommands: [BPKeyCommand]? {
var commands: [BPKeyCommand] = []
var mastervc = self.viewControllers.first
if (mastervc is UINavigationController) {
mastervc = (mastervc as! UINavigationController).viewControllers.last
}
if let masterCommands = mastervc.commands {
for command in masterCommands {
commands.append(command)
}
}
return commands
}
open override var keyCommands: [UIKeyCommand]? {
var commands: [UIKeyCommand] = []
if let bpkeycommands = BPKeyCommands {
for command in bpkeycommands {
let new = UIKeyCommand(input: command.input,
modifierFlags: command.modifierFlags,
action: #selector(executeKeyCommand(sender:)),
discoverabilityTitle: command.title)
commands.append(new)
}
}
return commands
}
#objc private func executeKeyCommand(sender: UIKeyCommand) {
if let index = keyCommands?.firstIndex(of: sender) {
if let command = BPKeyCommands?[index] {
command.action(command)
}
}
}
}
Now, as you might expect this throws an error at if let masterCommands = mastervc.commands {, because UIViewController doesn't contain the commands variable out of the box. My question is: how can I haveUIViewControllerhave that variable? Just like all controllers can overridekeyCommands` by default?
You have to create a protocol with command variable and make your view controller conform to it (step 1). Either you can provide values for particular view controller or you can provide a default implementation.
step 1:- Create a protocol with the variable you need.
protocol Commandable{
var commands: [String]{set get}
}
extension Commandable{
var commands: [String]{
get{return ["hello","world"]}
set{}
}
}
step 2:- Make then controllers which you are using conform it
step 3:- change the above code to get commands
if let commandProtocol = masterVC as? Commandable
{
let commands = commandProtocol.commands
}
else{
// handle it
}
Make sure the variable is unique so you don't accidentally override it.
Thank you.
you can create a extension of UIViewController and add that property on that extension of UIViewController. Then You will get it on child view controllers like UISplitViewController or any other custom ViewControllers. To know more about extensions, Which can be added on extension or what can be done by extension??

Access super class property from inherited class

I have a class, MyContainer, which has another custom class as a variable. This other class, MyInterface, is a view controller super class, which gets extended by two other child custom classes, MyVCA and MyVCB. The reason for this is because I have a bottom button which is used across both screens - only the content has to get updated every time, which I do programmatically. There is also a content manager which I use to know which of the two child classes to use, called MyContentManager.
The problem I am having is when I am going from a previous view controller to either MyVCA or MyVCB, because depending on which one it is, a certain task needs to be done or not. I am instantiating the view for MyVCB from the storyboard like so:
let vc = UIStoryboard(name: "Containers",
bundle: Bundle.main).instantiateViewController(withIdentifier:
"my_container") as! MyContainer
vc.contentManager = MyContentManager(type: .type_my_vc_a)
vc.shouldDoTask = true
self.navigationController?.pushViewController(vc, animated: true)
As can be seen I have created a flag, shouldDoTask, that needs to be set at this point (inside a previous view controller). But because it is set to the container super class, the children can not access it. So what needs to happen basically is that this flag needs to get propagated through the path MyContainer -> MyInterface -> MyVCA / MyVCB.
I have tried to use a property for the flag, in MyInterface:
private var _shouldDoTask: Bool = false
var shouldDoTask: Bool {
set { _shouldDoTask = newValue }
get { return _shouldDoTask }
}
And in MyContainer:
var content: MyInterface!
var shouldDoTask: Bool {
set {
if content != nil {
content.shouldDoTask = newValue
}
}
get {
return (content != nil)
? content.shouldDoTask
: false
}
}
Then in MyVCA / MyVCB I can access it like this:
class MyVCA: MyInterface {
func someMethod() {
if self.shouldDoTask {
// do task
}
}
}
This would work nicely, if it wasn't for the fact that the content is still nil when the flag gets set in the previous view controller. This is understandable because of course MyInterface has not been created yet. I am looking for a way past this. I have been thinking about a method that could get called in MyInterface's viewDidLoad method to set the flag, but I can't seem to figure it out.
Any ideas would be appreciated.
Something like this. Check if it helps.
protocol MyInterFaceDelegate {
func setValues()
}
MyInterFace {
let delegate : MyInterFaceDelegate
viewDidLoad() {
delegate.etValues()
}
}
extension MyContainer : MyInterFaceDelegate {
func setValues() {
content.shouldDoTast = self.shouldDoTast
}
}
When you create MyInterFace() after that you set the delegate
content = MyInterFace()
content.delegate = self

Swift Protocols not working

I am trying to call a function using delegates and for some reason it is not working. I set loginPresenter as self in the LoginPresenter init() but it still didn't work. I am not sure what else to try? Am i missing how delegates work?
Login View
class LoginView: UIViewController {
var loginPresenter: LoginPresenterProtocol?
override func viewDidLoad() {
super.viewDidLoad()
}
#objc func loginHandler() {
print("Tapped")
loginPresenter?.loginUser(username: "username", password: "123456")
}
}
Login Presenter View
class LoginPresenter: LoginPresenterProtocol {
weak var view: LoginViewProtocol?
init() {
view?.loginPresenter = self
}
func loginUser(username: String, password: String) {
print("recieved")
}
}
Protocols
protocol LoginViewProtocol: class {
var loginPresenter: LoginPresenterProtocol? { get set }
}
protocol LoginPresenterProtocol: class {
var view: LoginViewProtocol? { get set }
func loginUser(username: String, password: String)
}
The root of your problem is that you probably did not inject presenter to the view or it is not the same instance. I do not know how you deal with initialization, so I provide 2 concepts. It is better to use injection with Viper I guess.
Create init method for your presenter which takes view protocol, this applies for both approaches:
init(view: LoginViewProtocol) {
self.view = view
}
Registering your Dependencies
Below is example of what worked for me using Swinject(transformed to your cause but not tested).
Even though if you are not using storyboard, you can't inject both view to presenter and presenter to view in init. That is why I am using init completed here.
let mainStoryboard = UIStoryboard.init(name: "Main", bundle: nil)
container.register(LoginViewProtocol.self) { _ in
mainStoryboard.instantiateViewController(
withIdentifier: String(describing: LoginView.self)
) as! LoginViewProtocol
}.initCompleted { (r, loginVC) in
loginVC.presenter: r.resolve(LoginPresenterProtocol.self)!
}
container.register(LoginPresenterProtocol.self) { r in
LoginPresenter(
view: r.resolve(LoginViewProtocol.self)!
)
}
Then just resolve view controller when needed to be presented.
Without Dependency injection
Or if you do not want to deal with Dependency injection:
in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
presenter = LoginPresenter(view: self)
}
This could give you an idea how to deal with that. Anyway you will have to figure out what is the best for your cause. If you are not using Dependency injection I strongly recommend it.

Conditionally cast of generic view controller fails

Say I have the following:
class ContentSelectableViewController<T: NSManagedObject> : UIViewController { //... }
class PersonSelectionViewController: ContentSelectableViewController<Person> { // ... }
class PlaceSelectionViewController: ContentSelectableViewController<Place> { // ... }
Then in an instance of one of these subclasses, I have some code:
if let navCtrl = self.navigationController {
for viewController in navCtrl.viewControllers.reversed() {
if viewController is ContentSelectableViewController {
log.info("Worked for \(viewController.description)")
}
if let vc = viewController as? ContentSelectableViewController {
// This should be equivalent to the above.
}
}
}
My question is, when I have a stack full of subclasses of this generic baseclass, it doesn't always return true (go into the if statement) when checking if they are of type ContentSelectableViewController and I don't understand why. They inherit from the same baseclass.
EDIT:
I'm guessing it's because of the generic nature of the class. The if statements evaluate to true for the subclass that calls it.
So, it does in fact have something to do with trying to type check a generic class. It would work for the one and not the other because the one making the call implicitly adds its type.
i.e. (Pseudo-Swift)
if viewController is ContentSelectableViewController<Person> { //... }
What I did instead was to define a protocol that ultimately makes these ContentSelectableViewController<T> selectable:
enum ContentSelectionRole: Int {
case none = 0 // no selection going on right now.
case root // i.e. the one wanting content
case branch // an intermediary. think of a folder when looking for a file
case leaf // like a file
}
enum ContentSelectability: Int {
case noSelections = 0
case oneSelection = 1
case multipleSelections = 2
}
protocol ContentSelection {
var selectedObjects: [NSManagedObject] { get set }
var selectionRole: ContentSelectionRole { get set }
var selectionStyle: ContentSelectability { get set }
func popToSelectionRootViewController() -> Bool
func willNavigateBack(from viewController: UIViewController)
}
Making the definition:
class ContentSelectableViewController<T: NSManagedObject> : UIViewController, ContentSelection { //... }
And then, refactored the original post, to get:
#discardableResult func popToSelectionRootViewController() -> Bool {
if let navCtrl = self.navigationController {
for viewController in navCtrl.viewControllers.reversed() {
if let vc = viewController as? ContentSelection {
if vc.selectionRole == .root {
vc.willNavigateBack(from: self)
navCtrl.popToViewController(viewController, animated: true)
return true
}
}
}
}
return false
}
I still don't quite understand the aspect of the language that makes it fail, but this solution works.
Protocol-based Programming seems to be more Swifty anyway...

Swinject inject self's property into new UIViewController

Let's pretend we have an UITableViewController that on didSelectRowAtSection loads an instance of a class named i.e.: ClassToInject and it wants to inject it through a property injection because our ViewControllerToBePushed has a property of ClassToInject, that subsequently (because it's an UITabBarViewController) on the didSet callback it searches for all its viewControllers property that conforms to ClassToInjectPresentable simple as:
protocol ClassToInjectPresentable {
var property: ClassToInject { get set }
}
Until now, i would just do something like this:
func didSelectRowAtIndexPath {
let classToInject = self.loadClassToInjectFor(indexPath)
let tabBarViewController = SomeTabBarViewController()
tabBarViewController.property = classToInject
self.navigationController.push(tabBarViewController, animated: true)
}
And in SomeTabBarViewController ...
class SomeTabBarViewController: ClassToInjectPresentable {
var property: ClassToInject? {
didSet(newValue) {
self.viewControllers.filter{ $0 is ClassToInjectPresentable }.map{ $0 as! ClassToInjectPresentable }.forEach{ $0.property = newValue }
}
}
And everything should be get loaded nice and easy (but it's not). I've read about Swinject and this might be solved with it. I have seen lots of examples registering things like:
container.register(Animal.self) { _ in Cat(name: "Mimi") }
But I don't know if I can register some property that is loaded in self:
container.register(ClassToInjectInjector.self) { _ in
self.loadClassToInjectFor(indexPath) }
// And then
container.register(ClassToInjectPresentable.self) { _ in
SomeTabBarViewController() }
.initCompleted { r, p in
let tabBar = p as! SomeTabBarViewController
tabBar.property = r.resolve(ClassToInjectInjector.self)
// And lastly?
self.navigationController.pushViewController(tabBar, animated: true)
}
}
It is difficult to recommend proper solution without knowing details of your application, but here are some suggestions:
container.register(ClassToInjectInjector.self) { _ in
self.loadClassToInjectFor(indexPath)
}
In general, all register-ations should be done outside of your objects. Common setup ishaving one global Container, which contains all the registrations - you should look at them as instructions to build application objects without any implicit context. If your dependency needs to be created in the UITableViewController, you can pass it to resolve method as an argument:
container.register(ClassToInjectPresentable.self) { resolver, property in
let tabBar = SomeTabBarViewController()
tabBar.property = property
return tabBar
}
// in UItableVIewController
container.resolve(ClassToInjectPresentable.self,
argument: self.loadClassToInjectFor(indexPath))
Also this is usually a bad idea:
.initCompleted { r, p in
...
self.navigationController.pushViewController(tabBar, animated: true)
}
You should not mix application logic with DI - use Swinject purely for constructing your dependencies.
So your UITableViewController might look something like this:
func didSelectRowAtIndexPath {
let classToInject = self.loadClassToInjectFor(indexPath)
let tabBar = container.resolve(
SomeTabBarViewController.self, argument: loadClassToInjectFor(indexPath)
)
navigationController.push(tabBar, animated: true)
}
As for your TabBar and its view controllers: how do the UIViewControllers get into TabBar? Is it possible to do something like this?
class SomeTabBarViewController {
init(viewControllers: [UIViewController]) {
...
}
}
container.register(SomeTabBarViewController.self) { r, property
SomeTabBarViewController(viewControllers:[
r.resolve(MyViewController.self, argument: property),
r.resolve(MyViewController2.self, argument: property)
])
}
Finally I got the final answer by following the suggestions proposed.
public class Containers {
fileprivate init() { }
}
extension Containers {
static let activityPresentableContainer: Container = {
let container = Container()
container.register(ActivityTabBarController.self) { (r: Resolver, arg1: Activity) in
return ActivityTabBarController(activity: arg1)
}
container.register(ActivityPresentable.self) {
(r: Resolver, arg1: ActivityPresentableTabs, arg2: Activity) in
switch arg1 {
case .summary:
return ActivitySummaryViewController(activity: arg2)
case .detail:
return ActivityDetailPageViewController(activity: arg2)
case .map:
return ActivityMapViewController(activity: arg2)
case .charts:
return ActivityChartsViewController(activity: arg2)
case .strava:
return ActivityStravaViewController(activity: arg2)
}
}.inObjectScope(.transient)
return container
}()
With this approach, the named ActivityTabBarController gets instantiated always by the activityPresentableContainer using the following statement:
let controller = Containers.activityPresentableContainer.resolve(
ActivityTabBarController.self, argument: activity
)!
And then, each of the tabs inside the TabBarController gets instantiated using the required argument Activity and the type of tab itself using a .transient context. It resolves like this:
let activitySummary = Containers.activityPresentableContainer.resolve(
ActivityPresentable.self, arguments: ActivityPresentableTabs.summary, activity!
) as! UIViewController
This way I can generalize the tabs of the tab bar depending just on the information that they're using. If one of the tabs change in any moment, I can just change the registration, following the ActivityPresentable protocol.

Resources