Generic Paramter 'T' could not be inferred - Problems with generics - ios

I have this class called openApp, thats meant to open another app using a redirect url and store kit. Im not too familiar with generics and its making me run into this error
Generic Parameter 'T' could not be inferred
Am I not handling the use of T correctly? I really don't understand whats going on here.
public class openApp {
static func openOrDownloadPlayPortal<T>(delegate: T) where T: SKStoreProductViewControllerDelegate, T:
UIViewController {
let storeProductVC = SKStoreProductViewController()
let playPortalURL = URL(string: "redirect url")!
if UIApplication.shared.canOpenURL(playPortalURL) {
UIApplication.shared.openURL(playPortalURL)
}
else {
let vc = SKStoreProductViewController()
let params = [
SKStoreProductParameterITunesItemIdentifier: "app identifier"
]
vc.loadProduct(withParameters: params) { success, err in
if let err = err {
}
}
vc.delegate = delegate
delegate.present(vc, animated: true, completion: nil)
}
}
}

Since the issue appears when calling the openOrDownloadPlayPortal method as:
openApp.openOrDownloadPlayPortal(delegate: self)
You will face the mentioned error:
Generic parameter 'T' could not be inferred
if your class does not conforms to the SKStoreProductViewControllerDelegate. For example, let's assume that you are calling it in ViewController class, as:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
openApp.openOrDownloadPlayPortal(delegate: self)
}
}
So, you have to make sure that ViewController has:
extension ViewController: SKStoreProductViewControllerDelegate {
// ...
}
The reason of the error is: the compiler assumes that the T parameter in openOrDownloadPlayPortal method has to conforms to the SKStoreProductViewControllerDelegate, therefore implementing
openApp.openOrDownloadPlayPortal(delegate: self)
means that it will not be recognized as the appropriate type for the T, unless you make self (ViewController in the above example) to be conformable to SKStoreProductViewControllerDelegate.

Related

Value of type 'UIViewController' has no member 'newExerciseDelegate'

I've searched for a solution to this problem, even tried following a few tutorials to try to solve this, but for some reason I'm ending up with the same issue. I'm attempting to pass a custom object to a different view controller, but every time I try I get the error "Value of type 'UIViewController' has no member 'newExerciseDelegate'"
My delegate:
protocol exerciseDelegate {
func savedExercise(newExercise: Exercise)
}
my sending VC uses the following code:
var newExerciseDelegate: exerciseDelegate!
.
.
.
#IBAction func saveExerciseWasPressed(_ sender: UIButton) {
if (checkFields() == true){
newExercise = Exercise(name: exerciseNameField.text!, weight: Int32(weightField.text!)!, reps: Int32(numberOfRepsField.text!)!, sets: Int32(numberOfSetsField.text!)!, muscleGroupFocus: .cardio)
newExerciseDelegate.savedExercise(newExercise: newExercise)
dismiss(animated: false, completion: nil)
}
}
My receiving VC uses the following code:
#IBAction func addExerciseBtnWasPressed(_ sender: Any) {
guard let newExerciseVC = storyboard?.instantiateViewController(withIdentifier: "NewExerciseVC") else { return }
newExerciseVC.newExerciseDelegate = self // error present on this line
presentDetail(newExerciseVC)
}
I'm sure it's a stupid mistake, but I'm not seeing it. Any help is appreciated, thank you.
You should specify which class it is.After the code know which class actually it is, then you can access it's public objects, methods, variables etc.
#IBAction func addExerciseBtnWasPressed(_ sender: Any) {
guard let newExerciseVC = storyboard?.instantiateViewController(withIdentifier: "NewExerciseVC") as? NewExerciseViewController else { return }
newExerciseVC.newExerciseDelegate = self
presentDetail(newExerciseVC)
}
If you are accessing that delegate which is declared in your ViewController then you should call in the below way.
let childOne = self.storyboard?.instantiateViewController(withIdentifier:"WhatsNewViewController") as? WhatsNewViewController
You have to downcast the instantiated view controller to your custom view controller class:
guard let newExerciseVC = storyboard?.instantiateViewController(withIdentifier: "NewExerciseVC") as? YourViewControllerClass else { return }
newExerciseVC.newExerciseDelegate = self
Also you should use a capital E for your protocol's name.

Cast type not work in generic function scope. Why?

