Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to pass arguments #62

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,30 @@ container.register(Animal.self) { _ in Cat(name: "Mimi") }

If you implicitly instantiate `UIWindow` and its root view controller, the registrations setup for "Main" storyboard can be shared with the referenced storyboard since `defaultContainer` is configured in `setup` method.

### Arguments
if you want to pass some arguments, you can do it in such way:

1) Register VC
```swift
let container = SwinjectStoryboard.defaultContainer
container.storyboardInitCompleted(AnimalViewController.self) { r, c, arg1: Int, arg2: SomeValue in
c.animal = r.resolve(Animal.self)
c.countAnimals = arg1
c.someValue = arg2
}
container.register(Animal.self) { _ in Cat(name: "Mimi") }
```

2) Resolve VC
```swift
let sb = SwinjectStoryboard.create(
name: "Animals", bundle: nil, container: container)
let firstArg: Int = 5
let secondArg: SomeValue = SomeValue()
let catController = sb.instantiateViewController(withIdentifier: "SomeIdentifier", arg1: firstArg, arg2: secondArg) as! AnimalViewController
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be more consistent to have API similar to Swinject, i.e.
instantiateViewController(withIdentifier: "SomeIdentifier", arguments: firstArg, secondArg)

```


## Credits

SwinjectStoryboard is inspired by:
Expand Down
99 changes: 98 additions & 1 deletion Sources/SwinjectStoryboard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public class SwinjectStoryboard: _SwinjectStoryboardBase, SwinjectStoryboardProt
return viewController
}

