How do I pass arguments to the resolve method when using Swinject? - ios

I have a test project that I'm trying to pass an argument to the resolve method in a Swinject project.
Here is an example of what my Swinject storyboard extetion file has in it.
import Swinject
extension SwinjectStoryboard {
class func setup() {
let mainDm = MainDM()
defaultContainer.register(MainDM.self) { _ in
mainDm
}
defaultContainer.registerForStoryboard(ViewController.self) { r, c in
c.dm = r.resolve(MainDM.self)
c.container = defaultContainer
}
defaultContainer.register(GetMessageAction.self) { _, delegate in
GetMessageAction(dm:mainDm, delegate: delegate)
}
}
}
in my ViewController I'm trying to do the following to resolve the GetMessageAction
#IBOutlet weak var myText: UILabel!
var dm:MainDM!
var container:Container!
override func viewDidLoad() {
super.viewDidLoad()
NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(3), target: self, selector: #selector(ViewController.getMessage), userInfo: nil, repeats: false)
}
func getMessage() {
let action:GetMessageAction? = container.resolve(GetMessageAction.self, argument: self)!
action?.execute()
}
I get the following message when my getMessage function runs
fatal error: unexpectedly found nil while unwrapping an Optional value

As resolving with arguments is dependent on exactly matching types of arguments, you need to downcast passed object:
container.resolve(GetMessageAction.self, argument: self as GetMessageActionDelegate)!
Assuming that GetMessageActionDelegate is the type of delegate passed in constructor GetMessageAction(dm:delegate:).

The swift file of the ViewController you have created in your Storyboard must declare init(NSCoder), it is actually not mentioned in the README.md, I'm thinking about opening an issue regarding this...
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
You can take a look at my open source project using exactly this technique, I am setting up the dependencies using the extension of SwinjectStoryboard here for example the LoadingDataVC.
extension SwinjectStoryboard {
class func setup() {
defaultContainer.register(HTTPClientProtocol.self) { _ in
HTTPClient()
}.inObjectScope(.Container)
defaultContainer.register(APIClientProtocol.self) { r in
APIClient(
httpClient: r.resolve(HTTPClientProtocol.self)!
)
}.inObjectScope(.Container)
defaultContainer.register(ImagePrefetcherProtocol.self) { _ in
ImagePrefetcher()
}.inObjectScope(.Container)
defaultContainer.registerForStoryboard(GameVC.self) { r, c in
c.imagePrefetcher = r.resolve(ImagePrefetcherProtocol.self)
}
defaultContainer.registerForStoryboard(LoadingDataVC.self) { r, c in
c.apiClient = r.resolve(APIClientProtocol.self)
c.imagePrefetcher = r.resolve(ImagePrefetcherProtocol.self)
}
}
}
Once you have the required init it should work! :)

Use either of the following methods of a storyboard to get a view controller registered by registerForStoryboard.
instantiateViewControllerWithIdentifier
instantiateInitialViewController
https://github.com/Swinject/Swinject/blob/v1/Documentation/Storyboard.md
https://github.com/Swinject/SwinjectStoryboard/issues/5

Related

Convenience init for UIViewController not appearing when added via extension

I'm wanting to add a convenience initilizer to UIViewController via an extension because I want all UIViewControllers/UIViewController subclasses to have access to it. But when I add it, it doesn't appear in the drop down list of available initilizers and if I try to use it I get an error saying Missing argument label 'coder:' in call.
extension UIViewController {
convenience init(test: String) {
self.init(nibName: nil, bundle: nil)
print(test)
}
let testController = TestController(test: "Hello World!") // Missing argument label 'coder:' in call
Is there some kind of trick to get this to work?
I am able to add convenience initilizer's to other UIKit classes and have them appear as available inits.
Its working fine as you can check and match your code, maybe you need to delete derived data:
extension UIViewController {
convenience init(test: String) {
self.init(nibName: nil, bundle: nil)
print(test)
}
}
class SecondViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textf: UITextField!
var doubleValue: Double?
override func viewDidLoad() {
super.viewDidLoad()
let testController = SecondViewController(test: "ffwfew")
print(testController)
}
}

