how to refetch data that has already been fetched - swift

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")
}

Related

Swift: View position resets when app is minimized, then brough back to foreground

I have a UIView that starts off in a random position (set up through storyboard), then finds its actual position when viewDidLoad() is called, depending on the user preferences stored in UserDefaults.
The problem is when the app is minimized, and then brought back to the foreground, the position of that UIView resets to the initial random position.
How could I prevent the app from releasing that position determined at runtime? Or do I need to re-calculate that position through a function that is triggered when the app is brought back from being inactive?
I don't think there is much use to include code for this, but the position of the UIView (named 'contextUnderscoreLine') is set in viewDidLoad() with contextUnderscoreLine.center = savedCGPoint.
I think I found a solution for this, in case someone else was going through the same issue:
Add an observer in viewDidLoad() to detect when the application is 'active'. From documentation, my understanding is that this will get called once when the app is launched, and then every time the app comes back from being in the background. So the initial formatting of the problematic UIView can be done there instead of in viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
}
#objc func applicationDidBecomeActive(notification: NSNotification) {
// Application is back in the foreground
//determine the position of the UIView here, instead of in viewDidLoad()
}
Edit
For my specific case it was better to use UIApplication.willEnterForegroundNotification instead of UIApplication.didBecomeActiveNotification. That way the changes in UIView are done before the Application is back in the foreground, and therefore you avoid the noticeable UIView jitters as the changes are made in front of the user.

Swift Disable Navigation Controller buttons Until viewDidLoad is completed

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

Where is the right place to remove notification observers in Swift 2?

I have this code, taken from this answer: https://stackoverflow.com/a/29099066/406322
extension NSNotificationCenter {
func setObserver(observer: AnyObject, selector: Selector, name: String?, object: AnyObject?) {
NSNotificationCenter.defaultCenter().removeObserver(observer, name: name, object: object)
NSNotificationCenter.defaultCenter().addObserver(observer, selector: selector, name: name, object: object)
}
}
Now, in my view controller, I am setting my observers in viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
setObservers()
}
func setObservers() {
NSNotificationCenter.defaultCenter().setObserver(self, selector: #selector(BaseController.handleComment(_:)), name: "newComment", object: nil)
}
However, even with using this extension, where the observer is removed before getting added, each time I exit the view controller, and return to it, I get multiple notifications (one extra each time).
How is this possible?
If you need this setObserver extension, you are very likely doing something wrong. You should be able to balance your registration and removal easily. If you can't, your notification management is very likely too complicated or in the wrong place.
Typically the correct place to add observations is in viewWillAppear (or viewDidAppear, either is fine), and remove them in viewDidDisappear (or viewWillDisappear). This ensures that you do not receive notifications while you are offscreen, even if the view controller still exists (which is common).
If your view controller requires that it receive notifications while it is offscreen, then you have a design problem. View controllers should only manage onscreen views. If they're doing anything else, you have put too much of the model into the controller.
As #rmaddy notes, your specific problem is likely that you have two instances of this view controller. That's may be fine or it might be a mistake (it depends on how the view controller works). But if you balance adding and removing your registration when going on and offscreen, that part will be fine.

Swift, unable to reload chat window using JSQMessagesViewController

My app allows users to view another users info by tapping their avatar, at which they can block that user so that their message content is not visible.
What is the best way when returning to the chat view to clear the messages and reload them?
This would allow my blocking code to work on the fly. Currently it works when I dismiss the chat view and return but not when jump to another view and then back to the chat view.
Ive tried self.collectionView!.reloadData() but that does not do anything.
All you need is to implement
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.collectionView?.reloadData()
}
that is called when you come back from an already instantiated view.
Call the reloadData method after a delay.
self.performSelector(#selector(self.delayReload), withObject: nil, afterDelay: 0.1)
func delayReload() {
self.collectionView.reloadData()
}
Hope this will help you.
Sincerely,
Harri.

Application state and push notification

I have a server that successfully generates push notifications and sends them to devices. On the app side, if the user is already in the app or if the user enters the app from the notification I can easily 'find' the notification and run the necessary code to action the notification (app delegate gets invoked and the notification is stored in NSUserDefaults which I can then access throughout the app).
However, if the user has the app running in the background (for example, user was in the app, but then switched to another app), when the user reopens the app from the app icon, the app simply returns to the last state it was in and no code actually gets invoked (which is kind of what you expect).
What I am trying to figure out is how to either invoke AppDelegate again so that I can extract the notification before I return to the current state or how do I invoke some code so that I can intercept the notification.
Thoughts?
If I understood your question correctly, you want to be notified when coming out of background state. How the appdelegate method
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
Here is my solution.
On 'App Resume', In AppDelegate, I check the badge and store push if badge value is greater than 0:
func applicationDidBecomeActive(application: UIApplication) {
let installation = PFInstallation.currentInstallation()
if installation.badge != 0 {
NSUserDefaults.standardUserDefaults().setObject(true, forKey: "push")
NSNotificationCenter.defaultCenter().postNotificationName("indicatePush", object: nil)
installation.badge = 0
installation.saveEventually()
}}
The call to NSNotificationCenter, invokes a registered method in my base class, where I set the push variable to true
In the sub-class, on viewDidLoad I register two methods (you might only need one of them):
NSNotificationCenter.defaultCenter().addObserver(self, selector: "appplicationIsActive:", name: UIApplicationDidBecomeActiveNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "applicationEnteredForeground:", name: UIApplicationWillEnterForegroundNotification, object: nil)
Add the call to the 'pull from server' to one of the two methods. In my case:
func appplicationIsActive (notification: NSNotification){
refresh()
}
You're done!