One delegate Two classes - ios

I am using a UISearchController with GoogleMap Autocomplete View controller to send data to my view controllers.
I have a Tabbar controller and I want to send the data two my ViewController A and B.
I have done all the necessary jobs but only one ViewController gets notified when user has used the UISearchController.
I have tried setting the delegate of each tab to nil when I move to the other tab, For example if I move from ViewController A to B
I will set the delegate of A to nil and then set the delegate of B to itself.
I am kinda new to swift so Can anyone help me understand why isn't this working?
I have tried debugging my code to see is my delegate is nil and it wasn't .
Here is how i set and unset the delegate
func setDelegate() {
print("MapViewController is not nil")
print(resultsViewController?.delegate)
resultsViewController?.delegate = self
print(resultsViewController?.delegate)
}
func unSetDelegate() {
print("MapViewController is nil")
resultsViewController?.delegate = nil
}

You need an observer pattern, if you need that one class instance notify to a number of other instances you need make an array of delegates (called observers) and register and deregister from that notifier instance class
Further info Wikipedia Observer Pattern
example code
This is the protocol that must implement any observer class
protocol GeotificationsManagerObserver : NSObjectProtocol{
func nearestGeotificationsHasChanged(pgeotifications:[Geotification])
}
Notifier class
class GeotificationsManager: NSObject {
/**...*//code
fileprivate var observers : [GeotificationsManagerObserver] = []
/**...*//code
}
Observers methods
extension GeotificationsManager
{
func addGeotificationsManagerObserver(observer:GeotificationsManagerObserver)
{
for currentObs in self.observers {
if(observer.isEqual(currentObs))
{
//we don't want add again
return
}
}
self.observers.append(observer)
}
func removeGeotificationsManagerObserver(observer:GeotificationsManagerObserver)
{
var observerIndex = -1
for (index,currObserver) in self.observers.enumerated() {
if(observer.isEqual(currObserver))
{
observerIndex = index
break
}
}
if(observerIndex != -1)
{
self.observers.remove(at: observerIndex)
}
}
//here make the notification to all observers in observers array
func nearestsGeotificationsHasChanged()
{
for currObserver in self.observers {
currObserver.nearestGeotificationsHasChanged(pgeotifications: self.getNearesGeotifications())
}
}
}
Important
You must remove the observer once you don't need being notified if not you will have memory issue
Example: You can add a UIViewController as Observer in viewDidAppear and can be removed in viewDidDisappear

Related

What exactly happens when you assign self to delegate?

I'm new to Swift and I'm having a hard time understanding the purpose of assigning self to a delegate. Part of the difficulty stems from the fact that delegate seems to be used in two different ways.
First is as means to send messages from one class to another when a specific event happens, almost like state management. Second is to enable "a class or structure to hand off (or delegate) some of its responsibilities to an instance of another type," as stated in documentation. I have a feeling that these two are fundamentally the same and I'm just not getting it.
protocol PersonProtocol {
func getName() -> String
func getAge() -> Int
}
class Person {
var delegate: PersonProtocol?
func printName() {
if let del = delegate {
print(del.getName())
} else {
print("The delegate property is not set")
}
}
func printAge() {
if let del = delegate {
print(del.getAge())
} else {
print("The delegate property is not set")
}
}
}
class ViewController: UIViewController, PersonProtocol {
var person: Person!
override func viewDidLoad() {
person.delegate = self
person.printAge()
person.printName()
}
func getAge() -> Int {
print("view controller")
return 99
}
func getName() -> String {
return "Some name"
}
}
What is the purpose of person.delegate = self in this case? Isn't ViewController already required to conform to PersonProtocol without it?
I have a feeling that these two are fundamentally the same
The first is a special case of the second. "send messages from one class to another" is just a specific way of "handing off some of its responsibilities". The "messages" are the "responsibilities"
What is the purpose of person.delegate = self in this case?
Here, person delegates (i.e. hands off) some of its responsibilities to another object. It does this by sending messages to another object. First, it needs to identify which objects it can delegate these responsibilities to. This is achieved by requiring that its delegate conform to PersonProtocol, as PersonProtocol defines the messages that Person is going to send.
Next, person needs to know exactly which object it should send these messages to. This is what person.delegate = self does. Remember that person doesn't know anything about your ViewController until this point. Instead of = self, you could say:
person.delegate = SomeOtherClassThatConformsToPersonProtocol()
and person will send its messages to that object instead, and the methods in your ViewController won't be called.
Isn't ViewController already required to conform to PersonProtocol without it?
Correct, but without it, person doesn't know which object it should send its messages to, and as a result, the methods in your ViewController won't be called.
Note that the delegate property should be declared as weak to avoid retain cycles. When you do person.delegate = self, you get a retain cycle: self has a strong reference to person, person also has a strong reference to self via the delegate property.
If you notice inside your Person class, delegate is nil. If you don't execute person.delegate = self, delegate will remain nil.
In other words, assigning ViewController to person.delegate allows Person to identify who the delegate is (i.e., have a reference to ViewController), and that way you can successfully execute statements like delegate?.getName() or delegate?.getAge() from the Person class.
that means Person is not able to getName() and getAge() so Person class delegate that to other DataSource.
Lets say the your view controller has a data source class PersonDataSource which deal with API to get this information So
class PersonDataSource: PersonProtocol {
func getAge() -> Int {
print("view controller")
return 99
}
func getName() -> String {
return "Some name"
}
}
so the view controller will looks like this
class ViewController: UIViewController {
var person: Person!
var personDataSource = PersonDataSource()
override func viewDidLoad() {
person.delegate = personDataSource
person.printAge()
person.printName()
}
}