Problems getting around 'Super.init isn't called on all paths before returning from initializer swift.'

I'm trying to implement a BPM counter into a basic iOS app.
I'm using this implementation in an XCode project, connecting the addTap method to an onscreen button.
However, I receive the error.
Super.init isn't called on all paths before returning from initializer swift.
So following this answer I found by searching here, I realised I have to call super.init() as defined in the UIViewController class.
I've done that, as shown below - but now I'm getting another compiler error
Cannot convert value of type 'NSCoder.Type' to expected argument type 'NSCoder'
What I have at the minute is pasted below, TIA
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var display: UILabel!
private let timeOutInterval: TimeInterval
private let minTaps: Int
private var taps: [Date] = []
init(timeOut: TimeInterval, minimumTaps: Int) {
timeOutInterval = timeOut
minTaps = minimumTaps
super.init(coder:NSCoder)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addTap() -> Double? {
let thisTap = NSDate()
if let lastTap = taps.last {
if thisTap.timeIntervalSince(lastTap) > timeOutInterval {
taps.removeAll()
}
}
taps.append(thisTap as Date)
guard taps.count >= minTaps else { return nil }
guard let firstTap = taps.first else { return nil }
let avgIntervals = thisTap.timeIntervalSince(firstTap) / Double(taps.count - 1)
return 60.0 / avgIntervals
}
#IBAction func button(_ sender: Any) {
self.display.text = String(addTap())
}
}
Your issue is with the line:
super.init(coder:NSCoder)
The parameter needs to be an instance of NSCoder, not the class itself. But you shouldn't even be calling that initializer.
Change that line to:
super.init(nibName: nil, bundle: nil)

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.

How to use #objc protocol with optional and extensions at the same time?

