Update every day (my code working?) - ios

I want to update the the code inside viewDidLoad() every new day - even if the app is not running - closed or in background.
let Date = NSDate()
let Calendar = NSCalendar.currentCalendar()
var Days = 20
var Hours = 5
var Difference = 0
override func viewDidLoad() {
let FireDate = userCalendar.components(NSCalendarUnit.Hour, fromDate: Date)
if (FireDate.hour == 7) {
days--
Difference = (Days / Hours)
}
}
This code does not work how I want it to.

If that code is run at 7 then the check will succeed. That is, 7 on the device it's running on, considering the users timezone settings.
This code won't automatically run each day. You don't show where the code is but nothing will automatically run an iOS app at a set time each day. Possibly you could send a silent push as a trigger but you should really plan for the app to work when the user opens it instead of autonomously. Any time sensitive offline processing should be done by a server...
You could also look at using func applicationSignificantTimeChange(_ application: UIApplication) so that the application is told when the day has changed, in combination with your general processing in application:didFinishLaunchingWithOptions:.

viewDidLoad is called once and once only: When your view controller gets loaded. Trying to call this multiple times is going to get you into trouble.
Usually you set up some things in viewDidLoad (things that should be set up exactly once), and others in viewDidAppear or viewWillAppear (things that might be set up in different ways if your view appears multiple times).
Create a new method that does your setup that would usually be in viewDidAppear or viewWillAppear, making sure there are no problems if this code is called multiple times. Change viewDidAppear or viewWillAppear to call this method. Then in viewDidLoad create a timer that will call this at the right time, and in dealloc invalidate the timer.
If your app is in the background, even if the view is not loaded, there is no need for this to happen. Nobody will notice anyway. The timer will be called immediately when your app comes to the foreground (if it was due to be called), and if your app was closed, viewDidAppear is going to be called.

Related

how to tell when the device screen is locked or unlocked in swift

