Type 'MasterViewController' does not conform to protocol 'CropPerformanceControllerDelegate' - swift

On my app there are several views where you input certain values (settings) and so these settings get sent back to the master view using delegates. I already had this setup with 2 other views, and just copied and pasted the required code and changed the variables, however this brought up the error; "Type 'MasterViewController' does not conform to protocol 'CropPerformanceControllerDelegate'"The Code is below:
// MasterViewController
class MasterViewController: UIViewController, SettingsControllerDelegate, RainfallDataControllerDelegate, CropPerformanceControllerDelegate {
// Other variables/functions...
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "MasterToSettings" {
let vc = segue.destinationViewController as! Settings
vc.delegate = self
}
if segue.identifier == "MasterToRainfallData" {
let vc = segue.destinationViewController as! RainfallData
vc.delegate = self
}
if segue.identifier == "MasterToCropPerformance" {
let vc = segue.destinationViewController as! CropPerformance
vc.delegate = self
}
}
func cropPerformanceSettingsSaved(controller: CropPerformance, irrigationResponseFactor: Double, wdImpactFactor: Double, potentialYield: Double) {
controller.navigationController?.popViewControllerAnimated(true)
irrigationResponseFactorM = irrigationResponseFactor
wdImpactFactorM = wdImpactFactor
potentialYieldM = potentialYield
}
}
// New View
// CropPerformanceView
protocol CropPerformanceControllerDelegate {
func cropPerformanceSettingsSaved(controller: CropPerformance, irrigationResponseFactor: Double, wdImpactFactor: Double, potentialYield: Double)
}
class CropPerformance: UIViewController {
var delegate: CropPerformanceControllerDelegate? = nil
// Other functions and Variables
#IBAction func updateCropSettings(sender: AnyObject) {
// Other stuff
if (delegate != nil) {
delegate!.cropPerformanceSettingsSaved(self, irrigationResponseFactor: irrigationResponseFactor!, wdImpactFactor: wdImpactFactor!, potentialYield: potentialYield!)
}
}
}
So this exact same code is used for the settings and rainfallData views and there are no issues, however now on the master view the 'CropPerformanceControllerDelegate' does not seem to be recognised and any uses of the class "CropPerformance" cause the error; "Use of undeclared type 'CropPerformance'". I hope this is enough information, all code to do with the delegate is posted, all other unnecessary variable declarations and functions I left out.
I had looked for other answers and they all said that you need to implement all required methods if you want to conform to the protocols. What exactly does that mean? All of my functions are inside my class and the code works perfectly when I remove the parts regarding the delegate.
Thanks in advance.

I was not able to solve the problem as there was no mistake in the code, I simply started from scratch, made a new document, copy and pasted the same code, changed the name to CropPerformance2 and it worked. (It works with any other name except for CropPerformance). This is very strange and must have something to do with that the initial class "CropPerformace" that I had deleted as not completely wiped from the systems memory, at least that is my assumption. In conclusion there is no answer, I simply recreated everything and it worked.

Related

Access NSCache across view controllers in a tab view controller

