Swift Disable Navigation Controller buttons Until viewDidLoad is completed - swift

VC1 is my homepage in the project and I can go to 5 different view controllers from VC1. In VC1 viewDidLoad I am downloading data from my Firestore database and I need them all to be loaded to the phone before allowing the user to interact with the buttons in the navigation bar. Here is how my VC1 look like (3 buttons bottom, 2 buttons top):
But currently user can click a button in VC1 while the data from viewDidLoad is not fully loaded to the phone and gets them to go from VC1 to VC2 (if they are fast enough, they can accomplish this).
How can I prevent a user to interact with the buttons in the nav bar unless all the functions in the viewDidLoad is completed?
override func viewDidLoad() {
super.viewDidLoad()
func1()
func2()
func3()
}
Not sure if this is related but in my AppDelegate this is how I define my rootViewController:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
let storyboard = UIStoryboard(name: "Main", bundle: .main)
window?.rootViewController = storyboard.instantiateViewController(withIdentifier: "TabBarController")
}
And this is what I have in main Storyboard:
and this is my home storyboard:

The user cannot touch any button while viewDidLoad is running. Or to be more precise: The buttons will not react upon events while those viewDid..., viewWill... functions are running. This is because all those methods are executed in the main thread and will block any user interaction.
I assume your "problem" is caused because func1(), 2 or 3 are calling network services, which typically run in a arbitrary worker thread. Therefore, func1 returns before all data has been fetched, therefore also viewDidLoad returns too early for you.
Since you should never run the network calls in the main thread (and Firestore calls won't do so either), you need to do some extra work, for example:
in viewDidLoad, disable all buttons you don't want the user to interact with
in the Firestore handlers, check if all data has been received, and then enable the buttons again (in the main thread, you might need to call DispatchQueue.main.async for this)
if you have multiple background jobs running and you need to wait for all of them, you could use DispatchGroup

Related

Black screen with viewDidLoad after if else statement

I want my app to check at start conditionaly if a variable is correct or not. Based on that it should either go to an intro screen (where he can select a variable in my case select a team) or it should start the main view. After searching i found this code and edited it. But there still seems to be problems. First of all I dont have two identifier. The Intro has one but not the main view. My main View is called WeatherViewController and the Intro screen is called FirstScreenViewController. I also added a picture of my Main.storyboard.
I also googled a lot about conditional UINavigationController but I can only understand with a video and did not found a video about it.
I tried to use the code from here.
var id = hello ? "goToIntro" : "???"
self.window = UIWindow(frame: UIScreen.main.bounds)
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let WeatherViewController: UIViewController = mainStoryboard.FirstScreenViewController(withIdentifier: WVC has no identifier??) as UIViewController
self.window?.rootViewController = WeatherViewController
self.window?.makeKeyAndVisible()
if hello {
self.performSegue(withIdentifier: "goToIntro", sender: self)
} else {
/here nothing should happen. It should open the Main View
self.performSegue(withIdentifier: "???", sender: self)
}
Note: This answer was referring to the original question (before any edits) that was attempting a segue inside loadView().
You're supposed to manually create your controller's view in loadView. You're currently doing nothing, hence the black screen. Furthermore, if you are using a Storyboard or a xib for managing this controller you shouldn't be overriding loadView at all.
Having said that it might be a better idea to move this branching logic a step back, to "something" (a container controller like a UINavigationController or a custom one, or even directly setting the root controller of your window if it makes sense) that will present (or set) A or B based on some condition and thus avoid to load this controller altogether (have in mind that in your code, the controller is initialized, it will be part of the hierarchy and all the lifecycle methods will be called even if you segued directly to another one)
Finally, if you decide for some reason to override loadView you don't have to call viewDidLoad yourself, the system will call this method.

how to refetch data that has already been fetched

My tableView uses prefetching to cache images and works perfectly when I start the app but once I close out of the app, sending it to the background (not fully shutting it down) and click the app again, the cached images are gone but because the tableView already prefetched these images prior to closing, the prefetch method is not being called on the indexPaths that were previously loaded.
Im looking for a method or logic I can code that would call the prefetching method again based off the current indexPath allowing the indexPaths that were previously loaded and then lost to be reloaded. any help would be great?
When your app enters from background to foreground, in appDelegate file inside method
func applicationDidBecomeActive(_ application: UIApplication) {
NotificationCenter.default.post(Notification.init(name: Notification.Name(rawValue: "appDidEnterForeground")))
}
Now you can set up listeners in your UIViewControllers setup code :
override func viewDidLoad() {
NotificationCenter.default.addObserver(self,
selector: #selector(self.YOUR_METHOD_NAME),
name: NSNotification.Name(rawValue: "appDidEnterForeground"),
object: nil)
}
inside your UIViewController create your custom method and check if thats get called implement your logic inside that method.
#objc func YOUR_METHOD_NAME() {
print("notification method called")
}

