viewDidLoad loads every time - ios

I'm doing a simple project, using firebase.
In my viewDidLoad fiction I'm creating an object of the class. I want to to be created only once during my work cycle. But, it creating a new objects every time i'm visiting this ViewController.
override func viewDidLoad() {
super.viewDidLoad()
let urlArray = ["http://i.imgur.com/VAWlQ0S.gif", "http://i.imgur.com/JDzGqvE.gif", "http://67.media.tumblr.com/4cd2a04b60bb867bb4746d682aa60020/tumblr_mjs2dvWX6x1rvn6njo1_400.gif", "https://media.giphy.com/media/TlK63ELk5OPDzpb6Tao/giphy.gif", "http://i3.photobucket.com/albums/y90/spicestas/GeriHalliwell-Calling-new1.gif", "http://media.tumblr.com/tumblr_lnb9aozmM71qbxrlp.gif"]
var counter = 1
for url in urlArray {
let nsUrl = NSURL(string: url)
let girls = ProfileClass()
girls.profilePhotoUrl = url
girls.profileGender = "female"
girls.profileName = "girlsname\(counter)"
girls.profileSurname = "girlsurname\(counter)"
girls.interest = "men"
girls.uid = "\(randomStringWithLength(45))"
counter++
girls.SaveUser()
}
Maybe I should write my code in another function? Or the problem maybe be caused by my for loop?

You should create another class that handles your data. Call it your data manager.
Make this a singleton, or make it a member of your app delegate, or whatever.
When you load the view controller in question, get the data from this object.
This is a nice little article on singletons in Swift
https://thatthinginswift.com/singletons/

If you want your code to be executed only when the app is launched then move it inside the
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
of your AppDelegate.
This way it will be executed only when the user does launch your app.
As rigdonmr said in the comments below, there are several techniques you can use when you want run a block of code only once. My answer instead specifically tells you how to run a block of code once during when the app is launched.

Related

SwiftUI 2 accessing AppDelegate

I did a small prototype which uses Firebase Cloud Messaging and the new SwiftUI 2 app life cycle. I added a custom AppDelegate via
#UIApplicationDelegateAdaptor(AppDelegate.self) var delegate and disabled method swizzeling for FCM to work. Everything is working as expected.
Today a colleague asked me, if one can get that delegate object via UIApplication.shared.delegate. So I gave it a shot and noticed that there seems to be two different AppDelegate objects:
po delegate prints:
<MyProject.AppDelegate: 0x6000009183c0>
where as po UIApplication.shared.delegate prints:
▿ Optional<UIApplicationDelegate>
▿ some : <SwiftUI.AppDelegate: 0x600000b58ca0>
Now I'm wondering what is the correct way of accessing the AppDelegate? Should one get it via an #EnvironmentalObject and pass it along all views? Or use the old fashioned way via UIApplication?
Additionally I would like to understand why I end up with two AppDelegates.
Your MyProject.AppDelegate is not direct UIApplicationDelegate, it is transferred via adapter to internal private SwiftUI.AppDelegate, which is real UIApplicationDelegate and which propagates some delegate callback to your instance.
So the solution might be:
Use #EnvironmentalObject if you need access to your MyProject.AppDelegate only in SwiftUI view hierarchy (for this AppDelegate must be confirmed to ObservableObject).
Add and use MyProject.AppDelegate static property which is initialized with object created via adapter, like
class AppDelegate: NSObject, UIApplicationDelegate {
static private(set) var instance: AppDelegate! = nil
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
AppDelegate.instance = self // << here !!
return true
}
}
now everywhere in your code you can access your delegate via AppDelegate.instance.

How to update Core Data when data is entered from multiple view controllers

This is how my view controllers are setup: I have a Main view controller which contains a horizontal scrollview and acts as a container. This container has another three view controllers, lets called them A, B and C in which I add different data:
In A I add a start and end time and click Next
In B I add a message string and click Next
In C I add some more data and click Done
In C, when Done is pressed I want to save the data from all three view controllers to Core Data.
The way I am doing now is in C I just call a save() method from Main using delegates.
Inside Main I just call the IBOutlets from A, B and C when saving to Core Data.
I am not sure if this is the right solution.
Is there any other elegant solution for this ? I am looking for something that I could easy unit test.
In your case I would use a Singleton, although some may disapprove I think in your instance it may be helpful:
final class DataCoordinator: NSObject {
private override init() {
print("Data Coordinator Initialized")
}
//Shared Instance:
static let appData = DataCoordinator()
var someVar: String = "" //public variable
fileprivate var mysteryNumber: Int = 0
...
and access properties like this:
DataCoordinator.appData.someVar
make sure you can access the singleton in every VC by calling it from the app delegate...
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
...
print(DataCoordinator.appData)
...
return true
}
you can also have it handle CoreData as well, that is to say you can make the context a shared property within the data coordinator and then access it from any view controller as well as save it with the typical functions:
public func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
print("Data Saved to Context")
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
//use like this
DataCoordinator.appData.saveContext()
you may also find it handy to write functions or use didSet to clear variables. Likewise, you can also have the DataCoordinator reload data from CoreData. I am not a big fan of this approach myself but use it when it is appropriate, I strongly suggest you read up on Singletons before implementing one or you may run into some issues down the road.

Routing Viper architecture