How to show a view through protocol?

I want to detective networking state, when networking state changed, show a error view in current controller. But there is a problem by using protocol.
Here is the codes:
private func networkingDetection() {
//This is the detective method in appdelegate
try! reachability.startNotifier()
reachability.whenReachable = { [weak self] _ in
DispatchQueue.main.async {
self?.currentViewController().hideNetworkingErrorView()
}
}
reachability.whenUnreachable = { [weak self] _ in
DispatchQueue.main.async {
self?.currentViewController().showNetworkingErrorView()
}
}
}
And here is the protocol
protocol NetworkingErrorProtocol {
// I want to show the default view if there is no networkingErrorView, and
when declare a custom view in controller, show the custom view.
//var networkingErrorView: UIView? { get }
func showNetworkingErrorView()
func hideNetworkingErrorView()
}
extension UIViewController: NetworkingErrorProtocol {
func showNetworkingErrorView() {
}
func hideNetworkingErrorView() {
}
}
Anyone can tell me how to figure it out? It's really makes me crazy. Thanks a lot.
The issue with your setup is that conforming UIViewController to your protocol does not allow you to receive that call in your subclass. If you try to override the protocol function in your subclass you will get a compiler error: Declarations from extensions cannot be overridden yet
First off, a note about NotificationCenter. If you need multiple parts of your app to be notified of the change that would be a good way to go. If you only need to tell one controller, this is a classic usage for a delegate.
Here are two ways to get the desired functionality: using the delegate pattern and without.
Let's say Manager is the class where the monitoring is happening:
Using a delegate pattern
class Manager {
weak var networkDelegate : NetworkStatusListener?
func monitorNetworkStatus() {
var reachable = true;
if reachable {
// We can call the delegate directly
networkDelegate?.networkStatusChanged(.connected)
}
else {
networkDelegate?.networkStatusChanged(.disconnected)
}
}
}
And the same Manager without a delegate pattern. This would be the simplest fix for your current implementation issue.
class Manager {
func currentViewController() -> UIViewController { return vc }
func monitorNetworkStatus() {
var maybeAtListener = currentViewController()
// DON't SHIP THIS, but it can be helpful during development to make sure you didn't forget to conform one of your classes
assert(maybeAtListener is NetworkStatusListener, "Oops, did you mean to conform \(currentVC) to NetworkStatusListener")
var reachable = true;
if reachable {
// We can't be sure the controller conforms to the protocol but we can try
(maybeAtListener as? NetworkStatusListener)?.networkStatusChanged(.connected)
}
else {
(maybeAtListener as? NetworkStatusListener)?.networkStatusChanged(.connected)
}
}
}
Then for your view controller
class MyController : UIViewController, NetworkStatusDelegate {
func networkStatusChanged(_ status: NetworkStatus) {
switch status {
case .connected:
// Normal UI
break
case .disconnected:
// No network connect
break;
}
}
}
Also, not directly related to your question but for this example I used a slightly different approach to the protocol design that can be helpful for "status" oriented protocols. Having multiple functions can often become a little more tedious to conform to as protocols get larger.
enum NetworkStatus {
case connected
case disconnected
}
protocol NetworkStatusListener : class {
func networkStatusChanged(_ status: NetworkStatus)
}
Try using reachability class's NSNotificationCenter
add this in appdelegate's didFinishLaunchingWithOptions if you want for whole app
OR add in your specific viewcontroller if you want this in specific Viewcontroller
NotificationCenter.default.addObserver(self, selector:Selector(("checkForReachability:")), name: NSNotification.Name.reachabilityChanged, object: nil);
let reachability: Reachability = Reachability.forInternetConnection();
reachability.startNotifier();
This method called while network state changed .
func checkForReachability(notification:NSNotification)
{
let networkReachability = notification.object as! Reachability;
_ = networkReachability.currentReachabilityStatus()
// do yor additional work here
}

