How can I access arrays from other classes inside AppDelegate in Swift? - ios

I have an array inside a class called Anacii and obviously an AppDelegate class. I'm trying to save two arrays, both located inside the Anacii class, to UserDefaults when the application terminates. Everything works fine except getting the two arrays from the Anacii class from the AppDelegate class. Both arrays have multiple values inside them (I tested that with some print statements) and I can access them from my root view controller just fine with all the values inside of them but they return as empty arrays when I get them from the AppDelegate class.
Here are the two arrays defined in the Anacii class:
class Anacii {
// MARK: - Anacs / Rarities
var anacs = [String]()
var rarities = [Int]()
...
}
Here's where I set the actual values inside my root view controller:
class HomeController: UITableViewController, CLLocationManagerDelegate {
private let a = Anacii()
...
override func viewDidLoad() {
super.viewDidLoad()
for _ in 1...10 {
collectAnac()
}
...
}
...
// MARK: - Other functions
func collectAnac() {
let rarity = a.generateRarity()
let anac = a.findAnac(rarity: rarity)
a.anacs.append(anac)
a.rarities.append(rarity)
...
}
}
And finally, here is where I try to access the variables from the AppDelegate class:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
...
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
let a = Anacii()
let anacs = a.anacs // Comes out as []
let rarities = a.rarities // Comes out as []
...
}
}
The two values at the bottom of the AppDelegate class (anacs and rarities) both equal [] (tested by using print statements).
Sorry if this is a duplicate question, I looked at a lot of other posts like this but could find no answer that worked for me. Thanks!
TL;DR: You should read the whole thing but... Trying to access two arrays from the Anacii class from the AppDelegate class returns empty list, even though those two arrays are NOT empty (other classes see them with all the values they have). See the code above. Sorry if this is a duplicate post.

Putting it simply, you don't do this from the AppDelegate class. You do it from the class where the data actually is. In HomeController, register for the appropriate notification (e.g. the app is being backgrounded) from UIApplication and respond to it.
By the way, that notification should not be applicationWillTerminate, as it is never called.
(The actual reason for the phenomenon you're seeing is that Anacii() in AppDelegate is the wrong object. But it's best to do this the right way.)

Related

Why can't I call .send on a publisher in a class from a second class and have it update the subscription in third class?

I have this class in LRVDetails.swift
class LRVDetails {
var heading = CurrentValueSubject<String, Never>(K.LRV.Register)
// other publishers
}
I have this assign in LRVViewController.swift
private var details = LRVDetails()
private var subs = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
details
.heading
.assign(to: \.text!, on: heading)
.store(in: &subs)
}
and in LRVCoordinator.swift I have
private var details = LRVDetails()
func UserDidPressButton() { // this is a delegate method from the VC
details.heading.send("New Heading")
}
I thought that because LRVDetails is a class, it only stores a reference. so if I simply instantiate it in each file and then call send() on it, the publisher should update, and then emit the value to the LRVViewController subscription and update the heading.
But it does nothing. The subscriber never receives a value ( I checked with a .print() operator ). When i call lrvController?.details.send() -- without the private tag -- in the coordinator, it works fine.
Is this because the subscriber is stored in the 'subs' variable in LRVCoordinator, and thus it has to be updated with the subscriber in LRVViewController? That's my best bet.
If not, why doesn't this work?.
Thanks!
Edit:
If they are different instances, why does this code print two?
class myClass {
var int = 1
}
let test1 = myClass()
let test2 = myClass()
test1.int = 2
print(test2.int)
// prints 2
I thought that because LRVDetails is a class, it only stores a reference. so if I simply instantiate it in each file and then call send() on it, the publisher should update
Your understanding is incorrect. You have created two different LRVDetails objects. (The syntax LRVDetails() creates a new object) One in the VC, and the other in the coordinator. You are correct that it stores references though, so it would work if you actually make the two details variable refer to the same LRVDetails.
But actually, you don't seem to need a details variable in the coordinator. Since the coordinator is the delegate of the VC, you could just pass the LRVDetails via the delegate method:
func UserDidPressButton(_ viewController: LRVViewController) {
viewController.details.heading.send("New Heading")
}
Needless to say, you should also change the caller of UserDidPressButton to pass self as the parameter.