I did a class Router (subclass of UINavigationController) in my project for centralize and minimize the code used to instantiate and navigate between views. But when i try minimize a particular function (buildView), the type casting not work. But this works fine out of scope of function buildView, same whithout the as! operator in function goHome.
enum Routes {
case home
case account
var file: String {
switch self {
case .home:
return "HomeView"
case .account:
return "AccountView"
}
}
protocol HomeInterface: class {
func goTo(view: Routes)
func showModal(view: Routes, caller: UIViewController)
}
class HomePresenter: NSObject, HomeInterface {
init(view: HomeViewInterface) {
self.view = view
}
internal func goTo(view: Routes) { /* Implementation */ }
internal func showModal(view: Routes, caller: UIViewController) {/* Implementation */ }
}
protocol HomeViewInterface: class {
/* Implementation */
}
class HomeViewController: UIViewController, HomeViewInterface {
var presenter: HomeInterface?
override func viewDidLoad() {
super.viewDidLoad()
}
/* Implementation */
}
Working Code
func goHome() {
let viewInstance = buildView(view.file, HomeViewController.identifier, HomeViewController.self)
viewInstance.presenter = HomePresenter(view: viewInstance)
self.view?.pushViewController(viewInstance, animated: true)
}
private func buildView<T>(_ nameFile: String, _ identifier: String, _ viewClass: T.Type) -> T {
return UIStoryboard(name: nameFile, bundle: nil).instantiateViewController(withIdentifier: identifier) as! T
}
Desired final code, but does not work:
func goHome() {
buildViewFinal(view.file, HomeViewController.identifier, HomeViewController.self)
}
func buildViewFinal<T, P>(_ nameFile: String, _ identifier: String, viewClass: T, presenter: P) {
let viewInstance = UIStoryboard(name: nameFile, bundle: nil).instantiateViewController(withIdentifier: identifier) as? T
viewInstance.presenter = P(view: viewInstance)
self.view?.pushViewController(viewInstance, animated: true)
}
When i try minimize the code only to buildViewFinalfunction, the property presenter of viewInstance is not recognize, showing a compile error
Value of type 'T?' has no member 'presenter'
, and in pushViewControllershow error:
Cannot convert value of type 'T?' to expected element type
'UIViewController'
The main goal is turn all code to create and navigate useful and simple.
So, how this works fine in first code, but fails in recognize type inside buildViewFinal scope?
In your first piece of code, you are passing HomeViewController.self as viewClass and so it knows that buildViewFinal is going to return an instance of HomeViewController and that HomeViewController has a presenter property.
In the second code snippet, the compiler doesn't know anything about T, so it can't assume that it will have a presenter property.
You could use type constraint to enforce that T is a HomeViewController or whatever class defines the presenter property:
func buildViewFinal<T: HomeInterface, P>(_ nameFile: String, _ identifier: String, viewClass: T, presenter: P) {
let viewInstance = UIStoryboard(name: nameFile, bundle: nil).instantiateViewController(withIdentifier: identifier) as? T
viewInstance.presenter = P(view: viewInstance)
self.view?.pushViewController(viewInstance, animated: true)
}
but then you will have a problem that viewInstance can't be pushed because the compiler doesn't know that it is an instance of a UIViewController subclass.
Really generics and protocols are just complicating things here.
You are dealing with UIKit which is class oriented, so you might as well just use good old inheritance

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

Swinject inject self's property into new UIViewController

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.

Swift - Dynamic cast class unconditional?

It doesn't seem like I can cast a generic type to another? Swift is throwing DynamicCastClassException.
Basically here is the problem:
// T is defined as T: NSObject
let oebj1 = NetworkResponse<User>()
let oebj2 = oebj1 as NetworkResponse<NSObject>
Here is why I need to do this casting
class BaseViewController: UIViewController {
// Not allowed to make a generic viewController and therefore have to cast the generic down to NSObject
func fetchData(completion: (NetworkResponse<NSObject>)->()) {
fatalError("You have to implement fetchData method")
}
}
class UsersViewController: BaseViewController {
override func fetchData(completion: (NetworkResponse<NSObject>)->()) {
userNetworkManager.fetchUsers { networkUSerResponse in
completion(networkUSerResponse as NetworkResponse<NSObject>)
}
}
}
class UserNetworkManager {
func fetchUsers(completion: (NetworkResponse<User>)->()) {
// Do stuff
}
}
In general, there doesn't seem to be a way to do this. The basic problem is that NetworkResponse<NSObject> and NetworkResponse<User> are essentially completely unrelated types that happen to have identical functionality and similar naming.
In this specific case, it really isn't necessary since you're throwing away the known Userness of the result anyway, meaning that if you really want to treat it as a User later you'll have to do a conditional cast back. Just remove the generic from NetworkResponse and it will all work as expected. The major drawback is that within UserVC.fetchData you won't have access to the returned User result without a (conditional) cast.
The alternative solution would be to separate out whatever additional information is in NetworkResponse from the payload type (User/NSObject) using a wrapper of some sort (assuming there's significant sideband data there). That way you could pass the NetworkResponse to super without mutilation and down-cast the payload object as needed.
Something like this:
class User : NSObject {
}
class Transaction {
let request:NSURLRequest?
let response:NSURLResponse?
let data:NSData?
}
class Response<T:NSObject> {
let transaction:Transaction
let payload:T
init(transaction:Transaction, payload:T) {
self.transaction = transaction
self.payload = payload
}
}
class UserNetworkManager {
func fetchUsers(completion: (Response<User>) -> ()) {
completion(Response(transaction:Transaction(), payload:User()))
}
}
let userNetworkManager = UserNetworkManager();
class BaseVC {
func fetchData(completion: (Response<NSObject>) -> ()) {
fatalError("Gotta implement fetchData")
}
}
class UserVC : BaseVC {
override func fetchData(completion: (Response<NSObject>) -> ()) {
userNetworkManager.fetchUsers { response -> () in
completion(Response(transaction: response.transaction, payload: response.payload))
}
}
}
Although at that point, you're probably better off just separating the transaction information and payload information into separate arguments to the callback.

Resources