Weak delegate becomes nil

In my app I'm using delegates, so that I can read the data when ever it's ready.
I'm calling a delegate from two classes. Here is my code
protocol MyDelegate: class {
func getData()
}
class MyDelegateCalss {
weak var delegate: MyDelegate?
func loadData() {
// doing some actions
if(self.delegate != nil){
self.delegate!.getData!()
}
}
}
In one class I'm loading this method in tableview numberOfSections delegate method.
class A: UIViewController, MyDelegate {
func somefunc(){
let mydelegatecls : MyDelegateCalss = MyDelegateCalss()
mydelegatecls.delegate = self
mydelegatecls.loadData()
}
func getData(){
// Doing some actions
}
}
This method I'm loading from another calss.
class B: UIViewController, MyDelegate {
open func testfunc(){
let mydelegatecls : MyDelegateCalss = MyDelegateCalss()
mydelegatecls.delegate = self
mydelegatecls.loadData()
}
func getData(){
// doing some other stuff
}
}
class C: UIViewController {
func testfunc(){
let b : B = B()
b.testfunc()
}
}
Here from class A my delegate is working fine. and I'm able to see getData method is calling .
from Class B, the delegate becomes nil and unable to see getData method is called
If I make the delegate reference its working fine. But that will cause memory leak.
How can handle this case ?
Your delegate var is declared as weak. If nothing keep a strong reference on the object you assign as the delegate (implementing MyDelegate), your delegate will pass to nil as soon as the object is released (eg. the end of the scope where you instantiate it).
Some good read: https://cocoacasts.com/how-to-break-a-strong-reference-cycle/

Why is selector being sent to previous view controller?

