Swinject inject self's property into new UIViewController - ios

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.

Related

UITabBarController Shared Data Model - share & update model from anywhere

I'm using a TabBarcontroller type app and I'm using a shared model of the form:
enum WorkoutState {
case Stopped
case Started
case Paused
}
class BaseTBController: UITabBarController {
var workoutState: WorkoutState? = .Stopped
}
Currently all is working and I can access and update the variable across the different tabs using
let tabbar = tabBarController as! BaseTBController
if tabbar.workoutState = .Stop {
//do something
tabbar.workoutState = .Start
}
Now, the situation is that I seem to need to put this all over the place in my code. eg:
startRun()
resumeRun()
pauseRun()
Is there a better way to do this instead of putting
let tabbar = tabBarController as! BaseTBController
tabbar.workoutState = .Start
in each of the 3 functions?
You can always use protocol and default extension to achieve what you need
protocol HandleWorkStateProtocol where Self: UIViewController {
func updateWorkOutState(to: WorkoutState)
}
extension HandleWorkStateProtocol {
func updateWorkOutState(to state: WorkoutState) {
guard let tabBarController = self.tabBarController as? BaseTBController else { return }
tabBarController.workoutState = state
}
}
In all you view controller's that has these 3 methods (startRun, resumeRun, pauseRun) simply confirm to this protocol and call updateWorkOutState(to: with appropriate value to modify the status
class SomeTestViewController: UIViewController {
func startRun() {
self.updateWorkOutState(to: .Started)
}
func resumeRun() {
}
func pauseRun() {
self.updateWorkOutState(to: .Paused)
}
}
extension SomeTestViewController: HandleWorkStateProtocol {}
P.S
Case values of enum does not follow Pascal casing like Stopped instead it follows Camel casing stopped so change your enum values to
enum WorkoutState {
case stopped
case started
case paused
}

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

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...

Storing UIViewController generic with a protocol as a property

Okay, so I'm pretty sure I'm overthinking this.
I am passing through a viewController that conforms to a protocol as a generic like so:
static func sortPage<T: UIViewController>(controller: T, err: NSError) where T: SortAlertDelegate { }
What I want to be able to do is store that controlleras a property so I can access all the functions UIViewController gives me and the functions thats the SortAlerDelegate gives me.
Any ideas?
You can't specify a type and protocol conformance for a property. You'll need to cast your property to the correct type whenever you want to use specific features. However, you can make this less painful with a bit of boilerplate:
let myProperty: UIViewController? = nil {
willSet(newValue) {
if (newValue as? SortAlertDelegate != nil) {
myProperty = newValue
} else {
myProperty = nil
}
}
}
This way, if you try to set the property to an object which doesn't conform to the protocol, the set will be aborted and the property will be set to nil.
You can also write read-only properties in order to get your property as the type you need at the moment:
let myPropertyAsViewController: UIViewController? {
get { return myProperty }
}
let myPropertyAsDelegate: SortAlertDelegate? {
get {
if let myProperty = myProperty {
return myProperty as! SortAlertDelegate
} else {
return nil
}
}
}
In general it is not possible unless you move the generic constraint on top of your class definition like:
class ViewController<T: UIViewController> : UIViewController where T: SortAlertDelegate {
let delegateController: T
}
But you can also make two references in your class like
class ViewController: UIViewController {
let controller: UIViewController
let delegate: SearchAlertDelegate
}
And then store the same object as two different references.

Swinject register UIViewController best practice

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.

Resources