I want to access NSCache from more than one place in my APP, as I'm using it to cache images from an API endpoint.
For example table view 4 and viewcontroller 6 in the diagram below use the same images, so I do not want to download them twice.
Candidate solutions:
Singleton
class Cache {
private static var sharedCache: NSCache<AnyObject, AnyObject>?
static public func getCache () -> NSCache<AnyObject, AnyObject> {
if sharedCache == nil {
self.sharedCache = NSCache()
}
return sharedCache!
}
}
Seems to work fine, but "Singletons are bad" so...
Store the cache in TabViewController
This will tightly couple the views to the view controller so...
Store in the AppDelegate somehow. But isn't this the same as 1? So...
Use dependency injection. But we're in a tab view controller, so isn't this the same as 2?
I'm not sure the right strategy here, so am asking whether there is another method that can be used here.
What I've done Created an App with an example using a NSCache, and explored a singleton solution. Ive tried to use dependency injection but think that it doesn't make sense. I've looked at Stack overflow and documentation, but for this specific circumstance I have found no potential solutoins.
What I've given A minimal example, with a diagram and tested solution that I'm dissatisfied with.
What is not helpful are answers that say NSCache is incorrect, or to use libraries. I'm trying to use NSCache for my own learning, this is not homework and I want to solve this specific instance of this problem in this App structure.
What the question is How to avoid using a singleton in this instance, view controllers in a tab view controller.
First up. Singletons are not inherantly bad. They can make your code hard to test and they do act as dependancy magnets.
Singletons are good for classes that are tools e.g NSFileManager aka FileManger, i.e something that does not carry state or data around.
A good alternative is dependancy injection but with view controllers and storyboards it can be hard and feel very boilerplate. You end up passing everything down the line in prepareForSegue.
One possible method is to declare a protocol that describes a cache like interface.
protocol CacheProtocol: class {
func doCacheThing()
}
class Cache: CacheProtocol {
func doCacheThing() {
//
}
}
Then declare a protocol that all things that wish to use this cache can use.
protocol CacheConsumer: class {
var cache: CacheProtocol? { get set }
func injectCache(to object: AnyObject)
}
extension CacheConsumer {
func injectCache(to object: AnyObject) {
if let consumer = object as? CacheConsumer {
consumer.cache = cache
}
}
}
Finally create a concrete instance of this cache at the top level.
/// Top most controller
class RootLevelViewController: UIViewController, CacheConsumer {
var cache: CacheProtocol? = Cache()
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
injectCache(to: segue.destination)
}
}
You could pass the cache down the line in prepareForSegue.
Or you can use subtle sub-classing to create conformance.
class MyTabBarController: UITabBarController, CacheConsumer {
var cache: CacheProtocol?
}
Or you can use delegate methods to get the cache object broadcast downhill.
extension RootLevelViewController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
injectCache(to: viewController)
}
}
You now have a system where any CacheConsumer can use the cache and pass it downhill to any other object.
If you use the coordinator pattern you can save the cache in the coordinator for your navigation flow and access it from there/init with the cache. It also works nicely since when the navigation flow is removed the cache is also removed.
final class SomeCoordinator: NSObject, Coordinator {
var rootViewController: UINavigationController
var myCache = NSCache<AnyObject, AnyObject>()
override init() {
self.rootViewController = UINavigationController()
super.init()
}
func start() {
let vc = VC1(cache: myCache)
vc.coordinator = self
rootViewController.setViewControllers([vc], animated: false)
parentCoordinator?.rootViewController.present(rootViewController, animated: true)
}
func goToVC2() {
let vc = VC2(cache: myCache)
vc.coordinator = self
rootViewController.pushViewController(vc, animated: true)
}
func goToVC3() {
let vc = VC3(cache: myCache)
vc.coordinator = self
rootViewController.present(vc, animated: true)
}
func goToVC4() {
let vc = VC4(cache: myCache)
vc.coordinator = self
rootViewController.present(vc, animated: true)
}
deinit {
print("✅ Deinit SomeCoordinator")
}
}

Reusing view controllers in Swift without hacked property

I've been looking through a Coordinator tutorial and it brought up a problem with code I've written in the past.
Namely, when reusing a view controller I've used a property to be able to display different elements depending on which view controller the user arrived from. This is described in the above tutorial as a hack.
For example I segue to labelviewcontroller using
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "label" {
let vc = segue.destination as! LabelViewController
vc.originalVC = self
}
}
and then on labelViewController have a property
var originalVC: ViewController?
which I then change the items in viewDidLoad() through
override func viewDidLoad() {
super.viewDidLoad()
if originalVC != nil {
label.text = "came direct"
imageView.isHidden = true
}
else {
label.text = "button"
imageView.isHidden = false
}
}
I've a working example project here: https://github.com/stevencurtis/ReusibilityIssues
Now, I know the answer might be use the Coordinator tutorial, but is there any other method that I can use to simple reuse a viewController for two different circumstances, rather than using a property or is there anyway to clean this up to be acceptable practice?
You can do that without passing originalVC just by checking parent type if you are pushing it inside a navigation controller like this :
if let p = parent {
if p.isKind(of: OriginalViewController.self){
//it pushed in navigation controller stack after OriginalViewController
}
}
but is there any other method that I can use to simple reuse a viewController for two different circumstances
If the "two different circumstances" you describe are very different (by this I mean "require very different lines of code to be run"), then you should create two different view controller classes, because otherwise you would be violating the Single Responsibility Principle.
If your "two different circumstances" are different, but also quite related, then you can just have all the information that the VC needs to know as properties. You certainly don't need a whole ViewController.
For example, if your LabelViewController will show a "foo" button only if it is presented by ViewControllerFoo.
You can add a showFooButton property in LabelViewController:
var showFooButton = false
override func viewDidLoad() {
fooButton.isHidden = !showFooButton
}
And then in ViewControllerFoo.prepareForSegue:
if segue.identifier == "label" {
let vc = segue.destination as! LabelViewController
vc.showFooButton = true
}
I wouldn't call this a hack. This is the recommenced way described in this post and they didn't call it a hack.

