Passing data to a ViewController that is triggered by an external event - swift

I've set up a URL scheme for my application that will open it from a web browser. When received by the application, it presents AViewController.
I also have a ViewController, BViewController that contains some data. Ideally I would like to be able to pass this data from BViewController, to AViewController if BViewController is open at the time that the URL scheme is activated.
Since the URL scheme triggers a function in AppDelegate, I have no opportunity to pass the data from B to A.
What would be the best way to pass the data along?
The only solutions I can think of so far are:
Setting and getting a global variable for each piece of data
When the URL scheme call is received in AppDelegate, extracting the data from B by accessing the object properties, and setting them in A
Neither of these solutions I'm fully happy with, certainly not the first.
Is there be a better way of solving this?
Thanks in advance.

If you have…
class Object {
var someData…
}
class BViewController {
var object: Object!
func updateObject() {
object.someData = …
}
}
class AViewController {
var object: Object!
}
Then in AppDelegate, something like this (not real code!)…
func handleURL() {
if let b = rootViewController as? BViewController {
let a = AViewController()
a.object = b.object
rootViewController = a
}
}

Related

Calling an asynchronous closure from a delegate's source ViewController

I have the following setup :
Launch VC performs an asynchronous API request in a closure dataGatheringClosure
The dataGatheringClosure closure's completion handler passes the result to VC1 via delegation using protocol method setData.
Within setData, VC1 passes the result to VC2 using the following code:
if let vc2 = self.tabBarController?.viewControllers?[1] as? VC2Controller {
vc2.data = result
}
I'm able to transfer data from the Launch VC to VC2, but now I need to refresh the data by performing the dataGatheringClosure in Launch VC.
My question is: How do I access Launch VC's methods from VC2? And is this approach sound?
As a workaround, I copied the dataGatheringClosure closure code inside TabBar VC and had VC 2 call its self.tabBarController.dataGatheringClosure method. However, it is duplicative, and also I don't know if it's best practice to execute heavy API requests within a TabBarController.
The first step is to separate the data from the ViewControllers. Create a singleton class like below and use it to pass around the data you need.
class Data {
static let shared = Data()
// Declare any other data properties you need here...
var result = [String]()
private init() {}
func initialize() {
// Write code to initialize the data
refresh()
}
func refresh() {
// Write code to refresh the data
}
}
Initialize data at app startup or wherever required using the below code:
Data.shared.initialize()
You can now access data from anywhere within your app using the static variable Data.shared. To refresh data you can do the following:
Data.shared.refresh()
Hope this helps.

(re)-Pass data after click on backbutton

I'm trying to pass data from a SecondViewController to my FirstViewController when I click on my back button (UINaviagtionController).
For pass my data from FirstViewController to the SecondViewController I do this:
if segue.identifier == "showSecondVC" {
let vc = segue.destination as! SecondViewController
vc.rows = rows[pathForAVC]
vc.lap = lapTime[pathForAVC]
vc.indexPath = pathForAVC
}
But I have no idea how to pass data from SecondViewController to the FirstViewController and I really don't understand topics about it on Stack Overflow.
I want to transfer it when I click here:
Thanks.
You can use delegate pattern for that. You can grab the back button press event like this and update the data
override func viewWillDisappear(_ animated: Bool) {
if self.isMovingFromParentViewController {
self.delegate.updateData( data)
}
}
For more information on delegates you can go through this.
Actually things depend on your requirement, if you want data to be updated in first view controller as soon as it is updated in second view controller, you would need to call delegate as soon as the data is updated. But as in the question you have mentioned that you want it to be updated on back button only, above is the place to do it.
Another way would be to have Datasource as singleton so that it is available to all the view controllers and the changes are reflected in all view controllers. But create singleton if absolutely necessary, because these nasty guys hang around for entire time your application is running.
You should have a custom protocol such as:
public protocol SendDataDelegate: class {
func sendData(_ dataArray:[String])
}
Here I suppose you want to send a single array back to FirstViewController
Then make your first view controller to conform to the custom protocol, such as:
class FirstViewController: UIViewController, SendDataDelegate
In the second view controller, create a delegate a variable for that protocol, such as:
weak var delegate: SendDataDelegate?
and then you catch the back action and inside it you call your custom protocol function, such as:
self.delegate?.sendData(arrayToSend)
In the first viewController, in the prepare for segue function just set the delegate like
vc.delegate = self