I have an app with a data model class that declares a protocol, and two view controllers embedded in a navigation controller. The data model class is a shared instance. Both view controllers are delegates of the data model. The second view controller has a UITableView.
On start, calls to data model functions from the first view controller work as expected. When I segue from the first view controller to the second, calls to data model functions work as expected as well.
However, when I navigate back to the first view controller and a data model function is called, the app crashes with this error:
2017-04-03 09:48:12.623027 CoreDataTest[3207:1368182]
-[CoreDataTest.PracticesViewController noDupeWithResult:]: unrecognized selector sent to instance 0x15fe136e0
That PracticesViewController is the second view controller. I don't understand why a selector is being sent to what I am thinking of as the previous view controller. My expectation is that the selector should be sent to the first view controller that has just been navigated back to,
I am self-taught, so I presume there is something basic that I am missing, but I don't know what I don't know. Can someone explain why the crash is happening?
Code for the data model
import Foundation
import CoreData
import UIKit
#objc protocol PracticesDataDelegate {
#objc optional func practicesLoadError(headline:String,message:String)
#objc optional func practicesLoaded(practices:[NSManagedObject])
#objc optional func practiceStored()
#objc optional func practiceDeleted()
#objc optional func noDupe(result:String)
}
class PracticesDataModel {
static let sharedInstance = PracticesDataModel()
private init () {}
var delegate: PracticesDataDelegate?
var practices: [NSManagedObject] = []
// some code omitted . . .
/// Check for a duplicate exercise
func checkForDupe(title:String,ngroup:String,bowing:String,key:String){
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Practice")
let predicate = NSPredicate(format: "exTitle == %# AND notegroup == %# AND bowing == %# AND key == %#", title, ngroup, bowing, key)
fetchRequest.predicate = predicate
do {
practices = try managedContext.fetch(fetchRequest)
if practices.count == 0 {
self.delegate?.noDupe!(result:"none") // exception happens here
} else {
self.delegate?.noDupe!(result:"dupe")
}
} catch let error as NSError {
// to come
}
}
The top of the first view controller
import UIKit
class galamianSelection: UIViewController, ExcerciseDataModelDelegate, PracticesDataDelegate {
let exerciseModel = ExerciseDataModel.sharedInstance
let pModel = PracticesDataModel.sharedInstance
// some code omitted . . .
//// THE VIEW ////
override func viewDidLoad() {
super.viewDidLoad()
exerciseModel.delegate = self
pModel.delegate = self
exerciseModel.loadExercises()
}
//// RESPOND TO PRACTICE DATA MODEL ////
func practiceStored() {
print("exercise stored")
}
func noDupe(result: String) {
if result == "none" {
let d = Date()
pModel.storePractice(date: d, exTitle: theExerciseTitle.text!, notegroup: theNoteGroup.text!, bowing: theBowings.text!, rythem: theRhythms.text!, key: theKey.text!, notes: "")
} else {
print("dupe")
}
}
The top of the second view controller
import UIKit
class PracticesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, PracticesDataDelegate {
#IBOutlet weak var tableView: UITableView!
let pModel = PracticesDataModel.sharedInstance
// some code omitted . . .
//// THE VIEW ////
override func viewDidLoad() {
super.viewDidLoad()
pModel.delegate = self
pModel.getPractices()
}
delegate is properly set to self in both view controllers.
I am happy to provide more code is needed, but I suspect someone who knows can diagnose from what I've provided.
There are a few issues here.
You are trying to reuse a delegate but only setting it in viewDidLoad. viewDidLoad is only called when, as the name implies, the view initially loads. That means that if you have VC1 in a navigation controller, then you push to VC2, then pop back to VC1, viewDidLoad will not be called a second time. The view has already loaded. If you want a piece of code to be called every time a view controller comes back into focus, you should put it into viewWillAppear: or viewDidAppear:
You are force unwrapping an optional protocol method which you haven't actually implemented (a bug which you're seeing as a result of the first issue, but it's still an issue on its own). Change the force unwrap to an optional self.delegate?.noDupe?(result:"none")
You also declared your delegate like this var delegate: PracticesDataDelegate?. This makes your class retain its delegate and is generally not the right behavior. In your case it actually causes a retain cycle (until you change the delegate). You should change this declaration to weak var delegate: PracticesDataDelegate?
What appears to be happening is:
In PracticesViewController you set the model's delegate to self, as in pModel.delegate = self.
You navigate back to your first view controller. This means that PracticesViewController gets deallocated. But the model's delegate has not been changed, so it's still pointing to the memory location where the view controller used to be.
Later (but soon), your model tries to call a method on its delegate, but it can't because it was deallocated. This crashes your app.
You could reassign the value of the delegate, for example in viewDidAppear. Or you could have your model check whether its delegate implements a method before attempting to call it. That's a standard practice for optional protocol methods-- since they don't have to be implemented, you check first before calling them.
In general, don't use ! in Swift unless you want your code to crash there if something goes wrong.

Access another view controller's property after an event

I have 2 ViewControllers, the first one has an array that is filled by a fetch request once the ViewController appears. The second ViewController has a reference to that array. However, that is referencing an empty array which before it's being filled.
I want to get a reference once the array is filled. Here is a snippet.
class VC1: UIViewController {
let coreDataObjects = [NSManagedObject]() //At this moment, this array is empty
func viewWillAppear {
fetchRequestThatFillsTheArray() //Here, the array is filled
}
fetchRequestThatFillsTheArray() {
//calling NSFetchRequest
}
}
class VC2: UIViewController {
let vc1 = VC1()
vc1.coreDataObjects //Empty array
}
Thanks.
Here is the solution:
class VC1: UIViewController {
lazy var coreDataObjects = fetchRequestThatFillsTheArray()
func fetchRequestThatFillsTheArray() {
//calling NSFetchRequest
}
}
If you still want a solution on your original code, or you have issue to use fetchRequestThatFillsTheArray() in lazy . The you have to use NSNotification. Read Notification Programming Topics
--- UPDate for your comment----
You should change you code a little.
class VC1: UIViewController {
lazy var coreDataObjects:[NSManagedObject] = self.fetchRequestThatFillsTheArray()
func fetchRequestThatFillsTheArray() -> [NSManagedObject] {
//calling NSFetchRequest
}
}
However, since you used self.property in your fetchRequestThatFillsTheArray(), you can't use this solution.
You have to use NSNotification. That means you send a notification when fetchRequestThatFillsTheArray() finished. And register the notification in vc2. When vc2 get the notification, it does what it wants.

Resources