private func injectDependency(to viewController: UIViewController) {
fileprivate func injectDependency(to viewController: UIViewController) {
guard !viewController.wasInjected else { return }
defer { viewController.wasInjected = true }

Expand Down Expand Up @@ -158,3 +158,100 @@ public class SwinjectStoryboard: _SwinjectStoryboardBase, SwinjectStoryboardProt
}

#endif


extension SwinjectStoryboard {

#if os(iOS) || os(tvOS)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably want to extend this for OSX as well


private func injectDependency<Arg>(to viewController: UIViewController, arg: Arg) {
guard !viewController.wasInjected else { return }
defer { viewController.wasInjected = true }

let registrationName = viewController.swinjectRegistrationName

if let container = container.value as? _Resolver {
let option = SwinjectStoryboardOption(controllerType: type(of: viewController))
typealias FactoryType = (Resolver, Container.Controller, Arg) -> Container.Controller
let _ = container._resolve(name: registrationName, option: option) { (factory: FactoryType) in factory(self.container.value, viewController, arg) }
} else {
fatalError("A type conforming Resolver protocol must conform _Resolver protocol too.")
}

for child in viewController.childViewControllers {
injectDependency(to: child)
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a lot of repeated code between methods with different arguments. Can we reuse this code somehow? Alternatively we can generate this similarly to how it is done in Swinject (see. Container.Arguments.erb)


private func injectDependency<Arg1, Arg2>(to viewController: UIViewController, arg1: Arg1, arg2: Arg2) {
guard !viewController.wasInjected else { return }
defer { viewController.wasInjected = true }

let registrationName = viewController.swinjectRegistrationName

if let container = container.value as? _Resolver {
let option = SwinjectStoryboardOption(controllerType: type(of: viewController))
typealias FactoryType = (Resolver, Container.Controller, Arg1, Arg2) -> Container.Controller
let _ = container._resolve(name: registrationName, option: option) { (factory: FactoryType) in factory(self.container.value, viewController, arg1, arg2) }
} else {
fatalError("A type conforming Resolver protocol must conform _Resolver protocol too.")
}

for child in viewController.childViewControllers {
injectDependency(to: child)
}
}

private func injectDependency<Arg1, Arg2, Arg3>(to viewController: UIViewController,
arg1: Arg1, arg2: Arg2, arg3: Arg3) {
guard !viewController.wasInjected else { return }
defer { viewController.wasInjected = true }

let registrationName = viewController.swinjectRegistrationName

if let container = container.value as? _Resolver {
let option = SwinjectStoryboardOption(controllerType: type(of: viewController))
typealias FactoryType = (Resolver, Container.Controller, Arg1, Arg2, Arg3) -> Container.Controller
let _ = container._resolve(name: registrationName, option: option) { (factory: FactoryType) in factory(self.container.value, viewController, arg1, arg2, arg3) }
} else {
fatalError("A type conforming Resolver protocol must conform _Resolver protocol too.")
}

for child in viewController.childViewControllers {
injectDependency(to: child)
}
}

//MARK: - instantiateViewController with Args

public func instantiateViewController<Arg>(withIdentifier identifier: String,
arg: Arg) -> UIViewController {
let viewController = loadViewController(with: identifier)
injectDependency(to: viewController, arg: arg)
return viewController
}

public func instantiateViewController<Arg1, Arg2>(withIdentifier identifier: String,
arg1: Arg1, arg2: Arg2) -> UIViewController {
let viewController = loadViewController(with: identifier)
injectDependency(to: viewController, arg1: arg1, arg2: arg2)
return viewController
}

public func instantiateViewController<Arg1, Arg2, Arg3>(withIdentifier identifier: String,
arg1: Arg1, arg2: Arg2, arg3: Arg3) -> UIViewController {
let viewController = loadViewController(with: identifier)
injectDependency(to: viewController, arg1: arg1, arg2: arg2, arg3: arg3)
return viewController
}

private func loadViewController(with identifier: String) -> UIViewController {
SwinjectStoryboard.pushInstantiatingStoryboard(self)
let viewController = super.instantiateViewController(withIdentifier: identifier)
SwinjectStoryboard.popInstantiatingStoryboard()
return viewController
}

#endif

}
86 changes: 86 additions & 0 deletions Sources/SwinjectStoryboardExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// SwinjectStoryboardExtensions.swift
// SwinjectStoryboard
//
// Created by Malkevych Bohdan on 10.08.17.
// Copyright © 2017 Swinject Contributors. All rights reserved.
//

import Swinject

#if os(iOS) || os(OSX) || os(tvOS)
extension Container {
/// Adds a registration of the specified view or window controller that is configured in a storyboard.
///
/// - Note: Do NOT explicitly resolve the controller registered by this method.
/// The controller is intended to be resolved by `SwinjectStoryboard` implicitly.
///
/// - Parameters:
/// - controllerType: The controller type to register as a service type.
/// The type is `UIViewController` in iOS, `NSViewController` or `NSWindowController` in OS X.
/// - name: A registration name, which is used to differenciate from other registrations
/// that have the same view or window controller type.
/// - initCompleted: A closure to specifiy how the dependencies of the view or window controller are injected.
/// It is invoked by the `Container` when the view or window controller is instantiated by `SwinjectStoryboard`.

public func storyboardInitCompletedArg<C: Controller, Arg>(_ controllerType: C.Type,
name: String? = nil, initCompleted: @escaping (Resolver, C, Arg) -> ()) {
var arg: Arg!
let factory = { (_: Resolver, controller: Controller,
argLocal: Arg) -> Container.Controller in
arg = argLocal
return controller
}

let wrappingClosure: (Resolver, Controller) -> () = { r, c in
initCompleted(r, c as! C, arg)
}
let option = SwinjectStoryboardOption(controllerType: controllerType)
_register(Controller.self, factory: factory, name: name, option: option)
.initCompleted(wrappingClosure)
}

public func storyboardInitCompletedArgs<C: Controller, Arg1, Arg2>(_ controllerType: C.Type,
name: String? = nil,
initCompleted: @escaping (Resolver, C, Arg1, Arg2) -> ()) {
var arg1: Arg1!
var arg2: Arg2!
let factory = { (_: Resolver, controller: Controller,
argLocal1: Arg1, argLocal2: Arg2) -> Container.Controller in
arg1 = argLocal1
arg2 = argLocal2
return controller
}

let wrappingClosure: (Resolver, Controller) -> () = { r, c in
initCompleted(r, c as! C, arg1, arg2)
}
let option = SwinjectStoryboardOption(controllerType: controllerType)
_register(Controller.self, factory: factory, name: name, option: option)
.initCompleted(wrappingClosure)
}

public func storyboardInitCompletedArgs<C: Controller, Arg1, Arg2, Arg3>(_ controllerType: C.Type,
name: String? = nil,
initCompleted: @escaping (Resolver, C, Arg1, Arg2, Arg3) -> ()) {
var arg1: Arg1!
var arg2: Arg2!
var arg3: Arg3!
let factory = { (_: Resolver, controller: Controller,
argLocal1: Arg1, argLocal2: Arg2, argLocal3: Arg3) -> Container.Controller in
arg1 = argLocal1
arg2 = argLocal2
arg3 = argLocal3
return controller
}

let wrappingClosure: (Resolver, Controller) -> () = { r, c in
initCompleted(r, c as! C, arg1, arg2, arg3)
}
let option = SwinjectStoryboardOption(controllerType: controllerType)
_register(Controller.self, factory: factory, name: name, option: option)
.initCompleted(wrappingClosure)
}

}
#endif
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this file should probably be generated (see. Resolver.erb)

2 changes: 1 addition & 1 deletion SwinjectStoryboard.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Pod::Spec.new do |s|
s.homepage = "https://github.com/Swinject/SwinjectStoryboard"
s.license = 'MIT'
s.author = 'Swinject Contributors'
s.source = { :git => "https://github.com/Swinject/SwinjectStoryboard.git", :tag => s.version.to_s }
s.source = { :git => "https://github.com/SeductiveMobile/SwinjectStoryboard.git", :tag => s.version.to_s }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 😉


core_files = 'Sources/*.swift'
umbrella_header_file = 'Sources/SwinjectStoryboard.h' # Must be at the end of 'source_files' to workaround CococaPods issue.
Expand Down
8 changes: 8 additions & 0 deletions SwinjectStoryboard.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@
98978E6A1DFC354B0046B966 /* UnavailableItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98978E671DFC354B0046B966 /* UnavailableItems.swift */; };
98D562821CDB173500DECDC0 /* SwinjectStoryboard.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 98D562781CDB173500DECDC0 /* SwinjectStoryboard.framework */; };
98D5629E1CDB19AB00DECDC0 /* SwinjectStoryboard.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 98D562941CDB19AB00DECDC0 /* SwinjectStoryboard.framework */; };
B92E951F1F3CCEAD0051FD3E /* SwinjectStoryboardExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B92E951E1F3CCEAD0051FD3E /* SwinjectStoryboardExtensions.swift */; };
B92E95201F3CCEAD0051FD3E /* SwinjectStoryboardExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B92E951E1F3CCEAD0051FD3E /* SwinjectStoryboardExtensions.swift */; };
B92E95211F3CCEAD0051FD3E /* SwinjectStoryboardExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B92E951E1F3CCEAD0051FD3E /* SwinjectStoryboardExtensions.swift */; };
CD2C63AA1D7864840075BC14 /* SwinjectStoryboard+StoryboardReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD2C63A91D7864840075BC14 /* SwinjectStoryboard+StoryboardReference.swift */; };
CD2C63AB1D7864840075BC14 /* SwinjectStoryboard+StoryboardReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD2C63A91D7864840075BC14 /* SwinjectStoryboard+StoryboardReference.swift */; };
CD2C63AD1D786C1F0075BC14 /* RelationshipReference1.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CD2C63AC1D786C1F0075BC14 /* RelationshipReference1.storyboard */; };
Expand Down Expand Up @@ -254,6 +257,7 @@
98D562CA1CDB1E8800DECDC0 /* tvOS-Base.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "tvOS-Base.xcconfig"; sourceTree = "<group>"; };
98D562CB1CDB1E8800DECDC0 /* tvOS-Framework.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "tvOS-Framework.xcconfig"; sourceTree = "<group>"; };
98D562CC1CDB1E8800DECDC0 /* tvOS-StaticLibrary.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "tvOS-StaticLibrary.xcconfig"; sourceTree = "<group>"; };
B92E951E1F3CCEAD0051FD3E /* SwinjectStoryboardExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwinjectStoryboardExtensions.swift; sourceTree = "<group>"; };
CD2C63A91D7864840075BC14 /* SwinjectStoryboard+StoryboardReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwinjectStoryboard+StoryboardReference.swift"; sourceTree = "<group>"; };
CD2C63AC1D786C1F0075BC14 /* RelationshipReference1.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = RelationshipReference1.storyboard; sourceTree = "<group>"; };
CD2C63AE1D786CD70075BC14 /* RelationshipReference2.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = RelationshipReference2.storyboard; sourceTree = "<group>"; };
Expand Down Expand Up @@ -456,6 +460,7 @@
98978E671DFC354B0046B966 /* UnavailableItems.swift */,
985904091CDB0AA700275E4A /* SwinjectStoryboard.h */,
9859040B1CDB0AA700275E4A /* Info.plist */,
B92E951E1F3CCEAD0051FD3E /* SwinjectStoryboardExtensions.swift */,
);
path = Sources;
sourceTree = "<group>";
Expand Down Expand Up @@ -906,6 +911,7 @@
983DFF0B1CDB440800D39731 /* Box.swift in Sources */,
983DFEBE1CDB414900D39731 /* _SwinjectStoryboardBase.m in Sources */,
983DFEB41CDB410D00D39731 /* ViewController+Swinject.swift in Sources */,
B92E951F1F3CCEAD0051FD3E /* SwinjectStoryboardExtensions.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -943,6 +949,7 @@
983DFF0C1CDB440800D39731 /* Box.swift in Sources */,
983DFEC41CDB415300D39731 /* _SwinjectStoryboardBase.m in Sources */,
983DFEB51CDB410D00D39731 /* ViewController+Swinject.swift in Sources */,
B92E95201F3CCEAD0051FD3E /* SwinjectStoryboardExtensions.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -981,6 +988,7 @@
983DFF0D1CDB440800D39731 /* Box.swift in Sources */,
983DFEBF1CDB414900D39731 /* _SwinjectStoryboardBase.m in Sources */,
983DFEB61CDB410D00D39731 /* ViewController+Swinject.swift in Sources */,
B92E95211F3CCEAD0051FD3E /* SwinjectStoryboardExtensions.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down