Simple data transfer within viewcontroller without transition

So i am new to app development and i am trying to set up a very simple delegation/protocol pattern. I have been searching and trying different tutorials but can't seem to find anything that works and am getting in such a muddle. Please can somebody help. I will break i down so that its really clear as to what i need -
I have two view controllers, 'DetailedVC' and 'SelectionsVC'.
DetailedVC has a variable called -
var sendingData = (choice: "", choiceValue:0.0)
and
UIbutton buttonSelectTapped
SelectionsVC has a variable called -
var recievedData = (choice: "", choiceValue:0.0)
And all i want to do is send the data from the variable 'sendingData' in DetailedVC when the button (buttonSelectTapped) is tapped to the SelectionsVC and store it in the variable 'recievedData'. I do not want the VC to transition from one to the other or anything to be sent back, only to send the data to the other VC.
Then when the user views that controller 'SelectionsVC' at whatever stage, the data will be called in the viewDidLoad when loading that controller.
Use NSUserDefault to pass data between viewcontroller if you do not want the VC to transition from one to the other or anything to be sent back, only to send the data to the other VC.
DetailedVC Code
func viewDidLoad() {
NSUserDefaults.standardUserDefaults().removeObjectForKey("selectedData")
}
func didTapButtonSelectTapped() {
NSUserDefaults.standardUserDefaults().setDouble(sendingData , forKey: "selectedData")
}
SelectionsVC code
func viewDidLoad() {
if(NSUserDefaults.standardUserDefaults().doubleForKey("selectedData")) {
recievedData = NSUserDefaults.standardUserDefaults().doubleForKey("selectedData")
}
}
But as your question title describe their is no use of protocol/delegate in above code.
Passing Data on transition from viewcontroller :
DetailedVC Code
func didTapButtonSelectTapped() {
let vc = SelectionsVC()
vc.recievedData = sendingData
self.presentViewController(vc, animated: true, completion: nil)
}

Inter-thread inter-object communication in Swift 3 with Cocoa

My program consists of three relatively-distinct areas: listening on a network for new state, performing network actions, and updating the UI. So respectively I want three classes: StateListener, ActionSender, and ViewController, each chugging along on separate threads.
Would that it were so simple -- the three need to interact. Some states discovered by the StateListener require Actions to be sent by the ActionSender or the UI to be updated by the ViewController. Some responses to Actions require the UI to be updated by the ViewController. Some UI actions require Actions to be performed by the ActionSender.
Currently I do something like this (pseudocode):
/* ViewController.swift */
class ViewController : blah
{
//...
func buttonPressed()
{
// ?! Need to do an action here but I can't
// because actionSender is initialised below...
}
func viewDidLoad()
{
let actionSender = ActionSender(m_view: self)
let actionQueue = OperationQueue()
let stateListener = StateListener(m_view: self,
m_actionSender: actionSender,
m_actionQueue: actionQueue)
let stateQueue = OperationQueue()
stateQueue.addOperation(stateQueue.listen())
}
}
/* StateListener.swift */
class StateListener
{
// ...
func listen()
{
while true
{
var state = waitForNewState()
if shouldActOn(state)
{
m_actionQueue.addOperation(m_actionSender.act())
}
}
}
}
/* ActionSender.swift */
class ActionSender
{
// ...
func act()
{
var reply = sendAction()
OperationQueue.main.addOperation(m_view.m_textBox.append(reply))
}
}
This is fairly hellish and doesn't even do what I want it to do, because I can't have the ViewController perform actions (ActionSender's require a ViewController reference to update the view after the action, but I tried initialising the ActionSender within ViewController.init and I got bizarre errors to do with a Code.init that I hadn't implemented...). I want to get above ViewController and initialise all these OperationQueues and objects wherever ViewController gets initialised, but I can't find where that is...
What I've done above is basically object-reference injection of each object and OperationQueue. I know there are other ways of doing this (a hierarchy of callbacks, NSNotifications) but I'm unsure of which is best.
My question is in two parts:
What is the best (i.e., fastest, easiest to implement and maintain, most idiomatic in Swift) way to get the inter-object and inter-thread communication I desire?
I currently get things going from ViewController's viewDidLoad function, which seems awful (and means I can't get a 'higher-up' perspective of the ViewController. Where should this stuff go? AppDelegate advertises itself as the 'program startup' area, but I can't access the ViewController from there... XCode seems to have hidden the startup of my app from me!
I really appreciate your responses!
This post greatly helped with question 2 and the problem of passing self as a parameter to a data member in an initialiser : http://blog.scottlogic.com/2014/11/20/swift-initialisation.html
Specifically, I can use this pattern:
class Foo : blah
{
var m_bar : Bar!
init() {
// notice I get away with not initialising m_bar
}
func viewDidLoad {
m_bar = Bar(m_foo: self)
m_bar.doYourThing()
}
}
The blog prefers the following, which I feel I should add here out of gratitude to the author, though I prefer the above.
class Foo : blah
{
lazy var m_bar : Bar = {
return Bar(m_foo: self) // notice I can pass in self
}
init() {
// notice I get away with not initialising m_bar
}
func viewDidLoad {
m_bar.doYourThing()
}
}