Global Alamofire request (AppDelegate)

My app needs to perform multiple URL requests via Alamofire but I would like to perform these tasks independently on views or what user does in UI. This basically means "on background" so the handlers which actually performing the tasks would not be deinitialized until they are done.
My idea would be to call these requests from shared AppDelegate via some kind of class methods. I have only one question now:
What would be the best way to implement this scenario?
My actual knowledge would create something like this:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
class func someKindOfClassRequest() {
// ...
}
func someKindOfRequest() {
// ...
}
// ...
}
And I would call the method this way:
AppDelegate.someKindOfClassRequest()
or with not-class func, which of course will not solve the issue:
let sharedDelegate = UIApplication.shared.delegate as! AppDelegate
AppDelegate.someKindOfRequest()
As mentioned in the comments, Alamofire uses closure-based completions so they will not get deinitialized even if the calling object is deinitialized. For the sake of keeping your code well organized, why not just create a class that does these things instead of throwing it into your AppDelegate? For instance, create a class called BankgroundRequestController:
class BackgroundRequestController {
static let sharedInstance = BackgroundRequestController()
class func someKindOfClassRequest() {
// ...
}
}
Then you can call these functions like:
BackgroundRequestController.sharedInstance.someKindOfClassRequest()

How to get references for ViewController