This code does not compile and might sound stupid as it is, but i'll explain why it's so important!
#objc protocol p {
optional func f1()
func f2()
}
extension p {
func f1() { }
func f2() { }
}
class foo: p {
}
Compiler says Type c does not conform to protocol 'p' and that's maybe because you can not use #objc optional and extensions at the same time (and does not make sence in this scenario either). But consider the following example:
I want to set a selector on a non-optional method defined in protocol in my extension (main reason i used #objc):
func f1() { } -> func f1() { ... #selector(Self.f2) ... }
And i also want my f2() function to have default behaviour. If i mark f2() as optional, it can not be used in #selector because compiler does not know if this method actually exists in the case of need. Sure there're lots of nasty workarounds like global methods, sending Selectors to methods as input and etc, but is there a clean way to achieve it?
This is the practical issue
#objc
protocol Refreshable {
weak var refreshControl: UIRefreshControl? { get set }
optional func setupRefreshControl()
func refresh()
}
#objc
protocol ContentLoader {
func load(reset: Bool)
}
extension Refreshable where Self: ContentLoader {
func refresh() {
delay(0.75) { [weak self] in
self?.load(true)
}
}
}
extension Refreshable where Self: UICollectionViewController {
func setupRefreshControl() {
let newRefreshControl = UIRefreshControl()
newRefreshControl.tintColor = UIColor.grayColor()
newRefreshControl.addTarget(self, action: #selector(Self.refresh), forControlEvents: .ValueChanged)
collectionView?.addSubview(newRefreshControl)
refreshControl = newRefreshControl
}
}
Now if a ViewController implements Refreshable and ContentLoader, it does not find the default refresh function, but it does find setupRefreshControl. So i figured let's mark refresh as optional too, but by doing that, you can not send it to selector any more.
I even tried this:
func refresh() -> optional func refresh()
and
let str = "refresh"
let sel = Selector(str)
It silents the compiler yes, but does not work either... rises unrecognized selector sent to instance....
I think this is not possible in swift (because of the way it bridges to #objc protocols). But this is a work around(using Obj-c associated objects) to solve the unrecognized selector sent to instance... problem.
fileprivate class AssociatedObject: NSObject {
var closure: (() -> ())? = nil
func trigger() {
closure?()
}
}
// Keys should be global variables, do not use, static variables inside classes or structs.
private var associatedObjectKey = "storedObject"
protocol CustomProtocol: class {
func setup()
}
extension CustomProtocol where Self: NSObject {
fileprivate var associatedObject: AssociatedObject? {
get {
return objc_getAssociatedObject(self, &associatedObjectKey) as? AssociatedObject
}
set {
objc_setAssociatedObject(self, &associatedObjectKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
func setup() {
let object = AssociatedObject()
object.closure = { [weak self] in // Do not forget to use weak in order to avoid retain-cycle
self?.functionToCallIndirectlyWithSelector()
}
let selector = #selector(object.trigger)
// Uncomment next line to test it's functionality
object.perform(selector)
// Here, you must add selector to the target which needs to call the selector, for example:
// refreshControl.addTarget(object, action: selector, forControlEvents: .valueChanged)
self.associatedObject = object
}
func functionToCallIndirectlyWithSelector() {
print("Function got called indirectly.")
}
}
class CustomClass: NSObject, CustomProtocol {}
let instance = CustomClass()
instance.setup()
I added Self: NSObject constraint to be able to test it's functionality in playground, I'm not sure if it's necessary or not.

What is wrong with my template/generic Swift initializer/constructor?

I created a SlidingNavigationController where I wanted to have an initializer that takes three parameters. All three parameters should be UIViewControllers but they need to confirm to my SlidingIconProtocol. So I wrote code like this (simplified version):
struct SlidingItem {
var bigIconView: UIView
var smallIconView: UIView
}
protocol SlidingIconProtocol {
var slidingItem: SlidingItem { get set }
}
class SlidingNavigationController: UIViewController {
init<T:UIViewController where T:SlidingIconProtocol>(centralVC: T, leftVC: T, rightVC: T) {
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class CentralVC: UIViewController, SlidingIconProtocol {
var slidingItem = SlidingItem(bigIconView: UIView(), smallIconView: UIView())
}
class LeftVC: UIViewController, SlidingIconProtocol {
var slidingItem = SlidingItem(bigIconView: UIView(), smallIconView: UIView())
}
class RightVC: UIViewController, SlidingIconProtocol {
var slidingItem = SlidingItem(bigIconView: UIView(), smallIconView: UIView())
}
let myVC = SlidingNavigationController(centralVC: CentralVC(), leftVC: LeftVC(), rightVC: RightVC())
The problem is that Swift fails to compile on the last line of code with: "Cannot invoke initializer for type 'SlidingNavigationController' with an argument list of type '(centralVC: CentralVC, leftVC: LeftVC, rightVC: RightVC)'"
Not sure why this does not work, since even Swift/Xcode completion is giving me option to use this initializer. And all passed parameter confirm to SlidingIconProtocol.
Does anyone know what is wrong with the code and what is the right way in Swift to achieve the same (is it possible at all) ?
You can't use template like that way. In your code:
init<T:UIViewController where T:SlidingIconProtocol>(centralVC: T, leftVC: T, rightVC: T)
{
super.init(nibName: nil, bundle: nil)
}
T represents a class that is a subclass of UIViewController and implements SlidingIconProtocol. So when you call:
let myVC = SlidingNavigationController(centralVC: CentralVC(), leftVC: LeftVC(), rightVC: RightVC())
The T is assumed as CentralVC (first parameter), and the init method will be represented as:
init< CentralVC:UIViewController where CentralVC:SlidingIconProtocol>(centralVC: CentralVC, leftVC: CentralVC, rightVC: CentralVC)
{
super.init(nibName: nil, bundle: nil)
}
But you are passing different class object as the second and third parameter. And it will throw error. In your class the following code is valid:
let myVC = SlidingNavigationController(centralVC: CentralVC(), leftVC: CentralVC(), rightVC: CentralVC())
Because all the passed arguments are object of same class (CentralVC). So fixing the issue, you need to implement the init method in the following way:
init<T1:UIViewController, T2:UIViewController, T3:UIViewController where T1:SlidingIconProtocol, T2:SlidingIconProtocol, T3:SlidingIconProtocol>(centralVC: T1, leftVC: T2, rightVC: T3)
{
super.init(nibName: nil, bundle: nil)
}

Resources