Core Data stack implementation for iCloud sync in a UITabBarController app (Swift 1.2)

I've spent the last 4 days trying to implement a proper Core Data stack with iCloud sync for my Swift 1.2 app, but I can really use some help.
Before, I was using a global Managed Context accessed from everywhere in the app; knowing that it was a bad implementation, now that I'm adding iCloud sync I decided to get rid of it, even though the app was working fine.
So far, I've implemented a new, working Core Data stack with decent - but not perfect - cloud sync between devices.
Now I face two issues:
Sometimes, a few objects don't sync.
Given the particular structure of my app, which I'll explain in a moment, I have no idea how and where in my code I should handle the notifications that Core Data sends when the user logs in or out of iCloud.
But, before tackling those problems, I'd really appreciate - if appropriate - some validation of the work I've done so far and it is mainly for some confirmations that I'm writing this: since I've already spent a lot of time changing my Core Data stack, before going forward I'd like to know if I'm propagating the context properly (the structure of my app doesn't conform to any tutorial I found online, so I had to improvise a bit), or if I made some basic mistakes that will compromise reliable syncing or the future development.
My app is structured as follow:
UITabBarViewController as initial ViewController
1st tab: UIViewController (shown when the app starts)
2nd tab: a UITableViewController embedded in a UINavigationController
3rd tab: another UITableViewController embedded in another UINavigationController
I have a CoreDataStack.swift class with the following code:
import CoreData
#objc class CoreDataStack : Printable {
let context : NSManagedObjectContext
let psc : NSPersistentStoreCoordinator
let model : NSManagedObjectModel
let store : NSPersistentStore?
var description : String {
return "context: \(context)\n" + "model: \(model)"
}
var applicationDocumentsDirectory : NSURL = {
let fileManager = NSFileManager.defaultManager()
let urls = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask) as! [NSURL]
return urls[0]
}()
init() {
let modelURL = NSBundle.mainBundle().URLForResource("MyDataModel", withExtension:"momd")
model = NSManagedObjectModel(contentsOfURL: modelURL!)!
psc = NSPersistentStoreCoordinator(managedObjectModel: model)
context = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.MainQueueConcurrencyType)
context.persistentStoreCoordinator = psc
let documentsURL = applicationDocumentsDirectory
let storeURL = documentsURL.URLByAppendingPathComponent("MyApp.sqlite")
let options = [NSPersistentStoreUbiquitousContentNameKey: "MyApp", NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true]
var error: NSError? = nil
var failureReason = "There was an error creating or loading the application's saved data."
store = psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: options, error:&error)
if store == nil {
let dict = NSMutableDictionary()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
dict[NSLocalizedFailureReasonErrorKey] = failureReason
dict[NSUnderlyingErrorKey] = error
error = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict as [NSObject : AnyObject])
println("Error adding persistent store: \(error), \(error!.userInfo)")
abort()
}
}
func saveContext() {
var error: NSError? = nil
if context.hasChanges && !context.save(&error) {
println("Could not save: \(error), \(error!.userInfo)")
}
}
var updateContextWithUbiquitousContentUpdates: Bool = false {
willSet {
ubiquitousChangesObserver = newValue ? NSNotificationCenter.defaultCenter() : nil
}
}
private var ubiquitousChangesObserver : NSNotificationCenter? {
didSet {
oldValue?.removeObserver(self, name: NSPersistentStoreDidImportUbiquitousContentChangesNotification, object: psc)
ubiquitousChangesObserver?.addObserver(self, selector: "persistentStoreDidImportUbiquitousContentChanges:", name: NSPersistentStoreDidImportUbiquitousContentChangesNotification, object: psc)
}
}
func persistentStoreDidImportUbiquitousContentChanges(notification: NSNotification) {
println("Merging ubiquitous content changes")
context.performBlock {
self.context.mergeChangesFromContextDidSaveNotification(notification)
}
}
}
In my AppDelegate.swift I added the following code just under var window: UIWindow?:
lazy var coreDataStack = CoreDataStack()
coreDataStack.updateContextWithUbiquitousContentUpdates = true
// The following code is the way I found to propagate the managed context of the stack instantiated above in all the ViewControllers of the UITabBarController, including those embedded in the two NavigationControllers;
// since in the future I'll probably need some flexibility in term of adding / rearranging the VCs in the TabBar, I kind of like this way to pass around the context.
// I could have also passed the context to the CustomTabBarViewController and from there do the same thing, but I figured I could just pass the context from AppDelegate, since I already can access all the ViewControllers from here with the following code.
var tabBarController = self.window!.rootViewController as! CustomTabBarViewController
for eachViewController in tabBarController.viewControllers! {
if eachViewController.isKindOfClass(CustomViewController){
(eachViewController as! CustomViewController).passedManagedContext = coreDataStack.context // Context is passed to the VC of 1st tab
}
if eachViewController.isKindOfClass(UINavigationController){
var firstNavController = tabBarController.viewControllers![1] as! UINavigationController
for tvc in firstNavController.viewControllers! {
if tvc.isKindOfClass(FirstCustomTableViewController) {
(tvc as! FirstCustomTableViewController).passedManagedContext = coreDataStack.context // Context is passed to the TableVC inside the NavigationController in tab 2
}
}
var secondNavController = tabBarController.viewControllers![2] as! UINavigationController
for tvc in secondNavController.viewControllers! {
if tvc.isKindOfClass(SecondCustomTableViewController) {
(tvc as! SecondCustomTableViewController).passedManagedContext = coreDataStack.context // Context is passed to the TableVC inside the NavigationController in tab 3
}
}
}
}
// Of course, in applicationDidEnterBackground: and applicationWillTerminate: I save the context; obviously, I also save the context, when appropriate, from the other ViewControllers.
With this structure in place, I instantiate my stack in AppDelegate and from there I propagate it to the 3 elements of the TabBar; from those, I again propagate the context to every other ViewController I present. I logged to the console the context everywhere and I can confirm that it is always the same.
As a matter of fact, the app with this code works.
I can't say it is perfect because, as I said, sometimes a few objects don't sync, but I suspect the cause of those objects not syncing is another (briefly, I have 2 NSManagedObject subclasses; the objects of subclass1 have an object of subclass2 as property; if I create a new subclass1 object using an existing subclass2 object as property, sync is fine; if I also create a new subclass2 object, save it and immediately set it as property of subclass1, sometimes the subclass2 object doesn't sync on the other device, while the subclass1 does and then misses that property... I can work on that later).
Before digging into this sync issue, I'd really love to know if the work I've done so far with the stack makes sense, or if it is horrible and needs to be canned.
Then, if all the code above is not horrible and if the reason of the occasional missed sync of objects would turn out to be the one I suspect, comes the other issue, and it is a big one: where do I put the code to handle the notifications that occurr when the user logs in or out from iCloud (NSPersistentStoreCoordinatorStoresWillChangeNotification and NSPersistentStoreCoordinatorStoresDidChangeNotification)?
I tried to put methods I've written (without actual functionality, at the moment I only log something to the console to know that I got there) based on Core Data by Tutorials book in both my AppDelegate and my CoreDataStack class, but in both cases when I log in or out from iCloud while the app is running, the app crashes without a single line in the console, so I have no idea of the issue.
Maybe I should put the methods to handle these notifications in all the ViewControllers, since the fetch requests happen there and UI is updated from those classes, but I'm not passing the entire coreDataStack objects around, only the context... so I'm missing something. Should I pass the entire stack, not only the context? Is it okay to handle those notifications from my CoreDataStack, or should I do it from AppDelegate?
Any help would really be appreciated...
Thanks in advance and, please, excuse if my question is not clear (I'm quite a beginner and english is not my main language...).
Also, thank you for your time reading this long question!
#cdf1982
I think the problem is that iCloud + CD never worked properly. It's not a developer code issue, the problem is the Apple implementation of iCloud + CD that simply fails.