How to create good routing in project on Viper architecture? I start create file for routing but i don't understand what i must do next.
I create a file wireframe first controller and wireframe protocol:
// ChooseLanguageWireframeProtocol.swift
import UIKit
#objc protocol ChooseLanguageWireframeProtocol {
func presentChooseLanguageViewControllerWindow()
func presentAuthScreenViewController()
}
in file wireframe i add:
// ChooseLanguageWireframe.swift
import UIKit
class ChooseLanguageWireframe: NSObject , ChooseLanguageWireframeProtocol{
var chooseLanguageScreenViewController: ChooseLanguageViewController?
var window: UIWindow?
func presentChooseLanguageViewControllerWindow() {
let chooseLanguageViewController = UIStoryboard.init(name: "ChooseLanguage", bundle: nil).instantiateViewController(withIdentifier: "ChooseLanguage") as? ChooseLanguageViewController
self.chooseLanguageScreenViewController = chooseLanguageViewController
self.window!.rootViewController = chooseLanguageScreenViewController
self.window!.makeKeyAndVisible()
}
func presentAuthScreenViewController() {
}
}
After i create RootWireframe
// RootWireframe.swift
import UIKit
class RootWireframe: NSObject {
let chooseLanguageScreenWireframe : ChooseLanguageWireframe?
override init() {
//What i must init??
}
func application(didFinishLaunchingWithOptions launchOptions: [AnyHashable: Any]?, window: UIWindow) -> Bool {
self.chooseLanguageScreenWireframe?.window = window
return true
}
}
In file AppDelegate i change only
var window: UIWindow?
let rootWireframe = RootWireframe()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.rootWireframe.application(didFinishLaunchingWithOptions: launchOptions as [NSObject : AnyObject]?, window: self.window!)
}
What i must add or change for correctly work?
One way to do it is to inherit from UINavigationController and do the routing logic in there. You may end up with multiple classes doing routing in different parts of your application depending on your task.
But like with any cromulent buzzword you firstly have to ask yourself
if the "solutuon" solves more issues than it creates.
It starts to make sense when you get two dozen screens and start to get lost in
the very same issue that got resolved ages ago by adding storyboards to
plain old xibs.
So you really have to step back and ask yourself if you really want to follow
this cromulent architecture creating lots and lots of classes in the process,
a maze that will be hardly more readable than a standard MVC project.
If you can not use storyboards and want to use VIPER do so,
if you can use storyboards do VIPE :-)
But in most projects the datamodel is so pathetically simple and the
presentation is so tightly coupled that you have zero need in
UIViewController defattening.
And I suspect that the cure applied by the most viper affictionados is
worse than the decease itself.
2 possibilities:
Change the init of your rootViewframe to accept your dependency and initialize it.
And btw your variable type should not be the real type but the protocol, so you can mock it easily in tests
let chooseLanguageScreenWireframe : ChooseLanguageWireframeProtocol?
override init(chooseLanguage: ChooseLanguageWireframeProtocol) {
self.chooseLanguageScreenWireframe = chooseLanguage
}
And then create your chooseLanguageWireframe implementation in the appdelegate and pass it in the constructor.
This makes the dependency clear and visible. You can also remove the optionality, since you always initialize it.
Or solution 2
Create the chooseLanguageWireframe in the appdelegate and inject it there outside constructor
lazy var rootWireframe = {
let r = RootWireframe()
r.chooseLanguageScreenWireframe = self.chooseLanguageScreenWireframe
return r
}
In any case, you need to instantiate your dependencies somewhere, they cannot get created automatically.
You usually do that with factories and with the help of a dependency injection framework (check SwiftInject or Typhoon)
Also, declare all your dependencies with protocols type, it is the purpose of the VIPER architecture to simplify testing and mocking with isolations between actors

performSelector error with global function and AppDelegate class

I'm following this apple document and I'm trying to translate some of its parts in Swift language. I have this global function, with performSelector:
func RunLoopSourceScheduleRoutine(info:UnsafeMutableRawPointer? ,rl:CFRunLoop? , mode:CFRunLoopMode?) {
let obj : RunLoopSource = Unmanaged<RunLoopSource>.fromOpaque(info!).takeUnretainedValue()
let del = UIApplication.shared
let theContext = RunLoopContext(withSource: obj, andLoop: rl!)
del.performSelector(onMainThread:#selector(AppDelegate.registerSource) , with: theContext, waitUntilDone: false)
}
And AppDelegate class, in this class there are: methods that automatically adds Xcode in the normal routine of project creation (didFinishLaunchingWithOptions, applicationWillResignActive, etc) I added the sourcesToPing parameter and the registerSource() method:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var sourcesToPing : [RunLoopContext] = Array()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func registerSource(sourceInfo:RunLoopContext) {
sourcesToPing.append(sourceInfo)
}
}
but the compiler get the following error , in RunLoopSourceScheduleRoutine() function:
argument '#selector' refers to instance method 'registerSources(source Info:)' that is not exposed to Objective-C
what is the problem ? and how does it solve?
PerformSelector is an Objective-C method that predates GCD (Grand Central Dispatch). It should be possible to do it that way, but selectors are not type-safe and are awkward to use.
I'm not sure what's wrong with your current code. As Martin points out in his comment, the error you're reporting is complaining about a method called registerSources() but you show code for a method called registerSource() (with no final "e".) If you want to get that code working you need to get to the bottom of that discrepency.
Instead, why not use GCD code like this:
dispatchQueue.main.async() {
registerSource(theContext)
}
That will accomplish the same goal but using the more modern GCD

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