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.
Related
Is there any way of passing data from 1st view controller to (say) 3rd view controller without passing the data through the 2nd view controller?
I actually have a final submit button on the 4th view controller which gathers all the data right from the 1st view controller.
I want the data of each view controller to be directly transferred to the 4th view controller where the submit button is, without going through the view controllers to reach there.
I have already tried passing data through view controllers think there can be a more clear way of directly transferring data specially images as these are the main part of my data.
You could use a "Model" for this purpose with a delegate pattern.
A model is a class (or struct) which can be accessible by several VCs.
The delegate is going to be used to "notify" that a property value has changed.
/// Should be implemented by your VC
protocol MyModelDelegate: AnyObject {
func dataToShareChanged(to data: dataToShare)
}
/// Use the same instance for the VC1 and VC4
final class MyModel {
weak var delegate: MyModelDelegate?
var dataToShare: Foo {
didSet { delegate?.dataToShareChanged(to: dataToShare) }
}
}
In your case by the 1th and the 4th. Each of those VC should have the same instance of the model. You can achive this by giving the model object to the VCs if you initialize them.
If you are working with storyboards, you have to assging the models in the "viewDidLoad" for instance.
So you VC would look like:
class MyController: UIViewController, MyModelDelegate {
var model: MyModel?
func viewDidLoad() {
...
model.delegate = self
}
// Implementation of the delegate function.
func dataToShareChanged(to data: dataToShare) {
/// assign the new data value here
}
}
If you use this approach, you would not need to pass data though the VCs at all. Simple assign the new value in the model and the other VC is going to receive those data changes through the model delegate function.
Passing data forward from one view controller to the next isn't necessarily a bad thing. However when dealing with large amounts of data especially images you can easily run into memory pressure via this method.
Delegate way looks promising if all you needed was to inform the current viewcontroller neighbour (forward or backward) about data change.
Let me suggest an alternative set of solutions.
First off, don't manage image objects in memory. If you don't need it for anything else, write it to your apps temporary directory, keep hold of the URL and let go of the UIImage object. The snippet below lets you save your UIImage object to NSTemporaryDirectory with a name and return a URL object.
func saveImageToTempDirectory(image: UIImage, withName: String) -> URL? {
let url = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
.appendingPathComponent(withName, isDirectory: false)
.appendingPathExtension("jpg")
// Then write to disk
if let data = image.jpegData(compressionQuality: 0.8) {
do {
try data.write(to: url)
return url
} catch {
//handle error
return nil
}
}
return nil
}
You can choose to pass the URL from this method from one view controller to the other. Have this method in a Util class for better organization.
Method 1
Write the image urls from specific viewcontrollers into some local storage. You could use UserDefaults for this as its the easiest. You could also create separate folders for each viewcontroller while saving temp directory.
Method 2
Singletons. While singletons are frowned upon since they always hold state and becomes hard to test and/or debug, you could make use of a Singleton class that holds all your local URLs as part of arrays.
final class ImagePathManager {
static let shared = ImagePathManager()
var firstViewControllerImages: [URL] = []
//Initializer access level change now
private init(){}
}
You can append urls from first viewcontroller to ImagePathManager.shared.firstViewControllerImages and access them the same way from anywhere else in your application.
That being said, Singleton pattern usage is a slippery slope and you should always be very careful while using it in your apps.
I have a TableView inside my app and i populate it from a JSON, which is downloaded inside a DataService class. This DataService class simply does the download & save data to array operation.
Inside my ViewController class, i also have an array for this data that is initialized empty at the beginning, filled with the data from DataService afterwards. Since i need to fill the data array (the one inside ViewController class) once the download operation is completed, DataService class needs to reach this array. So i decided to pass the ViewController object itself to DataService class so that it can fill VC's array with downloaded data and also reload its tableView data.
So my DataService class looks like this:
var dataArray: [EtkinlikCellData] = []
func fetchData(senderVC: EtkinliklerVC) {
// Download & fill dataArray operations
senderVC.dataArray = self.dataArray // setting sender VC's array
senderVC.tableView.reloadData() // reloading sender VC's table data
}
And how's it used inside ViewController class:
var dataArray = [EtkinlikCellData]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
DataService.instance.fetchData(senderVC: self)
}
I know that if i did the fetch operation directly inside the ViewController class, there would be no problems since fetchData() method would reach the dataArray and tableView object itself. But since it is in another class, would it be wrong or harmful to pass a ViewController object as a parameter? Thanks.
The only threat is retain cycles and you can avoid it with weakly reference the object like
Option 1: // problem model shouldn't have any UI content
weak var delgate:EtkinliklerVC?
fetchData(senderVC: EtkinliklerVC) {
self.delegate = senderVC
....
self.delegate?.dataArray = self.dataArray
self.delegate?.tableView.reloadData()
Option 2 :
Establish a completion ( Recommended )
func fetchData(completion:#escaping:([String] -> ())) {
completion(arr)
}
then use it
DataService.instance.fetchData() { [weak self] arr in
// set it and reload
}
Plus:
Say you left your current code as it's , since the singleton alive which means it's strongly retained and you pass the vc object to it , user opens the vc and before data returns he pressed back button in your vc , since there is no weak reference between the singleton and the vc , then the vc will still be in memory while it should be de allocated
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.)
I have an array stored in a class that downloads its objects from the internet. My class is set up like so:
class StockManager {
var managerStock: [Dictionary<String, String>] {
return downloadStockFromDatabase()
}
...
}
I access the managerStock from other Swift files in my project like so, but it always resorts to re-downloading the stock again no matter if I have used the variable before (ie. recalls the function downloadStockFromDatabase):
let stockManager = StockManager()
print(stockManager.managerStock)
How would I make sure the managerStock only downloads once and I could use it in any of my files?
This is a question of correct software pattern usage. I would suggest:
make StockManager a singleton, so you will always access the same instance of it
initialize it e.g. in the AppDelegate, i.e. make sure it stays alive for the whole runtime
tip: call managerStock lazily, i.e. only when you really need it and not as part of initialization
As ff10 and holex suggested, make your class a singleton. It will look like this:
class StockManager {
static let sharedInstance = StockManager ()
var managerStock: [Dictionary<String, String>] {
return downloadStockFromDatabase()
}
...
}
Then access it using the static sharedInstance property:
print(StockManager.sharedInstance.managerStock)
For any object created I generally use two two scopes 1) Singleton 2) {local scope}. I am looking for something in between.
Say I have one object that 5 view controllers are editing. I want to share an object between view controllers without having to pass it between view controllers. But it should not also live throughout application since once I am done editing the object i don't need it anymore.
I don't want to inherit all view controller from another class an create a variable there. Since view controller are reusable for different objects. I want to create an object that comes to life before launch of first view controller, lives throughout the scope of 5 view controllers and then dies after I have saved it someway. Is there anyways I could do this in iOS.
An alternative is to use your AppDelegate. Within it you can declare a global var than 2 functions, a first one to get the current value and another one to set the value.
It might give something like this:
// Get AppDelegate instance
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate;
// Use your getter to get the current value
var something = appDelegate.getYourStuff();
// Or use a setter to set it, a modifier to modify oit
appDelegate.setYourStuff(yourStuff);
appDelegate.modifiyYourStuffAttribute(newAttributeValue);
Don't realize if such a method is a bad practice or not, but it works for me.
Open to other suggestions!
As Mat said you can do is in that what. For me better is to create specific class for that that will do one particular job.
class EditingSession {
class Factory {
private static let session = EditingSession() //do it lazy
static func create() -> EditingSession {
return session
}
}
func openSession() {
}
func endSession {
}
func getData () -> AnyObject {
...
}
}
In editing session create private initializer. Factory should give the shared instance.
You can pass this class to your ViewControllers and manipulate with the data. You can inject it or just set as property in your VC or VM.
If you are done with editing you should end session and clear data or other stuff.