Community!
I have a question regarding References in Swift 3 and Xcode.
My Plan is this:
At the moment my App has 4 Controllers. A MapViewController, a PositionController, a MarkerController and a APIDataController. Those of course handle the respective Models. My problem now comes in, when I want the controllers to communicate. Look at this code (Please ignore that most functions aren't implemented here):
First my MarkerController:
import Foundation
protocol MarkerControllerDelegate: class{
func willGetMarkerArray()
func didGetMarkerArray(_ newMarkerArray: Marker)
}
class MarkerController: NSObject{
var markerArray = [Marker]()
weak var delegate: MarkerControllerDelegate?
func createMarkerArray(){
}
func getMarkerArray() -> [Marker]{
return markerArray
}
}
extension MarkerController : APIDataControllerDelegate{
func didRetriveAPIData(_ APIDataModelArray: APIDataModel) {
createMarkerArray()
}
}
And now my APIDataController:
import Foundation
protocol APIDataControllerDelegate: class{
func didRetriveAPIData(_ APIDataModelArray: [APIDataModel])
}
class APIDataController: NSObject {
var APIDataModelArray = [APIDataModel]()
weak var delegate: APIDataControllerDelegate?
func retriveAPIData(){
//get the data
//.....
//finished getting data
delegate?.didRetriveAPIData(APIDataModelArray)
}
func getAPIDataModelArray() -> [APIDataModel] {
return APIDataModelArray
}
}
And finally the beginning of my MapViewController:
import UIKit
import GoogleMaps
import GoogleMaps
import CoreLocation
class MapViewController: UIViewController{
let positionController = PositionController()
let apiDataController = APIDataController()
let markerController = MarkerController()
override func viewDidLoad() {
super.viewDidLoad()
apiDataController.retriveAPIData()
//blablabla
}
//blablablabla
}
Now, this might be a lot of code at once, so let me explain you what is ought to happen:
At the beginning (or wherever in the code) MapViewController calls the retrieveAPIData method. Once the function is finished, APIDataController is planned to notify the MarkerController via the APIDataControllerDelegate-Protocol to start his work (run his createMarkerArray-Method).
Now, to do this, I have to declare a markerController-Instance as a Delegate to APIDataController. Now, since I have already created instances of all my controllers in mapViewController, I want to give those references to my other controllers where needed so that they all refer to the same Instances, kinda like a Singleton.
But, how do I create a Reference to my MapViewController IN my MapViewController, to pass it to my other Controllers so that they can from there go like:
let positionController = mapViewController.positionController?
Nothing I tried seem to work. So any help would be appreciated.
Also if there are better ways to let my Controllers communicate I am open to here them, I am sure there is a better way to do it than I currently try to do.
Using Delegates for the communication between the controllers is the recommended way to do it. Setting the delegates depends on the way these controllers interact in your app. Often you will have a segue from one app to another, that is a good chance to set delegates. You can also do it in code that is putting together your views

Chain of delegates in Swift

TDLR; I have three classes, when Class A object is updated, it's calling on its delegate (Class B) which is calling on its delegate (Class C) without doing anything else. Class C will use Class B in different ways depending on the values in Class A. Class B need to know of its Class A at touch events. Is this acceptable?
classA { var information: String }
classB { var thing: ClassA thing.delegate = self }
classC { var things: [ClassB] for thing in things { thing.delegate = self } }
My real example
I have three classes: A mapViewController, a mapMarker and a place (model). The map holds multiple mapMarkers, and every mapMarker has a property place, which contains information of what the marker should look like (like place type, "bar", "restaurant" etc). The place might receive new information via a silent push notification, and hence being updated. When the place is updated, I need to notify the mapViewController that the marker needs to be redrawn (I'm using MapBox and their annotations doesn't support redrawing in any way but removing and adding the marker again, since the imageForAnnotation method is a delegate one.)
My first thought was to make two protocols placeDelegate and mapMarkerDelegate.
Place:
protocol PlaceDelegate: class
{
func placeUpdated()
}
class Place {
weak var delegate: PlaceDelegate?
var propertyThatCanBeUpdate: String {
didSet {
//Checking if the newValue == oldValue
delegate.placeUpdated()
}
}
MapMarker
protocol MapMarkerDelegate: class
{
markerShouldReDraw(mapMarker: MapMarker)
}
class MapMarker: PlaceDelegate {
var place: Place!
weak var delegate: MapMarkerDelegate?
init(place: Place) {
self.place = place
place.delegate = place
}
func placeUpdate()
{
delegate.markerShouldReDraw(self)
}
}
MapViewController
class MapViewController {
//I could easily set the marker.delegate = self when adding the markers
func markerShouldReDraw(mapMarker: MapMarker)
functionForRedrawingMarker()
}
This feels a bit ugly, and a bit weird that the MapMarker is just passing the "my place has been updated" information forward. Is this acceptable as far as performance goes? Should I use some kind of NSNotification instead? Should I make the MapViewController the delegate of place and search my array of mapMarker for the one holding the correct place?

Accessing application delegate variable delays view loading in swift

I am a newbie to Swift and i have started my new project with Swift. I am facing a delay issue while loading a viewcontroller.
On the application delegate i have a variable
var allTerms: [Dictionary<String, AnyObject>]?
This allTerms is populated with data from a local json file of 900Kb. The total json data count is 800.
So far i have a home screen and a second view. From the home screen when i navigate to second screen i need to access this allTerms from the application delegate. Referring to great tutorials,i was able to access the allTerms variable from the application delegate
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate!
self.tableData = (appDelegate.allTerms! as NSArray) as? Array
However doing so this is causing a noticeable delay in loading the secondview , which doesnot happen if i comment the line
self.tableData = (appDelegate.allTerms! as NSArray) as? Array
Appreciate any suggestions!
You might want to create a separate data manager class instead of storing it in the app delegate. You could use something like this:
class DataManager {
var allTerms: [[String:AnyObject]]?
class var sharedInstance: DataManager {
struct Singleton {
static let instance = DataManager()
}
return Singleton.instance
}
// You can access allTerms by calling DataManager.sharedInstance.allTerms
}
This probably won't solve your lag, but it's a good practice to make a DataManager class to store things. I also rewrote your allTerms declaration to use the short form for the dictionary.

Resources