Skip to content

Instantly share code, notes, and snippets.

@flaksp
Last active March 10, 2024 12:43
Show Gist options
  • Save flaksp/cb31b5eb43fb6e34712e5194116be0aa to your computer and use it in GitHub Desktop.
Save flaksp/cb31b5eb43fb6e34712e5194116be0aa to your computer and use it in GitHub Desktop.
Compilable Needle dependency injection framework example

Compilable Needle RootComponent example

Documentation of the Needle dependency injection framework sucks, and most of articles teaching you how to use it are outdated. That's the reason why it's really hard to write even a minimal-working version of the code that compiles.

This is a simple example of RootComponent that compiles successfully. Based on a code from an article "Dependency Injection with Needle", but modified to make it work.

Bonus: How to configure Xcode

  1. Go to your target
  2. Build Phases
  3. Click "+" button
  4. New Run Script Phase

Move created build phase to very top of your list of build phases.

It's important to make sure "Shell" field containts /bin/sh, and into a field below add following bash script:

export SOURCEKIT_LOGGING=0 && /opt/homebrew/bin/needle generate $SRCROOT/$TARGETNAME/NeedleGenerated.swift $SRCROOT/$TARGETNAME

Note that a path to the needle binary should be absolute. You can locate this path with whereis needle command.

Screenshot 2024-03-10 at 15 34 14

One more thing to do:

  1. Go to your target
  2. Build Settings
  3. Type "sandboxing" into a search field
  4. Set "No" to "User Script Sandboxing" build option

With user script sandboxing enabled Xcode won't allow needle binary to read your project files and modify them.

Screenshot 2024-03-10 at 15 41 21

After you try to build a project NeedleGenerated.swift file will be generated. Add it to a project via menu bar:

  1. File
  2. Add Files to $TARGETNAME...
import NeedleFoundation
import UIKit
final class RootComponent: BootstrapComponent {
public var userInfo: UserDataParseable {
return shared { UserData() }
}
public var detailComponent: DetailComponent {
return DetailComponent(parent: self)
}
public var rootViewController: UIViewController {
return RootViewController(detailBuilder: detailComponent)
}
}
final class RootViewController: UIViewController {
public let detailBuilder: DetailBuilder
public let detailButton = UIButton()
init(detailBuilder: DetailBuilder) {
self.detailBuilder = detailBuilder
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
configureButton()
}
public func configureButton() {
view.addSubview(detailButton)
var buttonConfiguration = UIButton.Configuration.bordered()
buttonConfiguration.title = "Go to Details"
buttonConfiguration.baseBackgroundColor = .white
detailButton.configuration = buttonConfiguration
detailButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
detailButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
detailButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
detailButton.addTarget(self, action: #selector(detailButtonTap), for: .touchUpInside)
}
@objc
public func detailButtonTap() {
present(detailBuilder.detailViewController, animated: true)
}
}
protocol UserDataParseable {
var data: String? { get }
}
final class UserData: UserDataParseable {
public var data: String? = "Ana Maia"
}
protocol DetailDependency: Dependency {
var userInfo: UserDataParseable { get }
}
protocol DetailBuilder {
var detailViewController: UIViewController { get }
}
final class DetailComponent: Component<DetailDependency>, DetailBuilder {
public var detailViewController: UIViewController {
return DetailViewController(userData: dependency.userInfo)
}
}
final class DetailViewController: UIViewController {
public var titleLabel = UILabel()
init(userData: UserDataParseable) {
super.init(nibName: nil, bundle: nil)
titleLabel.text = userData.data
}
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
configureTitleLabel()
}
public func configureTitleLabel() {
view.addSubview(titleLabel)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
titleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
titleLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment