Swift extension only when conforming to Class AND protocol - swift

I have two protocols with extensions LoginPresenting and LoginDismissing. I want LoginPresenting extension only to apply to UIViewControllers that also implement LoginDismissing. I am trying to do it like so but have so far been unable to work out the syntax.
protocol LoginDismissing : class {
func loginHasCompleted(withController: UIViewController)
}
extension LoginDismissing where Self:UIViewController {
func loginHasCompleted(withController controller:UIViewController) {
//...code ommited
}
}
protocol LoginPresenting : class {
func presentLogin()
}
The following is bad code, but I think it explains how I am attempting to make LoginPresenting only apply to UIViewControllers that also implement LoginDismissing.
extension LoginPresenting where Self:UIViewController, LoginDismissing //Syntax error here
func presentLogin() {
let lc = LoginViewController()
let nav = UINavigationController(rootViewController: lc)
nav.modalPresentationStyle = .fullScreen
lc.loginDismissingDelegate = self //type LoginDismissing
self.present(nav, animated: true, completion: nil)
}
}

You can resolve your syntax error with the use of & instead of a comma:
extension LoginPresenting where Self: UIViewController & LoginDismissing {
// ...
}
This makes the extension only apply to view controllers that also conform to LoginDismissing.

You should enforce as many requirements as possible on the protocol itself, not the extensions.
protocol LoginPresenting: LoginDismissing & UIViewController {
func presentLogin()
}
Also, class is old syntax, though not deprecated.
protocol LoginDismissing: AnyObject {

Related

Best approach for reusable swift code UIKit

I cannot see a practical approach for reusable storyboard patterns in Xcode/Swift. I have a number of ViewControllers which have almost identical behavior and appearance. So the viewDidLoad() is always the same:
override func viewDidLoad() {
super.viewDidLoad()
labelKESumme.text = something
}
Because each ViewController has to be configured with its own class and Swift lacks multi inheritance I've tried to create an extension like this:
extension UIViewController {
func show() {
self.labelKESumme.text = something <== ... has no member
}
}
But that doesn't work due to 'has no member' errors. Also it's not possible to configure outlets between the label and the extension code. Need an approach for configure many view controllers with same content without code duplication.
You can use protocol extensions like below
// protocol
protocol Showable {
var labelKESumme: UILabel?
func show()
}
//protocol extension if you want same functionality everywhere
extension Showable {
func show() {
labelKESumme?.text = something
}
}
// just conform the the protocol
ABCViewController: Showable {
}
// just conform the the protocol
XYZViewController: Showable {
}
// In some code where you have viewController as UIViewController and you know
// that this object conforms to Showable protocol then you can do like this
guard let showable = viewController as? Showable {
return
}
showable.show()

Using self in function in a protocol

I have this protocols:
One to instantiate a ViewController from Storyboard:
protocol Storyboarded {
static func instantiate() -> Self
}
extension Storyboarded where Self: UIViewController {
static func instantiate() -> Self {
// this pulls out "MyApp.MyViewController"
let fullName = NSStringFromClass(self)
// this splits by the dot and uses everything after, giving "MyViewController"
let className = fullName.components(separatedBy: ".")[1]
// load our storyboard
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
// instantiate a view controller with that identifier, and force cast as the type that was requested
return storyboard.instantiateViewController(withIdentifier: className) as! Self
}
}
One to inject Dependencies in to Viewcontrollers:
protocol DependencyInjection where Self: UIViewController {
associatedtype myType: DependencyVC
func injectDependencys(dependency: myType)
}
Now I want to add another one, so I can create the ViewController from the Dependency itself:
protocol DependencyVC {
associatedtype myType: DependencyInjectionVC & Storyboarded
func createVC() -> myType
}
extension DependencyVC {
func makeVC<T: Storyboarded & DependencyInjection>() -> T where T.myType == Self {
let viewController = T.instantiate()
viewController.injectDependencys(dependency: self)
return viewController
}
}
But I get this error for self:
Cannot invoke 'injectDependencys' with an argument list of type
'(dependency: Self)'
This is a DependencyClass I have:
class TopFlopDependency: DependencyVC {
typealias myType = TopFlopVC
var topFlopState: TopFlopState
lazy var topFlopConfig: TopFlopConfig = {
let SIBM = StatIntervalBaseModel(stat: "ppc", interval: "24h", base: "usd")
return TopFlopConfig(group: Groups.large, base: "usd", valueOne: SIBM)
}()
init(state: TopFlopState) {
self.topFlopState = state
}
func createVC() -> TopFlopVC {
let topflopVC = TopFlopVC.instantiate()
topflopVC.injectDependencys(dependency: self)
let viewController: TopFlopVC = makeVC()
return topflopVC
}
}
I get this error when using makeVC:
'TopFlopDependency' requires the types 'TopFlopDependency.myType' and
'TopFlopDependency.myType' (aka 'TopFlopVC') be equivalent to use
'makeVC'
other Solution:
protocol DependencyVC {
}
extension DependencyVC {
func makeVC<T: Storyboarded & DependencyInjection>() -> T where T.myType == Self {
let viewController = T.instantiate()
viewController.injectDependencys(dependency: self)
return viewController
}
}
When trying to use:
let viewController: TopFlopVC = makeVC()
I get the error that T could not be inferred.
Why can I not do this? Do you have a solution how I would get it to work?
Thank you!
You need to add another constraint. Your DependencyInjection protocol requires a very specific type of DependencyVC (myType). But your DependencyVC extension works with any DependencyVC. So you need to constrain T’s myType to be the same type with a where clause: func createVC<T: Storyboarded & DependencyInjection>() -> T where T.myType == Self
So a complete example would look like this:
protocol Storyboarded {
static func instantiate() -> Self
}
extension Storyboarded where Self: UIViewController {
static func instantiate() -> Self {
...
}
}
protocol DependencyVC {
}
protocol DependencyInjection where Self: UIViewController {
associatedtype myType: DependencyVC
func injectDependencys(dependency: myType)
}
extension DependencyVC {
func makeVC<T: Storyboarded & DependencyInjection>(type _: T.Type? = nil) -> T where T.myType == Self {
let viewController = T.instantiate()
viewController.injectDependencys(dependency: self)
return viewController
}
}
struct MyDependency: DependencyVC {}
class MyVC: UIViewController, Storyboarded, DependencyInjection {
func injectDependencys(dependency: MyDependency) {
print(dependency)
}
}
When you call viewController.injectDependencys(dependency: self), self is known to be of some subtype of DependencyVC. However, DependencyInjection's associatedtype myType: DependencyVC just says that a type conforming to DependencyInjection will use some type for myType (that conforms to DependencyVC). So there's no guarantee that its actual type will be a subtype of myType.
associatedtypes don't quite work the same way as generic type parameters in that associatedtypes are given when "defining" a type, while generic type parameters are given when "using" a type.
It all boils down to the fact that you probably don't want to have an associatedtype myType, instead taking a DependencyVC directly.
Update
In light of the additional information you've provided, I believe this would be the best solution:
protocol DependencyInjection where Self: UIViewController {
func injectDependency(_ dependency: DependencyVC)
}
protocol DependencyVC {
func makeVC<T: Storyboarded & DependencyInjection>() -> T
}
extension DependencyVC {
func makeVC<T: Storyboarded & DependencyInjection>() -> T {
let viewController = T.instantiate()
viewController.injectDependency(self)
return viewController
}
}
As you may notice, I took the liberty of renaming injectDependencys(dependency: DependencyVC) to injectDependency(_ dependency: DependencyVC), because you're only injecting one dependency and the dependency: label doesn't really add anything at the call site.
Anyway, this allows you to create instances of view controllers using your dependency. Say you have the dependency stored in a variable named dependency, then you can create a view controller from it by going let topFlopVC: TopFlopVC = dependency.makeVC()

Swift generics, cannot infer type T

I've tried a bunch of different things but i'm not good at generics.
How do I call a function with a variable that needs to be both a solid type like UIViewController, as well as conforming to a protocol like NavBarResponder ?
Thanks!
weak var lastNavBarResponder: UIViewController?
func reloadExtendedNavBar() {
self.updateState()
self.updateStatusBadges()
if let vc = self.lastNavBarResponder {
self.setup(viewController: vc) // Error: Generic parameter T cannot be inferred
}
}
func setup<T: UIViewController>(viewController: T) where T: NavBarResponder {
...
I suspect the error is because UIViewController doesn't naturally conform to NavBarResponder, so the compiler can't guarantee that lastNavBarResponder fits the requirements of setup.
Try changing lastNavBarResponder to be of a type that is a UIViewController that conforms to the NavBarResponder protocol.
Here is a self-contained example showing how this would be implemented.
protocol NavBarResponder {}
class NavBarResponderController: UIViewController {}
extension NavBarResponderController: NavBarResponder {}
var lastNavBarResponder: NavBarResponderController? = NavBarResponderController()
func setup<T: UIViewController>(viewController: T) where T: NavBarResponder {
print("Works")
}
func reloadExtendedNavBar() {
if let vc = lastNavBarResponder {
setup(viewController: vc)
}
}
reloadExtendedNavBar()

Swift require protocol extension

If I have
protocol AppearingTextContainer {
func clipBounds() -> ()
}
extension AppearingTextContainer where Self: UIView {
func clipBounds() { self.clipsToBounds = true }
}
then a class that adopts my protocol CustomView: AppearingTextContainer is not forced by the compiler to implement clipBounds. If I remove the extension it won't compile. Is there any way to enforce CustomView to call clipBounds without having to delete the default clipBounds implementation?
Thanks
You've only provided a method that will become available to any UIView that chooses to conform to AppearingTextContainer.
So now ideally you would create a UIView subclass that conforms
class AppearingTextView: UIView, AppearingTextContainer {
// you can now call clipBounds() on this class
}
or
extension UIView: AppearingTextView { }
what I think you should consider is whether or not what you really want is a protocol extension vs. just an extension on UIView:
extension UIView {
func clipsBounds() -> Void { clipsToBounds = true }
}
if you want a protocol, I suggest going the route of:
protocol ClippingView {
var clipsToBounds: Bool { get set }
}
extension ClippingView {
func clipsBounds() -> Void { clipsToBounds = true }
}
extension UIView: ClippingView { }
let clippingViews: [ClippingView] = [......]
clippingViews.forEach { $0.clipsBounds() }
If your custom view class has its own clipBounds function, then you would have to cast your custom view to an AppearingTextContainer in order to get the AppearingTextContainer protocol extension implementation.
But keep in mind that Objective-C cannot see your protocol extension, so Cocoa will never call the protocol extension clipBounds for you; only a direct Swift call can call it.

Reference to class that conforms to a protocol

Let's say I have a protocol that looks like this:
protocol Foo {
var bar: Bool { get set }
}
Now I have my view controller that conforms to the Foo protocol;
class FooViewController: UIViewController, Foo {
...
}
In another class I want to do something like this:
class FooClass {
var viewController: UIViewController? // this should conform to Foo protocol
func setViewController(viewController: UIViewController) {
if let fooVC = viewController as? Foo {
// viewController implements Foo protocol
self.viewController = fooVC
} else {
print("ViewController does not conform to Foo protocol")
}
}
}
In Objective-C I would have a reference that looks like: UIViewController<Foo> *vc saying that vc is an object of class UIViewController conforming to Foo protocol. Is there a Swift equivalent?
EDIT: I guess it's not possible :(
Warning: This solution may become very painful.
As a workaround, you may use protocol compositions and eventually extensions.
In your example you could be interested only in UIResponder protocol methods which UIViewController conforms to.
typealias FooController = protocol<UIResponder, Foo>
class FooClass {
var viewController: FooController?
func setViewController(viewController: FooController) {
// viewController implements UIResponder and Foo protocols
}
}
But UIViewController has properties and methods that does not conforms to any protocol. In this case, you could make a protocol containing the methods that you want and extend UIViewController.
protocol ViewEvent {
func viewWillAppear(animated: Bool)
func viewDidAppear(animated: Bool)
func viewWillDisappear(animated: Bool)
func viewDidDisappear(animated: Bool)
}
extension UIViewController: ViewEvent {}
typealias FooController = protocol<UIResponder, ViewEvent, Foo>
This is the solution:
typealias FooController = UIViewController & Foo
class FooClass {
var viewController: FooController?
func setViewController(viewController: FooController) {
self.viewController = viewController
}
}
I see what you mean.
I don't think you can declare a variable of a given class type and conform to a protocol.
However if you just need to check whether viewController does conform to the Foo protocol (and you don't really need to use the bar property declared in Foo) then you could do this.
func setViewController(viewController: UIViewController) {
if viewController is Foo {
self.viewController = viewController
} else {
print("ViewController does not conform to Foo protocol")
}
}
You can define as viewController:Foo?
Based on our comment you should used if let fooVC = viewController as? FooViewController. If you are optional binding the viewController to Foo then assigning it to a UIViewController instance will not work.