Avoid losing notifications sent by a widget because of a spash screen or wrong controller active

I have an widget that calls its corresponding app through NSURL and extensionContext to activate a specific action in the app.
In AppDelegate's application:openURL:options: method I have:
func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {
if let path = url.path{
if path.containsString("action"){
NSNotificationCenter.defaultCenter().postNotificationName(MyViewController.purchasmyActionKey, object: nil)
}
}
return true
}
When the app is open, and has MyViewController active, the action is executed perfectly. But, if I am on another view controller in the app or the app is closed, the action is not executed.
Can somebody set me on the right track?
NB: My main controller is a UITabBarController with various child view controllers. Some are UINavigationControllers (which contain grid controllers) and the other one is a ListViewController.
The simplest option is to show your view controller which handles this as a modal over the tab controller. This is generally the least complex and the cleanest as the user can be easily returned to what they were doing before this interaction when they're done.
If you can't do that for some reason:
You need to designate some class the responsibility of ensuring that the correct view controller is shown and told to action the request when the notification is seen. That could be the app delegate directly, the tab bar controller or some other specific class that you create and provide with a reference to the tab controller.
It's job is to check the state of the tab controller and show the correct view controller if required, then tell that view controller to begin some action.
This class who owns this logic could be the one that observes your notification, or you could just pass the message directly as your app delegate will likely know the instance or be creating a new instance.

Come back to the tabBarController, swift

Currently on my viewController : Upload, my button send the data to my database only if all the information are filled out, and I come back to the preview view (table View) with :
self.navigationController?.popViewControllerAnimated(true)
I would like, if it is possible, to come back to my main view on the tabBarController. I tried many things, like directly on the storyboard with Present modally segue to "TabBar controller", but I come back to the TabBar without sending my data to the database and without checking in the information are filled out..
How can I do it?
Thanks!
UITabBarController has a property selectedIndex with which you can switch the selected tab. So on completion after dismissing the UploadViewController you can run:
self.tabBarController?.selectedIndex = 0 // Index to select
It would probably be best to create a delegate for your UploadViewController to fire a function to do all the work in your previewVC on API call completion.
(Super late response...in case someone has similar questions, presumably in later version of Swift, such as mine which is Swift 5, iOS 13.2). Steps:
Be sure to set an id for your UITabBarController storyboard, e.g. "TabBarViewController"
Next, add the following to an action that has already been connected to a button:
let ID_TABBAR = "TabBarViewCOntroller"
#IBAction func returnToTabbar(_ sender: Any) {
let tabBarController = self.storyboard?.instantiateViewController(identifier:ID_TABBAR) as! UITabBarController
self.navigationController?.pushViewController(tabBarController, animated:true)
}
Referenced from one of the responses from this post.
Update: In case your Tab Bar View Controller also happens to be the root view controller, the two lines of the code in the returnToTabbar method above can be:
self.dismiss(animated:true, completion:nil);
self.navigationController?.popViewController(animated:true);
(ref.: See answer here, for Swift4 but works just fine in Swift5)

prepareForSegue called after viewDidLoad in segue from ViewController to TableViewController

I am building an iOS 5 app with oauth integrated. My storyboard consists of a container NavigationController, with a rootViewController, and two viewControllers segueing from the rootView. When the app is launched, i perform a check to see if an access token is present, and direct the user accordingly.
// rootViewController.m viewDidAppear (i need to perform this check anytime the user is brought to the root view, say for e.g. he logs-out.)
if (accessToken) {
BOOL didAuth = [GTMOAuthViewControllerTouch authorizeFromKeychainForName:#"app name: service" authentication:accessToken
if (didAuth){
//perform segue to main User View (which is a TableViewController)
}
else{
//perform segue to sign-in controller, and direct the user to main view from there.
}
}
I have a couple of questions:
Is such a setup 'valid' as per Apple's Interface Guidelines?
I noticed that the prepareForSegue method in rootViewController gets called after the mainUserView's (which is a TableViewController) viewDidLoad. Is this standard behavior? I understand that this is the case for popovers, but for segues from a standard ViewController to a TableViewController?
Thanks!