Swift weak var scenario

I was reading the CoreData docs here and came across the following example illustrating how to implement a segue from a parent list to a child using dependency injection and was a little confused by the code sample given.
class DetailViewController: UIViewController {
weak var employee: AAAEmployeeMO?
}
and in the MasterViewController
let CellDetailIdentifier = "CellDetailIdentifier"
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
switch segue.identifier! {
case CellDetailIdentifier:
let destination = segue.destinationViewController as! DetailViewController
let indexPath = tableView.indexPathForSelectedRow!
let selectedObject = fetchedResultsController.objectAtIndexPath(indexPath) as! AAAEmployeeMO
destination.employee = selectedObject
default:
print("Unknown segue: \(segue.identifier)")
}
}
I've got a decent understanding of weak variables but am a bit confused with this particular case because I don't see how it is necessary in this scenario.
Can anyone enlighten me as to why it is used here? Where is the potential for a strong reference cycle?

class ViewController has no intitalizers

Here is the line of code causing the error
class ViewController: UIViewController, UITextFieldDelegate {
I'm following a tutorial so I can't imagine why this went wrong.
It was working just fine until I added this function into my code:
//Check if user is already logged in when opening app if so display dashboard page
override func viewDidAppear(animated: Bool){
if PFUser.currentUser() != nil{
if self.theUser.user_db_obj.email == "mike#chicagodrumlessons.com"{
self.performSegueWithIdentifier("adminLogin", sender: self) //dislay admin page
}
else{
self.performSegueWithIdentifier("userLogin", sender: self) //display user page
}
}
}
Here is the only lines of code where the delegate is referenced I believe
override func viewDidLoad() {
super.viewDidLoad()
self.email_tf.delegate = self
self.pass_tf.delegate = self
}
Any idea what caused this error and how to fix it?
My guess is you declare non-optional properties and they aren't initialized when the object is. The compiler is telling you you need an init or two to provide the non-optional properties values before the object can be initialized (or make the properties optionals).

Swift; delegate embedded view controller and parent

Sorry in advance that I can’t explain myself very well. I’m really new to programming and the topic of delegation still eludes me. I had some great help with this once before, but now I am trying to use a delegate in a different situation and I can’t get it right. I pieced together a bit of code that doesn’t work, and no matter how much I search I can’t find a way to fix it.
I have a view controller (MainController) with and embedded view controller (EmbeddedController) in a container view. I am trying to have a button in the embedded controller manipulate the container view (containerView).
EmbeddedController:
protocol ControllerDelegate {
func hideContainerView()
}
class EmbeddedController: UIViewController {
var delegate: VControllerDelegate?
#IBAction func button(sender: AnyObject) {
delegate?.hideContainerView()
}
}
MainController:
class MainController: UIViewController, ControllerDelegate {
#IBOutlet var containerView: UIView!
func hideContainerView() {
containerView.hidden = true
}
override func viewDidLoad() {
super.viewDidLoad()
var vc = EmbeddedController()
vc.delegate = self
}
}
Does anyone have any idea what I am doing wrong? And why this isn’t working?
What I ended up doing is adding this to the MainController:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "mySegue") {
let vc = segue.destinationViewController as! EmbeddedController
vc.delegate = self
}
}
In storyboard I selected the segue from the MainController to the EmbeddedController, and set the identifier to "mySegue".
Without the code above the delegate kept returning nil. I didn't look into this solution at first as I thought segues were only for transitioning between view controllers, and in my mind I didn't see the embedded controller as a transition. Maybe someone more knowledgable than me (which is practically anyone on here at this point) can explain how this is all fitting together.
In any case, this is how I solved my issue and hopefully someone else can benefit from this as well :)
First of all, to avoid strong reference cycles:
protocol ControllerDelegate: class {
func hideContainerView()
}
class EmbeddedController: UIViewController {
weak var delegate: ControllerDelegate?
And you haven't added your newly instantiated VC view to container view, and not added it as a child VC:
let vc = EmbeddedController()
vc.delegate = self
containerView.addSubview(vc.view)
self.addChildViewController(vc)
vc.didMoveToParentViewController(self)