Essentially I have a timer in my app and I need it to keep running if the phone or device gets locked while the timer is active. After a whole lot of searching I found that's not really possible, or well it is, but doing so could violate apple developer guidelines and could get the app removed from the store.
So I thought I would get a little clever and create a property in my AppDelegate of Int64 for "timerStartedAt" which is just a Date().millisecondsSince1970 (custom extension I have)...
extension Date {
var millisecondsSince1970: Int64 {
return Int64((self.timeIntervalSince1970 * 1000.0).rounded())
}
init(milliseconds: Int64) {
self = Date(timeIntervalSince1970: TimeInterval(milliseconds) / 1000)
}
}
Then in the "viewWillAppear" of my timer view controller I check that property and set the time has been running of now - that time. However, viewWillAppear doesn't get called when the screen is unlocked. It does get called if the user switches pages in the application, but not when the screen locks and then unlocks.
So I am trying to find a way to call a method in my view controller when the device gets unlocked. Is that even possible?
As the device is being locked, if your app was frontmost, then you'll get applicationWillResignActive(_:) in the app delegate, and the corresponding notification if you register for it.
As the device is being unlocked, if your app was frontmost, then it will be active and frontmost again and you'll get applicationDidBecomeActive in the app delegate, and the corresponding notification if you register for it.
(If your app was not frontmost, you have no way to detect that anything is happening, but that's okay because there is no "you" — the app is not running.)
That is sufficient to let you write a timer that "keeps counting" in the background by looking at the different between the time it started (or the time we deactivated) and the time when we are activated. So the timer effectively "runs" in the background.

AdMob Refresh Requests

I have several questions that I'm confused with:
Who is best responsible for ad refreshing? After I load the request bannerView.load(GADRequest()) is it best practice to let the developer or Google monitor it?
Should the refresh times be as little as 30 seconds or a minimum of 60 seconds? It seems odd that they recommend 60 seconds but give you the option to choose 30 seconds. I've read on different threads that you can get penalized for all sorts of things and your AdMob account will get closed no questions asked. If that's the case if you pick 30 seconds inside the console and they recommend 60, if they feel anything is "funny" they can terminate your account based on that alone. It's a very oblique process.
It says If your app is automatically refreshing ads, make sure ad requests are not made when the screen is off. If the answer to my first question is to let Google monitor/automatically refresh (without using my code below) how would Google know whether the screen is off or not? For example, the ad is shown in viewControllerA but the user presses a button and viewControllerB is pushed on. Since viewControllerA is still on the stack the ad will constantly get refreshed, I don't see how Google would know the screen is off until viewControllerA is deallocated. Also, if the user went to the background how would Google know? I'm assuming off means the user can no longer see the screen which means they can't see the ad so there's no need to refresh because they either switched tabs, pushed on another VC, or went to the background (all cases handled in the below code)
Where is the link to the refresh console page? Before you could go to https://apps.admob.com > Monetize and you would end up on the page with the refresh options. When I log in I no longer see a Monetize option but get a sidebar with a bunch of icons. None of the icons bring me to the refresh page. FYI I just started using AdMob yesterday and I only saw the refresh page in videos and online tutorials so I never used it before.
It says here (emphasis mine):
Refreshing ads
We recommend that you have ads persist for 60 seconds
or longer, depending on the functionality of your app. Our internal
tests have shown that this ensures users have enough time to engage
with ads, providing the best performance for both advertisers and
publishers. Furthermore, these tests have shown that refreshing ads
more often can hurt fill rate for our publishers.
If your app is automatically refreshing ads, make sure ad requests are
not made when the screen is off. Also, if users navigate to and from
pages with ads in an app over a short period of time, a new ad request
should not be made sooner than the recommended 60 second rate.
But it also says here:
Banner ads
We recommend using the Google-optimized automatic refresh
rate. The optimized rate is calculated using AdMob historical data to
ensure the ads shown in your ad units are being refreshed at the best
rate for banner ads.
You may also set a custom refresh rate of 30-120 seconds or disable
refresh rate completely.
Here's how I would manage the requests myself every 30 secs taking into account when the user goes to the background, switches tabs, or pushes on another VC. If I don't do all of this and left it up to them ("We recommend using the Google-optimized automatic refresh rate"), how would Google know the screen is off or not?
var timer: Timer?
override viewDidLoad() {
super.viewDidLoad()
// instantiate bannerView ...
NotificationCenter.default.addObserver(self, selector: #selector(startAdRefreshTimer), name: UIApplication.willEnterForegroundNotification, object: nil)
// *** the screen is OFF
NotificationCenter.default.addObserver(self, selector: #selector(stopAdRefreshTimer), name: UIApplication.willResignActiveNotification, object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
startAdRefreshTimer()
}
// *** the screen is OFF
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
stopAdRefreshTimer()
}
#objc func startAdRefreshTimer() {
timer?.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: 30,
repeats: true,
block: { [weak self](timer) in
self?.refreshAd()
})
}
func refreshAd() {
bannerView.load(GADRequest())
}
#objc func stopAdRefreshTimer() {
if timer != nil {
timer?.invalidate()
timer = nil
}
}
// this really isn't necessary because of what's in viewWillDisappear but I added it anyway
#objc func buttonToPushNextVCTapped() {
let nextVC = NextVC()
navigationController?.pushViewController(viewController: nextVC, animated: true, completion: { [weak self] in
// *** the screen is OFF
self?.stopAdRefreshTimer()
})
}
This isn't the perfect solution because if the user switched tabs back and forth quickly and repeatedly the requests would happen much sooner then 30 seconds which would probably get your account banned. This is just for example purposes.
Answer to my 1st, 2nd, and 3rd question:
1st this is my setup, I have the bannerView inside a headerView which seems to be totally fine. I'm also using a TabBarController. Btw the vc the collectionView is in handles the bannerView and load(GADRequest()). The headerView only displays the ad so I don't have to worry about it constantly getting called as the user scrolls and the headerView is recycled. You can read the link in my set up for more details.
2nd AdMob's BannerView has a delegate method that gets called when a new ad is received:
MyController: GADBannerViewDelegate {
/// Tells the delegate an ad request loaded an ad.
func adViewDidReceiveAd(_ bannerView: GADBannerView) {
print("adViewDidReceiveAd")
}
}
3rd. I placed a break point on that print statement to see what happens and here are the results.
When I left the headerView visible (didn't scroll it off of the scene) the delegate method fired every 60 secs. I could be off by a few seconds because I looked at the time on my computer and didn't use a timer. Anyway pretty much every 60 secs the break point was hit and the test ad was changed.
As soon as I scrolled the headerView off of the scene and waited the breakpoint never got hit which means that the delegate method was never called. I waited 10 minutes and nothing happened -no ads were served.
As soon as I scrolled the headerView back on scene, I waited a several seconds and the breakpoint was eventually hit which means the delegate was called. Like in the first scenario it got hit every 60 seconds after that.
I switched to a different tab, left for a few minutes and the same exact thing in #2 happened, break point wasn't hit because the delegate wasn't called.
As soon as I came back to the tab with the headerView, the same thing in #3 happened, breakpoint was hit every 60secs because the delegate was called.
I went to the background and like #2 again the break point wasn't hit because the delegate wasn't called.
As soon as I came back from the background like #3 the breakpoint was hit every 60secs because the delegate was called.
My conclusion is that the bannerView DEFINITELY knows when it's not present on the scene. Wether a tab is changed, the app goes to the background (probably uses a notification), and most mysteriously it even knows when it's a subView of another view and that view is scrolled on/off the scene.
I'm in the Bronx, NY, it's 8am (I started testing around 7am) and the ad changed basically every 60 seconds. Maybe in other parts of the world, different times of the day, or different times of the year the duration might be longer or shorter?
Knowing what I know now I think that it's best that I let Google monitor the load(GADRequest()) and change the ads when it's ready. An ad changing every 60s is fine by me. If the scene can hold the user's attention for longer than a minute that's 2 ads served. It's more beneficial because I can spend more time trying to keep their attention than focus on serving them an ad.
As far as the code from my question to handle all the off cases it seems the bannerView is capable of handling all of that on it's own. It's less code for me to maintain and deal with and more importantly it's 1 less reason to worry about getting banned for handling something improperly.
In summary (this works best for me)
let Google handle/monitor the bannerView.load(GADRequest()) to serve news ads
don't customize the refresh, it seems to change every 60 secs on it's own
when using a bannerView (and possibly letting Google monitor the load(GADRequest())) ads aren't requested when the screen is off so no need to waste time worrying about it
Answer to my 4th question:
The Monetization page to open App Refresh doesn't seem to exist anymore. After you login into AdMob follow these 5 steps (a new screen will appear for steps 3, 4, and 5 after your click your blue ad unit name in step 2).
I think you will find many confusing things in AdMob banners. To determine what was going on with network calls for ads, I had to use Charles proxy to monitor the network stream. I could not find any evidence that the banner, on any view that wasn't on the top of the stack, was continuing to request ads. But, I didn't dig into the framework to find out how/why because I was trying to solve another issue. I would recommend just allowing Admob to serve the ads with their timing using their stock code stubs:
bannerView = GADBannerView(adSize: kGADAdSizeBanner)
addBannerViewToView(bannerView)
I also added some debug code to the delegate function adViewDidReceiveAd(_ bannerView: GADBannerView) to make sure my app was not serving ads when the view was not on top of the stack. Couldn't find any evidence.
Last, would highly recommend never testing with production ads and to follow their best practices relentlessly on ad placement in your VCs as to not touch or otherwise be directly adjacent to other actionable objects. Very possible you could be put in AdMob jail and not know it for weeks or longer (only that ads will stop serving and you will get the dreaded code 1 "No Ad to Show" debug message).

Run Code Every Time App Opens - Better Way?

I need code to run every time the app opens, including if it was only suspended (user hit home button, then returned to app).
I already know that viewDidLoad only loads on the initial VC load, so that doesn't do what I need. And viewWillAppear / viewDidAppear don't do it either. This SO thread has an answer but it's from six years ago and I don't love the answer. Essentially, it says create an observer. However, that seems like a waste of resources and observers create those reference loops that keep things in memory.
If no one gives me a better solution I might try "applicationDidBecomeActive" in the AppDelegate, but I try not to load my appDelegate up with ViewController code.
My question is, in the six years since this SO thread was answered, do Swift/iOS10 now allow for a cleaner solution?
If you don't want to use NSNotificationCenter Than make global object of your Viewcontroller in AppDelegate
and put code in applicationDidBecomeActive with your viewController object
Because applicationDidBecomeActive calls everyTime when you start your application and when you press home button and came from background so you don't need to write your code in viewWillAppear.
As far as I can tell after additional research, there's no new way since that post six years ago. However, the applicationDidBecomeActive approach does work well:
func applicationDidBecomeActive(_ application: UIApplication) {
guard let rvc = self.window?.rootViewController as? firstVCName else { return }
rvc.nameOfMethodYouWantToCall()
}
The above gets a reference to the existing VC (instead of creating a whole new instance). Then it calls the method, without having to load up the appDelegate with code. Very simple and efficient.
I tested this and it's exactly what I needed, code that runs every time the app is opened. It's very consistent.

Confusion about applicationDidBecomeActive and applicationWillEnterForeground

I have some confusion about the above two app delegation method: I read this link, and it shows that the applicationWillEnterForeground will gets called before applicationDIdBecomeActive gets called. I am not sure about the before means.
If I have a social app: in my applicationWillEnterForeground function, I will check if the there is a current user session; in my applicationDIdBecomeActive, I will reload the content on timelineViewController: Thus, if there is no current user session, the timeline cannot be shown. Suppose a user enter the app from background with no current user session, the applicationWillEnterForeground will show a login page to indicate that there is no user, however, will the next-get-called applicationDIdBecomeActive return back to timeline which are not supposed to show?
If I don't want to modify code in my other viewcontroller, or check user session in applicationDIdBecomeActive. Is there any code I can add in applicationWillEnterForeground function to prevent applicationDIdBecomeActive function running?
Another question: I notice that for some app like Facebook, if I press the home button, turning it to background, but immediately turn it back to screen, the app doesn't show a lot of changes; however, if I let it stay in background for like an hour, it will "freeze" for a while (looks like it is refreshing) when I turn it back to foreground. How does the delegation method design to realize that? Is the system decides which delegation method(the above two) to call basing on the time that the app stayed in background?
BTW, I am using swift as the main programming language of iOS
You can't prevent applicationDidBecomeActive from getting called.
You could set an instance variable in applicationWillEnterForeground which is read later to determine the flow your application uses.
To refresh or initiate VC your app after certain interval like Facebook you can do as follows:
In applicationDidEnterBackground
let app = UserDefaults.standard
let date = Date()
app.set(date, forKey: "activeDate")
In applicationWillEnterForeground
let user = UserDefaults.standard
var interval = 0
if let date = user.object(forKey: "activeDate") as? Date {
interval = Int(Date().timeIntervalSince(date)); print ( "AppDeligate: Inactive interval: \(interval)")
}
if interval > 7200 { // 2hr
// Initiate VC OR Refresh data here
}
if you check time interval in applicationDidBecomeActive, then app get initiate or refresh data when yo view notification from swiping down or swipe up for control center. so applicationWillEnterForeground is the best place to do so.

Does delegate have function which is executed every time app comes from background

I have application with lot of view controllers and on every time when app comes from background I have to make some request to server and reinitialise some global variables. At the moment I am doing this by repeating code in every view controller in didViewLoad but I wonder is there way to this in delegate to avoid repeating on 10 places ? ( I check didFinishLuanchingWithOptions in delegate but it is called only first time not when app comes from background ).
I think you want to look at these two:
applicationDidBecomeActive
And
applicationWillEnterForeground
Check out the documentation for more details.

Resources